运算符重载
一个简单的时间函数相加的函数头来说明格式:
Time Time::operator +(const Time & t) const
函数体的实现就是按照常规来实现所有数据的处理,就像我们定义了一个sum()函数一样去实现。
然后调用的时候有两种形式:
//方式1, 太非主流了,直接调用函数。。。
total = time_1.operator+(time_2);
//方式2
total = time_1 + time_2
支持连加吗?
那要看我们的函数返回值,如果返回的也是一个Time类型的对象,当然可以,如果是其他情况,返回值类型不是作用域的类型,就不可以了。
重载限制
- 重载之后的运算符至少有一个操作数是用户定义的类型,防止用户重载C++为已有的一些数据类型定义的运算规则,比如不能把int类型的加法定义成计算差吧
- 重载之后的运算符不能违反原来的语法规则,比如不能改变运算符的操作数,优先级等
- 不能创建新的运算符
- 有些运算符只能通过类成员函数来重载:
= : 赋值运算符
(): 函数调用运算符
[]: 下标运算符
->: 通过指针访问类成员的运算符- 有些运算符不能被重载,太多就不列出
友元函数
友元分为3种:
- 友元函数
- 友元类
- 友元成员函数
引入友元函数,为了让非成员函数可以访问类的私有变量
友元函数必须在类内声明,才能标识该友元函数是这个类的友元
一个函数头例子:
friend Time operator*(double m, const Time & t);
因为这个函数并不是成员函数,所以就不用加上类作用域Time::,二期在实现函数体的时候是不需要加上friend关键字的。
我们在很多情况下涉及到,一个运算符的第一个操作数并不是类对象,而是其他类型,这时我们不能通过类的重载的运算符来调用函数,而是要通过定义友元函数去调用
常见的 << 和 >> 的重载:
给一个常见的函数定义示例:
ostream & operator << (ostream & os, const Time & t) {
os << t.hours << "hours, " << t.minutes << "minutes";
}
这个函数有两个点要说:
- 参数列表
之前我一直有一个困惑第一个参数os是干什么用的,今天看到这里才发现cout是一个ostream的类对象;
这样比如
cout << time_1;
这句话里,cout这个ostream类对象就是 “<<” 这个运算符的左操作数
- 返回值
为什么返回值是一个ostream的引用,是为了解决连续使用这个运算符的情况,如下,在处理每个右操作数的时候,保证左操作数都是一个ostream的引用
所以估计ostream自己在重载这个运算符的时候返回类型也是ostream的引用
cout << "This is time_1: " << time_1;
这样,” >> “操作符是不是类似了~只不过换成istream就好了
类的自动转换和强制类型转换
隐式转换:
- 赋值运算符调用符合参数列表的构造函数
如果我们有一个构造函数声明如下:
Stonewt(double a);
那么如下操作是可以通过编译的:
Stonewt mycat;
myCat = 19.6;
只针对构造函数的参数只有一个参数,或者有n个参数,但是其他n-1个参数都有默认值的情况;
防止意外的转换,我们在定义这类构造函数的时候可以用explicit关键字关闭这个特性:
explicit Stonewt(double a);
转换函数:
- 用户自定义的强制转换
我们想要实现如下的效果:
Stonewt wolfe(285.7);
double host = (double)wolfe;
那么我们就要在类里面重载C++自己的类型转换函数了
public:
operator double() const;
然后再去实现:
Stonewt::operator double() const {
return pounds;
//this->pounds;pounds是Stonewt类型的一个double类型的成员变量
}
这里你看,我们在声明函数和实现函数的函数头都没有定义返回值类型,但是函数体里却return了一个double类型的值,这也是转换函数的一个特殊的地方
另外,我们在定义了这个转换函数之后,也可以直接使用隐式转换:
Stonewt wolfe(285.7);
double host = wolfe;
谨慎使用隐式转换!!!
再谈友元函数:
我们又要重载运算符+, 有两种方式:
//成员函数
Stonewt Stonewt::operator + (const Stonewt & st) const {
double pds = pounds + st.pounds;
Stonewt sum(pds);
return sum;
}
//友元函数
Stonewt operator + (const Stonewt & st1, const Stonewt & st2) {
double pds = st1.pounds + st2.pounds;
Stonewt sum(pds);
return sum;
}
就算我们有了隐式转换,我们的隐式转换也不会应用于成员函数的调用者,也就是说,成员函数的左操作数必须一开始是Stonewt类型的成员,而不能是由double类型转换而来的;但是参数,和友元函数的参数都是可以接受隐式转换的结果。
哈哈,讲个笑话:
可以相互转换的混淆:
如果我在程序中定义了A, B两种类型,然后同时实现了A到B的隐式转换和B到A的隐式转换,那么如果我定义了一个函数参数有A类型,也有B类型,那转换会不会混淆呢?
结果应该是这种情况就不用转换了,该是谁就是谁
所以,还是尽量避免隐式转换,老老实实加一个explicit,会让自己分析代码的时候不至于晕过去