跳到主要内容
C++ 类与对象进阶:构造函数、拷贝构造与操作符重载 | 极客日志
C++ 算法
C++ 类与对象进阶:构造函数、拷贝构造与操作符重载 C++ 类与对象进阶涉及默认成员函数的核心机制,包括构造函数初始化、拷贝构造的深浅拷贝区别、析构函数资源释放及操作符重载语法。文章详细解析了编译器生成规则、内置与自定义类型的处理差异、内存泄漏风险及三法则原则。通过代码示例演示了对象生命周期管理、赋值运算符重载及流插入提取的实现方法,旨在帮助开发者理解 C++ 对象模型并编写安全高效的代码。
ArchDesign 发布于 2026/3/26 更新于 2026/4/23 2 浏览类的默认成员函数
定义: 默认成员函数是用户未显式实现,由编译器自动生成的成员函数。
在一个类中,如果不显式编写,编译器会默认生成以下 4 个重要的默认成员函数:
默认成员函数是 C++ 中重要且复杂的知识点,需要从两个维度进行掌握:
了解编译器自动生成的默认函数行为及其适用性
当编译器自动生成的默认函数无法满足需求时,掌握自定义实现的方法
一、构造函数
什么是构造函数?
构造函数用于在创建对象时初始化对象的状态。对象开辟空间是栈帧的任务,构造函数负责完成初始化。
1.1 构造函数的核心语法
#include <iostream>
using namespace std;
class Student {
public :
int age;
Student () {
age = 18 ;
cout << "调用了默认构造函数" << endl;
}
};
int main () {
Student s1;
cout << s1. age << endl;
return 0 ;
}
构造函数的语法要点:
类名 (形式参数)
无需返回值
名称与类名必须相同
形式参数可有可无,根据需求而定
1.2 构造函数的常见类型
1.2.1 无参构造函数
不带任何参数的构造函数。
class {
:
() {
_year = ;
_month = ;
_day = ;
}
:
_year;
_month;
_day;
};
Date
public
Date
1
1
1
private
int
int
int
1.2.2 带参数的构造函数 class Date {
public :
Date (int year, int month, int day) {
_year = year;
_month = month;
_day = day;
}
private :
int _year;
int _month;
int _day;
};
1.2.3 全缺省构造函数 在未传参时设置默认的初始值,且允许在创建对象时传入特定的值。
class Date {
public :
Date (int year = 1 , int month = 1 , int day = 1 ) {
_year = year;
_month = month;
_day = day;
}
private :
int _year;
int _month;
int _day;
};
注意: 全缺省构造函数和无参构造函数不能同时出现,因为当创建一个不带参数的对象时,编译器会产生歧义。
1.3 默认构造函数
无参构造函数
全缺省构造函数
编译器自动生成的默认构造函数(当用户未定义任何构造函数时)
无参构造函数和全缺省构造函数虽然构成函数重载,但是调用时会存在歧义。
很多读者会认为默认构造函数是编译器默认生成的才叫默认,实际上无参构造函数、全缺省构造函数也是默认构造。
总结来说,不传实参就可以调用的构造函数就叫默认构造函数。
1.4 编译器默认生成的构造函数
1.4.1 生成的条件 编译器生成这个构造函数有且仅有一个条件:你的类中没有定义任何默认构造函数。
如果未写构造函数,编译器会生成一个默认无参的构造函数。
一旦自己写了任意一个默认构造函数(哪怕是带参数的),编译器就会停止生成默认构造函数。
class A {
};
class B {
public :
B (int x) { cout << x << endl; }
};
int main () {
B b;
B b (1 ) ;
return 0 ;
}
1.4.2 执行的逻辑 C++ 把类型分成内置类型(基本类型)和自定义类型。
内置类型: C++ 提供的原生数据类型,如 int, char, double, 指针等。
自定义类型: 使用 class/struct 等关键字自己定义的类型。
编译器生成的默认构造函数对待不同类型的成员变量态度截然不同:
A. 对待'自定义类型' —— 负责
如果类里包含其他的类对象(比如 string, vector 或另一个 class),编译器生成的构造函数会自动调用这些成员的默认构造函数。
B. 对待'内置类型' —— 不处理
对于基础数据类型,编译器默认生成的构造函数什么都不做,这意味着这些变量的内存里原来是什么数据,现在还是什么(可能是垃圾数据)。
typedef int STDataType;
class Stack {
public :
Stack (int n = 4 ) {
_a = (STDataType*)malloc (sizeof (STDataType) * n);
if (nullptr == _a) {
perror ("malloc 申请空间失败" );
return ;
}
_capacity = n;
_top = 0 ;
}
private :
STDataType* _a;
size_t _capacity;
size_t _top;
};
class MyQueue {
public :
private :
Stack pushst;
Stack popst;
};
int main () {
MyQueue mq;
return 0 ;
}
如果在 Stack 类中删除了显式定义的构造函数,将会发生连带反应:
编译阶段:代码依然可以编译通过。原因:当你没有定义任何构造函数时,编译器会按照 C++ 标准自动为你生成一个隐式的默认构造函数。
运行阶段:极度危险(未定义行为)。编译器自动生成的默认构造函数对内置类型成员变量(如 int、指针、size_t)不做任何初始化处理。此时 _a (指针) 变成了野指针,存储的是内存中的随机值;_capacity 和 _top 也是随机值。
class Date {
public :
void Print () {
cout << _year << "/" << _month << "/" << _day << endl;
}
private :
int _year;
int _month;
int _day;
};
int main () {
Date d1;
d1. Print ();
return 0 ;
}
结论: 永远不要信任编译器默认生成的构造函数来处理内置类型如 int、bool 或指针等。
1.5 构造函数小结
名称与类名相同。
没有返回值:连 void 都不用写。
自动调用:不需要手动调用,它会在对象被创建的那一刻自动执行。
可以重载:一个类可以有多个构造函数,只要参数列表不同即可。
如果类中没有显式定义构造函数,则 C++ 编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
无参构造函数、全缺省构造函数、我们不写构造时编译器默认生成的构造函数,都叫做默认构造函数。但是这三个函数有且只有一个存在,不能同时存在。
二、拷贝构造函数 简单来说,它的作用是'克隆':用一个已存在的对象来初始化一个新对象。
2.1 拷贝构造的核心语法 class ClassName {
public :
ClassName (const ClassName& other) {
}
};
引用 & 是必须的:如果不传引用而是传值,传参过程本身又要拷贝,就会无限递归调用拷贝构造函数,导致栈溢出。
const 通常要加:保证在拷贝过程中,不会意外修改那个'原件'。
前置知识:按值传递的代价
在 C++ 中,当通过传值传参的方式调用函数时,编译器需要将实参对象复制给形参对象,此时复制的过程就会调用拷贝构造函数。
灾难推演:如果去掉了 &
假设我们将拷贝构造函数写成了这样(去掉了 &),通过调用拷贝构造函数让对象 d1 初始化新对象 d2。
class Date {
public :
Date (Date d) {
_year = d._year;
_month = d._month;
_day = d._day;
}
};
程序执行 Date d2(d1),准备调用拷贝构造函数。系统发现拷贝构造函数 Date(Date d) 的参数是 Date d(按值传递)。为了调用这个函数,系统必须先把实参 d1 复制给形参 d。如何复制?系统必须调用 Date 的拷贝构造函数。系统再次准备调用拷贝构造函数……回到第 2 步。结果:这就形成了一个无限递归调用。
为什么加了引用 & 就没事了?
引用传递的含义是:形参 d 只是实参 d1 的一个别名。传递参数时,不需要创建新的副本,也不需要分配新的内存,只是把 d1 的地址/身份传进去了。既然不需要创建新对象,就不会触发'拷贝'动作,也就不会再次调用拷贝构造函数。死循环被打破了,函数顺利进入内部执行赋值操作。
2.2 拷贝构造的特性 C++ 规定自定义类型对象进行拷贝行为必须调用拷贝构造,所以这里自定义类型传值传参和传值返回都会调用拷贝构造完成。
场景一:传值传参
当你把一个对象作为参数传给函数时,本质上是用实参初始化形参。
void func (Student s)
{
}
int main () {
Student s1;
func (s1);
return 0 ;
}
开辟空间:函数 func 被调用,栈内存中为形参 s 开辟空间。
触发拷贝:既然 s 是新诞生的对象,且它的初值来源于 s1,编译器必须调用拷贝构造函数 Student(const Student&)。
结果:s 成为 s1 的一份独立拷贝。当函数结束后,s 被销毁,s1 不受影响。
场景二:传值返回
这是比较隐蔽的拷贝场景,当函数返回一个对象时,局部变量在函数结束时就会销毁,所以通过'拷贝'把值传出去。
Student createStudent () {
Student temp;
return temp;
}
int main () {
Student s2 = createStudent ();
return 0 ;
}
创建临时对象:createStudent 结束前,编译器会在调用处的栈帧上创建一个'临时匿名对象'。
第一次拷贝:调用拷贝构造函数,把 temp 拷贝给这个'临时匿名对象'。(然后 temp 销毁)。
第二次拷贝:在 main 函数中,使用'临时匿名对象'去初始化 s2,再次调用拷贝构造函数。(然后临时对象销毁)。
2.3 编译器默认生成的拷贝构造
2.3.1 生成的条件 若未显式定义拷贝构造,编译器会自动生成拷贝构造函数。
2.3.2 执行的逻辑
对内置类型成员变量会完成值拷贝/浅拷贝(一个字节一个字节的拷贝)。
对自定义类型成员变量会调用他的拷贝构造。
class Date {
public :
Date () { _year = 1 ; _month = 1 ; _day = 1 ; }
Date (const Date& d) {
_day = d._day;
_month = d._month;
_year = d._year;
}
void Print () { cout << _year << "/" << _month << "/" << _day << endl; }
private :
int _year;
int _month;
int _day;
};
int main () {
Date d1;
Date d2 = d1;
return 0 ;
}
代码示例 B:对自定义类型的成员,调用其类中的拷贝构造函数
#include <iostream>
#include <string>
using namespace std;
class Wallet {
public :
Wallet () {}
Wallet (const Wallet& w) {
cout << "【自定义类型】Wallet 的拷贝构造被调用了!" << endl;
}
};
class Person {
public :
int age;
int * scorePtr;
Wallet myWallet;
};
int main () {
Person p1;
p1. age = 18 ;
int score = 100 ;
p1. scorePtr = &score;
cout << "--- 开始拷贝 ---" << endl;
Person p2 = p1;
cout << "p1 的年龄:" << p1. age << " " << "p2 的年龄:" << p2. age << endl;
cout << "p1 成绩的地址:" << p1. scorePtr << " " << "p2 成绩的地址:" << p2. scorePtr << endl;
cout << "--- 拷贝结束 ---" << endl;
return 0 ;
}
2.4 拷贝构造的易错点
2.4.1 浅拷贝操作 浅拷贝是 C++ 编译器默认的复制行为,当你把一个对象赋值给另一个对象时,编译器会进行'按位拷贝',即把原对象中所有变量的值直接复制给新对象。
对于普通成员变量(如 int, double, char):浅拷贝没有问题,值被直接复制。
对于指针成员变量:浅拷贝只复制了指针本身(内存地址),而没有复制指针指向的数据。
typedef int STDataType;
class Stack {
public :
Stack (int n = 4 ) {
_a = (STDataType*)malloc (sizeof (STDataType) * n);
if (nullptr == _a) {
perror ("malloc 申请空间失败" );
return ;
}
_capacity = n;
_top = 0 ;
}
Stack (const Stack& st) {
_a = st._a;
_capacity = st._capacity;
_top = st._top;
}
~Stack () { free (_a); _a = nullptr ; }
private :
STDataType* _a;
size_t _capacity;
size_t _top;
};
int main () {
Stack st1;
Stack st2 = st1;
return 0 ;
}
数据共享冲突:如果朋友修改了房间里的东西,你回到家也会看到东西变了。
重复释放:这是最严重的问题。当你们两个的生命周期结束时(对象销毁),析构函数都会尝试'销毁房间'。当对象 A 释放了内存,对象 B 尝试释放同一块已经被释放的内存,造成程序崩溃。
2.4.2 深拷贝操作 为了克服浅拷贝的局限性,我们需要采用深拷贝机制。
在深拷贝过程中,当遇到指针成员时,会执行以下操作:
为新对象分配独立的内存空间
将原对象指针指向的数据完整复制到新内存中
使新对象的指针指向新分配的内存区域
这种处理方式确保了对象间的完全独立性。
typedef int STDataType;
class Stack {
public :
Stack (int n = 4 ) {
_a = (STDataType*)malloc (sizeof (STDataType) * n);
if (nullptr == _a) {
perror ("malloc 申请空间失败" );
return ;
}
_capacity = n;
_top = 0 ;
}
Stack (const Stack& st) {
_a = (STDataType*)malloc (sizeof (STDataType) * st._capacity);
if (nullptr == _a) {
perror ("malloc 申请空间失败!!!" );
return ;
}
memcpy (_a, st._a, sizeof (STDataType) * st._top);
_capacity = st._capacity;
_top = st._top;
}
~Stack () { free (_a); _a = nullptr ; }
private :
STDataType* _a;
size_t _capacity;
size_t _top;
};
int main () {
Stack st1;
Stack st2 = st1;
return 0 ;
}
避免了数据共享冲突:对象 st1 数据进行改变,对象 st2 的数据不会受到影响。
避免了重复释放:当 st1 和 st2 进行释放空间时,两者不会出现重复释放而是独立地释放各自的空间。
2.5 拷贝构造小结
拷贝构造函数是构造函数的一种重载形式。
拷贝构造函数的第一个参数必须是类类型对象的引用。若采用传值方式,编译器会直接报错,因为这会引发无限递归调用。拷贝构造函数可以包含多个参数,但第一个参数必须是类类型对象的引用,后续参数必须具有默认值。
C++ 规定自定义类型对象进行拷贝时必须调用拷贝构造函数。因此,在传值参数和传值返回自定义类型对象时,都会调用拷贝构造函数完成操作。
如果未显式定义拷贝构造函数,编译器会自动生成一个。自动生成的拷贝构造函数会对内置类型成员变量进行值拷贝(浅拷贝,即逐字节复制),对自定义类型成员变量则会调用其拷贝构造函数。
对于像 Date 这样仅包含内置类型成员且不涉及资源管理的类,编译器自动生成的拷贝构造函数即可满足需求,无需手动实现。
但对于像 Stack 这样虽然使用内置类型但涉及资源管理(如指针 _a 指向资源)的类,自动生成的浅拷贝无法满足需求,需要手动实现深拷贝。
若类中包含自定义类型成员,编译器会自动调用自定义成员类的拷贝构造函数,因此也不需要手动实现拷贝构造函数。
传值返回会通过拷贝构造函数生成临时对象,而传引用返回则直接返回对象的引用(别名),不会产生拷贝。
特别注意:若返回的是函数局部对象(函数结束后即销毁),则不能使用引用返回,否则会导致野引用(类似于野指针),只有当返回对象在函数结束后仍然有效时,才适合使用引用返回来减少拷贝开销。
三、析构函数 析构函数是面向对象编程(尤其是 C++)中的一个核心概念,简单来说,它是构造函数的'反义词'。如果说构造函数是'对象出生时的初始化(比如分配资源)',那么析构函数就是'对象临终前的清理(比如释放资源)'。
3.1 析构函数的核心语法
名称与类名相同,但在前面加一个波浪号 ~。
没有返回值,也没有类型。
不接受任何参数(因此析构函数不能被重载,一个类只能有一个析构函数)。
class MyClass {
public :
MyClass () { }
~MyClass () { }
};
typedef int STDataType;
class Stack {
public :
Stack (int n = 4 ) {
_a = (STDataType*)malloc (sizeof (STDataType) * n);
if (nullptr == _a) {
perror ("malloc 申请空间失败" );
return ;
}
_capacity = n;
_top = 0 ;
}
~Stack () {
free (_a);
_a = nullptr ;
}
private :
STDataType* _a;
size_t _capacity;
size_t _top;
};
注意: 在构造函数中动态分配内存资源时,必须显式实现析构函数来释放这些资源,以防止内存泄漏。
3.2 析构函数的调用条件
3.2.1 离开作用域 离开作用域(栈对象):在函数内部定义的局部变量,当函数执行完毕或遇到右大括号 } 时。
代码示例:局部对象在函数执行完毕时自动调用析构函数
#include <iostream>
using namespace std;
class Date {
public :
Date () { _year = 1 ; _month = 1 ; _day = 1 ; }
~Date () { cout << "析构函数被调用" << endl; }
private :
int _year;
int _month;
int _day;
};
void testScope () {
cout << "---testScope 函数开始 ---" << endl;
Date obj;
cout << "--- testScope 函数即将结束 ---" << endl;
}
int main () {
cout << "---执行 main 函数,准备调用 testScope 函数---" << endl;
testScope ();
cout << "---回到 main 函数,继续执行---" << endl;
return 0 ;
}
3.2.2 delete 操作 delete 操作:当你拥有一个指向堆对象的指针,并显式调用 delete pointer 时进行析构函数的调用。
class Date {
public :
Date () { _year = 1 ; _month = 1 ; _day = 1 ; }
~Date () { cout << "析构函数被调用" << endl; }
private :
int _year;
int _month;
int _day;
};
void testHeap () {
cout << "--- testHeap 函数开始 ---" << endl;
Date* ptr = new Date;
cout << "--- 做一些操作 ---" << endl;
delete ptr;
cout << "--- testHeap 函数结束 ---" << endl;
}
int main () {
testHeap ();
return 0 ;
}
3.3 编译器默认生成的析构函数
3.3.1 生成的条件 编译器生成这个析构函数有且仅有一个条件:你的类中没有定义任何析构函数。
3.3.2 行为逻辑
对待'自定义类型' :如果你的类里包含其他的类对象,编译器生成的析构函数会自动调用这些成员的析构函数。
对待'内置类型' :对于基础数据类型,编译器默认生成的构造函数什么都不做。
typedef int STDataType;
class Stack {
public :
Stack (int n = 4 ) { _a = (STDataType*)malloc (sizeof (STDataType) * n); _capacity = n; _top = 0 ; }
~Stack () { free (_a); _a = nullptr ; }
private :
STDataType* _a;
size_t _capacity;
size_t _top;
};
class MyQueue {
public :
private :
Stack pushst;
Stack popst;
};
typedef int STDataType;
class Stack {
public :
Stack (int n = 4 ) { _a = (STDataType*)malloc (sizeof (STDataType) * n); _capacity = n; _top = 0 ; }
private :
STDataType* _a;
size_t _capacity;
size_t _top;
};
特别注意:编译器生成的默认析构函数回收了栈区的 _a(那把钥匙),堆区的那块内存(那个房间)依然被标记为'占用',但因为钥匙没了,你再也无法访问它,也无法释放它,这就导致了内存泄漏。
3.4 析构函数调用顺序
3.4.1 同一作用域的局部对象 如果在同一个函数里定义了多个对象,它们的析构顺序遵循栈 (Stack) 的特性:后进先出 (LIFO)。
class Date {
public :
Date () { _year = 1 ; _month = 1 ; _day = 1 ; }
~Date () { cout << "析构函数被调用" << endl; }
private :
int _year;
int _month;
int _day;
};
int main () {
Date d1;
Date d2;
}
3.4.2 数组对象 对于数组对象而言:数组元素的析构顺序与构造顺序严格相反。
构造顺序:按照数组下标从小到大 (0 -> N)。
析构顺序:按照数组下标从大到小 (N -> 0)。
3.4.3 局部静态对象和全局对象 这些对象像是'元老',程序的生命不结束,它们就不退休。
全局对象:在 main 开始前出生,main 结束后销毁。
静态对象:第一次运行到定义处出生,main 结束后销毁。
#include <iostream>
#include <string>
using namespace std;
class Date {
public :
Date (string name) : _name(name) { cout << "构造:" << _name << endl; }
~Date () { cout << "析构:" << _name << endl; }
private :
string _name;
};
Date globalObj ("Global 对象" ) ;
void func () {
static Date staticObj ("Static 对象" ) ;
}
int main () {
func ();
return 0 ;
}
3.5 析构函数的小结
析构函数名由类名前加波浪号 ~ 构成。
析构函数无参数且无返回值。
每个类只能有一个析构函数。若未显式定义,编译器会自动生成默认析构函数。
当对象生命周期结束时,系统会自动调用其析构函数。
与构造函数类似,编译器生成的默认析构函数对内置类型成员不做处理,但会调用自定义类型成员的析构函数。
显式定义的析构函数同样会调用自定义类型成员的析构函数。
当类未申请资源时,可以不写析构函数而使用编译器生成的默认版本;若默认析构函数已满足需求,也无需显式定义。
但涉及资源申请时,必须自定义析构函数以避免资源泄漏。
在局部作用域中,多个对象的析构顺序遵循 C++ 规定:后定义的对象先析构。
四、操作符重载
操作符重载让你能够为自定义类型(类或结构体)重新定义运算符(如 +, -, *, << 等)的行为。
这项功能的主要价值在于提升代码可读性,让自定义类型的使用体验与内置类型一样自然直观。
4.1 运算符重载 核心概念:C++ 支持通过运算符重载为自定义类型赋予新的运算语义,当对类对象使用运算符时,编译器会自动将其转换为对应的重载函数调用。
若未定义相关运算符重载,则会触发编译错误。
当且仅当参数中至少包含一个自定义类型,才会触发运算符重载函数。
4.1.1 运算符重载的语法 语法规则:返回类型 + 关键字 operatorOp + 参数。
返回类型:可以是 void, int, bool, 返回'引用', 返回'对象'等。
关键字和操作符:关键字必须是 operator。操作符 Op 必须是 C++ 现有的符号。
参数:重载运算符函数的参数个数和该运算符作用的运算对象数量一样多。
一元运算符:仅需一个参数。
二元运算符:需要两个参数,对于二元运算符而言,其左侧的运算对象传给第一个参数,其右侧运算对象传给第二个参数。
有 5 个特殊的符号,绝对禁止重载:. (点号), :: (双冒号), sizeof, ?:, .*。
代码示例:对 Person 这个自定义类型实现'+'运算符重载
#include <iostream>
using namespace std;
class Person {
public :
Person (int age) { _age = age; }
private :
int _age;
};
int operator +(Person a, Person b) {
return a._age + b._age;
}
int main () {
Person p1 (18 ) ;
Person p2 (20 ) ;
cout << "p1 和 p2 的总年龄为:" << p1 + p2 << endl;
return 0 ;
}
4.1.2 全局函数和成员函数的运算符重载 1. 全局函数的运算符重载
当我们将运算符重载为全局函数时,该函数不属于任何类。
核心机制:所有操作数都必须通过参数显式传递。
参数数量:等于运算符原本需要的操作数。
2. 成员函数的运算符重载
当我们将运算符重载为类的成员函数时,该函数是类的一部分。
核心机制:运算符左侧的操作数会自动成为调用该函数的对象(即 this 指针指向的对象)。
参数数量:比运算符原本需要的操作数少一个。
4.1.3 全局函数和成员函数的选择
必须用成员函数的情况: =, [], (), ->。
必须用全局函数的情况: <<, >>。
建议用全局函数的情况: 所有的二元算术运算符 (+, -, *, /),比较运算符 (==, !=, <)。
建议用成员函数的情况: 复合赋值运算符 (+=, -=, *=)。
4.1.4 特殊的运算符重载 ① 前置 ++ 和后置 ++ 的运算符重载
在重载 ++ 运算符时,存在前置 ++ 和后置 ++ 两种形式,它们都使用 operator++ 作为函数名。
前置 ++: 先加,后用。返回引用。原型:Type& operator++()。
后置 ++: 先用,后加。必须带有一个 int 类型的占位参数。返回旧值的副本。原型:Type operator++(int)。
#include <iostream>
using namespace std;
class MyInt {
private :
int value;
public :
MyInt (int v = 0 ) : value (v) {}
MyInt& operator ++() {
value++;
return *this ;
}
MyInt operator ++(int ) {
MyInt temp = *this ;
value++;
return temp;
}
void print () const { cout << "Value: " << value << endl; }
};
int main () {
MyInt a (10 ) ;
MyInt b = ++a;
MyInt c = a++;
return 0 ;
}
② 流插入 << 和 流提取 >> 的运算符重载
核心规则:必须实现为全局函数(非成员函数),并通常声明为类的 friend(友元)。
1. 流插入运算符 <<
函数原型:ostream& operator<<(ostream& out, const Type& obj)。
2. 流提取运算符 >>
函数原型:istream& operator>>(istream& in, Type& obj)。
#include <iostream>
using namespace std;
class Complex {
private :
int real;
int imag;
friend ostream& operator <<(ostream& out, const Complex& c);
friend istream& operator >>(istream& in, Complex& c);
public :
Complex (int r = 0 , int i = 0 ) : real (r), imag (i) {}
};
ostream& operator <<(ostream& out, const Complex& c) {
out << c.real << "+" << c.imag << "i" ;
return out;
}
istream& operator >>(istream& in, Complex& c) {
in >> c.real >> c.imag;
return in;
}
int main () {
Complex c1;
cin >> c1;
cout << "你输入的复数是:" << c1 << endl;
return 0 ;
}
4.2 赋值运算符重载 什么是赋值运算符重载?
赋值运算符重载是一个默认成员函数,用于实现两个已存在对象之间的拷贝赋值。
4.2.1 赋值重载的核心语法 核心语法:ClassName& operator=(const ClassName& 参数名)。
返回类型:ClassName&,支持链式赋值。
函数名:operator=。
参数:const ClassName&,避免拷贝实参,提高效率。
class Date {
public :
Date& operator =(const Date& d) {
if (this == &d) {
return *this ;
}
_year = d._year;
_month = d._month;
_day = d._day;
return *this ;
}
private :
int _year;
int _month;
int _day;
};
4.2.2 编译器默认生成的赋值重载 如果你没有自己编写赋值运算符重载,编译器会自动为你生成一个。
内置类型成员变量会进行值拷贝(逐字节复制)。
自定义类型成员变量则会调用其赋值运算符重载函数。
对于基础类型,直接复制数值;对于指针类型,直接复制地址值,这是最危险的地方,因为它不复制指针指向的内容。
五、总结:三法则 (Rule of Three) C++ 有一个著名的原则:如果你需要显式定义以下其中一个,你通常需要定义全部三个:
析构函数 (Destructor)
拷贝构造函数 (Copy Constructor)
拷贝赋值运算符 (Copy Assignment Operator)
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online