运算符重载

本文详细介绍了C++中的运算符重载,包括加减法、赋值、关系、一元运算符以及数组下标的重载。讲解了重载的目的、规则和注意事项,并给出了各种运算符的成员函数和全局函数实现方式。强调了赋值运算符重载时深浅拷贝的问题,以及关系运算符重载和前置、后置++运算符重载的实现细节。
摘要由CSDN通过智能技术生成

目录

运算符重载

加,减法运算符

全局方式(建议)

成员函数方式

左移运算符重载

 全局函数方式(only)

右移运算符

全局函数方式(only)

赋值运算符重载

默认的赋值运算符重载

成员函数(only)

关系运算符重载

前置++和后置++运算符重载

成员函数重载(建议)

临时对象问题

数组下标重载

成员函数重载(only)


运算符重载

定义:运算符重载就是对已有的运算符进行重新定义,赋予其另一种功能,以适应不同的数据类型

目的:让语法更加简洁

本质:调用另一种函数(是编译器去调用)

关键字:operator

对象名 operator运算符(对象)

 通用:

        1.一般运算符都重载了基础数据类型

        2.作为类的成员函数,重载运算符只能有一个参数

注:

一元运算符:只有一个操作数,比如,a++,a--

二元运算符:有两个操作数,比如,a+b,a-b,a/b,a*b

注意:

        1.除了赋值号(=)外,基类中被重载的操作符都将被派生类继承

        2.<< 和 >> 操作符最好通过友元函数进行重载

        3.不要重载 && 和 || 操作符,因为无法实现短路规则

        4.运算符重载不能改变本来寓意,不能改变基础类型寓意☆☆☆

加,减法运算符

加减法运算符重载差不多,这里只展示加法运算符重载,无论是加法和减法,都是第一个参数减或加第二个参数

两个不同的对象相加建议使用全局函数

两个相同的对象相加那么建议使用成员变量

全局方式(建议)

class Maker
{
public:
    Maker(int id, int age)
    {
        this->id = id;
        this->age = age;
    }
public:
    int id;
    int age;
};
//全局方式,需要两个参数 //2.编译器调用这个函数
Maker operator+(Maker &p1,Maker &p2)//3.编译器检查参数是否对应,p1,加号左边的,p2,加号右边的
{
    Maker temp(p1.id + p2.id, p1.age + p2.age);
    return temp;
}
void test01()
{ 
    Maker m1(1, 20);
    Maker m2(2, 22);
    //+也叫双目运算符
    Maker m3 = m1 + m2; //1.编译器看到两个对象相加,那么编译器机会去找有没有operator函数
    Maker m4 = m1 + m2+m3;//是从左到右运算,然后赋值给m4

    cout << "id:" << m4.id << " age:" << m4.age << endl;
}

成员函数方式

class Maker
{
public:
    Maker(int id, int age)
    {
        this->id = id;
        this->age = age;
    }
    //写成成员函数,那么只需要一个参数,这个参数是加号的右边
    Maker operator+(Maker &m2)
    {
        Maker temp(this->id + m2.id, this->age + m2.age);
        return temp;
    }
public:
    int id;
    int age;
};
void test01()
{ 
    Maker m1(1, 20);
    Maker m2(2, 22);
    //+也叫双目运算符
    Maker m3 = m1 + m2; //1.编译器看到两个对象相加,那么编译器机会去找有没有operator函数
    Maker m4 = m1 + m2+m3;//是从左到右运算,然后赋值给m4

    cout << "id:" << m4.id << " age:" << m4.age << endl;
}

解释:

1.为什么这里的operator函数返回的不是引用类型?

因为:不能返回局部变量的引用。

2.为什么这里的operator函数的参数是引用类型?

因为:参数是引用类型可以避免拷贝,节省时间和空间。

左移运算符重载

<<运算符重载,一般是用来输出对象数据的。

注意:左移运算符重载不能以成员函数的形式重载因为:左移运算符的形式是cout<<m,如果以成员函数的形式重载,因为类的成员函数重载只能有一个参数,这样就会形成m<<cout的局面,所以是错误的。

 全局函数方式(only)

class Maker
{
    friend ostream& operator<<(ostream& out, Maker& m);
public:
    Maker(int id,string name)
    {
        this->id = id;
        this->name = name;
    }
    Maker operator-(Maker& m1)
    {
        Maker temp(this->id - m1.id,this->name);
        return temp;
    }
private:
    int id;
    string name;
};
//1.形参和实参是一个对象
//2.不能改变库类中的代码,所以函数返回要用ostream &
//3.ostream中把拷贝函数私有化了
//4.如果要和endl一起使用,那必须返回ostream的对象
//5.左移运算符通常用来输出数据
ostream& operator<<(ostream &out,Maker &m)
{
    cout << m.id << endl;
    cout << m.name << endl;
    return out;
}

解释:

1.为什么这里的operator函数返回的是引用类型?

因为:返回的不是局部对象

2.为什么这里的operator函数要返回ostream对象,而不能返回void呢?

因为:一般输出是cout<<n<<endl;这样子输出,要与endl一起使用,经查询源代码得知,<<重载运算符的左边是ostream类型的对象,所以为了保证cout<<n<<endl;输出不会出错,operator函数要返回ostream对象

3.为什么这里的operator函数的第一个参数要返回引用类型?

因为:ostream对象是库类中的代码,不能改变库类中的代码,且ostream中把拷贝函数私有化了,即不允许拷贝行为的发生,所以要用引用类型。

右移运算符

>>运算符重载,一般是用来输入对象数据的。

注意:右移运算符重载也不能以成员函数的形式重载,理由如上。

全局函数方式(only)

class Maker
{
    friend istream& operator>>(istream& in, Maker& m);
private:
    string name;
    int age;
public:
    Maker(string name, int age)
    {
        this->age = age;
        this->name = name;
    }
    int getAge()
    {
        return age;
    }
};

istream &operator>>(istream& in, Maker& m)
{
    in >> m.age>> m.name;
    return in;
}

赋值运算符重载

通常是给对象与对象间数据进行赋值操作,使用赋值运算符重载,需要考虑深浅重载的问题

默认的赋值运算符重载

编译器会提供一个默认的赋值运算符重载,一般就是简单的赋值。

class Maker
{
public:
    int id;
    int age;
public:
    Maker()
    {
        id = 0;
        age = 0;
    }
    Maker(int id, int age)
    {
        this->age = age;
        this->id = id;
    }
};
void test()
{
    Maker m1(10, 20);
    Maker m2;

    m2 = m1;//赋值操作
    //默认的赋值运算符重载函数进行了简单的赋值操作
    cout << m2.id << " " << m2.age << endl;
}

 但如同深浅拷贝一样,直接简单的赋值容易产生错误。

比如:

class Student
{
public:
    Student(const char *name)
    {
        pName = new char[strlen(name)+1];
        strcpy_s(pName, strlen(name) + 1, name);
    }
    ~Student()
    {
        if (pName != NULL)
        {
            delete[] pName;
            pName = NULL;
        }
    }
    //拷贝函数,避免浅拷贝
    Student(const Student& stu)
    {
        pName = new char[strlen(stu.pName) + 1];
        strcpy_s(pName, strlen(stu.pName) + 1, stu.pName);
    }
    void printStudent()
    {
        cout << "Name:" << pName << endl;
    }
public:
    char* pName;
};

void test02()
{
    Student s1("悟空");
    Student s2("小玲");

    s1.printStudent();
    s2.printStudent();

    s1 = s2;//简单赋值,s1.pName=s2.pName;,会导致s1.pName原来所指向的内存泄露了,然后调用析构函数,s2.pname指向的空间被释放了两次
    s1.printStudent();
    s2.printStudent();
    
}

如以上代码所示,如果我进行的是简单的赋值,那么会执行s1.pName=s2.pName,这样会直接把原s1存储的0x1的地址给直接覆盖掉,导致0x1内存的泄露,到后面函数结束s1和s2一起执行析构函数,又会对同一片空间释放两次

成员函数(only)

所以,为了解决默认赋值重载造成的问题,我们需要重写赋值运算符重载函数。

步骤:

        1.不清楚this->pName指向的空间是否装得下s中的数据,所以得先释放this指向的空间

        2.申请堆区空间

        3.拷贝数据

        4.返回对象本身(this指针的使用)

class Student
{
public:
    Student(const char *name)
    {
        pName = new char[strlen(name)+1];
        strcpy_s(pName, strlen(name) + 1, name);
    }
    ~Student()
    {
        if (pName != NULL)
        {
            delete[] pName;
            pName = NULL;
        }
    }
    Student(const Student& stu)
    {
        pName = new char[strlen(stu.pName) + 1];
        strcpy_s(pName, strlen(stu.pName) + 1, stu.pName);
    }
    Student &operator=(const Student& s)
    {
        //1.不清楚this->pName指向的空间是否装得下s中的数据,所以得先释放this指向的空间
        if (pName != NULL)
        {
            delete[] pName;
            pName = NULL;
        }
        //2.申请堆区空间
        pName= new char[strlen(s.pName) + 1];
        //3.拷贝数据
        strcpy_s(this->pName, strlen(s.pName) + 1, s.pName);
        //4.返回对象本身
        return *this;
    }
    void printStudent()
    {
        cout << "Name:" << pName << endl;
    }
public:
    char* pName;
};

 解释:

        1.为什么这里的operator函数要返回引用?

        因为:赋值操作是存在s1=s2=s3的,这样的话应该是先s2=s3,重载运算符函数返回的是s2,再s1=s2,重载运算符函数返回的是s1,如果不用引用,那么函数会拷贝一份临时对象然后返回,这样的话返回的就不是原来的s2了,而是这个临时对象了

        2.为什么这里的operator函数要使用const常量参数?

        因为:常量参数确保了对象不会被修改

关系运算符重载

成员函数和全局函数都可以

class Maker
{
public:
    int id;
    int age;
public:
    Maker()
    {
        id = 0;
        age = 0;
    }
    Maker(int id, int age)
    {
        this->age = age;
        this->id = id;
    }
    //关系运算符重载
    bool operator==(Maker& m)
    {
        if (this->id == m.id)
            return true;
        return false;
    }
};

void test()
{
    Maker p1(1, 20);
    Maker p2;
    if (p1 == p2)
    {
        cout << "真" << endl;
    }
    else
        cout << "假" << endl;
}

前置++和后置++运算符重载

通常用来对对象的数据进行++操作

成员函数重载(建议)

前置++和后置++是一元运算符,写在成员函数里时,不需要参数,但是为了区分是前置++还是后置++,就需要占位参数,有占位参数的是后置加加,没占位参数的是前置加加,这里的占位参数没有意义,只是用来区分的。

class Maker
{
    friend ostream& operator<<(ostream& out,const Maker& m);
private:
    int a;
public:
    Maker(int a)
    {
        this->a = a;
    }
    //前置加加,++a
    Maker& operator++()
    {
        ++this->a;
        return *this;
    }
    //后置加加,a++
    Maker operator++(int)//占位参数,必须是int
    {
        //先返回,后加加
        Maker tmp(*this);//1.*this里面的值a是为2
        ++this->a;//这个对象的a是为3的
        return tmp;
    }
};
ostream& operator<<(ostream& out,const Maker& m)
{
    cout << m.a << endl;
    return out;
}
void test02()
{
    Maker m1(1);
    cout << m1 << endl;//1
    cout << ++m1 << endl;//2
    cout << m1++ << endl;//2  这里返回的是tmp的拷贝,也就是临时对象
    cout << m1 << endl;//3  这里返回的是++this->a的对象
}

后置++的步骤(要做到先返回,后加加):

        1.先申请一个临时变量令其与要加加的对象的值相等,这样就能先保持这个值不变

        2.让对象加加

        3.返回临时变量

所以可知后置++返回的是临时变量,临时变量具有很多的不确定性(解释1.),所以,如果可以,++的选择前置++要优先于后置++

解释:

临时对象问题

        1.为什么这里的ostream& operator<<(ostream& out,const Maker& m)第二个参数要使用const?(临时对象问题)

        因为:这里是一个临时对象的问题,在test02()函数中,第四行,当我使用了m1++后,因为是后置++所以后置++重载运算符函数结束后返回的是一个临时对象,而临时对象"一般"不能被修改(C++11引入右值引用&&之后,其实临时对象也可以被修改的),临时对象只能用常量引用绑定,临时变量也只能用常量引用绑定,比如:int a=10;int &b=a++;这里a++后产生的就是一个临时变量。

数组下标重载

就是对 [ ] 进行重载,目的是让对象里的数据可以像数组一样操作

成员函数重载(only)

.h文件
class MyArray
{
public:
	MyArray();
	~MyArray();
	MyArray(const MyArray& arr);
	MyArray(int capacity, int val = 0);
	MyArray& operator=(const MyArray& m);
	int& operator[](int index);

private:
	int* pArray;//指向堆区空间,储存数据
	int mSize;//元素个数
	int mCapacity;//容量
};
.cpp文件
MyArray::MyArray()
{
	mSize = 0;
	mCapacity = 20;
	pArray = new int[mCapacity];
	for (int i = 0; i < mCapacity; i++)
		pArray[i] = 0;
}
MyArray::~MyArray()
{
	if (pArray != NULL)
	{
		delete[] pArray;
		pArray = NULL;
	}
}
MyArray::MyArray(const MyArray& arr)
{
	mSize = arr.mSize;
	mCapacity = arr.mCapacity;
	pArray = new int[mCapacity];
	for (int i = 0; i < mSize; i++)
	{
		pArray[i] = arr.pArray[i];
	}
}
MyArray::MyArray(int capacity, int val)//申明和定义不能同时有函数的默认参数
{
	this->mCapacity = capacity;
	this->mSize = capacity;
	this->pArray = new int[mCapacity];
	for (int i = 0; i < mSize; i++)
		pArray[i] = val;
}

MyArray& MyArray::operator=(const MyArray& m)
{
	if (this->pArray != NULL)
	{
		delete[] this->pArray;
		this->pArray = NULL;
	}
	this->pArray = new int[m.mCapacity];
	this->mCapacity = m.mCapacity;
	this->mSize = m.mSize;
	for (int i = 0; i < this->mSize; i++)
	{
		this->pArray[i] = m.pArray[i];
	}
	return *this;
}

//引用,能当左右值
int& MyArray::operator[](int index)
{
    //赋值的时候++,不赋值的时候不++,即返回的是左值的时候++,右值的时候不加加
	if (this->mSize<=index)
	{
		this->mSize++;
	}
	return this->pArray[index];
}
.cpp
void test02()
{
    MyArray arr;
    for (int i = 0; i < 20; i++)
    {
        arr[i] = i + 10;
    }
    MyArray arr2;
    arr2 = arr;

    for (int i = 0; i < 20; i++)
    {
        cout << arr2[i] << " ";
    }
    cout << endl;
}

注意:在这个函数里,小心一个BUG,就是当你实例化对象时(test02第1行),自动调用的是无参构造函数,无参构造函数里size=0,而你实例化对象后,当你运用数组下标重载直接进行赋值(test02第4行),而不同步修改size的值时,你的size就一直都会是0,后面当你调用赋值运算符重载时,把arr的数组数据传给arr2时(test02第7行),到赋值运算符重载函数的for循环那里,因为size一直都是0,所以这个for循环不会执行,而导致arr2的数据全部错误。这里解决了这个错误,当你运用数组下标重载,会同步修改size的值,不会产生报错

解释:

        1.为什么返回的是int类型?

        因为:是数组的引用,即arr[]操作的是什么,就返回什么值,比如这里,arr[]操作的是MyArray类中的int *pArray类型,所以arr[]要返回int类型

        2.为什么参数也是int类型?

        因为:arr[],[]里的是int类型。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值