1. 纯虚函数一般都没有实现,但它可以被实现,也可以被静态的调用B::api(),但是不能借助虚拟机制进行调用。
2. 纯虚析构函数必须被实现,因为每个子类的析构函数都会被编译器扩展以调用基类及再上层基类的的析构函数。否则会导致链接错误:lnk2001 unresolved external symbol。因为:c++保证继承体系中任一类的析构必须会被调用。
3. 无继承情况下的对象构造。Plain Old Data(POD)约等于前面说过的bitwise copy semantic的符合条件,他们的构造函数是trivial,基本不会被构造。实际上,VC就没有构造,根据汇编代码就会发现。
4. 继承体系下带有数据成员的类的构造过程,按照如下顺序依次构造(书上说的不太完整,还有一些小错误):
Ø 虚基类的构造函数,从左到右,从最顶层到最底层。它和非虚基类不同:是由最底层子类调用的。
Ø 非虚基类的构造函数,按照基类被声明顺序从左到右调用。它与虚基类的不同:是由直接子类调用的。
Ø 如果类中有虚表指针,则设置vptr初值;若增加有新的虚函数或者覆盖基类虚函数,则修改vtbl内的信息。
Ø 成员变量以其声明顺序进行初始化构造。假设x定义在y前面:即使y放到了成员初始化列表中,x没有在其中;甚至y可以放到x前面;或者y在成员初始化列表,而x放到构造函数的user code里面。x总是永远比y先行构造。
Ø 构造函数中,用户自定义的代码(user code)最后被执行。
5. 在copy构造函数中,如果没有与资源分配、释放相关的特殊数据,那么可以不用特别强调自我赋值的检查。
6. 在一个类的构造函数中,经由“构造中”的对象调用一个虚函数,其函数实体应该被决议为此类中的版本。因为根据构造函数的调用顺序:从root到leaf,从left到right,故当基类构造时,子类实体还没有构建出来,所以无法virtual。
7. 关于析构函数的合成以及析构的过程,其原理与构造函数完全一样,其各种顺序都和构造函数完全相反。
实例代码:
namespace ch5{
namespace initObj{
/*关于使用大括号初始化,c使用,但c++不推荐
class和struct如果定义了构造函数的话,都不能用大括号进行初始化
如果没有定义构造函数,struct可以用大括号初始化。
如果没有定义构造函数,且所有成员变量全是public的话,可以用大括号初始化。
*/
class cls{
public:
cls(int x, int y, int z): _x(x), _y(y), _z(z){};
private:
int _x, _y, _z;
};
class cls2{
public:
int _x, _y, _z;
};
struct cls3{
int _x, _y, _z;
};
void test(){
cls c2(1,2,3);
cls2 d1 = {1,2,3};
cls3 e1 = {1,2,3};
}
}
namespace comaOperator{
void test(){
int a = 3, b = 7;
// “逗号”操作符的执行:从左到右计算,返回值为最右面的表达式
// 下面的输出是:a < 3,b < 8
// 返回值:b1=true(因为b<8)
bool bl = (a < 3, printf( "a < 3,"), printf( "b < 8"), b < 8);
for ( int n = 0; n < a, n < b; n++)
printf( "n:..%d/n", n);
}
}
// P193,只要""析构函数如果没有实现"",无论是:纯虚拟析构函数,
// 一般虚拟析构函数,还是一般函数,
// 那么会出现link 错误:LNK2001
// 因为,默认情况下,析构函数(无论是子类的还是基类的)都会被系统调用
// 除非你不生成这个类的实例
namespace virtualDtor{
class B{
public:
//virtual ~B()=0; // link 错误
// virtual ~B(){}; // 没有 错误
virtual ~B() = 0 {}; // 没有 错误
};
class D: public B{
public:
//~D(); // link 错误
~D(){}; // 没有错误
};
void test(){
D d; // 如果不生成这个实例,那么无论如何也不会有link错误
}
}
// P206: 构造过程
namespace ctor{
class A{
public:
A(int m = 0){ n = m;}
int n;
};
class B{
public:
// 即使y放到了成员初始化列表中,x没有在其中;
// 甚至y可以放到x前面;
// 或者y在成员初始化列表,而x放到构造函数的user code里面。
// 没关系,x总是永远比y先行构造
B():y(0){};
A x;
A y;
};
void test(){
B b;
}
}
// P210: 初始化虚拟基类的数据
namespace virtBaseData{
class A{
public:
int x, y;
A():x(0),y(0){};
};
class B1: public virtual A{
public:
int z;
};
class B2: public virtual A{
public:
int z2;
};
class C: public B1, public B2{};
class C2: public B1, public B2{};
class X{
public:
int x, y;
X():x(0),y(0){};
};
class Y: public X{
public:
int z;
};
class Y2: public X{
public:
int z2;
};
class Z: public Y, public Y2{};
void test(){
// ****
// 注意下面的 ---------> (1)和(2)是这两种继承机制的构造顺序的根本差别
// 对于Z:Y1,Y2:X这样的继承体系来说(没有virtual 继承)
// X内部的数据的初始化,是由Y和Y2调用的,而不是由Z调用
// Z调用Y,Y调用X ---------> (1)
// 调用堆栈(call stack)不好表示,基本上就是Z调Y1, Y2;Y1调X
Z z;
// 但是对于C:B1,B2:virtual A来说
// A内部的数据的初始化,是由C调用的,而不是由B1和B2调用
// C依次调用A,B1和B2 ---------> (2)
// 汇编代码:
/*
ch5::virtBaseData::C::C:
......
004024EC call @ILT+345(ch5::virtBaseData::A::A) (0040115e)
004024F1 push 0
004024F3 mov ecx,dword ptr [ebp-4]
004024F6 call @ILT+375(ch5::virtBaseData::B1::B1) (0040117c)
004024FB push 0
004024FD mov ecx,dword ptr [ebp-4]
00402500 add ecx,8
00402503 call @ILT+380(ch5::virtBaseData::B2::B2) (00401181)
*/
C d;
C2 d2;
}
}
// main test
void test(){
printf("ch5 begin/n");
comaOperator::test();
initObj::test();
ctor::test();
virtBaseData::test();
printf("ch5 end/n");
}
};
#endif