C++面向对象学习之运算符重载(1)

时间:2022-07-26
本文章向大家介绍C++面向对象学习之运算符重载(1),主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

运算符重载

1.为什么要进行运算符重载?

2. 重载运算符过程中需要遵循的基本规则

3.重载运算符的两种处理方式及其适用背景

· 友元函数

· 类的成员函数

4.重载运算符实例

· 双目运算符

· 单目运算符(以前置自增运算符和后置自增运算符为例)

1.为什么要进行运算符重载?

运算符重载(函数重载)是C++多态的重要实现手段之一。通过运算符重载对运算符功能进行特殊定制,使其支持特定类型对象的运算,执行特定的功能,增强C++的扩展功能。

以上都是烦人的概念,下面尽可能用人话说明

所谓重载(Overloading),便是实现一个现有运算符的多种数据类型操作。举个例子:你的嘴既可以吃饭,也可以说话,这其中就蕴含了重载的概念,当它 作为进食器官时,它发挥其应有功能,而你想让它说话时,你就好比一台计算机,检测到你的嘴有被重载过,让它支持说话的功能,重载其实就这么简单 当然上面这个例子可能不是那么科学,但作为对于初学重载的同学来说已经够形象说明了~

来看这样一个例子,我想让你用c++实现一个复数相加的函数,你一定会想,这不是很简单吗,于是,学过类定义及其使用的你很快就写出了这样一段代码

#include <iostream>
using namespace std;
class Complex
{
public:
    Complex(){}
    Complex(double a, double b){real=a,imag=b;display();}
    Complex add(const Complex&a);
    Complex operator+(const Complex&a);
    void display();
private:
    double real,imag;
};
Complex Complex::add(const Complex&a)
{
    Complex c;
    c.real=real+a.real,c.imag=imag+a.imag;
    return c;
}
Complex Complex::operator+(const Complex&a)
{
    Complex c(real+a.real,imag+a.imag);
    return c;
}
void Complex::display()
{
    cout<<real<<" "<<imag<<" "<<endl;
}
int main()
{
    Complex a(1,2),b(3,4);
    Complex result=a.add(b);
    //Complex result=a+b;
    result.display();
    return 0;
}

但今天,我想让你用运算符重载的方式解决这个问题,并想让你通过这两种写法 来比较其表达方式上的差异,代码就是下面的两段(运算符重载作为成员函数和友元函数),把注释掉的代码去注释就可以实现了,我们观察到了什么?

#include <iostream>
using namespace std;
class Complex
{
public:
    Complex(){}
    Complex(double a, double b){real=a,imag=b;display();}
    //Complex add(const Complex&a);
    Complex operator+(const Complex&a);
    void display();
private:
    double real,imag;
};
/*Complex Complex::add(const Complex&a)
{
    Complex c;
    c.real=real+a.real,c.imag=imag+a.imag;
    return c;
}*/
Complex Complex::operator+(const Complex&a)
{
    Complex c(real+a.real,imag+a.imag);
    return c;
}
void Complex::display()
{
    cout<<real<<" "<<imag<<" "<<endl;
}
int main()
{
    Complex a(1,2),b(3,4);
    Complex result=a+b;
    result.display();
    return 0;
}
#include <iostream>
using namespace std;
class Complex
{
public:
    Complex(){}
    Complex(double a, double b){real=a,imag=b;display();}
    Complex add(const Complex&a);
    friend Complex operator+(const Complex &a,const Complex&b);
    void display();
private:
    double real,imag;
};
/*Complex Complex::add(const Complex&a)
{
    Complex c;
    c.real=real+a.real,c.imag=imag+a.imag;
    return c;
}*/
Complex operator+(const Complex&a ,const Complex&b)
{
    return Complex (a.real+b.real,a.imag+b.imag);;
}
void Complex::display()
{
    cout<<real<<" "<<imag<<" "<<endl;
}
int main()
{
    Complex a(1,2),b(3,4);
    Complex result=a+b;
    //Complex result=a+b;
    //result.display();
    return 0;
}

函数重载的基本格式:

重载运算符作为成员函数时

<返回类型说明符> operator <运算符符号>(<参数表>)
{
 
     <函数体>
 
}
可以看到,重载函数 operator+访问了两个对象中的成员,一个是对象中的成员,另一个是形参对象
的成员。 在将运算符函数重载为成员函数后,如果出现含该运算符的表达式,如 c1+c2,编译系统把
它解释为 c1.operator+(c2)  即通过对象 c1 调用运算符重载函数,并以表达式中第二个参数
(运算符右侧的类对象 c2)作为函数实参。 运算符重载函数的返回值是 Complex 类型,返回值是复
数 c1和 c2 之和(Complex(c1.real + c2.real,c1.imag+c2.imag))。 

而与成员函数所不同的是:运算符函数不作为成员函数,而把它放在类外,在 Complex 类中声明它
为友元函数。 同时将运算符函数改为有两个参数。 在将运算符“+”重载为非成员函数后,C++编译系
统将程序中的表达式 a+b 解释为:operator+(a,b) 即执行 a+b 相当于调用:
Complex operator + (Complex &a,Complex &b) {return Complex(a.real+b.real,a.imag+b.imag);} 

那什么时候将运算符函数作为友元函数,什么时候作为类的成员函数呢?两者又有什么区别呢?
这里给出大致的解答:

如果将运算符重载函数作为成员函数,它可以
通过 this指针自由地访问本类的数据成员,因此可以少写一个函数的参数。 但
必须要求运算表达式第一个参数(即运算符左侧的操作数)是一个类对象,  
而且与运算符函数的类型相同。 因为必须通过类的对象去调用该类的
成员函数,而且只有运算符重载函数返回值与该对象同类型,运算结果才有意
义。 
如想将一个复数和一个整数相加,如 c1+i,可以将运算符重载函数作为成
员函数,如下面的形式:  
Complex Complex∷operator+(int &i)  // 运算符重载函数作为 Complex
类的成员函数{return Complex(real+i,imag);} 
注意在表达式中重载的运算符“+”左侧应为 Complex 类的对象,如 
c3=c2+i; 不能写成 
c3=i+c2;  // 运算符 “+” 的左侧不是类对象 , 编译出错

如果出于某种考虑,要求在使用重载运算符时运算符左侧的操作数是整
型量(如表达式 i+c2,运算符左侧的操作数 i 是整数),这时是无法利用前面定义
的重载运算符的,因为无法调用 i.operator+函数。 可想而知,如果运算符左侧
的操作数属于C++标准类型(如int)或是一个其他类的对象,则运算符重载函数
不能作为成员函数,只能作为非成员函数。 如果函数需要访问类的私有成员,
则必须声明为友元函数。 可以在 Complex 类中声明:  
friend Complex operator+(int &i,Complex &c); // 第一个参数可以不是
类对象
 
在类外定义友元函数:  
Complex operator+(int &i, Complex &c) // 运算符重载函数不是成员函数 
{return Complex(i+c.real,c.imag);} 将双目运算符重载为友元函数时,在函数的形参表列中必须有两个参数,
不能省略,形参的顺序任意,不要求第一个参数必须为类对象。 但在使用运算
符的表达式中,要求运算符左侧的操作数与函数第一个参数对应,运算符右侧
的操作数与函数的第二个参数对应。 如 
c3=i+c2;  // 正确 , 类型匹配
c3=c2+i;  // 错误 , 类型不匹配
请注意,数学上的交换律在此不适用。 如果希望适用交换律,则应再重载
一次运算符“+”。 如 Complex operator+(Complex &c, int &i) // 此时第一个参数为类对象
 {return Complex(i+c.real,c.imag);} 这样,使用表达式 i+c2和 c2+i 都合法,编译系统会根据表达式的形式选择
调用与之匹配的运算符重载函数。 可以将以上两个运算符重载函数都作为友
元函数,也可以将一个运算符重载函数(运算符左侧为对象名的) 作为成员函
数,另一个(运算符左侧不是对象名的)作为友元函数。 但不可能将两个都作为
成员函数,原因是显然的。 

C++中可重载的运算符重载为数众多,也存在着一些限制,这些限制包括:

   1、为防止用户为标准类型重载运算符,重载后的运算符必须至少有一个是用户自定义类型的数据。

   2、不能违反运算符原有的运算规则。

   3、不能重载不存在的运算符,即不能创建新的运算符

   4、以下运算符不可重载:

          sizeof :sizeof运算符

          . :成员运算符

          .* :成员指针运算符

          :: : 域解析运算符

          ? : 条件运算符

          typid : RTTI运算符

          const_cast、dynamic_cast、reinterpret_cast、static_cast  :强制类型转换

   5、只能用作成员函数重载的运算符:

          = :赋值运算符

          () :函数调用运算符

          [] :下标(索引)运算符

          -> :通过指针访问类成员的运算符

   6、可重载运算符列表

+

-

*

/

%

^

&

|

~=

!

=

<

>

+=

-+

*=

/=

%=

^=

&=

|=

<<

>>

>>=

<<=

==

!=

<=

>=

&&

||

++

--

,

->*

->

()

[]

new

delete

new[]

delete[]

双目运算符重载实例

#include<iostream>
#include<cstring>
#include<bits/stdc++.h>
using namespace std;
class String
{
public:
    String(){p=NULL;}
    String(char* str){p=str;}
    friend bool operator<(const String&a,const String&b);
    friend bool operator>(const String&a,const String&b);
    friend bool operator==(const String&a,const String&b);
    void display();
private:
    char*p;
};
void String::display()
{
    cout<<p;
}
bool operator<(const String&a,const String&b)
{
    return strcmp(a.p,b.p)<0?1:0;
}
bool operator>(const String&a,const String&b)
{
    return strcmp(a.p,b.p)<=0?0:1;
}
bool operator==(const String&a,const String&b)
{
    return !strcmp(a.p,b.p)?1:0;
}
inline void compare(String&a,String&b)
{
    if(operator<(a,b)){a.display();cout<<"<";b.display();cout<<endl;}
    if(operator>(a,b)){a.display();cout<<">";b.display();cout<<endl;}
    if(operator==(a,b)){a.display();cout<<"==";b.display();cout<<endl;}
}
int main()
{
    String a("Hello"),b("Book"),c("Computer"),d("Hello");
    compare(a,b),compare(b,c),compare(a,d);
    //while(1)getchar();
    return 0;
}

重载单目运算符(以自增运算符为例)

#include<bits/stdc++.h>
using namespace std;
class Time
{
public:
    Time(){minute=0,sec=0;}
    Time(int m,int s){minute=m,sec=s;}
    Time operator++();
    Time operator++(int);
    void display(){cout<<setw(2)<<setfill('0')<<minute<<":"<<setw(2)<<setfill('0')<<sec<<endl;}
private:
    int minute,sec;
};
Time Time::operator++()
{
    if(++sec>=60)sec-=60,minute++;
    return *this;
}
Time Time::operator++(int)
{
    Time temp=*this;
    sec++;
    if(sec>=60)sec-=60,minute++;
    return temp;
}
int main()
{
    Time a(34,59),b;
    cout<<"a=";
    a.display();
    cout<<"++a=";
    ++a;
    a.display();
    b=a++;
    cout<<"a++=";
    a.display();
    cout<<"b=";
    b.display();
    while(1)getchar();
    return 0;
}

可以看到: 在程序中对运算符“++”进行了重载,使它能用于 Time 类对象。 “++”和“–”运算符有两种使用方式,前置自增运算符和后置自增运算符,它们的作用是不一样的,在重载时怎样区别这二者呢? 针对“++”和“–”这一特点,C++约定: 在自增(自减)运算符重载函数中,增加一个 int型形参,就是后置自增(自减)运算符函数。 可以看到: 重载后置自增运算符时,多了一个 int 型的参数,增加这个参数只是为了与前置自增运算符重载函数有所区别,此外没有任何作用。 编译系统在遇到重载后置自增运算符时,会自动调用此函数。 请注意前置自增运算符“++”和后置自增运算符“++”二者作用的区别。 前者是先自加,返回的是修改后的对象本身。 后者返回的是自加前的对象,然后对象自加。

预知后事如何,且看下期

C++面向对象学习之运算符重载(2)

重载标准输出输入流运算符 运算符重载小结 不同数据类型转换(类型转换函数)