文章目录
- [1. 拷贝构造函数和赋值运算符重载](#1. 拷贝构造函数和赋值运算符重载)
-
- [1.1 拷贝构造函数](#1.1 拷贝构造函数)
-
- [1.1.1 拷贝构造函数的特性](#1.1.1 拷贝构造函数的特性)
- [1.1.2 显示实现拷贝构造函数的条件](#1.1.2 显示实现拷贝构造函数的条件)
- [1.2 赋值运算符重载](#1.2 赋值运算符重载)
-
- [1.2.1 赋值运算符的特性](#1.2.1 赋值运算符的特性)
- [1.2.2 显示实现赋值运算符的条件](#1.2.2 显示实现赋值运算符的条件)
- [2. 运算符重载](#2. 运算符重载)
-
- [2.1 前言](#2.1 前言)
- [2.2 运算符重载的本质](#2.2 运算符重载的本质)
- [2.3 运算符重载的特性](#2.3 运算符重载的特性)
- [3. const成员函数](#3. const成员函数)
- [4. 取地址运算符重载](#4. 取地址运算符重载)
1. 拷贝构造函数和赋值运算符重载
1.1 拷贝构造函数
拷贝构造函数本质上是拷贝一个对象,并以该对象来构造一个新的对象。
1.1.1 拷贝构造函数的特性
- 拷贝构造函数是构造函数的重载函数。并且拷贝构造函数的第一个参数必须是类类型的参数,因为如果不是类类型参数,则会因为语法错误而引发无穷递归调用。可以有多个参数,但是后面的参数必须给上缺省值。
cpp
Date(const Date& d)
{}
- 规定自定义类型对象进行拷贝构造行为必须调用拷贝构造,所以这里自定义类型传值传参和传值返回都会调用拷贝构造完成。
- 若未显式定义拷贝构造,编译器会自动生成拷贝构造函数。自动生成的拷贝构造对内置类型成员变量会完成值拷贝/浅拷贝(一个字节一个字节的拷贝)。
- 传值返回会调用拷贝构造;传值引用返回,返回的是返回对象别名,没有调用拷贝构造,可以提高效率。但是如果引用的是一个临时变量,函数结束栈帧销毁,返回的有可能是一个野指针。所以需要根据实际情况来判断是否需要使用传值引用返回。
1.1.2 显示实现拷贝构造函数的条件
一般情况下,编译器默认实现的默认拷贝构造函数就能满足基本的需求。那么,什么时候需要显示实现拷贝构造函数呢?
- 像Stack这样的类,虽然也都是内置类型,但是_a指向了资源,编译器自动生成的默认拷贝构造函数完成的值拷贝/浅拷贝不符合我们的需求,所以需要我们自己实现深拷贝(对指向的资源也进行拷贝)。
- 显示调用了析构函数并释放资源的类。
1.2 赋值运算符重载
赋值运算符重载本质上是把一个已经存在的对象拷贝复制给另一个已经存在的对象。
1.2.1 赋值运算符的特性
- 赋值运算符规定必须重载为成员函数,不能定义为全局函数。并且为了避免传值传参,需要将参数写成const 当前类类型引用。
cpp
Date& operator=(const Date& d)
{}
- 有返回值,是为了完成连续赋值等场景。而且返回值写成当前类类型的引用,可以提高程序效率。
cpp
Date& operator=(const Date& d)
{
return this;
}
- 没有显示实现赋值运算符重载时,编译器会默认生成一个赋值运算符重载,默认赋值运算符重载与默认拷贝构造函数类似,会完成值拷贝/浅拷贝(一个一个字节的拷贝)。
1.2.2 显示实现赋值运算符的条件
一般情况下,编译器默认实现的赋值运算符重载就能满足基本的需求。那么,什么时候需要显示实现赋值运算符重载呢?
- 像Stack这样的类,虽然也都是内置类型,但是_a指向了资源,编译器自动生成的赋值运算符重载完成的值拷贝/浅拷贝不符合我们的需求,所以需要我们自己实现深拷贝(对指向的资源也进行拷贝)。
- 显示调用了析构函数并释放资源的类。
2. 运算符重载
2.1 前言
一般而言,编译器已经定义好各种运算符,用来计算各种基础类,例如:+ - = == 等运算符计算 int char 等基础类。
但是在CPP中,由于语言允许程序员自己定义各种类。一般这种情况下,基础的运算符无法满足特殊类的计算。这种时候就需要程序员自己定义各种运算符,来完成对特殊类的运算。
同时,是否重置运算符是取决于对于类来说是否有意义的。例如日期类中,日期减日期能得到两个日期差多少天,这是有意义的;但是,一个日期加一个日期就没有意义。
2.2 运算符重载的本质
运算符重载的本质就是通过函数重载的特性,来创建一个具有运算符功能的函数。由于该函数与运算符代表的功能一致,所以将该函数称之为运算符重载。
运算符重载的名字是由operator和后⾯要定义的运算符共同构成。并且和其他函数一样,运算符重载也需要返回类型、参数列表和函数体。
例如:日期类中的运算符重载
cpp
Date& operator+=(int day)
{函数体}
同时,运算符重载的参数个数由运算符的作用对象个数决定。运算符的作用对象有一个,那么参数列表就只有一个;运算符的作用对象有两个,那么参数列表的作用对象就有两个。
调用运算符时,默认从左向右给参数赋值。运算符重载之后的优先级和结合性与原本的运算符相同。
例如:
cpp
// ++ 运算符作用对象包括对象 x。
Date& operator++()//这里的参数有1个,是隐藏的*this
{函数体}
cpp
// += 运算符作用对象包括对象 x 和 y。
Date& operator+=(int day)//这里的参数有两个,一个是隐藏的*this,另一个是day
{函数体}
注意:
不能通过运算符重载创建一个本来就不存在的运算符。
例如:
cpp
Date& operator#()
由于本来就不存在#运算符,所以不能通过运算符重载来创建一个#运算符。
2.3 运算符重载的特性
cpp
.* :: ?: sizeof .
//以上五个运算符无法重载
- 运算符重载必须含有一个类类型的参数,不能通过运算符重载改变内置类型的含义。例如日期类中的+运算符重载:
cpp
int operator+(int x,int y)
- 前置++和后置++的函数名相同,仅相差一个int形参。
cpp
//前置++
Date& operator++()
cpp
//后置++
Date operator++(int)
- 重载 << 和 >> 时,需要重载为全局函数。因为重载为成员函数的话,由于第一个参数默认成this指针,在调用时需要变成 << cout 。不符合习惯用法。重载为全局函数把 ostream 和 istream 放在第一个形参就能解决。
cpp
//流提取的运算符重载
ostream& operator<<(ostream& out, const Date& d);
//流插入的运算符重载
istream& operator>>(istream& in, Date& d);
3. const成员函数
将 const 放在成员函数后面,称为const成员函数。
cpp
Date operator-(int) const
{}
const成员函数本质上涉及的问题是权限的放大和缩小。
当创建一个新的对象的时,使用const修饰新的对象,那么就需要使用const成员函数。因为对象自己由于const而无法修改,不能在调用成员函数时发生改变。
因此const成员函数修饰的是this指针,表明禁止在该成员函数中对类的任何成员进行修改。
那么就又引出了新的问题。哪些成员函数需要const修饰,哪些又不需要?
很简单,只需要观察 this 指针指向的函数是否发生改变。例如:
cpp
Date operator+=(int)
{}
上面的 += 运算符重载不能使用 const 修饰。因为 += 运算符会改变 this 所指向的值。
cpp
Date operator-(int) const
{}
而上面 - 运算符重载则能使用 const 修饰。因为 - 运算符不会改变 this 指针所指向的值。
4. 取地址运算符重载
取地址运算符重载分为 普通取地址运算符重载 和 const取地址运算符重载 ,一般情况下,编译器默认生成的就足够使用。
但是,在某些特殊情况下,不想让他人知道成员函数的地址,这时就可以自己实现两个运算符重载。
cpp
Date* operator&()
{
//return this;//返回的是成员函数地址
// return nullptr;//返回空
return (const Date*)0x12345FFF;//返回假地址
}
const Date* operator&()const
{
//return this;//返回的是成员函数地址
// return nullptr;//返回空
return (const Date*)0x12345FFF;//返回假地址
}