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;
}
posted @ 2020-01-25 17:30  天地辽阔  阅读(493)  评论(0编辑  收藏  举报