C++ 杂记
虚继承
父类中只有一份子类的数据, 父类数据并不直接放在虚继承的子类中,子类中会定义一个虚基表指针,指向父类.虚基表中存在偏移量,这个量就是表地址到父类地址的距离.位置大概是:

虚继承只有在多继承关系(隔代继承)的时候才有比较有效果, 单继承虚基类效果不显著. 虚基表中的数据属于类的公共部分.虚基类和普通类中如果含有一个同名函数(变量), 优先显示普通类的函数(变量)
注意带有虚继承的构造函数与析构函数的调用顺序:
- 先调用虚基类的构造函数. 并按照它们被继承的顺序构造。(注:即使不是直接虚继承, 是父类虚继承,也是先调用父类虚基类的构造函数),再调用非虚基类的构造函数,并按照它们被继承的顺序构造。
- 先调用类中成员的构造函数,并按照声明调用,再调用类本身的构造函数。
- 析构的时候先调用派生类本身的析构函数, 再调用对象成员的析构函数, 最后调用基类析构函数. 基类析构函数先调用普通类析构函数, 再调用虚基类构造函数。
来看这个程序:
#include<iostream>
using namespace std;
class A{
public:
A(int a):x(a){ cout<<"A constructor..."<<x<<endl; }
int f(){return ++x;}
~A(){cout<<"destructor A..."<<endl;}
private:
int x;
};
class B:public virtual A{
private:
int y;
A Aobj;
public:
B(int a,int b,int c):A(a),y(c),Aobj(c){ cout<<"B constructor..."<<y<<endl;}
int f(){
A::f();
Aobj.f();
return ++y;
}
void display(){ cout<<A::f()<<"\t"<<Aobj.f()<<"\t"<<f()<<endl; }
~B(){cout<<"destructor B..."<<endl;}
};
class C:public B{
public:
C(int a,int b,int c):B(a,b,c),A(0){ cout<<"C constructor..."<<endl;}
};
class D:public C,public virtual A{
public:
D(int a,int b,int c):C(a,b,c),A(c){ cout<<"D constructor..."<<endl;}
~D(){cout<<"destructor D...."<<endl;}
};
int main()
{
D d(7,8,9);
d.f();
d.display();
return 0;
}
output:
A constructor...9
A constructor...9
B constructor...9
C constructor...
D constructor...
11 11 11
destructor D....
destructor B...
destructor A...
destructor A...
- 先调用虚基类的构造函数, a(9)
- 注意第二个构造函数的调用是因为虚基类的构造函数只调用一次, 所以开始调用 Aobj 类成员变量的构造函数 Aobj(9)
- 类成员变量构造函数全部调用完后开始调用类构造函数 B(a,b,c)
- 父类构造函数调用完后依次调用子类构造函数 C(), D()
- 虚基类和普通类中如果含有一个同名函数(变量), 优先显示普通类的函数(变量), 故调用 B::func() , 虚基类 a 中的x = 10, Aobj.x = 10, B.y = 10 , 然后调用 B.display(), 调用虚基类 A.f() , 返回 ++x = 11 , 再调用 AObj.f() , 返回 ++Aobj.x = 11, 再调用B.f() , 返回 ++ y = 11
- 然后开始析构, 先析构派生类构造函数, 再调用父类析构函数; 先调用普通类析构函数, 再调用虚基类构造函数;故先析构派生类 D, 再析构 C(无输出), 析构 B的时候,先调用 B的析构函数, 再调用成员 Aobj 的析构函数, 最后调用虚基类 A 的析构函数
虚函数与多态
在父类含有虚函数,子类重写父类虚函数的情况下, 使用父类指针或者父类引用绑定子类并调用虚函数, 即为多态.
虚函数表
类成员函数并不占用类对象的内存空间. 当对象引入虚函数之后,类内会插入一个虚函数指针,该指针占用对象一个指针大小的内存空间(4位或者8位)
当存在至少一个虚函数时, 编译期间会为类生成一个虚函数表.
当带有虚函数的类生成的时候, 编译器会为类的构造函数中增加一个为对虚函数指针赋值的操作
- 把子类直接复制给子类, 只复制子类中父类的部分,子类会重新创建虚函数表,所以调用的仍为最初的虚函数,使用的不是子类的虚函数表.
- base & b = a;或者 base* b = &a; a 为派生类, 引用赋值实际调用的还是派生类, 但是它会把派生类的成员隐藏起来无法调用, 但是可以正常调用父类成员, 同时如果父类中存在虚函数, 子类重写虚函数, 那么通过 虚函数指针调用子类重写的虚函数(因为虚函数表还是没改变).如果是虚继承的父类, 也会直接把虚基类的部分直接继承, 同理虚函数指针也会继承, 所以会调用子类重写的虚函数.
- 在父类中再调用父类中的虚函数, 即使被子类重写,也会加上base:: 作用域而调用父类虚函数
c++ 函数重载细节:
很久以前(八十年代),没有办法区分++和--操作符的前缀与后缀调用。这个问题遭到程序员的报怨,于是C++语言得到了扩展,允许重载increment 和 decrement操作符的两种形式。
然而有一个句法上的问题,重载函数间的区别决定于它们的参数类型上的差异,但是不论是increment或decrement的前缀还是后缀都只有一个参数。为了解决这个语言问题,C++规定后缀形式有一个int类型参数,当函数被调用时,编译器传递一个0做为int参数的值给该函数:
// 重载前置自增运算符
Counter& operator++() {
}
// 重载后置自增运算符
Counter& operator++(int) { // 要加入 int 的占位参数
}
使用引用就可以实现链式法则 , 即a++++, 但是注意这里并无法对前置自增的返回值也进行处理
任何时候都有比放弃更好的选择。

浙公网安备 33010602011771号