跳到主要内容
C++ 深入:类与对象的默认成员函数及运算符重载 | 极客日志
C++
C++ 深入:类与对象的默认成员函数及运算符重载 C++ 类与对象的核心机制解析。涵盖编译器自动生成的六个默认成员函数,包括构造函数、析构函数、拷贝构造函数及赋值运算符重载。重点阐述浅拷贝与深拷贝的区别,解决资源泄漏问题。详解运算符重载规则,特别是前置后置自增、比较运算符及流操作符的实现细节。通过日期类(Date)的综合实战案例,演示如何正确管理内存、处理 const 成员函数以及自定义取地址行为,确保代码的安全性与效率。
PentesterX 发布于 2026/3/29 0 浏览类的默认成员函数
编译器会自动生成一些成员函数,我们称之为默认成员函数。在 C++11 之前,一个类如果不写任何构造函数,编译器会默认生成以下 6 个函数。C++11 之后又增加了移动构造和移动赋值。
我们可以从两个角度来理解它们:
不写时,编译器生成的行为是否符合预期?
如果不符合,如何手动实现?
构造函数
构造函数的核心任务是对象实例化时的初始化。就像以前写栈或队列需要显式调用 Stack Init(),有了构造函数就不需要这一步了。
构造函数的特点:
函数名与类名相同。
无返回值(连 void 都不能写)。
对象实例化时系统自动调用。
可以重载。
如果类中没有显式定义构造函数,编译器会自动生成一个无参的默认构造函数。一旦用户定义了任意构造函数,编译器将不再自动生成无参版本。这里要注意,所谓的'默认构造函数'不仅指编译器生成的那个,也包括无参构造函数和全缺省构造函数。总结来说,不传实参就能调用的构造都叫默认构造。但不传实参时,内置类型成员变量是否初始化是不确定的(看编译器),而自定义类型成员变量会调用其默认构造函数,若没有则报错,此时需用初始化列表解决。
基本运用
class Date {
public :
Date () { _year = 1 ; _month = 1 ; _day = 1 ; }
Date (int year, int month, int day) {
_year = year;
_month = month;
_day = day;
}
void Print () {
cout << _year << "/" << _month << "/" << _day << endl;
}
private :
int _year;
int _month;
int _day;
};
int main {
Date d1;
d ();
;
d ();
;
}
()
1.
Print
Date d2 (2025 , 11 , 25 )
2.
Print
return
0
注意:通过无参构造函数创建对象时,对象后面不用跟括号,否则编译器无法区分这里是函数声明还是实例化对象。
特殊运用:队列中的构造 当类中包含自定义类型的成员变量时,构造函数的调用顺序很重要。
typedef int STDataType;
class Stack {
public :
Stack (int n = 4 ) {
_a = (STDataType*)malloc (sizeof (STDataType) * n);
if (_a == nullptr ) {
perror ("malloc fail" );
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 ;
}
析构函数 析构函数与构造函数功能相反,负责对象生命周期结束时的资源清理。它不是销毁对象本身,而是释放对象占用的资源。
函数名是类名前加 ~。
无参数、无返回值。
一个类只能有一个析构函数。
未显式定义时,系统自动生成默认析构函数。
对于内置类型成员,默认析构不做处理;对于自定义类型成员,会调用其析构函数。如果类中申请了资源(如 malloc),必须手动编写析构函数释放,否则会造成内存泄漏。
析构函数运用 typedef int STDataType;
class Stack {
public :
Stack (int n = 4 ) {
_a = (STDataType*)malloc (sizeof (STDataType) * n);
if (_a == nullptr ) {
perror ("malloc fail" );
return ;
}
_capacity = n;
_top = 0 ;
}
~Stack () {
free (_a);
_a = nullptr ;
_capacity = _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 ;
}
拷贝构造函数 如果一个构造函数的第一个参数是自身类类型的引用,且后续参数都有默认值,它就是拷贝构造函数。它是构造函数的一个重载。
第一个参数必须是类类型对象的引用,传值会导致无穷递归。
若未显式定义,编译器生成默认拷贝构造(浅拷贝)。
对内置类型成员完成值拷贝,对自定义类型成员调用其拷贝构造。
Date 类 :全是内置类型,无需资源管理,默认拷贝构造够用。
Stack 类 :包含指针指向堆内存,默认浅拷贝会导致析构时重复释放,需实现深拷贝。
MyQueue 类 :成员是自定义类型 Stack,编译器生成的拷贝构造会调用 Stack 的拷贝构造,若 Stack 已正确实现,MyQueue 可沿用默认。
小技巧 :如果一个类显式实现了析构并释放资源,通常也需要显式实现拷贝构造。
常见误区:无穷递归
错误写法(传值) class Date {
public :
Date (const Date d) { _year = d._year; _month = d._month; _day = d._day; }
private :
int _year, _month, _day;
};
正确写法(引用) class Date {
public :
Date (const Date& d) { _year = d._year; _month = d._month; _day = d._day; }
private :
int _year, _month, _day;
};
浅拷贝与深拷贝
浅拷贝问题 class Stack {
public :
Stack (int n = 4 ) {
_a = (STDataType*)malloc (sizeof (STDataType) * n);
_capacity = n;
_top = 0 ;
}
~Stack () {
cout << "~Stack()" << endl;
free (_a);
_a = nullptr ;
_top = _capacity = 0 ;
}
private :
STDataType* _a;
size_t _capacity;
size_t _top;
};
int main () {
Stack st1;
st1. Push (1 );
Stack st2 (st1) ;
return 0 ;
}
深拷贝修正 class Stack {
public :
Stack (const Stack& st) {
cout << "Stack(const Stack& st)" << endl;
_a = (STDataType*)malloc (sizeof (STDataType) * st._capacity);
if (_a == nullptr ) {
perror ("malloc fail!!!" );
return ;
}
memcpy (_a, st._a, sizeof (STDataType) * st._top);
_top = st._top;
_capacity = st._capacity;
}
};
引用返回与传参优化 传值返回会产生临时对象,效率低。传引用返回可以减少拷贝,但要注意返回对象的生命周期。
Stack& func2 () {
static Stack st;
return st;
}
赋值运算符重载 赋值运算符用于两个已经存在的对象之间拷贝。它与拷贝构造的区别在于:一个是初始化新对象,一个是赋值给旧对象。
必须重载为成员函数。
参数建议为 const 当前类类型引用。
返回值建议为 当前类类型引用(支持连续赋值 a=b=c)。
若实现了析构释放资源,必须实现赋值运算符重载。
class Date {
public :
Date& operator =(const Date& d) {
if (this != &d) {
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this ;
}
private :
int _year, _month, _day;
};
运算符重载 C++ 允许通过运算符重载指定类类型对象的行为。重载后的优先级和结合性与内置类型一致。
一元运算符有一个参数,二元运算符有两个参数。
若是成员函数,左侧运算对象由隐式的 this 指针传递。
<< 和 >> 必须重载为全局函数,因为 this 会抢占第一个位置。
后置 ++ 需增加一个 int 形参以区分前置 ++。
访问私有成员
成员公开(不推荐)。
提供 Getter 函数。
友元函数。
重载为成员函数。
class Date {
friend bool operator ==(const Date& d1, const Date& d2);
};
bool operator ==(const Date& d1, const Date& d2) {
return d1. _year == d2. _year && d1. _month == d2. _month && d1. _day == d2. _day;
}
实战案例:日期类实现 下面是一个完整的日期类实现,涵盖了构造函数、析构、拷贝、赋值、运算符重载及流操作。
头文件 (Date.h) #pragma once
#include <assert.h>
#include <iostream>
using namespace std;
class Date {
friend ostream& operator <<(ostream& out, const Date& d);
friend istream& operator >>(istream& in, Date& d);
public :
Date (int year = 1990 , int month = 1 , int day = 1 ) {
_year = year; _month = month; _day = day;
if (!CheckDate ()) {
cout << "非法日期" ;
Print ();
}
}
void Print () {
cout << _year << "-" << _month << "-" << _day << endl;
}
inline int GetMonthDay (int year, int month) {
assert (month > 0 && month < 13 );
static int MontDayArray[13 ] = {-1 , 31 , 28 , 31 , 30 , 31 , 30 , 31 , 30 , 31 , 30 , 31 , 30 };
if ((month == 2 && ((year % 4 == 0 && year % 100 != 0 ) || year % 400 == 0 ))) {
return 29 ;
}
return MontDayArray[month];
}
bool CheckDate () ;
bool operator <(const Date& d);
bool operator <=(const Date& d);
bool operator >(const Date& d);
bool operator >=(const Date& d);
bool operator ==(const Date& d);
bool operator !=(const Date& d);
Date& operator +=(int day);
Date operator +(int day);
Date& operator -=(int day);
Date operator -(int day);
Date operator ++(int );
Date& operator ++();
Date operator --(int );
Date& operator --();
int operator -(const Date& d);
private :
int _year;
int _month;
int _day;
};
ostream& operator <<(ostream& out, const Date& d);
istream& operator >>(istream& in, Date& d);
实现文件 (Date.cpp) #define _CRT_SECURE_NO_WARNINGS
#include "Date.h"
bool Date::CheckDate () {
if (_month < 1 || _month > 12 || _day < 1 || _day > GetMonthDay (_year, _month)) {
return false ;
}
return true ;
}
Date& Date::operator +=(int day) {
if (day < 0 ) return this ->operator -=(-day);
_day += day;
while (_day > GetMonthDay (_year, _month)) {
_day -= GetMonthDay (_year, _month);
_month++;
if (_month == 13 ) {
_year++;
_month = 1 ;
}
}
return *this ;
}
Date Date::operator +(int day) {
Date tmp = *this ;
tmp += day;
return tmp;
}
Date& Date::operator -=(int day) {
if (day < 0 ) return this ->operator +=(-day);
_day -= day;
while (_day <= 0 ) {
_month--;
if (_month == 0 ) {
_year--;
_month = 12 ;
}
_day += GetMonthDay (_year, _month);
}
return *this ;
}
Date Date::operator -(int day) {
Date tmp = *this ;
tmp -= day;
return tmp;
}
bool Date::operator <(const Date& d) {
if (_year < d._year) return true ;
else if (_year == d._year) {
if (_month < d._month) return true ;
else if (_month == d._month) return _day < d._day;
}
return false ;
}
bool Date::operator <=(const Date& d) { return *this < d || *this == d; }
bool Date::operator >(const Date& d) { return !(*this <= d); }
bool Date::operator >=(const Date& d) { return !(*this < d); }
bool Date::operator ==(const Date& d) { return _year == d._year && _month == d._month && _day == d._day; }
bool Date::operator !=(const Date& d) { return !(*this == d); }
Date Date::operator ++(int ) {
Date tmp = *this ;
*this += 1 ;
return tmp;
}
Date& Date::operator ++() {
*this += 1 ;
return *this ;
}
Date Date::operator --(int ) {
Date tmp = *this ;
*this -= 1 ;
return tmp;
}
Date& Date::operator --() {
*this -= 1 ;
return *this ;
}
int Date::operator -(const Date& d) {
int flag = 1 ;
Date max = *this ;
Date min = d;
if (*this < d) { max = d; min = *this ; flag = -1 ; }
int n = 0 ;
while (min != max) {
++min;
++n;
}
return n * flag;
}
ostream& operator <<(ostream& out, const Date& d) {
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
istream& operator >>(istream& in, Date& d) {
cout << "请输入年月日: >" ;
in >> d._year >> d._month >> d._day;
return in;
}
取地址运算符重载与 Const 成员函数
Const 成员函数 将 const 修饰的成员函数称为 const 成员函数。它修饰的是隐含的 this 指针,表明在该函数内不能修改任何成员变量。
class Date {
public :
void Print () const {
cout << _year << "/" << _month << "/" << _day << endl;
}
};
int main () {
const Date d1 (2025 , 11 , 30 ) ;
d1. Print ();
return 0 ;
}
取地址运算符重载 分为普通地址运算符和 const 取地址运算符重载。一般编译器生成的足够用,除非有特殊场景(如不想让别人获取对象地址)。
class Date {
public :
Date* operator &() {
return this ;
}
const Date* operator &() const {
return this ;
}
private :
int _year, _month, _day;
};
相关免费在线工具 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
JSON美化和格式化 将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online