C++ 杂记

虚继承

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

图片1

虚继承只有在多继承关系(隔代继承)的时候才有比较有效果, 单继承虚基类效果不显著. 虚基表中的数据属于类的公共部分.虚基类和普通类中如果含有一个同名函数(变量), 优先显示普通类的函数(变量)

注意带有虚继承的构造函数与析构函数的调用顺序:

  1. 先调用虚基类的构造函数. 并按照它们被继承的顺序构造。(注:即使不是直接虚继承, 是父类虚继承,也是先调用父类虚基类的构造函数),再调用非虚基类的构造函数,并按照它们被继承的顺序构造。
  2. 先调用类中成员的构造函数,并按照声明调用,再调用类本身的构造函数。
  3. 析构的时候先调用派生类本身的析构函数, 再调用对象成员的析构函数, 最后调用基类析构函数. 基类析构函数先调用普通类析构函数, 再调用虚基类构造函数。

来看这个程序:

#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...
  1. 先调用虚基类的构造函数, a(9)
  2. 注意第二个构造函数的调用是因为虚基类的构造函数只调用一次, 所以开始调用 Aobj 类成员变量的构造函数 Aobj(9)
  3. 类成员变量构造函数全部调用完后开始调用类构造函数 B(a,b,c)
  4. 父类构造函数调用完后依次调用子类构造函数 C(), D()
  5. 虚基类和普通类中如果含有一个同名函数(变量), 优先显示普通类的函数(变量), 故调用 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
  6. 然后开始析构, 先析构派生类构造函数, 再调用父类析构函数; 先调用普通类析构函数, 再调用虚基类构造函数;故先析构派生类 D, 再析构 C(无输出), 析构 B的时候,先调用 B的析构函数, 再调用成员 Aobj 的析构函数, 最后调用虚基类 A 的析构函数

虚函数与多态

在父类含有虚函数,子类重写父类虚函数的情况下, 使用父类指针或者父类引用绑定子类并调用虚函数, 即为多态.

虚函数表

​ 类成员函数并不占用类对象的内存空间. 当对象引入虚函数之后,类内会插入一个虚函数指针,该指针占用对象一个指针大小的内存空间(4位或者8位)

​ 当存在至少一个虚函数时, 编译期间会为类生成一个虚函数表.

​ 当带有虚函数的类生成的时候, 编译器会为类的构造函数中增加一个为对虚函数指针赋值的操作

  1. 把子类直接复制给子类, 只复制子类中父类的部分,子类会重新创建虚函数表,所以调用的仍为最初的虚函数,使用的不是子类的虚函数表.
  2. base & b = a;或者 base* b = &a; a 为派生类, 引用赋值实际调用的还是派生类, 但是它会把派生类的成员隐藏起来无法调用, 但是可以正常调用父类成员, 同时如果父类中存在虚函数, 子类重写虚函数, 那么通过 虚函数指针调用子类重写的虚函数(因为虚函数表还是没改变).如果是虚继承的父类, 也会直接把虚基类的部分直接继承, 同理虚函数指针也会继承, 所以会调用子类重写的虚函数.
  3. 在父类中再调用父类中的虚函数, 即使被子类重写,也会加上base:: 作用域而调用父类虚函数

c++ 函数重载细节:

很久以前(八十年代),没有办法区分++和--操作符的前缀与后缀调用。这个问题遭到程序员的报怨,于是C++语言得到了扩展,允许重载increment 和 decrement操作符的两种形式。

  然而有一个句法上的问题,重载函数间的区别决定于它们的参数类型上的差异,但是不论是increment或decrement的前缀还是后缀都只有一个参数。为了解决这个语言问题,C++规定后缀形式有一个int类型参数,当函数被调用时,编译器传递一个0做为int参数的值给该函数:

// 重载前置自增运算符
    Counter& operator++() {
        
    }
 
    // 重载后置自增运算符
    Counter& operator++(int) { // 要加入 int 的占位参数
        
    }

使用引用就可以实现链式法则 , 即a++++, 但是注意这里并无法对前置自增的返回值也进行处理

posted @ 2024-12-08 15:43  -风间琉璃-  阅读(32)  评论(0)    收藏  举报