C++【8】(运算符重载)

211 阅读5分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

一、运算符重载

1.1 基本概念

运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。运算符重载(operator overloading)只是一种”语法上的方便”,也就是它只是另一种函数调用的方式。在C++中,可以定义一个处理类的新运算符。这种定义很像一个普通的函数定义,只是函数的名字由关键字operator及其紧跟的运算符组成。差别仅此而已。它像任何其他函数一样也是一个函数,当编译器遇到适当的模式时,就会调用这个函数。

  1. 运算符重载:给运算符重新赋予新的含义,原来的功能保留。

    注意:功能保留并不是说运算符有各种功能,而是需要我们自己实现原功能的逻辑运算符重载的本质,就是函数函数重载 例: operator=

  2. 重载的方法:定义一个运算符重载的函数,在需要执行对应的运算逻辑时,编译器自动找到对应的运算符重载函数

  3. 运算符重载函数的参数类型或个数由 运算符自身 决定

  4. 由运算符左侧的操作数来调用运算符重载函数

    例: operator=
    A = B;  //从编译器的角度  A.operator+(B)   ----左调右参
    
  5. 当没有定义运算符重载函数时,不能直接对==类的对象==进行相关操作

注意:

  • 运算符重载可以在全局实现,但是定义成全局的,对类的私有的成员访问不方便如果非要全局实现,最好用 ==friend==
  • 成员函数版本和全局函数版本只能实现一个(因为调用时,会有歧义)

2.2 可重载的运算符和不可重载的运算符

https://note.youdao.com/yws/public/resource/d23ff061138554293b27d88e32be9553/xmlnote/3E20465DDC6C4B86B2EE32E9ED28D5C3/669B3DEA993F4AFABB2703A5035C4FF5/84337

2.2.1 运算符重载的格式

2.2.1.1 双目运算符的重载

+ - * / %  &  |  ^  >  <  ==  !=   >=  <=
表达式:
    L # R
    L:做操作数
    R:右操作数
    #:运算符

    左操作数L : 既可以是一个左值 也可以是一个右值 
    右操作数L : 既可以是一个左值 也可以是一个右值
    表达式的结果:只能是右值
成员函数版本:
+ - * / %  &  |  ^
const 类名 operator#(const 类名 &R)const;

>  <  ==  !=   >=  <=
const bool operator#(const 类名 &R)const;
全局函数版本:
+ - * / %  &  |  ^
friend const 类名 operator#(const 类名 &L, const 类名 &R);

>  <  ==  !=   >=  <=
friend const bool operator#(const 类名 &L, const 类名 &R);

2.2.1.2 赋值类运算符的重载

  =  +=  -=  *=  /=  %=   &=  |=  ^=
    表达式:L # R
    左操作数:只能是左值
    右操作数:既可以是一个左值 也可以是一个右值
    表达式的结果:做操作数自身
成员函数版本:
类名 &operator#(const 类名 &R);
全局函数版本:
friend 类名 &operator#(类名 &L,const 类名 &R);

2.2.1.3 单目运算符的重载

-(负)  !(非)  ~(取反)
表达式:#R
操作数:	既可以是一个左值 也可以是一个右值
表达式的结果:只能是右值
成员函数版本:
const 类名 operator#(void)const{}
全局函数版本:
friend const 类名 operator#(const 类名 &R){}

2.2.1.4 自增/自减运算符重载

前自增/前自减
    ++i   --i
    表达式:		#R
    操作数:		只能是左值
    表达式的结果:	左值
成员函数版本:
类名 &operator#(void){}

全局函数版本:
friend 类名 &operator#(类名 &R){}
后自增/后自减
    i++   i--
    表达式:		R#
    操作数:		只能是左值
    表达式的结果:	右值
成员函数版本:
const 类名 operator#(int){}

全局函数版本:
friend const 类名 operator#(类名 &R,int){}

2.2.1.5 插入和提取运算符重载

<<(提取)   >>(插入)
需要用到  istream   ostream  两个类

namespace std{
    istream	cin;
    ostream cout;
}
插入和提取运算符的重载  是能是全局版本

<< 运算符的重载

cout<<a;  ----  cout.operator<<()

friend ostream &operator<<(ostream &x,const 类名 &R);
#include <iostream>
using namespace std;

class Time{
	private:
		int hour;
		int min;
		int sec;
	public:
		Time(){}
		Time(int h,int m,int s):hour(h),min(m),sec(s){}
		void show(){
			cout<<hour<<":"<<min<<":"<<sec<<endl;
		}
		friend const Time operator-(const Time &L, const Time &R);
};


const Time operator-(const Time &L, const Time &R){
	Time temp;
	temp.hour = L.hour - R.hour;
	temp.min = L.min - R.min;
	temp.sec = L.sec - R.sec;
	return temp;
}

int main(){
	Time t1(20,20,20);
	Time t2(10,15,3);
	Time t3 = t1 - t2;
	t3.show();

	return 0;
}

2.2.2 不能重载重载的运算符

  1. . 取成员运算符
  2. :: 作用域限定符
  3. ?: 三目运算符
  4. sizeof
  5. .* 成员指针运算符
  6. # 预处理符号

2.3 当类的成员是公有时编写运算符重载函数

#include <iostream>

using namespace std;

class Goods{
public:
    Goods(){};
    Goods(string name, int money, string date)
    {
        this->name = name;
        this->money = money;
        this->date = date;
    }

    string name;
    int money;
    string date;
};


void sumMoney(Goods &obj1, Goods &obj2)
{
    int sum = obj1.money + obj2.money;

    cout << "一共话费" << sum << "元" << endl;
}

//+ -运算符的重载
int operator+(Goods &obj1, Goods &obj2)
{
    int sum = obj1.money + obj2.money;

    cout << "一共话费" << sum << "元" << endl;

    return sum;
}

int operator-(Goods &obj1, Goods &obj2)
{
    int sub = obj1.money - obj2.money;

    cout << "价格差值为" << sub << "元" << endl;

    return sub;
}

void test1()
{
    Goods phone("iphone 13", 5999, "2021/9/16");
    Goods computer("Lenovo", 6888, "2020/12/12");

    sumMoney(phone, computer);

    //只要实现了运算符+和-的重载函数,那么以下表达式执行的时候会自动
    //调用对应的运算符重载函数
    int sum = phone + computer;
    int sub = phone - computer;
    cout << sum << ", " << sub << endl;

    //运算符重载函数也可以主动调用,但是一般不会这样操作
    //operator+(phone, computer);
}

//++运算符的重载函数

//后缀++的运算符重载函数,参数里面必须有一个int参数
int operator++(Goods &obj, int)
{
    //先使用后自增
    int n = obj.money;
    obj.money++;

    return n;
}

//前缀++的运算符重载函数
int operator++(Goods &obj)
{
    //先自增后使用
    obj.money++;
    int n = obj.money;

    return n;
}

void test2()
{
    Goods phone("iphone 13", 5999, "2021/9/16");
    Goods computer("Lenovo", 6888, "2020/12/12");

    int m1 = phone++;
    cout << m1 << ", " << phone.money << endl;
    int m2 = ++computer;
    cout << m2 << ", " << computer.money << endl;
}

int main()
{
    test2();

    return 0;
}

2.4 当类的成员是私有时编写运算符重载函数

  • 方法1:使用友元函数实现运算符重载
  • 方法2:类内定义公有的成员函数实现运算符重载

2.4.1 使用友元函数实现运算符重载

#include <iostream>

using namespace std;

class Goods{
    friend int operator+(Goods &obj1, Goods &obj2);
    friend int operator-(Goods &obj1, Goods &obj2);
    friend int operator++(Goods &obj, int);
    friend int operator++(Goods &obj);
public:
    Goods(){};
    Goods(string name, int money, string date)
    {
        this->name = name;
        this->money = money;
        this->date = date;
    }
    int getMoney()
    {
        return this->money;
    }
private:
    string name;
    int money;
    string date;
};

//+ -运算符的重载
int operator+(Goods &obj1, Goods &obj2)
{
    int sum = obj1.money + obj2.money;

    cout << "一共话费" << sum << "元" << endl;

    return sum;
}

int operator-(Goods &obj1, Goods &obj2)
{
    int sub = obj1.money - obj2.money;

    cout << "价格差值为" << sub << "元" << endl;

    return sub;
}

void test1()
{
    Goods phone("iphone 13", 5999, "2021/9/16");
    Goods computer("Lenovo", 6888, "2020/12/12");

    int sum = phone + computer;
    int sub = phone - computer;
    cout << sum << ", " << sub << endl;
}

//++运算符的重载函数
int operator++(Goods &obj, int)
{
    int n = obj.money;
    obj.money++;

    return n;
}

//前缀++的运算符重载函数
int operator++(Goods &obj)
{
    obj.money++;
    int n = obj.money;

    return n;
}

void test2()
{
    Goods phone("iphone 13", 5999, "2021/9/16");
    Goods computer("Lenovo", 6888, "2020/12/12");

    int m1 = phone++;
    cout << m1 << ", " << phone.getMoney() << endl;
    int m2 = ++computer;
    cout << m2 << ", " << computer.getMoney() << endl;
}

int main()
{
    test1();

    return 0;
}

2.4.2 类内定义公有的成员函数实现运算符重载

#include <iostream>

using namespace std;

class Goods{
public:
    Goods(){};
    Goods(string name, int money, string date)
    {
        this->name = name;
        this->money = money;
        this->date = date;
    }

    //如果将成员函数实现运算符重载,一定注意是调用谁的运算符重载函数,
    //因为此时是成员函数,这个对象的成员变量是可以任意操作的
    int operator+(Goods &obj)
    {
        int sum = this->money + obj.money;
        return sum;
    }

    //后缀++的重载函数
    int operator++(int)
    {
        int n = this->money;
        this->money++;

        return n;
    }

    //前缀++的重载函数
    int operator++()
    {
        this->money++;
        int n = this->money;

        return n;
    }

private:
    string name;
    int money;
    string date;
};

void test1()
{
    Goods phone("iphone 13", 5999, "2021/9/16");
    Goods computer("Lenovo", 6888, "2020/12/12");

    int sum = phone + computer;
    cout << sum << endl;

    int add = phone++;
    int sub = ++computer;
    cout << add << ", " << sub;
}

int main()
{
    test1();

    return 0;
}

2.4.3 等号=的运算符重载函数

#include <iostream>
#include <string.h>

using namespace std;

//当成员变量中有指针时,构造、析构和拷贝构造的书写

class Person{
public:
    //构造函数
    Person()
    {
        cout << "无参构造函数" << endl;
        p_id = 0;
        p_name = NULL;
        p_score = 0;
        p_address = NULL;
    }
    Person(int id, char *name, int score, char *address)
    {
        cout << "有参构造函数" << endl;
        p_id = id;

        p_name = new char[strlen(name) + 1];
        strcpy(p_name, name);

        p_score = score;

        p_address = new char[strlen(address) + 1];
        strcpy(p_address, address);
    }

    //析构函数
    //释放堆区的空间
    ~Person()
    {
        cout << "析构函数" << endl;
        if(p_name != NULL)
        {
            delete []p_name;
            p_name = NULL;
        }

        if(p_address != NULL)
        {
            delete []p_address;
            p_address = NULL;
        }
    }

    //拷贝构造函数
    //拷贝构造函数里面需要对指针变量重新开辟空间,防止浅拷贝
    Person(const Person &obj)
    {
        cout << "拷贝构造函数" << endl;

        p_id = obj.p_id;

        p_name = new char[strlen(obj.p_name) + 1];
        strcpy(p_name, obj.p_name);

        p_score = obj.p_score;

        p_address = new char[strlen(obj.p_address) + 1];
        strcpy(p_address, obj.p_address);
    }

    void printMsg()
    {
        cout << p_id << ", ";
        cout << p_name << ", ";
        cout << p_score << ", ";
        cout << p_address << endl;
    }

    void operator=(Person &obj)
    {
        this->p_id = obj.p_id;

        if(this->p_name == NULL)
        {
            this->p_name = new char[strlen(obj.p_name) + 1];
        }
        else
        {
            delete []this->p_name;
            this->p_name = new char[strlen(obj.p_name) + 1];
        }
        strcpy(this->p_name, obj.p_name);

        this->p_score = obj.p_score;

        if(this->p_address == NULL)
        {
            this->p_address = new char[strlen(obj.p_address) + 1];
        }
        else
        {
            delete []this->p_address;
            this->p_address = new char[strlen(obj.p_address) + 1];
        }
        strcpy(this->p_address, obj.p_address);
    }

private:
    int p_id;
    char *p_name;
    int p_score;
    char *p_address;
};

void test1()
{
    Person p1(1001, "张三", 90, "北京");
    p1.printMsg();

    Person p2 = p1;
    p2.printMsg();

    Person p3(1002, "李四", 100, "北京");
    p3.printMsg();
    //当对象定义完毕之后,同另一个对象给当前对象赋值,
    //会调用默认的运算符=的重载函数,这个函数内部默认就是值传递
    //但是现在成员变量中有指针变量,如果值传递,对象释放空间调用
    //析构函数就会将同一块空间释放两次,出现浅拷贝的错误,
    //所以需要重写运算符=的重载函数
    p3 = p1;
    p3.printMsg();
}

int main()
{
    test1();

    return 0;
}

2.4.3 练习:自己实现字符串类

mystring.h

#ifndef MYSTRING_H
#define MYSTRING_H

#include <iostream>
#include <cstring>

using namespace std;

class MyString
{
public:
    MyString();
    MyString(const char *str);
    MyString(const MyString &obj);

    //[]重载
    char &operator[](int index);

    //=重载
    MyString& operator=(const char * str);
    MyString& operator=(const MyString& str);

    //字符串拼接 重载+号
    MyString operator+(const char * str );
    MyString operator+(const MyString& str);

    //字符串比较
    bool operator== (const char * str);
    bool operator== (const MyString& str);

    void StringShow();


private:
    char *pString;
    int m_size;
};

#endif // MYSTRING_H

mystring.cpp

#include "mystring.h"

MyString::MyString()
{
    this->pString = NULL;
    this->m_size = 0;
}

MyString::MyString(const char *str)
{
    this->pString = new char[strlen(str) + 1];
    strcpy(this->pString, str);

    this->m_size = strlen(str);
}

MyString::MyString(const MyString &obj)
{
    this->pString = new char[strlen(obj.pString) + 1];
    strcpy(this->pString, obj.pString);

    this->m_size = obj.m_size;
}

char &MyString::operator[](int index)
{
    static char ch = -1;
    if(index >= 0 && index < this->m_size)
    {
        return this->pString[index];
    }
    else
    {
        printf("越界操作了\n");
        return ch;
    }
}

MyString &MyString::operator=(const char *str)
{
    if(this->pString != NULL)
    {
        delete []this->pString;
        this->pString = new char[strlen(str) + 1];
    }
    else
    {
        this->pString = new char[strlen(str) + 1];
    }

    strcpy(this->pString, str);
    this->m_size = strlen(str);

    return *this;
}

MyString &MyString::operator=(const MyString &str)
{
    if(this->pString != NULL)
    {
        delete []this->pString;
        this->pString = new char[strlen(str.pString) + 1];
    }
    else
    {
        this->pString = new char[strlen(str.pString) + 1];
    }

    strcpy(this->pString, str.pString);
    this->m_size = str.m_size;

    return *this;
}

MyString MyString::operator+(const char *str)
{
    if(this->pString != NULL)
    {
        char *s = new char[this->m_size + 1];
        strcpy(s, this->pString);

        delete []this->pString;
        this->pString = new char[this->m_size + strlen(str) + 1];

        strcpy(this->pString, s);
        strcat(this->pString, str);

        this->m_size += strlen(str);

        delete []s;
    }
    else
    {
        this->pString = new char[strlen(str) + 1];
        strcpy(this->pString, str);

        this->m_size = strlen(str);
    }

    return *this;
}

MyString MyString::operator+(const MyString &str)
{
    if(this->pString != NULL)
    {
        char *s = new char[this->m_size + 1];
        strcpy(s, this->pString);

        delete []this->pString;
        this->pString = new char[this->m_size + strlen(str.pString) + 1];

        strcpy(this->pString, s);
        strcat(this->pString, str.pString);

        this->m_size += strlen(str.pString);

        delete []s;
    }
    else
    {
        this->pString = new char[strlen(str.pString) + 1];
        strcpy(this->pString, str.pString);

        this->m_size = strlen(str.pString);
    }

    return *this;
}

bool MyString::operator==(const char *str)
{
    if(strcmp(this->pString, str) == 0)
    {
        return true;
    }
    else
    {
        return false;
    }
}

bool MyString::operator==(const MyString &str)
{
    if(strcmp(this->pString, str.pString) == 0)
    {
        return true;
    }
    else
    {
        return false;
    }
}

void MyString::StringShow()
{
    cout << "长度:" << this->m_size << ", ";
    cout << "内容:" << this->pString << endl;

}

main.cpp

#include <iostream>
#include "mystring.h"

using namespace std;

int main()
{
    MyString m1("hello world");
    m1.StringShow();

    MyString m2(m1);
    m2.StringShow();

    cout << m2[3] << endl;
    cout << m2[0] << endl;
    cout << m2[30] << endl;

    m2 = "nihao beijing";
    m2.StringShow();

    MyString m3;
    m3 = m2 = m1;
    m1.StringShow();
    m2.StringShow();
    m3.StringShow();

    cout << "********************" << endl;

    MyString m4;
    m4 = m4 + "hello world";
    m4.StringShow();

    MyString m5("hahahaha");
    m5.StringShow();
    m5 = m5 + "hello world";
    m5.StringShow();

    MyString m6("88888888");
    m6 = m6 + m5;
    m6.StringShow();

    if(m6 == m5)
    {
        cout << "m6 = m5" << endl;
    }
    else
    {
        cout << "m6 != m5" << endl;
    }

    return 0;
}

作业1:实现<<的运算符重载函数

#include <iostream>

using namespace std;

class Person{
    friend ostream &operator<<(ostream &cout, Person &obj);
public:
    Person(){}
    Person(int id, string name, char sex, int score):
        id(id),name(name),sex(sex),score(score){}
    void printMsg()
    {
        cout << this->id << ", ";
        cout << this->name << ", ";
        cout << this->sex << ", ";
        cout << this->score << endl;
    }
private:
    int id;
    string name;
    char sex;
    int score;
};

ostream &operator<<(ostream &cout, Person &obj)
{
    cout << obj.id << ", ";
    cout << obj.name << ", ";
    cout << obj.sex << ", ";
    cout << obj.score;

    return cout;
}

int main()
{
    Person p1(1001, "张三", 'M', 90);
    Person p2(1002, "里斯", 'W', 87);

    cout << p1 << endl << p2 << endl;
}