一、六个默认成员函数
在一个类域中,如果我们不显示写出编译器就会自动生成的六个函数就是默认成员函数,它们维护着类的最基础的作用。包括初始化和清理工作,由构造函数和析构函数完成;拷贝复制,由拷贝构造函数和赋值重载函数完成。以及取地址重载函数,这个函数很少让我们自己去实现,因此不去着重描写。
1.构造函数
构造函数在类中担任着在Stack和Date类中Init函数的作用,就是在对象实例化时初始化对象,因为自动调用的特性,它可以完美的代替Init。
构造函数的特点:
1.)构造函数名字与类名一致。
2.)构造函数无返回值
3.)构造函数可以重载
4.)无参构造函数,全缺省构造函数,不写构造函数编译器自动生成的构造函数,它们都叫做默认构造函数,但是在一个类中只能存在一个,不能同时存在。
5.)对象实例化时可自动调用
6.)当用户在一个类中显示定义了构造函数,编译器则不再自动生成默认构造函数。反之则会生成一个无参的默认构造函数
7.)对于内置类型的成员变量初始化,编译器会不会自动生成默认构造函数是不确定的,主要看编译器的版本。但对于自定义类型的成员变量初始化,编译器会自动调用该类的构造函数,若没有显示定义构造函数,编译器则会报错。
接下来我会用一个Date类来演示构造函数的运用:
1.)显示定义了一个无参默认构造函数,编译器自动调用Date(),我们可以看到成员变量确实被初始化成了1 ,这就是构造函数作用最直接的展示。

2.)多个默认构造函数
我们可以看到,我写了两个默认构造函数,一个无参,一个全缺省,编译器进行了报错,说明默认构造函数在一个类中只能存在一个。但需要传参使用的构造函数可以和默认构造函数同时存在。

3.)以上代码如下:
cpp
#include<iostream>
#include<cstdio>
using namespace std;
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date()//无参默认构造函数
{
_year = 1;
_month = 1;
_day = 1;
}
/*Date(int year = 1, int month = 1, int day = 2)//全缺省默认构造函数
{
_year = year;
_month = month;
_day = day;
}*/
Date(int year, int month, int day)//构造函数
{
_year = year;
_month = month;
_day = day;
}
void print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
};
2.析构函数
析构函数的作用与构造函数的作用相反, 但是他并不是对对象本身进行销毁,例如局部对象存在栈帧,当函数生命周期结束则自动销毁。c++规定,当对象在进行销毁时自动调用析构函数,完成对象中资源的清理释放活动。 与Stack中的destory函数类似。
析构函数特点:
1.)在类名前加上一个~就定义了析构函数
2.)无返回值无参数
3.)一个类只能有一个析构函数,若没有显示定义,编译器就会自动生成默认析构函数
4.)对象生命周期结束时,编译器就会自动调用析构函数
5.)内置类型,自定义类型的默认析构函数的生成与构造函数相同
下面用Stack来演示析构函数的使用:
cpp
typedef int STDataType;
class Stack {
private:
STDataType* _a;
size_t _capacity;
size_t _top;
public:
Stack(int n = 4)
{
_a = (STDataType*)malloc(n * sizeof(STDataType));
if (nullptr == _a)
{
perror("malloc申请空间失败!");
return;
}
_capacity = n;
_top = 0;
}
void push(STDataType x)
{
if (_top == _capacity)
{
size_t newcapacity = _capacity * 2;
STDataType* tmp = (STDataType*)realloc(_a, sizeof(STDataType) * newcapacity);
if (nullptr == tmp)
{
perror("realloc申请空间失败!");
return;
}
_capacity = newcapacity;
_a = tmp;
}
_a[_top++] = x;
}
void pop()
{
if (_top == 0)
{
cout << "栈已空,无法pop!" << endl;
return;
}
_top--;
}
~Stack()
{
cout << "~Stack运行了" << endl;
free(_a);
_a = nullptr;
_capacity = _top = 0;
}
};
以上代码在运行后会输出如下图所示,这表明在对象销毁时,编译器自动调用了我写的析构函数

3.拷贝构造函数
如果一个构造函数的第一个参数是自身类类型的引用,且任何额外的参数都有默认值,则此构造函数是拷贝构造函数。
特点:
- )拷贝构造是构造函数的重载形式。
- )第一个参数必须是引用,传值会引发无穷递归。
- )未显式定义时,编译器会自动生成,完成浅拷贝(一个字节一个字节的拷贝)。若不符合要求则可以自己定义完成深拷贝。
- )用于用已有对象初始化新对象的场景。
- )c++规定,传值传参和传值返回都要调用拷贝构造函数来完成
下面演示拷贝构造函数的使用:
cpp
#include<iostream>
#include<cstdio>
using namespace std;
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
void print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
};
int main()
{
Date d1(2026,4,7);
Date d2(d1);
d1.print();
d2.print();
return 0;
}
上述代码运行生成后就是两个(2026,4,7).
这就完成了对d2的拷贝构造。
而对Stack的拷贝构造就是这样去写:
以下代码运行后就会生成与原栈内容相同,地址不同的新栈。
cpp
Stack(const Stack& st)
{
_a = (STDataType*)malloc(sizeof(STDataType) * st._capacity);
if (_a == nullptr)
{
perror("malloc申请空间失败!");
}
memcpy(_a, st._a, sizeof(STDataType) * st._top);
_capacity = st._capacity;
_top = st._top;
}
4.赋值重载
赋值运算符重载是运算符重载 的一种,用于处理两个已存在对象 之间的赋值操作(对象1 = 对象2)。如果没有显式定义,编译器会自动生成,完成浅拷贝 ,有指针的类必须自己实现深拷贝。
定义格式:
cpp
类名& operator=(const 类名& 引用名)
{
// 实现深拷贝逻辑
return *this;
}
- )赋值运算符重载是成员函数,不能是全局函数。
- )参数必须是 const 引用:避免拷贝,保护原对象不被修改。
- )*必须返回 this :支持连续赋值 ,如
a = b = c。 - )必须先检查自赋值 (
this == &st):防止自己给自己赋值出错。 - )必须先释放当前对象的旧空间:防止内存泄漏。
- )未显式定义时,编译器自动生成浅拷贝,有指针的类会崩溃。
- )用于两个已存在对象之间的赋值操作。
下面以 Date 类演示赋值重载
cpp
#include<iostream>
using namespace std;
class Date
{
private:
int _year;
int _month;
int _day;
public:
// 构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
// 赋值运算符重载
Date& operator=(const Date& d)
{
// 1. 防止自赋值
if (this == &d)
{
return *this;
}
// 2. 赋值成员变量
_year = d._year;
_month = d._month;
_day = d._day;
// 3. 返回自身
return *this;
}
void print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
};
int main()
{
Date d1(2026, 4, 7);
Date d2(2000, 1, 1);
d2 = d1; // 调用赋值重载
d1.print();
d2.print();
return 0;
}
Stack 类的赋值运算符重载(深拷贝)
cpp
Stack& operator=(const Stack& st)
{
// 1. 防止自赋值
if (this == &st)
{
return *this;
}
// 2. 释放旧空间,避免内存泄漏
free(_a);
// 3. 开辟新空间
_a = (STDataType*)malloc(sizeof(STDataType) * st._capacity);
if (_a == nullptr)
{
perror("malloc申请空间失败!");
}
// 4. 拷贝数据
memcpy(_a, st._a, sizeof(STDataType) * st._top);
// 5. 拷贝容量和栈顶
_capacity = st._capacity;
_top = st._top;
// 6. 返回自身
return *this;
}
拷贝构造 vs 赋值重载
- 拷贝构造 :用已存在对象 初始化新对象
Date d2(d1);或Date d2 = d1; - 赋值重载 :两个已存在对象 之间赋值
d2 = d1; - 都需要深拷贝(有指针的类必须手写)
- 都必须用const 引用传参
5.运算符重载
C++ 允许给自定义类 重新定义运算符的行为,让对象可以像内置类型一样使用 + - = == > < 等运算符,本质是一个特殊的函数。它的作用是让自定义类型的代码更直观、更符合使用习惯。
格式:
cpp
// 成员函数格式
返回值 operator运算符(参数)
{
实现功能
}
- )不能创造新运算符,只能重载已有运算符。
)::.*.?:sizeof这 5 个不能重载。- )必须和类 / 枚举类型一起使用。
- )保持原有运算符的逻辑和优先级不变。
- )赋值运算符
=必须重载为成员函 数。
下面用Date类来实现一个运算符重载:
cpp
int GetMonthDay(int year,int month)//获取每月天数
{
assert(month > 0 && month < 13);
static int monthDayArray[13] = { -1,31,28,31,30,31,30,31,31,30,31,30,31 };
if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0))
{
return 29;
}
return monthDayArray[month];
}
Date& operator+=(int day)
{
if (day < 0)
{
return *this += -day;
}
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
++_month;
if (_month == 12)
{
++_year;
_month = 1;
}
}
return *this;
}
以上代码就是通过重载+=来实现日期相加的功能。