运算符重载三种形式(成员函数,友元函数,普通函数)详解

首先,介绍三种重载方式:

 1 //作为成员函数重载(常见)
 2 class Person{
 3 Private:
 4      string name;
 5      int age;
 6 public:
 7      Person(const char* name, int age):name(name),age(age){}
 8       bool operator<(const Person& b);
 9
10 };
11 bool Person::operator<(const Person& b)
12 {
13 //作为成员函数时,*this即为左操作数a
14 ...
15 }
 1 //作为友元函数重载
 2 class Person{
 3 private:
 4     string name;
 5     int age;
 6 public:
 7     Person(const char* name, int age):name(name),age(age){}
 8      friend bool operator<(const Person& a,const Person& b);
 9
10 };
11 bool operator<(const Person& a,const Person& b)
12 {
13 ...
14 }
 1 //作为普通函数重载(不推荐)
 2 class Person{
 3 public://注意,重载运算符为普通函数时,使用到的类成员必须为public
 4     string name;
 5     int age;
 6 public:
 7     Person(const char* name, int age):name(name),age(age){}
 8
 9 };
10 bool operator<(const Person& a,const Person& b)
11 {
12 ...
13 }

先介绍第一种:

bool Person::operator<(const Person& b),bool是函数返回类型,Person::只是指定了成员函数所属类名。

在作为函数成员重载中,先看下这句话:单目运算符作为类成员函数重载时没有型参(除了后置自增(自减)有一个整型参数:详细点击),双目运算符作为类成员函数重载时只有一个型参,作为运算符的右操作数,其左操作数就是本对象自己,也就是this。

单目运算符一般重载为成员函数。

因此在作为成员函数进行重载时,是以

 1 #include <iostream>
 2 #include <cstdlib>
 3
 4 using namespace std;
 5
 6 class A {
 7 private:
 8     int a;
 9     int b;
10 public:
11     A(int x = 0, int y = 0):a(x), b(y){}
12     A operator+ (A &C);
13 };
14
15 A A::operator+ (A &C) {
16     A G;
17     G.a = this->a + C.b;
18
19     return G;
20 }
21
22 int main()
23 {
24     A G(5, 6);
25     A J(7, 8);
26     A K;
27     K = G + J;
28
29     return 0;
30 }

而计算机对于K = G + j;进行重载后形式是

K = G.operator+(J);

G为对象,J为参数。

而对于第二种形式的重载: 友元函数(友元函数则是指某些虽然不是类成员却能够访问类的所有成员的函数)进行重载,那么它就不存在this指针了,所以需要定义两个参数来运算(对于双目运算符),而友元函数的实现可以在外面定义,但必须在类内部声明。

 1 #include <iostream>
 2 #include <cstdlib>
 3
 4 using namespace std;
 5
 6 class A {
 7 private:
 8     int a;
 9     int b;
10 public:
11     A(int x = 0, int y = 0):a(x), b(y){}
12     friend A operator+ (A &C, A &D);
13 };
14
15 A operator+ (A &C, A &D) {
16     A G;
17     G.a = D.a + C.b;
18
19     return G;
20 }
21
22 int main()
23 {
24     A G(5, 6);
25     A J(7, 8);
26     A K;
27     K = G + J;
28
29     return 0;
30 }

推荐类内声明,外部定义,这样不会显得类臃肿。

对于K = G + J;计算机将重载为:

K = operator+(G, J);

声明为友元函数的好处:

1.和普通函数重载相比,它能够访问非公有成员。

2.将双目运算符重载为友元函数,这样就可以使用交换律。

第一条没解释的必要,跳过,哈哈哈哈嗝。        =。=

第二条:交换律也可以理解成对操作数对称处理。

 1 #include <iostream>
 2 using namespace std;
 3
 4 //复数类
 5 class Complex{
 6 public:
 7     Complex(): m_real(0.0), m_imag(0.0){ }
 8     Complex(double real, double imag): m_real(real), m_imag(imag){ }
 9     Complex(double real): m_real(real), m_imag(0.0){ }  //转换构造函数
10 public:
11     friend Complex operator+(const Complex &c1, const Complex &c2);
12 public:
13     double real() const{ return m_real; }
14     double imag() const{ return m_imag; }
15 private:
16     double m_real;  //实部
17     double m_imag;  //虚部
18 };
19
20 //重载+运算符
21 Complex operator+(const Complex &c1, const Complex &c2){
22     Complex c;
23     c.m_real = c1.m_real + c2.m_real;
24     c.m_imag = c1.m_imag + c2.m_imag;
25     return c;
26 }
27
28 int main(){
29     Complex c1(25, 35);
30     Complex c2 = c1 + 15.6;
31     Complex c3 = 28.23 + c1;
32     cout<<c2.real()<<" + "<<c2.imag()<<"i"<<endl;
33     cout<<c3.real()<<" + "<<c3.imag()<<"i"<<endl;
34
35     return 0;
36 }

如果将 operator+ 定义为成员函数,根据“+” 运算符具有左结合性”这条原则,Complex c2 = c1 + 15.6;会被转换为下面的形式:

Complex c2 = c1.operator+(Complex(15.6));

这就是通过对象调用成员函数,是正确的。而对于Complex c3 = 28.23 + c1;,编译器会尝试转换为不同的形式:

Complex c3 = (28.23).operator+(c1);

很显然这是错误的,因为 double 类型并没有以成员函数的形式重载 +。

也就是说,以成员函数的形式重载 +,只能计算 c1 + 15.6,不能计算 28.23 + c1,这是不对称的

使用&的好处:

将重载的返回类型定义为引用类型,能够实现连续输入(输出)。

 1 #include <iostream>
 2 #include <cstdlib>
 3
 4 using namespace std;
 5
 6 class A {
 7 private:
 8     int a;
 9     int b;
10 public:
11     A(int x = 0, int y = 0):a(x), b(y){}
12     friend A operator+ (A &C, A &D);
13     friend ostream& operator<<(ostream & out, A &W);
14 };
15
16 A operator+ (A &C, A &D) {
17     A G;
18     G.a = D.a + C.b;
19
20     return G;
21 }
22
23 ostream& operator<<(ostream & out, A &W) {
24     out << W.a << " " << W.b;
25
26     return out;
27 }
28
29 int main()
30 {
31     A G(5, 6);
32     A J(7, 8);
33     A K;
34     K = G + J;
35
36     cout << K << " " << J;
37
38     return 0;
39 }

将流提取运算符 >> 或流插入运算符 << 声明为友元函数,能够访问非公有成员。

对于第三种普通函数重载:

因为不属于类了,自然也就没比较加Person::

要注意的是这种形式无法访问非公有成员。

其他知识点:

为什么我们要使用两个参数来重载流提取运算符 >> 和流插入运算符 << 呢?

如果我们要用成员函数,则会有cout.operator<<(const A& W),但重载双目操作符(即为类的成员函数),就只要设置一个参数作为右侧运算量,而左侧运算量就是对象本身,而cin和cout并不是对象本身(你如果声明为对象,下面岂不是this->a + W.b,注意this是类对象的)

如果一定要声明为成员函数,只能成为如下的形式:

ostream & operator<<(ostream &output)

{

  return output;

}

所以在运用这个<<运算符时就变为这种形式了:

t<<cout;

而不是

 cout<<t

而且也无法链式使用了

cout<<t<<t<<t<<endl;

对于t << cout 我们可以反向理解cout << t,cout >> t会被重载为cout.operator(t),错误。

 1 #include <iostream>
 2 #include <cstdlib>
 3
 4 using namespace std;
 5
 6 class A {
 7 public:
 8     int a;
 9     int b;
10 public:
11     A(int x = 0, int y = 0):a(x), b(y){}
12     friend A operator+ (A &C, A &D);
13     ostream& operator<<(ostream & out);
14 };
15
16 A operator+ (A &C, A &D) {
17     A G;
18     G.a = D.a + C.b;
19
20     return G;
21 }
22
23 ostream& A::operator<<(ostream & out) {
24
25     return out;
26 }
27
28 int main()
29 {
30     A G(5, 6);
31     A J(7, 8);
32     A K;
33     K = G + J;
34
35     K << cout;
36
37     return 0;
38 }

这段借用网上的:

不能重载的根本原因在于, 大部份的标准库实现中,对ostream,istream类体系采用了构造函数保护继承的方式。。。致使即使以继承的方式来扩展流类,也会在对象实例化时遭遇阻碍。。。 另一方面,标准库中的流类,其插入符函数没有声明为虚函数,因此子类不能对其实现进行覆盖,所以也使成员函数重载遭遇到实质的困难。。。 总的来说,C++标准I/O库非常繁杂且难,其实现思想很多都与常规的OOP有所出入。。。在使用的时候要谨慎,并最好遵从惯例。。。

至于单目运算符的声明定义就不多做介绍了,要看的点击此处,前面脚本之家的链接也有单目运算符重载的介绍。

以上如有错误,还请前辈们斧正,谢谢!!!

原文地址:https://www.cnblogs.com/Mayfly-nymph/p/9034936.html

时间: 2024-10-11 17:23:11

运算符重载三种形式(成员函数,友元函数,普通函数)详解的相关文章

函数基础之定义,三种形式,返回值

函数基础 函数的定义 函数是一种思想. 在程序中,函数就是具备某一功能的功能,事先讲工具准备好就是函数的定义,遇到场景拿来就用就是函数的调用. 为何用函数 我们如果过不使用函数的话,写程序时将会遇到三个问题: 1.程序冗长 2.程序的扩展性差 3.程序的可读性差 如何使用函数 定义函数 先定义函数,后调用: 定义函数 def 函数名(param1,param2....): """ 函数功能的描述信息 :param1 描述 :param2 描述 :return 返回值 code

文件内光标的移动 函数基础 定义函数的三种形式

# with open(r'a.txt', 'r', encoding='utf-8')as f:# data1=f.read()# print('>1>:',data1)# print(f.tell()) # 44 只有一种情况下,光标的意思是字符# data2=f.read()# print('>2>:',data2) # 第一次有结果,第二次没有,第一次读取数据后光标已经移到了文件尾 # 只有一种情况下,光标以字符为单位:文件以rt方式打开,read()# with open

Qt学习 之 多线程程序设计(QT通过三种形式提供了对线程的支持)

QT通过三种形式提供了对线程的支持.它们分别是, 一.平台无关的线程类 二.线程安全的事件投递 三.跨线程的信号-槽连接. 这使得开发轻巧的多线程Qt程序更为容易,并能充分利用多处理器机器的优势.多线程编程也是一个有用的模式,它用于解决执行较长时间的操作而不至于用户界面失去响应.在Qt的早期版本中,在构建库时有不选择线程支持的选项,从4.0开始,线程总是有效的. 线程类 Qt 包含下面一些线程相关的类: QThread 提供了开始一个新线程的方法 QThreadStorage 提供逐线程数据存储

解梯度下降法的三种形式BGD、SGD以及MBGD

原帖地址:https://zhuanlan.zhihu.com/p/25765735           在应用机器学习算法时,我们通常采用梯度下降法来对采用的算法进行训练.其实,常用的梯度下降法还具体包含有三种不同的形式,它们也各自有着不同的优缺点. 下面我们以线性回归算法来对三种梯度下降法进行比较. 一般线性回归函数的假设函数为: $$h_\theta=\sum_{j=0}^n\theta_jx_j$$   对应的损失函数为: $$J_{train}(\theta)=\frac1{2m}\s

javascript oop编程 — 实现继承的三种形式

javascript  oop编程  - 实现继承的三种形式[1] (1)模拟类的方式, 我们都知道js是原型继承机制,不存在class和instance分离的这种方式 假设,我们有两个类 function  Animal(){ this.name = "animal"; this.eat = function(){ consle.log("eating"); } } function Cat(){ this.say = function(){ console.lo

a^b%c 的三种形式

求a^b%c,(1  <= a,b <= 2^62, 1 <= c <= 10^9) 最基本的快速幂 _LL mod_exp(_LL a, _LL b, int c) { _LL ans = 1; _LL t = a%c; while(b) { if(b&1) ans = ans*t%c; t = t*t%c; b >>= 1; } return ans; } 求a^b%c,(1 <= a,b,c <= 2^62) 因为c在long long范围内,

PHP数组输出三种形式 PHP打印数组

PHP数组输出三种形式 PHP打印数组 $bbbb=array("11"=>"aaa","22"=>"bbb");//方式一:只能输出值value不能输出keyforeach($bbbb as $color)echo $color; //方法二:value与key都可输出foreach($bbbb as $key=>$value)echo $key."=>".$value; //方法

js判断变量初始化的三种形式

<1> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> <script type="text/javascript"> //js判断变量初始化有三种形式 var x; if (x == null) { alert("x为null"); } if (typeof (x) == "un

信息标记的三种形式

标记后的信息可形成信息组织结构,增肌了信息维度,可用于通信,存储或展示,利于程序的理解和运用 XML是被设计用来描述数据的,重点是:什么是数据,如何存放数据. HTML是被设计用来显示数据的,重点是:显示数据以及如何显示数据更好上面. 信息标记的三种形式:xml, json,yaml yaml说明 =>无类型键值对key:value,缩进表示所属关系,- 表示并列关系, |表示块数据,#表示注释 信息提取