一、explicit 关键字:禁止隐式转换
在 C++ 中,单参数的构造函数允许隐式类型转换,这可能导致意外的行为。使用 explicit 关键字可以禁止这种隐式转换。
cpp
class P {
private:
int x, y;
public:
P(int x, int y) :x(x),y(y){}
explicit P(int x):x(x),y(0) {}
};
如果不加 explicit,以下代码可以编译通过:
cpp
P p1 = 20; // 隐式转换成 P(20),y 被默认初始化为 0
但加上 explicit 后,必须显式调用构造函数:
cpp
P p1 = 20; // 编译错误!禁止隐式转换
P p1(20); // 正确,显式调用
P p1{20}; // 正确,列表初始化
最佳实践 :单参数构造函数一律加 explicit,除非你真的需要隐式转换。
二、运算符重载
2.1 类内重载成员函数方式
+= 运算符用于将另一个对象的坐标加到当前对象上。this 指针指向当前调用函数的对象。
cpp
void operator+=(const P& p) {
x += p.x;
y += p.y;
}
> 运算符用于比较两个点的大小,只有当 x 和 y 都更大时才返回 true。
cs
bool operator>(P& p) {
return x > p.x && y > p.y;
}
2.2 类外重载普通函数方式
流运算符 << 通常需要在类外重载,因为左侧操作数是 ostream 对象而非本类对象。为了匹配 cout 的类型,需要放在 std 命名空间中。
cpp
namespace std {
ostream& operator<<(ostream& cout, P& p) {
cout << "(" << p.getX() << "," << p.getY() << ") ";
return cout;
}
}
返回 ostream& 引用是为了支持链式调用,例如 cout << p1 << p2。
三、不能重载的运算符
C++ 中有 5 个运算符不能被重载:
| 运算符 | 名称 |
|---|---|
. |
成员访问 |
.* |
成员指针访问 |
:: |
作用域解析 |
sizeof |
求大小 |
?: |
三目条件 |
尝试重载这些运算符会导致编译错误。
四、输入输出运算符重载
可以为自定义类型重载 << 和 >> 运算符,使其可以直接使用 cout 和 cin。
cpp
struct Stu {
int sid;
string name;
};
namespace std {
ostream& operator<<(ostream& cout, Stu& s) {
cout << s.name << "," << s.sid ;
return cout;
}
istream& operator>>(istream& cin, Stu& s) {
cout << "请输入学号与姓名:";
cin >> s.sid >> s.name;
return cin;
}
}
使用示例:
cpp
Stu s1{ 1001, "Lucy" };
cout << s1 << endl; // 输出: Lucy,1001
Stu s2{0};
cin >> s2; // 提示输入并读取
cout << s2 << endl;
五、完整运算符重载示例
以下是一个更完整的运算符重载示例,展示了多种运算符的实现方式。
cpp
class Num {
private:
int v;
public:
explicit Num(int v):v(v){}
int getV() { return v; }
void operator+=(int&& v) {
this->v += v;
}
void operator+=(const Num& v) {
this->v += v.v;
}
void operator*=(int&& v) {
this->v *= v;
}
void operator++() {
++v;
}
void operator++(int) {
v++;
}
bool operator>(int&& v) {
return this->v > v;
}
bool operator>(const Num& v) {
return this->v > v.v;
}
};
前置++与后置++的区别
前置 ++ 运算符没有参数,返回引用,效率较高。后置 ++ 运算符带一个 int 类型的占位参数,用于区分前置和后置版本。
表格
| 特性 | 前置++ | 后置++ |
|---|---|---|
| 声明 | operator++() |
operator++(int) |
| 参数 | 无 | 带 int 占位参数 |
| 效率 | 高(直接修改) | 低(需要保存旧值) |
| 使用建议 | 优先使用 | 需要旧值时才用 |
六、深拷贝与浅拷贝
6.1 问题引入
当类包含指针成员时,默认的拷贝构造函数会进行浅拷贝,即只复制指针的值,而不是指针指向的内容。这会导致多个对象共享同一块内存,造成双重释放或数据混乱。
cpp
class Person {
private:
int pid;
char* name;
public:
Person(int pid, const char* name) :pid(pid) {
this->name = new char[32] {0};
memcpy(this->name, name, strlen(name));
}
};
如果使用默认的拷贝构造函数:
cpp
Person p1(1001, "disen");
Person p2 = p1; // 浅拷贝,p1 和 p2 的 name 指向同一块内存
浅拷贝的问题在于:
-
修改 p2 的 name 会影响 p1
-
析构时会 delete[] name 两次,导致程序崩溃
6.2 深拷贝解决方案
自定义拷贝构造函数,为指针成员申请新的内存空间,并复制内容。
cpp
Person(const Person& other) {
pid = other.pid;
name = new char[32] {0};
memcpy(name, other.name, strlen(other.name));
}
深拷贝确保每个对象拥有独立的内存空间,互不干扰。
| 特性 | 浅拷贝 | 深拷贝 |
|---|---|---|
| 行为 | 复制指针值 | 申请新空间,复制内容 |
| 适用场景 | 无指针成员 | 有指针成员(堆内存) |
| 问题 | 双重释放、数据共享 | 内存开销稍大 |
| 实现 | 编译器默认提供 | 必须自定义拷贝构造 |
七、构造函数与析构函数
7.1 对象生命周期
对象的生命周期包括三个阶段:
-
编译器创建对象空间
-
调用构造函数初始化(编译器默认提供无参构造)
-
对象离开有效作用域时,调用析构函数释放资源(编译器默认也提供)
7.2 析构函数的定义
析构函数的特点:
-
无返回类型
-
函数名同类名,前面加
~ -
无参数表
cpp
class Point {
private:
int x, y;
public:
Point(int x, int y) :x(x), y(y) {
cout << "Point(int x, int y):" <<this << endl;
}
~Point() {
cout << " ~Point():" << this << endl;
}
};
7.3 构造与析构的区别
| 特性 | 构造函数 | 析构函数 |
|---|---|---|
| 功能 | 初始化对象 | 回收资源 |
| 重载 | 可重载 | 只有一个,且无参 |
| 初始化列表 | 有 | 无析构列表 |
7.4 生命周期演示
cpp
int main() {
Point p1(10, 20);
{
Point p2(1, 2);
p2.draw();
}
cout << "-------p1-------" << endl;
p1.draw();
cout << "----end p1---" << endl;
return 0;
}
执行流程:
-
构造 p1
-
构造 p2
-
p2 离开作用域,析构 p2
-
输出分隔线
-
使用 p1
-
main 结束,析构 p1
栈对象遵循"先构造的后析构"原则。
八、完整代码解析
cpp
#include <iostream>
using namespace std;
class P {
private:
int x, y;
public:
P(int x, int y) :x(x),y(y){}
explicit P(int x):x(x),y(0) {}
void operator+=(const P& p) {
x += p.x;
y += p.y;
}
bool operator>(P& p) {
return x > p.x && y > p.y;
}
int getX() { return x; }
int getY() { return y; }
};
namespace std {
ostream& operator<<(ostream& cout, P& p) {
cout << "(" << p.getX() << "," << p.getY() << ") ";
return cout;
}
}
int main() {
P p1( 20 );
P p2(1, 2);
P p3(4, 5);
cout << p1 << ", " << p2 << "," << p3 << endl;
p1 += p2;
cout << (p3 > p1) << endl;
return 0;
}
代码执行结果:
-
p1 初始化为 (20, 0),因为使用了单参数 explicit 构造函数
-
p2 为 (1, 2),p3 为 (4, 5)
-
p1 += p2 后,p1 变为 (21, 2)
-
p3 > p1 比较 (4>21 && 5>2),结果为 false,输出 0