C++继承与多态进阶

C++模板进阶与继承:从动态多态到静态多态的设计范式迁移 在C++编程中,泛型编程和面向对象编程是两大核心范式。泛型编程通过模板实现,允许编写与类型无关的代码,在编译时生成特定类型版本,提升代码复用性和类型安全。面向对象编程则依赖继承机制,建立类之间的层次关系,实现代码复用和运行时多态。当这两者结合,可以创造出更强大的编程范式,例如用模板技术优化传统的继承体系。其核心价值在于实现静态多态(编译时多态),通过编译期类型检查和函数绑定,消除虚函数调用的运行时开销,同时保持多态性的灵活性。这在需要极致性能的场景下尤为重要,例如实现类型安全的异构容器、编译期策略选择等。具 阅读详情

目录


一、引言

这篇文章将基于 SGI-STL 源码分析,带你深入 C++ 继承与多态的底层世界。文章会从菱形虚拟继承的内存模型讲到单继承和多继承的虚函数表结构,力求让你读完后有一种"原来如此"的通透感。


二、菱形虚拟继承原理深度剖析

2.1 问题起源:菱形继承的"致命缺陷"

在 C++ 中,一个类可以同时继承多个父类,这就是多继承。多继承带来了灵活性,但也引入了一个经典问题——菱形继承(Diamond Inheritance)。

看下面这个继承体系:

class Person {
public:
    string _name;  // 姓名
};

class Student : public Person {
protected:
    int _num;      // 学号
};

class Teacher : public Person {
protected:
    int _id;       // 职工编号
};

class Assistant : public Student, public Teacher {
protected:
    string _majorCourse;  // 主修课程
};

这个继承体系形成了一个菱形结构:

            Person
           /      \
      Student    Teacher
           \      /
         Assistant

问题一:二义性(Ambiguity)

Assistant a;
a._name = "peter";  // 编译错误!编译器不知道访问哪一个 _name

因为 Student 和 Teacher 各自继承了一份 Person 的 _name,所以在 Assistant 对象中存在两份 _name。编译器无法确定你指的是 Student::_name 还是 Teacher::_name。

问题一的"半解决"——显式指定作用域:

a.Student::_name = "xxx";
a.Teacher::_name = "yyy";

但这只是解决了二义性,数据冗余的问题依然存在——同一个人的姓名被存了两份!这显然不合理。

2.2 虚继承是如何解决问题的

C++ 引入了虚继承(Virtual Inheritance)来解决菱形继承的数据冗余和二义性问题。关键字是 virtual:

// 关键改动:Student 和 Teacher 虚继承 Person
class Student : virtual public Person {
protected:
    int _num;
};

class Teacher : virtual public Person {
protected:
    int _id;
};

class Assistant : public Student, public Teacher {
protected:
    string _majorCourse;
};

核心区别:使用虚继承后,Person 这个虚基类在 Assistant 对象中只存在一份。

此时再访问 _name 就没有二义性了:

Assistant a;
a._name = "peter";  // 成功!只有一份 _name

2.3 深入内存模型——见证真相的时刻

重要提示:VS 编译器的监视窗口经过了特殊处理,它展示的对象模型是"美化"过的,并不是真实的内存布局。要看清真实的内存模型,必须借助内存窗口(快捷键:调试时 Ctrl+Alt+M,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;
};

通过内存窗口观察 D 对象的真实布局,我们发现:

D 对象内存模型(概念图):

  B 部分:
  +----------------------+
  |  vbptr_B  -> 虚基表B |  <- 指向虚基表(存储偏移量)
  |  _b                  |  <- B 自己的成员
  +----------------------+
  C 部分:
  |  vbptr_C  -> 虚基表C |  <- 指向虚基表(存储偏移量)
  |  _c                  |  <- C 自己的成员
  +----------------------+
  |  _d                  |  <- D 自己的成员
  +----------------------+
  A 部分(公共的虚基类):
  |  _a                  |  <- 只有一份!
  +----------------------+

关键发现:

  1. B 和 C 部分各自包含一个虚基表指针(vbptr),指向各自的虚基表(virtual base table);
  2. 虚基表中存储的是当前部分距离公共虚基类 A 的偏移量;
  3. 公共的虚基类 A 部分在 D 对象中只有一份——这就是数据冗余被消除的原因。

2.4 虚基表指针的统一访问方式

一个非常精妙的设计细节:不仅是 D 对象,B 自己的对象模型也采用了同样的虚基表访问方式。

B b;
b._a = 7;  // B 对象访问 _a 同样通过虚基表指针
b._b = 8;

D d;
d._a = 3;
d._b = 4;

B* p1 = &d;  // B 的指针指向 D 对象(切片)
B* p2 = &b;  // B 的指针指向 B 对象

当我们打开汇编代码观察时,会发现:

p1->_a++;  // 通过虚基表指针 -> 查偏移量 -> 访问 _a
p2->_a++;  // 通过虚基表指针 -> 查偏移量 -> 访问 _a

两条语句生成的汇编代码完全相同! 这就是统一访问方式的精妙之处——无论 B 的指针指向的是 B 自己还是 D 的切片,访问 _a 成员都通过虚基表指针来定位。这种一致性保证了指针切片后的正确行为。

2.5 动手验证——写出菱形虚拟继承模型

完整验证代码:

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;  // 通过 B 的虚基表访问 _a
    d.C::_a = 2;  // 通过 C 的虚基表访问 _a —— 但这是同一个 _a!
    d._a = 3;      // 最终 _a 的值是 3(前面的 1 和 2 都被覆盖了)
    d._b = 4;
    d._c = 5;
    d._d = 6;

    // 验证:B 对象也使用虚基表访问 A
    B b;
    b._a = 7;
    b._b = 8;

    B* p1 = &d;  // 指向 D 对象(切片视角)
    B* p2 = &b;  // 指向 B 对象
    p1->_a++;    // p1 和 p2 的 _a 访问方式是相同的
    p2->_a++;

    return 0;
}

小结:菱形虚拟继承通过虚基表(存储偏移量)实现了公共基类的唯一存储。B 和 C 的虚基表中记录了各自部分到公共 A 的偏移距离,无论指针如何切片,都能正确定位到虚基类成员。


三、虚函数表深入探索——揭开多态的神秘面纱

3.1 重温多态与虚函数表的基础

回顾多态的基础知识:

  • 带有虚函数的类,每个对象的前 4 个字节(32 位)或 8 个字节(64 位)存储一个虚函数表指针(vfptr),指向该类的虚函数表(vftable)。
  • 虚函数表本质是一个函数指针数组,存储了该类所有虚函数的地址。
  • 当通过基类指针/引用调用虚函数时,运行时会从虚表中查找实际应该调用的函数——这就是多态的底层实现。

C++ 祖师爷 Bjarne Stroustrup 设计虚函数机制的目标:在不牺牲效率的前提下,支持运行时多态。虚函数表的引入,使得多态调用的额外开销仅为一次间接寻址。

然而,VS 的监视窗口并不会展示虚表的全貌。为了看到真相,我们需要直接读内存。

3.2 单继承虚函数表——监视窗口骗了你

先看单继承的场景:

class Base {
public:
    virtual void func1() { cout << "Base::func1" << endl; }
    virtual void func2() { cout << "Base::func2" << endl; }
private:
    int a;
};

class Derive : public Base {
public:
    virtual void func1() { cout << "Derive::func1" << endl; }  // 重写 func1
    virtual void func3() { cout << "Derive::func3" << endl; }  // 新增虚函数
    virtual void func4() { cout << "Derive::func4" << endl; }  // 新增虚函数
    void func5() { cout << "Derive::func5" << endl; }           // 普通函数
private:
    int b;
};

在监视窗口中,你可能只能看到 func1 和 func2,而 func3 和 func4 却不翼而飞了!

真相是:func3 和 func4 就在虚表中,只是被监视窗口"隐藏"了。下面我们写一段代码,直接读取对象内存,强行打印虚函数表:

typedef void(*VFPTR)();  // 定义虚函数指针类型

void PrintVTable(VFPTR vTable[]) {
    cout << " 虚表地址>" << vTable << endl;
    // VS 下虚表末尾以 nullptr 结尾,G++ 下则不一定
    for (int i = 0; vTable[i] != nullptr; ++i) {
        printf(" 第%d个虚函数地址:0X%p,->", i, vTable[i]);
        VFPTR f = vTable[i];
        f();  // 直接调用,看看打印的是什么
    }
    cout << endl;
}

int main() {
    Base b;
    Derive d;

    // 32位程序的访问思路:
    // 1. 取对象地址,强转为 int*,解引用取到前 4 字节的值 -> 这就是虚表指针
    // 2. 将虚表指针强转为 VFPTR* -> 虚函数指针数组
    // 3. 传给 PrintVTable 打印

    VFPTR* vTable1 = (VFPTR*)(*(int*)&b);
    PrintVTable(vTable1);
    // 输出:
    // 虚表地址>0x...
    // 第0个虚函数地址:0x...,->Base::func1
    // 第1个虚函数地址:0x...,->Base::func2

    VFPTR* vTable2 = (VFPTR*)(*(int*)&d);
    PrintVTable(vTable2);
    // 输出:
    // 虚表地址>0x...
    // 第0个虚函数地址:0x...,->Derive::func1  <- 重写的
    // 第1个虚函数地址:0x...,->Base::func2   <- 未重写,保持基类的
    // 第2个虚函数地址:0x...,->Derive::func3  <- Derive 新增的!
    // 第3个虚函数地址:0x...,->Derive::func4  <- Derive 新增的!

    return 0;
}

关键发现:

  1. 重写的虚函数会替换掉虚表中原来基类虚函数的地址(func1 从 Base:: 变成了 Derive::);
  2. 未重写的虚函数保持基类的地址不变(func2 仍然是 Base::func2);
  3. 派生类新增的虚函数会被追加到虚表末尾(func3 和 func4 都在表中);
  4. func5 是普通成员函数,不在虚表中。

踩坑提醒:这段强制打印虚表的代码有时会崩溃。原因是编译器在许多场景下不会在虚表末尾放置 nullptr(VS 调试模式下会放,但有时"不干净"),导致循环越界。解决方法是:点击菜单栏 生成 -> 清理解决方案,再重新编译运行即可。

3.3 多继承虚函数表——更复杂的真相

多继承时的虚表结构更为复杂。看下面的例子:

class Base1 {
public:
    virtual void func1() { cout << "Base1::func1" << endl; }
    virtual void func2() { cout << "Base1::func2" << endl; }
private:
    int b1;
};

class Base2 {
public:
    virtual void func1() { cout << "Base2::func1" << endl; }
    virtual void func2() { cout << "Base2::func2" << endl; }
private:
    int b2;
};

class Derive : public Base1, public Base2 {
public:
    virtual void func1() { cout << "Derive::func1" << endl; }  // 同时重写两个基类的 func1
    virtual void func3() { cout << "Derive::func3" << endl; }  // 新增虚函数
private:
    int d1;
};

多继承的内存布局:

Derive 对象内存模型:

  Base1 部分(先继承,放在前面):
  +----------------------+
  |  vfptr1 -> 虚表1     |  <- Base1 的虚函数表指针
  |  b1                  |
  +----------------------+
  Base2 部分(后继承,放在后面):
  |  vfptr2 -> 虚表2     |  <- Base2 的虚函数表指针
  |  b2                  |
  +----------------------+
  |  d1                  |  <- Derive 自己的成员
  +----------------------+

关键规律:

  1. 多继承时,Derive 对象包含多张虚表——继承了几个带虚函数的基类,就有几张虚表;
  2. 派生类新增的虚函数放在先继承的基类的虚表中——例如 func3 放在 Base1 的虚表末尾;
  3. 重写的虚函数 func1 同时替换了两张虚表中对应的位置。

验证代码:

int main() {
    Derive d;

    // 打印第一张虚表(Base1 部分)
    VFPTR* vTableb1 = (VFPTR*)(*(int*)&d);
    PrintVTable(vTableb1);
    // 第0个: Derive::func1  <- 重写了 Base1::func1
    // 第1个: Base1::func2   <- 未重写
    // 第2个: Derive::func3  <- Derive 新增,放在先继承的 Base1 虚表中

    // 打印第二张虚表(Base2 部分)
    // Base2 在内存中紧挨着 Base1,需要偏移 sizeof(Base1)
    VFPTR* vTableb2 = (VFPTR*)(*(int*)((char*)&d + sizeof(Base1)));
    PrintVTable(vTableb2);
    // 第0个: ???           <- 注意!这个地址和 vTableb1[0] 不一样!
    // 第1个: Base2::func2  <- 未重写

    return 0;
}

3.4 多继承中的 this 指针修正——最精妙的细节

细心的同学会发现:Derive 重写的 func1 在两张虚表中的地址居然不一样!这是为什么?

Base1* p1 = &d;
p1->func1();  // 多态调用

Base2* p2 = &d;
p2->func1();  // 多态调用

d.func1();    // 普通调用(不经过虚表)

真相揭秘:

当 Base2* p2 = &d; 时,由于 Base2 部分在 Derive 对象中间位置,指针会发生偏移——p2 指向的是 Base2 子对象的起始位置,而不是 Derive 对象的起始位置。

当多态调用 p2->func1() 时:

  1. func1 是被 Derive 重写的,函数体内的 this 应该指向 Derive 对象的开头;
  2. 但 p2 指向的是 Base2 子对象的开头(发生了偏移);
  3. 所以在调用前,this 指针需要被修正回去。

解决方案:Base2 虚表中的 func1 地址并不是真正的 Derive::func1,而是一个包装过的 thunk 函数(或者是一条调整指令),它的工作是:

1. 先将 this 指针修正回 Derive 对象的起始地址
2. 再跳转到真正的 Derive::func1

这个过程涉及相当的汇编知识。如果你有兴趣深究,可以在 VS 中打断点,按 Ctrl+F11 查看反汇编代码,会看到 sub ecx, N 这样的 this 调整指令。

这就是为什么两张虚表中 func1 的地址不同——Base1 虚表中的直接指向 Derive::func1(因为 Base1 就在 Derive 开头,不需要修正),而 Base2 虚表中的指向了一个 thunk(因为需要先修正 this)。

3.5 菱形继承与菱形虚拟继承的虚表

温馨提示:菱形继承 + 虚函数的组合极为复杂,实际开发中强烈不推荐设计出这样的继承体系。一方面太复杂容易出 bug,另一方面访问基类成员有一定的性能损耗(需要间接查表)。

这里给出一个菱形虚拟继承 + 虚函数的示例代码,你可以通过内存窗口自行观察:

class A {
public:
    virtual void func1() {}
public:
    int _a;
};

class B : virtual public A {
public:
    virtual void func1() {}
    virtual void func2() {}
public:
    int _b;
};

class C : virtual public A {
public:
    virtual void func1() {}
    virtual void func3() {}
public:
    int _c;
};

class D : public B, public C {
public:
    virtual void func1() {}  // 重写
    virtual void func4() {}  // 新增
public:
    int _d;
};

如果好奇心强想深入研究的同学,推荐阅读:


四、和多态高频面试题精讲

以下是 C++ 面试中关于继承和多态的高频考题,我将每个问题的核心要点提炼出来:

Q1: 什么是多态?

答:多态就是同一个接口,不同的对象调用产生不同的行为。在 C++ 中,多态通过虚函数实现。当基类指针或引用指向派生类对象,并调用虚函数时,实际调用的是派生类中重写的版本——这就是运行时多态。

Q2: 什么是重载、重写(覆盖)、重定义(隐藏)?

概念作用域关键字要求
重载(Overload)同一作用域无函数名相同,参数不同
重写(Override)基类/派生类virtual函数名、参数、返回值完全相同,必须是虚函数
重定义/隐藏(Hide)基类/派生类无派生类的函数名和基类相同就构成隐藏

Q3: 多态的实现原理?

答:虚函数表(vftable) + 虚函数表指针(vfptr)。

  • 每个含有虚函数的类有一张虚函数表,存储虚函数地址;
  • 每个对象的前几个字节存储指向该虚函数表的指针;
  • 调用虚函数时,运行时从虚表中找到实际函数地址再调用。

Q4: inline 函数可以是虚函数吗?

答:可以。但如果一个 inline 函数被声明为虚函数,编译器会忽略其 inline 属性,这个函数就不再是内联的。因为虚函数需要放入虚函数表中(需要取地址),而 inline 函数是直接在调用处展开的,二者不能同时存在。

Q5: 静态成员函数可以是虚函数吗?

答:不能。原因有二:

  1. 静态成员函数没有 this 指针,无法通过对象去访问虚函数表;
  2. 静态成员函数可以通过 类名::函数名 直接调用,这种调用方式完全绕过了虚表机制。

Q6: 构造函数可以是虚函数吗?

答:不能。因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的。如果构造函数是虚函数,那么在构造之前就需要通过虚表查找它——但此时虚表指针还未初始化,形成了"鸡生蛋、蛋生鸡"的悖论。

Q7: 析构函数可以是虚函数吗?

答:可以,而且强烈建议将基类的析构函数定义为虚函数。

经典场景:

Base* p = new Derive();
delete p;  // 如果 ~Base() 不是虚函数,则只会调用 ~Base(),造成资源泄漏!

如果基类析构函数是虚函数,delete p 时会从虚表中查找实际析构函数,正确调用 ~Derive() -> ~Base() 的完整析构链。

判断准则:如果一个类被设计为基类(会被继承),那么它的析构函数就应该声明为虚函数。

Q8: 对象访问普通函数快还是虚函数更快?

答:分两种情况:

  • 普通对象调用(非指针/引用):一样快。因为编译器在编译期就能确定调用哪个函数,不涉及虚表查找。
  • 指针/引用调用:普通函数更快。因为构成多态时,运行时需要通过虚函数表间接查找函数地址,多了一次间接寻址的开销。

Q9: 虚函数表是什么阶段生成的,存在哪里?

答:

  • 生成时机:编译阶段就生成了;
  • 存储位置:一般情况下存在代码段(常量区/只读数据段)。

Q10: C++ 菱形继承的问题?虚继承的原理?

答:

  • 菱形继承问题:数据冗余(同一份基类数据存多份)+ 二义性(编译器无法确定访问哪个基类的成员);
  • 虚继承原理:通过虚基表(virtual base table)存储偏移量,使得公共虚基类在最终派生类对象中只存在一份。访问虚基类成员时,通过虚基表指针查找偏移量来定位。
  • 注意:虚函数表(vftable)和虚基表(vbtable)是两个完全不同的概念,面试时不要混淆!

Q11: 什么是抽象类?抽象类的作用?

答:

  • 抽象类:包含纯虚函数(virtual void func() = 0;)的类。抽象类不能实例化。
  • 作用:
    1. 强制派生类重写虚函数——派生类必须实现纯虚函数才能被实例化;
    2. 体现接口继承关系——抽象类定义接口规范,派生类提供具体实现。这是面向对象设计中"只继承接口,不继承实现"的最佳实践。

六、总结与思考

本文核心知识图谱

C++ 继承与多态底层核心
|
+-- 菱形虚拟继承
|   +-- 问题:数据冗余 + 二义性
|   +-- 解决方案:virtual 继承
|   +-- 核心机制:虚基表(存储偏移量)
|   +-- 设计亮点:统一的虚基表访问方式
|
+-- 虚函数表(单继承 & 多继承)
|   +-- 单继承:一张虚表,新增虚函数追加到末尾
|   +-- 多继承:多张虚表,新虚函数放在先继承的基类虚表中
|   +-- this 指针修正:多继承中通过 thunk 修正 this
|
+-- 反向迭代器适配器
|   +-- 模式:适配器设计模式
|   +-- 核心:operator* 访问前一个位置
|   +-- 对称设计:rbegin()<->end(), rend()<->begin()
|
+-- 高频面试题(11题全覆盖)
    +-- 虚函数 vs 内联 vs 静态
    +-- 构造/析构与虚函数
    +-- 虚表生成时机与存储位置
    +-- 抽象类的设计意义

几点深度思考

  1. "零开销抽象"的代价:C++ 的设计哲学是"不为不用的特性付出代价"。虚继承和多态的额外开销(指针间接访问、内存占用)只在真正使用这些特性时才会产生。这是 C++ 相比 Java/C# 等语言的重要优势。

  2. 不要混淆虚函数表和虚基表:两者虽然都以 “virtual” 为前缀,都涉及指针查表,但服务于完全不同的目的——前者服务于多态调用(运行时确定函数地址),后者服务于虚基类成员访问(确定偏移量)。面试时这是必考点!

  3. 简单的设计才是好的设计:菱形继承虽然 C++ 支持,但实际项目中应尽量避免设计菱形继承体系。同样,反向迭代器的适配器模式告诉我们:用组合 + 委托比用复杂继承更容易写出清晰、可维护的代码。

  4. 面试背后的考察点:那些看似刁钻的面试题(“构造函数可以是虚函数吗”),其实考察的是你对对象构造过程、虚表初始化时机等底层机制的深入理解。死记硬背答案是不够的——理解"为什么"才是关键。


如果对你有帮助,欢迎点赞、收藏、关注三连支持!

C++进阶六:继承和多态 一个类里面定义了虚函数,那么编译阶段,编译器给这个类类型产生一个唯一的vftable虚函数表,虚函数表中主要存储的内容就是RTTI指针(类类型信息指针)和虚函数的地址。当程序运行时,每一张虚函数表都会加载到内存的.rodata区。 阅读详情

相关推荐

docker安装fastdfs+nginx配置

docker安装fastdfs+nginx,实现图片或文件的上传与下载

weixin_47325126的博客 2191

C++之继承与多态(含模版进阶)

非类型模版参数,模版特化,继承,多态

2302_81120572的博客 1039

FANUC机器人电弧跟踪功能深度解析:从电流采样原理到ROBOGUIDE仿真避坑指南

本文深度解析FANUC机器人电弧跟踪技术,从电流采样原理到ROBOGUIDE仿真避坑指南,详细介绍了电流波形分析、实时补偿机制及现场调试技巧。针对工业焊接中的常见问题,提供从底层原理到高阶应用的解决方案,帮助工程师优化自动化焊接质量。

weixin_31785177的博客 547

C++进阶:继承和多态

面向对象编程中的一个核心概念,它允许对象以多种形式出现,并通过相同的接口来调用不同的实现。在 C++ 中,多态通常通过函数重载、运算符重载和虚函数来实现。多态的主要类型包括编译时多态(静态多态)和运行时多态(动态多态)。简单来说多态就是:就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。例如:每个动物都有叫声,我们将叫声写为一个函数,然后每个动物去继承这个 动物类,每个动物去调用叫声这个函数都会得到不同的结果,这就是多态。

2301_79969994的博客 1864

从零开始编写一个上位机(串口助手)QT Creator + C++

提示:本博客作为学习笔记,有错误的地方希望指正,此文可能会比较长,作为学习笔记的积累,希望对来着有帮助。   绪论:笔者这里使用的是QTCreator和C++来实现一个简单的串口上位机的开发的简单过程,使用到C++,主要是为了后面使用Python开发上位机打下基础。这里主要分为初识C++的一些基本知识,其次是QT Creator的使用。特别声明感谢【北京迅为】嵌入式学习之QT学习篇的学习视频,这里的学习笔记就是根据视频中的内容实战记录下来的。 文章目录一、C++知识1、初识C++2、C++输入输出控制2.1

believe666的博客 4万+

ControlCAN.dll开发适配USBCAN_E/2E_U设备

摘要:本文提供了USBCAN_E/2E_U设备的配置指南:1)安装专用驱动;2)获取最新的ControlCAN.dll文件;3)修改VCI调用顺序,新增VCI_SetReference函数设置波特率;4)使用工具计算波特率配置值,并给出了常见波特率对应的代码参考值(如500K对应0x060007)。配置过程强调使用特定工具计算精确的波特率参数,并提供了相关软件下载链接。

weixin_43969033的博客 387

【C++进阶】继承、多态的详解(继承篇)

本篇博客主要介绍C++进阶部分内容——继承,C++中的继承和多态是比较复杂的,需要我们认真去深挖其中的细节。

m0_74215144的博客 2289

vc mfc listctrl使用虚拟列表 虚拟列表用法

note 虚拟列表可有效加载大量数据 需要处理listctrl的 LVN_GETDISPINFO 消息 使用虚拟列表 不能 使用 insert的方法插入数据,而是告诉listctrl 当前要显示多少行 使用SetItemCount函数告诉listctrl当前显示多少行 使用虚拟列表需要自己维护数据源 使用 添加list ctrl 控件 将其Owner Data 属性 设置为 True 添加...

知其所以然 397

《C++进阶之继承多态》【普通类/模板类的继承 + 父类&子类的转换 + 继承的作用域 + 子类的默认成员函数】

C++ 继承机制详解 摘要 本文深入讲解 C++ 继承机制,主要内容包括: 继承的基本概念与使用方式、继承的三种访问权限(public/protected/private) 父类与子类的类型转换规则、继承中的作用域问题、子类默认构造函数的特性。通过学生类和教师类的设计案例,展示了如何通过继承消除代码冗余并实现代码复用。文章详细分析了继承格式、基类/派生类关系以及继承方式对成员访问权限的影响,为理解 C++ 面向对象编程的核心机制提供了清晰的指导。

序属秋秋秋的博客 2093

高通拨号流程介绍

本文介绍了高通平台(如SDX55)中APSS(A7处理器)与ModEM(Q6处理器)间的通信机制。

四儿家的小祖宗的博客 1067

沉降预测算法-指数曲线法

沉降预测的指数曲线法原理和代码实现

yh523的博客 1008

【C++进阶】继承、多态的详解(多态篇)

本篇博客主要介绍C++中多态有关的知识,是C++中的一大难点,刚子将带你深入C++多态的知识

m0_74215144的博客 1040

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

【C++进阶】2.继承与多态详解

子类虚函数表继承父类的虚函数表后,有虚函数,会在生成一个虚函数,并覆盖父类继承下来的地址,因此子类虚函数表包含:继承父类的、子类重写的、子类生成的地址。子类的虚函数表先会继承父类的虚函数表,但和父类的虚函数表不是同一个(地址不同),可以类比于父类和子类的元素,名字相同,但不是同一个。如果没有构造函数,想用编译器生成的,那也要在初始化列表写空括号,此时自定义类型调用默认构造,内置类型随机。运行时,根据指向对象的虚函数表中找到虚函数地址,进行调用,因此,只有在运行时才能确定地址。

m0_68617924的博客 992

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

《C++高级进阶教程》 第八章继承与多态

目录 8.1 私有成员会被继承吗 8.2 怎样理解构造函数不会被继承 8.3 什么是虚拟继承 8.4 怎样编写一个不能被继承的类 8.5 关于隐藏 8.6 什么是RTTI 8.7虚调用的几种具体情况 8.8不要在构造函数或者析构函数中调用虚函数 8.9 虚函数可以是私有的吗 8.10 动态联编是怎么实现的 8.11 !操作符重载 8.12 []操作符重载 8.13...

席行飞工作室 307

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

《C++进阶之继承多态》【多态:概念 + 实现 + 拓展 + 原理】

本文介绍了C++中多态的概念、实现方式和底层原理。文章分为静态多态和动态多态两类。重点讲解了动态多态的实现条件:继承关系、虚函数重写和基类指针/引用调用。还介绍了协变返回类型等细节,并分析了虚函数表的多态实现机制。全文系统梳理了多态从概念到实践的关键知识点。

序属秋秋秋的博客 1400

C++进阶篇:继承与多态一篇拿下

多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。比如Student继承了Person。Person对象买票全价,Student对象买票半价。那么在继承中要构成多态还有两个条件1.必须通过基类的指针或者引用调用虚函数2.被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。

MaoRuofeng的博客 1268

C++面向对象进阶——封装、继承与多态深度实战

本文深入讲解C++面向对象编程的三大核心特性:封装、继承与多态。通过对比过程化编程与面向对象编程的差异,突出OOP在代码复用性、可维护性和可扩展性方面的优势。重点解析了封装如何通过访问控制符实现数据隐藏与接口暴露,并给出银行账户类的安全封装示例。文章还详细介绍了继承机制和多态原理,包括虚函数表、动态绑定等底层实现,帮助读者彻底掌握面向对象编程的核心思想与实战技巧。

今天比昨天更努力一点! 9514

【C++进阶之路】继承与多态的概念考察

继承与多态面试常考概念题与问答题。

Shun_Hua的博客 656

C++进阶--继承和多态常见题目

B和C都是虚继承,D继承B和C,那么D也是虚继承,整个继承体系就一份A,针对D对象,对于A的初始化不在B也不在C中,所以初始化列表的B、C、A中,首先应该初始化的就是A。版本二:当子类继承了父类的虚函数并完成重写,则就构成了多态,其底层是父类和子类都有一个虚表指针指向一个虚表,这个虚表是用来存放虚函数的地址的(不是真正的地址,可以理解为间接地址),当父类的指针和引用来调用虚函数时,取决于对象本身(即接收的父类就调用父类,接收的子类就调用子类),父类和子类就会分别去各自的虚表指针里找到相应的虚函数。

sushang的博客 1524

《C++进阶之继承多态》【final + 继承与友元 + 继承与静态成员 + 继承模型 + 继承和组合】

C++继承机制深入解析 本文系统探讨了C++继承相关的五个核心主题:final关键字、继承与友元、继承与静态成员、继承模型以及继承与组合的关系。 本文通过代码示例和理论分析,深入浅出地讲解了这些继承相关的重要概念,对理解C++面向对象编程具有重要参考价值。

序属秋秋秋的博客 1506

C++ 进阶:深入理解虚函数、继承与多态

在 C++ 的面向对象编程中,继承和多态是两个核心概念。今天我们将深入探讨 C++ 中与多态密切相关的几个重要特性:虚函数、virtual关键字、override关键字、多重继承以及虚继承。这些内容是理解 C++ 多态机制和复杂类层次结构的关键。虚函数和virtual关键字:实现运行时多态,通过虚函数表实现函数调用。override关键字:明确表示重写基类虚函数,提高代码可读性和防止拼写错误。多重继承:允许派生类同时继承多个基类,但可能带来菱形继承和命名冲突问题。虚继承。

坏柠的博客 692

C++进阶之继承与多态篇 (全)

继承是面向对象程序设计使代码可以复用的最重要的手段。它允许我们在保持原有类特性的基础上进行扩展,增加方法和属性,从而产生新的类(派生类)。// 没有继承之前:Student和Teacher有大量重复代码public:void identity() { /* 身份认证 */ }void study() { /* 学习 */ }protected:// 姓名// 地址// 电话int _age;// 年龄int _stuid;

kalsiw的博客 293

C++ 多态的进阶应用:抽象类、继承与组合的选择

本文深入探讨了C++多态的进阶应用,重点分析了抽象类与接口继承的特性区别,指出抽象类通过纯虚函数强制派生类实现接口规范。同时对比了继承(is-a关系)与组合(has-a关系)两种代码复用方式的优缺点,强调优先使用组合以降低耦合度。最后提出优秀的设计需要结合抽象类、多态和组合,构建出灵活可扩展的系统架构,遵循开闭原则和合成复用原则,实现代码的高效维护与扩展。

kongsicheng10的博客 807

【C++进阶】继承与多态详解

但是,如姓名、地址等都是两者共同的,我们就可以将相同的放在一个类。适配器,因此可以直接继承,复用 vector。是 vector 里的,叫它一起实例化掉。首先,假设在一个系统里有老师、学生的信息。代码语言:javascript。里,师生可以共同继承这个类。代码语言:javascript。大概率为专门为了继承而来的。代码语言:javascript。,在类之后加一个类。

m0_74824496的博客 546
上一篇: C++栈与队列进阶实战
下一篇: C++哈希扩展实战
jinyishu_
博客等级 码龄1年 53粉丝 82原创
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值