zoukankan      html  css  js  c++  java
  • C++运算符重载

    一、基本属性

      (1)运算符重载的目的是扩展C++中提供的运算符的适用范围,使之能作用于对象,或自定义的数据类型;

      (2)运算符重载的实质是函数重载,可以重载为普通成员函数,也可以重载为成员函数;

      (3)运算符重载也是多态的一种,和函数重载称为静态多态,表示函数地址早绑定,在编译阶段就确定好了地址;

    二、运算符重载的原理

      以+运算符为例,对于内置的数据类型,比如整型,浮点型,+运算符可以进行相加操作,但是对于自定义的数据类型,如对象等,会报错,没有与这些操作符匹配的+运算符。

      以对象相加为例,我们可以在类中定义成员函数实现对象中的属性相加,一般为如下形式:

     1 class Person{
     2 public:
     3     Person(){};
     4     Person(const Person&){}
     5     Person(int a, int b) :m_A(a), m_B(b){};
     6 
     7     int m_A;
     8     int m_B;
     9 
    10     //下面这种方式可以返回局部变量,因为返回的是指针,生命周期在代码块之外,若返回Person则不安全,这种说法是不对的
    11     Person* plusPerson(Person &p){
    12         Person tmp_1(0, 0); //这里是自定义的tmp_1进行初始化,调用了无效的拷贝构造函数,完成初始化,跳过编译器检查,后面继续赋值,完成功能
    13         Person *tmp = new Person(tmp_1);
    14         (*tmp).m_A = this->m_A + p.m_A;
    15         (*tmp).m_B = this->m_B + p.m_B;
    16         return tmp;
    17     }
    18 };
    19 
    20 void test01(){
    21     Person p1(10, 20);
    22     Person p2(20, 30);
    23 
    24     Person *p3 = p1.plusPerson(p2);//不想用这种函数调用的方式
    25     cout << p3->m_A << " " << p3->m_B << endl;
    26     delete p3;
    27 }

      注意:上述代码中有个Bug,(1)如果只定义函数初始化列表,则默认构造函数和默认拷贝构造都不会提供了;(2)自定义的拷贝构造函数体为空,在返回局部对象时,会显示为初始化的错误。(3)返回局部对象的指针时,不会调用拷贝构造函数。上述代码中可以直接返回局部对象而不用在堆上开辟指针。

      但我们并不想用成员函数的方式调用,这是可以用"opertor 运算符"进行重载,只需要将plusPerson函数名换成operator+即可,将函数实现复制即可。另外除了返回对象指针,返回对象也可以。

     1 class Person{
     2 public: 5     Person(int a, int b) :m_A(a), m_B(b){};
     6 
     7     int m_A;
     8     int m_B;
     9 
    10     //成员函数进行运算符重载
    11     Person* operator+(Person &p){
    12         Person tmp_1(0, 0);
    13         Person *tmp = new Person(tmp_1);
    14         (*tmp).m_A = this->m_A + p.m_A;
    15         (*tmp).m_B = this->m_B + p.m_B;
    16         return tmp;
    17     }
    18 };
    19 
    20 //--------全局函数对运算符进行重载
    21 //Person* operator+(Person &p1, Person &p2){
    22 //    Person tmp_1(0, 0);
    23 //    Person *tmp = new Person(tmp_1);
    24 //    (*tmp).m_A = p1.m_A + p2.m_A;
    25 //    (*tmp).m_B = p1.m_B + p2.m_B;
    26 //    return tmp;
    27 //}
    28 //---------------------------
    29 
    30 void test01(){
    31     Person p1(10, 20);
    32     Person p2(20, 30);
    33 
    34     Person *p3 = p1 + p2; //相当于Person *p3 = p1.operator+(p2);或者operator+(p1, p2)
    35     cout << p3->m_A << " " << p3->m_B << endl;
    36     delete p3;
    37 }

    三、运算符重载总结与示例

    1.总结

      (1)重载运算符(),[] ,->, =的时候,运算符重载函数必须声明为类的成员函数;

      (2)重载运算符<<,>>的时候,运算符只能通过全局函数配合友元函数进行重载;

      (3)不要重载&&和||运算符,因为无法实现短路原则。

    2.示例

    (1)重载<<和>>运算符

      重载<<的目的是为了让自定义数据类型和内置类型一样通过cout输出,如cout<<p1<<endl;但从上面+运算符重载的例子可以看出,p1+p2的实现中,成员函数为:p1.operator+(p2),全局函数为:operator+(p1, p2)。

      (a)将cout << p1和p1+p2进行比较可以看出,成员函数要cout.operator<<(p1),全局函数要operator<<(cout, p1),因此只能通过全局函数进行重载;

      (b)配合友元函数是因为要访问私有成员属性;

      (c)为了实现连续<<输出(链式编程思想),需要将返回值写为ostream&引用类型。

     1 class Person{
     2     friend ostream& operator<<(ostream& cout, Person& p1);
     3 public: 6     Person(int a, int b) :m_A(a), m_B(b){};
     7 
     8 private:
     9     int m_A;
    10     int m_B;
    11 };
    12 
    13 ostream& operator<<(ostream& cout, Person& p1){
    14     cout << "m_A的值:" << p1.m_A << "m_B的值" << p1.m_B ; //重载函数内部为常规的函数实现
    15     return cout;
    16 }
    17 
    18 void test01(){
    19     Person p1(10, 20);
    20 
    21     operator<<(cout, p1);
    22 
    23     cout << p1 << "你看我可以继续输出" << endl;
    24 }

     (2)重载前置后置运算符,其中前置运算符的返回值为类名+引用,后置运算符的返回值为类名。

      第一个代码调用了无效的拷贝构造函数,但完成初始化跳过了编译器检查,然后又再次赋值从而实现功能,但这种代码无法返回局部对象

     1 class Person{
     2     friend ostream& operator<<(ostream& cout, Person& p1);
     3 public:
     4     Person(){ m_A = 10; };
     5     Person(const Person&p){ 
     6         cout << "调用拷贝构造函数" << endl;
     7     }
     8 
     9     //前置++,引用作为返回值,返回局部对象
    10     Person& operator++(){
    11         this->m_A++;
    12         return *this;
    13     }
    14 
    15     //后置++,代码中用到了无效的拷贝构造函数,进行了无效的初始化,跳过编译器检查,然后又再次赋值从而实现效果
    16     Person* operator++(int){
    17         Person* tmp = new Person (*this);
    18         tmp->m_A = this->m_A;
    19         this->m_A++;
    20         return tmp;
    21     }
    22 private:
    23     int m_A;
    24 };

      对上面的代码进行修改,可以添加有效的拷贝构造函数,或者取消掉无效的拷贝构造函数均可,这样就可以返回局部对象了。

     1 class Person{
     2     friend ostream& operator<<(ostream& cout, Person& p1);
     3 public:
     4   Person(const Person&p){
     5     this->m_A = p.m_A;}; 6     Person(int a) :m_A(a){}
     7 
     8     //前置++,引用作为返回值,返回局部对象
     9     Person& operator++(){
    10         this->m_A++;
    11         return *this;
    12     }
    13 
    14     //后置++,返回局部对象会调用拷贝构造函数
    15     Person operator++(int){
    16         Person tmp(*this);
    17         this->m_A++;
    18         return tmp;
    19     }
    20 
    21 
    22 private:
    23     int m_A;
    24 };
    25 
    26 
    27 ostream& operator<<(ostream& cout, Person& p1){
    28     cout << "m_A的值:" << p1.m_A ; //重载函数内部为常规的函数实现
    29     return cout;
    30 }
    31 
    32 void test01(){
    33     Person p1(10);
    34 
    35     cout << ++p1 << "前置++" << endl;
    36     cout << p1++ << "后置++" << endl;
    37 }

      注意:C++内置类型的后置++返回的是变量的拷贝,也就是不可修改的值;前置++返回的是变量的引用,因此可以作为修改的左值。即++(++a)或(++a)++都可以,但++(a++)不可以,(C++默认必须修改a的值,如果不修改则报错)。但我们实现的重载中,++(a++)不会报错,因为我们仅仅是为了输出值,没有对是否修改a做警告处理,但并没有对a加两次。

     (3)重置指针运算符与智能指针

       一般用于实现智能指针,托管自定义类型的对象,让对象自动释放;若想让智能指针像Person *p等智能指针一样,则需要重载->和*,必须在成员函数中重载。

     1 class Person{
     2 public:
     3     Person(){ m_A = 0; }
     4     Person(int a) :m_A(a){}
     5 
     6     void showage(){
     7         cout << this->m_A << endl;
     8     }
     9 
    10     ~Person(){
    11         cout << "对象析构" << endl;
    12     }
    13 
    14 private:
    15     int m_A;
    16 };
    17 
    18 //常规的对象指针在堆上开辟后,要记得释放
    19 void test00(){
    20     Person *p = new Person(10);
    21     p->showage();
    22     delete p;
    23 }
    24 
    25 //智能指针类的成员属性为原指针
    26 class SmatrPointer{
    27 public:
    28 
    29     SmatrPointer(){}
    30 
    31     SmatrPointer(Person *person){
    32         cout << "有参构造" << endl;
    33         this->person = person;
    34     }
    35     SmatrPointer(const SmatrPointer& person){
    36         cout << "拷贝构造" << endl;
    37         this->person = new Person;
    38         this->person = person.person;
    39     }
    40 
    41 
    42     ~SmatrPointer(){
    43         cout << "智能指针析构" << endl;
    44 
    45         if (this->person != NULL){
    46             delete this->person;
    47             this->person = NULL;
    48         }
    49     }
    50 
    51     //若实现sp->showage()像Person *p的p->showage()一样
    52     //即sp->为p   返回为指针类型,实际为sp->->showage(),这里编译器做了优化
    53     Person* operator->(){
    54         return this->person;
    55     }
    56 
    57     //若实现(*sp).showage()像Person *p的(*p).showage()一样
    58     //即*sp为*p    返回为对象类型
    59     Person& operator*(){
    60         return *this->person;
    61     }
    62 
    63 private:
    64     Person *person;
    65 };
    66 
    67 void test01(){
    68     SmatrPointer *sp = new SmatrPointer(new Person(10)); //调用有参构造,堆上开辟
    69     (*sp)->showage();
    70     (**sp).showage();
    71     delete sp;
    72     
    73     SmatrPointer sp_1(new Person(20)); //调用有参构造,栈上开辟
    74     //若想让智能指针像Person *p一样,则需要重载->和*
    75     sp_1->showage();
    76     (*sp_1).showage();
    77     
    78 }

    (4)赋值运算符重载

      一个类默认创建默认构造,析构,拷贝构造和operator=赋值运算符(可以进行简单的值传递),需要注意new的用法。

     1 class Person{
     2 public:
     3 
     4     Person(char *name){
     5         //m_Name = new char(strlen(name)+1); //这里出错,声明出错,应该为字符数组,这里为赋初值了
     6         m_Name = new char[strlen(name) + 1];
     7         strcpy(m_Name, name);
     8     }
     9 
    10     Person(const Person&p){
    11         if (this->m_Name != NULL){
    12             delete [] this->m_Name;
    13             this->m_Name = NULL;
    14         }
    15         this->m_Name = new char[strlen(p.m_Name) + 1];
    16         strcpy(this->m_Name, p.m_Name);
    17     }
    18 
    19     ~Person(){
    20         if (this->m_Name != NULL){
    21             delete [] this->m_Name;
    22             this->m_Name = NULL;
    23         }
    24     }
    25 
    26     
    27     //void operator=(Person &p){
    28     //    if (this->m_Name != NULL){
    29     //        delete [] this->m_Name;
    30     //        this->m_Name = NULL;
    31     //    }
    32     //
    33     //    this->m_Name = new char[strlen(p.m_Name) + 1];
    34     //    strcpy(this->m_Name, p.m_Name);
    35     //}
    36 
    37     Person& operator=(Person &p){
    38         if (this->m_Name != NULL){
    39             delete[] this->m_Name;
    40             this->m_Name = NULL;
    41         }
    42 
    43         this->m_Name = new char[strlen(p.m_Name) + 1];
    44         strcpy(this->m_Name, p.m_Name);
    45 
    46         return *this;
    47     }
    48 
    49     void show(){
    50         cout << this->m_Name << endl;
    51     }
    52 
    53     char *m_Name;
    54 };
    55 
    56 void test01(){
    57     Person p1("我滴神啊");
    58     Person p2("My God");
    59     p1.show();
    60 
    61     //默认提供的operator=可以实现简单值的复制,如果涉及到指针成员属性
    62     //则会将地址进行赋值,出现内存泄漏问题,和深浅拷贝差不多
    63     //因此需要进行运算符重载
    64     p1 = p2;
    65     p1.show();
    66 
    67     //有的人想进行链式赋值,这里链式编程思想,返回类名&
    68     Person p3("诶呀");
    69     p1 = p2 = p3;
    70     p3.show();
    71 }

    (5)[]运算符重载

      主要实现通过[]对类写成的动态数组进行索引。

    1 //将对应索引的数组值输出,若还想通过索引进行修改加引用
    2 int& MyArray::operator[](int index){
    3     return this->address[index];
    4 }
    5 
    6 void test01(){
    7     MyArray p3;
    8     cout << p3[1 << endl;
    9 }

    (6)关系运算符的重载

      对==,!=进行重载,返回值为bool值

    (7)()重载

      看上去像函数调用,实际为仿函数

     1 class MyPrint{
     2 public:
     3     MyPrint(){}
     4 
     5     void myprint(const string text){
     6         cout << text << endl;
     7     }
     8 
     9     void operator()(const string text){
    10         cout << text << endl;
    11     }
    12 
    13 };
    14 
    15 void test01(){
    16     MyPrint myPrint;
    17     myPrint.myprint("拔罐啦");
    18 
    19     //如果要让对象进行调用,必须对()重载,看上去想是函数调用,其实是仿函数
    20     myPrint("理疗真疼");
    21 
    22 }
  • 相关阅读:
    webpack学习_管理输出(管理资源插件)
    vue路由
    vue动态组件,组件缓存
    vue组件间传参
    模块化
    安装Vue脚手架,创建Vue项目
    Vue常用指令
    VUE概述
    小程序调用微信支付接口
    Android音视频开发之-WebRTC技术实践
  • 原文地址:https://www.cnblogs.com/qinguoyi/p/10268832.html
Copyright © 2011-2022 走看看