《C++ primer》学习笔记之二十八:类点滴,记录琐碎的类的语法或注意事项

来源:互联网 发布:js舞蹈视频教程 编辑:程序博客网 时间:2024/05/16 14:19

类点滴:
 1.0  const和引用成员数据必须在initailize list中初始化,在constructor中都不行。
   也不能在声明处初始化(只有static const可以),
 1. 在const成员函数中,可以访问member data,但member data都有const属性,试图去改变他们的值会导致编译错: error C2166: l-value specifies const object
   如果想在member data中修改成员变量,则可以用关键字mutable来修饰成员变量
   语句
    cout << typeid( this ).name() << endl;
   在普通成员函数中输出:class T *
   在const普通成员函数中输出:class T const *
 2. 判断double型变量是否值为0:
  double d = 0;
  if( d == 0 ) {}  //没效果
  if( d == 0.0 ) {}  //没效果
  if( d ) {}   //ok
 3. static成员数据的初始化要放在.cpp中,不能放在header中,因为它只能初始化一次。
  但static const可以。
 4. 指向static成员的指针的类型中不需要使用class scope修饰:
  double *pd;  //而不是:double T:: *pd;
  pd = T::d;
  void (*pf)(); //而不是:double (T:: *pf)();
  pf = T::f;

      
 7. 如果在类T定义前使用T会抱错:使用为定义类型T
  class T;
  class S
  {
   S(T t){} //error. 这个会调用T的构造函数
   S(T &rt){} //Ok. 引用可以,因为不用创建对象
   S(T *t){} //Ok. 指针可以,因为不用创建对象
   void printT(T *t, T &rt) //ok.
   {
    cout << t->value() << endl;  //error.调用成员函数value
    cout << rt.value() << endl;  //error.调用成员函数value
   }
  };
 8. 显示调用析构函数:
  t.T::~T(); //注意scope修饰
  注意:如果t是placement new起来的,可以显示调用析构函数
     如果是普通的new,则用delete t
     如果是普通的对象,则编译器会自动调用析构函数
     否则的话显示调用析构函数会死的很惨
 
 9. class T中对同一种类型S定义了构造函数和转换函数,则会出现二义调用:
  class T
  {
  public:
   T(S s){}
   operator S(){ return S(); }
  };
  
  void test(S s){}
  test(t);   //error.不知道调用T(S)还是调用operator S().二者去掉一个即可。
 10. template class的成员函数也是template function,它只在被调用时才实例化,并不随class一起实例化。
 11. 类的成员函数如果在类外定义则不能在头文件中定义,以免发生重复定义的编译错。
  如果是template类,则其成员函数必须在头文件中定义,因为VC对template采用的是inclusive model.
 12. 在类T的成员函数的定义中,通过类型为T的对象obj访问类T的成员函数时:可以访问T的public/protected/private成员函数,可以访问T的基类的public/protected函数(不可以访问基类的private函数)
  如果obj的类型不是T,而是S,(既使S为T的基类),则只能访问S的public成员。
  想象看,this指针实际上就是一个T*,它也不能访问T基类的private成员。
 13. friendship不能继承。一个函数是类型T的基类的friend,它不“自然”是T的friend,除非显示在T中声明。
 14. friend function可以在类中定义,但它不是类的成员函数。
  此时,不可像在global scope一样直接调用该函数,也不可以用class scope指定,因为该函数不在class scope中。
  可以用“argument-dependent lookup”来调用该函数:
   class T
   {
    //注意:如果一个函数不需要T的对象作参数的话就没有必要声明为T的friend。因为friend的用处是为了方便访问T的private成员    
    friend void fr() { cout << "I'm you friend" << endl; } 
    
    friend void fr(T t) { cout << "I'm you friend, t." << endl; }
   };
   
   int main()
   {
    fr(); //error
    
    T t;
    fr(t); //ok.
      //“argument-dependent lookup”会到参数t的类型T所在的namespace和class T scope中去找“候选”函数
   }
  在讲重载时会再涉及这点。
 15. 子类的构造函数的执行顺序为:父类的构造函数 -> initailize list -> 子类构造函数
   如果父类的构造函数在initailize list中出现,则执行指定的构造函数;如果没有出现,则执行父类的default constructor
   按照成员数据声明的次序依次初始化这些成员,如果这些成员在initailize list中显示初始化,则调用指定的构造函数;如果没有,则调用缺省构造函数
    注意:内建类型没有缺省构造函数,如果它们不在initailize list中显示初始化,则它们将不会被初始化。
   子类构造函数中也可以对成员数据进行赋值,但注意这些成员数据已经在执行initailize list的过程中被初始化了。
  子类的析构函数的执行顺序正好和构造函数的执行顺序相反:子类的析构 -> 按声明的相反次序执行成员数据的析构函数 -> 父类的析构函数
 16. 为了使子类的析构函数在执行delete操作时能正确被调用,父类的析构函数应该定义为virtual
 17. template class:其内的成员函数都将是template function,所以与普通的template function一样,
  这些成员函数的定义应该放在头文件中,最好与类定义放一起.  
 18. 抽象类Abstract classes是有成员函数为纯虚函数的类,它不能被实例化。
  class T
  {
   void f() = 0;    //error.只有虚函数可以被赋予0
   virtual void print() = 0; //ok.print为纯虚函数
   
   virtual void value() = 0  //ok.value为纯虚函数。纯虚函数也可以有定义
   {
    cout << "T::value()" << endl;
   }   
  };
  
  class S: public T
  {
   void print() { cout << "S::print()" << endl; }
   
   void value() { cout << "T::value()" << endl; }  //既使基类T中已经实现了value,但继承类S中也必须实现自己的value
               //如果S中不实现纯虚函数value,则S也为一个抽象类,不能被实例化
  };
  
  int main()
  {
   T t;   //error.
   S s;  //ok
   
   s.value();  //输出:S::value()
   s.T::value(); //输出:T::value()
  }
 19.
  在基类的构造函数中调用virtual函数,则virtual函数总是基类自己的,因为继承类此时还没有创建,不可能去调用继承类的成员函数。
  在基类的其它函数(包括析构函数)中调用virtual函数,则如果指针指向子类,则virtual函数为子类的。
  
  构造函数声明为virtual会报编译错。
  operator new()声明为virtual会报编译错。因为它在对象创建前被调用。而创建前是没办法有多态功能的。
  
   class T
   {
   public:
    T() { init(); }
    void f() { cout << "in f: "; init(); }
    virtual void vf() { cout << "in vf: "; init(); }
    virtual void init() { cout << "T::init" << endl; }
    virtual ~T() { cout << "~T" << endl; }
   };
   
   class S: public T
   {
   public:
    void init() { cout << "S::init" << endl; }
    virtual ~S() { cout << "~S" << endl; }
   };
   
   int main()
   {
    T *pt = new S;  //输出: T::init
    pt->f();   //输出: in f: S::init
    pt->vf();   //输出: in vf: S::init
    delete pt;   //输出: ~S~T. 如果析构函数不是virtual,则输出~T
   }
  
 20. 继承的访问控制:
  当为public继承时:子类继承父类的接口,可认为子类是"face"
  当为private继承时:父类的接口对外部和子类的子类都不可见
       private继承实际是组合(composite)的一种替代,它们的区别是:"继承关系"容许子类重定义父类的某些方法,而组合则只能重用成员的方法
  当为protected继承时:它与private继承的区别是父类的public/protected成员它的子类还可以访问。
       还没看出这个有什么必要。
       
  如果想“紧缩”父类的接口,可以考虑用protected继承。
  
  当继承是protected/private时,指向子类的指针的不能转为指向父类的指针。
  MSDN的解释:Access protection (protected or private) prevented conversion from a pointer to a derived class to a pointer to the base class.
 20.2: 
  当为protected/private继承时,可以用using将父类的成员函数在子类中声明,
   class T
   {
   public:
    void publicF() {}
   protected:
    void protectedF() {}
   };
   
   class S : private T
   {
   public:
    using T::publicF;  //注意:publicF在基类中为public,则在S中声明时也只能是public
   protected:
    using T::protectedF; //注意:protectedF在基类中为protected,则在S中声明时也只能是protected
   }
  这样通过S的对象可以访问T::publicF, S的子类可以访问T::protectedF.
  
 21. 多重继承的麻烦点在于:
    G1     G2 //祖父类
     /     /
     F1   F2 //父类
       /  / 
        Son  //这个是子类,
 
   1. 当一个函数名f不在子类中出现而在多个父类中出现时,父类中的这些同名函数将导致“重复定义”的编译错,既使这些同名函数的参数不相同。
    注意:父类继承自祖父类的成员函数(包括private)也算在父类中出现。
    注意:当一个函数名f在子类中出现又同时在多个父类中出现时,父类的函数将不可见,不出现在“候选”列表中。
   2. 继承树中的类的成员的scope都在自己类中,所以一个父类的成员函数不看作是另一个父类的纯虚函数的实现,子类得自己实现
   
  lippman说:A common pattern of multiple inheritance is to inherit the public interface of one class and the private implementation of a second class.
  
 22. 多重virtual继承: 一个子类有两个父类,而这两个父类继承自同一个类,继承树为:
     GF
       /  /
     F1   F2
       /  /
        Son
  这样类Son的组成中有两个GF对象,那么调用GF的方法时总会出现“二义调用”:Son::F1::GF::f 和 Son::F2::GF::f
  解决办法是:F1 virtual继承GF, F2 virtual继承GF,这样Son的组成中将只有一个GF对象,且该对象由Son来创建而不是F1和F2.这时可以调用GF的方法了。
  
  在Son的initailize list中可以直接调用GF的构造函数(如果不是virtual继承,是不容许直接调用祖父类的构造函数的)
  编译器会先调用Son的virtual基类的构造函数,如果这些构造函数在Son的initailize list中没有指定,则调用默认的构造函数;再依继承树调用非virtual基类的构造函数
 
 23. composite:
   1. composite by value    如果该成员数据是类的每个对象私有的,且可能各不相同,则用by value
   2. composite by pointer/reference 如果该成员数据是类的多个对象共有的或者是个外部对象,用by pointer/reference
     by pointer 指针可以初始化为0,这表示该成员数据可以没有
     by reference 引用必须在intialize list中初始化为一个已知对象的别名,且该对象已经创建好,这表示该成员数据总有,且对象创建时就存在。
 24. typeid: typeid(express) 或 typeid(type),其返回值为type_info类型的对象。type_info的构造函数和赋值符都是私有的。
 

原创粉丝点击