cpp自学 day18(继承)

一、基本语法

继承的好处:减少重复代码

语法: class 子类 : 继承方式 父类 

 class A : public B;

  • A 类称为子类派生类
  • B 类称为父类基类

// 基类:父类
class Parent {
public:
    void show() {
        cout << "I am a parent class." << endl;
    }
};

// 派生类:子类
class Child : public Parent {
public:
    void display() {
        show(); // 调用父类的show函数
        cout << "I am a child class." << endl;
    }
};

派生类中的成员,包含两大部分: 一类是从基类继承过来的,一类是自己增加的成员。

从基类继承过过来的表现其共性,而新增的成员体现了其个性。


二、继承方式

  • 公有继承
  • 保护继承
  • 私有继承

cpp自学 day18(继承)_第1张图片


三、继承中的对象模型

问题:从父类继承过来的成员,哪些属于子类对象中?

答:

  • 父类中所有非静态成员属性都会被子类继承下去
  • 父类中私有成员属性 是被编译器给隐藏了,因此是访问不到,但是确实被继承下去了

可利用开发人员命令提示工具查看对象模型

跳转盘符  F:
跳转文件路径 cd 具体路径下
查看命名
 cl /d1 reportSingleClassLayout类名 文件名

四、继承中构造和析构顺序

子类继承父类后,当创建子类对象,也会调用父类的构造函数

问题:父类和子类的构造和析构顺序是谁先谁后?

答:先构造父类,再构造子类,析构的顺序与构造的顺序相反


五、同名成员处理

  • 子类对象可以直接访问到子类中同名成员
  • 子类对象加作用域可以访问到父类同名成员
  • 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数

子类对象加作用域形式如下(Base是父类,s是子类)

成员属性:

s.Base::m_A;

成员函数:

s.Base::func();

六、继承同名静态成员处理方式

问题:继承中同名的静态成员在子类对象上如何进行访问?

静态成员和非静态成员出现同名,处理方式一致(Base是父类,s是子类)

  • 访问子类同名成员 直接访问即可
  • 访问父类同名成员 需要加作用域

同名静态成员属性

void test01()
{
    //1、通过对象访问
    Son s;
    cout << "Son 下 m_A = " << s.m_A << endl;
    cout << "Base 下 m_A = " << s.Base::m_A << endl;
    cout << "Base 下 m_A = " << Son::Base::m_A << endl;
    //第一个::代表通过类名方式访问  第二个::代表访问父类作用域下

    //2、通过类名访问
    cout << "Son 下 m_A = " << Son::m_A << endl;
}

同名静态成员函数

//同名静态成员函数
void test02()
{
    //1、通过对象访问
    cout << "通过对象访问" << endl;
    Son s;
    s.func();
    s.Base::func();

    //2、通过类名访问
    cout << "通过类名访问" << endl;
    Son::func();
    Son::Base::func();
}
//子类出现和父类同名静态成员函数,也会隐藏父类中所有同名成员函数
//如果想访问父类中被隐藏同名成员,需要加作用域

七、多继承

C++允许一个类继承多个类


语法:class 子类:继承方式_父类1,继承方式_父类2…

多继承可能会引发父类中有同名成员出现,需要加作用域区分

例:

class Son : public Base1, public Base2

当父类中出现同名成员,需要加作用域进行区分


八、菱形继承与虚继承

问题描述

当出现以下继承结构时:

     A
    / \
   B   C
    \ /
     D

特点:

  1. D类通过B、C两条路径继承A
  2. 导致D类中包含两份A的成员
  3. 产生数据冗余和二义性问题(即不知道D中的属性该是A的还是B的)

问题示例

class A {
public:
    int m_age;
};

class B : public A {};
class C : public A {};

class D : public B, public C {};

void test() {
    D d;
    // d.m_age = 10;          // 错误:ambiguous access
    d.B::m_age = 10;         // √ 明确指定路径
    d.C::m_age = 20;         // √ 不同路径不同存储位置
}

解决方案

使用虚继承: 此时A作为最大的继承,叫做虚基类

class B : virtual public A {};  // 虚继承
class C : virtual public A {};  // 虚继承

class D : public B, public C {};

注意事项

  1. 虚基类的构造函数由最终派生类直接调用
D(int age) : A(age), B(), C() {}  // 直接初始化虚基类
  1. 虚基类初始化优先级高于非虚基类
  2. 虚继承会增加内存访问开销(通过虚基表指针间接访问)
  3. 非必要不使用虚继承,优先考虑改进设计

九、虚继承内存结构详解

普通继承的内存问题

// 普通继承结构
class A { int a; };        // size=4
class B : public A {};     // size=4
class C : public A {};     // size=4
class D : public B, public C {};  // size=8

内存布局:
+---+---+
|B::A|C::A|  // 两份A的成员
+---+---+

虚继承解决方案

// 虚继承结构
class A { int a; };               // size=4
class B : virtual public A {};    // size=4+8=12(64位)
class C : virtual public A {};    // size=4+8=12(64位)
class D : public B, public C {};  // size=12+12+4=28 → 内存对齐后32

内存布局(64位系统):
+----------------+----------------+-----+
| B的虚基表指针   | C的虚基表指针   | A::a |
+----------------+----------------+-----+

虚基表指针工作原理

  1. 每个虚继承的类携带虚基表指针(vbptr——virtual base point tr(指针))) 
  2. ​**虚基表(vbtable)**存储偏移量信息
  3. 访问流程示例:
D d;
d.a = 10;  // 访问路径:
1. 通过B的vbptr找到虚基表
2. 查表得到A的偏移量(+16字节)
3. 访问实际存储位置

虚基表结构示例

// D类的虚基表内容
B的虚基表:
+-------------------+
| 0 (自身偏移量)     | → 通常为0
| 16 (A的偏移量)     |
+-------------------+

C的虚基表:
+-------------------+
| 0 (自身偏移量)     |
| 20 (A的偏移量)     | → 实际可能因对齐不同
+-------------------+

内存验证方法

使用VS开发者命令提示符:

cl /d1 reportSingleClassLayoutD test.cpp

输出示例:

class D size(32):
+---
 0  | +--- (base class B)
 0  | | {vbptr}
     | +---
 8  | +--- (base class C)
 8  | | {vbptr}
     | +---
16  | | 
20  | +--- (virtual base A)
20  | | a
     | +---

重要特性

特征 说明
内存开销 每个虚继承类增加一个指针大小(64位+8字节)
访问代价 多一次指针跳转
初始化特殊性 最终子类直接初始化虚基类
多级虚继承 虚基表会记录多级偏移量

AI整理版笔记


C++ 继承详解

一、基本语法

继承的好处

  1. 减少代码冗余
  2. 提高代码复用性
  3. 增强代码可维护性和扩展性
  4. 为多态奠定基础
// 基类(父类/超类)
class Parent {
public:
    void show() {
        cout << "I am a parent class." << endl;
    }
};

// 派生类(子类)
class Child : public Parent {  // 公有继承
public:
    void display() {
        show(); // 调用继承自父类的成员函数
        cout << "I am a child class." << endl;
    }
};

二、继承方式

继承方式 基类public成员 基类protected成员 基类private成员
public 仍为public 仍为protected 不可见
protected 变为protected 仍为protected 不可见
private 变为private 变为private 不可见

重要特性

  • class默认private继承,struct默认public继承
  • 基类private成员始终不可直接访问(可通过基类public/protected方法间接访问)

三、继承中的对象模型

内存模型特点

  1. 父类所有非静态成员都会被子类继承
  2. 父类私有成员被编译器隐藏,但仍存在于子类对象中
  3. 验证方法(VS开发者命令提示符):
cd /d 文件目录
cl /d1 reportSingleClassLayout类名 文件名.cpp

四、继承中构造和析构顺序

执行顺序

  1. 构造:基类构造 → 成员对象构造 → 派生类构造
  2. 析构:派生类析构 → 成员对象析构 → 基类析构

多继承构造顺序:按继承列表从左到右执行基类构造

class Son : public Base1, public Base2 {
    // 构造顺序:Base1() → Base2() → Son()
};

五、同名成员处理

访问规则

  1. 直接访问:优先访问子类成员
  2. 访问父类成员:使用作用域运算符
// 成员属性
s.Base::m_A; 

// 成员函数
s.Base::func();

// 函数重载特例
class Base {
public:
    void func(int a) { ... }
};

class Son : public Base {
public:
    void func() { ... }  // 隐藏基类所有func版本
};

Son s;
s.func();       // √ 正确
s.func(10);     // × 错误,基类重载被隐藏
s.Base::func(10); // √ 正确

六、同名静态成员处理

访问方式

// 通过对象访问
cout << s.Base::m_A; 

// 通过类名访问
cout << Son::Base::m_A;

// 静态成员函数
Son::Base::func();

重要特性:静态成员同样遵循名称隐藏规则,但内存中只存在一份基类静态成员副本


七、多继承

语法格式

class 派生类 : 继承方式 基类1, 继承方式 基类2... 
{
    // 类主体
};

注意事项

  1. 可能产生二义性(需使用作用域运算符解决)
  2. 菱形继承问题:
     A
    / \
   B   C
    \ /
     D

解决方案:虚继承

class B : virtual public A {...};
class C : virtual public A {...};
class D : public B, public C {...};

八、进阶特性

  1. 派生类初始化列表
Child(int a, int b) : Parent(a), m_b(b) {...}
  1. 类型转换
Parent* p = new Child();  // 向上转型(安全)
// Child* c = new Parent();  // 向下转型(危险,需要dynamic_cast)
  1. 虚函数与多态
class Parent {
public:
    virtual void show() { ... }  // 虚函数
};
  1. 友元与继承
  • 友元关系不可继承
  • 每个类的友元需要单独声明

九、注意事项

  1. 避免过度使用多继承
  2. 优先使用public继承
  3. 注意基类析构函数应声明为virtual(当有多态需求时)
  4. 使用using声明解决重载函数隐藏问题:
class Son : public Base {
public:
    using Base::func;  // 暴露基类func重载
    void func() { ... }
};

你可能感兴趣的:(c++,算法,前端)