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):
重载标准输出输入流运算符 运算符重载小结 不同数据类型转换(类型转换函数)
- HDUOJ-------Being a Good Boy in Spring Festival
- Golang语言--slice 切片原理
- GoLang语言--的函数运用
- HDUOJ----John
- 一个SQL性能问题的优化探索(二)(r11笔记第38天)
- HDUOJ---kiki's game
- Golang语言中的多维数组传递
- HDUOJ-----Brave Game
- 事实证明Linux永远是NO.1
- HDUOJ-Counting Triangles
- HDUOJ---三角形(组合数学)
- HDUOJ--Strange fuction
- HDUOJ---Can you solve this equation?
- HDUOJ---A + B Again
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 剑指Offer LeetCode 面试题15. 二进制中1的个数
- 剑指Offer LeetCode 面试题11. 旋转数组的最小数字
- 剑指Offer 面试题09. 用两个栈实现队列
- 剑指Offer 面试题06. 从尾到头打印链表
- 剑指Offre 面试题05. 替换空格
- Datatables获取选中行的某一列的数据
- 终于懂了建造者模式
- (力扣)面试题04. 二维数组中的查找
- 一条命令查询电脑多久没关机
- Android Studio无法运行程序调试程序出现Unable to connect to ADB.Check the Event Log for possible issues.Verify th
- MySQL 中的 DML 语句执行流程,你理解的跟我一样吗?
- Android SharedPreferences的使用
- 【DB笔试面试846】在Oracle中,TWO_TASK环境变量的作用是什么?
- 分治策略之归并排序(Python实现)
- 分治策略之最大子数组(Python实现)