Chapter15 友元,异常和其他

友元

  • 类可以将其他函数、其他类和其他类的成员函数作为友元。

  • 类并不只是能拥有友元函数,也可以将类作为友元。友元类,所有函数都可以访问原始类的私有成员和保护成员。

          friend class Remote
    

    友元声明可以位于公有,私有或保护部分,所在位置无关紧要。

  • 友元成员函数:选择尽让特定的类成员成为另一个类的友元

          class TV
          {
              friend void Remote::set_chan( Tv &t, int c);
           }
    

    由于Tv类涉及Remote,则Remote定义应在Tv前面;Remote函数提到Tv对象,则Tv定义应当位于Remote定义之前。为了避免这种循环依赖,使用前向声明,如下:

          class Tv;
          class Remote {...};
          class Tv {...};
            
    只能为以上顺序,是因为Tv类声明中,看到Remote的函数被声明为Tv类的友元之前,应该先看到Remote类的声明和set_chan函数的声明;
      
    由于Remote的**inline代码**调用Tv的函数,则必须已知Tv声明,但是实际情况是该声明位于Remote声明之后。则解决办法是Remote声明中只包好函数声明,实际定义放在Tv类之后,函数之前加上inline,同样效果
      
           class Tv;
           class Remote { declaration };
           class Tv { ... };
           // Remote mehods difinition 
    
  • 对于使用Remote对象的Tv函数,Tv原型可在Reomte类声明之前声明,但必须在Remote类声明之后定义

  • 两个类互为友元类或友元成员函数

  • 如果类中有友元类或友元成员函数,而函数调用调用的参数或者友元成员函数在后面才进行声明,则在第一个类中,只使用声明,定义放在友元类定义之后。即确认函数定义中,所使用的参数或调用函数,都已定义或声明

  • 共同友元:函数需要访问两个类的私有数据。其可是一个类的成员,同时也是另一个类的友元;或者将函数作为两个类的友元

嵌套类

  • 嵌套类:在另一个类中声明的类,通过提供洗澡你的类型类作用域,避免名称冲突

          class Queue
          {
               class Node
               {
                  public: 
                      Item item;
                      Node *next;
                      Node ( const Item &i);
                }
          }
    

    则类外的的定义,通过两次作用域解析运算符完成:Queue::Node::Node( const Item &i)

  • 类嵌套与包含的区别:

    • 包含,是将类对象作为另一个类的成员
    • 类嵌套,是对类进行嵌套,而不创建类成员,只是定义了一种类型,该类型尽在有嵌套类声明的类中有效
  • 嵌套类的声明位置决定了嵌套类的作用域,即决定了程序的那些部分可创建这种类的对象;嵌套类的公有,保护和私有部分控制了对类成员的方位

  • 作用域:如果嵌套类在另一个类的私有部分声明,则Queue成员可使用Node对象,对于Queue之外或者Queue的派生,Node对其都是不可见的;对于保护部分,公有部分,嵌套类与普通类成员保持一致,只不过对于公有部分,需要通过类限定符使用

异常

  • abort()函数,位于cstdlib,其典型实现是向标准错误流(即cerr使用的错误流)发送消息abnormal program termination(程序异常终止)

  • exit()函数,终止程序,刷新文件缓冲区,但不显示消息

  • 返回错误码,是使用函数的返回值来指出问题,比异常终止更灵活。通过使用指针参数或引用承诺书来将值返回给调用程序,并使用函数的返回值来支出成功还是失败,使程序可以采取除异常终止程序之外的其他措施。例如,将函数的返回值定义为bool,让返回值指出成功或失败

  • 异常机制,异常提供了将控制权从程序的一个部分传递到另一部分的途径。对异常的处理有3个组成部分

    • 引发异常

      程序在出现问题时,将引发异常。throw语句实际上是跳转,即命令程序跳到另一条语句。throw关键字标识引发异常,紧随其后的值指出了异常的特征。引发错误代码:

          if ( a==-b)
            throw "bad hmean()" 
      

      其中被引发的异常是字符串”bad hmean()”。异常类型可以是字符串或其他C++类型

      执行throw语句类似于执行返回语句,因为其将终止函数的执行,但throw不是将控制权返回给调用程序,而是导致程序沿函数调用序列后退,直到找到tyr块的函数。

      上述李梓,throw将程序控制权返回给main(),程序在main()中寻找与引发的异常类型匹配的异常处理程序(位于try块的后面)

    • 使用处理程序捕获异常

      程序使用异常处理程序( exception handler )来捕获异常,异常处理程序位于要处理问题的程序中,关键字catch表示捕获异常。处理程序以关键字catch开头,随后是位于括号中的类型声明,其指出了异常处理程序要相应的异常类型;然后是花括号中的代码块,指出要采取的措施。catch关键字和异常类型用作标签,指出当异常被引发时,程序应跳到这个位置执行。异常处理程序也被称为catch块

       处理程序(或catch块):
      
        catch ( char *s )
        {
          std::cout << s << std::endl;
          continue;
        }
         
        catch块类似于函数定义,但不是函数定义。关键字catch表明这是一个处理程序,而char *s则表示该处理程序与字符串异常匹配。匹配的异常引发将被赋给s,当异常与该处理程序匹配时,程序将执行括号中的代码。**捕获类型最好使用引用**
      
    • 使用try块

      try块标识其中特定的异常可能被激活的代码块,其后面跟一个或多个catch块。try块是由关键字try指示的,关键字try后面是代码快,表明需要注意这些代码引发的异常。try块如下:
            
           try{
              z=hmean(x,y);
           }
                 
      如果其中的某条语句导致异常被引发,则后面的catch块将对异常进行处理。如果程序在try块的外面调用hmean(),将无法处理异常
      

    执行完try块中的语句后,如果没有引发任何异常,则程序跳过try块后面的catch块,直接执行处理程序后面的第一条语句。

  • 将对象用作异常类型:引发异常的函数将传递一个对象,可以使用不同的异常类型来区分不同的函数在不同情况下引发的异常。

    catch块可根据这些信息来决定采取什么样的措施,例如

          class bad_hmean
          {
              private:
                  double v1;
                  double v2;
              public:
                  bad_hmean( int a=0, int b=0): v1(a), v2(b){}
                  void mesg();
          }
    
  • 栈解退:假设函数由于出现异常而终止,则程序也将释放栈中的内存,但是不会在释放栈的第一个返回地址后停止,而是继续释放栈,直到找到一个位于try块中的返回地址。随后,控制权将转到块尾的异常处理程序,而不是函数调用后面的第一条语句。即throw语句处理try块和throw块之间,整个函数调用序列放在栈中的对象,自动释放

  • 其他异常特性:throw语句将控制权向上返回到第一个能够捕获相应异常的try-catch组合。

    对于异常类层次结构,并要分别处理不同的异常类型,则使用基类引用将能够捕获任何异常,派生类对象智能捕获其所属类以及其派生的类的对象。catch块的排列顺序应该与派生顺序相反。

    用省略号表示异常类型,从而捕获任何异常,类似switch中的default

          catch ( ... )
    
  • exception类:在程序设计中加入错误处理功能,使用exception类,可引发exception异常,也可将exception类用作基类。其中,虚拟成员函数what(),返回一个字符串,该字符串特征岁实现而异,可派生重新定义。

    异常类有很多种:domain_error,invalid_argument,length_error等

  • 对于使用new导致的内存分配问题,是让new引发bad_alloc异常

  • 空指针和new:C++标准提供了在失败时,返回空指针的new

      int *pi=new (std::nothrow) int[500];
    
  • 异常,类和继承:从一个异常类可派生出另一个类;可在类定义中嵌套异常类声明来组合异常;嵌套声明本身可被继承,也可用作基类

  • 异常迷失方向:未捕获异常或意外异常

  • 应该在设计程序时,就加入异常处理功能。异常规范不适用于模板,因为模板函数引发的异常可能岁特定的具体化而异。