一、引言:让对象也能像普通变量一样运算
在前面的学习中,我们已经掌握了 C++ 的基础语法、函数、指针、面向对象、继承与多态。我们可以轻松定义类、创建对象、封装数据、实现多态接口。但你是否想过:
为什么两个 int 可以直接 a + b,而我们自己写的对象 Student、Point、Complex 不能直接相加、比较、输出?
因为 C++ 内置的运算符(+ - * / == != <<>> \[\] () = ++ -- 等)默认只支持基本数据类型,并不认识我们自定义的对象。
为了让对象也能自然、直观、优雅地参与运算,C++ 提供了 运算符重载(Operator Overloading) 机制。它是面向对象编程中提升代码可读性、简洁性的关键技术。
与此同时,为了在特定场景下让外部函数或类能够访问类的私有成员,C++ 提供了 友元(Friend)。
而为了真正理解对象为什么能调用成员、this 指针是什么、对象占多少内存、多态底层如何实现,我们必须深入 C++ 对象模型。
本篇文章将系统、深入、完整讲解:
- 运算符重载的意义与语法
- 常用运算符重载:+ - == != ++ -- <<>> \[\] = ()
- 友元函数与友元类
- C++ 对象内存模型
- this 指针底层原理
- 常函数、常对象、mutable
- 运算符重载的工程规范与陷阱
本篇内容偏向底层与进阶,是从 "会用 C++" 到 "精通 C++" 的必经之路。
二、运算符重载:让对象支持数学运算
(一)什么是运算符重载
运算符重载:对 C++ 已有运算符重新定义,使其支持自定义对象的运算。
本质: 运算符本质是 函数 。 例如: a + b 等价于 operator+(a, b)
重载后,我们可以写出这样的代码:
cpp
运行
Point p1(1,2), p2(3,4);
Point p3 = p1 + p2;
(二)运算符重载语法
cpp
运行
返回值类型 operator 运算符 (参数) {
// 实现逻辑
}
例如重载加号:
cpp
运行
Point operator+(Point &other) {
return Point(x + other.x, y + other.y);
}
(三)运算符重载两种实现方式
- 成员函数重载(this 指针作为左操作数)
- 全局函数重载(需要用友元访问私有成员)
三、常用运算符重载实战
1. 加号运算符 +
cpp
运行
class Point {
private:
int x, y;
public:
Point(int x=0, int y=0) : x(x), y(y) {}
Point operator+(Point &p) {
return Point(x+p.x, y+p.y);
}
};
2. 等号 ==、不等!=
cpp
运行
bool operator==(Point &p) {
return x==p.x && y==p.y;
}
bool operator!=(Point &p) {
return !(*this == p);
}
3. 自增 ++(前置、后置)
cpp
运行
// 前置 ++p
Point& operator++() {
x++; y++; return *this;
}
// 后置 p++
Point operator++(int) {
Point temp = *this; x++; y++; return temp;
}
4. 左移运算符 <<(输出对象,必须全局 + 友元)
cpp
运行
friend ostream& operator<<(ostream &out, Point &p) {
out << p.x << "," << p.y;
return out;
}
使用:
cpp
运行
cout << p1 << endl;
5. 赋值运算符 =(重点解决浅拷贝问题)
如果对象中有指针,必须重载 =,实现深拷贝。
cpp
运行
class Test {
private:
int *p;
public:
Test(int val) { p = new int(val); }
// 重载赋值
Test& operator=(Test &t) {
if (this == &t) return *this; // 防止自赋值
delete p; // 释放旧内存
p = new int(*t.p); // 深拷贝
return *this;
}
~Test() { delete p; }
};
6. 下标运算符 \[\]
cpp
运行
int& operator[](int index) {
return arr[index];
}
7. 函数调用运算符 ()
用于实现仿函数,是 STL 核心基础。
四、不能重载的运算符
以下 5 个运算符不能重载(C++ 固定规则):
::域解析.成员访问.*成员指针解引用sizeof?:三目运算符
五、友元:打破封装的 "特殊授权"
(一)什么是友元
友元允许 外部函数 / 外部类 访问当前类的 private/protected 成员。
友元的作用:
- 实现运算符重载(如 <<>>)
- 提高某些场景效率
- 方便类之间协作
注意:友元破坏封装,不要滥用!
(二)友元函数
cpp
运行
class A {
private: int x;
public:
friend void show(A &a); // 友元函数
};
void show(A &a) {
cout << a.x; // 可访问私有
}
(三)友元类
cpp
运行
class B;
class A {
friend class B; // B是A的友元类
private: int x;
};
class B {
public:
void f(A &a) { cout << a.x; }
};
六、C++ 对象模型深入(底层核心)
(一)对象占多少内存?
对象内存 = 所有非静态成员变量大小之和
- 成员函数 不占对象内存
- 静态成员变量 不属于对象,属于类
- 空对象大小:1 字节(占位)
示例:
cpp
运行
class A {
int a; double b;
};
cout << sizeof(A); // 16(内存对齐)
(二)多态对象内存布局
如果类中有虚函数:
- 对象前 4/8 字节多一个 vptr 虚指针
- 指向 vtable 虚函数表
多态调用底层: p->speak() → 通过对象找到 vptr → 通过 vptr 找到 vtable → 在表中找到函数地址 → 调用
(三)this 指针底层
- 每个非静态成员函数 都隐含一个参数:
this - this 指向当前调用对象的地址
- 成员函数访问变量本质是:
this->x
七、常函数、常对象与 mutable
(一)常函数
函数后加 const:
cpp
运行
void show() const {
// 不能修改成员变量
}
常函数中 this 是 const 指针。
(二)常对象
cpp
运行
const Person p;
只能调用常函数。
(三)mutable
被 mutable 修饰的成员变量,在常函数中也可以修改。
八、运算符重载工程规范
- 保持运算符原有语义(+ 不要做减法)
- 对称运算符建议全局重载(+ - * / == != << >>)
- 赋值运算符 = 必须成员重载
- 自增自减返回值要符合惯例
- 对象含指针必须重载 = 并深拷贝
- 不要重载逻辑运算符 && ||(会丢失短路特性)
- 链式运算符必须返回引用(<< = ++)
九、经典综合案例:复数类(Complex)
cpp
运行
#include <iostream>
using namespace std;
class Complex {
private:
double real, imag;
public:
Complex(double r=0, double i=0) : real(r), imag(i) {}
// +
Complex operator+(Complex &c) {
return Complex(real+c.real, imag+c.imag);
}
// ==
bool operator==(Complex &c) {
return real==c.real && imag==c.imag;
}
// 前置++
Complex& operator++() {
real++; imag++; return *this;
}
// 友元<<
friend ostream& operator<<(ostream &out, Complex &c);
};
ostream& operator<<(ostream &out, Complex &c) {
out << c.real << "+" << c.imag << "i";
return out;
}
int main() {
Complex c1(1,2), c2(3,4);
Complex c3 = c1 + c2;
cout << c3 << endl;
++c3;
cout << c3 << endl;
return 0;
}
十、常见错误与陷阱
- 重载 << 写成成员函数 → 无法链式调用
- 自增后置不返回临时对象 → 逻辑错误
- 赋值运算符不深拷贝 → 重复释放崩溃
- 赋值运算符不判断自赋值 → 崩溃
- 常函数修改成员 → 编译失败
- 滥用友元 → 破坏封装、难以维护
十一、本章总结
本篇文章系统、深入、完整讲解了 C++ 高级面向对象核心:
- 运算符重载:让对象支持 + - * / == != ++ -- <<>> \[\] = ()
- 友元函数 / 友元类:授权访问私有成员
- 对象内存模型:成员不占对象内存、虚指针、虚表
- this 指针底层:指向当前对象
- 常函数、常对象、mutable
- 深拷贝是重载赋值运算符的核心
掌握本篇,你将能够:
- 写出优雅、直观、接近自然语言的面向对象代码
- 理解 C++ 对象底层实现,彻底搞懂多态
- 写出工程级、高健壮性的类
- 为后续学习 STL 打下坚实基础
下一篇预告
第 9 篇《C++ 模板与泛型编程:STL 核心思想》(4000 字以上)
将带你学习:
- 函数模板
- 类模板
- 泛型编程思想
- 模板特化、分离编译
- 这是 STL(标准模板库)的基石