文章目录
1、前言
本章介绍类与对象中的默认成员函数。当一个类中没有任何成员时,我们称这个类为空类,而在空类中并不是真正的空,编译器会自动生成以下几个默认成员函数。
默认成员函数:用户不显示实现,编译器自动生成的成员函数(我们不写编译器自己生成,我们写编译器不生成)。
默认成员函数有:
1、【初始化和清理】:构造函数和析构函数。
2、【拷贝复制】:拷贝构造和赋值运算符重载函数
3、【取地址重载】:普通对象和const对象取地址
2、构造函数
在C语言实现一些数据结构的时候,我们可能老是忘记初始化数据或者销毁数据,或者是每次对初始化和销毁都需要留心感到很麻烦。
实现C++的大佬也可能有这样的顾虑,所以就设计了默认成员函数,使得在创建对象的同时编译器就自动调用了默认成员函数。
2.1 构造函数的特性
功能:构造函数在对象创建的同时,编译器通过调用构造函数完成对对象的初始化。
特征:
- 构造函数的函数名和类名要一样。
- 构造函数没有返回值
- 对象实例化后,编译器自动调用构造函数。
- 构造函数可以重载。
看代码:
class Test
{
public:
//无参构造函数
Test()
{
_e1 = 1;
_e2 = 2;
_e3 = 3;
}
//带参构造函数
Test(int e1, int e2, int e3)
{
_e1 = e1;
_e2 = e2;
_e3 = e3;
}
private:
int _e1;
int _e2;
int _e3;
};
int main()
{
Test t1; // 调用无参构造函数
Test t2(1, 2, 3); // 调用带参构造函数
//Test t3(); //错误写法 会让编译器误以为是一个函数声明
return 0;
}
由于构造函数是支持重载的,在上面代码中有一个无参构造函数和一个有参构造函数,我们可以给上面两个函数简化一下,毕竟我们学过缺省函数嘛。
这样写更能适应多个场景,同时也支持两种调用方式
class Test
{
public:
Test(int e1 = 1, int e2 = 2, int e3 = 3)
{
_e1 = e1;
_e2 = e2;
_e3 = e3;
}
private:
int _e1;
int _e2;
int _e3;
};
int main()
{
Test t1; // 默认1,2,3
Test t2(2,3,4); //2,3,4
return 0;
}
2.2 编译器对构造函数的处理
当然对于构造函数,如果我们写了,编译器就不会生成了,下面看看如果我们不写呢?
先看一段代码:
class Test
{
public:
void func1()
{
cout << _e1 << endl;
cout << _e2 << endl;
cout << _e3 << endl;
}
private:
int _e1;
int _e2;
int _e3;
};
int main()
{
Test t1;
t1.func1();
return 0;
}
再看以下代码
class Temp
{
public:
Temp()
{
cout << "Time()" << endl;
_t1 = 0;
_t2 = 0;
_t3 = 0;
}
private:
int _t1;
int _t2;
int _t3;
};
class Test
{
public:
void func()
{
cout << _e1 << endl;
cout << _e2 << endl;
cout << _e3 << endl;
}
private:
// 基本类型(内置类型)
int _e1;
int _e2;
int _e3;
// 自定义类型
Temp _t;
};
int main()
{
Test d;
d.func();
return 0;
}
结论是:
当我们没有在类中写构造函数的时候,编译器会自动生成构造函数,自动生成的构造函数对内置类型不做处理,但会对自定义类型做处理,调用自定义类型的构造函数。
如果我们想要对内置类型做处理怎么办?
在C++11中打了个补丁,可以对成员变量声明时给个缺省值(不占物理空间,只是标识)。
class Temp
{
public:
Temp()
{
cout << "Time()" << endl;
_t1 = 0;
_t2 = 0;
_t3 = 0;
}
private:
int _t1;
int _t2;
int _t3;
};
class Test
{
public:
void func()
{
cout << _e1 << endl;
cout << _e2 << endl;
cout << _e3 << endl;
}
private:
// 基本类型(内置类型)
int _e1 = 1;
int _e2 = 2;
int _e3 = 3;
// 自定义类型
Temp _t;
};
int main()
{
Test d;
d.func();
return 0;
}
应用举例:
用栈实现队列
队列的构造函数就不用写了。
class Stack
{
public:
Stack()
{
_a = NULL;
_capacity = _top = 0;
}
private:
int* _a;
int _capacity;
int _top;
};
class Queue
{
public:
void push(size_t x)
{}
private:
Stack _PushSt;
Stack _PopSt;
};
int main()
{
Queue q1;
return 0;
}
总结:
如果默认生成的能满足你的需求,那就不用写。
2.3 默认构造函数
让我们再跳回这个代码,如果只留带参构造函数,这时编译器不会自己生成构造函数了,如果我们调用的时候不传参,这个时候就会出现问题了。
class Test
{
public:
//带参构造函数
Test(int e1, int e2, int e3)
{
_e1 = e1;
_e2 = e2;
_e3 = e3;
}
private:
int _e1;
int _e2;
int _e3;
};
int main()
{
Test t1(1, 2, 3); // 正常运行
//Test t2; //编译错误
return 0;
}
这里说的默认构造函数是什么?
默认构造函数分为这几种:
- 无参的构造函数。
- 全缺省的构造函数。
- 编译器生成的构造函数。
所以创建对象没有默认传参的话,一定得有默认构造函数。
3、析构函数
3.1 析构函数的特征
前面学习的构造函数是用来初始化对象的,而析构函数是用来清除对象所占资源(不是销毁对象)的函数。
特征:
- 析构函数名是在类名前加个~。
- 无参数无返回值,所以不能重载
- 一个类只能有一个析构函数,如果写了编译器就不生成,不然编译器会生成
- 析构函数在对象生命周期结束时自动调用。
看代码了解特性:
class Temp
{
public:
Temp()
{
cout << "Time()" << endl;
_t1 = 0;
_t2 = 0;
_t3 = 0;
}
~Temp()
{
cout << "~Temp()" << endl;
}
private:
int _t1;
int _t2;
int _t3;
};
class Test
{
public:
void func()
{
cout << _e1 << endl;
cout << _e2 << endl;
cout << _e3 << endl;
}
~Test()
{
cout << "~Test()" << endl;
}
private:
// 基本类型(内置类型)
int _e1 = 1;
int _e2 = 2;
int _e3 = 3;
// 自定义类型
Temp _t;
};
int main()
{
Test d;
d.func();
return 0;
}
对象d销毁的时候,清除Test类的资源,所以先调用~Test(),在清除到Temp类型数据的时候再进入Temp类调用 ~Temp(),最后完成整个对象d的资源清除。
但实际上对于栈区里的数据,编译器在程序结束也就自动销毁了,所以这里的析构函数可写可不写。
3.2 编译器对析构函数的处理
在上面我们了解了通过自己写的析构函数程序是怎么运行的。
那么接下来让我们看看编译器生成的析构函数。
下面是一个用栈实现队列的部分:
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 4)
{
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (NULL == _array)
{
perror("malloc fail");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(DataType data)
{
_array[_size] = data;
_size++;
}
~Stack()
{
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
int _capacity;
int _size;
};
class Queue
{
public:
void Push(DataType x)
{
_Pushst.Push(x);
}
private:
Stack _Pushst;
Stack _Popst;
};
int main()
{
Queue q;
q.Push(1);
q.Push(2);
return 0;
}
初始化过程:
在创建对象q的时候,调用编译器生成的类Queue构造函数,在这个构造函数中再调用类Stack的构造函数最后完成了q的初始化。
释放资源过程:
在对象q的生命周期结束时,调用编译器生成的类Queue析构函数,在这个析构函数中再调用类Stack的析构函数完成了q的资源释放。
总结:
当只有栈中的数据需要消除时,析构函数可写可不写,当有资源(如动态内存、关闭文件)需要释放的时候编译器的生成只是一个"连接",最终总有一个类中是需要自己写析构函数的。
有资源释放的需求要写,默认可以处理的不写。
4、拷贝构造函数
拷贝构造函数是一个特殊的构造函数,用已有的对象创建新的对象时,编译器自动调用对新的对象进行初始化。
4.1 拷贝构造函数的特征
拷贝构造函数特征:
- 函数名和类名一样,是构造函数的重载形式
- 没有返回值,参数只有一个且必须是类类型已有的对象的引用。
- 用类类型对象创建新对象时,编译器自动调用。
看代码看特征:
class Test
{
public:
Test(int e1 = 1, int e2 = 2, int e3 = 3)
{
_e1 = e1;
_e2 = e2;
_e3 = e3;
}
//其实注释了这个函数也是一样的结果
//加const 防止d的值被更改
Test(const Test& d)
{
cout << "Test(const Test& d)" << endl;
_e1 = d._e1;
_e2 = d._e2;
_e3 = d._e3;
}
void func()
{
cout << _e1 << endl;
cout << _e2 << endl;
cout << _e3 << endl;
}
private:
int _e1;
int _e2;
int _e3;
};
int main()
{
Test d1(2, 3, 4);
Test d2(d1);
d2.func();
return 0;
}
Test d1(2, 3, 4);
构造 初始化
Test d2(d1);
构造 给d2初始化
4.2 拷贝构造函数的引用参数
为什么拷贝构造函数的参数必须用引用?
先分析一下这段代码
class Test
{
public:
Test(int e1 = 1, int e2 = 2, int e3 = 3)
{
cout << "Test()" << endl;
}
//其实注释了这个函数也是一样的结果
//加const 防止d的值被更改
Test(const Test& d)
{
cout << "Test(const Test& d)" << endl;
}
private:
int _e1;
int _e2;
int _e3;
};
void func1(const Test d)
{
cout << "func1(const Test d)" << endl;
}
void func2(const Test& d)
{
cout << "func1(const Test& d)" << endl;
}
int main()
{
Test d1;
func1(d1);
func2(d1);
return 0;
}
运行结果:
实际步骤:
- 创建d1对象,调用构造函数。
- 调用func1传值,传值先对实参d1拷贝,第一次调用拷贝构造函数后将形参传给func1,打印。
- 引用传参不需要拷贝,所以直接打印。
结论:
对象传值,需要调用拷贝构造,所以如果拷贝构造函数传值,那么就会反复调用自己,最后造成死循环。
4.3 编译器对拷贝构造函数的处理
如果未显示定义拷贝构造函数,编译器会生成默认的拷贝构造。
对于内置类型
还是这段代码
class Test
{
public:
Test(int e1 = 1, int e2 = 2, int e3 = 3)
{
_e1 = e1;
_e2 = e2;
_e3 = e3;
}
//其实注释了这个函数也是一样的结果
//加const 防止d的值被更改
//Test(const Test& d)
//{
//cout << "Test(const Test& d)" << endl;
//_e1 = d._e1;
//_e2 = d._e2;
//_e3 = d._e3;
//}
void func()
{
cout << _e1 << endl;
cout << _e2 << endl;
cout << _e3 << endl;
}
private:
int _e1;
int _e2;
int _e3;
};
int main()
{
Test d1(2, 3, 4);
Test d2(d1);
d2.func();
return 0;
}
对于一些内置类型就算没有写拷贝构造函数,结果还是一样的,因为编译器生成的拷贝构造函数完成了值拷贝。
但有一些内置类型,如果靠编译器生成的拷贝构造就会出问题。
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 4)
{
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (NULL == _array)
{
perror("malloc fail");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(DataType data)
{
_array[_size] = data;
_size++;
}
~Stack()
{
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
int _capacity;
int _size;
};
int main()
{
Stack st;
st.Push(1);
st.Push(2);
Stack st1(st);
return 0;
}
程序运行结果:程序崩溃
默认拷贝构造是浅拷贝,属于值拷贝,两个对象的指针都指向一个空间。
当浅拷贝两个动态空间的指针值时,就会发生错误,因为最后会对一个空间析构两次,程序也就崩溃了。
深拷贝通过将建立新的空间,拷贝相同的数据放入新空间,就能实现有效拷贝。
//深拷贝的拷贝构造函数写法
Stack(const Stack& st)
{
_array = (int*)malloc(sizeof(int) * st._capacity);
if (_array == nullptr)
{
perror("malloc fail");
exit(-1);
}
memcpy(_array, st._array, sizeof(int) * st._size);
_size = st._size;
_capacity = st._capacity;
}
结论:
需要写析构函数的类,需要拷贝构造函数。
不需要写析构函数的类,默认拷贝构造函数也够用。
对于自定义类型:
#include <iostream>
using namespace std;
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 4)
{
cout << "stack构造" << endl;
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (NULL == _array)
{
perror("malloc fail");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(DataType data)
{
_array[_size] = data;
_size++;
}
Stack(const Stack& st)
{
cout << "stack拷贝构造" << endl;
_array = (int*)malloc(sizeof(int) * st._capacity);
if (_array == nullptr)
{
perror("malloc fail");
exit(-1);
}
memcpy(_array, st._array, sizeof(int) * st._size);
_size = st._size;
_capacity = st._capacity;
}
~Stack()
{
cout << "stack析构" << endl;
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
int _capacity;
int _size;
};
class Queue
{
public:
void Push(DataType x)
{
_Pushst.Push(x);
}
private:
Stack _Pushst;
Stack _Popst;
};
int main()
{
Queue q;
q.Push(1);
q.Push(2);
Queue q1(q);
return 0;
}
Queue类可以不写拷贝构造函数,对于自定义类型编译器自动生成的拷贝构造函数会自动调用Stack类的拷贝构造函数
5、运算符重载和赋值重载
5.1 运算符重载
为什么要对运算符进行重载?
如果有一个日期的对象,我们需要计算距今100天后的具体日期,简单内置的+肯定是不能完成这个工作的,对于类似情况,就需要我们对运算符进行重载。
5.1.1 运算符重载的使用
运算符重载是一个特殊的函数
函数名:operator后面接需要重载的运算符
原型:返回值 operator操作符(参数)
如:
bool Date::operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
运算符重载很注重代码的可读性,所以调用的形态很易懂。
如:
class Date
{
public:
//构造
Date(int year = 2022, int month = 10, int day = 9)
{
_year = year;
_month = month;
_day = day;
}
//拷贝构造
Date(const Date& d)
{
cout << "Date(const Date& d)" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
//析构
~Date()
{
cout << "~Date()" << endl;
}
//打印
void Print()
{
cout << _year << " " << _month << " " << _day << endl;
}
//==重载函数 写类里的方式
//bool operator==(const Date& d)
//{
// return _year == d._year
// && _month == d._month
// && _day == d._day;
//}
//==重载函数声明
bool operator==(const Date& d);
private:
int _year;
int _month;
int _day;
};
//==重载函数 写类外的方式
bool Date::operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
int main()
{
Date d1(2022, 10, 8);
d1.Print();
Date d2(d1);
//写法和平时一样 但实际d1 == d2 是 d1.operator==(d2);
cout << (d1 == d2) << endl;
return 0;
}
让我们通过==重载函数的实现看一些东西
bool Date::operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
- 为什么参数只有一个d,不是
(const Date& d1, const Date& d2)
,这是因为在参数中有一个隐藏的this指针。 - &的使用相较传值可以避免d的拷贝构造,从而提高效率,相较传指针可读性更强。
- const避免d的值被改变,并且使得函数调用适合多种情况(可以传Date类型,也可以传const Date类型),因为权限可以缩小不能扩大。
注意:
- 不能通过连接其它符号来创建新的操作符,比如operator@。
- 重载操作符必须有一个类类型参数,不然就失去了重载的意义。
- 运算符重载后不能改变运算符本该有的含义。
- sizeof 、? : 、:: 、. 、*. 这5个操作符不能进行重载。
5.1.2 实现多种运算符重载
在实现一定的操作符重载函数后,对于实现一些其它的操作符重载函数我们可以复用
比如:
bool Date::operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
bool Date::operator>(const Date& d)
{
if (_year > d._year)
{
return true;
}
else if (_year == d._year && _month > d._month)
{
return true;
}
else if (_year == d._year && _month == d._month && _day > d._day)
{
return true;
}
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 !(*this == d);
}
以下函数都定义在类外,所以需要添加类作用域限定符。
int Date::GetMonthDay(int year, int month)
{
int MonthArr[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if (year % 4 == 0 && year % 400 != 0)
{
MonthArr[2] = 29;
}
return MonthArr[month];
}
Date& Date::operator+=(int day)
{
if (day < 0)
{
return *this -= abs(day);
}
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day = _day - GetMonthDay(_year, _month);
_month++;
if (_month > 12)
{
_year++;
_month = 1;
}
}
return *this;
}
Date Date::operator+(int day)
{
Date tmp(*this);
tmp += day;
return tmp;
}
前置++和后置++的实现与区别
前置++,直接返回+1后的结果。
后置++,拷贝一个临时变量,对象+1,再返回临时变量。
//后置++
//C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器
//自动传递
Date Date::operator++(int)
{
Date tmp(*this);
*this += 1;
return tmp;
}
//前置++
Date& Date::operator++()
{
*this += 1;
return *this;
}
日期相减的重载
int Date::operator-(const Date& d)
{
Date max = *this;
Date min = d;
int flag = 1;
if (max < min)
{
max = d;
min = *this;
flag = -1;
}
int n = 0;
while (max != min)
{
++min;
++n;
}
return n*flag;
}
5.2 赋值运算符重载
当我们想完成一个d1 = d2 的赋值重载
Date d1(2022, 10, 8);
d1.Print();
Date d2;
d2 = d1;
赋值运算符重载也有自己的格式
- 参数类型:const 类类型& 提高效率
- 返回值类型:类类型& 提高效率,并且使得赋值能够连续
- 考虑自己给自己赋值的情况
- 返回*this使得符合赋值连续
5.2.1 赋值运算符的赋值连续
下面我们通过代码理解它的格式
这里重点是理解连续赋值是怎么实现的
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 2022, int month = 10, int day = 9)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
cout << "Date(const Date& d)" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
~Date()
{
cout << "~Date()" << endl;
}
void Print()
{
cout << _year << " " << _month << " " << _day << endl;
}
Date& operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2022, 10, 8);
d1.Print();
Date d2;
Date d3(2022, 10, 9);
d2 = d3 = d1; //先右到左 (d3 = d1)的返回值和d2比 等价 d2 = (d3 = d1);
return 0;
}
因为需要返回值和下一个对象进行比较,所以需要返回*this。
为什么不能返回d呢?
Date& operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
依然是权限的问题,d返回类型变成了Date&,权限扩大,如果要返回d返回类型需要改为const Date&,但这可能不是我们需要得到的类型。
5.2.2 赋值和拷贝的区别
赋值需要在对象都存在时才能调用。
Date d1(2022, 10, 8);
Date d2;
d2 = d1;
拷贝是用一个对象创建一个新的对象
Date d1(2022, 10, 8);
Date d2(d1);
那么这样写呢?
Date d1(2022, 10, 8);
Date d2=d1;
本质是创建一个新的对象,再对其初始化,所以是拷贝。
5.2.3 赋值运算符重载不能写在类外
首先,如果我们在类中不自己实现一个赋值运算符重载,在调用时,编译器会自动生成并且以字节序进行值拷贝,所以如果写在类外就会就会与编译器生成的冲突。
比如这种情况我们不写赋值重载
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 2022, int month = 10, int day = 9)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
cout << "Date(const Date& d)" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2022, 10, 8);
Date d2;
Date d3(2022, 10, 9);
d2 = d3 = d1; //先右到左 (d3 = d1)的返回值和d2比 等价 d2 = (d3 = d1);
return 0;
}
就算没有写赋值重载,编译器生成的也能实现。
但对于这种情况,编译器自动生成的就会出问题
class stack
{
public:
//构造
stack(int capacity = 4)
{
cout << "stack(int capacity = 4)" << endl;
_a = (int*)malloc(sizeof(int*) * capacity);
if (_a == nullptr)
{
perror("malloc fail");
exit(-1);
}
_capacity = capacity;
_top = 0;
}
//拷贝构造
stack(const stack& st)
{
cout << "stack(const stack& st)" << endl;
_a = (int*)malloc(sizeof(int*) * st._capacity);
if (_a == nullptr)
{
perror("malloc fail");
exit(-1);
}
memcpy(_a, st._a, sizeof(int) * st._top);
_capacity = st._capacity;
_top = st._top;
}
//入栈
void Push(int x)
{
//扩容
_a[_top++] = x;
}
//析构
~stack()
{
free(_a);
_a = nullptr;
_capacity = _top = 0;
}
private:
int* _a;
int _capacity;
int _top;
};
int main()
{
stack st1(100);
st1.Push(1);
st1.Push(2);
stack st2(st1);
st2.Push(10);
st2.Push(20);
stack st3(20);
st3.Push(100);
st3.Push(200);
st1 = st2 = st3;
}
结果就是程序崩溃,对一个空间多次析构,还是值拷贝的问题,所以我们需要自己实现赋值重载。
大致和拷贝构造函数一样,值得注意的一个细节就是考虑自己赋值给自己的情况。
stack& operator=(const stack& st)
{
//自己等于自己
if (this != &st)
{
free(_a);
_a = (int*)malloc(sizeof(int*) * st._capacity);
if (_a == nullptr)
{
perror("malloc fail");
exit(-1);
}
memcpy(_a, st._a, sizeof(int) * st._top);
_capacity = st._capacity;
_top = st._top;
}
return *this;
}
6、const成员和取地址重载
被const修饰的对象,表示不能对该对象进行任何修改,只能拥有可读的权限,所以const对象的调用是我们需要讨论的问题。
6.1 const成员
当我们用一个const对象调用一个函数时,由于编译器生成的this指针默认类型是 类* 类型,当我们传const 类* 类型,就会发生权限扩大,这是禁止的。
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 2022, int month = 10, int day = 9, int tmp = 4)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
cout << "Date(const Date& d)" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
~Date()
{
cout << "~Date()" << endl;
}
void Print()
{
cout << _year << " " << _month << " " << _day << endl;
}
private:
int _year;
int _month;
int _day;
const int _tmp;
};
int main()
{
const Date d1;
d1.Print();
return 0;
}
而解决这个问题只需要在Print函数右边加上const,编译器会自动将this指针调整成const Date* 类型。
void Print() const
{
cout << _year << " " << _month << " " << _day << endl;
}
因为函数加const后,带上const的对象和不加const的对象都能正常调用,所以如果函数没有修改对象的操作,那么最好是加上const。
6.2 取地址重载
还是实现日期类的取地址重载,注意对加const的对象要有特别处理。
//一般不用自己定义 除非要让别人得到特定内容
// 如:
// 要求这个类的对象不让取地址
Date* operator&()
{
/*return nullptr;*/
return this;
}
const Date* operator&() const
{
/*return nullptr;*/
return this;
}
但其实对于取地址重载编译器生成的都能用,我们一般不用写,除非是想让调用的人看不到,这时候可以在重载中返回nullptr。
本章完~