C++类和对象(中) 之 【运算符重载、赋值运算符重载、前置++与后置++、const成员、取地址及const取地址操作符重载】
1.运算符重载
为了增强代码可读性,C++引入了运算符重载这种特性
运算符重载具体来说就是
通过实现一个特殊的函数,
使得相对应的运算符可以直接作用于类对象上,并且表达相对应的含义
1.1运算符重载
1.1.1运算符重载函数的格式
运算符重载需使用函数实现:
函数名字为:关键字operator后面接需要重载的运算符符号
函数原型:返回值类型 operator操作符(参数列表)
1.1.2运算符重载注意事项
注意事项:
(1)不能通过连接其他符号来创建新的操作符:比如operator@
(1)重载操作符必须有一个类类型参数
至少拥有一个类类型才符合我们引入运算符重载的目的
(1)运算符进行重载时,尽量让它执行与内置类型相似的操作,从而表达相对应的含义
例如:实现一个日期d1,并算出它100天之后是哪一天
这时候我们就可以主观的认为 这是 日期加上天数
所以, 重载运算符 +
使 + 能够进行 日期(类)与天数(内置类型)之间的运算
(1)作为类成员函数重载时,其形参看起来比操作数数目少1
因为成员函数的第一个参数为隐藏的this指针
(1)
.*
::
sizeof
?:
.
注意以上5个运算符不能重载
规矩就是规矩,不能重载就不能重载,哈哈哈哈~
1.1.3运算符重载函数实现
当我们想在全局域定义运算符重载函数时,往往会遇到成员变量不可访问的障碍
这时候有两个方法可以解决:
(1)在类中去定义运算符重载函数
(2)声明友元函数,类和对象(下)进行讲解
在类中去定义运算符重载函数
前面我们提到过:
作为类成员函数重载时,其形参看起来比操作数数目少1
因为成员函数的第一个参数为隐藏的this指针
bool operator==(const Date& d2)
{
return _year == d2._year
&& _month == d2._month
&& _day == d2._day;
}
这才是类中正确实现的运算符重载函数
当我们比较两个Date类对象时,编译器 就会去调用相应的运算符重载函数
在运算符左边的就是左操作数,因为 d1在d2之前,那么函数内部, this指向的内容就应该是左操作数d1 == d2; // d1.operator==(d2)
1.2赋值运算符重载
赋值运算符重载函数是类中的默认成员函数
即,程序员不主动定义赋值运算符重载函数时,编译器仍然会自动生成
1.2.1. 赋值运算符重载格式
(1)参数类型:const T&
传递引用可以提高传参效率
(1)返回值类型:T&
返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
(1)检测是否自己给自己赋值 ,这是为了
第一,减少不必要的操作
第二,防止代码在尝试释放和重新分配相同的内存时导致不必要的错误
(1)返回*this :要复合连续赋值的含义
在函数体中,
将一个对象的相应成员变量的值赋值给另一个对象的相应成员变量后,
选择返回被赋值对象的引用,此时
被赋值对象的生命周期不会再随函数栈帧销毁而销毁
我们一般也选择返回左操作数的引用
1.2.2赋值运算符只能重载成类的成员函数不能重载成全局函数
前面提到,赋值运算符重载函数是类中的默认成员函数
那么,如果我们再将其重载为全局函数,就会发生冲突
1.2.3用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝
注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值
类似于拷贝构造函数,如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必 须要实现
1.3前置++和后置++
1.3.1前置++
Date& operator++()
{
_day++;
return *this;
}
上面就是前置++被重载为类的成员函数,此时,
让天数+1(举例就没有考虑进位)
再返回对象的引用即可
返回的对象并不会随函数栈帧的销毁而销毁
传递引用提高效率
1.3.2后置++
Date operator++(int)
{
Date temp = *this;
_day++;
return temp;
}
(1)为了与前置++进行区别,后置++进行重载的时候
规定声明一个int型的参数,用户不需要显示传递,编译器自动传递
(1) 后置++是要先返回之前的值,所以我们要先保存之前的对象
(1)
Date temp = *this;//Date temp(*this);
虽然这行代码使用了 = 符号,但实际上,这里会调用一个拷贝构造函数,进行创建temp
因为:用一个对象去创建另一个对象时,调用拷贝构造函数
两个已存在的对象进行赋值时,才会调用赋值运算符重载函数
(1) temp是一个局部对象,所以这里我们只能以值的方式进行返回
综上,前置++的效率肯定高于后置++,减少了拷贝构造的开销
2.const成员
将const修饰的“成员函数”称之为const成员函数
void Print()const
{
cout << _year << "年" << endl;
}
上面展示的就是一个被const修饰的成员函数,const 位于 () 后
我们知道 ,成员函数的实际原型应该是这样的:
void Print();// void Print(Date* const this);
此时的const修饰的是 this 指针,以确保, this不会发生改变
使用const修饰成员函数之后:
void Print() const;//void Print(const Date* const this);
此时的第一个const修饰的是 *this ,以确保, this指向的对象的内容不会发生改变
1. const对象可以调用非const成员函数吗?
不可以,因为非const成员函数可能修改对象中的值
const
对象只能调用const
成员函数。因为非const
成员函数可能会修改对象的成员变量,而const
对象被承诺不会进行任何修改。
2. 非const对象可以调用const成员函数吗?
可以,因为非const对象的成员变量可以被改变,
如果你要求它不变,即调用const成员函数后,
那它就不变好了,这只是一个权限缩小的过程,是可以实现的
非const
对象可以调用const
成员函数,因为const
成员函数承诺不会修改对象的状态。这可以被视为对对象权限的缩小,即主动限制了对对象的修改。
3. const成员函数内可以调用其它的非const成员函数吗?
不可以,const成员函数内,要求对象的内容不能被改变,
调用其它的非const成员函数后,在非const成员函数内部就可能被改变
const
成员函数不能调用非const
成员函数,因为非const
成员函数可能会修改对象的成员变量,这违反了const
成员函数的承诺,即不修改对象的状态。
4. 非const成员函数内可以调用其它的const成员函数吗?
可以,因为非const成员函数内部成员变量可以被改变,
如果你要求它不变,即调用const成员函数后,
那它就不变好了,这只是一个权限缩小的过程,是可以实现的
非const
成员函数可以调用const
成员函数,因为非const
成员函数本身不限制对对象的修改,而调用const
成员函数只是进一步承诺在该调用期间不修改对象的状态。
3.取地址及const取地址操作符重载
这两个默认成员函数一般不用重新定义 ,编译器默认会生成
class Date
{
public:
Date* operator&()
{
return this;
}
const Date* operator&()const
{
return this;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需 要重载,比如想让别人获取到指定的内容!