深入理解 C++ 三大特性之一 继承

发布于:2025-04-06 ⋅ 阅读:(18) ⋅ 点赞:(0)

欢迎来到干货小仓库!!!

今日的Commit 是明日的 Releasse,用持续交付的心态活成终身迭代的版本。


1.继承的定义

1.1定义格式

1.2继承关系和访问限定符

1.3继承基类成员访问方式的变化

类成员/继承方式 public继承 protected继承 private继承
基类的public成员 派生类的public成员 派生类的protected成员 派生类的private成员
基类的protected成员 派生类的protected成员 派生类的protected成员 派生类的private成员
基类的private成员 在派生类中不可见 在派生类中不可见 在派生类中不可见

总结:

1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它

2. 基类 private 成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected 可以看出保护成员限定符是因继承才出现的
3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min( 成员在基类的访问限定符,继承方式 ) public>protected>private。
4. 使用关键字 class 时默认的继承方式是 private ,使用 struct 时默认的继承方式是 public 不过 最好显示的写出继承方式
5. 在实际运用中一般使用都是 public 继承,几乎很少使用 protetced/private 继承 ,也不提倡使用protetced/private 继承,因为 protetced/private 继承下来的成员都只能在派生类的类里 面使用,实际中扩展维护性不强.

2.基类和派生类对象赋值转换

a. 派生类对象 可以 赋值给基类的对象/基类的指针/基类的引用。(向上转换/切割/切片,不会产生临时变量

b. 基类对象 不能赋值给 派生类对象。(禁止 向下转换)

3.继承中的作用域

① 在继承体系中 基类派生类 都有独立的作用域。

②子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)

③ 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。

④ 注意在实际中在继承体系里面最好不要定义同名的成员

代码示例:

示例一:相同的成员变量,默认先访问派生类中的

class Person
{
protected :
 string _name = "小李子"; // 姓名
 int _num = 111;   // 身份证号
};
class Student : public Person
{
public:
 void Print()
 {
 cout<<" 姓名:"<<_name<< endl;
 cout<<" 身份证号:"<<Person::_num<< endl;
 cout<<" 学号:"<<_num<<endl;
 }
protected:
 int _num = 999; // 学号
};
void Test()
{
 Student s1;
 s1.Print();
};

示例二:相同的函数名,构成隐藏

// B中的fun和A中的fun不是构成重载,因为不是在同一作用域
// B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。
class A
{
public:
 void fun()
 {
 cout << "func()" << endl;
 }
};
class B : public A
{
public:
 void fun(int i)
 {
 A::fun();
 cout << "func(int i)->" <<i<<endl;
 }
};

4.派生类的默认成员函数

a. 继承中 基类的成员声明 默认在派生类的成员声明的前面,初始化列表的顺序按声明顺序初始化.

b. 派生类的基类若没有默认构造,则需要在初始化列表中 对 基类进行 初始化.

c. 派生类中 会自动 基类的析构,把基类自己的资源进行释放。(不需要我们手动调用)

无需我们显示调用基类的析构----->编译器做了处理(子类析构函数完成时,自动调用基类的析构函数),由于交给我们手动释放会 无法保证先析构派生类,再析构基类。

为什么要先析构派生类 再析构基类?

由于 子类可能 使用到了 基类中的资源。

d. 编译器会统一把析构函数的函数名,进行特殊处理,处理成destructor(),由于后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同。

5.继承与友元

友元关系不能继承,也就是说基类的有源不能访问子类私有成员和保护成员。

示例:改代码编译不通过,出现语法错误

class Student;
class Person
{
public:
 friend void Display(const Person& p, const Student& s);
protected:
 string _name; // 姓名
};
class Student : public Person
{
protected:
 int _stuNum; // 学号
};
void Display(const Person& p, const Student& s)
{
 cout << p._name << endl;
 cout << s._stuNum << endl;
}
int main()
{
 Person p;
 Student s;
 Display(p, s);
 return 0;
}

6.继承与静态成员

若基类定义了 static静态成员,则整个体系里面只有一个这样的成员。无论派生出多少个子类都只有一个 static成员示例。(相当于只是拥有使用权)

静态成员变量属于父类和派生类。

示例:

7.复杂的菱形继承及菱形虚拟继承

单继承:一个子类只有一个直接父类时称这个继承关系为单继承

多继承:一个子类有两个或两个以上直接父类时称这个继承关系为多继承。

菱形继承:是多继承的一种特殊情况。

导致 最后一个派生类有两份  class  Person 的数据,形成了 数据冗余和二义性

解决方式:菱形虚拟继承,加上 virtual(继承公共基类的派生类上加)

7.1菱形虚拟继承底层原理

class A
{
public:
 int _a;
};
class B : virtual public A
{
public:
 int _b;
};
class C : virtual public A
{
public:
 int _c;
};
class D : public B, public C
{
public:
 int _d;
};
int main()
{
 D d;
 d.B::_a = 1;
 d.C::_a = 2;
 d._b = 3;
 d._c = 4;
 d._d = 5;
 return 0;
}

菱形虚拟继承的内存对象模型:

这里是通过了 B C 的两个指针,指 向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量 可以找到下面的 A

7.2习题

8.继承和组合的区别

9.继承的总结和反思


觉得不错的可以点赞+收藏咯!!!谢谢大家