• c++ primer 18th 用于大型程序的工具


    一、异常处理
    1. 抛出异常

      当执行一个throw时,跟在throw后面的语句将不再执行。相反,程序的控制权从throw转移到与之匹配的catch模块。该catch可能是同一个函数的局部catch,也可能位于直接或间接调用了发生异常的函数的另一个函数中。控制权从一处转移到另一处,这有两个重要的含义:

      • 沿着调用链的函数可能会提早退出
      • 一旦程序开始执行异常处理代码,则沿着调用链创建的对象将被销毁
    2. 栈展开

      当抛出一个异常后,程序暂停当前函数的执行过程并立即开始寻找与异常匹配的catch字句。如果对抛出异常的函数的调用位于try语句块内,则检查与该try块关联的catch子句。如果找到了匹配的catch,就使用该catch处理异常。否则,如果该try语句嵌套在其他try块中,则继续检查与外层try匹配的catch字句。如果仍然没有找到匹配的catch,则退出当前这个主调函数,继续在调用了刚刚退出的这个函数的其他函数中寻找,以此类推。

      上述过程被称为栈展开过程。栈展开过程沿着嵌套函数的调用链不断查找,直到找到了与异常匹配的catch子句为止;或者也可能一直没找到匹配的catch,则退出主函数后查找过程终止。

      如果没找到匹配的catch子句,程序将退出。因为异常通常被认为是妨碍程序正常执行的事件,所以一旦引发了某个异常,就不能对它置之不理。当找不到匹配的catch时,程序将调用terminate

      一个异常如果没有被捕获,则它将终止当前的程序。

      栈展开过程中对象被自动销毁:如果在栈展开过程中退出了某个块,编译器将负责确保在这个块中创建的对象能被正确地销毁。如果某个局部对象的类型是类类型,则该对象的析构函数将被自动调用。与往常一样,编译器在销毁内置类型的对象时不需要做任何事情。(析构函数仅仅是释放资源,一定要确保它们的析构函数不会引发异常)

    3. 异常对象

      当我们抛出一条表达式时,该表达式的静态编译时类型决定了异常对象的类型。如果一条throw表达式解引用一个基类指针,而给指针实际指向的是派生类对象,则抛出的对象将被切掉一部分。

    4. 查找匹配的处理代码

      与实参和形参的匹配规则相比,异常和catch异常声明的匹配规则受到更多限制。此时,绝大多数类型转换都不被允许,要求异常的类型和catch声明的类型是精确匹配的:

      • 允许从非常量向常量的类型转换,也就是说,一条非常量对象的throw语句可以匹配一个接受常量引用的catch语句。
      • 允许从派生类向基类的类型转换。
      • 数组被转换成指向数组类型的指针,函数被转换指向该函数类型的指针。
    5. 重新抛出

      有时,一个单独的catch语句不能完整地处理某个异常。在执行了某些校正操作之后,当前的catch可能会决定由调用链更上一层的函数接着处理异常。

      这里的重新抛出仍然是一条throw语句,只不过不包含任何表达式:

      throw;
      
    6. 捕获所有异常的处理代码

      void mainp()
      {
      	try
      	{
      	
      	}
      	catch(...)
      	{
      	
      	}
      }
      

      catch(...)既能单独出现,也能与其他几个catch语句一起出现。

      如果catch(...)与其他几个catch语句一起出现,则catch(...)必须在最后的位置。出现在捕获所有异常语句后面的catch语句将永远不会被匹配。

    7. 虚函数处理

      如果一个虚函数承诺了它不会抛出异常,则后续派生出来的虚函数也必须做出同样的承诺;与之相反,如果基类的虚函数允许抛出异常,则派生类的对应函数既可以允许抛出异常,也可以不允许抛出异常。

      class Base
      {
      public:
          virtual double f1(double) noexcept;
          virtual int f2() noexcept(false);
          virtual void f3();
      };
      class Derived : public Base
      {
      public:
          double f1(double); //错误
          int f2() noexcept(false);
          void f3() noexcept;
      };
      
    8. 异常类层次

    二、命名空间
    1. 未命名的命名空间

      #include<bits/stdc++.h>
      using namespace std;
      namespace 
      {
          int i;
      }
      namespace local
      {
          namespace
          {
              int i;
          }
      }
      int main()
      { 
          i = 0;
          local::i = 1;
          cout << i << endl;
          cout << local::i << endl;
      }   
      
    2. using指示与作用域

      #include<bits/stdc++.h>
      using namespace std;
      namespace blip
      {
          int i = 16,j = 15,k = 23;
      }
      int j = 0;
      int main()
      {
          using namespace blip;
          ++i;
          //++j;//错误
          ++::j;
          ++blip::j;
          int k = 97;
          ++k;
      }
      
    3. 有元声明与实参相关的查找

      #include<iostream>
      using namespace std;
      namespace A
      {
          class C
          {
              friend void f2();
              friend void f(const C&);
          };
      }
      int main()
      {
          A::C cobj;
          f(cobj);
          f2(); //错误
      }
      

      因为f接受一个类类型的实参,而且f在C所属的命名空间进行了隐式的声明,所以f能被找到。相反,因为f2没有形参,所以它无法被找到。

    4. 重载与命名空间

      1. 与实参相关的查找与重载

        #include<iostream>
        using namespace std;
        namespace NS
        {
            class Quote
            {
        
            };
            void display(const Quote&)
            {
        
            }
        }
        class Bulk_item : public NS::Quote
        {
        
        };
        int main()
        {
            Bulk_item book1;
            display(book1);
            return 0;
        }
        
      2. 重载与using声明

        using声明语句声明的是一个名字,而非一个特定的函数

        using NS::print(int);//错误
        using NS::print;
        
      3. 跨越多个using指示的重载

        #include<iostream>
        using namespace std;
        namespace AW
        {
            int print(int);
        }
        namespace Primer
        {
            double print(double);
        }
        using namespace AW;
        using namespace Primer;
        long double print(long double);
        int main()
        {
            print(1); //AW
            print(3.1);//Primer
        }
        
    三、多重继承与虚继承
    1. 继承的构造函数与多重继承

      允许派生类从它的一个或几个基类中继承构造函数。但是如果从多个基类中继承了相同的构造函数,则程序将产生错误。

      struct Base1
      {
          Base1() = default;
          Base1(const string&);
      };
      struct Base2
      {
          Base2() = default;
          Base2(const string&);
      };
      struct D1 : public Base1 , public Base2{
          using Base1::Base1;
          using Base2::Base2;
      };
      int main()
      {
          D1 d("123");  //错误 都继承D1::D1(const string&)
      }
      
      #include<iostream>
      using namespace std;
      struct Base1
      {
          Base1() = default;
          Base1(const string&)
          {
      
          }
      };
      struct Base2
      {
          Base2() = default;
          Base2(const string&)
          {
      
          }
      };
      struct D2:public Base1,public Base2
      {
          using Base1::Base1;
          using Base2::Base2;
          D2(const string&s):Base1(s),Base2(s)
          {
      
          }
          //D2() = default;//一旦D2定义它自己的构造函数,则必须出现
      
      };
      int main()
      {
          D2 d("123");
          return 0;
          //cout << "hello world" << endl;      
      }
      
      
    2. 基于指针类型或引用类型的查找

      #include<iostream>
      using namespace std;
      class Bear
      {
      public:
          Bear()
          {
              cout << 1 << endl;
          }
          void print()
          {
              cout << 4 << endl;
          }
      };
      class Panda : public Bear
      {
      public:
          Panda()
          {
              cout << 2 << endl;
          }
          void print()
          {
              cout << 3 << endl;
          }
      };
      int main()
      {
          Bear *b = new Panda();
          b->print();
      }
      //1 2 4
      

      与只有一个基类的继承一样,对象、指针和引用的静态类型决定了我们能够使用哪些成员。如果我们使用一个Bear指针,则只有定义在Bear中的操作是可以使用的,Panda接口中的Bear特有的部分都不可见。类似一个派生类指针或引用只能访问自身和基类的成员。

    3. 多重继承下的类作用域

      class Bear
      {
      public:
          Bear()
          {
              cout << 1 << endl;
          }
          void print()
          {
              cout << 4 << endl;
          }
          int age;
      };
      class ZooAnimal
      {
      public:
          int age;
      };
      class Panda : public Bear,public ZooAnimal
      {
      public:
          Panda()
          {
              cout << 2 << endl;
          }
          void print()
          {
              cout << 3 << endl;
          }
          int max_age()
          {
              return max(ZooAnimal::age,Bear::age);
          }
      };
      
    4. 虚继承

      虚继承是一种机制,类通过虚继承指出它希望共享其虚基类的状态。在虚继承下,对给定虚基类,无论该类在派生类层次中作为虚基类出现多少次,只继承一个共享的基类子对象。共享的基类子对象称为虚基类。

      必须在虚派生的真实需求出现前就完成虚派生的操作。

      虚派生只影响从指定了虚基类的派生类中进一步派生出的类,它不会影响派生类本身。

    5. 使用虚基类

      class Raccoon : public virtual ZooAnimal {};
      class Bear : virtual public ZooAnimal {};
      

      virtual说明符表明了一种愿望,即在后续的派生类当中共享虚基类的同一份实例。至于什么样的类能够作为虚基类并没有特殊规定。

      如果某个类指定了虚基类,则该类的派生仍按常规方式进行:

      class Panda: public Bear,public Raccoon, public Endangered{}
      
      #include<bits/stdc++.h>
      using namespace std;
      class ZooAnimal
      {
      public:
          void print1()
          {
              cout << 1 << endl;
          }
      };
      class Bear : public virtual ZooAnimal
      {
      public:
          void print2()
          {
              cout << 2 << endl;
          }
      };
      class Panda : public Bear
      {
      public:
          void print3()
          {
              cout << 3 << endl;
          }
      };
      int main()
      {
          Panda *b = new Panda();
          b->print1();
          b->print2();
          b->print3();
      }
      
    6. 虚基类成员的可见性

      因为在每个共享的虚基类中只有唯一一个共享的子对象,所以该基类的成员可以被直接访问,并且不会产生二义性。此外,如果虚基类的成员只被一条派生路径覆盖,则我们仍然可以直接访问这个被覆盖的成员。但是如果成员被多于一个基类覆盖,则一般情况下派生类必须为该成员自定义一个新的版本。

      例如,假定类B定义了一个名为x的成员,D1和D2都是从B虚继承得到的,D继承了D1和D2,则在D的作用域中,x通过D的两个基类都是可见的。如果我们通过D的对象使用x,有三种可能性:

      • 如果在D1和D2中都没有x的定义,则x将被解析为B的成员,此时不存在二义性,一个D的对象只含有x的一个实例。
      • 如果x是B的成员,同时是D1和D2中某一个的成员,则同样没有二义性,派生类的x比共享虚基类B的x优先级更高。
      • 如果在D1和D2中都有x的定义,则直接访问x将产生二义性问题。
    7. 当创建Panda对象的时候:
      (1)首先使用构造函数初始化列表中指定的初始化式构造ZooAnimal部分。
      (2)接下来,构造Bear部分。忽略Bear的用于ZooAnimal构造函数初始化列表的初始化式。
      (3)然后,构造Raccoon部分。再次忽略ZooAnimal初始化式。
      (4)最后,构造Panda部分。
      如果Panda构造函数不显式初始化ZooAnimal基类,就使用ZooAnimal默认构造函数;如果ZooAnimal没有默认构造函数,则代码出错。

    8. 无论虚基类出现在继承层次中任何地方,总是在构造非基类之前构造虚基类:

      class Character{ /*....*/ };
      class BookCharacter: public Character{ /*....*/ };
      class ToyAnimal{ /*...*/ };
      class TeddyBear: public BookChatacter,public Bear,public virtual ToyAnimal{ /*...*/ };
      

      按声明次序检查直接基类,确定是否在虚基类。例中,首先检查BookChatacter的继承子树,然后检查Bear的继承子树,最后检查ToyAnimal的继承子树。按从根类开始向下倒最低层派生类的次序检查每个子树。
      TeddyBear的虚基类的构造次序是先ZooAnimal再ToyAnimal。一旦构造了虚基类,就按声明次序调用非虚基类的构造函数:首先是BookChatacter,它导致调用Character的构造函数,然后是Bear。因此,为了创建TeddyBear对象,按下面次序调用构造函数:

      • ZooAnimal();
      • ToyAnimal();
      • Character();
      • BookChatacter();
      • Bear();
      • TeddyBear();

      在这里,由最低层派生类TeddyBear指定用于ZooAnimal和ToyAnimal的初始化式。

  • 相关阅读:
    vue.js环境的搭建
    图片上传简单demo及springboot上传图片
    mybatise 模糊查询
    thymeleaf th:onclick 传参
    thymeleaf的特殊属性赋值
    无限分类的设计及前后台代码
    mysql 多个属性排序查询
    java添加对象成功后想知道当前添加对象的id
    SpringBoot2.x集成MQTT实现消息推送
    linux下安装MQTT服务器
  • 原文地址:https://www.cnblogs.com/Jawen/p/11158351.html
Copyright © 2020-2023  润新知