一、运算符重载
1.1运算符重载的格式
使用关键字operator+运算符可以直接进行运算符重载
返回值类型 operator运算符(参数列表)
{
//...
}
例如比较日期类(Date)d1与d2
bool operator>(Date& const d1,Date& const d2)
{
//...
}
//在使用的时候
//d1>d2
//即可
1.2运算符重载的注意事项
①不能通过链接其他符号来创建新的操作符,例如不可以是operator@
②重载操作符至少要有一个类类型的参数,例如不可以是operator-(int a,int b)
③用于内置类型的运算符,重载时其含义原则上不变动,例如对于+,该加还是把类相加,不宜重载时候搞成减
④作为类成员函数进行运算符重载的时候,它的第一个参数为隐含的this指针
⑤以下
1>.* 2> . 3>sizeof 4>? : 5>::
这五个运算符不可以被重载
1.2补:对.*操作符的初步了解
它的作用是用 类中函数指针类型 的对象(令为ptrFunc)存储类中函数后,再创建一个类类型对象(令为temp),用这两个对象调用函数时使用,且看例子
class OB
{
void func()
{ }
}
//重命名类中void返回值函数指针类型
typedef void (OB::*ptrFunc) ();
//对于成员函数,必须要加&才能取到函数指针
ptrfunc fp=&OB::func;
OB temp;
//运算符使用
(temp.*fp)();
1.3运算符重载过程中,私有成员无法访问的解决办法
①提供这些函数的get/set方法,get其实就是写一个成员函数直接返回私有对象的值
②友元函数,具体可以参考“五”编号处介绍
③直接重载为成员函数,但要注意this指针顶替了一个参数位置的问题
注:如果在全局和类中都有运算符重载,那么类中的优先级更高,主要是因为遇到如d3==d4之类的,调用默认为d3.oprator==(d4),而不是oprator(d3,d4);
二、赋值重载函数(六大默认成员函数之四)
2.1赋值重载函数的主要任务
一个已经存在的对象,拷贝赋值给另一个已经存在的对象
2.2赋值重载函数中涉及到的引用返回问题
2.2.1返回局部变量的坑
我们常书写类类型的返回对象,容易遇到返回对象是一个局部对象或临时对象,在这时候出了函数作用域这些对象就被析构销毁了,用引用返回是存在风险的,因为引用对象在对应的函数栈帧之中已经被销毁,内存可能会继续使用这块空间,返回的也就是一个野指针了。
综上,要用引用返回,必须要保证返回的对象出了函数作用域还存在
2.2.2引用返回的好处
当我们书写类类型的返回对象时,要门用正常返回,要么用引用返回
①使用正常返回会经历调用拷贝构造函数将返回的对象赋值给中间变量,再把中间变量对应值进行返回,有一定的消耗
②引用返回则是只返回别名,无需进行拷贝构造函数的调用
2.3不写构造函数,只靠默认生成时的情况
用户没有显式写,编译器会自动生成一个赋值重载函数,会进行逐字节拷贝
因此它的情况与第二篇中拷贝构造函数类似,根据有无资源管理来确定是否需要显式来写。
2.4赋值重载函数很特殊,不允许重载为全局函数
原因:类中不写也会默认生成,所以全局的会与类中冲突,他们传参相同无法构成重载。
三、有关类和对象中的其他几个问题
3.1运算符重载中对于前置++与后置++的处理
此处规定,前置++正常进行运算符重载,后置++要在参数列表中加上一个参数int,依次来用于区别前置++
例如
class Date
{
//...
Date& operator++();//前置++
Date operator++(int);//后置++
//...
}
3.2在头文件中进行函数定义的问题
当我们在头文件如Date.h中定义了一个全局的函数,然后又在两个源文件如Date.cpp和Test.cpp中对这一头文件进行展开的时候,会有函数双重定义的问题
此时,我们可以在函数之前加一个static解决问题,因为它改变了链接时的属性,使其在链接的时候不重复
四、关于cout加深理解
4.1.cout本质究竟是什么
cout实际上是类ostream创建的一个对象,
如我们常见的
int a=10;
cout << a;
double b=1.1;
cout << b;
其实是因为函数
ostream& operator<<(int val);
ostream& operator<<(double val);
他们的存在(类似的内置类型在ostream中都有了定义)
原代码执行了
cout.operator<<(a);
cout.operator<<(b);
实现打印
4.2可以用cout直接输出一个类类型的对象吗
这是可以的,我们只需要对输出操作符进行重载即可
但是别重载为成员函数,否则因为this指针占用了我们第一个参数的位置而出现
d<<cout;
这种代码简化式;
注:重载的一个小规则
运算符重载中,参数顺序与操作数顺序一致。
正确的做法是重载为全局函数
如
ostream& operator<<(ostream& out,const Date& d)
{
//...
}
此处选用ostream&类型作为返回值是为了使
cout<<d1<<d2<<d3;
这一类连续赋值变得可行。
4.3cin是否同理
是同理的,如函数
istream& operator>>(istream& in,Date& d)
{
//...
}
五、友元函数简介
5.1友元函数作用
在定义全局函数的时候,有时候需要用到类中的私有成员值,此时友元函数可以让这次访问变得可行
5.2友元函数的用法
在class类定义的开始,将friend+函数声明写到类中(访问限定符什么都可以)
例如
class Date
{
friend int GetMonthDay();
public:
//...
}
六、const成员函数
例如定义了一个对象
const Date d1(2024 4 10);
//在Date类中有一个Print函数
d1.Print();
此时我们知道,Print中有一个隐含的this指针,它的类型是
Date& const
所以很明显,单独对于this指针指向的值d1来说,他就是
Date&,可读可写
但是我们的d1其实是一个只读的对象,所以存在权限的放大
要解决这个问题,我们可以在Print函数后面加上一个const(规定在这个位置,因为我们无法直接对隐含的this指针进行类型的改变)
void Print() const;
综上,我们日常写代码能加const就要加上。
七、取地址及const取地址运算符的重载
一般他们指的是这两个函数(在类中)
A* operator&()
{
return this;
}
const A* operator&() const
{
return this;
}
而这两个函数编译器默认生成的就够用,很少显式去写它们