类点滴:
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的构造函数和赋值符都是私有的。