c++运算符重载

BS老师的书看不懂,于是再网上看了几篇相关技术文章.当然我也就我的理解和遇到的问题做了相关解释和说明.

C++中预定义的运算符的操作对象只能是基本数据类型。但实际上,对于许多用户自定义类型(例如类),也需要类似的运算操作。这时就必须在C++中重新定义这些运算符,赋予已有运算符新的功能,使它能够用于特定类型执行特定的操作。运算符重载的实质是函数重载,它提供了C++的可扩展性.

运算符重载是通过创建运算符函数实现的,运算符函数定义了重载的运算符将要进行的操作。运算符函数的定义与其他函数的定义类似,惟一的区别是运算符函数的函数名是由关键字operator和其后要重载的运算符符号构成的。

运算符重载时要遵循以下规则:

   (1) 除了类属关系运算符"."、成员指针运算符".*"、作用域运算符"::"、sizeof运算符和三目运算符"?:"以外,C++中的所有运算符都可以重载。

在这里要提醒读者的是,自定义类的运算符重载函数也是函数,你重载的一切运算符不会因为是你自己定义的就改变其运算的优先级,自定义运算符的运算优先级同样遵循与内部运算符一样的顺序。

(2) 重载运算符限制在C++语言中已有的运算符范围内的允许重载的运算符之中,不能创建新的运算符。


(
3) 运算符重载实质上是函数重载,因此编译程序对运算符重载的选择,遵循函数重载的选择原则。

(
4) 重载之后的运算符不能改变运算符的优先级和结合性,也不能改变运算符操作数的个数及语法结构。

(
5) 运算符重载不能改变该运算符用于内部类型对象的含义。它只能和用户自定义类型的对象一起使用,或者用于用户自定义类型的对象和内部类型的对象混合使用时。

(
6) 运算符重载是针对新类型数据的实际需要对原有运算符进行的适当的改造,重载的功能应当与原有功能相类似,避免没有目的地使用重载运算符。

运算符函数重载一般有两种形式:重载为类的成员函数和重载为类的非成员函数。非成员函数通常是友元。(可以把一个运算符作为一个非成员、非友元函数重载。但是,这样的运算符函数访问类的私有和保护成员时,必须使用类的公有接口中提供的设置数据和读取数据的函数,调用这些函数时会降低性能。可以内联这些函数以提高性能。)

成员函数运算符

1 运算符重载为类的成员函数的一般格式为:
2 
3     <函数类型> operator <运算符>(<参数表>)
4 
5     {
6 
7      <函数体>
8 
9     }

友元函数运算符

1 运算符重载为类的友元函数的一般格式为:
2 
3     friend <函数类型> operator <运算符>(<参数表>)
4 
5     {
6 
7      <函数体>
8 
9     }

当运算符重载为类的友元函数时,由于没有隐含的this指针,因此操作数的个数没有变化,所有的操作数都必须通过函数的形参进行传递,函数的参数与操作数自左至右一一对应。

1 调用友元函数运算符的格式如下:
2 
3     operator <运算符>(<参数1>,<参数2>)
4 
5     它等价于
6 
7     <参数1><运算符><参数2>
8 
9     例如:a+b等价于operator +(a,b)。

两种重载形式的比较

  在多数情况下,将运算符重载为类的成员函数和类的友元函数都是可以的。但成员函数运算符与友元函数运算符也具有各自的一些特点:

   (1) 一般情况下,单目运算符最好重载为类的成员函数;双目运算符则最好重载为类的友元函数。

(
2) 以下一些双目运算符不能重载为类的友元函数:=、()、[]、->

(
3) 类型转换函数只能定义为一个类的成员函数而不能定义为类的友元函数。

(
4) 若一个运算符的操作需要修改对象的状态,选择重载为成员函数较好。

(
5) 若运算符所需的操作数(尤其是第一个操作数)希望有隐式类型转换,则只能选用友元函数。

(
6) 当运算符函数是一个成员函数时,最左边的操作数(或者只有最左边的操作数)必须是运算符类的一个类对象(或者是对该类对象的引用)。如果左边的操作数必须是一个不同类的对象,或者是一个内部 类型的对象,该运算符函数必须作为一个友元函数来实现。

(
7) 当需要重载运算符具有可交换性时,选择重载为友元函数。

 1 //程序作者:管宁      
 2 //站点:www.cndev-lab.com      
 3 //所有稿件均有版权,如要转载,请务必著名出处和作者     
 4   
 5 #include <iostream>  
 6 using namespace std;  
 7   
 8 class Test  
 9 {  
10     public:  
11         Test(int a = 0)  
12         {  
13             Test::a = a;  
14         }  
15         friend Test operator +(Test&,Test&);  
16         friend Test& operator ++(Test&);  
17     public:  
18         int a;  
19 };  
20 Test operator +(Test& temp1,Test& temp2)//+运算符重载函数  
21 {  
22     //cout<<temp1.a<<"|"<<temp2.a<<endl;//在这里可以观察传递过来的引用对象的成员分量  
23     Test result(temp1.a+temp2.a);  
24     return result;  
25 }  
26 Test& operator ++(Test& temp)//++运算符重载函数  
27 {  
28     temp.a++;  
29     return temp;  
30 }  
31 int main()  
32 {  
33     Test a(100);  
34     Test c=a+a;  
35     cout<<c.a<<endl;  
36     c++;  
37     cout<<c.a<<endl;  
38     system("pause");  
39 }

如上运行管宁的代码时 发现报错:

 错误: 没有为后缀‘++’声明‘operator++(int)’ [-fpermissive]

运算符++和--有前置和后置两种形式,如果不区分前置和后置,则使用operator++( )或operator--( )即可;否则,要使用operator++( )或operator--( )来重载前置运算符,使用operator++(int)或operator--(int)来重载后置运算符,调用时,参数int被传递给值0。但是他的重载是++x,并没有重载x++,所以正确代码把36行改为:++c;就可以了.

用的gcc system("pause")

未编译通过. 查看了下system包含于头文件 #include<cstdlib>.

Things to Avoid in C/C++ -- system("pause"), Part 4 中 在C++中不提倡使用这种原因如下解释

Reasons:

  • It's not portable. This works only on systems that have the PAUSE command at the system level, like DOS or Windows. But not Linux and most others...(hhb翻译:不方便[不可移植],system("pause")只能在如DOS或者windows系统这样的有中断命令的系统中才起作用[这仅适用于系统暂停在系统级的命令,如DOS或Windows],而linux或者别的系统则不可行)

  • It's a very expensive and resource heavy function call. It's like using a bulldozer to open your front door. It works, but the key is cleaner, easier, cheaper. What system() does is:(是一个代价昂贵消耗很多资源的功能性调用,就好像用挖土机打开你家大门,的确可以打开你的家门,但钥匙是不是更方便直接简单呢,.让我们来看看 system()都干了啥)
    1. suspend your program (中断你的程序)

    2. call the operating system(调用操作系统)

    3. open an operating system shell (relaunches the O/S in a sub-process)(打开操作系统的 shell即操作系统的一个子进程[用一个子进程重启操作系统进程?])

    4. the O/S must now find the PAUSE command(操作系统必定要发现这个中断指令)

    5. allocate the memory to execute the command(分内存来执行指令)

    6. execute the command and wait for a keystroke(执行完执行等待键盘输入)

    7. deallocate the memory(释放内存)

    8. exit the OS(退出操作系统)

    9. resume your program(再继续你的程序)

    There are much cleaner ways included in the language itself that make all this unnessesary.(在C++语言中有更多方便直接的方法可以去除这些不必要)

  • You must include a header you probably don't need: stdlib.h or cstdlib(另外你还要加一个头文件 stdlib.h或者cstdlib哦)

It's a bad habit you'll have to break eventually anyway.(你将不得不突然无缘无故中断,这真是个很糟糕的习惯.)

Instead, use the functions that are defined natively in C/C++ already. So what is it you're trying to do? Wait for a key to be pressed? Fine -- that's called input. So in C, use getchar() instead. In C++, how aboutcin.get()? All you have to do is press RETURN and your program continues.

(其实,使用在C/C++语言中本身已经定义的功能就可以了.为什么不尝试下呢?是要等一个键盘输入?好吧,其实就是输入!在C中咱可以使用getchar()代替,在c++中cin,get()也是可以的,所有的你要做的就是敲下回车你的程序就继续运行了.)

2.

 1 #include <iostream>  
 2 using namespace std;  
 3   
 4 class Test  
 5 {  
 6     public:  
 7         Test(int a = 0)  
 8         {  
 9             Test::a = a;  
10         }  
11         friend Test operator +(Test&,const int&);  
12     public:  
13         int a;  
14 };  
15 Test operator +(Test& temp1,const int& temp2)//+运算符重载函数  
16 {  
17     Test result(temp1.a * temp2);  
18     return result;  
19 }  
20 int main()  
21 {  
22     Test a(100);  
23     Test c = a + 10;  
24     cout<<c.a<<endl;  
25    cin.get();
26 }

对于运算符重载来说,我们并不一定要用它一定要做同类型对象的加法或者是其它运算,运算符重载函数本身就是函数,那么在函数体内部我们是可以做任何事情的,但是从不违背常规思维的角度来说,我们没有必要让重载加运算的函数来做与其重载的符号意义上完全不相符的工作,所以在使用重载运算符脱离原意之前,必须保证有足够的理由。

 1 //程序作者:管宁      
 2 //站点:www.cndev-lab.com      
 3 //所有稿件均有版权,如要转载,请务必著名出处和作者     
 4   
 5 #include <iostream>  
 6 using namespace std;  
 7   
 8 class Test  
 9 {  
10     public:  
11         Test(int a = 0)  
12         {  
13             Test::a = a;  
14         }  
15         Test(Test &temp)  
16         //运算符重载函数为值返回的时候会产生临时变量,临时变量与局部变量result的复制会调用拷贝构造函数,临时变量的生命周期是在拷贝构造函数运行完成后才结束,但如果运算符重载函数返回的是引用,那么不会产生临时变量,而局部变量result的生命周期在运算符重载函数退出后立即消失,它的生命周期要比临时变量短,所以当外部对象获取返回值的内存地址所存储的值的时候,获得是一个已经失去效果的内存地址中的值,在这里的值返回与引用返回的对比,证明了临时变量的生命周期比局部变量的生命周期稍长。  
17         {  
18             cout<<"载入拷贝构造函数"<<"|"<<temp.a<<endl;//注意这里,如果修改运算符重载函数为返回引用,这里就会出现异常,temp.a将获得一个随机值。  
19             Test::a = temp.a;  
20         }  
21         ~Test()//在mian()内析构的过程是result局部变量产生的  
22         {  
23             cout<<"载入析构函数!"<<endl;  
24             cin.get();  
25         }  
26         Test operator +(Test& temp2)//+运算符重载函数  
27         {  
28             //cout<<this->a<<endl;  
29             Test result(this->a+temp2.a);  
30             return result;  
31         }  
32         Test& operator ++()//++运算符重载函数  
33   
34         //递增运算符是单目运算符,使用返回引用的运算符重载函数道理就在于它需要改变自身。  
35         //在前面我们学习引用的单元中我们知道,返回引用的函数是可以作为左值参与运算的,这一点也符合单目运算符的特点。  
36         //如果把该函数改成返回值,而不是返回引用的话就破坏了单目预算改变自身的特点,程序中的++(++c)运算结束后输出c.a,会发现对象c只做了一次递增运算,原因在于,当函数是值返回状态的时候括号内的++c返回的不是c本身而是临时变量,用临时变量参与括号外的++运算,当然c的值也就只改变了一次。  
37         {  
38             this->a++;  
39             return *this;  
40         }  
41     public:  
42         int a;  
43 };  
44   
45 int main()  
46 {  
47     Test a(100);  
48     Test c=a+a;  
49     cout<<c.a<<endl;  
50     c++;  
51     cout<<c.a<<endl;  
52     ++c;  
53     cout<<c.a<<endl;  
54     ++(++c);  
55     cout<<c.a<<endl;  
56     system("pause");  //应改为cin.get()
57 }

出现的错误:

 1 ibm@ubuntu:~$ g++ -Wall 运算符重载.cpp 
 2 运算符重载.cpp: 在函数‘int main(int, char**)’中:
 3 运算符重载.cpp:42:11: 错误: 对‘Test::Test(Test)’的调用没有匹配的函数
 4 运算符重载.cpp:42:11: 附注: 备选是:
 5 运算符重载.cpp:12:3: 附注: Test::Test(Test&)
 6 运算符重载.cpp:12:3: 附注:   no known conversion for argument 1 from ‘Test’ to ‘Test& 7 运算符重载.cpp:8:3: 附注: Test::Test(int)
 8 运算符重载.cpp:8:3: 附注:   no known conversion for argument 1 from ‘Test’ to ‘int 9 运算符重载.cpp:44:3: 错误: 没有为后缀‘++’声明‘operator++(int)’ [-fpermissive]
10 ibm@ubuntu:~$ 

尚未解决~待研究!!-------->在CSDN中得到lm_whales大神的解答解决:

1)
a+a ==>Test operator +(Test& temp2)
返回值 Test类型,是个临时对象,和拷贝构造函数Test(Test &temp)不能匹配。
Test 和 Test & 不匹配,
Test c=a+a; 就产生以下编译错误和提示信息:

运算符重载.cpp: 在函数‘int main(int, char**)’中:
运算符重载.cpp:42:11: 错误: 对‘Test::Test(Test)’的调用没有匹配的函数
运算符重载.cpp:42:11: 附注: 备选是:
运算符重载.cpp:12:3: 附注: Test::Test(Test&)
运算符重载.cpp:12:3: 附注:   no known conversion for argument 1 from ‘Test’ to ‘Test&’
运算符重载.cpp:8:3: 附注: Test::Test(int)
运算符重载.cpp:8:3: 附注:   no known conversion for argument 1 from ‘Test’ to ‘int’

改成

1 Test(const Test &temp)

类型就匹配了。

拷贝构造函数的一般形式就是:
Test(const Test &temp)

2) c++; 需要后缀++运算符
而你的程序没有重载
运算符重载.cpp:44:3: 错误: 没有为后缀‘++’声明‘operator++(int)’ [-fpermissive]整理以下:

后缀++运算符重载的一般形式

1 Test operator ++(int)
2 {
3     // 2.1)可以这么做
4        Test ret(a++);
5     // 2.2)也可以这样 Test ret(a);  ++a; 
6     // 代码其实可以任意写,不过后缀++的语义,是对象自增,返回自增前的值 ...... 
7     // 符合运算符的语义的运算符重载,才是你所需要的。
8      return ret;
9 }

整理一下:

 1 class Test{
 2 //
 3 public:
 4        // Test(int a=0)
 5        // {
 6        //     Test::a=a;//这样可能没有错误,不过很少人这样写。
 7        // }
 8        //多半是这样写: 
 9        Test(int a=0):a(a){}
10        //或者这样写: 
11        //  Test(int a = 0)
12        // {
13        //     this->a = a;//这样是为了区分成员变量和形参,
14        //      一般来说,形参不要和成员变量同名,以免遮蔽同名的成员变量。
15        // }
16 //  拷贝构造函数,一般传递常量引用。    
17 Test(const Test &temp)
18         {
19             cout<<"载入拷贝构造函数"<<"|"<<temp.a<<endl;
20              //Test::a=temp.a; //这里加类名是多余的。
21              a = temp.a; //这样写就成。
22         }
23  ~Test()
24         {
25             cout<<"载入析构函数!"<<endl;
26             cin.get();
27         }
28 Test& operator ++()
29         {
30             this->a++;
31             return *this;
32         }
33 
34 Test& operator ++()
35         {  Test ret(*this);
36             ++ (*this);
37             return ret;
38         }
39 public://一般就用private:了,不过你这里要在main 里用到,马马虎虎,可以这么做。
40        //类的成员变量,多数时候,都是 private 或者protected 的。
41        int a;
42 };
43 int main(int argc,char* argv[])
44 {
45     Test a(100);
46     Test c=a+a;
47     cout<<c.a<<endl;
48     c++;
49     cout<<c.a<<endl;
50     ++c;
51     cout<<c.a<<endl;
52     ++(++c);
53     cout<<c.a<<endl;
54     cin.get();
55     return 0; //C++ 要求带返回值的函数,必须返回一个值。
56 }
57        
 1 #include <iostream>
 2 using namespace std;
 3 
 4 
 5 class Test
 6 {
 7 public:
 8     Test(int a=0)
 9     {
10         Test::a=a;
11     }
12     Test(const Test &temp)
13     {
14         cout<<"载入拷贝构造函数"<<"|"<<temp.a<<endl;
15         Test::a=temp.a;
16     }
17 
18     ~Test()
19     {
20         cout<<"载入析构函数!"<<endl;
21         cin.get();
22     }
23 
24     Test operator +(Test& temp2)
25     {
26         cout<<this->a<<endl;
27         Test result(this->a+temp2.a);
28         return result;
29     }
30     Test& operator ++()
31     {
32         this->a++;
33         return *this;
34     }
35     Test& operator ++(int)
36     {
37         Test ret(a++);
38         return ret;
39     }
40 
41 public:
42     int a;
43 };
44 
45 int main(int argc,char* argv[])
46 {
47     Test a(100);
48     Test c=a+a;
49     cout<<c.a<<endl;
50     c++;
51     cout<<c.a<<endl;
52     ++c;
53     cout<<c.a<<endl;
54     ++(++c);
55     cout<<c.a<<endl;
56     cin.get();
57 }

接下来我们具体分析一下运算符重载函数的值返回与引用返回的差别。 

  当我们把代码中的加运算重载函数修改成返回引用的时候:

        Test& operator +(Test& temp2)//+运算符重载函数    
        {  
            Test result(this->a+temp2.a);    
            return result;    
        }

  执行运算符重载函数返回引用将不产生临时变量,外部的Test c=a+a; 将获得一个局部的,栈空间内存地址位置上的值,而栈空间的特性告诉我们,当函数退出的时候函数体中局部对象的生命周期随之结束,所以保存在该地址中的数据也将消失,当c对象去获取存储在这个地址中的值的时候,里面的数据已经不存在,导致c获得的是一个随机值,所以作为双目运算的加运算符重载函数是不益采用返回引用方式编写的,当然如果一定要返回引用,我们可以在堆内存中动态开辟空间存储数据,但是这么做会导致额外的系统开销,同时也会让程序更难读懂。 


  对于递增运算符来说,它的意义在于能够改变自身,返回引用的函数是可以作为左值参与运算的,所以作为单目运算符,重载它的函数采用返回引用的方式编写是最合适的。 

  如果我们修改递增运算符重载函数为值返回状态的时候,又会出现什么奇怪的现象呢? 

  代码如下:

        Test operator ++()  
        {  
            return this->a++;  
        }

  表面上是发现不出什么特别明显的问题的,但是在main()函数中++(++c);的执行结果却出乎意料,理论上应该是204的值,却只是203,这是为什么呢?

  因为当函数是值返回状态的时候括号内的++c返回的不是c本身而是临时变量,用临时变量参与括号外的++运算,当然c的值也就只改变了一次。结果为203而不是204。 

  对于运算符重载函数来说,最后我们还要注意一个问题,当运算符重载函数的形式参数类型全部为内部类型的时候,将不能重载

 以上.大部分内容抄自http://pcedu.pconline.com.cn/empolder/gj/c/0503/581281_all.html#content_page_1以及http://blog.csdn.net/zgl_dm/article/details/1767201,

原文地址:https://www.cnblogs.com/huhaibo/p/3383398.html