C++独孤九剑第七式——庖丁解牛(各种重载操作)

来源:互联网 发布:烟筒防风帽制作数据 编辑:程序博客网 时间:2024/06/07 13:45

庖丁解牛,庖丁指的是编译器,而我们则是提供牛的人,牛就是被重载的操作。当编译器遇到被重载的操作,能够准确地找出合适一个,犹如庖丁解牛,游刃有余。

重载主要分为函数重载和操作符重载。函数重载应该大家都会稍微熟悉一些,操作符可能略显生疏。但是不管怎样,我们讨论完后肯定会有更深刻的理解。

一、函数重载(成员或非成员)

具有相同名字而形参表不同的多个函数形成了函数的重载。

注意:仅返回值不同是不能形成函数重载的。首先,因为调用函数时还尚不清楚它的返回值究竟是什么类型(这是书本的说法);但这只是表象,其实只有返回值不同的多个函数根本就通不过编译,何来调用一说。看过第二式的同学都知道,编译器在处理函数名时,有一个“mangling”操作,而该操作是以函数名和各个参数的类型为基础的(其中并不包括返回值),所以说只有返回值不同的多个函数经过编译器的处理后依然同名(而参数个数或类型不同则不会同名)。为什么C++可以支持函数重载,就是因为它有函数名的“mangling”操作,通过该操作后,符合重载要求的函数就会获得不同的函数名,而函数名可以得到函数地址,通过函数地址来调用相应的函数。如果有两个相同的函数名,那么编译器同学根本无法确定你要调用的到底是哪个函数,这时候它只有很不开心地提示一个错误了。

还有一个在使用函数时遇到的问题就是,第一个有默认值的参数后面所有的参数必须都有默认值。这主要是为了避免参数传递的二义性。如果符合上述条件,那编译器只需按照参数的声明顺序传递实参就行了;如果不符合该条件编译器君可能就会有小脾气了。例如下面的情况:

voidfunc (int a, int b = 0, int c)

如果调用是这样:func(1,2);参数是传给a,b还是a,c?如果每次都提供3个参数那中间的默认值还有什么意义?你可能说肯定是传给a,c因为传给a,b的话,那c的实参不就没了吗。有默认值又不是不能传参数,谁知道你是不是真的少传了一个参数。编译器君比较单纯,这个复杂的社会它暂时还是无法适应,所以大家还是体谅一下吧。

-----------------------------------------------------------------

在进入操作符重载的具体情况之前,先来个“学前培训”。

操作符的重载主要是为了给我们自定义的类提供方便的操作。大部分操作符可以重载,不能重载的操作符有如下四个:

::      .*      .      ?:

重载操作符的基本语法如下:

返回值类型  operator操作符(参数列表)

需要注意事项如下:

1.  不能创建任何新的操作符

2.  用于内置类型的操作符,其含义不能改变,也不能增加额外的新操作

3.  操作符的优先级、结合性和操作数数目不能改变

4.  除了函数调用操作符operator()之外,重载操作符时使用默认实参是非法的。

5.  经过重载的操作符将失去短路求值特性

6.  若为成员函数,则其隐含的this形参限定为第一个操作数;非类成员而操作类实例时需为友元关系

 

二、算数运算符重载(包括自增/自减)

算数操作符比较多,我就选几个基础的为大家抛砖引玉一下(∩_∩)

例如有如下类:

class A{public:int a;A& operator+(A& right);};

操作符函数定义如下:

A& A::operator +(A& right){this->a += right.a;return *this;}

则可如下调用:

A a1,a2;//定义A的实例对象a1.a = 1;a2.a = 2;a1 = a1 + a2;//a1调用operator+函数并将a2作为参数

后置++操作符的定义(假设已在类A中声明):

A  A::operator ++(int)//int形参主要是为了和前自增相区别{A temp(*this);++this->a;return temp;}//后置时应返回旧值

调用:

A a1;a1.a = 1;a1++;

前置++操作符的定义(假设已在类A中声明):

A& A::operator ++(){this->a++;return *this;}//为了与内置类型一致,返回该类类型的引用

调用:

A a1;a1.a = 1;++a1;


三、输入/输出操作符重载

为了与IO标准库一致,操作符应该接受istream/ostream的引用作为第一个形参,对类类型const对象的引用作为第二个形参,并返回对应形参的引用。

接着用上面的类,定义如下函数:

istream& operator>>(istream& in, A& one){in>>one.a;return in;}

可如下调用:

A a1;cin>>a1;

输出操作符定义如下:

ostream& operator<<(ostream& out, A& one){out<<one.a<<endl;return out;}

可如下调用:

cout<<a1;

重载输入/输出操作符后就可以在输入/输出时自定义相应的操作,用起来真的很方便呢。


四、强制类型转换操作符重载

该重载函数没有返回值(其实已经隐含在函数名中),没有形参,形式如下:

operatortype();

只要type是一个类型,包括基本数据类型,我们定义的类或者结构体都可以进行相应的转换。

接着用上面的类定义,假设已声明该成员函数,定义如下:

A::operator int(){return this->a;}

可进行如下的调用:

A a1;a1.a = 6;int i = (int)a1;//调用我们定义的类型转换函数

当然也可以定义其它类型的转换,如果你喜欢的话,但是我们就不一一展示了哟。


五、下标操作符重载

首先,下标操作符必须定义为类成员函数;其次,类定义下标操作符时,一般需要定义两个版本(可以由const或非const对象来调用)。一个为非const成员并返回引用,另一个为const成员并返回const引用。

我做如下类定义:

class A{public:vector<int> data;int& operator[](const size_t);const int& operator[](const size_t)const;};

实现相应的操作符函数:

int& A::operator[](const size_t index){return data[index];}const int& A::operator[](const size_t index)const{return data[index];}

则可实现如下调用:

A a1;a1.data.push_back(1);a1.data.push_back(2);a1.data.push_back(3);cout<<a1[1]<<endl;//这里调用了重载操作符(非const版本)

六、函数调用操作符重载

函数调用操作符也可以用于重载(即括号操作符),且必须声明为成员函数。一个类可以定义多个版本的函数调用操作符,由形参的个数或类型加以区别。

定义了调用操作符的类,其对象成为函数对象,在STL中有较多的应用。

接着用上面定义的类,添加成员函数,定义如下:

void A::operator()()//第一个括号是被重载的操作符,第二个是形参列表{cout<<"Hello world!"<<endl;}//简单地输出信息

可以这样调用:

A()();//第一个括号产生对象,第二个是调用重载的操作符函数

或者:

A  a;a();

还是挺好玩的吧(*^-^*)

 

最后,有些操作符是必须定义为成员函数的,有些则不然。那对于那些可以定义为非成员函数的情况,我们如何确定何时定义为成员呢?我个人还是倾向于不管三七二十一把它定义为成员函数,因为通常的操作都会涉及类中的成员变量,封装性好的类定义是不会允许外面的函数随意访问成员变量的。当然只要符合使用习惯,即使定义为非成员函数也是无伤大雅的。

还有一种是不能定义为成员的情况,这种情况是使用友元的典型场景:我们无法得到操作符第一形参的类定义(因为成员函数的this指针将绑定到第一个形参),而又需要通过它来操作我们的类成员(例如输入/输出操作符)。

 















1 0
原创粉丝点击