18-用于大型程序的工具

来源:互联网 发布:淘宝助理导入csv失败 编辑:程序博客网 时间:2024/06/06 02:31

用于大型程序的工具

  • 大规模程序设计的要求:
    • 1.在独立开发的子系统协同处理错误的能力
    • 2.使用各种库进行协同开发的能力;
    • 3.对比较复杂的应用概念进行协同开发的能力;
    • 异常处理,命名空间和多重继承;
  • 异常

    • 1.通过抛出一条表达式来引发一个异常,被抛出的表达式的类型以及当前的调用链共同决定了了那段处理代码用来处理这些异常.被选中的处理类型是用在调用
      链中与抛出对象类型匹配的最近的处理代码.
    • 2.在执行一个throw语句时,throw后面的语句就不会被执行.相反程序的控制权从throw转移到与之匹配的catch模块.在catch中可能是同一个函数的局部catch,也可能是直接或者间接调用了发生异常的函数的另一个函数中,控制权从一处转移到另一处:
      • 1.沿着调用链的函数可能会提早推出;
      • 2.一旦程序开始执行异常处理代码,则沿着调用链创建的对象将会被销毁;
    • 栈展开:
      • 1.首先在当前的语句块中匹配try语句中的catch关键字;
      • 2.在本层语句块中没有查找到,就继续向外层语句块进行查找;
      • 3.如果在本层中没有找到,就继续向外层进行查找,
      • 4.退出当前主函数;
    • 如果一个异常没有被捕获,就会终止当前程序;
    • 在栈展开的过程中,位于调用链中上的语句可能会提前退出;程序在这些块中创建一些局部对象;块退出后局部对象将会被销毁,这条过程也是适用于占翟开的过
      程的;
    • 如果程序在栈展开的过程退出了某个块,编译器将会负责确保在这个块中创建的对象能被正确的销毁.如果局部对像是的类型是类类型,那么这个对象的析构函
      数将会被自动调用.与往常一样:编译器在销毁内置类型的对象时,不需要做任何事情;
    • 如果异常发生在构造函数中,需要确保已构造的成员可以被正确销毁;
    • 在栈展开的过程中,运行类类型的局部对象的析构函数.析构函数是自动执行的,所以不应该抛出异常的.如果在站炸开的过程中抛出了异常,这个异常没有被正
      常捕获到,析构函数释放资源就会截止;
    • 异常对象是一种特殊的对象,编译器使用异常表达式来对异常对象进行拷贝初始化,throw语句中的表达式必须拥有完全类型;

      • 如果这些表达式是类类型,那么相应的类必须有一个可访问的析构函数和一个可访问的拷贝或者移动构造函数;
      • 如果这个表达式是数组类型或者是函数类型,那么表达式将被转换为与之对应的指针类型;
      • 很多种情况下程序抛出的表达会死类型来自于某个继承体系,如果一条throw表达式解引用一个基类指针,但是这个指针是将指向的是派生类对象,那么
        抛出的对象将会被切除一部分,只有基类部分会被抛出;
      • 抛出指针要求在任何对应的处理代码存在的地方,指针所指向的对象都必须存在.
    • 捕获异常

      • 异常声明的类型决定所能捕获的异常类型,类型必须是完全类型,可以是左值引用,但是不能够是右值引用;
      • catch的参数是基类类型,那么我们可以使用其派生类类型的异常对象对其进行初始化,;
      • 如果catch的参数是非引用类型,那么异常对象将会被切掉一部分;
      • 如果catch的类型是基类类型的引用,那么该参数将按照常规方式绑定在异常对象上面;
      • 通常情况下,如果catch接受的异常与某个继承体系有关,那么最好将该catch的参数定义为引用类型;
      • 在使用catch进行查找的过程中未必是最佳匹配,一定是第一个与异常进行匹配的catch语句;
    • 异常参数的匹配规则:
      • 1.在形参和实参进行匹配的过程中,大多数类型转换都是不被允许的;
      • 2.允许从非常量到常量的类型转换;
      • 3.允许从派生类向基类的类型转换;
      • 4.数组转换为数组指针,函数被转换为函数指针;
      • 5.除此之外的类型转换,包括标准的算术类型转换和类类型转换在内,都不能够被使用;
    • 如果在多个catch语句的类型之间存在这继承关系,那么我们应该把继承链最底端的类放在前面,而将继承链最顶端的类放在后面;
    • 处理构造函数初始值异常的唯一的方法是将构造函数写成函数try语句块;
    • noexpect异常说明:
      • 编译器在知道某个函数不抛出异常之后,会对代码进行优化操作,通过提供noexcept指定某个函数是不用抛出异常的;
      • noexpect这个说明需要出现在函数的所有声明语句和定义语句中,否则一次都不出现;
      • noexcept需要出现在函数的尾置返回类型之前.
      • noexpect函数指针的声明和定义中,但是不能够在typedef或者类型别名中不能够出现noexpect.
      • 在成员函数中,noexpect说明符需要仅仅跟在const及引用限定符之后,但是需要在final,override或者虚函数=0之前;
      • 如果在声明了noexcept,却使用了抛出异常的语句,那么将不会执行栈展开;但是这种情况在程序编译时是不会出错的;
        • noexcept说明符是接受一个可选的实参的,该实参必须能够转换为bool类型,如果实参是true,那么函数不会抛出异常,如果实参是false,那么
          函数可能会抛出异常;
        • noexcept运算符是一个一元运算符,它的返回值类型是bool类型的右值常量表达式,用于表示给定的常量表达式是否会抛出异常.
      • noexcept有两层含义:
        • 1.noexcept在函数参数列表后面时,表示异常说明符;
        • 2.noexcept异常说明的bool实参出现时,表示一个运算符;
      • 函数指针以及指针所指的函数必须具有一致的异常说明.
      • 如果一个虚函数承诺了它不会抛出异常,那么后续派生出来的虚函数也必须做出相同的承诺;
      • 如果基类的虚函数允许抛出异常,那么派生类的对应函数既可以抛出异常,也可以不允许抛出异常;
  • 命名空间

    • 1.同名函数,变量,模板在同一个命名空间时,将会引发命名空间污染;
    • 2.每一个命名空间都是一个作用域;

      namespace cpluscplus_primer{}
    • 3.命名空间作用于后面没有分号;

    • 4.定义在某个命名空间中的名字可以被该命名空间内的其他成员直接访问;位于命名空间之外的代码则必须明确指出所用的名字是属于那个命名空间的;
    • 5.命名空间可以是不连续的;
      • 1.命名空间的一部分成员作用是定义类,以及声明作为类接口的函数以及对象,那么这些成员应该位于头文件里面,这些头文件将被包含在使用这些的头文
        件中.
      • 2.命名空间成员的定义应该位于另外的源文件中.
      • 3.在程序中,非内联函数,静态数据成员,变量等实体只能够被定义一次,命名空间中的名字也是满足这一要求的;
    • 6.内联命名空间的名字可以被外层命名空间直接使用,通过在命名空间前添加inline来实现;
    • 7.未命名的命名空间是指没有名字的命名空间.在未命名的命名空间里面定义的变量具有静态生命周期,在一次使用前被创建,直到程序结束时被销毁;
    • 8.一个未命名的命名空间在给定的文件内部不连续,不能够跨越多个文件;
    • 9.某些程序实体只能够被定义一次,包括内联函数,静态数据成员,变量;
    • 10.命名空间的一部分作用是定义类,以及声明作为接口的函数以及对象,则这些函数成员应该位于头文件里面,这些头文件将被包含在使用了这些成员的文件中;
    • 11.命名空间的定义部分则置于另外的源文件中;
      1. 全局作用域中定义的名字(包含所有类,函数以及命名空间之外定义的名字),就是定义在全局命名空间.
        • 1.全局命名空间是按照隐式的方式进行声明,并且在所有的程序中都存在.全局作用域中定义的名字被隐式的添加到全局命名空间里面;
        • 2.嵌套的命名空间同时也是一个嵌套的作用域,内层在嵌套外层作用域,内层的命名空间的名字将隐藏外层命名空间声明的同名成员;
    • 13.C++新标准引入了内联命名空间,内联命名空间中的名字可以被外层命名空间直接使用,内联命名空间可以在关键字namespace前加关键字inline;
      inline namespace FithEd;
    • 14.关键字inline必须出现在命名空间第一次定义的地方,后续打开命名空间的时候可以使用inline,也可以不写;
    • 15.未命名命名空间表示的是关键字namespace后面没有名字的一系列关键字;未命名的命名空间中定义变量具有静态生命周期;
    • 16.未命名的命名空间仅仅在特定文件内部有效,其作用范围不会横跨多个不同的文件.
    • 17.定义的未命名的命名空间中的名字可以直接使用,也不需要使用作用域限定符;
    • 命名空间可以通过别名的方式来使用namespace Qlib = cpluscplus_primer::QueryLib;
    • 一个命名空间可以具有好几个同义词或者别名,所有别名都与命名空间原来的名字等价;
    • using 声明和using 指示:
      • using声明:一次只能够引入命名空间的一个成员,作用范围从using声明开始到using声明所在的作用域结束为止,using声明语句可以出现在全
        局作用域,命名空间作用域以及类作用域中;
      • using指示:例如using namespace std,就是using指示;
      • using指示:可以出现在全局作用域,局部作用域和命名空间作用域中,但是不能够出现在类作用域;using指示是将命名空间的成员提升到包含命名
        空间本身和
        using指示的最近作用域的能力;</li>
        <li>头文件如果在顶层作用域含有
        using指示或者using声明,则会将名字注入到包含该头文件的文件中;头文件最多只能够在它的函数或者命名空间中使用
        using指示或者using`声明.
    • 类,命名空间与作用域:
      • 1.命名空间内部名字的查找按照从外依次查找每个外层作用域,依次从内层向外层作用域进行查找,直到查找过程终止;
      • 2.可以从函数的限定名称推断出查找名字时检查作用域的次序,限定名按照相反次序指出被查找的作用域;
      • 当我们给函数传递一个类类型对象时,除了常规的类类型检查之外,还会查找实参所属的命名空间,这个例外对于引用或者指针的调用同样有效;
      • 实参查找:如果应用程序中定义了一个标准库中已有的名字,那么就会按照下面情况来处理:
        • 1.根据一般的重载规则确定某次调用应该执行那个版本的函数;
        • 2.应用程序根本就不会执行函数的标准库版本;
      • 当类声明了一个友元时,该友元声明并没有使友元本身可见,但是一个未声明的类或函数如果第一次出现在友元声明中,那么就认为它是最近的外层命名空
        间的成员;
    • 命名空间和重载
      • 1.using声明或者using指示能够将某些函数添加到某些候选函数集;
      • 2.对于接受类类型实参的函数来说,其名字查找将在实参所属的命名空间中进行,对于候选函数集来说,将在实参类以及实参类的基类所属的命名空间搜寻
        候选函数,在命名空间里面所有和被调用函数同名的函数都会被添加到候选集;
      • 3.using声明语句声明的是一个名字,而不是一个函数;,所以如果存在同名函数,那么这个同名函数的所有版本都会被添加到当前作用域里面;
      • 4.一个using声明引入的函数将重载该声明语句所属作用域中已有的其他同名函数.如果using声明引入的名字和当前作用域已有的函数重名,那么将引
        发错误;
      • using指示和重载:
        • 1.using指示将命名空间的成员提升到外层作用域中,如果命名空间的某个函数与该命名空间所属作用域的函数同名,不会引发错误,而是将命名空间
          的函数加载到重载集合中;
        • 2.如果存在多个using指示,那么来自每个命名空间的名字都会成为候选函数集的一部分;
    • 多重继承和虚继承:
      • 1.多重继承表示的是从多个直接基类产生派生类的能力,多重继承的派生类继承了所有父类的属性;
      • 2.多重继承的派生类列表必须是已经被定义过的类,并且这些类不能够是final的;在每个给定的派生类列表中,同一个基类只能够出现一次.
      • 3.多重继承关系中,派生类的对象包含有每个基类的子对象;
      • 4.构造一个派生类对象将同时初始化他所有基类子对象.多重继承的派生类的构造函数初始值只能够初始化它的直接基类;
      • 5.派生类的构造函数初始值列表将每个实参分别传递给每个直接基类.其中基类的构造顺序与派生类列表中基类的出现顺序保持一致,而与派生类构造函数
        初始值列表中基类的顺序无关;
      • 6.在C++11新标准中,允许派生类从它的一个或几个基类中继承构造函数,但是从多个基类中继承了相同的构造函数,也就是形参列表完全相同,那么程序
        会出现错误;
      • 7.如果一个类从它的多个基类中继承了相同的构造函数,那么这个类必须为该构造函数定义它自己的版本;
      • 8.派生类的析构函数只负责清除派生类本身分配的资源,派生类的成员以及基类都是自动销毁的;合成的析构函数体为空;
      • 9.多重继承的派生类如果定义了自己的拷贝/赋值构造函数和赋值运算符,那么必须在完整的对象上执行拷贝,移动或赋值操作;
      • 10.只有当派生类使用的是合成版本的拷贝,移动或者是赋值成员时,才会自动对其基类部分进行这些操作.
      • 11.在合成的拷贝控制成员中,每个基类分别是用自己对象成员隐式的完成构造,赋值或销毁操作;
      • 类型转换和多个基类:
        • 1.在只有一个基类的情况下,派生类的指针或者引用可以自动转换成为一个可访问的基类指针或者引用;对于多个基类的情况,我们可以令某个可访问
          基类的指针或引用直接指向一个派生类对象;
      • 多重继承下的类作用域:
        • 1.在多重继承体系中,相同的查找过程在所有直接基类中同时进行,如果名字在多个基类中都被找到,那么改名字具有二义性;
        • 2.当一个类拥有多个基类时,有可能出现派生类从两个或更多基类继承了同名成员情况,此时,不加前缀限定符直接使用这个名字,将会引发二义性;
    • 虚继承
      • 1.派生类列表中,同一个基类只能够出现一次,但是派生类可以同时多次继承同一个基类.派生类可以通过两个直接基类间接继承同一个基类,也可以直接
        继承一个基类,然后再通过另一个基类再次继承这个基类;
      • 2.对于上面的情况就会问题:如果某个类在派生的过程中出现了很多次,那么每个派生类都会包含这个类的多个子对象.这种问题是需要解决的;
      • 虚继承机制通过对被继承了很多次的类作出说明,表示进行这个类资源的共享,其中共享的基类子对象称为虚继承类;
      • 在使用了虚继承类的情况下,无论虚基类在继承体系中出现了多少次,在派生类体系中都只包含唯一一个共享的虚基类对象;
      • 虚派生只影响从指定了虚基类的派生类中进一步派生出的类,但是不会影响派生类本身.
      • 使用虚基类的方式:class Raccon:public virtual ZoonAnimal;
      • 虚基类支持向派生类的馋鬼类型转换,基类的指针或者引用都可以访问派生类对象;
      • 构造函数和虚继承
        • 在虚派生中,虚基类是由最底层的派生类初始化的;
      • 虚继承的对象的构造方式:
        • 首先使用提供给最底层派生类构造函数的初始值初始化该对象的虚基类子部分,接下来按照直接基类在派生类列表中出现的次序依次对其进行初始化.
        • 虚基类总是先于非虚基类构造,和他们在继承体系中的次序和位置无关;
        • 当一个类有多个虚基类时,虚基类的子对象按照在派生类列表中出现的顺序从左向右依次构造;编译器按照直接基类的声明顺序对其依次进行检查,确定
          是否含有虚基类,如果含有虚基类,先构造虚基类,然后按照声明顺序构造其他基类;
        • 合成的拷贝和移动构造函数按照完全相同的顺序执行,合成的赋值运算符中的成员也按照这个顺序进行赋值;
        • 对象的销毁顺序和构造顺序刚好相反;
原创粉丝点击