一、类的默认成员函数
默认成员函数就是用户没有显式实现,编译器会自动生成的成员函数称为默认成员函数。
⼀个类,我们不写的情况下编译器会默认生成以下 6 个默认成员函数,需要注意的是这6个中最重要的是前 4 个,最后两个取地址重载不重要,我们稍微了解⼀下即可。其次就是 C++11 以后还会增加两个默认成员函数,移动构造和移动赋值,这个我们后⾯再讲解。默认成员函数很重要,也比较复杂,我们要从两个方面去学习:
- 第⼀:我们不写时,编译器默认生成的函数行为是什么,是否满足我们的需求。
- 第⼆:编译器默认生成的函数不满⾜我们的需求,我们需要自己实现,那么如何自己实现?

1、构造函数
1.1 定义
构造函数 是一个特殊的成员函数,名字与类名相同 , 创建类类型对象时由编译器自动调用 ,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。
构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象 (我们常使用的局部对象是栈帧创建时,空间就开好了),而是对象实例化时初始化对象。
其特点如下:
- 函数名与类名相同。
- 无返回值。 (返回值啥都不需要给,也不需要写 void,不要纠结,C++ 规定如此)
- 对象实例化时系统会自动调用对应的构造函数。
- 构造函数可以重载。
说明:C++ 把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的原生数据类型,如:int/char/double/指针 等,自定义类型就是我们使用 class/struct 等关键字自己定义的类型。
下面是一个日期类的构造函数:
cpp
class Date
{
public:
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(1,1,1);//自动调用
d1.Print();
return 0;
}

- 构造函数的功能就相当于我们之前书写的初始化函数,但由于其自动调用的特性,大大提升了代码的容错率。
1.2 注意
- 如果类中没有显式定义构造函数,则 C++ 编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
cpp
class Date
{
public:
/*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;
};
- 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。
cpp
class Date
{
public:
Date()//无参
{
_year = 1900;
_month = 1;
_day = 1;
}
Date(int year = 1900, int month = 1, int day = 1)//全缺省
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d;//引起歧义
return 0;
}

当存在多个默认构造函数时,一旦我们对对象进行实例化,编译器不知道调用哪个构造函数,就会引起歧义。
- 编译器生成的默认构造函数只会对自定义类型(类)进行初始化,内置类型(int,double...)不会进行初始化,即调用自定义类型的构造函数。
cpp
class Betty
{
public:
Betty()
{
cout << "Betty" << endl;
}
private:
int _a;
};
class Date
{
public:
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
Betty b;
int _year;
int _month;
int _day;
};
int main()
{
Date d;
d.Print();
return 0;
}

从上述实例观察,编译器自动生成的默认构造函数的确只对自定义类型进行初始化。
特别注意 :C++11 中针对内置类型成员不初始化的缺陷,又进行了优化,即:内置类型成员变量在类中声明时可以给默认值。
cpp
class Date
{
public:
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year = 1;//缺省值
int _month = 1;//缺省值
int _day = 1;//缺省值
};
int main()
{
Date d;
d.Print();
return 0;
}

对于自定义类型,要求调用这个成员变量的默认构造函数初始化。如果这个成员变量,没有默认构造函数,那么就会报错。如下代码所示:
cpp
#include<iostream>
using namespace std;
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;
};
// 两个Stack实现队列
class MyQueue
{
public:
//编译器默认生成 MyQueue 的构造函数调用了Stack的构造,完成了两个成员的初始化
private:
Stack pushst;
Stack popst;
};
int main()
{
MyQueue mq;
return 0;
}
调试可以看到,mq 对象调用了 Stack 的构造函数。



这里我们给 MyQueue 加一个 size 成员。可以看到 size 被初始化为0,但是编译器对于内置类型是不确定的,也就是说这个 size 不同平台的实现的不同。也就是说这里的 size 就是个坑,因为有可能这里被初始化为 0,其他平台又是随机值。

再来看一下,我们把栈的构造改为带参的。编译器报错。

因为带参的构造不是默认构造。这样 MyQueue 初始化时调用栈的默认构造就找不到。就会报错说没有合适的默认构造。

1.3 初始化列表
之前我们实现构造函数时,初始化成员变量主要使用函数体内赋值,构造函数初始化还有⼀种方式,就是初始化列表。
定义:初始化列表作用与构造函数类似,它是在构造函数中以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。
下面我们还是以一个日期类来示范:
cpp
class Date
{
public:
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 d(2024,1,3);
d.Print();
return 0;
}

# 注意:
- 每个成员变量在初始化列表中只能出现一次 ( 初始化只能初始化一次 ) 。
- 类中包含以下成员,必须放在初始化列表位置进行初始化 :引用成员变量,const 成员变量,自定义类型成员(且该类没有默认构造函数时)。因为这些变量都需要在定义时初始化。
cpp
class A
{
public:
A(int a)
:_a(a)
{}
private:
int _a;
};
class B
{
public:
B(int a, int ref)
:_b(a)
, _ref(ref)
, _n(3)
{}
private:
A _b; // 没有默认构造函数
int& _ref; // 引用
const int _n; // const常量
};
- 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。
cpp
class A
{
public:
A(int a = 1)//默认构造
:_a(a)
{
cout << "A(int a = 1)" << endl;
}
private:
int _a;
};
class B
{
public:
B(int a)
:_m(a)
{}
private:
int _m;
A _b;
};
int main()
{
B b(2);
return 0;
}

- 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。
cpp
class A
{
public:
A(int a)
:_a1(a)
, _a2(_a1)
{}
void Print()
{
cout << _a1 << endl;
cout << _a2 << endl;
}
private:
int _a2;
int _a1;
};
int main()
{
A aa(1);
aa.Print();
}//输出??
如果是以初始化列表的顺序,那应该输出 1 和 1 。如果以声明顺序,那应该是 1 与随机值。




那初始化列表和函数体内赋值可以混着用吗?可以,因为有些场景必须混着用。
cpp
Date(int& x,int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
,a(1)
,_ref(x)
,_ptr((int*)malloc(size(int)//成员定义
{
if (_ptr == nullptr)
{
perror(malloc fail!);
}
}
例如有个指针,我们 malloc 以后需要检查是否失败。那就必须在函数体内检查。
2、析构函数
2.1 定义
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。其特点如下:
- 析构函数名是在类名前加上字符 ~ 。
- 无参数无返回值类型。
- 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
- 对象生命周期结束时,C++ 编译系统系统自动调用析构函数。
下面是一个日期类的析构函数:
cpp
class Date
{
public:
//构造函数
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
//析构函数
~Date()
{
_year = _month = _day = 0;
}
private:
int _year;
int _month;
int _day;
};
析构函数就相当于 C 语言中的销毁函数,但由于其自动调用的特性,大大提升了代码的容错率。

析构函数负责对象指向资源的清理,如果对象没有指向资源则不用写析构函数。
2.2 注意
- 如果类中没有显式定义析构函数,则 C++ 编译器会自动生成一个析构函数,一旦用户显式定义编译器将不再生成。
cpp
class Date
{
public:
//构造函数
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
//析构函数
/*~Date()
{
_year = _month = _day = 0;
}*/
//编译器会自动生成一个析构函数
private:
int _year;
int _month;
int _day;
};
- 编译器生成的析构函数对内置类型(int,double...)不会进行处理,对于自定义类型一定会调用其析构函数。
cpp
class Betty
{
public:
~Betty()
{
cout << "~Betty" << endl;
}
private:
int _a;
};
class Date
{
public:
//构造函数
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
//默认生成
private:
Betty b;
int _year;
int _month;
int _day;
};

我们再来看 Stack 实现的 MyQueue:
cpp
class MyQueue
{
public:
//编译器默认⽣成MyQueue的析构函数调⽤了Stack的析构,释放的Stack内部的资源
// 显⽰写析构,也会⾃动调⽤Stack的析构
~MyQueue()
{
cout << "~MyQueue()" << endl;
}
private:
Stack pushst;
Stack popst;
};
例如现在我们显示写了析构,但是没有清理栈的资源。

按理说我们自己写了析构就不会生成默认析构,但是编译器还是会跳转到栈的析构,所以自定义类型不论写不写都会调用他的析构。

- 因为指针类型也属于内置类型,所以默认成员在动态内存开辟内存后,必须显式写成析构函数。不能靠编译器默认生成。
cpp
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 2)
{
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (nullptr == _array)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(DataType data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
~Stack()
{
if (_array!=nullptr)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
int _capacity;
int _size;
};
比如说上述代码,默认生成的析构函数并不会释放其内存,就可能造成内存泄漏。
3、拷贝构造函数
3.1 定义
拷贝构造函数: 只有单个形参,该形参是对本类类型对象的引用 ( 一般常用 const 修饰 ),在用已存在的类类型对象创建新对象时由编译器自动调用。其特点如下:
拷贝构造函数是构造函数的⼀个重载。
C++ 规定自定义类型对象进行拷贝行为必须调用拷贝构造,所以这里自定义类型传值传参和传值返回都会调用拷贝构造完成。
cpp
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024,2,2);
Date d2(d1);//拷贝构造
Date d3 = d1;//拷贝构造
d1.Print();
d2.Print();
d3.Print();
return 0;
}

3.2 注意
- 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
cpp
Date(const Date d) // error
{
_year = d._year;
_month = d._month;
_day = d._day;
}
int main()
{
Date d(2024, 9, 8);
Date d1(d);
return 0;
}


因为 C++ 规定传值传参会调用拷贝构造,所以不用引用就会引发无穷递归。
- 若未显式定义,编译器会生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
cpp
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_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;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024, 2, 2);
Date d2(d1);//拷贝构造
Date d3 = d1;//拷贝构造
d1.Print();
d2.Print();
d3.Print();
return 0;
}

- 因为编译器默认生成的拷贝构造函数是值拷贝,在某些场景下就会出错。比如说以下场景:
cpp
class Stack
{
public:
Stack(int n = 4)
{
_a = (STDataType*)malloc(sizeof(STDataType) * n);
if (nullptr == _a)
{
perror("malloc申请空间失败");
return;
}
_capacity = n;
_top = 0;
}
void Push(STDataType x)
{
if (_top == _capacity)
{
int newcapacity = _capacity * 2;
STDataType* tmp = (STDataType*)realloc(_a, newcapacity *
sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail");
return;
}
_a = tmp;
_capacity = newcapacity;
}
_a[_top++] = x;
}
~Stack()
{
cout << "~Stack()" << endl;
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
STDataType* _a;
size_t _capacity;
size_t _top;
};
int main()
{
Stack s;
s.Push(1);
Stack s1(s);
}

为什么会出现这种情况呢?因为我们没写栈的拷贝构造,而默认生成的拷贝构造只是进行值拷贝,对于 size,capacity 的拷贝并不会出现问题,但是当 s1 的 _array 拷贝给 s2 的 _array 时,就会让 s1 与 s2 的同时指向同一片空间。而我们知道当对象的作用域结束时,会自动调用析构函数,同时对同一片空间析构两次 ,就会报错。而且 s1 修改也会影响 s 。

这里我们加上拷贝构造两个栈就会指向不同的空间。

所以当类中需要资源申请时,都需要手动写拷贝构造。
- 拷贝构造的应用场景有很多,能用引用尽量用引用,减少拷贝,提高程序效率。
- 传值返回会产生⼀个临时对象调用拷贝构造,传值引用返回,返回的是返回对象的别名 ( 引用 ),没有产生拷贝。但是如果返回对象是⼀个当前函数局部域的局部对象,函数结束就销毁了,那么使用引用返回是有问题的,这时的引用相当于⼀个野引用,类似⼀个野指针⼀样。传引用返回可以减少拷贝,但是⼀定要确保返回对象,在当前函数结束后还在,才能用引用返回。
cpp
Stack fun()
{
Stack s;
return s;
}
int main()
{
Stack s;
s = fun();
return 0;
}


所以要注意使用引用返回时注意不要返回局部变量。
4、赋值运算符重载
4.1 运算符重载
4.1.1 定义
C++ 为了增强代码的可读性引入了运算符重载,运算符重载 是具由运算符 operator 定义的一个有特殊函数名的函数,也具有其返回值类型、函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。该函数能让我们自定义类型像内置类型一样使用 + , - , * , / 等运算符。
下面实现了简单判断日期是否相当的运算符重载:
cpp
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{
//...
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
int _year;
int _month;
int _day;
};
bool operator == (const Date&d1,const Date& d2)
{
return d1._year == d2._year
&& d1._month == d2._month
&& d1._day == d2._day;
}
int main()
{
Date d1(2024,1,1);
Date d2(2024, 1, 1);
if (d1 == d2)//也可以显示调用operator==(d1,d2);
{
cout << "日期相等" << endl;
}
else
{
cout << "日期不相等" << endl;
}
return 0;
}
重载运算符函数的参数个数和该运算符作用的运算对象数量⼀样多。⼀元运算符有⼀个参数,⼆元运算符有两个参数,⼆元运算符的左侧运算对象传给第⼀个参数,右侧运算对象传给第⼆个参数。

当然我们也可以将运算符重载声明在类中。如果⼀个重载运算符函数是成员函数,则它的第⼀个运算对象默认传给隐式的 this 指针,因此运算符重载作为成员函数时,参数比运算对象少⼀个。
所以此时成员函数的写法就只用传一个参数。通过 this 指针调用第一个参数
cpp
bool operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
4.1.2 注意
- 不能通过连接其他符号来创建新的操作符:比如operator@重载操作符必须有一个类类型参数
- 重载操作符至少有⼀个类类型参数, 用于内置类型的运算符,其含义不能改变,例如:内置的整型 + ,不 能改变其含义
- 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
.*sizeof? : :: .注意以上5个运算符不能重载。
对于5个不能重载的运算符,可能大家对第一个 .* 不太熟悉,这里就给大家浅浅介绍一下
我们先使用 typedef 声明一个成员函数指针,然后再给函数指针赋值,一般情况,直接把函数名赋值给函数指针就可以,但是 C++ 规定成员函数要加&才能取到函数指针,所以下面编译会报错。

一般函数指针回调只需要(*pf)();,但是成员函数有隐含的 this 指针,所以我们也要隐式传地址,那么就需要通过类对象来隐式传,这个时候就会出现 .* 运算符。如下图:

- 重载 ++ 运算符时,有前置 ++ 和后置 ++,运算符重载函数名都是 operator++,无法很好的区分。因为后置会增加拷贝所以后置做出改变。C++ 规定,后置 ++ 重载时,增加⼀个 int 形参,形参可以不用名字。因为实际上并不接收。只是为了跟前置 ++ 构成函数重载,方便区分。
cpp
Date operator++(int);//后置
Date& operator++();//前置
Date operator--(int);//后置
Date& operator--();//前置
- 重载 << 和 >> 时,需要重载为全局函数,因为重载为成员函数,this 指针默认抢占了第⼀个形参位置,第⼀个形参位置是左侧运算对象,调用时就变成了 对象 << cout,不符合使用习惯和可读性。重载为全局函数把 ostream / istream 放到第⼀个形参位置就可以了,第⼆个形参位置当类类型对象。

需要注意的是全局和类里面都同时存在运算符重载时,优先调用全局的。
- 如果重载为全局函数,会面临访问私有成员变量的问题,如下编译会报错:

那有啥办法呢?
- 成员放公有
- Date 提供 getxxx 函数(在类中实现一个获取成员变量的函数,然后在类外可以调用函数来访问成员变量)
- 友元函数(下文讲解)
- 重载为成员函数
这里建议重载为成员函数,这样更方便。如下图所示:重载运算符 == 为成员函数,在调用时有两种方式可以调用。

4.2 赋值运算符重载
4.2.1 定义
赋值运算符重载是将运算符 = 进行运算符重载。但是它相较于其他运算符重载有着自己独特的特点。
赋值运算符重载是⼀个默认成员函数 ,用于完成两个已经存在的对象直接的拷贝赋值,这里要注意跟拷贝构造区分,拷贝构造用于⼀个对象拷贝初始化给另⼀个要创建的对象。
cpp
int main()
{
Date d1(2024, 7, 5);
Date d2(d1);
Date d3(2024, 7, 6);
//赋值重载
d1 = d3;
// 需要注意这⾥是拷⻉构造,不是赋值重载
// 请牢牢记住赋值重载完成两个已经存在的对象直接的拷⻉赋值
// ⽽拷⻉构造⽤于⼀个对象拷⻉初始化给另⼀个要创建的对象
//拷贝构造
Date d4 = d1;
return 0;
}
赋值运算符重载的特点:
- 参数类型:const T& ,传递引用可以提高传参效率。
- 返回值类型:T& ,返回引用可以提高返回的效率,支持连续赋值。
- 检测是否自己给自己赋值。
- 返回值 * this :要复合连续赋值的值。
cpp
Date& operator=(const Date& d)
{
_year = d.year;
_month = d.month;
_day = d.day;
return *this;
}

4.2.2 注意
- 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
cpp
class Time
{
public:
Time()
{
_hour = 1;
_minute = 1;
_second = 1;
}
Time& operator=(const Time& t)
{
cout << "Time& operator=(const Time& t)" << endl;
if (this != &t)
{
_hour = t._hour;
_minute = t._minute;
_second = t._second;
}
return *this;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
// 基本类型(内置类型)
int _year = 2024;
int _month = 1;
int _day = 1;
// 自定义类型
Time _t;
};
int main()
{
Date d1;
Date d2;
d1 = d2;
return 0;
}

- 因为编译器默认生成默认赋值运算符重载的是值拷贝,在某些场景下就会出错。具体实例参考拷贝构造函数。
- 赋值运算符只能重载成类的成员函数不能重载成全局函数。
cpp
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date& operator=(Date& left, const Date& right) // bingo
{
if (&left != &right)
{
left._year = right._year;
left._month = right._month;
left._day = right._day;
}
return left;
}
int _year;
int _month;
int _day;
};
Date& operator=(Date& left, const Date& right) //error
{
if (&left != &right)
{
left._year = right._year;
left._month = right._month;
left._day = right._day;
}
return left;
}
因为赋值运算符如果不显式实现,编译器会生成一个默认的赋值运算符重载。此时用户再在类外自己实现一个全局的赋值运算符重载,就会和编译器在类中生成的默认赋值运算符重载冲突。
像 Date 这样的类成员变量全是内置类型且没有指向什么资源,编译器自动生成的赋值运算符重载就可以完成需要的拷贝,所以不需要我们显示实现赋值运算符重载。
像 Stack 这样的类,虽然也都是内置类型,但是 _a 指向了资源,编译器自动生成的赋值运算符重载完成的值拷贝/浅拷贝不符合我们的需求,所以需要我们自己实现深拷贝(对指向的资源也进行拷贝)。
像 MyQueue 这样的类型内部主要是自定义类型 Stack 成员,编译器自动生成的赋值运算符重载会调用 Stack 的赋值运算符重载,也不需要我们显示实现 MyQueue 的赋值运算符重载。
这里还有⼀个小技巧,如果⼀个类显示实现了析构并释放资源,那么他就需要显示写赋值运算符重载,否则就不需要。
**5、**取地址运算符重载
5.1 const修饰函数
首先我们得知道一个规则就是:const 修饰的常变量不能赋值给普通变量,因为这样造成 const 权限的放大,但是普通变量可以赋值给 const 修饰的常变量。
所以让我们来看看这段代码:
cpp
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << "Print()" << endl;
cout << "year:" << _year << endl;
cout << "month:" << _month << endl;
cout << "day:" << _day << endl << endl;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
int main()
{
const Date d2(2022, 1, 13);
d2.Print();//error
return 0;
}
这段代码会出错,因为 d2 进行函数传参是将 const Date* 传过去,而函数接受参数的类型为 Date*,这样就会造成权限的放大。为了解决这个问题,就需要使用 const 修饰原函数。
cpp
void Print() const
{
cout << "Print()" << endl;
cout << "year:" << _year << endl;
cout << "month:" << _month << endl;
cout << "day:" << _day << endl << endl;
}
并且与原函数构成重载,可以同时存在,同时非 const 成员也可以调用 const 成员函数,因为权限可以缩小。所以加上 const 可以防止我们的程序不小心篡改(原本不应该修改却不小心修改会报错),同时也让我们的的成员函数传参更宽泛,const 成员也可以调用,所以能加尽加。
5.2 取地址及const取地址运算符重载
取地址运算符重载分为普通取地址运算符重载和 const 取地址运算符重载,⼀般这两个函数编译器自动生成的就可以够我们⽤了,不需要去显示实现。除非⼀些很特殊的场景,比如我们不想让别⼈取到当前类对象的地址,就可以自己实现⼀份,胡乱返回⼀个地址。
cpp
class Date
{
public:
Date* operator&()
{
return this;
// return nullptr;
}
const Date* operator&()const
{
return this;
// return nullptr;
}
private:
int _year; // 年
int _month; // ⽉
int _day; // ⽇
};
一般这两个成员函数都不需要我们显示的写,因为他们两个是默认成员函数。
