• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
zuoanddie
博客园    首页    新随笔    联系   管理    订阅  订阅

C++-典型双、单目操作符重载,输入输出操作符重载,其他操作符重载及限制(day7)

 一、双目操作符重载

1、运算类双目操作符(更多见昨天的笔记)

昨天讲的L.operator#(R)是成员函数的形式,

如:c1+c2=c1.operator+(c2);

也可以被编译器处理为::operator(L,R)的全局函数的形式,该函数的返回值也是表达式的值。

如:c1+c2=::operator(c1,c2);

注意:

  编译器因为会提供默认的拷贝构造函数,所以在实现了+重载之后,可以直接使用+,实现+=重载

注意:

  全局的重载不能和局部的重载同时存在

 

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

 

复制代码
注意:
  通friend关键字,可以把一个全局函数声明为某个类的友元,友元函数可以访问类中的任何成员
 
2、赋值双目运算符重载:=、+=、-=
关于赋值双目运算符限定:
  (1)左操作数必须是左值(不能是常量),右操作数既可以是左值也可以是右值
  (2)表达式的结果是一个左值,就是左操作数自身:如(a=10)=20;结果就是a=20;
  
(1)拷贝赋值
  根据赋值双目运算符限定的两条,可以基本确定拷贝赋值函数的基本形式
类名& 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)部分操作符只能通过成员函数形式进行重载,不能通过友元形式

posted @ 2019-08-02 09:39  zuoanddie  阅读(564)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3