【C++】掌握C++类的六个默认成员函数:实现高效内存管理与对象操作(二)https://developer.aliyun.com/article/1617297
六、 赋值运算重载
赋值运算符重载格式:
- 参数类型:
const typename &
传递引用可以提高传参效率- 返回值类型:
typename&
返回引用可以减少拷贝,提高返回的效率,有返回值目的是为了支持连续赋值- 检查是否存在自己给自己赋值
- **返回*this:**要复合连续赋值的含义
6.1 判断拷贝构造函数与赋值运算重载
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 operator=(const Date& d) { _year = d._year; _month = d._month; _day = d._day; } private: int _year; int _month; int _day; }; int main() { Date d1(2024, 1, 23); Date d2 = (2024, 2, 28); d2 = d1; Date d3 = d1; return 0; }
无论是赋值运算符重载还是拷贝构造函数都可以完成对内置类型处理,那么如何识别属于赋值还是拷贝呢?那么d2 = d1
是赋值还是拷贝,Date d3 = d1;
赋值还是拷贝?
判断方法:
- 拷贝构造:同类型定义对象需要初始化要创建对象
- 赋值运算符重载:已经存在的对象,一个拷贝赋值给另一个
6.2 关于连续赋值
int main() { Date d1(2024, 1, 23); Date d2(2024, 2, 28); Date d3; int i; int j = 10; i = j = 20; d3 = d2 = d1; return 0; }
- 内置类型:连续赋值是从右到左,这里先处理
j=20
表达式,返回临时变量存储返回值,再跟i赋值。 - 自定义类型:连续赋值,先处理
d2=d1
这里就会调用赋值运算符重载。连续赋值返回值需要改为Date,并且返回对象*this
Date operator=(const Date& d) { _year = d._year; _month = d._month; _day = d._day; return *this; }
这种写法不推荐。由于返回值传值返回先存储到寄存器中,传值不会返回对象本身,而是会返回他的拷贝。如果是同类,就需要调用拷贝构造。无论如何会导致浪费,不如使用引用做返回值,减少拷贝次数。这也是指针跟引用差异。
Date& operator=(const Date& d) { _year = d._year; _month = d._month; _day = d._day; return *this; }
6.3 自己给自己赋值
可能会存在一种情况,自己给自己赋值(一般人都不这么干,主要是白干),对此一般会加给判断语句
Date& operator=(const Date& d) { if(this!=&d) { _year=d._year; _month=d._month; _day=d._day; } return *this; }
6.4 赋值运算符重载不能重载为全局函数
赋值运算符重载跟拷贝构造类似,如果不显式实现,编译器会生成一个默认的赋值运算符重载,此时用户再类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突,故而赋值运算符只能是类的成员函数(其他运算符函数可以重载为全局函数)
- 特性:用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝
默认生成赋值运算符重载对于内置类型与自定义类型处理方式
- 内置类型成员变量直接赋值的
- 自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值
6.5 赋值运算符中深拷贝
既然编译器生成的默认赋值运算符重载已经可以完成字节序的值拷贝,还需要自己实现吗?
接下来如果继续使用浅拷贝程序就会崩溃掉,就需要使用深拷贝解决。因为这里两个对象调用同一个函数,对同一块空间进行free,重复free会报错
// 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。 typedef int DataType; class Stack { public: Stack(size_t capacity = 10) { _array = (DataType*)malloc(capacity * sizeof(DataType)); if (nullptr == _array) { perror("malloc申请空间失败"); return; } _size = 0; _capacity = capacity; } void Push(const DataType& data) { // CheckCapacity(); _array[_size] = data; _size++; } ~Stack() { if (_array) { free(_array); _array = nullptr; _capacity = 0; _size = 0; } } private: DataType *_array; size_t _size; size_t _capacity; }; int main() { Stack s1; s1.Push(1); s1.Push(2); s1.Push(3); s1.Push(4); Stack s2; s2 = s1; return 0; }
小结:
- 如果类中未涉及到资源管理,赋值运算符是否实现都是可以的
- 如果类中涉及到资源管理,赋值运算符则必须实现
七、前置++与后置++运算符重载
前置++和后置++ 都这样子写,编译器是无法区分的。对此需要进行特殊处理(解决语法逻辑不自洽)。虽然++operator可以解决问题,但是C++给出其他解决方式
//++d1 Date operator++(); //d1++ Date operator++();
C++规定后置++重载时,多增加一个int类型的参数(用来完成重载,没有实际意义),但是调用函数时该参数不用传参,编译器会自动传递。
//前置++ Date& operator++() { _day += 1; return *this; } //后置++ Date operator++(int) { Date temp(*this); _day += 1; return temp; }
八、const成员函数
将const
修饰的"成员函数"称之为const
成员函数,const
修饰类成员函数,实际修饰改成员隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改
问题:
cosnt
对象可以调用非const
成员函数吗?非const
对象可以调用const
成员函数吗?const
成员函数内可以调用其他的非const
成员函数吗?非const
成员函数内可以调用其他的const
成员函数吗?
接下来,带着我们的问题进行下去
int main() { Date d1(2024, 1, 31); d1.Print(); const Date d2(2024, 3, 31); d2.Print(); return 0; }
这里原因很显然是d2
的权限被放大了(权限可以缩小,但是不能放大)
既然const
修饰类成员函数,实际修饰改成员隐含的this指针。那么只要const
修饰this指针就可以解决权限问题。但是对于我们需要修饰this指向的内容,但是规定在this形参和实参位置不允许显式,也是不能修改的,那么怎么修改呢?
8.1 函数定义添加const修饰
祖师爷给出解决措施,在函数定义地方加个const
。void fname() const
,至于为什么不是 const void fname()
还是那一句,我们是语法的学习者。这样处理完了之后,对于const
对象和非const
对象都可以调用该函数
8.2 const修饰全部函数可行?
- 如果对成员变量只进行读访问的函数建议加
const
,这样const
对象和非const
对象都可以使用; - 如果对成员变量进行读写访问的函数,不能加上
const
,否则不能修改成员变量(需要修改读写权限)
补充:全局实现的运算符重载函数不存在this指针,而const
修饰成员函数是修饰this指针的。那么流插入与流提取不是在类中实现,没有隐含的this指针,不能使用const
修饰。
对于上面的几个问题的答案:
cosnt
对象可以调用非const
成员函数吗?(不可以,权限放大)非const
对象可以调用const
成员函数吗?(可以,权限缩小)const
成员函数内可以调用其他的非const
成员函数吗?(不可以,权限放大)非const
成员函数内可以调用其他的const
成员函数吗?(可以,权限缩小)
九、&取地址及const取地址操作符重载
这两个默认成员函数一般不用重新定义,编译器会默认生成的
class Date { public: Date* operator&() { return this; } const Date* operator&() const { return this; } private: int _year; // 年 int _month; // 月 int _day; // 日 }; int main() { int a = 0; const int b = 10; cout << &a << endl; cout << &b << endl; return 0; }
那么对于返回地址具有选择性,可以指定返回空或者返回一个像模像样的地址(传了假地址都很难发现,写bug小妙招~)
class Date { public: Date* operator&() { return null; } const Date* operator&() const { return (const Date*)0xeeffee; } private: int _year; // 年 int _month; // 月 int _day; // 日 };
以上就是本篇文章的所有内容,在此感谢大家的观看!这里是店小二呀C++笔记,希望对你在学习C++语言旅途中有所帮助!