一、运算符函数
string类对象为什么可以使用 运算符 操作。
C++中把运算符当作函数处理,在表达式中遇到运算符时,编译器会先寻找相关的运算符函数。
例如a+b,会先寻找 [const]T[&] operator+ ([const] T& a,[const] T& b) 格式的函数,这种函数又被称为全局运算符函数,运算对象都做为参数传递给该函数。
如果运算对象是自定义类型,还可以把运算符函数实现该类型的内部,a+b的运算符函数格式为 [const] T[&] operator+([const]T& that) [const],这种函数又被称为成员运算符函数,一般由左边的运算对象发起调用,右边的运算对象作为参数。
二、运算符重载
在设计结构、联合、类时,为了让它们的对象的操作更简便、易懂,我们会为它们实现部分运算符函数,这种行为被称为运行符重载,我们以自定义string为例进行讲解运算符重载。
1、双目运算符重载
重载为成员运算符函数
1、左边的运算对象如果可能是常量,则需要把运算符函数设置为常函数。
2、如果右边的运算对象可能是常量,则需要给形参设置const属性。
3、一般在运算符函数中不修改运算对象的数据,而是由左右的运算对象的运算结果产生临时对象。
#include <iostream>
#include <stdlib.h>
using namespace std;
class Int
{
int num;
public:
Int(int num):num(num) {}
// 双目运算符 成员函数
const Int operator+(const Int& that)const
{
return Int(num+that.num);
}
const Int operator-(const Int& that)const
{
return Int(num-that.num);
}
const Int operator*(const Int& that)const
{
return Int(num*that.num);
}
const Int operator/(const Int& that)const
{
return Int(num/that.num);
}
const Int operator%(const Int& that)const
{
return Int(num%that.num);
}
};
重载为全局运算符函数
给类对象优先重载成员运算符函数时,必须要在类的内部实现,当无法修改对象的源码时,也就无法在结构、联合、类的内部增加成员运算符函数,又想给类对象增加运行符函数,这种情况只能定义全局的运算符函数,把运算对象都作为参数传递给运算符函数。
*友元
如何在全局运行符函数内访问私有成员—友元
当一个非成员函数,需要访问类中的私有成员时,可以把函数设置为该类的friend函数(也叫友元函数),这样在函数内就可以访问类的所有成员变量和成员函数。
一般情况下,在类的内部进行声明该函数(头文件内),并在声明的前面添加friend关键字,在类外实现友元函数。
也可以直接在类的内部实现有友函数,即使在类的内部实现也依然不是成员函数。
当重载全局运算符函数基本上都需要使用友元函数,我个人建议把友元函数实现在类的内部。
#include <iostream>
#include <stdlib.h>
using namespace std;
class Int
{
int num;
public:
Int(int num):num(num) {}
void show(void)const
{
cout << "num=" << num << endl;
}
friend const Int operator-(const Int& i1,const Int& i2);
friend const Int operator*(const Int& i1,const Int& i2);
friend const Int operator/(const Int& i1,const Int& i2);
friend const Int operator%(const Int& i1,const Int& i2);
};
const Int operator-(const Int& i1,const Int& i2)
{
return Int(i1.num-i2.num+100);
}
const Int operator*(const Int& i1,const Int& i2)
{
return Int(i1.num*i2.num+100);
}
const Int operator/(const Int& i1,const Int& i2)
{
return Int(i1.num/i2.num+100);
}
const Int operator%(const Int& i1,const Int& i2)
{
return Int(i1.num%i2.num+100);
}
3、输入、输出运算符重载
1、cin、cout是标准库中具有输入、输出功能的类对象,它的类名叫istream、ostream,当我们想给某种类对象增加输入、输出的功能时,就需要实现 >>、<<运算符函数对于该类对象的支持,也就是重载输入、输出运算符。
2、由于cin、cout都在>>、<<运算的左边,如果想实现成员运算符函数,就需要在istream、ostream类中实现,而我们无法增加或修改istream、ostream类的代码,所以只能实现全局的 >>、<< 运算符函数。
3、由于输入、输出过程中需要使用cin、cout记录成功、失败等状态,所以输入、输出函数的istream、ostream要使用引用,且不能用const修饰。
4、输入、输出过程中还可能要访问对象的 私有、保护的成员,所以全局的 >>、<< 运算符函数有必要设置成友元函数(右边对象)。
friend ostream& operator<<(ostream& os,const <类名>& obj)
{
return os << obj.成员1 << obj.成员1 << ...;
}
friend istream& operator>>(istream& is,<类名>& str)
{
return is >> obj.成员1 >> obj.成员1 >> ...;
}
实现自定义的MyString类,实现它的构造、拷贝构造、赋值、析构,并重载以下运算符。
== > < >= <= != + += >> <<
#include <iostream>
#include <cstring>
using namespace std;
class MyString
{
char *cStr;
public:
MyString(const char *str="")
{
cStr=new char[strlen(str)+1];
strcpy(cStr,str);
}
~MyString(void)
{
delete[] cStr;
}
//拷贝构造
MyString(const MyString& that)
{
cStr=new char[strlen(that.cStr)+1];
strcpy(cStr,that.cStr);
}
//赋值构造
const MyString operator=(const MyString& that)
{
if(this!=&that)
{
delete[] cStr;
cStr=new char[strlen(that.cStr)+1];
strcpy(cStr,that.cStr);
}
return *this;
}
friend int operator==(const MyString& left,const MyString& right)
{
return !strcmp(left.cStr,right.cStr);
}
friend int operator>(const MyString& left,const MyString& right)
{
char *p1=(char*)left.cStr;
char *p2=(char*)right.cStr;
while(*p1==*p2)
{
p1++;
p2++;
}
if(*p1-*p2>0)
return 1;
else
return 0;
}
friend int operator<(const MyString& left,const MyString& right)
{
char *p1=(char*)left.cStr;
char *p2=(char*)right.cStr;
while(*p1==*p2)
{
p1++;
p2++;
}
if(*p1-*p2<0)
return 1;
else
return 0;
}
friend int operator>=(const MyString& left,const MyString& right)
{
char *p1=(char*)left.cStr;
char *p2=(char*)right.cStr;
while(*p1==*p2)
{
p1++;
p2++;
}
if(*p1-*p2>=0)
return 1;
else
return 0;
}
friend int operator<=(const MyString& left,const MyString& right)
{
char *p1=(char*)left.cStr;
char *p2=(char*)right.cStr;
while(*p1==*p2)
{
p1++;
p2++;
}
if(*p1-*p2<=0)
return 1;
else
return 0;
}
friend int operator!=(const MyString& left,const MyString& right)
{
return strcmp(left.cStr,right.cStr);
}
friend char* operator+(const MyString& left,const MyString& right)
{
return strcat(left.cStr,right.cStr);
}
friend char* operator+=(MyString& left,const MyString& right)
{
strcpy(left.cStr,strcat(left.cStr,right.cStr));
}
friend ostream& operator<<(ostream& os,const MyString& right)
{
return os<<right.cStr;
}
friend istream& operator>>(istream& is,MyString& right)
{
return is>>right.cStr;
}
};
int main(int argc,const char* argv[])
{
MyString str1="hehe";
MyString str2="xoxo";
MyString str3=str1+str2;
str3+=str1;
cout << str3 <<endl;
return 0;
}
4、单目运算符的重载
单目运算符的重载与双目运算符重载的规则几乎相同,只是运算对象数量不同而已
重载为成员运算符函数
唯一的运算对象就是函数的调用者,参数列表为空,[cosnt] T[&] operator<单目运算符>(void)[const]。
#include <iostream>
#include <stdlib.h>
using namespace std;
class Int
{
int num;
public:
Int(int num):num(num) {}
const Int operator-(void)const
{
return Int(0-num);
}
bool operator!(void)const
{
return !num;
}
const Int operator~(void)const
{
return Int(~num);
}
Int* operator&(void)
{
return (Int*)#
}
const Int* operator&(void)const
{
return (const Int*)#
}
void show(void)const
{
cout << "num=" << num << endl;
}
};
重载为全局运算符函数
与双目运算符一样,只有无法修改运算对象的代码时,才会重载全局运算符函数,参数列只有一个,[cosnt] T[&] operator<单目运算符>([const] T&)[const]。
#include <iostream>
#include <stdlib.h>
using namespace std;
class Int
{
int num;
public:
Int(int num):num(num) {}
void show(void)const
{
cout << "num=" << num << endl;
}
friend const Int operator-(const Int& that)
{
return Int(0-that.num);
}
friend bool operator!(const Int& that)
{
return !that.num;
}
friend const Int operator~(const Int& that)
{
return ~that.num;
}
friend Int* operator&(Int& that)
{
return (Int*)&that.num;
}
friend const Int* operator&(const Int& that)
{
return (const Int*)&that.num;
}
};
注意:尽量重载为成员运算符函数。
5、自变运算符重载
前自变运算符重载 ++i/--i
前自变运算符与普通的单目运算符重载方法相同。
后自变运算符重载 ++i/--i
后自变运算符的运算对象也只有一个,为了区别前自变,我们需要在参数列增加一个什么都不做的int关键字,我们把这种用法叫哑元。
#include <iostream>
#include <stdlib.h>
using namespace std;
class Int
{
int num;
public:
Int(int num):num(num) {}
void show(void)const
{
cout << "num=" << num << endl;
}
Int& operator++(void)
{
num++;
cout << "前++" << endl;
return *this;
}
Int operator++(int) // 哑元
{
cout << "后++" << endl;
return Int(num++);
}
/*
friend Int& operator++(Int& that)
{
cout << "前++" << endl;
that.num++;
return that;
}
friend Int operator++(Int& that,int) // 哑元
{
cout << "后++" << endl;
return Int(that.num++);
}
*/
};
6、new/delete运算符的重载
在C++中是把new/delete关键字当作运算符,也就是运算符函数,所以如果有特殊需要可以对它们进行重载,重载格式参考单目运算符。
new运算符的重载:
new 运算符有两中用法,也就有两个格式重载函数。
格式1:new TYPE
该格式既可以重载为全局函数,也可以重载为成员函数。
格式2:new(ptr) TYPE
该格式只能重载为成员函数,因为C++标准库中已经为该格式的函数。
new/delete运算符有什么用:
1、记录下申请、释放的内存块的地址,方便程序员检测是否有内存泄漏。
2、防止用户大量分配、释放小块内存,产生内存碎片。
3、默认的new只能给类对象分配内存,重载后可以给成员指针一起分配内存,该方法适合重载为类的成员函数。
void* operator new(size_t size)
{
void* ptr = malloc((size/4+1)*4);
cout << ptr << "自定义的new"<< endl;
return ptr;
}
void operator delete(void* ptr)
{
cout << ptr << "自定义的delete" << endl;
free(ptr);
}
#include <iostream>
#include <stdlib.h>
using namespace std;
class Student
{
char sex;
short age;
float score;
public:
char* name;
void* operator new(size_t size)
{
cout << "自定义的new运算符成员函数" << endl;
Student* stup = (Student*)malloc(size);
stup->name = (char*)malloc(20);
return stup;
}
void operator delete(void* ptr)
{
cout << "自定义的delete运算符成员函数" << endl;
Student* stup = (Student*)ptr;
free(stup->name);
free(stup);
}
};
int main(int argc,const char* argv[])
{
Student* stup = new Student;
delete stup;
return 0;
}
三、运算符重载的局限性
1、无法重载的运算符
. 成员访问运算符
:: 作用域运算符
sizeof 长度运算符
?: 条件运算符
# 预处理符号
2、只能重载为成员函数的运算符
在C++中,有几个函数只能被重载为成员函数,它们分别是:
-
赋值运算符重载
=
,也叫赋值函数,要进行深拷贝时就需要重载。 -
索引运算符重载
[]
,可以把对象伪装成数组使用。 -
函数调用运算符重载
()
,可以把对象伪装成函数使用。 -
成员访问运算符重载
->
,可以把对象伪装成指针使用。需要注意的是,这些函数只能被定义为成员函数,而不能被定义为全局函数或友元函数。除了上述函数,其他函数可以被定义为全局函数或友元函数。
3、优先重载为成员函数
只有无法修改该对象的代码时,才建议重载为全局函数,但重载全局函数需要在运算对象的类内设置友元,所以语法上支持,但实际情况是个死局。
4、重载运算符时要注意的问题
1、重载运算符的目标是为了提高代码可读性、实用性,如果达不该效果建议定义为函数。
2、重载运算符函数时,要符合情况情理,不要反人性,不要成为你炫技的工具。