从C语言到C++⑤(第二章_类和对象_中篇)(6个默认成员函数+运算符重载+const成员)(中):https://developer.aliyun.com/article/1513647
5.2 赋值运算符重载使用
赋值运算符重载主要有以下四点:
① 参数类型
② 返回值
③ 检查是否给自己复制
④ 返回 *this
#include <iostream> using namespace std; class Date { public: Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } Date& operator=(const Date& d) { if (this != &d) // 防止自己跟自己赋值(这里的&d是取地址) { _year = d._year; _month = d._month; _day = d._day; } return *this; // 返回左操作数d1 } void Print() { cout << _year << "年" << _month << "月" << _day << "日" << endl; } private: int _year; int _month; int _day; }; int main() { Date d1(2023, 5, 3); Date d2(2023, 5, 4); d1 = d2; d1.Print(); d2.Print(); return 0; }
自己给自己赋值是无意义的,这里加 if 语句来判断就是为了防止极端情况下,自己给自己赋值,加上这条判断后就算遇到自己给自己赋值,就会不做处理,直接跳过。
因为出了作用域 *this 还在,所以我们可以使用引用来减少拷贝。(因为传值返回不会直接返回对象,而是会生成一个拷贝的对象,这里减少了两次拷贝构造的调用)
5.3 默认生成的赋值运算符重载
赋值运算符重载是默认成员函数,所以如果一个类没有显式定义赋值运算符重载,编译器默认生成复制重载,跟拷贝构造做的事情完全类似:
① 内置类型成员,会完成字节序值拷贝 —— 浅拷贝。
② 对于自定义类型成员变量,会调用它的 operator= 赋值。
把我们自己写的赋值运算符重载注释掉:
#include <iostream> using namespace std; class Date { public: Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } //Date& operator=(const Date& d) //{ // if (this != &d) // 防止自己跟自己赋值(这里的&d是取地址) // { // _year = d._year; // _month = d._month; // _day = d._day; // } // return *this; // 返回左操作数d1 //} void Print() { cout << _year << "年" << _month << "月" << _day << "日" << endl; } private: int _year; int _month; int _day; }; int main() { Date d1(2023, 5, 3); Date d2(2023, 5, 4); d1 = d2; d1.Print(); d2.Print(); return 0; }
既然编译器会自己默认生成,已经可以完成字节序的值拷贝了,我们还需要自己实现吗?当然像日期这样的类是没有必要的,但有时候还是需要自己实现的。比如下面的情况:
#include<iostream> using namespace std; typedef int DataType; class Stack { public: Stack(int capacity = 10) { _array = (DataType*)malloc(capacity * sizeof(DataType)); if (_array == nullptr) { perror("malloc申请空间失败"); return; } _size = 0; _capacity = capacity; } void Push(const DataType& data) { _array[_size] = data; _size++; } ~Stack() { if (_array) { free(_array); _array = nullptr; _capacity = 0; _size = 0; } } private: DataType* _array; int _size; int _capacity; }; int main() { Stack s1; s1.Push(1); s1.Push(2); s1.Push(3); s1.Push(4); Stack s2; s2 = s1; return 0; }
如果类中未涉及到资源管理,赋值运算符是否实现都可以,一旦涉及到资源管理则必须要实现。
6. const 成员
6.1 const 成员的作用
上面定义的日期类,普通对象对它调用 Print ,是可以调得动的。
#include <iostream> using namespace std; 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: int _year; int _month; int _day; }; int main() { Date d1(2023, 5, 3); const Date d2(2023, 5, 4); d1.Print(); d2.Print(); return 0; }
如果这个对象是 const 的呢?这样编译就报错了:
error C2662: “void Date::Print(void)”: 不能将“this”指针从“const Date”转换为“Date &”
这块报错的原因是什么?这里涉及的问题是 "权限的放大" ,这个知识点在下面这篇博客讲过:
从C语言到C++②(第一章_C++入门_中篇)缺省参数+函数重载+引用_GR C的博客-CSDN博客
此时可以使用 const 修饰类的成员函数来解决这种情况。
6.2 const 修饰类的成员函数
将 const 修饰的类成员函数,我们称之为 const 成员函数。
const 修饰类成员函数,实际修饰的是该成员函数隐含的 this 指针,
表明在该成员函数中不能对类的任何成员进行修改。
这里我们可以在函数后面加 const,保持权限的统一:
#include <iostream> using namespace std; class Date { public: Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } void Print() const //这里语法就是加在这里的,虽然有点奇怪 { //void Print(Date* const this)变成了void Print(const Date* const this) cout << _year << "年" << _month << "月" << _day << "日" << endl; } private: int _year; int _month; int _day; }; int main() { Date d1(2023, 5, 3); const Date d2(2023, 5, 4); d1.Print(); d2.Print(); return 0; }
权限的放大会报错,这里d1是权限的缩小,d2没有改变权限。
使用建议:建议能加上 const 都加上,这样普通对象和 const 对象都可以调用了。但是,如果要修改成员变量的成员函数是不能加的,比如日期类中 += ++ 等等实现。它是要修改的,加不了就算了。
7. 取地址及const取地址操作符重载(两个默认成员函数)
这两个运算符一般不需要重载,因为它是默认成员函数,编译器会自己默认生成。一般用编译器自己生成的就够了,所以这里只是简单演示一下使用:
7.1 取地址及const取地址操作符重载(自己实现)
#include <iostream> using namespace std; class Date { public: Date(int year, int month, int day) { _year = year; _month = month; _day = day; } Date* operator&() { return this; } const Date* operator&() const { return this; } private: int _year; int _month; int _day; }; int main() { Date d1(2023, 5, 3); cout << &d1 << endl;// 取出d1的地址 const Date d2(2023, 5, 4); cout << &d2 << endl;// 取出d2的地址 return 0; }
7.2 取地址及const取地址操作符重载(默认生成)
直接把我们写的注释掉:
#include <iostream> using namespace std; class Date { public: Date(int year, int month, int day) { _year = year; _month = month; _day = day; } //Date* operator&() //{ // return this; //} //const Date* operator&() const //{ // return this; //} private: int _year; int _month; int _day; }; int main() { Date d1(2023, 5, 3); cout << &d1 << endl;// 取出d1的地址 const Date d2(2023, 5, 4); cout << &d2 << endl;// 取出d2的地址 return 0; }
只有特殊情况才需要重载,比如你不想让别人取到你的地址:
可以放到私有,或者自己实现返回空指针:
#include <iostream> using namespace std; class Date { public: Date(int year, int month, int day) { _year = year; _month = month; _day = day; } Date* operator&() { return nullptr; } const Date* operator&() const { return nullptr; } private: int _year; int _month; int _day; }; int main() { Date d1(2023, 5, 3); cout << &d1 << endl;// 取出d1的地址 const Date d2(2023, 5, 4); cout << &d2 << endl;// 取出d2的地址 return 0; }