🤖 AI文章摘要 qwen-turbo-latest
加载中...

继承概述

继承是代码编写的一种思想, 是一种抽象思维. 通过类继承的方式, 共享父类的一些特性, 达到减少代码冗余的目的

C++类继承

单继承

  • 继承语法: class 子类名字 : public|protected|private 基类名
  • 继承的原理: 派生类通过调用基类的构造函数在派生类的内存空间初始化基类的变量来实现继承 所以继会将基类所有成员继承下来. 逐级继承就涉及到递归的概念: A->B->C, C调用B的构造函数, 而B的构造函数又调用 A的构函数. 逐级在自己的作用域内初始化基类的变量. 编译器会自动调用基类默认构造函数, 要确保基类默认构造函数存在且非私有. 否则必显式调用基类的构造函数!!!!!!! 示例: class Base_class { public: Base_class(){}//基类构造函数 } class Derive_class : public Base_class { public: Derive_class() : Base_class(){}//派生类构造函数显式调用基类的构造函数 }
  • 继承模式 公有继承(public): 父类的public和protected成员权限不变 保护继承(protected): 父类的public和protected成员权限都是protected 私有继承(private): 父类的public和protected成员权限都是private 所有继承方式下, 父类的private成员子类都不可访问!!!!!!!
  - 测试代码
父类:
class person
{
public:
	//person(){.......}                              //默认构造
	person(int a) {....}                           //此时已经没有默认构造
	person(const person & p){......}     //拷贝构造
	void CommentFunction(){..... }      //非静态成员函数
	static void StaticFunction(){.....}     //静态成员函数
	void operator=(){ .........}                //赋值运算符重载
	~person(){}                                    //析构函数
private:
	static int StaticVariate ;                 //静态成员变量
public:
	 int CommentVariate ;                  //非静态成员变量: 4U
}
子类
class son : public person
{
public:
	son(int a) : person(a) {} // 1.当基类没有默认构造时,要用参数列表法显示调用基类的构造
pubic:
	 int CommentVariate ;
}
	2. 派生类的内存结构
SON_Structure 
{
	base class person//2.调用基类构造, 派生类不继承基类的构造和析构还有"'="'重载函数
	{
	private:
		CommentVariate //3. 基类的非静态成员由基类的构造函数初始化, 只继承非静态成员(重名也继承!!!!!)
	}
public:
	CommentVariate // 4.派生类非静态成员变量由派生类构造函数初始化
}
				
	3. 派生类的成员调用
->非静态成员变量的调用:
	son s1;
	s1.CommentVariate;//5.直接调用优先调用派生类的成员, 找不到时才往基类里找, 找不到就报错
	s1.person ::  CommentVariate;//6.重名想调用父类成员必须加作用域限定. 基类为私有所以这个是不能访问的

->静态成员变量的调用: 
	person :: StaticVariable
	基类实例.StaticVariable 
	son  :: person :: staticVariate  //7.基类的静态成员被派生类所有实例共享
	son :: staticVariate (不重名时)
	派生类实例.staticVariate
  

多继承

  • 多继承的语法: class 子类名字 : 继承模式 基类名1, 继承模式 基类名2 ……
  • 多继承的问题: ->二义性: 当多继承的两个类中含有相同声明的成员, 子类调用时会出现二义性 ->菱形继承问题: 当多继承的两个类具有相同的基类, 那么这个基类的构造函数会被调用两次造成冗余
  • 解决方法: 两个问题都可以通过作用域运算符指明基类作用域解决, 菱形继承问题还可以通过虚继承解决
  class father1
{
public:
	int a;
}
class father2
{
public:
	int a;
}
class son : public father1, public father2
{
}
void test(void)
{
	son s1;
	s1.a;//编译器并不知道要调用那个父类的a
	s1.father1::a;
	s1.father2::a;//添加作用域就能区分了
}
  

虚继承

  • 虚基类: 基类在派生类中是无形的, 是形式意义上的. 它是通过指针去访问的.
  • 虚继承: 将基类当作虚基类继承 class 类名 : virtual public|protected|private 基类名
  • 虚继承的原理: 编译器对虚基类的派生类添加一个调用虚基类构造函数的限制条件: 对于一个虚基类的派生类, 虚基类的构造函数交由这个类完成调用, 忽略其继承链上其他类对虚基类构造函数调用 也就是说虚基类的派生类不仅要调用直接基类的构造函数, 还要调用虚基类的构造函数 可以从以下四个例子理解这个规则, 体会派生类与基类之间的构造函数函数调用关系 (1)B是虚基类A的直接派生类, 所以他要调用A的构造函数

(2)C是虚基类A的间接派生类, 它不仅要调用直接基类B的构造函数, 还要调用A的构造函数. 忽略B对A的调用.

(3)D是虚基类A的间接派生类, 它不仅要调用直接基类B,C的构造函数, 还要调用A的构造函数. 忽略BC对A的调用

(4)E是虚基类A的间接派生类, 它不仅要调用直接基类D的构造函数, 还要调用A的构造函数. 忽略BCD对A的调用

对于虚基类的直接派生类, 需要引入一个虚基类表指针vbptr, 而这个指针指向这个类的虚基类表vb_table , 它是整形数组, 存放着这个类的虚基类首地址相对于虚基类表指针的偏移量.
所以vbptr是实实在在充当着虚基类的直接派生类对虚基类的寻址功能!!!!!!! 值得注意的是, 编译时类的构造函数调用关系是确定的, 所以类的结构是确定的. 故虚基类表是在编译阶段完成的, 运行时载入内存, 所有类实例共享一份数据

类对象内存结构

  class Animal
{
	int age;
public:
	Animal(){};//Animal的默认构造函数
};
class Sheep: virtual public Animal
{
	Sheep(): Animal() {}//虚基类的直接或间接派生类要显式调用直接基类和虚基类的构造函数
};
💡Sheep的内存结构

>>>执行解读: Sheep先构造vbptr将其指向自己的虚基类表后, 调用Animal构造函数
class SheepSon : public Sheep
{
public:
	SheepSon(): Sheep(), Animal(){}  //显示调用父类的构造函数, 同时调用继承链上的虚基类构造函数
}
💡SheepSon的内存结构

>>>执行解读: SheepSon调用Sheep的构造函数后, 然后再调用Animal构造函数. 一开始vbptr指向的是Sheep的虚基类
				后来SheepSon将其指向了自己的虚基类表 
>>>虚基类的寻址示例(高端操作):
int main(void)
{
SheepSon ss;
cout << "base class Sheep对象中的vbptr地址: " << (int*)&ss << endl;
cout << "virtual base Animal相对于vbptr的偏移量: " << *((int*)*(int*)&ss + 1) << endl;
cout << "virtual base Animal的地址: " << &ss.Animal::age<<endl;
}