重载呀重载

C++中预定的运算符,只能用于基本的数据类型 ,不能用于对象间的操作,怎么办呢?

呢就只能重载一下运算符了,使其可以拥有对于不同的类型的对象有不同的行为

C中的常用的运算符有

运算符含义
+ / - / * / (/)加,减,乘,整除
%取模
^ / &按位异或,按位与,按位或
= / ==赋值运算符,判断相等运算符
&&逻辑与,逻辑或
~ / !按位取反,逻辑取反
++, +=自增一,自增n(其余操作略)
>>, <<流操作符,或左移右移操作符

本博客的md解析不支持|,表格中或操作符均未写出

学习的意义:

让你的程序看起来很酷

Guo Wei@PKU

提前总结-返回值类型:

  • 赋值号=:返回T&,即*this
  • 流控制符<<:返回ostream&istream&
  • 类型转换int():返回T
  • 前置++:返回T&
  • 后置++:返回T
  • 加等+=:返回T&
  • 下标[]:返回T&
  • 加减乘除=-*/:返回T

形式

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

  • 运算符的表达式转换成对于函数的调用
  • 把运算符的操作数转换为运算符函数的参数
  • 被多次重载时,根据实参类型决定调用哪个运算符函数

格式满足:

返回值 operator 运算符(形参表){
    
}

举个例子:

Code

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& b){
    return Complex(real-b.real,imag-b.imag);

}

调用的时候满足以下特性

Complex a,b,c,d;
c = a + b; //等价于c = operator+(a,b)
d = a-b; //等价于 a.operator-(b)
为什么成员函数中只需要写一个参数,而不是两个呢?

因为类的成员函数会自动的添加一个this指针

赋值运算符的重载

定义

有些时候两边的值并不是同一类型,那要怎么操作呢?一般来说有两种想法

  1. 初始化时,使用类型转换构造函数
  2. 其余情况,重载=

两者相互弥补,不可缺少!

通过使用这个,可以轻松显示的解决浅拷贝和深拷贝的问题

(默认是深拷贝)

注意:

  1. 赋值运算符只能重载为成员函数
  2. 赋值语句不是初始化语句,看两个例子

例子一

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){
    delete[] str;
    str = new char[strlen(s)+1];
    strcpy(str,s);
    cout << "called operator=" << endl;
    return *this;
}


int main(){
    String a;
    a = "abcabc";   //正常,输出called operator=
    cout << a.c_str() << endl;
    String b = "abcabc";    //错误
}

例子一

class String{
    private:
        char* str;
    public:
        String():str(new char[1]){str[0]=0;};
        const char* c_str(){return str;};
        String(const char* s):str(new char[strlen(s)+1]){
            strcpy(str,s);
        }
        ~String(){
            delete []str;
        }
};

int main(){
    String a;   
    a = "abcabc";   //出错
    cout << "a: " << a.c_str() << endl;    //输出空
    String b = "abcabc";    //正常
    cout << "b: " << b.c_str() << endl;
    return 0;
}

让我们来分析上面的两个例子:

  1. 例子一中为什么b的赋值是失败的?

    这里不是赋值,是初始化。等号不是初始化语句。

  2. 例子二中为什么a的赋值是错误的?

    首先,这地方不是初始化语句,报错是当然的,其次:

    gcc会默认使用类型转换函数创建临时变量来满足等号的使用需求,临时变量在销毁的时候会把指针删掉。因为通过使用默认等号连接的两个String的其中一个(临时变量)调用了析构函数,导致a中存储的指针是被删除过的野指针。

    像这样子的情况常有发生,所以必须重载运算符,不仅仅可以提高代码逻辑的稳定性,同时可以提高性能。

解决指针的问题

对于上面的问题,其正确写法,应该如下

Code

class String{
    private:
        char* str;
    public:
        String():str(new char[1]){str[0]=0;};
        const char* c_str(){return str;};
        String(const char* s):str(new char[strlen(s)+1]){
            cout << "transConstructor called" << endl;
            strcpy(str,s);
        }
        ~String(){
            delete []str;
        }
        String& operator=(String& b){
            cout << "String= called" << endl;
            delete[] str;
            str = new char[strlen(b.c_str())+1];
            strcpy(str,b.c_str());
            return *this;
        }
        String& operator=(const char* b){
            cout << "char*= called" << endl;
            delete[] str;
            str = new char[strlen(b)+1];
            strcpy(str,b);
            return *this;
        }

};

int main(){
    String a;   
    a = "abcabc";   //正常
    cout << "a: " << a.c_str() << endl;
    String b = "abcabc";    //正常
    cout << "b: " << b.c_str() << endl;
    return 0;
}

考虑另外一种情况,如果发生

String a;
// do something
a = a;

该怎么处理?

有两种处理方法:

  1. 临时变量法:先创建一个临时变量完成操作,再把值传到a的里面,代码实现如下

    String& String::operator=(String& b){
        cout << "String= called" << endl;
        char* tmp = new char[strlen(b.c_str())+1];
        strcpy(tmp,b.c_str());  
        delete[] str;
        str = tmp;
        return *this;
    }
  2. 判断法:检查this指针是否与b相同

    String& String::operator=(String& b){
        cout << "String= called" << endl;
        if( this == &b){
            return *this;
        }
        delete[] str;
        str = new char[strlen(b.c_str())+1];
        strcpy(str,b.c_str());
        return *this;
    }

推荐第二种,因为是显式的

返回值的类型

思考下面类型是否可以作为赋值函数返回值?

  • void可以么?
  • String可以么?
  • String&可以么?(废话当然可以,上面的代码用了)

为什么?

对运算符进行重载的时候,好的风格是应该尽量保留运算符原本的特性

对于赋值语句,有下面的两条重要规则

  1. a = b = c,其结果应是a = b = c,如果返回是void则赋值无法进行
  2. (a = b) = c,其结果应是a = c,与 b没有关系,如果返回是String类的话,会出现a = b,不等于c,不满足要求

重载为友元函数

考虑下面的几种情况

  1. 上面说过,符号的重载可以是成员函数,也可以是普通的全局函数,但是普通函数可以访问私有变量么?
  2. 当操作符号的前后变量颠倒时

    比如复数类Comple重载了与int的加法,所以c+5是正常的,但是5+c是错误的。这该怎么办?只能使用全局函数emm

所以运算符的重载函数也可以声明为友元,在类的public中添加友元声明即可

friend Complex operator+(double r, const Complex& c)

这样就解决了访问私有成员的问题了

流运算符

思考如下语句

cout << 5 << "this";

为什么左移运算符可以在这里使用?

因为coutiostream类的对象,这个类重载了左移/右移运算符,使其变成了 流插入/流提取。

但是一般的重载只能解决cout << 5或者cout << "this",怎么解决cout << 5 << "this"呢?

让返回值是cout&类型就好了,因此就得到了如下的代码

ostream& ostream::operator<<(int n){
    // do something;
    return *this;
}

所以这句话的本质,就可以理解为

cout.operator<<(5) . operator<<("this");

通过这样的思想,我们就可以使用ostream来完成一些类的操作

Code

class CStudent{
    public:
        int nAge;
};

ostream& operator<<(ostream& o,const CStudent& s){
    o << s.nAge;
    return o;
}

int main(){
    CStudent s;
    s.nAge = 4;
    cout << s << "hello";

    return 0;
}

注意!对于ostream类来说,只能使用全局函数来重定义!因为ostream的代码在头文件中完成。

如果想要访问私有变量,设置友元即可。

Code

class CStudent{
    private:
        int nAge;
    public:
        void setAge(int a){nAge=a;};
        friend ostream& operator<<(ostream&, const CStudent&);
};

ostream& operator<<(ostream& o,const CStudent& s){
    o << s.nAge;
    return o;
}

int main(){
    CStudent s;
    s.setAge(4);
    cout << s << "hello";

    return 0;
}

举个例子,通过使用重定向,完成复数类的输入与输出。

Code

class Complex {
    double real, imag;

  public:
    Complex(double r = 0, int i = 0) : real(r), imag(i){};
    friend ostream &operator<<(ostream &o, Complex &a);
    friend istream &operator>>(istream &o, Complex &a);
};

ostream &operator<<(ostream &o, Complex &a) {
    string tmp = to_string(a.real) + '+' + to_string(a.imag) + 'i';
    o << tmp;
    return o;
}

istream &operator>>(istream &o, Complex &a) {
    string tmp;
    o >> tmp;
    int pos = tmp.find("+",0);
    string stmp = tmp.substr(0,pos);
    a.real = atof(stmp.c_str());
    stmp = tmp.substr(pos+1,tmp.length()-pos-2);
    a.imag = atof(stmp.c_str());
    return o;
}

类型转换运算符

形如double()int()的都叫强制类型转换运算符,当然也可以重载,举个例子

class Complex {
    double real, imag;

  public:
    Complex(double r = 0, double i = 0) : real(r), imag(i){};
    operator double() { return real; }
};

比较灵异的事情是,如果当前类没有重载运算符号,但是却有类型转换函数,编译器会自动调用类型转换函数来满足运算符的需求。

比如说下面这个例子:

Code

class Complex {
    double real, imag;

  public:
    Complex(double r = 0, double i = 0) : real(r), imag(i){};
    operator double() { return real; }
};


int main() {
    Complex c(1.2, 1.3);
    cout << c << endl;
    double n = 2 + c;
    cout << n << endl;
    return 0;
}

代码能正常的输出

但是会严重影响性能,函数是在运行时才链接的,而不是在编译时链接的!最恶劣情况下,程序需要自己尝试所有的情况才可以决定哪个可以输出。

自增自减运算符

定义

自增自减有前后的区分,我们把前置运算符的作为一元运算符来看待,后置运算符作为二元运算符看待.简单来说就是:

++T;    //等价于    T.operator++()
T++;    //等价于    T.operator++(int )

注意:

  1. 后置函数的int 无意义!
  2. 全局函数要多声明一个this引用

在C语言中,满足前置++传引用,后置++回传对象,简单来说就是

  1. (++a)=1 //可以修改a的值
  2. (a++)=1 //失败,因为修改的是临时变量

所以前置++更快,省略了构造对象的过程

Code

class CDemo {
    public:
    int x;
    CDemo(int a=0):x(a){};
    CDemo& operator++(){x++;};
    CDemo operator++(int );
    operator int(){return x;}
    friend CDemo& operator--(CDemo& );
    friend CDemo operator--(CDemo&, int);
};

CDemo& CDemo::operator++(){
    ++x;
    return *this;
}

CDemo CDemo::operator++(int ){
    CDemo tmp(*this);
    ++x;
    return tmp;
}

CDemo& operator--(CDemo a){
    a.x++;
    return a;
}

CDemo& operator--(CDemo a,int ){
    CDemo tmp(a);
    a.x++;
    return tmp;
}

通过代码,你也可以明白++i是比i++快的

注意点

  1. C++不允许定义新的运算符
  2. 重载后的运算习惯应该满足日常生活
  3. 运算符的重载不改变运算符的优先级
  4. 以下运算符不能被重载

    • 点运算符 .
    • 星号运算符*
    • 命名空间运算符::
    • 三目表达式?:
    • 空间大小函数sizeof
  5. 重载()[]->,和=时,运算符重载函数必须声明为成员函数

长长短短,短短长长

通过使用运算符重载,手动实现vector

难点与解决方法

难点也就两个:

  1. 如何变长变短?
  2. 如何保存数据?

利用运算符重载和动态分配!

实现内容

  1. 重载[]=运算符
  2. 支持长度统计
  3. 实现push_back操作
  4. 支持不同类型的构造函数
小心size, index, mem_size 之间的关系

实现

Code

class CArray {
    int size; //个数
    int index;  //当前的实际指针
    int *ptr; //分配的数组
    int mem_size;//实际内存大小,用于优化

  public:
    CArray(int s=0);
    CArray(int s,int value);
    CArray(CArray& a);
    ~CArray();
    int getMemSize(){return mem_size;}
    void push_back(int n);
    CArray& operator=(const CArray& a);
    int length(){return size;}
    int& operator[](int x){
        if(x<=size) return ptr[x];
        else{
            cout << "out of index" << '\n';
        }
    }
};

CArray::CArray(int s):size(s),mem_size(s*2){
    if(s==0){
        ptr = NULL;
        index = 0;
    }else{
        ptr = new int[mem_size];
        index = size-1;
    }
}

CArray::CArray(int s,int value):size(s),mem_size(s*2){
    if(s==0){
        ptr = NULL;
        index = 0;
    }else{
        index = size-1;
        ptr = new int[mem_size];
        for(int i=0;i<=index;++i){
            ptr[i] = value;
        }
    }
}

CArray::CArray(CArray& a){
    if(!a.ptr){
        ptr = NULL;
        size = 0;
        mem_size = 0;
        index = 0;
        return;
    }

    ptr = new int[a.mem_size];
    memcpy(ptr,a.ptr,sizeof(int)*a.size);
    size = a.size;
    mem_size = a.mem_size;
    index = a.index;
}

CArray::~CArray(){
    if(ptr) delete[] ptr;
}

CArray& CArray::operator=(const CArray& a){

    //cout <<'\n' <<"s_size = " << a.size <<"\t d_size= "<< size << endl;
    if(ptr == a.ptr)
        return *this;   //解决 a = a的情况,详细原因见之前的文章

    if(!a.ptr){
        if(ptr) delete[] ptr;
        ptr = NULL;
        size = 0;
        index = 0;
        mem_size = 0;
        return *this;
    }
    
    //如果原有空间足够大,那么就不需要重新分配
    //如果原有空间不够,那么我们需要重新分配
    if(mem_size < a.size){  
        if(ptr) delete[] ptr;
        ptr = new int[a.mem_size];
        mem_size = a.mem_size;
    }

    memcpy(ptr, a.ptr, sizeof(int)*a.size);
    size = a.size;
    index = a.index;
    return *this;
}

void CArray::push_back(int x){
    //当前指针为null时
    if(!ptr){
        size = 1;
        mem_size = 2;
        index = 0;
        ptr = new int[2];
        ptr[index] = x;
        return;
    }

    //当前指针不为null时
    //当前大小能放下x时
    ++size;
    ++index;
    if(mem_size > size){
        ptr[index] = x;
    }else//放不下时
    {
        mem_size = size*2;
        int* tmpPtr = new int[mem_size];
        memcpy(tmpPtr,ptr,sizeof(int)*size);
        delete[] ptr;
        ptr = tmpPtr;
        ptr[index] = x;
    }

}

奇怪的MyString类(一)

Code

#include <cstring>
#include <iostream>
#include <string>
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;
    }

    MyString(const MyString &s) {
        if (s.p) {
            p = new char[strlen(s.p) + 1];
            strcpy(p, s.p);
        } else
            p = NULL;
    }
    void Copy(char *s) {
        if (p) delete[] p;
        if (s) {
            p = new char[strlen(s) + 1];
            strcpy(p, s);
        } else
            p = NULL;
    }
    MyString &operator=(const MyString &s) {
        if (s.p == p) return *this;
        if (p) delete[] p;
        if (s.p) {
            p = new char[strlen(s.p) + 1];
            strcpy(p, s.p);
        } else
            p = NULL;
        return *this;
    }
    MyString &operator=(const char *s) {
        if (p) delete[] p;
        if (s) {
            p = new char[strlen(s) + 1];
            strcpy(p, s);
        } else
            p = NULL;

        return *this;
    }
    friend ostream &operator<<(ostream &o, const MyString &s) {
        if (s.p) { o << s.p; }
        return o;
    }
};
int main() {
    char w1[200], w2[100];
    while (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;
    }
}

注意一下几点:

  1. 发生重载=(T& )时,一定要写if(*this == A) return *this

    避免回环问题

  2. 友元函数可以在类中直接展开
  3. 小心NULL

高精度加

懒的用自己写的了,用系统自带的string舒舒服服。

Code

class CHugeInt {

    // 在此处补充你的代码
  public:
    string num;
    int len;

    CHugeInt() : num(""), len(0){};
    CHugeInt(const char *x) {
        if (!x) {
            num = "";
            len = 0;
            return;
        };

        len = strlen(x);
        for (int i = len - 1; i >= 0; --i) { num += x[i]; }
    }

    CHugeInt(int x) {
        string tmp = to_string(x);
        len = tmp.size();
        num = "";
        for (int i = len - 1; i >= 0; --i) { num += tmp[i]; }
    }

    CHugeInt operator+(CHugeInt &a) {
        CHugeInt ans;
        int mmin;

        //最长的为基础
        if (len > a.len) {
            ans.num = num;
            mmin = a.len;
        } else {
            ans.num = a.num;
            mmin = len;
        }

        int upgrade = 0;
        int tmp;

        for (int i = 0; i < mmin; ++i) {
            tmp = num[i] - '0' + a.num[i] - '0' + upgrade;
            upgrade = tmp / 10;
            ans.num[i] = (tmp % 10) + '0';
        }

        while (upgrade > 0) {

            if (mmin >= ans.num.size()) {
                tmp = upgrade;
                ans.num += (tmp % 10) + '0';
            } else {
                tmp = ans.num[mmin] - '0' + upgrade;
                ans.num[mmin] = (tmp % 10) + '0';
            }

            upgrade = tmp / 10;
            mmin++;
        }
        ans.len = ans.num.size();
        return ans;
    }

    CHugeInt operator+(int a) {
        CHugeInt tmp(a);
        return this->operator+(tmp);
    }

    friend CHugeInt operator+(int x, CHugeInt &a) {
        CHugeInt tmp(x);
        return a.operator+(tmp);
    };

    CHugeInt &operator++() {
        int mmin = 1;
        int upgrade = 0;
        int tmp;

        for (int i = 0; i < mmin; ++i) {
            tmp = num[i] - '0' + 1 + upgrade;
            upgrade = tmp / 10;
            num[i] = (tmp % 10) + '0';
        }

        while (upgrade > 0) {

            if (mmin >= num.size()) {
                tmp = upgrade;
                num += (tmp % 10) + '0';
            } else {
                tmp = num[mmin] - '0' + upgrade;
                num[mmin] = (tmp % 10) + '0';
            }

            upgrade = tmp / 10;
            mmin++;
        }

        len = num.size();
        return *this;
    };

    CHugeInt operator++(int) {
        CHugeInt tmp(*this);
        this->operator++();
        return tmp;
    };

    CHugeInt operator+=(int a) {
        CHugeInt tmp(*this);
        for (int i = 1; i <= a; i++) this->operator++();
        return tmp;
    }

    friend ostream &operator<<(ostream &o, const CHugeInt &a) {
        string tmp = "";
        for (int i = a.len - 1; i >= 0; --i) { tmp += a.num[i]; }
        o << tmp;
        return o;
    }
};

Last modification:March 29th, 2020 at 03:19 pm
Compared with money, your comment could inspire me more.
相较于钱财,你的留言更能激发我创作的灵感