class Complex{
public:
Complex(int r,int i):m_r(r),m_i(i){}
const Complex operator+(const Comple& c)const{//将参数改为传引用,那么c2不能为const对象,因为一般引用不能引用常对象,解决方法是将参数改为万能的常引用
return Complex(m_r+c.m_r,m_i+c.m_i);
}
//三个const的作用
//修饰返回值,使返回值为右值
//修饰右操作数,使右操作数可以是左值也可以是右值
//修饰左操作数,可以是左值也可以是右值
private:
int m_r;
int m_i;
friend const Complex operator-(const Complex& l,const Complex& r);//声明友元函数operator-();
};
const Complex operator-(const Complex& l,const Complex& r){//将操作符重载定义为全局函数
return Complex(l.m_r-r.m_r,l.m_i-r.m_i);
}
//主函数中
Complex c1(1,2);//如果加上const修饰,那么操作符重载函数将不能得到调用,因为成员函数有this指针,this又没有被const修饰,即传到this的实参是常对象。this定义时要求不是常对象,扩大了操作范围
//所以最好在操作符重载函数中加上常函数修饰限定
Complex c2(3,4);//如果将参数改为传引用,那么c2不能为const对象,因为一般引用不能引用常对象,解决方法是将参数改为万能的常引用
Complex c3=c1+c2;//通过操作重载可以实现自定义类型运算
(c1+c2)=c3;//前面说自定义类型的这种写法会调用拷贝赋值,因为operator=可以由常对象来调用,这不符合我们基本类型的运算逻辑,解决方法是在返回前面加const
类名& operator=(const 类名(可以是和返回值不同的类)& r){}
//返回值因为必须是左值,所以不能加const修饰,并且要返回调用自身对象的引用
//不能是常函数,因为左操作数,即调用者数据会被更新
(2)实现方法
与运算类双目操作符重载的区别:
(1)返回值为左操作数自身的引用
(2)左操作数不能是右值
(3)重载函数是非常函数(相对与成员函数实现形式而言)
1)成员函数形式:L#R L.operator#(R)
2)全局函数形式:L#R ::operator#(R)
注意:
operator=函数没有全局函数形式,因为没有定义拷贝赋值时,编译器会默认加上缺省的拷贝赋值函数,这会形成歧义。
二、单目操作符重载 #O
1、计算类弹幕操作符,-(负),~、!、&(取地址)
int a =10;
int b=-10;
-a;//返回值是临时变量
注意:
(1)操作数可以是左值也可以右值
(2)返回值是右值
(3)实现方法
成员函数形式:#O -> O.operator#(void)
全局函数形式:#O -> ::operator#(O)
例如有Integer类,operator-()函数重载形式如下:
cosnt Integer operator-(void)const{
return Integer(-m_i);//m_i为Integer的私有成员变量
}
也可以定义为全局函数。
2、自增减运算符
参数、返回值、调用对象的限定参考双目操作符重载笔记
1)前自增减操作符
操作数必须是左值,返回值就是操作数自身,他也是左值,实现方式也有两种
成员函数形式:#O --> O.operator#(void)
全局函数形式:#O —> O.operator#(O)
2)后自增减
操作数必须是一个左值,表达式的结果是右值,返回值应该是操作数自增减前的一个副本,为了使返回值是一个右值,需加上cosnt修饰
成员函数形式:#O --> O.operator#(哑元)
全局函数形式:#O —> O.operator#(O,哑元)
3、插入和提取操作符的重载 << 、>>
在<iostream>中定义了输入输出流对象,所以一般不把输入输出运算符重载为成员函数:
ostream:cout(左操作数是左值,有操作数可以是左值,也可以是右值)
istream:cin(左右操作数都是左值)
friend ostream$& operator<< (ostream& os,const RIGHT& right){}
friend istream$& operator>> (ostream& is,RIGHT& right){}//右操作数必须是左值
4、下标运算符"[]"重载
功能:可以让一个对象当做数组一样的方式去使用
1)操作数可以是左值,也可以是右值
2)非常对象返回左值,常对象返回右值
class Array{
public:
Array(size_t size):m_data(new int[size]),m_size(size){}
~Array(void){
delete[] m_data;
m_data=NULL;
}
int& operator[](size_t i){
return m_data[i];
}
int operator[](size_t i)const{//返回类型不是引用,为int型的临时变量
return m_data[i];
}
private:
int* m_data;//数组地址
size_t m_size;//容器的大小
};
int main(void){
Array a(10);//左操作数为左值,重载函数返回值也为左值
a[0]=11;//a.operator[](0)
a[1]=12;
a[9]=20;
const Array& r=a;
r[0]=21;//报错,因为调用的是常函数,返回右值,不能作为左值
return 0;
}
5、函数操作符“()”
功能:把一个对象当做函数去使用,可以进行重载
class Square{
public:
double operator()(double x)const{
return x*x;
}
};
int main(){
Square square;
//square.operator()(13.0)
cout<<square(13.0)<<endl;
return 0;
}
6、解引用(取目标)和间接成员访问操作符: *、->
功能:将对象当做指针使用,用于实现智能指针
class A{
public:
A(const string& str):m_str(str){}
~A(void){}
string m_str;
};
int main(){
A* pa=new A("hello world!");
//...
if("error"){
return -1;//进入异常,pa将得不到释放
}
//...
delete pa;
return 0;
}
(1)智能指针
class PA{
public:
PA(A* pa):m_pa(pa){}
~PA(void){
if(m_pa){
delete m_pa;//调用A对象的析构函数
m_pa=NULL;
}
private:
A* m_pa;
}
};
int main(){
PA* pa(new A("hello world!"));
//...
if("error"){
return -1;//进入异常,pa在栈区,由}调用析构函数
}
//...
return 0;
}
像上面pa这样,有时候需要在堆区分配内存,但是因为某些原因执行不到delete,这时候可以像上面一样使用智能指针来完成。即封装一个类的指针的类的对象,当它离开作用域时,其析构函数负责释放该该指针指向的动态内存,以免泄漏。
(2)*、->操作符重载
通过pa可以智能实现堆区A对象的释放,但是pa并不能像A的指针一样使用*、->操作符。为此,可以在PA类中实现这两个操作符的重载。
A* operator-> (void)const{
return m_pa;
}
A& operator-*(void)const{
return *m_pa;
}
重载后,可以实现以下操作:
cout<<(*pa).m_str<<pa->m_str<<ndl;
//编译器解释:pa.operator->()->m_str;
//pa.operator*().m_str;
注意:
上面为了解决内存泄漏问题,需要自己写一个智能指针,C++标准库提供一个叫auto_ptr的智能指针,包含头文件:#include<memory>它的用法如下:
auto_ptr<A> pa2(new A("hello world"));
7、类型转换操作符
功能:类 类型到其他类型的转换
通过类型转换构造函数,可以使实现:(1)基本类型到类类型转换(2)类类型到类类型的转换
通过类型转换操作符,可以实现:(1)类类型到基本类型(2)类类型到类类型
通过上面两种方式,无法实现基本类型到基本类型的转换
1、类类型到基本类型
operator 目标类型(void)const{...}
前面讲到了通过单参数的构造函数,可以把基本类型隐式的转换为相应的类类型,反过来,可以通过操作符重载,把类类型转换为基本类型。例如有一个Integer类,m_i十一个私有的成员变量。
operator int (void)const{//类型转换操作符函数,注意前面没有返回类型,这里面的int既是操作符,也是返回类型,是一种固定写法。
return m_i;
}
这样就可以直接输出一个Interger对象,而不用再重载输入输出操作符。
8、new、delete操作符
static void* operator new(size_t size){}//注意只能声明为静态成员函数,因为使用new操作符之前,此时还没有对象被创建,需要使用类名来进行调用,如果不加,编译器会自动添加。通过new操作符重载,可以实现在不同存储器上分配内存
static void operator delete(void* p){}
class A{
public:
A(void){
cout<<"A::A()"<<endl;
}
~A(void){
cout<<"A::~A()"<<endl;
}
static void* operator new(size_t size){
cout <<"A::new"<<endl;
void*pv =malloc(size);
return pv;
}
static void operator delete(void* pv){
coutt<<"A::delete"<<endl;
free(p);
p=NULL;
}
};
注意:
如果一个类中一个成员变量都没有,那么它的对象大小默认是一个字节,这是为了保存对象在内存上的唯一性
int main(){
A* pa=new A;
//1)pa= (A*)A::operator new(sizeif(A));先分配内存
//2)pa->A::A();再调用构造函数
delete pa;
//1)pa->A::A();
//2)A::operator delete (void)
}
9、总结
(1)不是所有操作符都能重载,下面的操作符不能重载
作用域限定运算符“::”
直接成员访问运算符“.”
直接成员指针解引用运算符“.*”
条件操作符“? :”
字节长度操作符“sizeof”
类型信息操作符“typeid”
注意:关于sizeof
sizeof(3+3.14);//8
int a=0;
sizeof(a=3+3.14);//4
cout<<a<<endl;//0,sizeof不会计算表达式,只取表达式结果
(2)如果操作符的所有操作数都是基本类型,编译器不允许重载,如:
int operator+(int a,int b){
return a-b;//error
}
(3)操作符的重载无法改变操作符的优先级
(4)操作符重载无法改变操作数的个数,如:无法通过重载%来实现百分数的重载
(5)无法通过操作符重载实现新的操作符,如:operator@,operator$等
(6)部分操作符只能通过成员函数形式进行重载,不能通过友元形式