运算符重载在c++中是经常用到的一个知识点,也是c++富有扩展性的重要支持点。
今天我准备把运算符重载在各种情况下的应用,都一一举例,待会我们就会知道运算符重载的威力和带来的便捷性。
运算符重载函数的两种形式:
1、作为类的成员函数
赋值(=)、下标([])、调用( () )、成员访问箭头(->) , 这四个运算符必须是成员函数,其他运算符既可以是成员函数,也可以使非成员函数。
注意点:如果我们把运算符函数定义成成员函数时,它的左侧运算对象必须是所属类的一个对象。
2、作为非成员函数
一般情况下,只有具有对称性的运算符才会定义成非成员函数(比如: &&、==) , 非成员函数,一般情况下都是类的友员函数。
由此可以看出,如果一个运算符被定义为非成员函数的形参数量比其定义为成员函数多一个。
下面是各种运算符的重载
1、重载输入输出运算符
<span style="font-size:12px;">class base { public: base() {} base(int new_x , double new_y): x(new_x) , y(new_y) {} friend istream &operator>>(istream &in , base &chs); friend ostream &operator<<(ostream &out , base &chs); private: int x; double y; }; istream &operator>>(istream &in , base &chs) { in>>chs.x>>chs.y; return in; } ostream &operator<<(ostream &out , base &chs) { out<<chs.x<<" "<<chs.y; return out; }</span>
如上图的代码:
1、输入输出必须得定义成友元函数,因为如果我们定义为成员函数,那么就意味着第一个参数必须是所在类的对象,则我们调用的时候就得这样:
<span style="font-size:14px;"> base x ; x>>cin;</span>
2、我们一般情况下都要返回流的引用,因为这是为了可以连续的输入: cin>>x>>y;
2、算术和关系运算符
算数运算符主要是普通的运算符,如: + - * / && || !等运算符
其中,算术运算符和关系运算符一般都是非成员函数。
下面解释为什么算术运算符和关系运算符不能是成员函数:
1、如果运算符 ‘+’ 是成员函数
class base { public: base() {} base(int new_x , double new_y): x(new_x) , y(new_y) {} <span style="color:#ff0000;">base operator +(const base &chs) { x += chs.x; y += chs.y; return *this; }</span> private: int x; double y; };
base x(1 , 2.1) , y(3 , 3.4) , z;
z = x+y; //这个时候即改变了x , 也改变了 z , 但我们的本意是只改变在 , 而不改变x;
2、下面是友元函数
base operator +(const base &ch , const base &chs) { base bz = ch; bz.x += chs.x; bz.y += chs.y; return bz; }
<pre class="cpp" name="code">base x(1 , 2.1) , y(3 , 3.4) , z;
z = x+y; // 这个时候,我们由于在函数中用了一个中间变量,因此只改变了 z , 而x不变
通过上面我们知道,对于一个运算符重载的函数形式很关键,如果用错了函数形式,那么可能会带来意料不到的危险。
因此,当我们要写一个运算符重载的函数时,我们首先要弄清这个运算符是干什么了 ,在内置类型上有哪些用法,我们再根据这些用法去决定,是要写成成员函数 , 还是非成员函数。
3、下标运算符 [ i ]
下标运算符主要是用于数组、指针、容器等,下标运算符的作用有下面几点:
1、通过下标运算符,得到数组中该位置的值
2、我们还需要通过下标运算符改变该位置的值
我们能知道,下标运算符一定是要作为成员函数。
根绝上面的作用分析,我们可以得到
1、函数需要一个整形形参,告诉函数要去哪个位置的值
2、函数必须得返回该位置的引用值
3、我们需要重载一个常量版本的函数。
由上面,我们就得到了代码:
char &base::operator [](int i) { if(i >= length) return chs[0]; return chs[i]; } const char &base::operator [](int i) const { if(i >= length) return chs[0]; return chs[i]; }
4、递增递减运算符
在迭代器或平时的内置类型上,我们经常都会使用 (--)和(++)运算符,对于初学者来说,他们肯定特想把这两个运算符弄死掉。下面我们就以(++)运算符,来分析其作用;
对于递增(++)运算符的作用
1、运算符在变量前面,直接递增变量的值
2、运算符在变量后面,这时我们就要先保存对象的状态,然后再把 递增的值传给他
对于这两个运算符,我们一般把函数写为成员函数。
我们先写递增运算符前置的函数,前置时,我们返回的是该变量自身的引用
base &base::operator ++() { x += 1; return *this; }
递增运算符(++) 后置,这时我们就要考虑怎么才能实现,先保存其状态?
我们这样,我们先返回一个中间变量 , 那我们下次调用该变量时,就是递增之后的变量了。
由于我们返回的是中间变量,所以我们不能返回引用了。
<span style="color:#000000;">base base::operator ++<span style="color:#cc0000;">(int)</span> { base bz = *this; x += 1; return bz; }</span><span style="color:#990000;"> </span>
之所以加上一个整形形参,是因为编译器需要区分前置和后置两个情况。因此下次调用时,我们就必须传递一个整形给它
5、函数调用运算符 ()
函数调用运算符必须得是成员函数。
函数调用运算符,使得我们可以像调用函数一样,调用该类的对象
class base { public: void operator ()() //没有参数的函数调用运算符 { cout<<x<<endl; } void operator() (int z) //存在一个整形形参 { cout<<x+z<<endl; } private: int x; };
//下面是调用形式
base x;
x(); //调用第一个函数
x(4); //调用第二个函数
c++中的lambda表,就类似于在一个类中重载了函数调用运算符函数。
6、类型转换运算符 , int double
类型转换运算符负责把类转换成另个需要的类型
因此,其没有显示的返回类型,也没有任何参数。
class base { public: operator int() { return x; //把类类型转黄成一个整形 } private: int x; };
//这是一个既可以隐身转换,也能显示转换的
base x;
int z = x , y = static_cast<int>(x);
//如果在函数前面加上explicit就只能显示的转换
explicit operator int();
int x = z; //这是错误的 , 不能隐式转换
int x = static_cast<int>(x);
当我们重载类型转换运算符时,我们一定要小心二义性。
运算符重载,确实给我们带来了很大自由和便捷。但同时它也带来了危险,因此在使用的时候我们一定要对家注意。
我们重载运算符时,我们要记住,重载的运算符,一定要符合这个运算符本身的特性,也要尽量满足我们再内置类型上对这个运算符的使用习惯。
共勉。
c++笔记——运算符重载