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):
重载标准输出输入流运算符 运算符重载小结 不同数据类型转换(类型转换函数)