C++重载的奥义之运算符重载
0、引言
重载,顾名思义从字面上理解就是重复装载,打一个不恰当的比方,你可以用一个篮子装蔬菜,也可以装水果或者其它,使用的是同一个篮子,但是可以用篮子重复装载的东西不一样。
正如在之前的文章《重载的奥义之函数重载》中介绍的类似,函数的重载是指利用相同的函数名设计一系列功能相近,但是功能细节不一样的函数接口;因此运算符重载也是指对于同一个运算符来说,它可以用于实现不同的功能。下面就一起来理解下运算符重载的应用。
1、运算符重载定义
正常来说,我们一般使用的运算符是对基本的数据类型进行操作,但是在C++中有了对象,导致对象无法通过运算符进行运算,故引入了运算符重载即需要重新的定义这些运算符,赋予已有运算符新的功能,使它能够用于特定类型执行特定的操作。运算符重载的实质是函数重载,它提供了C++的可扩展性。
运算符重载是通过创建运算符函数实现的,运算符函数定义了重载的运算符将要进行的操作。运算符函数的定义与其他函数的定义类似,唯一的区别是运算符函数的函数名是由关键字operator和其后要重载的运算符符号构成的。运算符函数定义的一般格式如下:
- 1 <返回类型说明符> operator <运算符符号>(<参数表>)
- 2 {
- 3 <函数体>
- 4 }
其中,“返回类型说明符”指出重载运算符的返回值类型,operator是定义运算符重载函数的关键字,“运算符符号”指出要重载的运算符名字,是C++中可重载的运算符,比如要重载加法运算符,这里直接写“+”即可,“参数表”指出重载运算符所需要的参数及其类型。可以看出,运算符重载是一种形式C++多态的体现。
例如,使用“+”将两个对象相加,编译器将根据操作数的数目和类型决定使用哪种加法定义,这样可以让代码看起来更加自然。
- 1 //正常情况下两个数组的数相加
- 2 for(int i= 0; i<10; i++)
- 3 c[i] = a[i] + b[i];
- 4 //可以通过定义一个数组的类,重载“+”运算符后
- 5 //隐藏了内部机制,并强调了实质
- 6 arry operator+(arry p,arry q)
- 7 {
- 8 arry t;
- 9 for(int i= 0; i<10; i++) //c = a + b;
- 10 {
- 11 t.a[i]=p.a[i]+q.a[i];
- 12 }
- 13 return t;
- 14 }
运算符重载就是对已有的运算符重新进行定义,赋予其另一种功能,以达到适应不同的数据类型。运算符重载不能改变它本来的寓意(也就是加法不能变更为减法),运算符重载只是一种 “语法上的方便” ,它只是一种函数调用的方式。
2、作为成员函数进行重载
我们就以“+”运算符重载举例:
- 1 #include <iostream>
- 2 using namespace std;
- 3 class addfloat
- 4 {
- 5 public:
- 6 addfloat(float p);
- 7 //声明运算符重载
- 8 addfloat operator+(const addfloat &A) const;
- 9 void show() const;
- 10 private:
- 11 float m_p;
- 12 };
- 13 addfloat::addfloat(float p)
- 14 {
- 15 m_p = p;
- 16 }
- 17 //作为类的成员函数实现运算符重载
- 18 addfloat addfloat::operator+(const addfloat &A) const
- 19 {
- 20 addfloat B;
- 21 B.m_p = this->m_p + A.m_p;
- 22 return B;
- 23 }
- 24 void addfloat::show() const
- 25 {
- 26 cout<<"输出结果是"<<m_p<<endl;
- 27 }
- 28
- 29
- 30 int main()
- 31 {
- 32 addfloat m(5.1);
- 33 addfloat n(1.5);
- 34 addfloat t;
- 35 t = m + n; //两个addfloat类对象相加:t = m.operator+(n);
- 36 t.show();
- 37 return 0;
- 38 }
运行结果为:
- 1 输出结果是6.6
从上面的例子可以看出,在addfloat类中对“+”运算符进行了重载 ,重载后可以对该类的对象进行加法运算。当运行 t = m + n时,编译器检测到“+”左边的m(“+”具有左结合性,所以先检测左边)是一个 addfloat类对象,就会调用成员函数 operator+(),将表达式转换成如下格式:
t = m.operator + (n);
表达式中m作为调用函数的对象,n作为函数的实参。
3、作为全局函数进行重载
对于之前的例子:t = m + n,m和n是作为addfloat类的对象进行相加的,使用成员函数 operator+()转换为了t = m.operator+(n),如果n不是类的对象,而是一个常数,例如:
t = m + 5.2;那么可以转换t = m.operator+(5.2);
但是如果m是一个常数时,即:t = 5.2 + n;则t = (5.2).operator + (n)这种转换是不允许的,编译器会报错,因为5.2不能作为类的对象调用运算符重载operator+()函数。
这种场景下针对“+”这种运算符作为类的成员函数进行重载是不可以的。运算符重载不仅仅可以通过类的成员函数来实现,也可以通过全局函数来实现。
我们需要将运算符重载的全局函数声明为友元函数,因为大多数时候重载运算符要访问类的私有数据,(当然也可以设置为非友元非类的成员函数,但是非友元又不是类的成员函数,是没有办法直接访问类的私有数据的),如果不声明为类的友元函数,而是通过在此函数中调用类的公有函数来访问私有数据会降低性能。所以一般都会设置为类的友元函数,这样我们就可以在此非成员函数中访问类中的数据了。
- 1 #include <iostream>
- 2 using namespace std;
- 3 class addfloat
- 4 {
- 5 public:
- 6 addfloat(float p);
- 7 //声明为友元函数
- 8 friend addfloat operator+(const addfloat &A, const addfloat &B);
- 9 void show() const;
- 10 private:
- 11 float m_p;
- 12 };
- 13 addfloat::addfloat(float p)
- 14 {
- 15 m_p = p;
- 16 }
- 17
- 18 void addfloat::show() const
- 19 {
- 20 cout<<"输出结果是"<<m_p<<endl;
- 21 }
- 22
- 23 //作为全局函数进行重载
- 24 addfloat operator+(const addfloat &A, const addfloat &B)
- 25 {
- 26 addfloat C;
- 27 C.m_p = A.m_p + B.m_p;
- 28 return C;
- 29 }
- 30
- 31 int main()
- 32 {
- 33 addfloat m(5.1);
- 34 addfloat n(1.5);
- 35 addfloat t;
- 36 t = m + n; //两个addfloat类对象相加:t = m.operator+(n);
- 37 t.show();
- 38 return 0;
- 39 }
由上述程序可以看出,运算符重载函数operator+()不是 addfloat类的成员函数,但是却用到了 addfloat类的 private 成员变量m_p,所以需要在 addfloat类中将operator+()函数声明为友元函数。
当运行t = m + n时,编译器检测到“+”两边都是addfloat类的对象,就会转换为类似下面的函数调用:
t = operator + (m, n);
因此,m和n都可以看作是函数的实参:
t = m + 5.2转换为 t = operator + (m, 5.2);
t = 5.2 + n转换为 t = operator + (5.2, n);
以全局函数的形式重载“+”,是为了保证“+”运算符的操作数能够被对称的处理;换句话说,常数在“+”左边和右边都是正确的;
因此,运算符左右两边都有操作对象时,且这两个操作对象可以互换,最好可以使用全局函数的形式重载,例如:+、-、*、/、==、!= ,这些符合运算符两边有操作对象的运算符。
4、运算符重载的一些规则
(1)可以重载的运算符
(2)不可以重载的运算符
. (成员访问运算符)
.* (成员指针访问运算符)
:: (域运算符)
sizeof (长度运算符)
?: (条件运算符)
(3) 只能以成员函数的形式重载的运算符(与 this关联太多)
= (赋值运算符)
() (函数调用运算符)
[] (下标运算符)
-> (成员访问运算符)
(4)只能以全局函数重载的运算符
<< (左移运算符)
>> (右移运算符)
(5)运算符重载函数既可以作为类的成员函数,也可以作为全局函数。友元函数运算符重载函数与成员运算符重载函数的区别是:友元函数没有this指针,而成员函数有,因此,在两个操作数的重载中友元函数有两个参数,而成员函数只有一个。
(6)有一部分运算符重载既可以是成员函数也可以是全局函数,虽然没有一个必然的、不可抗拒的理由选择成员函数,但我们应该优先考虑成员函数,这样更符合运算符重载的初衷。
(7)对于复合的赋值运算符如 +=、-=、*=、/=、&=、!=、~=、%=、>>=、<<= 建议重载为成员函数;
单目运算符最好重载为成员函数;
对于其它运算符,建议重载为全局函数。
(8)使用运算符不能违反运算符原来的语法规则,原来有几个操作数、操作数在左边还是在右边,这些都不会改变。算符重载函数不能有默认的参数,否则就改变了运算符操作数的个数。
(9)运算符的优先级不能被重载改变。然而,圆括号能够强制改变表达式中重载运算符的求值顺序。
(10)运算符的结合性不能被重载改变。如果一个运算符的结合性是从左向右,那么,它的所有重载的版本的结合性依然是从左向右
(11)不能创造新的运算符,即只能重载现有的运算符。例如不能定义operator** (···)来表示求幂。
(12)重载的运算符必须和用户定义的对象一起使用,运算符参数(操作的对象)中至少应有一个是类对象(或类对象的引用)。
更多技术内容和书籍资料获取,入群技术交流敬请关注“明解嵌入式”
0、引言 重载,顾名思义从字面上理解就是重复装载,打一个不恰当的比方,你可以用一个篮子装蔬菜,也可以装水果或者其它,使用的是同一个篮子,但是可以用篮子重复装载的东西不一样。
正如在之前的文章《重载的奥义之函数重载》中介绍的类似,函数的重载是指利用相同的函数名设计一系列功能相近,但是功能细节不一样的函数接口;因此运算符重载也是指对于同一个运算符来说,它可以用于实现不同的功能。下面就一起来理解下运算符重载的应用。
1、运算符重载定义 正常来说,我们一般使用的运算符是对基本的数据类型进行操作,但是在C++中有了对象,导致对象无法通过运算符进行运算,故引入了运算符重载即需要重新的定义这些运算符,赋予已有运算符新的功能,使它能够用于特定类型执行特定的操作。运算符重载的实质是函数重载,它提供了C++的可扩展性。
运算符重载是通过创建运算符函数实现的,运算符函数定义了重载的运算符将要进行的操作。运算符函数的定义与其他函数的定义类似,唯一的区别是运算符函数的函数名是由关键字operator和其后要重载的运算符符号构成的。运算符函数定义的一般格式如下:
<返回类型说明符> operator <运算符符号>(<参数表>){ <函数体>}1.2.3.4. 其中,“返回类型说明符”指出重载运算符的返回值类型,operator是定义运算符重载函数的关键字,“运算符符号”指出要重载的运算符名字,是C++中可重载的运算符,比如要重载加法运算符,这里直接写“+”即可,“参数表”指出重载运算符所需要的参数及其类型。可以看出,运算符重载是一种形式C++多态的体现。
例如,使用“+”将两个对象相加,编译器将根据操作数的数目和类型决定使用哪种加法定义,这样可以让代码看起来更加自然。
//正常情况下两个数组的数相加for(int i= 0; i<10; i++) c[i] = a[i] + b[i];//可以通过定义一个数组的类,重载“+”运算符后//隐藏了内部机制,并强调了实质arry operator+(arry p,arry q){ arry t; for(int i= 0; i<10; i++) //c = a + b; { t.a[i]=p.a[i]+q.a[i]; } return t;}1.2.3.4.5.6.7.8.9.10.11.12.13.14. 运算符重载就是对已有的运算符重新进行定义,赋予其另一种功能,以达到适应不同的数据类型。运算符重载不能改变它本来的寓意(也就是加法不能变更为减法),运算符重载只是一种 “语法上的方便” ,它只是一种函数调用的方式。
2、作为成员函数进行重载 我们就以“+”运算符重载举例:
#include <iostream>using namespace std;class addfloat{public: addfloat(float p); //声明运算符重载 addfloat operator+(const addfloat &A) const; void show() const;private: float m_p; };addfloat::addfloat(float p){ m_p = p;}//作为类的成员函数实现运算符重载addfloat addfloat::operator+(const addfloat &A) const{ addfloat B; B.m_p = this->m_p + A.m_p; return B;}void addfloat::show() const{ cout<<"输出结果是"<<m_p<<endl;}
int main(){ addfloat m(5.1); addfloat n(1.5); addfloat t; t = m + n; //两个addfloat类对象相加:t = m.operator+(n); t.show(); return 0;}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38. 运行结果为:
输出结果是6.61. 从上面的例子可以看出,在addfloat类中对“+”运算符进行了重载 ,重载后可以对该类的对象进行加法运算。当运行 t = m + n时,编译器检测到“+”左边的m(“+”具有左结合性,所以先检测左边)是一个 addfloat类对象,就会调用成员函数 operator+(),将表达式转换成如下格式:
t = m.operator + (n);
表达式中m作为调用函数的对象,n作为函数的实参。
3、作为全局函数进行重载 对于之前的例子:t = m + n,m和n是作为addfloat类的对象进行相加的,使用成员函数 operator+()转换为了t = m.operator+(n),如果n不是类的对象,而是一个常数,例如:
t = m + 5.2;那么可以转换t = m.operator+(5.2);
但是如果m是一个常数时,即:t = 5.2 + n;则t = (5.2).operator + (n)这种转换是不允许的,编译器会报错,因为5.2不能作为类的对象调用运算符重载operator+()函数。
这种场景下针对“+”这种运算符作为类的成员函数进行重载是不可以的。运算符重载不仅仅可以通过类的成员函数来实现,也可以通过全局函数来实现。
我们需要将运算符重载的全局函数声明为友元函数,因为大多数时候重载运算符要访问类的私有数据,(当然也可以设置为非友元非类的成员函数,但是非友元又不是类的成员函数,是没有办法直接访问类的私有数据的),如果不声明为类的友元函数,而是通过在此函数中调用类的公有函数来访问私有数据会降低性能。所以一般都会设置为类的友元函数,这样我们就可以在此非成员函数中访问类中的数据了。
#include <iostream>using namespace std;class addfloat{public: addfloat(float p); //声明为友元函数 friend addfloat operator+(const addfloat &A, const addfloat &B); void show() const;private: float m_p; };addfloat::addfloat(float p){ m_p = p;}
void addfloat::show() const{ cout<<"输出结果是"<<m_p<<endl;}
//作为全局函数进行重载addfloat operator+(const addfloat &A, const addfloat &B){ addfloat C; C.m_p = A.m_p + B.m_p; return C;}
int main(){ addfloat m(5.1); addfloat n(1.5); addfloat t; t = m + n; //两个addfloat类对象相加:t = m.operator+(n); t.show(); return 0;}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39. 由上述程序可以看出,运算符重载函数operator+()不是 addfloat类的成员函数,但是却用到了 addfloat类的 private 成员变量m_p,所以需要在 addfloat类中将operator+()函数声明为友元函数。
当运行t = m + n时,编译器检测到“+”两边都是addfloat类的对象,就会转换为类似下面的函数调用:
t = operator + (m, n);
因此,m和n都可以看作是函数的实参:
t = m + 5.2转换为 t = operator + (m, 5.2);
t = 5.2 + n转换为 t = operator + (5.2, n);
以全局函数的形式重载“+”,是为了保证“+”运算符的操作数能够被对称的处理;换句话说,常数在“+”左边和右边都是正确的;
因此,运算符左右两边都有操作对象时,且这两个操作对象可以互换,最好可以使用全局函数的形式重载,例如:+、-、*、/、==、!= ,这些符合运算符两边有操作对象的运算符。
4、运算符重载的一些规则(1)可以重载的运算符
(2)不可以重载的运算符
. (成员访问运算符)
.* (成员指针访问运算符)
:: (域运算符)
sizeof (长度运算符)
?: (条件运算符)
(3) 只能以成员函数的形式重载的运算符(与 this关联太多)
= (赋值运算符)
() (函数调用运算符)
[] (下标运算符)
-> (成员访问运算符)
(4)只能以全局函数重载的运算符
<< (左移运算符)
>> (右移运算符)
(5)运算符重载函数既可以作为类的成员函数,也可以作为全局函数。友元函数运算符重载函数与成员运算符重载函数的区别是:友元函数没有this指针,而成员函数有,因此,在两个操作数的重载中友元函数有两个参数,而成员函数只有一个。
(6)有一部分运算符重载既可以是成员函数也可以是全局函数,虽然没有一个必然的、不可抗拒的理由选择成员函数,但我们应该优先考虑成员函数,这样更符合运算符重载的初衷。
(7)对于复合的赋值运算符如 +=、-=、*=、/=、&=、!=、~=、%=、>>=、<<= 建议重载为成员函数;
单目运算符最好重载为成员函数;
对于其它运算符,建议重载为全局函数。
(8)使用运算符不能违反运算符原来的语法规则,原来有几个操作数、操作数在左边还是在右边,这些都不会改变。算符重载函数不能有默认的参数,否则就改变了运算符操作数的个数。
(9)运算符的优先级不能被重载改变。然而,圆括号能够强制改变表达式中重载运算符的求值顺序。
(10)运算符的结合性不能被重载改变。如果一个运算符的结合性是从左向右,那么,它的所有重载的版本的结合性依然是从左向右
(11)不能创造新的运算符,即只能重载现有的运算符。例如不能定义operator** (···)来表示求幂。
(12)重载的运算符必须和用户定义的对象一起使用,运算符参数(操作的对象)中至少应有一个是类对象(或类对象的引用)。
更多技术内容和书籍资料获取,入群技术交流敬请关注“明解嵌入式”
-----------------------------------著作权归作者所有:来自51CTO博客作者Sharemaker的原创作品,请联系作者获取转载授权,否则将追究法律责任C++重载的奥义之运算符重载https://blog.51cto.com/Sharemaker/6196336
C++重载的奥义之运算符重载的更多相关文章
- C++:成员运算符重载函数和友元运算符重载函数的比较
5.2.4 成员运算符重载函数和友元运算符重载函数的比较 (1)对双目运算符而言,成员运算符重载函数参数表中含有一个参数,而友元运算符重载函数参数表中有两个参数:对于单目运算符而言,成员运算符重载函数 ...
- C++:运算符重载函数之成员运算符重载函数
5.2.3 成员运算符重载函数 在C++中可以把运算符重载函数定义为某个类的成员函数,称之为成员运算符重载函数. 1. 定义成员运算符重载函数的语法形式 (1)在类的内部,定义成员运算符重载函数的格式 ...
- C++:运算符重载函数之友元运算符重载
5.2.2 友元运算符重载函数 运算符重载函数一般采用两种形式定义: 一是定义为它将要操作的类的成员函数(简称运算符重载函数): 二是定义为类的友元函数(简称为友元运算符重载函数). 1.定义友元运算 ...
- C++学习之路—运算符重载(二)运算符重载作为类的成员函数和友元函数
(根据<C++程序设计>(谭浩强)整理,整理者:华科小涛,@http://www.cnblogs.com/hust-ghtao转载请注明) 对运算符重载的函数有两种处理方式:(1)把运算符 ...
- 从零开始学C++之运算符重载(三):完善String类([]、 +、 += 运算符重载)、>>和<<运算符重载
在前面文章中使用过几次String类的例子,现在多重载几个运算符,更加完善一下,并且重载流类运算符. []运算符重载 +运算符重载 +=运算符重载 <<运算符重载 >>运算符重 ...
- 完善String类([]、 +、 += 运算符重载)、>>和<<运算符重载
在前面文章中使用过几次String类的例子,现在多重载几个运算符,更加完善一下,并且重载流类运算符. []运算符重载 +运算符重载 +=运算符重载 <<运算符重载 >>运算符重 ...
- C++ 运算符重载一(二元运算符重载)
//二元运算符重载 #include<iostream> using namespace std; class Point { public: Point(int x,int y){ th ...
- 我的c++学习(8)运算符重载和友元
运算符的重载,实际是一种特殊的函数重载,必须定义一个函数,并告诉C++编译器,当遇到该运算符时就调用此函数来行使运算符功能.这个函数叫做运算符重载函数(常为类的成员函数). 方法与解释 ◆ 1.定义运 ...
- C++:运算符重载函数
5.运算符重载 5.1 在类外定义的运算符重载函数 C++为运算符重载提供了一种方法,即在运行运算符重载时,必须定义一个运算符重载函数,其名字为operator,后随一个要重载的运算符.例如,要重载& ...
- C++之运算符重载(2)
上一节主要讲解了C++里运算符重载函数,在看了单目运算符(++)重载的示例后,也许有些朋友会问这样的问题.++自增运算符在C或C++中既可以放在操作数之前,也可以放在操作数之后,但是前置和后置的作用又 ...
随机推荐
- windows 获取USB,发现安卓设备,转载自www.jb51.net/article/164456.htm
转载 作者:jgw2008 import win32com.client def CheckDev(): wmi = win32com.client.GetObject ("winmgmts ...
- obj文件格式解读
学习了很长一段时间的建模,obj文件一直都在使用,但是却很少去研究过,只是知道这是软件之间的通用格式,直到最近因为刚好要在python中加载obj文件,才发现原来obj文件是如此的有规律 随便用记事本 ...
- JS this指向相关
function Foo() { getName = function() { console.log(1) } return this;}Foo.getName = function() { con ...
- Linux env commands
1.新机新增root 密码 sudo passwd root 2.新增用户密码 sudo passwd YOUR_USER_NAME NEW PW: NEW PW: 3.SSH Server sudo ...
- Windows软件堆栈溢出(stack overflow)的一些解决方法
欢迎访问我的个人博客:xie-kang.com 原文地址 Windows平台下,有三种方法调整堆栈大小(适用于MSVC编译器): 1)添加编译参数:/F size #其中size的单位是byte可设 ...
- python中时间的datatime的模块
datetime.datetime.now().strftime('%Y-%m-%d-%H_%M_%S')1.python datetime模块用strftime 格式化时间 import datet ...
- C#开发微信
C#开发微信门户及应用教程 C#开发微信门户及应用(1)--开始使用微信接口... 6 1.微信账号... 6 2.微信菜单定义... 7 3.接入微信的链接处理... 8 4.使用开发方式创建菜 ...
- java的maven项目打包成.exe可执行文件
打包exe可执行脚本: 1.源代码maven项目写完后打包成可执行jar包,此处我使用的是assembly插件. <plugin> <groupId>org.apache.ma ...
- Android--观察APP运行日志以及APP的工程目录结构解释
运行日志 Log:d--便于跟踪调试 APP开发基础 APP的运行环境 第一种情况,就是在Android studio软件客户端上面使用模拟器运行APP 第二种情况,就是使用真实的手机运行APP程序 ...
- 操作系统 && C语言 每日学习记录(day1 ~ day8) 已寄
现在正式工作了,发现之前学的东西,很多一知半解,不通透,准备再好好系统学一些计算机原理的东西,每天学一学,在这里记录一下. 规划(7.17开始): 同学分享了个超级好的操作系统课程,每天看个一节:ht ...