十一、运算符重载

所谓重载,就是重新赋予新的含义。函数重载就是对一个已有的函数赋予新的含义,使之实现新功能,因此,一个函数名就可以用来代表不同功能的函数,也就是”一名多用”。

运算符也可以重载。实际上,我们已经在不知不觉之中使用了运算符重载。例如,大家都已习惯于用加法运算符”+”对整数、单精度数和双精度数进行加法运算,如5+8,5.8+3.67等,其实计算机对整数、单精度数和双精度数的加法操作过程是很不相同的,但由于C++已经对运算符”+”进行了重载,所以就能适用于int,float,double类型的运算。

又如”<<“是C++的位运算中的位移运算符(左移),但在输出操作中又是与流对象cout配合使用的流插入运算符,”>>“也是位移运算符(右移),但在输入操作中又是与流对象cin配合使用的流提取运算符。这就是运算符重载(operator overloading)。C++系统对“<<”和“>>”进行了重载,用户在不同的场合下使用它们时,作用是不同的.对”<<“和”>>“的重载处理是放在头文件stream中的。因此,如果要在程序中用”<<“和”>>”作流插入运算符和流提取运算符,必须在本文件模块中包含头文件stream(当然还应当包括”using namespace std“)。现在要讨论的问题是:用户能否根据自己的需要对C++已提供的运算符进行重载,赋予它们新的含义,使之一名多用.

运算符重载的本质是函数重载。

重载函数的一般格式如下:

函数类型 operator 运算符名称(形参表列){
      重载实体;
}

operator 运算符名称 在一起构成了新的函数名。比如

const Complex operator+(const Complex &c1,const Complex &c2);

我们会说,operator+ 重载了重载了运算符+。

1.友元重载和成员重载

class Complex
{
   public:
     Complex(int a,int b){
           this->a = a;
           this->b = b;
     }

    void print(){
       cout<<"("<<a<<"+"<<b<<"i)"<<endl
     }

  //普通全局友元
   friend Complex addComplex(Complex &c1,Complex &c2);

  //友元重载!
   friend Complex operator+(Complex &c1,Complex &c2)

   friend Complex operator-(Complex &c1,Complex &c2)


  //操作符重载的成员函数
  //这个+和上面的友元+会造成编译器不明确,所以只能选择一种
   Complex operator+(Complex &another){
   Complex temp(this->a+another.a,this->b+another.b);
}


   private:
      int a;//实数部分
      int b;//虚数部分
};

//让c1,c2相加,只能写一个(友元)全局函数
Complex addComplex(Complex &c1,Complex &c2)
{
    return Complex temp(c1.a+c2.a,c1.b+c2.b);
}

//全局提供一个+号操作符重载
Complex operator+(Complex &c1,Complex &c2){
     return Complex temp(c1.a+c2.a,c1.b+c2.b);
}

Complex operator-(Complex &c1,Complex &c2){
     return Complex temp(c1.a-c2.a,c1.b-c2.b);
}


int main(void){
  Complex c1(10,20);
  Complex c2(1,2);

  c1.print();
  c2.print();

  Complex c3 = addComplex(c1,c2);//通过全局函数调用
  c3.print();

  Complex c4 = operator+(c1,c2);//显示的调用了一个+号重载操作符
  c4.print();

  Complex c5 = c1 + c2;//operator+(c1,c2)
  c5.print(); 

  Complex c6 = c1 + c2;//operator-(c1,c2)
  c6.print();  


  //使用成员重载
  Complex c7 = c1.operator+(c2);
  c7.print(); 

  Complex c8 = c1+c2;
//正常一般操作符运算+支持,全局的调用方式,和成员函数的调用方式
//如果有全局的operator+函数,会operator+(c1,c2);
//如果c1有成员函数operator+,会调用c1.operator(c2);
//两者只能存在一个,推荐使用成员重载
  c8.print(); 

  return 0;

}

2.重载规则

2.1 C++不允许用户自己定义新的运算符,只能对已有的 C++运算符进行重载。

例如,有人觉得BASIC中用“”作为幂运算符很方便,也想在C++中将“”定义为幂运算符,用“3**5”表示3^5,这是不行的

2.2 C++允许重载的运算符

C++中绝大部分运算符都是可以被重载的

1.png

不能重载的运算符只有 4 个:

2.png

前两个运算符不能重载是为了保证访问成员的功能不能被改变,域运算符合sizeof运算符的运算对象是类型而不是变量或一般表达式,不具备重载的特性。

2.3 重载不能改变运算符运算对象(即操作数)的个数。

如,关系运算符“>”和“<”等是双目运算符,重载后仍为双目运算符,需要两个参数。运算符“ + ”,“ - ”,“ * ”,“ & ”等既可以作为单目运算符,也可以作为双目运算符,可以分别将他们重载为单目运算符或双目运算符。

2.4重载不能改变运算符的优先级别

例如“ * ”和“ / ”优先级高于“ + ”和“ - ”,不论怎样进行重载,各运算符之间的优先级不会改变。有时在程序中希望改变某运算符的优先级,也只能使用加括号的方法枪支改变重载运算符的运算顺序。

2.5重载不能改变运算符的结合性

如,赋值运算符“ = ”是右结合性(自右至左),重载后仍为右结合性

2.6重载运算符的函数不能有默认的参数

否则就改变了运算符参数的个数,与前面第3点矛盾

2.7重载的运算符必须和用户定义的自定义类型的对象一起使用,其参数至少应该有一个是类对象(或类对象的引用)

也就是说,参数不能全部是C++的标准类型,以防止用户修改其标准类型数据成员的运算符的性质,如下面这样是不对的

int operator+(int a,int b){
   return (a-b);
}

原来运算符+的作用是对两个数相加,现在企图通过重载使它的作用改为两个数相减。如果允许这样重载话,如果有表达式4+3,它的结果是7还是1呢?显然,这是绝对要禁止的

2.8用于类对象的运算符一般必须重载,但有两个例外“=”和运算符“&”不必用户重载

复制运算符“=”可以用于每一个类对象,可以用它在同类对象之间相互赋值。因为系统已为每一个新生命的类重载了一个赋值运算符,它的作用是逐个赋值类中的数据。成员地址运算符&也不必重载,它能返回类对象在内存中的起始地址

2.9应当使重载运算符的功能类似于该运算符作用于标准类型数据时候所实现的功能

例如,我们会去重载“+”以实现对象的相加,而不会去重载“+”以实现对象相减的功能,因为这样不符合我们对“+”原来的认知

2.10运算符重载函数可以是类的成员函数,也可以是类的友元函数,还可以是既非类 的成员函数也不是友元函数的普通函数

3.双目运算符重载和单目运算符(前++)

//使⽤用:L#R
operator#(L,R);//全局函数
L.operator#(R);//成员函数


class Complex{
public:
    Complex(int a=0,int b=0){
         this->a = a;
         this->b = b;
     }
    printComplex(){
       cout<<"("<<a<<"+"<<b<<"i)"<<endl
  }
#if 0//友元全局
   friend Complex &operator+=(Complex &c1,Complex &c2);

//全局的前++写法
   friend Complex &operator++(Complex &c1);

//全局的后++写法
   friend const  Complex operator++(Complex &c1,int);

#endif
   Complex &operator+=(Complex &another);

  //前++的成员函数
   Complex &operator++();//c1.operator++()
   {
    this->a++;
    this->b++;
    return *this;
   }

//后++的成员函数
   const Complex operator++(int);//c1.operator++()
   {
     Complex temp(this->a,this->b);
     this->a++;
     this->b++;
     return temp;
   }



private:
     int a;
     int b;
};

#if 0//友元重构
operator+=(c1,c2)
Complex &operator+=(Complex &c1,Complex &c2)
{
     c1.a+=c2.a;
     c1.b+=c2.b;
     return c1;
}

//operator++(c1)
Complex &operator++(Complex &c1)
{
     c1.a++;
     c1.b++;
     return c1;
}

//就是后++的写法
const Complex operator++(Complex &c1,int)//加个亚元//占位参数
{

    Complex temp(c1.a,c1.b);//先返回,后++
    
   c1.a++;
   c1.b++; 
   return temp;
    
}

#endif

//实现+=操作符成员函数
//c1.operator(c2);
Complex &operator+=(Complex &another)
{
     this->a += another.a;
     this->b += another.b;
     return *this;
}





int main(void){

   Complex c1(10,20);
   Complex c2(5,6);

  //返回没有引用的情况!
   c2 += c1;//operator+=(c2,c1);返回匿名对象,但是c2已经被改变,所以输出改变
   c2.printComplex();

  //返回是引用的情况
   Complex c3(8,8);
   c3 += c1;//直接改变了c3,没有返回匿名对象,省空间!
   c3.printComplex(); 

   Complex c4(10,10);
   ++c4;
//如果返回值不是&引用类型,会有什么结果?
//第一次和返回&引用类型的结果是一样
//++++c4,结果还是和第一次一样,为什么?
//Complex operator ++(Complex &c1)首先返回一个临时变量
//第二次(或更多)++都是对这个临时变量操作的,和c4无关了,所以一定要返回&引用类型
    c4.printComplex();

 //后++
  Complex c5(11,12);
  c5++;//加了const之后不能写c5++++了
  c5.printComplex();

  
    return 0;
}

4. 操作符重载输出输出操作符

istream & operator>>(istream&,⾃自定义类&);
ostream & operator<<(ostream&,⾃自定义类&);

cout<<c12;
//cout.operator<<(c12)
//operator<<(cout,c12);

通过友元来实现,避免修改c++的标准库

class Complex{
public:
    Complex(int a=0,int b=0){
         this->a = a;
         this->b = b;
     }
    printComplex(){
       cout<<"("<<a<<"+"<<b<<"i)"<<endl
   }

 //成员重构
//<<运算符,重载只能写在全局中,如果写在自己的Complex中,调用顺序就反了
//除非修改ostream类的源码,在那里面写一个ostream & operator <<(Complex &c)
ostream & operator<<(ostream &os);//c1.operator(cout)---->c1<<cout
//所以 << 写不了成员重构
{
    os<<"("<<this->a<<"+"<<this->b<<")"<<endl;
     return os;
}

  #if 0 //友元重构
   friend ostream & operator<<(ostream &os,Complex &c);

   friend istream & operator<<(istream &os,Complex &c);
  #endif
private:
    int a;
    int b;
}


#if 0//友元重构
//operator<<(cout,c)
//返回ostream才能连续使用<<a<<b
ostream &operator<<(ostream &os,Complex &c)
{
     os<<"("<<c.a<<"+"<<c.b<<")"<<endl;
     return os;
}

istream &operator>>(istream &is,Complex &c)
{
   cout<<"a:";
   is>>c.a;
   cout<<"    ";
   is>>c.b;
   return is;
}


#endif

int main(void)
{
   Complex c1(10,20);
   Complex c2(5,6);
   cout<<c1<<c2;
//c2<<cout;
   Complex c3;
   cin>>c3;
   return 0;
}

5.等号操作符

class Student{
   public:
    Student(int id,char *name) {
      this->id = id;
      int len = strlen(name);
      
      this->name = (char*)malloc(len+1);//将name在堆上开辟一个空间
      strcpy(this->name,name);
    }

   //防止浅拷贝,提供一个显示的拷贝构造函数
 Student (const Student &s){
      this->id = s.id;
      int len = strlen(s.name);
      this->name = (char*)malloc(len+1);
      strcpy(this->name,s.name);
  }

//重构系统给的“=”运算符
  Student & operator=(const Student &s){
      if(this->name != NULL){
             delete[] this->name;
             this->name = NULL;
             this->id = 0;
      }
      this->id = s.id;
      int len = strlen(s.name);
      this->name = (char*)malloc(len+1);
      strcpy(this->name,s.name);
      return *this;
  }


  void printS()
  {
    cout<<"id:"<<this->id<<"   "<<"name:"<<this->name<<endl;
  }

   ~Student(){
     if(this->name != NULL){
           delete[] this->name;
           this->name = NULL;
           this->id = 0;
        }
     }

   private:
     int id;
     char *name;//准备在堆上开辟空间
}


int main(void){
   Student s1(1,"zhang3");
   s1.printS();

   //Student s2 = s1;//浅拷贝,必然崩
   Student s2(s1);
   s1.printS();
   Student s3 = s1;
   s3.printS();
    
   return 0;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,928评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,192评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,468评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,186评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,295评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,374评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,403评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,186评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,610评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,906评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,075评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,755评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,393评论 3 320
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,079评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,313评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,934评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,963评论 2 351

推荐阅读更多精彩内容

  • C++运算符重载-上篇 本章内容:1. 运算符重载的概述2. 重载算术运算符3. 重载按位运算符和二元逻辑运算符4...
    Haley_2013阅读 2,292评论 0 51
  • C++语言的一个很有意思的特性就是除了支持函数重载外还支持运算符重载,原因就是在C++看来运算符也算是一种函数。比...
    欧阳大哥2013阅读 2,702评论 0 8
  • C++运算符重载-下篇 本章内容:1. 运算符重载的概述2. 重载算术运算符3. 重载按位运算符和二元逻辑运算符4...
    Haley_2013阅读 1,435评论 0 49
  • 注意:本文中代码均使用 Qt 开发编译环境 面向对象的多态性可以分为四类:重载多态、强制多态、包含多态和参数多态,...
    赵者也阅读 1,136评论 0 3
  • 大学读的广告学,毕业后一心想走广告的路,却找不到一家广告公司要我。好不容易找了一家小广告公司做影视广告文案,可是没...
    七月十五日雪阅读 279评论 0 0