当前位置: 首页 > news >正文

深入解析C++核心特性:运算符重载、继承、多态与抽象类

目录

  • 前言
  • 一、运算符重载
  • 1.1 特殊运算符重载
  • 1.1.1 赋值运算符重载
  • 1.1.2 类型转换运算符重载
  • 1.2 总结
  • 1.3 代码示例
  • 二、继承
  • 2.1 定义
  • 2.2 分类
  • 2.3 单一继承
  • 2.3.1 成员访问规则
  • 2.3.2 构造函数与析构函数
  • 2.4 多重继承
  • 2.4.1 二义性问题
  • 2.4.2 构造与析构函数的执行顺序
  • 2.4.2.1 构造函数顺序
  • 2.4.3 继承方式与成员访问权限
  • 2.4.4 继承方式的应用场景
  • 2.5 练习
  • 三、多态
  • 3.1 定义
  • 关键特性:
  • 3.2 核心作用
  • 3.3 实现条件
  • 3.4 关键实现机制
  • 3.4.1 函数覆盖(重写)
  • 3.4.2 虚函数机制
  • 4.3 虚析构函数
  • 四、抽象类
  • 4.1 定义
  • 4.2 纯虚函数格式
  • 4.3 特性
  • 4.4 代码示例
  • 4.5练习
  • 总结

前言

很高兴继续我们的C++学习之旅!今天我们将深入探讨一些进阶主题,包括之前提到的析构函数和运算符重载的更多细节,并扩展到相关的高级概念。


一、运算符重载

1.1 特殊运算符重载

1.1.1 赋值运算符重载

  • 默认行为:若未显式定义赋值运算符,编译器会自动生成一个浅拷贝版本的默认赋值运算符。
  • 自定义必要性:当类管理动态资源(如堆内存、文件句柄)时,需自定义赋值运算符实现深拷贝,避免资源重复释放或内存泄漏。
  • 语法要求
    • 必须定义为类成员函数
    • 参数应为 const ClassName& 类型(允许常量对象作为右值)。
    • 需返回 ClassName& 以支持链式赋值(如 a = b = c)。
    • 自赋值检查:在操作动态资源时,需检查 if (this != &rhs) 避免逻辑错误。
class Integer {
public:Integer& operator=(const Integer& rhs) {if (this != &rhs) {  // 自赋值检查value = rhs.value;  // 若为动态资源需深拷贝}return *this;}
};

1.1.2 类型转换运算符重载

  • 语法特性
    • 定义为 operator TargetType(),无返回值类型和参数。
    • 必须为类成员函数
    • 可添加 explicit 关键字(C++11+)禁止隐式转换。
  • 用途:实现类对象向其他类型(如 intdouble)的显式/隐式转换。
class Integer {
public:// 允许显式转换:explicit operator int()operator int() const {  // const 保证不修改对象return value;}
};

1.2 总结

特性说明
默认赋值运算符执行浅拷贝,可能引发资源冲突
类型转换运算符无参数,无返回类型,可声明为 explicit
友元函数适用场景需支持左操作数非类类型时(如 <<>>
参数默认值运算符重载函数禁止使用
动态资源管理需自定义拷贝构造、赋值运算符、析构函数(三/五法则)

1.3 代码示例

/** 描述:演示自定义整数类实现运算符重载* 注意:类名正确拼写应为"Integer",此处为示例保留原始拼写"Interger"*/#include <iostream>  // 添加输入输出头文件
using namespace std;class Interger {
private:int value;  // 存储整数值的私有成员变量public:// 默认构造函数(注意:未初始化value成员变量)Interger() {}  // 带参数的构造函数,用给定值初始化成员变量Interger(int v) : value(v) {}  // 获取存储值的公有方法int getVal() {return value;}// 赋值运算符重载(注意:参数应为const引用以符合最佳实践)Interger& operator=(Interger& i) {this->value = i.value;  // 将参数对象的value赋值给当前对象return *this;  // 返回当前对象的引用以支持链式赋值}// 类型转换运算符重载(允许隐式转换为int类型)operator int() {return value;  // 返回存储的整数值}
};int main() {// 创建并初始化Interger对象Interger i1(10);  Interger i2;       // 使用默认构造函数创建对象(value未初始化)// 使用重载的赋值运算符// 等价于:i2.operator=(i1);i2 = i1;  // 输出赋值后的值cout << i2.getVal() << endl;  // 应输出10// 使用类型转换运算符将Interger对象转换为intint n = i1;  cout << n << endl;  // 应输出10return 0;
}

二、继承

2.1 定义

继承允许派生类复用基类的成员,体现代码复用思想。基类(父类)与派生类(子类)是相对概念,支持直接继承和间接继承。


2.2 分类

  • 单一继承:子类只有一个直接父类
    class SubClass : public BaseClass {};
  • 多重继承:子类有多个直接父类
    class SubClass : public Base1, protected Base2 {};

2.3 单一继承

2.3.1 成员访问规则

  • 私有成员:可继承但不可直接访问(通过基类接口间接访问)。
  • 保护/公有成员:继承后权限由继承方式决定(见2.5节)。

 代码示例

#include <iostream>
using namespace std;// 父类定义:包含姓氏和工作信息
class Father{
private:string surname = "张";  // 私有成员变量,存储姓氏"张"
public:// 公有成员函数:输出父类姓氏void getSname(){cout << surname << endl;}// 公有成员函数:输出工作身份为老板void work(){cout << "我是一个老板" << endl;}
};// 子类定义:公有继承父类,重写work方法并扩展新功能
class Son : public Father{
public:// 重写父类work方法(函数隐藏),输出工作身份为程序员void work(){cout << "我是一个程序员" << endl;}// 新增成员函数:显示游戏信息void playGame(){cout << "原神" << endl;}
};int main()
{Son s;s.getSname();           // 调用继承自父类的getSname()方法s.work();               // 调用子类重写的work()方法s.Father::work();       // 通过作用域解析运算符调用父类被隐藏的work()方法return 0;
}

2.3.2 构造函数与析构函数

  • 透传构造:通过初始化列表显式调用基类构造函数。
    SubClass::SubClass(args) : BaseClass(args), member(init) {}
  • 执行顺序
    • 构造:基类 → 子类成员 → 子类构造函数体
    • 析构:子类析构函数体 → 子类成员 → 基类

 代码示例

/** 内容:演示C++类的继承、构造函数透传和方法覆盖*/#include <iostream>
using namespace std;// 基类 Father
class Father {
private:string surname;  // 姓氏int age;         // 年龄public:// 基类构造函数(使用初始化列表初始化成员)Father(string sn, int a) : surname(sn), age(a) {cout << "father constructor" << endl;}// 显示基本信息的方法void getVal() {cout << surname << " " << age << endl;}
};// 派生类 Son(继承自Father)
class Son : public Father {
private:string name;    // 名字char gender;    // 性别('m'/'f')public:/* * 派生类构造函数说明:* 参数顺序:子类参数在前,父类参数在后* 初始化列表顺序:* 1. 必须首先初始化父类(Father构造函数)* 2. 然后初始化子类成员* (虽然代码中Father(s, a)写在后面,但实际执行顺序仍优先于成员初始化)*/Son(string nm, char gd, string s, int a): Father(s, a),  // 显式调用基类构造函数name(nm),gender(gd) {}// 覆盖(override)基类的getVal方法void getVal() {cout << "我是" << name << " 性别:" << gender << endl;}
};int main()
{// 创建Son对象(参数顺序:子类参数 + 父类参数)Son s("张三", 'm', "张", 50);s.getVal();            // 调用子类覆盖后的方法s.Father::getVal();    // 显式调用父类被覆盖的方法return 0;
}/** 程序输出:* father constructor    (构造时父类构造函数输出)* 我是张三 性别:m      (子类getVal输出)* 张 50                (父类getVal输出)*/

2.4 多重继承

2.4.1 二义性问题

  • 同名冲突:使用类名::成员显式指定。
    obj.Base1::func();

代码示例

class Sofa{
public:void sit(){cout << "坐在沙发上" << endl;}void position(){cout << "放在客厅" << endl;}
};
class Bed{
public:void lay(){cout << "躺在床上" << endl;}void position(){cout << "放在卧室" << endl;}
};
// 沙发床类多继承自沙发和床
// 注意:两个父类都存在position()方法,形成同名覆盖问题
class SofaBed:public Sofa , public Bed{
};
int main()
{SofaBed sfb;sfb.sit();      // 正确:调用Sofa的sit()sfb.lay();      // 正确:调用Bed的lay()// 当多个父类中存在同名函数时,直接调用会产生二义性// sfb.position(); // 错误:编译器无法确定调用哪个父类的position()// 使用作用域解析运算符显式指明调用路径sfb.Sofa::position(); // 正确:明确调用Sofa的position()sfb.Bed::position();  // 正确:明确调用Bed的position()return 0;
}

 菱形继承:虚继承(virtual关键字)解决数据冗余和二义性

class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {}; // A仅保留一份副本

代码示例 

#include <iostream>
using namespace std;// 基类 Furniture(家具)
class Furniture {
public:void fun() {cout << "Furniture" << endl;}
};// 沙发类,使用虚继承自Furniture
// 虚继承确保在多重继承时,Furniture基类只有一个实例被创建
class Sofa : virtual public Furniture {  // 虚继承解决菱形继承问题
};// 床类,同样使用虚继承自Furniture
class Bed : virtual public Furniture {  // 虚继承确保与Sofa共享同一个Furniture基类
};// 沙发床类,同时继承Sofa和Bed
// 由于Sofa和Bed都是虚继承,此时Furniture基类在SofaBed中只有一份副本
class SofaBed : public Sofa, public Bed {  
};int main() {SofaBed sfb;sfb.fun();  // 如果没有虚继承,这里会因二义性报错(两个Furniture副本)return 0;
}

2.4.2 构造与析构函数的执行顺序

2.4.2.1 构造函数顺序
  • 单继承:先执行基类(父类)的构造函数,再执行派生类(子类)的构造函数。
  • 多重继承:按基类声明顺序(从左到右)依次执行构造函数,最后执行派生类构造函数。
    class B1, B2;
    class D : public B1, public B2 { ... };
    // 构造顺序:B1 → B2 → D
    

2.4.2.2 析构函数顺序

  • 单继承:先执行派生类的析构函数,再执行基类的析构函数。
  • 多重继承:按基类声明顺序的逆序(从右到左)执行析构函数。
    // 析构顺序:D → B2 → B1

注意事项

  • 基类构造顺序由派生类定义时的基类列表顺序决定,与初始化列表顺序无关。
  • 若存在虚继承(virtual inheritance),构造顺序会因虚基类优化而调整(优先构造虚基类)。
  • 多态场景下,基类析构函数应声明为 virtual,以确保正确调用派生类析构函数。


2.4.3 继承方式与成员访问权限

C++ 支持三种继承方式:publicprotectedprivate。继承方式决定了基类成员在派生类中的访问权限。

基类成员权限 \ 继承方式public 继承protected 继承private 继承
publicpublic(派生类)protected(派生类)private(派生类)
protectedprotectedprotectedprivate
private不可直接访问不可直接访问不可直接访问

关键规则

  1. 基类私有成员

    • 始终存在于派生类对象中,但派生类无法直接访问(只能通过基类的公有/保护方法间接访问)。
  2. 公有继承(public

    • 基类 public 和 protected 成员在派生类中保持原有权限。
    • 派生类对象可隐式转换为基类指针/引用(满足“是一个”关系)。
  3. 保护继承(protected

    • 基类 public 和 protected 成员在派生类中变为 protected
    • 派生类对象不能隐式转换为基类指针/引用。
  4. 私有继承(private

    • 基类 public 和 protected 成员在派生类中变为 private
    • 派生类对象不能隐式转换为基类指针/引用。
    • 可通过 using 声明调整成员访问权限:
      class Base {
      public:void func() {}
      };
      class Derived : private Base {
      public:using Base::func; // 将 func() 提升为 public
      };
      

2.4.4 继承方式的应用场景

  1. 公有继承

    • 用途:表示“是一个(is-a)”关系(如 Dog 继承 Animal)。
    • 特点:保留基类接口,支持多态。
  2. 保护/私有继承

    • 用途:表示“实现继承”而非接口继承(类似组合)。
    • 场景
      • 保护继承:允许派生类的子类复用基类实现。
      • 私有继承:完全隐藏基类接口,仅内部使用。

2.5 练习

(1)已有vehicle类;vehicle类有 print()成员函数打印车轮数和总载重量

(2)定义并实现一个小车类car,是vehicle类的公有派生类,小车本身的私有属性有载人数,小车的函数有init(设置车轮数,重量和载人数),getpassenger(获取最多载人数),print(打印车轮数,重量和载人数)

/** 内容:演示继承机制的C++代码示例,包含基类Vehicle和派生类Car*/#include <iostream>
using namespace std;// 交通工具基类
class Vehicle {
public:// 初始化重量和车轮数量// 参数:we - 重量(单位:千克),wh - 车轮数量void init(float we, int wh) {weight = we;wheels = wh;}// 打印交通工具基本信息void print() {cout << "重量:" << weight << "kg,车轮数量:" << wheels << endl;}private:float weight;  // 交通工具重量(千克)int wheels;    // 车轮数量
};// 汽车类(继承自Vehicle)
class Car : public Vehicle {
public:// 初始化汽车参数(重载父类init方法)// 参数:n - 载客人数,we - 重量,wh - 车轮数量void init(int n, float we, int wh) {num = n;Vehicle::init(we, wh);  // 调用基类初始化方法}// 打印汽车完整信息(重写父类print方法)void print() {Vehicle::print();      // 先调用基类的打印方法cout << "载人数:" << num << endl;}// 获取最大载客数(固定为5人)void getPassenger() {cout << "最大载客数:" << maxnum << endl;}private:int num;             // 当前载客人数const int maxnum = 5; // 最大载客数(常量)
};int main() {Car c;// 初始化:载客5人,重量1000kg,4个车轮c.init(5, 1000, 4);c.print();         // 打印车辆信息(重量、车轮数、载客数)c.getPassenger();  // 显示最大载客数return 0;
}

三、多态

3.1 定义

多态(Polymorphism)意为"多种形态",核心思想是"一个接口,多种实现"。允许程序在运行时根据对象类型决定调用具体方法。

关键特性:

  • 接口与实现分离
  • 使用统一的方法调用,根据对象差异执行不同策略
  • 支持运行时动态绑定

3.2 核心作用

  1. 提升代码复用性

    • 减少重复代码
    • 便于功能扩展
  2. 降低耦合度

    • 通过接口隔离实现细节
    • 增强系统灵活性
  3. 支持动态行为

    • 运行时自动选择匹配方法
    • 适应复杂业务场景

3.3 实现条件

  1. 公有继承(Public Inheritance)
  2. 虚函数重写(Virtual Function Overriding)
  3. 基类指针/引用指向派生类对象

3.4 关键实现机制

3.4.1 函数覆盖(重写)

  • 必要条件:基类使用 virtual 声明虚函数
  • 派生类:重写同名函数(自动继承virtual特性)
  • 面试要点
    函数重载函数隐藏函数覆盖
    作用域同一类域继承关系继承关系
    函数签名必须不同可以相同必须相同
    virtual无关无关必须使用

3.4.2 虚函数机制

语法规范

virtual 返回值类型 函数名(参数列表);

使用限制

  • ❌ 静态成员函数不能为虚函数
  • ❌ 构造函数不能为虚函数
  • ✅ 普通成员函数和析构函数可设为虚函数
  • ✅ 声明与定义分离时,只需在声明处加 virtual

代码示例

/* 这是一个演示C++多态性的示例程序* 通过虚函数实现运行时多态,根据对象类型调用不同方法*/#include <iostream>
using namespace std;// 基类 Animal 定义
class Animal {
public:// 虚函数声明,实现多态的关键// 派生类可以重写此方法实现不同的行为virtual void eat() {cout << "动物吃东西" << endl;}
};// Dog 类继承自 Animal
class Dog : public Animal {
public:// 重写基类的eat方法(C++11起可用override关键字明确表示)void eat() override {  // override 不是必须但推荐使用,可以增强代码可读性cout << "狗吃骨头" << endl;}
};// Cat 类继承自 Animal
class Cat : public Animal {
public:void eat() override {  // 同样重写基类方法cout << "猫吃鱼" << endl;}
};// 测试函数:通过基类引用接收对象
// 体现多态性——实际调用的方法取决于传入对象的类型
void test(Animal &am) {am.eat();  // 这里会根据实际对象类型调用对应的eat方法
}int main()
{Dog d;  // 创建Dog对象Cat c;  // 创建Cat对象test(d);  // 传递Dog对象,将调用Dog::eat()test(c);  // 传递Cat对象,将调用Cat::eat()return 0;
}/*
程序输出:
狗吃骨头
猫吃鱼关键点说明:
1. 虚函数机制:通过virtual关键字实现动态绑定(运行时多态)
2. 引用传递:test函数使用基类引用接收派生类对象
3. 方法重写:派生类定义与基类虚函数签名相同的函数来覆盖实现
4. 多态优势:统一的接口处理不同类型的对象,提高代码扩展性和维护性
*/

4.3 虚析构函数

必要性

  • 确保通过基类指针删除派生类对象时,正确调用完整析构链
  • 防止内存泄漏

实现示例

#include <iostream>
using namespace std;class Animal {
public:// 虚析构函数确保当通过基类指针删除派生类对象时,// 会调用正确的派生类析构函数,避免资源泄漏virtual ~Animal() {cout << "Animal destructor" << endl;}
};class Dog : public Animal {
public:// 即使没有显式声明virtual,由于基类有虚析构函数,// Dog的析构函数也自动成为虚函数~Dog() {cout << "Dog destructor" << endl;}
};int main() {Animal* am = new Dog;  // 基类指针指向派生类对象delete am;  // 由于虚析构函数,会先调用Dog::~Dog(),再调用Animal::~Animal()return 0;
}/* 代码输出:
Dog destructor
Animal destructor
*/

四、抽象类

4.1 定义

  • 抽象类用于表示抽象概念,不直接对应具体对象。
  • 核心特征:类中至少包含一个纯虚函数
  • 抽象类不能实例化(无法创建对象),只能作为基类被继承。
  • 纯虚函数仅声明不定义,强制派生类实现具体功能。

4.2 纯虚函数格式

virtual 返回值类型 函数名(参数列表) = 0;

4.3 特性

  1. 必须包含纯虚函数
    至少一个纯虚函数的存在使类成为抽象类。

  2. 不可实例化,允许指针/引用

    • 不能创建抽象类的对象。
    • 可声明指向抽象类的指针或引用,指向派生类对象以实现多态。
  3. 派生类必须实现纯虚函数
    若派生类未完全实现基类的所有纯虚函数,则派生类仍为抽象类。

  4. 可包含非纯虚成员与数据

    • 抽象类可以定义普通成员函数、数据成员、静态成员。
    • 示例:抽象类可包含静态变量或工具函数。
  5. 虚析构函数必要性

    • 必须声明虚析构函数(virtual ~ClassName() {...}),确保通过基类指针删除派生类对象时正确调用析构链。

4.4 代码示例

#include <iostream>
using namespace std;// 抽象基类(包含纯虚函数,无法实例化)
class Animal{
public:// 纯虚函数声明,使该类成为抽象类// 所有派生类必须实现这个接口virtual void speak() = 0; // "= 0" 表示纯虚函数
};// Dog类继承自Animal,实现具体功能
class Dog: public Animal{ // 公有继承
public:// 实现基类的纯虚函数(必须实现才能创建对象)void speak() override { // override关键字明确表示重写虚函数cout << "旺旺旺~" << endl;}
};int main()
{Dog d;          // 创建Dog对象(合法,因为实现了所有纯虚函数)d.speak();      // 直接调用Dog的speak方法// 抽象类不能直接实例化对象// Animal a;    // 错误:无法创建抽象类的实例// 多态应用:通过基类的引用/指针操作派生类对象Animal &am = d; // 基类引用绑定到Dog对象(合法)am.speak();     // 通过基类引用调用虚函数,实际执行Dog的speak// 同样可以使用指针形式:// Animal* p = &d;// p->speak();return 0;
}

4.5练习

已定义一个Shape类,在此基础上派生出矩形Rectangle和圆形Circle类,二者都有GetPerim()函数计算对象的周长,并编写测试main()函数。

#include <iostream>   // 输入输出流头文件
using namespace std;  // 使用标准命名空间// 形状抽象基类,定义计算周长的接口
class Shape {
public:// 纯虚函数,子类必须实现该方法来计算周长virtual void GetPerim() = 0;
};// 矩形类,继承自Shape类
class Rectangle : public Shape {
private:int width, height;  // 私有成员:宽度和高度
public:// 构造函数,初始化宽高Rectangle(int w, int h) : width(w), height(h) {}// 实现计算矩形周长的功能(重写父类方法)void GetPerim() override {cout << "周长:" << 2 * (width + height) << endl;}
};// 圆形类,继承自Shape类
class Circle : public Shape {
private:int radius;  // 私有成员:半径
public:// 构造函数,初始化半径Circle(int r) : radius(r) {}// 实现计算圆形周长的功能(重写父类方法)// 注意:这里使用3.14作为π的近似值void GetPerim() override {cout << "周长:" << 2 * 3.14 * radius << endl;}
};int main() {Rectangle r(3, 4);  // 创建矩形对象(宽3,高4)Circle c(5);        // 创建圆形对象(半径5)r.GetPerim();  // 计算并输出矩形周长c.GetPerim();  // 计算并输出圆形周长return 0;       // 程序正常退出
}

总结

        本文系统探讨C++面向对象编程的四大核心特性。运算符重载部分解析赋值运算符与类型转换运算符的实现规则,强调深拷贝必要性及自赋值检查的重要性;继承章节详述单继承与多重继承的成员访问规则、构造函数顺序及菱形继承的虚继承解决方案,并通过代码示例展示派生类与基类的关系;多态机制围绕虚函数重写与动态绑定展开,结合虚析构函数应用说明多态的实际价值;抽象类部分定义纯虚函数的作用,阐述其作为接口规范的实现逻辑,并通过矩形与圆形周长计算案例验证多态扩展性。全文通过理论结合代码,呈现C++面向对象设计的灵活性与严谨性。

相关文章:

  • 第三节第二部分:Static修饰方法应用场景
  • 学习黑客搜索技巧
  • 解决应用程序在JAR包中运行时无法读取类路径下文件的问题
  • SSH(安全外壳协议)
  • 软件安全(二)优化shellcode
  • FreeRTOS如何实现100%的硬实时性?
  • 龙虎榜——20250509
  • 编译原理实验 之 语法分析程序自动生成工具Yacc实验
  • nvidia-smi 和 nvcc -V 作用分别是什么?
  • 算法设计与分析复习代码(hnust)
  • LVGL源码学习之渲染、更新过程(3)---绘制和刷写
  • 操作系统导论——第26章 并发:介绍
  • 68、微服务保姆教程(十一)微服务的监控与可观测性
  • 专题练习1
  • 赤色世界 陈默传 第一章 另一个陈默
  • 路由组件1
  • java volatile关键字
  • 二分系列题
  • 什么是AIOps
  • TDengine 在智慧油田领域的应用
  • “科创板八条”后百单产业并购发布,披露交易金额超247亿
  • 上海第四批土拍成交额97亿元:杨浦宅地成交楼板单价半年涨近7000元
  • 玉渊谭天丨中方为何此时同意与美方接触?出于这三个考虑
  • 中国一重集团有限公司副总经理陆文俊被查
  • 本周看啥|喜欢二次元的观众,去电影院吧
  • 王受文已任全国工商联党组成员