文章目录
把标准运算符用于类对象(使代码外观更自然美观)
之前说过了函数重载,也叫函数多态,现在我们要把重载的概念进行扩展,扩展到运算符上,使得C++的运算符也可以有多中含义。
多态是一种很贴近生活的特性,我们人类的语言处处可见多态的影子,比如抬起左脚和抬起右脚都用一个单词,抬起任何东西都是一个词,lift,如果每一个物种的“抬起”都要用一个不同的词语来表达,那我们人类的语言将变得多么的笨拙和困难···沟通代价都变大了
已经被重载的C++运算符有:*,要么是乘法运算符(有两个操作数且都是可以做乘法运算的类型),要么是解引用(当它的操作数只有一个且是地址时);
&,要么是地址运算符(作用于变量或常量),要么是引用运算符(作用于变量或常量,但前面还有个类型名)。
现在我们还要重点讨论把运算符重载到类的对象上,比如使得两个对象相加,让编译器根据操作数的数目和类型决定使用哪一种加法定义。比如你可以重载加法运算符,使得可以直接把两个相同长度的数组传入进行对应元素相加,这样就不需要写循环来实现了。这样做后,代码看起来也更简单更符合人类思维,隐藏了内部机理,强调了实质,实现了OOP目标。
重载运算符的本质(底层实现):运算符函数
虽然重载运算符后代码简单了,但是要知道实际上编译器会把你的简单代码准换为你自己写的运算符函数的调用形式
示例 两个等长数组对应元素相加(Array类)
不重载运算符
//main.cpp
#include <iostream>
int main()
{
const int SIZE = 5;
double morning[SIZE] = {1.1, 2.1, 3.1, 4.1, 5.1};
double evening[SIZE] = {5.2, 6.2, 7.2, 8.2, 9.2};
double day[SIZE] {};
int i;
for (i = 0; i < SIZE; ++i)
day[i] = morning[i] + evening[i];
for (i = 0; i < SIZE; ++i)
std::cout << morning[i] << ' ';
std::cout << '\n';
for (i = 0; i < SIZE; ++i)
std::cout << evening[i] << ' ';
std::cout << '\n';
for (i = 0; i < SIZE; ++i)
std::cout << day[i] << ' ';
std::cout << '\n';
return 0;
}
重载加法运算符
//Array.cpp
#include <iostream>
#include "array.h"
Array::Array(Item item[])
{
int i;
for (i = 0; i < SIZE; ++i)
items[i] = item[i];
}
Array::Array()
{
items[SIZE] = {};
}
Array & Array::operator+(const Array & ar)
{
int i;
for (i = 0; i < SIZE; ++i)
(*this)[i] += ar[i];
return *this;
}
void Array::showArray(const Array & ar) const
{
int i;
for (i = 0; i < SIZE; ++i)
std::cout << ar[i] << ' ';
std::cout << '\n';
}
//array.h
#ifndef ARRAY_H_
#define ARRAY_H_
typedef double Item;
class Array{
private:
enum {SIZE = 5};
Item items[SIZE];
public:
Array(Item item[]);
Array();
Array & operator+(const Array & ar);//运算符函数
void showArray(const Array & ar) const;
};
#endif // ARRAY_H_
//main.cpp
#include <iostream>
#include "array.h"
int main()
{
double a[5] = {1.1, 2.1, 3.1, 4.1, 5.1};
double b[] = {5.2, 6.2, 7.2, 8.2, 9.2};
Array morning = Array(a);
Array evening = Array(b);
Array day;
showArray(morning);
showArray(evening);
showArray(day);
day = morning + evening;
showArray(day);
return 0;
}
示例2 Time类,两个时间相加
//mytime.h
#ifndef MYTIME_H_
#define MYTIME_H_
typedef unsigned int Item;
class Time{
private:
enum {SEC_PER_MIN = 60, MIN_PER_HR = 60};
Item hour;
Item min;
Item sec;
public:
Time(Item & h, Item & m, Item & s);
Time();
Time & sum(const Time & t);
void showTime() const;
};
#endif
#include <iostream>
#include "mytime.h"
Time::Time(Item & h, Item & m, Item & s)
{
hour = h;
min = m;
sec = s;
}
Time::Time()
{
hour = 0;
min = 0;
sec = 0;
}
Time & Time::sum(const Time & t)
{
Time temp = *this;//存住初始值
this->sec = (t.sec + temp.sec) % SEC_PER_MIN;
this->min = (t.min + temp.min + (t.sec + temp.sec) / SEC_PER_MIN) % MIN_PER_HR;
this->hour = t.hour + temp.hour + (t.min + temp.min + (t.sec + temp.sec) / SEC_PER_MIN) / MIN_PER_HR;
return *this;
}
void Time::showTime() const
{
std::cout << this->hour << ":" << this->min << ":" << this->sec << '\n';
}
//main.cpp
#include <iostream>
#include "mytime.h"
int main()
{
unsigned int h = 15, m = 14, s = 35;
unsigned int h1 = 2, m1 = 49, s1 = 55;
Time t1 = Time(h, m, s);
Time t2(h1, m1, s1);
t1.showTime();
t2.showTime();
t1.sum(t2);
t1.showTime();
t2.showTime();
return 0;
}
终于自己写对了一个类,中间出错很多,比如:
- sum函数还是像以前,习惯性的设置两个参数,想着把两个参数相加,返回一个结果。但是现在sum是类的方法,所以有一个对象调用它,这个调用对象就是一个参数,所以方法只需要一个参数。
- 同样的,showTime方法根本不需要参数,因为显示调用对象即可。
- 写时间的相加函数,sum时,刚开始没有设置临时对象保存住调用对象的值,结果是错的,因为三个计算,后者会被前者的结果所影响,所以必须有临时对象存储调用对象的初始值且不可以把临时对象设置为引用对象,因为那样就不是副本,还是在操作调用对象本体。
- 我写的构造函数的参数是引用参数,所以在主程序中初始化Time类对象时,必须给构造函数传递可修改的左值,不能传递常量和不可修改的左值,否则报错
Time t1 = Time(1, 2, 3);//报错,常量
Time t1 = Time(h+1, m+1, s+1);//报错,表达式,还是常量
15:14:35
2:49:55
18:4:30
2:49:55
上面的代码没有重载加法运算符,只是用了sum函数,那么怎么重载加法运算符呢,只需把sum名称改为operator+即可
原型和函数头的改变
Time & operator+(const Time & t);
调用改为:
t1 + t2;//以前是t1.sum(t2);
结果正确
但是上面的加法都是把第一个操作对象和第二个操作对象加起来的值放在了第一个操作对象里,这种做法并不是最通用的,还是要改为c=a+b的形式,即三个不同操作数,a,b不被改
所以函数定义改为(注意返回值不能是引用,因为temp是临时对象),由于函数不修改参数的值,所以函数是const成员函数
Time Time::operator+(const Time & t) const
{
Time temp = *this;//存住初始值
temp.sec = (t.sec + this->sec) % SEC_PER_MIN;
temp.min = (t.min + this->min + (t.sec + this->sec) / SEC_PER_MIN) % MIN_PER_HR;
temp.hour = t.hour + this->hour + (t.min + this->min + (t.sec + this->sec) / SEC_PER_MIN) / MIN_PER_HR;
return temp;
}
//main.cpp
#include <iostream>
#include "mytime.h"
int main()
{
unsigned int h = 15, m = 14, s = 35;
unsigned int h1 = 2, m1 = 49, s1 = 55;
Time t1 = Time(h, m, s);
Time t2(h1, m1, s1);
t1.showTime();
t2.showTime();
Time t3 = t1 + t2;
Time t4 = t1.operator+(t2);
Time t5 = t1 + t2 + t3;
t1.showTime();
t2.showTime();
t3.showTime();
t4.showTime();
t5.showTime();
return 0;
}
可以看到,也可以写运算符函数的全名进行调用,结果一样
15:14:35
2:49:55
15:14:35
2:49:55
18:4:30
18:4:30
36:9:0
示例 再加两个方法,两个时间相减,以及时间乘以一个数(成员运算符优先级高于解引用!!!)
//main.cpp
#include <iostream>
#include "mytime.h"
int main()
{
unsigned int h = 15, m = 14, s = 35;
unsigned int h1 = 2, m1 = 49, s1 = 55;
Time t1 = Time(h, m, s);
Time t2(h1, m1, s1);
t1.showTime();
t2.showTime();
Time t3 = t1 - t2;
Time t4 = t1 * 2.0;
t1.showTime();
t2.showTime();
t3.showTime();
t4.showTime();
return 0;
}
#include <iostream>
#include "mytime.h"
typedef unsigned long ulong;
Time::Time(Item & h, Item & m, Item & s)
{
hour = h;
min = m;
sec = s;
}
Time::Time()
{
hour = 0;
min = 0;
sec = 0;
}
Time Time::operator+(const Time & t) const
{
Time temp = *this;//存住初始值
temp.sec = (t.sec + this->sec) % SEC_PER_MIN;
temp.min = (t.min + this->min + (t.sec + this->sec) / SEC_PER_MIN) % MIN_PER_HR;
temp.hour = t.hour + this->hour + (t.min + this->min + (t.sec + this->sec) / SEC_PER_MIN) / MIN_PER_HR;
return temp;
}
Time Time::operator-(const Time & t) const
{
ulong temp = (*this).toSecs() - t.toSecs();//成员运算符优先级高于解引用!!!
return toTime(temp);
}
Time Time::operator*(double mult) const
{
ulong temp = (*this).toSecs();//成员运算符优先级高于解引用!!!
return toTime(ulong(mult * temp));
}
void Time::showTime() const
{
std::cout << this->hour << ":" << this->min << ":" << this->sec << '\n';
}
ulong Time::toSecs() const
{
return ((this->hour * MIN_PER_HR + this->min) * SEC_PER_MIN + this->sec);
}
Time toTime(ulong secs)
{
Item hour, min, sec;
hour = secs / 3600;
secs %= 3600;
min = secs / 60;
sec = secs % 60;
return Time(hour, min, sec);
}
//mytime.h
#ifndef MYTIME_H_
#define MYTIME_H_
typedef unsigned long ulong;
typedef unsigned int Item;
class Time{
private:
enum {SEC_PER_MIN = 60, MIN_PER_HR = 60};
Item hour;
Item min;
Item sec;
ulong toSecs() const;
public:
Time(Item & h, Item & m, Item & s);
Time();
Time operator+(const Time & t) const;
void showTime() const;
Time operator-(const Time & t) const;
Time operator*(double mult) const;
};
Time toTime(ulong secs);
#endif
结果完全正确
15:14:35
2:49:55
15:14:35
2:49:55
12:24:40
30:29:10
写这两个方法的过程中,遇到很多原型和定义的函数头不匹配的情况,因为一边写错了另一边没来得及修改。
另外,我设计了一个函数把时分秒格式的时间转换为秒数,是类方法;还设计了一个非类方法,用于把纯秒数转换为Time类对象,即时分秒格式。但是后者的编写让我在访问控制上碰了一鼻子灰,我还是用了Time类的私有成员,包括hour,min,sec和两个枚举常量的名称,于是被无情报错,由于不是类方法,所以根本不能访问人家。于是我只好改为定义三个Item变量,然后转换好再调用构造函数构造一个对象传回去。并且由于用不了常量我直接用的3600和60.这不是个好办法啊
重载运算符的一些限制
-
至少有一个操作数时用户定义的类型。这主要是为了防止用户对标准运算符进行重载。比如把减号试图定义为两个double的和。这是不可以的。
-
重载时不可以违反运算符原本的规则,比如操作数个数和运算符优先级。比如你不可以把%重载为只使用一个操作数。
-
不可以自己创建新的运算符,这个很好理解,因为创建新的就不叫重载了。比如试图用operator**()函数表示求幂,这是不行的,因为C++根本没有**运算符。(python有 ∗ ∗ ** ∗∗运算符,表示求幂次)
-
有一些运算符不可以重载:
sizeof
. 成员
.* 成员指针
:: 作用域解析
?: 条件
typeid 一个RTTI运算符
const_cast 强制类型转换
dynamic_cast 强制类型转换
reinterpret_cast 强制类型转换
static_cast 强制类型转换 -
有些运算符虽然可以重载,但是只可以通过成员函数重载。不能通过非成员函数进行重载。
= 赋值
() 函数调用运算符
[] 数组下标索引运算符
-> 间接成员 -
下列运算符中除了刚说的四个不能用非成员函数重载的以外,都可以用成员函数和非成员函数两种方式进行重载。
可以看到,大多数运算符都可以用成员函数和非成员函数两种方式进行重载。
-
最后一个限制:重载运算符时,不要随意乱来,适合重载才重载,不适合就自己写类方法。给安排的新工作要和运算符本身的工作相近,比如你不要把 ∗ * ∗重载为交换两个对象的数据成员,因为从 ∗ * ∗上一看,根本看不出来是交换成员的意思。。你交换成员还是老老实实写类方法。
但是这个限制只是建议,可以打破。比如ostream类重载了左移运算符<<,把他变成了一个输出工具,插入运算符。