多继承:

多继承与同名成员处理:

如果子类重定义了父类的同名函数,不管参数是否相同,子类都将屏蔽父类的同名函数。如果非要想访问同名函数,需要加作用域(无论是数据成员还是成员函数都沿用此办法)

继承-菱继承:

继承-虚继承:
#include <iostream>

// 基类 Base
class Base {
public:
    int baseValue;
    Base(int val = 0) : baseValue(val) {
        std::cout << "Base 构造函数, 地址: " << this << ", baseValue: " << baseValue << std::endl;
    }
    virtual ~Base() {
        std::cout << "Base 析构函数, 地址: " << this << std::endl;
    }
    void printBaseValue() const {
        std::cout << "Base::baseValue = " << baseValue << std::endl;
    }
};

// 派生类 Derived1 虚继承 Base
class Derived1 : virtual public Base {
public:
    int d1Value;
    Derived1(int d1Val = 0, int baseVal = 0)
        : Base(baseVal), d1Value(d1Val) { // 注意:Base构造函数在这里被调用,但实际由MostDerived负责
        std::cout << "Derived1 构造函数, 地址: " << this << ", d1Value: " << d1Value << std::endl;
    }
};

// 派生类 Derived2 虚继承 Base
class Derived2 : virtual public Base {
public:
    int d2Value;
    Derived2(int d2Val = 0, int baseVal = 0)
        : Base(baseVal), d2Value(d2Val) { // 注意:Base构造函数在这里被调用,但实际由MostDerived负责
        std::cout << "Derived2 构造函数, 地址: " << this << ", d2Value: " << d2Value << std::endl;
    }
};

// 最终派生类 MostDerived 多重继承 Derived1 和 Derived2
// 由于 Base 是虚基类,MostDerived 负责构造它,并确保只有一份实例。
class MostDerived : public Derived1, public Derived2 {
public:
    int mdValue;
    MostDerived(int mdVal = 0, int d1Val = 0, int d2Val = 0, int baseVal = 0)
        : Base(baseVal),      // 最终派生类负责初始化虚基类
          Derived1(d1Val, baseVal), // Derived1中的Base构造函数调用会被忽略
          Derived2(d2Val, baseVal), // Derived2中的Base构造函数调用会被忽略
          mdValue(mdVal) {
        std::cout << "MostDerived 构造函数, 地址: " << this << ", mdValue: " << mdValue << std::endl;
    }
};

int main() {
    MostDerived obj(100, 10, 20, 5); // 构造一个MostDerived对象

    std::cout << "--- 访问共享基类成员 ---" << std::endl;
    obj.printBaseValue(); // 直接访问 Base 的成员
    std::cout << "通过 obj.baseValue 访问: " << obj.baseValue << std::endl;

    std::cout << "--- 验证 Base 子对象地址一致性 ---" << std::endl;
    // 获取不同路径下的 Base 指针
    Base* basePtrFromMostDerived = &obj; // 从 MostDerived 对象直接转换为 Base*
    Derived1* d1Ptr = &obj;
    Base* basePtrFromDerived1 = d1Ptr; // 从 Derived1* 转换为 Base*
    Derived2* d2Ptr = &obj;
    Base* basePtrFromDerived2 = d2Ptr; // 从 Derived2* 转换为 Base*

    std::cout << "通过 MostDerived 获取 Base 地址: " << basePtrFromMostDerived << std::endl;
    std::cout << "通过 Derived1 获取 Base 地址: " << basePtrFromDerived1 << std::endl;
    std::cout << "通过 Derived2 获取 Base 地址: " << basePtrFromDerived2 << std::endl;

    // 输出将显示所有 Base 地址都相同,证明 Base 子对象只存在一份。
    return 0;
}

多态:

向上类型转换:用一个父类的 指针/引用 来保存一个子类空间的地址

形参是一个父类的指针或者引用
实参是子类的对象

这里需要引入一个绑定概念  向上类型转换就是早绑定决定的 早绑定:把函数体与函数调用相联系称为绑定(捆绑,binding) 当绑定在程序运行之前(由编译器和连接器)完成时,称为早绑定(early binding)

早绑定会造成无法调用子类,只能输出父类的值,所以要将早绑定修改为晚绑定(迟绑定)[迟绑定又称为运行时绑定才能确定具体要调用哪一个对象的函数]

实现迟绑定用虚函数:将父类中的函数写成虚函数 在函数前加一个关键字:virtual 
如果父类中的函数是虚函数,那么子类重写虚函数以后,子类中的成员函数默认也就是虚函数
所以子类加不加关键字(
virtual )都行

多态-抽象基类&纯虚函数:

纯虚函数就像一个接口,被称为抽象基类

当一个类包含至少一个纯虚函数时,它就被称为抽象基类(Abstract Base Class, ABC)。抽象基类不能被直接实例化(即不能创建其对象)。它的主要目的是为了定义一个接口规范,强制所有继承它的子类必须实现这些纯虚函数,从而确保子类拥有某些特定的行为

纯虚函数: 很多时候在继承关系中,父类中的成员函数一般不需要写实际的实现代码,只需要作为向上类型转换来使用。只想让父类作为一个接口使用,不希望去执行父类中的函数体的内容基于这种情况,我们就可以让父类的虚函数作为纯虚函数使用,就是只有声明,不需要函数体.

如何定义纯虚函数?   函数要使用virtual关键字   还要在函数后使用=0,表示这个函数是没有函数体的,纯虚函数没有函数体
 

//virtual void driver() = 0;  // 这就是一个纯虚函数

如果一个类中存在至少一个纯虚函数,那么这个类就被叫做抽象类
抽象类不能被实例化

如果一个类继承了一个抽象类,那么子类中必须重写所有的纯虚函数,否则子类就也是一个抽象类

在一些编程语言中,我们也会将抽象类叫做 接口

建立公共接口(抽象类)目的是为了将子类公共的操作抽象出来,可以通过一个公共接口来 操纵一组类,且这个公共接口不需要事先(或者不需要完全实现)。可以创建一个公共类.

模板方法模式:抽象类

例子中子类请记住这句话:如果一个类继承了一个抽象类,那么子类中必须重写所有的纯虚函数,否则子类就也是一个抽象类

class AbstractDrinking{ 

public: 

//烧水

virtual void Boil() = 0; 

//放入咖啡/茶叶

virtual void Brew() = 0; 

//倒入杯中

virtual void PourInCup() = 0; 

//加入辅料

virtual void PutSomething() = 0; 

//规定流程,这个函数是来决定上面4个纯虚函数执行顺序的
void MakeDrink(){ 

    this->Boil(); 	// 烧水

    Brew(); 		// 放入咖啡/茶叶

    PourInCup(); 	// 倒入杯中

    PutSomething(); 	// 加辅料

} 

}; 

//制作咖啡

class Coffee : public AbstractDrinking{
public: 

//烧水

virtual void Boil(){ 

cout << "煮农夫山泉!" << endl; 

} 

//冲泡

virtual void Brew(){ 

cout << "冲泡咖啡!" << endl; 

} 

//倒入杯中

virtual void PourInCup(){ 

cout << "将咖啡倒入杯中!" << endl; 

} 

//加入辅料

virtual void PutSomething(){ 

cout << "加入牛奶!" << endl; 

} 

}; 

//制作茶水

class Tea : public AbstractDrinking{ 

public: 

//烧水

virtual void Boil(){ 

cout << "煮自来水!" << endl; 

} 

//冲泡

virtual void Brew(){ 

cout << "冲泡茶叶!" << endl; 

} 

//倒入杯中

virtual void PourInCup(){ 

cout << "将茶水倒入杯中!" << endl; 

} 

//加入辅料

virtual void PutSomething(){ 

cout << "加入食盐!" << endl; 

} 

}; 

//业务函数

void DoBussiness(AbstractDrinking* drink){ 

drink->MakeDrink(); 

delete drink; 

} 

void test(){ 

    // 如果传入coffee类,那么就会执行coffee类从父类中所继承的MakeDrink(),那么就将执行Coffee中的重写的4个纯虚函数
DoBussiness(new Coffee);
    cout << "--------------" << endl; 

DoBussiness(new Tea); 

} 

多态-虚析构函数:

虚析构函数 跟虚构函数差不多
纯虚析构函数:使用virutal修饰,函数头后面加一个=0,析构函数是必须要有函数体
它们两个唯一的区别,如果一个类中存在纯虚析构函数,那么这个类就是一个抽象类
virtual ~Car() = 0

ps:一个面试题

C++异常

什么是 C++ 异常?(通俗解释)

想象你在写一个程序,就像是在安排一个送外卖的流程。

  • 正常流程 (try):外卖小哥接单 -> 取餐 -> 送达。

  • 出现意外 (throw):突然,外卖小哥发现“电动车坏了”或者“餐洒了”。这时候他不能假装没看见继续送,他必须**抛出(throw)**这个问题,停止送货,向上级汇报。

  • 处理问题 (catch):客服(上级)**捕获(catch)**到了这个报告,根据不同的情况做处理(退款、补发、或者换个骑手)。

核心语法三剑客:

  1. try (尝试): 把可能出问题的代码包起来。

  2. throw (抛出): 当代码运行不下去时,扔出一个错误(可以是数字、字符、字符串等)。

  3. catch (捕获): 专门用来接住 throw 扔出来的错误,并处理它。

- 异常严格类型匹配:

这是 C++ 异常最特别的地方,也是新手最容易晕的地方。

在普通函数调用中,如果你传一个整数 10 给一个需要浮点数 double 的函数,C++ 会自动帮你转成 10.0

但在异常处理中,C++ 是“直男”性格,非常死板:

  • 你扔出一个 整数 (int),我就只用 catch(int) 来接。

  • 你扔出一个 字符 (char),我就只用 catch(char) 来接。

  • 绝对不会发生隐式类型转换! 即使 int 可以转成 double,异常机制也不会理会。

一个简单的案例代码
 

#include <iostream>
using namespace std;

// 这是一个模拟做饭的函数
// 参数 choice 代表选择做什么
void Cook(int choice) {
    if (choice == 1) {
        // 情况1:发现没盐了
        // 抛出一个【整数】类型的异常,代表错误代码
        throw 404; 
    }
    else if (choice == 2) {
        // 情况2:锅烧糊了
        // 抛出一个【字符】类型的异常,代表错误等级
        throw 'C'; 
    }
    else if (choice == 3) {
        // 情况3:食材坏了
        // 抛出一个【浮点数】类型的异常
        // 注意:虽然 3.14 是数字,但它不是 int,类型不同!
        throw 3.14; 
    }
    
    cout << "饭做好了,真香!" << endl;
}

int main() {
    
    // 我们尝试做饭,这里填入 1, 2, 或 3 来测试不同的错误
    // 你可以修改这个变量来观察不同的 catch 结果
    int myChoice = 3; 

    try {
        // 1. 监控区域:这里面的代码可能会“炸”
        cout << "开始做饭..." << endl;
        
        Cook(myChoice); 
        
        cout << "流程结束(如果没有异常,这行才会显示)" << endl;
    }
    catch (int e) {
        // 2. 专门捕获【整数】类型的异常
        cout << "【捕获整数异常】: 错误代码是 " << e << " (没盐了!)" << endl;
    }
    catch (char e) {
        // 3. 专门捕获【字符】类型的异常
        cout << "【捕获字符异常】: 错误等级是 " << e << " (锅烧糊了!)" << endl;
    }
    catch (double e) {
        // 4. 专门捕获【浮点数】类型的异常
        // 重点:严格匹配!如果 Cook 抛出的是 int,这里绝对不会执行!
        cout << "【捕获浮点异常】: 数据是 " << e << " (食材坏了!)" << endl;
    }
    catch (...) {
        // 5. 兜底捕获:如果上面都没接住,这个能接住所有异常
        cout << "【捕获未知异常】: 发生了什么我也不知道!" << endl;
    }

    cout << "------- 异常处理完毕,程序继续运行 -------" << endl;

    return 0;
}

代码深度解析(为什么叫严格匹配?)

场景 A:如果 throw 404 (int)
  1. 程序运行到 Cook 里面,抛出了整数 404

  2. 程序立马跳出 Cook 函数,回到 maincatch 列表。

  3. 它看到第一个 catch (int e)

  4. 匹配成功! 因为类型完全一样。执行里面的代码,打印“没盐了”。

场景 B:如果 throw 3.14 (double)
  1. 程序抛出 3.14

  2. 它看到第一个 catch (int e)

    • 普通函数: 3.14 会变成 3 传进去。

    • 异常机制: 匹配失败! C++ 说:“你是 double,他是 int,不约。”

  3. 它看到第二个 catch (char e)匹配失败!

  4. 它看到第三个 catch (double e)匹配成功! 执行打印“食材坏了”。

场景 C:如果我把 catch (double e) 删掉?

如果你抛出了 3.14,但是代码里没有catch(double),且没有写兜底的 catch(...)

  • 程序会直接崩溃(Crash/Terminate)

  • 因为它找不到处理这个异常的人,就像外卖出事了没人管,系统就崩了

异常的多态使用:
// 定义一个基本的父类异常类
class BaseException
{
public:
	virtual void printExceptionMsg() {};
};
// 定义一个空指针异常,继承于BaseException
class NullPointerException :public BaseException
{
public:
	void printExceptionMsg()
	{
		cout << "null pointer exception!" << endl;
	}
};
// 定义一个数组下标越界异常,继承于BaseException
class IndexOutOfException :public BaseException
{
public:
	void printExceptionMsg()
	{
		cout << " Index out of range!" << endl;
	}
};
void fun1()
{
	try
	{
		throw IndexOutOfException();
	}
	// 如果要处理多种异常,没必要在这个地方写N多个catch
	// 让它们继承于同一个父类,然后在这个地方用父类引用接收不同子类对象就可以
	// 这就是之前讲过的多态
	catch (BaseException &e)
	{
		e.printExceptionMsg();
	}
}
Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐