C++ 运算符重载(超详细)

我们知道重载是编译器允许两者重名,有了符号重载,我们就可以自定义符号

运算符重载的实质:将运算符重载为一个函数,使用运算符就是对重载函数的调用。

注意事项

1、运算符可以重载为全局函数,此时函数的参数个数 = 运算符的操作数个数

2、运算符可以重载为成员函数,此时函数的参数个数 = 运算符的操作数个数 - 1

(运算符的操作数有一个要作为对象,调用该成员函数)

3、运算符重载不改变运算符的优先级

4、有些运算符不能被重载: .     .*  ::     ?:     sizeof

5、运算符 = () [ ]    ->   只能重载为成员函数,不能重载为全局函数

语法: operator + 符号

其实就是写一个函数,operator + 符号 是函数名,函数体里写想要实现的操作

例如: void operator+(int a) { ... }   //重载了+号运算符

加号运算符重载(+)

        我们先定义一个Person类,里面有2个成员变量,现在我们创建两个对象,再令它们相加,目的是令它们的成员变量各自相加,可以吗?不可以,如下:

class Person{
public:
    Person(){    //初始化数据
        num_1 = 1;
        num_2 = 2;
    }
    int num_1;
    int num_2;
};

Person p1, p2;
Person p = p1 + p2;
/*我们的目的是让:
p.num_1 = p1.num_1 + p2.num_1
p.num_2 = p1.num_2 + p2.num_2
显然这不行
*/

所以我们可以重载一下加号运算符,就可以实现上述操作:

1、全局函数重载

下面这段代码我们定义一个全局函数func,它可以实现传入两个对象,在内部将它们的成员各自相加,然后再返回一个Person类的类型。

Person func(Person& p1, Person& p2){
    Person temp;
    temp.num_1 = p1.num_1 + p2.num_1;
	temp.num_2 = p1.num_2 + p2.num_2;
	return temp;
}

然后我们只要把函数名 func 换成 operator+ 就大功告成了,如下:

Person operator+(Person& p1, Person& p2){
    Person temp;
    temp.num_1 = p1.num_1 + p2.num_1;
	temp.num_2 = p1.num_2 + p2.num_2;
	return temp;
}

这样编译器就已经重载好了+运算符,为了让大家更明白一点,我们先像平时一样调用这个函数,然后再写简化后的版本:

Person p1, p2;
//老实调用函数:
Person p = operator+(p1, p2);
cout << p.num_1 << p.num_2 << endl;   //输出 2 4
//简化后直接操作:
Person p = p1 + p2;
cout << p.num_1 << p.num_2 << endl;   //输出 2 4

2、成员函数重载

成员函数重载的话,调用该函数的时候,就需要用一个对象来调用,那么传入的参数就变成一个对象了(全局函数是两个),不理解的可以看看代码思考一下:

class Person {
public:
	Person() {   //初始化属性值
		num_1 = 1;
		num_2 = 2;
	}
    //成员函数重载
	Person operator+(Person& p2) {
		Person temp;
		temp.num_1 = this->num_1 + p2.num_1;   //this指针指向调用的对象
		temp.num_2 = this->num_2 + p2.num_2;   //相当于是上面的p1
		return temp;
	}
    //属性
	int num_1;
	int num_2;
};

Person p1, p2;
//老实调用函数:
Person p = p1.operator+(p2);
//简化后:
Person p = p1 + p2;

我们既然能重载+号,也同样可以重载 - * / % ,只要在operator后面加上相应的符号即可

左移运算符重载(<<)

        同样以上面的Person类举例,原先 cout << 只能输出一个数据,要想输出多个就要写多个数据,现在我们重载它,让它能够实现,输入一个对象,输出对象的所以属性:

class Person{
public:
    Person(){
        num_1 = 1;
        num_2 = 2;
    }
    int num_1;
    int num_2;
};

Person p;
cout << p;   //企图输出 p.num_1 和 p.num_2

注意!cout 是输出流类ostream实例化出的一个对象,它的数据类型是ostream,同理 cin 是istream,感兴趣的可以去了解一下

1、全局函数重载

注意 cout 要写在前面,后面会讲原因

void operator<<(ostream& cout, Person& p){
    cout << p.num_1 << p.num_2 << endl;
}
Person p;
//老实调用函数:
operator<<(cout, p);  //输出 1 2
//简化:
cout << p;   //输出 1 2

        但是上面的函数返回的是void,那么就不能一直输出,比如 cout << p << endl 就不行,只能到 cout << p,后面不能再加东西。因为输出的必须是输出流类型,才能一直输出下去,所以我们可以让函数返回一个输出流类型,这样就可以一直输出了。

//改进后的代码,函数返回输出流类型
ostream& operator<<(ostream& cout, Person& p){
    cout << p.num_1 << p.num_2 << endl;
    return cout;
}
Person p;
cout << p << endl;   //输出 1 2 换行

2、成员函数重载

        我们会发现一个问题:如果用成员函数重载<< ,那么需要一个对象来调用这个函数,原先全局函数的参数有两个:cout 和 p,现在就只剩一个了:cout ,因为对象p需要写在函数外面来调用这个函数,此时就会出现上面没说明的问题:cout 一定要写在前面

        下面说明为什么:如果cout写在后面,也是可以的,但是想要输出p,我们就要这么写:

p << cout,两者的顺序要反一下,同理,用成员函数重载的话,cout就一定在p后面了

class Person {
public:
    Person(){
        num_1 = 1;
        num_2 = 2;
    }
    ostream& operator<<(ostream& cout){
        cout << num_1 << num_2 << endl;
        return cout;
    }
    int num_1;
    int num_2;
};

Person p;
//老实调用函数:
p.operator<<(cout);   //从这里可以看出 cout 一定只能在 p 的后面
//简化:
p << cout << endl;

tips:不要用成员函数重载左移运算符

递增运算符重载(++)

tips:最好写在类内(方便),即成员函数

我们知道++a的操作,会让a + 1,现在我们想实现,让a每次都加10,并且只能自己定义一个整型数据,只能在成员函数内重载++运算符(--也是一样)

1、前置++
class Person {
public:
    //初始化
    Person() {
        a = 1;
    }
    //重载++运算符
    void operator++() {
        a += 10;
    }
    //属性
    int a;
};
//重载<<运算符,方便输出对象
ostream& operator<<(ostream& cout, Person& p) {
    cout << p.a;
    return cout;
}

Person p;
//老实调用函数:(正确的)
p.operator++();
cout << p << endl;   //输出 11
//简化:(其实不能这么写,会报错)
cout << ++p << endl;  //错误的

我们发现最后一行简化版的代码是错误的,为什么呢?

解释:

        最后一行代码中的 << 是重载后的 <<,它要传入的参数是 Person 类,所以 cout << p 是可行的,但是 cout << ++p不行,我们看重载++运算符的成员函数,它返回的是一个void,而不是Person类,所以我们把代码修改成下面这样就可以了:

class Person {
public:
    //初始化
    Person() {
        a = 1;
    }
    //重载++运算符
    Person& operator++() {   //此处最好返回引用,不要返回值
        a += 10;
        return *this;   //*this返回自身
    }
    //属性
    int a;
};
//重载<<运算符,方便输出对象
ostream& operator<<(ostream& cout, Person& p) {
    cout << p.a;
    return cout;
}

Person p;
//简化:
cout << ++p << endl;  //正确
2、后置++

我们要知道 a++ 与 ++a 的区别,a++ 先调用a再+1,++a先+1再调用a

int a = 1;
cout << a++ << a;  //先输出 1 再输出 2

int a = 1;
cout << ++a << a;  //都输出 2

有几点要注意:

1、上面的前置++函数返回的是自身的引用,因为如果返回值,当我们多次调用该函数的时候就会发现数据丢失,因为返回值的操作实际上编译器会拷贝一个副本然后返回。但是下面的后置++,就需要返回值了,因为最好不要返回局部变量的引用,具体看代码

2、如果一个程序同时重载了前置++和后置++,那么这两个函数就会重定义(因为名字一模一样),为了区分它们,就需要在其中一个函数内加一个参数int,int是占位参数,在调用的时候不用传入实参进去,它的目的是做区分,让两个函数形成重载。

class Person {
public:
    //初始化
    Person(){
        a = 1;
    }
    //前置++
    Person& operator++() {
        a += 10;
        return *this;
    }
    //后置++
    Person operator++(int) {  //中间要加 int 做区分
        Person temp = *this;  //先记录一下当前数据,后面要输出
        a += 10;
        return temp;
    }
    //属性
    int a;
};
//!!后置++函数返回的是值,所以重载<<运算符的时候,形参也应该是值,而不是引用
ostream& operator<<(ostream& cout, Person p) {  //本来是 Person& p
    cout << p.a;
    return cout;
}
//输出,简化版:
cout << p++ << endl;   //输出 1
cout << p << endl;     //输出 10

赋值运算符重载(=)

只能成员函数重载

首先要知道:

        当我们创建了一个类时,编译器会自动为我们添加四个函数,1、默认构造函数  2、默认析构函数  3、默认拷贝构造函数  4、赋值运算符重载(浅拷贝)函数

        既然编译器都已经重载了赋值运算符了,为什么我们还要再写一遍呢?因为当我们在类中开辟了堆区的内存时,我们需要在析构函数中将其释放,而编译器自带的函数实现的是浅拷贝的操作,这时就会出现堆区内存重复释放的问题,所以我们要重新用深拷贝的方式写一遍。详细可见我的这篇博客:C++拷贝构造函数详解(浅拷贝、深拷贝)_错过王小姐的博客-CSDN博客

因为编译器自动重载了 赋值运算符,所以我们可以实现以下操作:

class Person {
public:
    //有参构造
    Person(int age) {
        m_age = age;
    }
    //属性
    int m_age;
};
Person p1(18);
Person p2(22);
p2 = p1;  //可以直接写 p2 = p1,不会报错
cout << p2.m_age << endl;

如果我们要在堆区开辟内存,下面的代码就会出错:

class Person {
public:
    //有参构造
    Person(int age) {
        m_age = new int(age);  //在堆区开辟内存
    }
    //析构函数(释放堆区内存)
    ~Person() {
        if(m_age != NULL) {
            delete m_age;
            m_age = NULL;
        }
    }
    //属性
    int* m_age;
};
Person p1(18);
Person p2(22);
p2 = p1;  //可以直接写 p2 = p1,不会报错
cout << *p2.m_age << endl;

错误原因:堆区的内存被重复释放。  解决办法:重载赋值运算符,使用深拷贝

单看重载赋值运算符的函数:

//系统给的浅拷贝
void operator=(Person& p) {
    m_age = p.m_age;
}

//我们要改成深拷贝
void operator=(Person& p) {
    m_age = new int(*p.m_age);
}

但是这样写还会有2个问题:

1、Person p1(18);

      Person p2(22);

      p2 = p1;

      可以看到 p2 本身有一块堆区的内存,里面存放了数据 22,但是现在我们又重新给它开辟了一块内存,里面存放数据 18,这样原先的那块内存就无法释放了,所以要先释放原先的内存。

2、不能实现 连等于 的操作:p1 = p2 = p3

      因为上面的函数返回值是void ,使用连等于相当于是 p1 = void,会报错,所以我们要让函数返回自身的数据类型(return *this 即返回自身)

//修改后的代码
Person& operator=(Person& p) {
    //释放原先在堆区开辟的内存
    if(m_age != NULL) {
        delete m_age;
        m_age = NULL;
    }
    //重新指向一块新的内存
    m_age = new int(*p.m_age);
    //返回自身
    return *this;
}

看完整代码:

class Person {
public:
    //有参构造
    Person(int age) {
        m_age = new int(age);  //在堆区开辟内存
    }
    //析构函数(释放堆区内存)
    ~Person() {
        if(m_age != NULL) {
            delete m_age;
            m_age = NULL;
        }
    }
    //赋值运算符重载
    Person& operator=(Person& p) {
        if(m_age != NULL) {
            delete m_age;
            m_age = NULL;
        }
        m_age = new int(*p.m_age);
        return *this;
    }
    //属性
    int* m_age;
};
Person p1(18);
Person p2(22);
p2 = p1;
cout << *p2.m_age << endl;

关系运算符重载(==)

关系运算符有:== ,!= , < , > , <=, >=

想要实现的操作:判断两个对象是否相等

全局函数重载
class Person {
public:
    //有参构造
    Person(string name, int age) {
        m_name = name;
        m_age = age;
    }
    //属性
    string m_name;
    int m_age;
};
//重载==运算符
bool operator==(Person& p1, Person& p2) {
    if (p1.m_name == p2.m_name && p1.m_age == p2.m_age) {
        return true;
    } else {
        return false;
    }
}

Person p1("Bob", 18);
Person p2("Bob", 18);
if(p1 == p2){   //此时就可以直接比较两个对象
    cout << "same" << endl;
}

成员函数重载
class Person {
public:
    //有参构造
    Person(string name, int age) {
        m_name = name;
        m_age = age;
    }
    //重载==运算符
    bool operator==(Person& p) {
        if(m_name == p.m_name && m_age == p.m_age) {
            return true;
        } else {
            return false;
        }
    }
    //属性
    string m_name;
    int m_age;
};

Person p1("Bob", 18);
Person p2("Bob", 18);
if(p1 == p2){   //此时就可以直接比较两个对象
    cout << "same" << endl;
}

函数调用运算符重载()

只能成员函数重载

就是重载括号(),由于重载后使用的方式非常像函数的调用,所以称为仿函数

仿函数非常灵活,没有固定的写法,主要体现在它的参数个数不受限制,自己想写几个就写几个,但是对于其他的运算符,参数基本上是2个,比如 + ,==,它们的操作是两者相加或者两者相等,所以不需要第三个参数。

可以理解为:在类内写函数,之后创建的对象本身就是函数

class Person {
public:
    //打印字符串的函数
    void operator()(string str) {
        cout << str << endl;
    }
    //计算三者平均值的函数
    float operator()(int a, int b, int c) {
        return (a + b + c) / 3;
    }
};
Person p;
//调用打印字符串
p("hello world");  //p是不是很像函数名,就像调用函数一样
//调用三者平均值
float n = p(1, 2, 3);
cout << n << endl;
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值