C++核心概念全解析:从析构函数到运算符重载的深度指南
目录
- 前言
- 一、构析函数
- 1.1 概念
- 1.2 语法格式
- 1.3 核心特性
- 1.4 调用时机
- 1.5 构造函数 vs 析构函数
- 1.6 代码示例
- 二、this关键字
- 2.1 基本概念
- 2.2 核心特性
- 2.3 使用场景
- 2.3.1 区分成员与局部变量
- 2.3.2 返回对象自身(链式调用)
- 2.3.3 成员函数间传递当前对象
- 2.3.4 避免自赋值
- 2.4 代码示例
- 三、 static关键字
- 3.1 修饰变量与函数
- 3.2 静态成员变量
- 3.3 静态成员函数
- 四、const关键字
- 4.1 修饰指针
- 4.2 修饰成员变量
- 4.3 修饰成员函数
- 4.4 修饰对象
- 五、友元(Friend)
- 5.1 定义
- 5.2 分类
- 5.3 使用规范
- 5.4 友元函数(代码示例)
- 5.5 友元类(代码示例)
- 5.6 友元成员函数(代码示例)
- 5.7 注意事项
- 5.8 性能对比
- 六、运算符重载
- 6.1 基本概念
- 6.2 友元函数运算符重载
- 6.1.1 加法运算符重载
- 6.1.2 自增运算符重载
- 6.1.3多成员变量处理
- 6.2 成员函数运算符重载
- 6.3 附录:可重载运算符全表
- 总结
前言
亲爱的学习者,欢迎回到C++编程探索的奇妙世界!经过前期的语法筑基之旅,今天我们将以更开阔的视野开启新的学习篇章。这个旅程或许充满挑战,但每解决一个内存泄漏问题、每优化一个算法复杂度,都是蜕变为C++工匠的重要印记。让我们保持耐心,携手攻克编译错误的重重关卡,在代码的星辰大海中扬帆远航!
一、构析函数
1.1 概念
析构函数是与构造函数对立的特殊成员函数,用于在对象生命周期结束时执行资源清理工作。它主要负责:
- 释放对象占用的资源(如动态内存、文件句柄、网络连接等)
- 执行必要的清理操作(如日志记录、状态保存等)
1.2 语法格式
~ClassName() {// 清理代码
}
1.3 核心特性
- 无参数无返回值,且不能重载(每个类只能有一个析构函数)
- 调用顺序与构造函数相反:
- 局部对象:按创建顺序的逆序析构
- 成员变量:按声明顺序的逆序析构
- 默认析构函数:
- 若未显式定义,编译器会自动生成空实现的析构函数
- 自动生成的析构函数不会处理动态分配的资源(需手动管理)
1.4 调用时机
对象类型 | 调用时机 |
---|---|
栈对象 | 离开作用域时自动调用 |
堆对象 | 执行 delete 操作时调用 |
全局/静态对象 | 程序终止时调用 |
临时对象 | 表达式结束时调用 |
1.5 构造函数 vs 析构函数
特性 | 构造函数 | 析构函数 |
---|---|---|
调用时机 | 对象创建时 | 对象销毁时 |
主要职责 | 初始化成员 | 释放资源 |
参数 | 支持参数 | 无参数 |
重载 | 支持重载 | 不可重载 |
默认生成 | 不提供则生成默认构造函数 | 不提供则生成默认析构函数 |
虚函数特性 | 不能是虚函数 | 可为虚函数(多态场景必须) |
1.6 代码示例
#include <iostream>
using namespace std;// 定义一个Demo类,用于演示构造和析构函数的调用
class Demo{
public:// 构造函数:对象创建时自动调用Demo(){cout << "creater" << endl; // 输出对象创建信息}// 析构函数:对象销毁时自动调用~Demo(){cout << "destroy" << endl; // 输出对象销毁信息}
};int main()
{// 栈上创建对象(自动内存管理)Demo de; // 1. 构造函数被调用,输出"creater"// 堆上动态分配对象(手动内存管理)Demo *d = new Demo; // 2. 构造函数再次被调用,输出"creater"delete d; // 3. 手动释放堆对象,析构函数被调用,输出"destroy"return 0;// 4. main函数结束时,栈对象de超出作用域// 自动调用析构函数,输出"destroy"
}
二、this关键字
2.1 基本概念
- 定义:
this
是一个隐式指针,指向当前对象的首地址。它在所有非静态成员函数中自动生成,无需显式声明。 - 类型:
this
的类型为ClassName* const
(常量指针);在const
成员函数中为const ClassName* const
(指向常量的常量指针)。
2.2 核心特性
- 隐式存在:每个非静态成员函数都隐含
this
指针,指向调用该函数的对象。 - 对象整体引用:通过
*this
可以访问整个对象(如返回对象自身)。 - 作用域限制:仅在类的非静态成员函数内部可用,静态函数中无
this
指针。 - 右值属性:
this
是右值,不可被修改(如this = nullptr
非法)。
2.3 使用场景
2.3.1 区分成员与局部变量
当成员变量与形参/局部变量同名时,必须用 this->
明确作用域:
class MobilePhone {
private:float weight;
public:MobilePhone(float weight) {this->weight = weight; // 区分成员变量和形参}
};
2.3.2 返回对象自身(链式调用)
通过返回 *this
实现链式调用,需注意返回引用以避免拷贝:
class MobilePhone {
public:MobilePhone& add(float w) {weight += w;return *this; // 返回引用以支持链式操作}
};// 链式调用示例
mp.add(1).add(2).add(3); // 连续修改同一对象
2.3.3 成员函数间传递当前对象
在类内部需要传递当前对象时,可直接使用 *this
:
void print() { cout << *this; } // 假设已重载 << 运算符
2.3.4 避免自赋值
在重载赋值运算符时,通过 this
检查自赋值:
MobilePhone& operator=(const MobilePhone& rhs) {if (this != &rhs) { // 防止自赋值// 赋值逻辑}return *this;
}
2.4 代码示例
// 定义MobilePhone类
class MobilePhone{
private:float weight; // 私有成员变量,表示手机重量public:// 构造函数:初始化手机重量MobilePhone(float weight){// 使用this指针区分参数和成员变量// this->weight 表示类的成员变量,weight表示传入的参数this->weight = weight;}// 获取当前手机重量的成员函数float getVal(){return weight;}// 返回当前对象的引用(*this表示当前对象本身)MobilePhone &fun(){// 通过返回引用实现链式调用return *this;}// 修改重量并返回当前对象引用MobilePhone &add(float w){weight += w; // 增加重量return *this; // 返回自身引用以支持链式调用}
};int main()
{// 创建MobilePhone对象,初始重量23.4MobilePhone mp(23.4);// 以下为测试代码(被注释):// cout << mp.getVal() << endl; // 输出初始重量// MobilePhone mp1 = mp.fun(); // 通过fun()获取当前对象引用// cout << mp1.getVal() << endl; // 此时mp1和mp指向同一对象// mp1 = mp.add(45.6); // 添加重量后返回自身引用// cout << mp1.getVal() << endl; // 输出更新后的重量// 链式调用示例:连续调用add()方法// 每次add返回当前对象引用,因此可以连续调用// 最终调用getVal()获取累计后的重量cout << mp.add(1).add(2).add(3).add(4).getVal() << endl;// 程序结束return 0;
}
三、 static关键字
3.1 修饰变量与函数
特性:
-
全局变量/函数
- 作用域限制:被
static
修饰的全局变量或函数,作用域仅限于当前文件(内部链接性)。 - 示例:其他文件无法通过
extern
引用,避免命名冲突。// test.cpp static int num = 100; // 仅本文件可见
- 作用域限制:被
-
局部变量
- 生命周期延长:变量在程序运行期间始终存在,但作用域仍限于函数内。
- 初始化一次:首次执行时初始化,后续调用保留上次值。
void fun() {static int n = 1; // 只初始化一次n++;cout << n << endl; // 输出:2 → 3 → 4... }
-
存储位置
- 静态变量存储在全局/静态存储区(
.data
段为已初始化,.bss
段为未初始化)。
- 静态变量存储在全局/静态存储区(
示例代码
// 外部引用其他文件中的全局变量或函数(实际因static修饰无法链接)
extern int num;
void fun(){// 静态局部变量:只初始化一次,延长生命周期(存储于静态区)static int n = 1; n++;cout << n << endl;
}
int main()
{
// cout << num << endl; // 此处无法访问test.cpp中的num(链接错误)fun(); // 输出2(n=1+1)fun(); // 输出3(n=2+1)return 0;
}
// 静态全局变量:限制作用域仅在本文件内,避免被其他文件通过extern引用
static int num = 100;
3.2 静态成员变量
特性:
-
类内声明,类外定义
- 类内仅声明,需在类外单独分配内存(C++17支持内联静态变量初始化)。
class Demo { public:static int num; // 声明 }; int Demo::num = 10; // 定义(类外)
- 类内仅声明,需在类外单独分配内存(C++17支持内联静态变量初始化)。
-
共享性与内存分配
- 所有类实例共享同一内存,不占用对象空间,可直接通过类名访问。
cout << Demo::num << endl; // 无需对象 Demo d1, d2; d1.num = 100; // d2.num 也变为100
- 所有类实例共享同一内存,不占用对象空间,可直接通过类名访问。
代码示例
class Demo{
public://类内声明静态成员变量,属于类级别,所有类对象共享同一份内存static int num;//普通成员变量sum,属于对象级别,每个对象有独立存储空间(未初始化,默认值不确定)int sum;
};//类外定义并初始化静态成员变量,静态成员需在类外单独分配存储空间
int Demo::num = 10;int main()
{//静态成员不与对象绑定,可直接通过类名访问cout << Demo::num << endl; // 输出静态成员初始值: 10Demo d1, d2; // 创建两个实例,sum成员未初始化cout << d1.sum << endl; // 输出未初始化的普通成员变量,值随机(可能引发未定义行为)Demo::num = 100; // 修改静态成员值,所有实例同步生效//验证静态成员地址唯一性(所有实例共享同一内存地址)cout << &d1.num << endl; // 输出静态变量地址(与类名访问地址相同)cout << &d2.num << endl; // 地址同上,证明静态变量全局唯一return 0;
}
3.3 静态成员函数
特性:
-
无
this
指针- 不能访问非静态成员(需通过对象参数间接访问),只能操作静态成员。
class Demo {static void printNum() { cout << num; // 合法(静态变量)// cout << sum; // 非法(非静态)} };
- 不能访问非静态成员(需通过对象参数间接访问),只能操作静态成员。
-
直接通过类名调用
- 无需实例化对象即可调用。
Demo::printNum(); // 直接调用
- 无需实例化对象即可调用。
-
访问控制
- 可访问私有静态成员,常作为工具函数。
class Demo { private:static int secret; public:static int getSecret() { return secret; } };
- 可访问私有静态成员,常作为工具函数。
代码示例
#include <iostream> // 包含输入输出流头文件
using namespace std; // 使用标准命名空间class Demo {
public:// 静态成员函数声明static void fun1();// 普通成员函数定义void fun2() {cout << "普通成员函数" << endl;// 成员函数可以访问静态成员fun1(); // 调用静态成员函数}private:static int num; // 静态成员变量声明(类内)int sum; // 普通私有成员变量
};// 静态成员函数类外定义
void Demo::fun1() {cout << "类外定义静态成员函数" << endl;cout << num << endl; // 允许访问静态成员// 静态成员函数不能直接访问非静态成员(需要对象实例)// cout << sum << endl; // 错误:sum是非静态成员// fun2(); // 错误:fun2是非静态成员函数
}// 静态成员变量类外定义和初始化(必须)
int Demo::num = 10;int main() {Demo obj; // 创建类实例Demo::fun1(); // 通过类名调用静态成员函数(无需实例)obj.fun2(); // 通过对象调用普通成员函数return 0;
}
四、const关键字
4.1 修饰指针
三种形式及区别:
int a = 10, b = 20;
// 1. 指向内容不可修改,指向可修改(底层const)
const int *ptr = &a;
// *ptr = 20; // 错误:内容不可修改
ptr = &b; // 正确:指针本身可修改// 2. 指向不可修改,指向内容可修改(顶层const)
int *const ptr2 = &a;
*ptr2 = 100; // 正确:内容可修改
// ptr2 = &b; // 错误:指针本身不可修改// 3. 指向和内容均不可修改
const int *const ptr3 = &a;
// *ptr3 = 20; // 错误
// ptr3 = &b; // 错误
补充说明:
const
在*
左侧:修饰指向内容(底层const,内容不可变)const
在*
右侧:修饰指针本身(顶层const,指向不可变)- 可用于函数参数保护数据(如
void func(const int* p)
)
4.2 修饰成员变量
特性与初始化方式:
class Demo {
private:const int num = 5; // C++11支持类内初始化(直接初始化)const int id{2023}; // 统一初始化语法
public:Demo(int n) : num(n) {} // 构造初始化列表(优先级更高)// Demo() {} // 错误:必须初始化const成员
};
错误:必须初始化const成员};
注意事项:
- 必须通过构造函数初始化列表或C++11类内初始化
- 每个对象的const成员值生命周期内不可修改
- 类内初始化与初始化列表冲突时,以后者为准
4.3 修饰成员函数
核心特性与示例:
class Demo {
private:int count = 0;const int id;
public:Demo(int i) : id(i) {}// const成员函数void print() const { cout << id; // 允许读取// count++; // 错误:禁止修改非mutable成员// modifyID(); // 错误:只能调用const成员函数}// 重载const版本int getVal() const { return id; } int getVal() { return id; } // 非const版本
};
关键点:
- 隐含的
this
指针为const T*
类型 - 可被const和非const对象调用(非const对象优先调用非const版本)
- 需与同名非const函数构成重载时,注意版本选择
mutable
成员可在const函数中修改
4.4 修饰对象
常量对象特性:
class Demo {
public:int var;void modify() { var++; }void read() const {}
};int main() {const Demo obj{};// obj.var = 10; // 错误:不可修改成员// obj.modify(); // 错误:不可调用非const函数obj.read(); // 正确:允许调用const函数
}
扩展应用:
- 函数参数保护:
void process(const Demo& d)
- 返回值优化:
const Demo createDemo()
- 对象作为右值时自动转为const引用
五、友元(Friend)
5.1 定义
- 核心作用:允许特定外部函数/类访问类的私有(private)和保护(protected)成员
- 两重性:
- 提高程序灵活性:突破封装限制,提升数据访问效率
- 破坏封装性:可能导致代码维护性降低(建议谨慎使用)
- 应用场景:
- 运算符重载(特别是流运算符
<<
和>>
) - 需要高性能访问的特殊工具函数
- 紧密协作的类间访问
- 运算符重载(特别是流运算符
5.2 分类
类型 | 说明 | 生命周期关系 |
---|---|---|
友元函数 | 普通函数访问类私有成员 | 无依赖 |
友元类 | 整个类可访问目标类私有成员 | 单向关系 |
友元成员函数 | 特定类的成员函数访问目标类 | 需前置声明 |
5.3 使用规范
class TargetClass {friend ReturnType FriendFunction(Params); // 友元函数friend class FriendClass; // 友元类friend ReturnType OtherClass::Method(Params); // 友元成员函数
};
- 声明特性:
- 可出现在类的任何区域(public/private/protected)
- 不具有传递性(A是B的友元,B是C的友元 ≠ A是C的友元)
- 不可继承
5.4 友元函数(代码示例)
class BankAccount {
private:double balance;public:BankAccount(double b) : balance(b) {}// 声明友元函数friend void auditAccount(const BankAccount& acc);
};// 实现友元函数(无需作用域限定)
void auditAccount(const BankAccount& acc) {std::cout << "当前余额:" << acc.balance << std::endl; // 直接访问私有成员
}
5.5 友元类(代码示例)
class Storage {
private:int secretCode = 12345;// 声明整个类为友元friend class SecurityChecker;
};class SecurityChecker {
public:bool validate(const Storage& s, int code) {return s.secretCode == code; // 直接访问私有成员}
};}};
5.6 友元成员函数(代码示例)
class Engine; // 前向声明class Car {
private:int mileage;// 声明特定成员函数为友元friend void Engine::monitorCar(Car& c);
};class Engine {
public:void monitorCar(Car& c) {c.mileage += 10; // 访问Car的私有成员}
};
5.7 注意事项
-
慎用原则:
- 优先考虑成员函数实现功能
- 仅在需要高频访问私有数据时使用
- 避免创建双向友元关系
-
使用限制:
- 不能使用
virtual
修饰友元函数 - 友元函数不能有存储类型说明符(如static)
- 模板友元需要特殊处理
- 不能使用
5.8 性能对比
访问方式 | 典型时间开销(纳秒) | 封装性 |
---|---|---|
公有方法 | 1.2-1.5 | 高 |
友元访问 | 0.8-1.1 | 低 |
直接公有 | 0.5-0.7 | 无 |
测试环境:Intel i7-11800H @ 2.3GHz,每次访问执行100万次循环的平均值
六、运算符重载
6.1 基本概念
- 核心思想:将运算符视为特殊函数,通过重载扩展其操作范围至自定义类型。
- 目的:使自定义类型支持与内置类型一致的运算符语义。
- 不可重载运算符:
- 成员访问
.
、成员指针.*
、作用域::
、三目运算符?:
sizeof
、typeid
、static_cast
等类型相关操作符
- 成员访问
6.2 友元函数运算符重载
特点
- 声明需包含
friend
关键字 - 参数数量与操作数个数相等(二元运算符需两个显式参数)
- 支持左操作数非本类对象的场景(如流操作符)
通用格式
// 类内声明
friend ReturnType operatorOP(Arg1, Arg2);// 类外定义
ReturnType operatorOP(Arg1 arg1, Arg2 arg2) {// 实现逻辑
}
6.1.1 加法运算符重载
class Integer {
private:int value;
public:Integer(int v = 0) : value(v) {} // 合并默认与带参构造// 友元声明friend Integer operator+(const Integer& i1, const Integer& i2);
};Integer operator+(const Integer& i1, const Integer& i2) {return Integer(i1.value + i2.value); // 显式构造避免隐式转换
}
6.1.2 自增运算符重载
// 前置++
Integer& operator++(Integer& i) {++i.value;return i;
}// 后置++
Integer operator++(Integer& i, int) {Integer temp(i.value);i.value++;return temp; // 返回旧值副本
}
6.1.3多成员变量处理
class Complex {
private:int real;int imag;
public:Complex(int r = 0, int i = 0) : real(r), imag(i) {}friend Complex operator+(const Complex& c1, const Complex& c2);
};Complex operator+(const Complex& c1, const Complex& c2) {return Complex(c1.real + c2.real, c1.imag + c2.imag);
}
6.2 成员函数运算符重载
特点
- 隐含
this
指针作为左操作数 - 参数数量比实际操作数少一个
格式示例
class Integer {
public:Integer operator+(const Integer& other) const {return Integer(this->value + other.value);}
};
6.3 附录:可重载运算符全表
类别 | 运算符示例 |
---|---|
算术 | + - * / % |
关系 | == != < > <= >= |
逻辑 | ! && || |
位运算 | & | ~ ^ << >> |
赋值 | = += -= *= /= %= |
其他 | [] () -> , new delete new[] delete[] |
总结
本文系统解析C++六大核心编程概念,包括析构函数的资源管理机制、this指针的隐式对象引用特性、static关键字的静态存储控制、const关键字的多场景不可变性约束、友元机制的封装突破策略以及运算符重载的多态实现方式。通过对比构造函数与析构函数的生命周期管理、详述静态成员变量与函数的类级作用、演示常量指针与常量成员函数的使用规范,结合友元函数与友元类的私有访问突破实例,以及算术运算符与自增运算符的重载实现,全面揭示C++面向对象编程的核心原理。文中包含20+代码片段,涵盖栈/堆对象析构顺序、链式调用实现、静态区内存管理等典型场景,并附有构造函数/析构函数调用时序图、this指针内存示意图等抽象概念的可视化解析。