• C++课程学习笔记第四周:运算符的重载


    前言:本文主要是根据MOOC网北大课程——《程序设计与算法(三):C++面向对象程序设计》内容整理归纳而来,整理的课程大纲详见 https://www.cnblogs.com/inchbyinch/p/12398921.html

    本文介绍了运算符重载的概念和用法,包括string对象和可变长数组对象的设计。

    1 运算符重载基本概念和形式

    1.1 运算符重载基本概念

    • 在数学上,两个复数可以直接进行+、-等运算。但在C++中,直接将+或-用于复数对象是不允许的,因为C++中预定义的运算符并未提供这种功能。
    • 为了让对象也能通过运算符进行运算(这样代码更简洁,容易理解),C++提供了运算符重载的机制。
    • 运算符重载的含义:对已有的运算符(C++中预定义的运算符)赋予多重的含义,使同一运算符作用于不同类型的数据时导致不同类型的行为。比如 5 + 4 = 9; complex_a + complex_b 生成新的复数对象。
    • 运算符重载的目的:扩展C++中提供的运算符的适用范围,使之能作用于对象。

    1.2 运算符重载的形式

    返回值类型 operator 运算符(形参表){
        ……
    }
    
    • 运算符重载的实质是函数重载;
    • 把含运算符的表达式转换成对运算符函数的调用,把运算符的操作数转换成运算符函数的参数;
    • 运算符被多次重载时,根据实参的类型决定调用哪个运算符函数。
    • 可以重载为普通函数,也可以重载为成员函数;
    • 重载为成员函数时,参数个数为运算符目数减一;重载为普通函数时参数个数为运算符目数。
    //示例
    class Complex{
    public:
        double real,imag;
        Complex( double r = 0.0, double i= 0.0 ):real(r),imag(i) { }
        Complex operator-(const Complex & c);
    };
    Complex operator+( const Complex & a, const Complex & b){
        return Complex( a.real+b.real,a.imag+b.imag); // 返回一个临时对象
    }
    Complex Complex::operator-(const Complex & c){
        return Complex(real - c.real, imag - c.imag); // 返回一个临时对象
    }
    
    int main(){
        Complex a(4,4),b(1,1),c;
        c = a + b; // 等价于c=operator+(a,b);
        cout << c.real << "," << c.imag << endl;
        cout << (a-b).real << "," << (a-b).imag << endl;
        //a-b 等价于a.operator-(b)
        return 0;
    }
    

    2 常见运算符的重载

    2.1 赋值运算符 ‘=’ 的重载

    • 有时候希望赋值运算符两边的类型可以不匹配,比如,把一个int类型变量赋值给一个Complex对象,或把一个 char * 类型的字符串赋值给一个字符串对象,此时就需要重载赋值运算符“=”。
    • 赋值运算符“=”只能重载为成员函数。
    class String {
    private:
        char * str;
    public:
        String():str(new char[1]) { str[0] = 0;}
        const char * c_str() { return str; };
        String & operator=(const char * s);
        ~String() { delete [] str; }
    };
    String & String::operator = (const char * s){ 
        // 重载“=”得 以使得 obj = “hello” 能够成立
        delete [] str;
        str = new char[strlen(s)+1];
        strcpy( str, s);
        return * this;
    }
    
    int main(){
        String s;
        s = "Good Luck," ; //等价于 s.operator=("Good Luck,");
        cout << s.c_str() << endl;
        // String s2 = "hello!"; // 这条语句要是不注释掉就会出错
        s = "Shenzhou 8!"; //等价于 s.operator=("Shenzhou 8!");
        cout << s.c_str() << endl;
        return 0;
    }
    

    上述代码实现了两个功能:将 char* 类型赋给string对象;返回值为String&类型,故可以作为左值。但还需要解决以下问题:

    • 使用对象赋值 s2 = s1 时,造成浅拷贝。因为复制的是指针,故两者指向同一个地方;若一个对象消亡或指向其他地方,则会调用delete,那么另一个对象的指针将无法访问这块内容。
    • 对象自身赋给自身:s2 = s2 ,可能会导致将指向的内容删掉。
    • 编译器默认加上的复制构造函数也会形成浅拷贝。

    因此需要加上两个成员函数:赋值运算符的重载函数和复制构造函数。

    //赋值运算符的重载函数
    String & operator = (const String & s){
        if( this == & s) return * this; //排除自身赋给自身的情况
        delete [] str;
        str = new char[strlen(s.str)+1];
        strcpy( str,s.str);
        return * this;
    }
    //复制构造函数
    String( String & s){
        str = new char[strlen(s.str)+1];
        strcpy(str,s.str);
    }
    

    另外:

    • 一般情况下,将运算符重载为类的成员函数,是较好的选择。
    • 但有时,重载为成员函数不能满足使用要求,重载为普通函数,又不能访问类的私有成员,所以需要将运算符重载为友元。

    2.2 运算符重载实例:可变长整型数组

    //要编写可变长整型数组类,使之能如下使用:
    int main() { 
        CArray a; //开始里的数组是空的
        for( int i = 0;i < 5;++i)
            a.push_back(i);
        CArray a2,a3;
        a2 = a;
        for( int i = 0; i < a.length(); ++i )
            cout << a2[i] << " " ;
        a2 = a3; //a2是空的
        for( int i = 0; i < a2.length(); ++i ) //a2.length()返回0
            cout << a2[i] << " ";
        cout << endl;
        a[3] = 100;
        CArray a4(a);
        for( int i = 0; i < a4.length(); ++i )
            cout << a4[i] << " ";
        return 0;
    }
    //程序输出结果为
    //0 1 2 3 4
    //0 1 2 100 4
    

    下面的代码可以满足当前要求:

    #include <iostream>
    #include <cstring>
    using namespace std;
    
    class CArray{
        int* p;
        int n;
    public:
        CArray(int s=0);
        CArray(CArray& a);
        ~CArray(){ if(n>0) delete []p;}
        CArray& operator=(const CArray& a);
        int& operator[](int i){if(i>=0 && i<n) return p[i];}
        void push_back(int k);
        int length(){return n;}
    };
    CArray::CArray(int s):n(s){
        if(n == 0)  p = NULL;
        else  p = new int[n];
    }
    CArray::CArray(CArray& a){
        n = a.n;
        if(n == 0){
            p = NULL;
        }else{ //n>0
            p = new int[n];
            memcpy(p, a.p, sizeof(int)*n);
        }
    }
    CArray& CArray::operator=(const CArray& a){
        if(this == &a) return *this;
        //若原空间不足,则需要重新开辟空间,否则不需要
        if(n < a.n){ //n可能为0
            if(n > 0) delete []p;
            p = new int[a.n];
        }
        //复制数据
        if(a.n == 0){
            p = NULL;
        }else{ //a.n>0
            memcpy(p, a.p, sizeof(int)*a.n);
        }
        n = a.n;
        return *this;
    }
    void CArray::push_back(int k){
        //if(n > 0) delete []p;
        int* temp = new int[n+1];
        memcpy(temp, p, sizeof(int)*n);
        temp[n] = k;
        if(n > 0) delete []p;
        p = temp;
        n++;
    }
    
    
    int main() {
        CArray a; //开始里的数组是空的
        for( int i = 0;i < 5;++i)
            a.push_back(i);
        CArray a2,a3;
        a2 = a;
        for( int i = 0; i < a.length(); ++i )
            cout << a2[i] << " " ;
        a2 = a3; //a2是空的
        for( int i = 0; i < a2.length(); ++i ) //a2.length()返回0
            cout << a2[i] << " ";
        cout << endl;
        a[3] = 100;
        CArray a4(a);
        for( int i = 0; i < a4.length(); ++i )
            cout << a4[i] << " ";
        return 0;
    }
    

    2.3 流插入运算符和流提取运算符的重载

    cout 是ostream 类的对象,是在 iostream 中定义的。
    “<<” 能用在cout上是因为,在iostream里对 “<<” 进行了重载。
    cout << 5 << “this” 的本质就是cout.operator<<(5).operator<<(“this”);

    //重载成成员函数
    ostream & ostream::operator<<(int n){
        …… // 输出n的代码
        return * this;
    }
    //重载成全局函数
    ostream & operator<<( ostream & o, const CStudent & s){
        o << s.nAge ;
        return o;
    }
    

    2.4 类型强制转换运算符的重载

    类型强制转换运算符被重载时不能写返回值类型,实际上其返回值类型就是该类型强制转换运算符代表的类型。

    #include <iostream>
    using namespace std;
    class Complex{
        double real,imag;
    public:
        Complex(double r=0,double i=0):real(r),imag(i) { };
        operator double () { return real; } //重载强制类型转换运算符 double
    };
    int main(){
        Complex c(1.2,3.4);
        cout << (double)c << endl; //输出 1.2
        double n = 2 + c; //等价于 double n=2+c.operator double()
        cout << n; //输出 3.2
    }
    

    2.5 自增自减运算符的重载

    自增运算符++、自减运算符--有前置/后置之分,为了区分所重载的是前置运算符还是后置运算符,C++规定:

    • 前置运算符作为一元运算符重载。比如重载为成员函数:T & operator++();重载成全局函数:T1 & operator++(T2)。
    • 后置运算符作为二元运算符重载,多写一个没用的参数:比如重载为成员函数:T operator++(int);重载成全局函数:T1 operator++(T2,int)。

    此处需注意:

    • 通常前置返回引用,后置返回类。且后置涉及较多的开销,故尽量用前置。
    • int 作为一个类型强制转换运算符被重载,故(int) s 等效于 s.int()。
    class CDemo {
    private :
        int n;
    public:
        CDemo(int i=0):n(i) { }
        CDemo & operator++(); // 用于前置形式
        CDemo operator++( int ); // 用于后置形式
        operator int ( ) { return n; } //强制类型转换运算符的重载
        friend CDemo & operator--(CDemo & );
        friend CDemo operator--(CDemo & ,int);
    };
    
    CDemo & CDemo::operator++(){ //前置 ++
        n ++;
        return * this;
    } // ++s即为: s.operator++();
    CDemo CDemo::operator++( int k ){ //后置 ++
        CDemo tmp(*this); // 记录修改前的对象
        n ++;
        return tmp; // 返回修改前的对象
    } // s++即为: s.operator++(0);
    CDemo & operator--(CDemo & d){// 前置--
        d.n--;
        return d;
    } //--s即为: operator--(s);
    CDemo operator--(CDemo & d,int){// 后置--
        CDemo tmp(d);
        d.n --;
        return tmp;
    } //s--即为: operator--(s, 0);
    
    
    int main(){
        CDemo d(5);
        cout << (d++ ) << ","; //于 等价于 d.operator++(0);
        cout << d << ",";
        cout << (++d) << ","; //于 等价于 d.operator++();
        cout << d << endl;
        cout << (d-- ) << ","; //于 等价于 operator--(d,0);
        cout << d << ",";
        cout << (--d) << ","; //于 等价于 operator--(d);
        cout << d << endl;
        return 0;
    }
    //输出结果:
    //5,6,7,7
    //7,6,5,5
    

    对于运算符的重载,注意事项:

    • C++不允许定义新的运算符;
    • 重载后运算符的含义应该符合日常习惯;
    • 运算符重载不改变运算符的优先级;
    • 以下运算符不能被重载:“.”、“ .* ”、“::”、“?:”、sizeof;
    • 重载运算符()、[]、->或者赋值运算符=时,运算符重载函数必须声明为类的成员函数。

    3 练习

    需要掌握string类和可变长数组类的设计。(注:更为全面的string类可见第五章练习)

    //001:MyString  http://cxsjsxmooc.openjudge.cn/2019t3fall4/001/
    #include <iostream>
    #include <cstring>
    using namespace std;
    
    class MyString {
        char * p;
    public:
        MyString(const char * s) {
            if(s){
                p = new char[strlen(s) + 1];
                strcpy(p,s);
            }else
                p = NULL;
        }
        ~MyString() { if(p) delete [] p; }
    
    //your code start here
        MyString(const MyString& str1){
            p = new char[strlen(str1.p)+1];
            strcpy(p, str1.p);
        }
    
        MyString& operator=(MyString& str1){
            if(this==&str1) return *this;
            if(p) delete[]p;
            p = new char[strlen(str1.p)+1]; //即便str1.p为空也可以
            strcpy(p, str1.p);
            return *this;
        }
    
        MyString& operator=(const char* s){
            if(p) delete []p;
            p = new char[strlen(s)+1];
            strcpy(p, s);
            return *this;
        }
    
        void Copy(const char* s){
            if(p) delete []p;
            p = new char[strlen(s)+1];
            strcpy(p, s);
        }
    
        friend ostream& operator<<(ostream& o, MyString& str1){
            cout << str1.p;
            return o;
        }
    //your code end here
    };
    
    
    int main(){
        char w1[200],w2[100];
        cin >> w1 >> w2;
        MyString s1(w1),s2 = s1;
        MyString s3(NULL);
        s3.Copy(w1);
        cout << s1 << "," << s2 << "," << s3 << endl;
        s2 = w2;
        s3 = s2;
        s1 = s3;
        cout << s1 << "," << s2 << "," << s3 << endl;
    }
    
  • 相关阅读:
    nginx配置
    线程与进程的区别:
    java面试题1
    递归的定义和优缺点
    使用jedis连接redis可能会出现的问题及解决方案
    Linux上安装Redis
    Linux 权限管理
    Maven
    网址备份
    反射
  • 原文地址:https://www.cnblogs.com/inchbyinch/p/12233222.html
Copyright © 2020-2023  润新知