一、运算符重载#

1. 什么是运算符重载#

函数可以重载, 运算符也是可以重载的。 运算符重载是对已有的运算符重新进行定义,赋予其另一种功能,以达到适应不同的数据类型。运算符重载不能改变它本来的寓意(也就是 加法不能变更为 减法)

运算符重载只是一种 “语法上的方便” , 背后实际上是一种函数调用的方式。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include<iostream>

using namespace std;

class Student{
    int age;

    public:
        Student(int age):age(age){

        }
};

int main(){

    int a = 3 ;
    int b = 4 ; 

    //两个整数相加,这是允许的。
    int c = a + b ; 

    Student s1(10) ; 
    Student s2(20) ; 

    //两个学生相加,则编译失败。即使你认为两个学生的年纪之和为第三个学生的年纪
    //依然不允许通过编译,因为 + 运算符在定义之初就不认识Student这种类型。
    Student s3 = s1 + s2 ; // 编译器不通过

    return 0 ;
}

2. 运算符重载的意义#

运算符的重载实际上背后是调用对应的函数,重载运算符使得把复杂的代码包装起来,对外暴露简单的一个符号即可。实际上不使用运算符重载,也一样可以实现功能,如下面两个学生相加的示例所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include<iostream>
using namespace std;

class Student{

public:
    int age;

    Student(int age):age(age){

    }

};

int main(){

    Student s1(10);
    Student s2(20);

    //先对两个学生的年龄做加法
    int age = s1.age + s2.age;

    //再赋值给第三名学生
    Student s3 (age);

    cout << "第三名学生的年龄是: " << s3.age << endl;

    return 0 ;
}

3. 定义运算符#

重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。比如,要重载+ 运算符 ,那么可以声明一个函数为 operator+() ,函数声明的位置可以是类的内部,也可以是类的外部,所以又有了成员函数和全局函数的划分。与其他函数一样,重载运算符函数,也可以拥有返回值和参数列表。此处仍然以学生相加的案例举例。

1. 成员函数方式#

把重载的运算符函数定义在类中,此时只需要接收一个参数,因为类的对象本身作为+ 的前面调用者。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include<iostream>
using namespace std;

class Student{

public:
    int age;

    Student(int age):age(age){

    }


    //两个学生的年龄之和,则为第三个学生的命令,所以此处需要返回一个学生对象。
    //好方便在外面接收。
    Student operator+ (Student &s ){
        Student temp(this->age + s.age);
        return temp;
    }

};

int main(){

    Student s1(10);
    Student s2(20);

    //这里等于使用s1的对象,调用了operator+这个函数, +后面的 s2 则被当成参数来传递
    Student s3 = s1 + s2;

    cout << "s3.age = " << s3.age << endl;

    return 0 ;
}

2. 全局函数方式#

并不是所有的运算符重载都能定义在类中,比如,需要扩展一些已有的类,对它进行运算符重载,而这些类已经被打成了一个库来使用,此时通过全局函数的方式来实现运算符重载。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<iostream>
using namespace std;

class Student{

public:
    int age;

    Student(int age):age(age){

    }
};

//由于函数并非定义在类中,所以此处无法使用到this指针,则需要传递两个对象进来。
Student operator+ (Student &s , Student &ss ){
Student temp(s.age + ss.age);
return temp;
}

int main() {

    Student s1(20);
    Student s2(30);

    //这里等于使用s1的对象,调用了operator+这个函数, +后面的 s2 则被当成参数来传递
    Student s3 = s1 + s2;


    cout << "s3.age = " << s3.age << endl;
    return 0;
}

4. 输出运算符重载#

输出运算符重载,实际上就是 << 的重载。 << 实际上是位移运算符,但是在c++里面,可以使用它来配合cout进行做控制台打印输出。 cout 其实是ostream 的一个实例,而ostrem 是 类basic_ostream的一个别名,所以之所以能使用cout << 来输出内容,全是因为 basic_ostream里面对 << 运算符进行了重载.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<<` 运算符只能输出常见的基本数据类型,对于自定义的类型,是无法识别的。比如: `student
#include <iostream>  
using namespace std;

class Student{
public:
   string name {"zhangsan"};

public:
   Student(string name){
       this->name = name;
   }
};

//对 << 运算符进行重载。 这里使用的是全局函数 ,第一个参数ostream 是因为在外面调用<<这个运算符的时候,cout 在前,cout 为ostream这种类型的对象。  
ostream& operator<< (ostream& o, Student &s1){
    o << s1.name ;
    return o;
}


int main() {
    Student s1("张三");
    cout << s1  <<endl ;
    return 0;
}

5. 输入运算符重载#

输入运算符重载,实际上就是 >> 的重载。用意和上面的输出运算符相似

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>  
using namespace std;

class Student{
public:
   string name 



//对 << 运算符进行重载。
ostream& operator<< (ostream& o, Student &s1){
    o << s1.name ;
    return o;
}

//对 >> 运算符进行重载
istream& operator>> (istream& in, Student &s1){
    in >> s1.name;
    return in;
}


int main() {

    Student s1;

    cout << "请输入学生的姓名:" << endl;
    cin >>  s1;

    //打印学生, 实际上是打印他的名字。
    cout << s1  <<endl ;
    return 0;
}

6.赋值运算符重载#

1. 默认的赋值运算符#

赋值运算符在此前编码就见过了,其实就是 = 操作符 。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Student{
    int no;
    int age ;

public :
    Student(no , age){
        this->no = no ;
        this->age = age ;
    }
}

int main(){

    Student s1(10001 , 15);
    Student s2 ;
    s2 = s1; //此处使用了默认的赋值运算符。


    Student s2 = s1;//此处执行的是拷贝构造函数

    return 0 ;
}

2. 拷贝赋值运算#

其实这里说的就是 = 赋值运算符

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<iostream>

using namespace std;

class Student{
    int no;
    int age ;

public :
    Student(no , age){
        this->no = no ;
        this->age = age ;
    }

    //拷贝赋值
    Stu& operator=(const Stu &h){
        cout <<"执行拷贝赋值函数" << endl;
        d = new int();
        *d = *h.d;
    }
}


int main(){
    Stu stu1("张三",18);
    Stu stu2 ;

    stu2 = stu1; //拷贝赋值

    return 0 ;
}

3. 移动赋值运算#

移动赋值运算,接收的是一个右值,并且移动之后,原有对象将不再拥有对数据的控制权。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include<iostream>

using namespace std;

class Student{
    int no;
    int age ;

public :
    Student(no , age){
        this->no = no ;
        this->age = age ;
    }

    //拷贝赋值
    Stu& operator=(const Stu &h){
        cout <<"执行拷贝赋值函数" << endl;
        d = new int();
        *d = *h.d;
    }


    //移动赋值
    Stu& operator=(const Stu &&h){
        cout <<"执行移动赋值函数" << endl;
        d = h.d;
        h.d = nullptr;

        return *this;
    }

}


int main(){
    Stu stu1("张三",18);
    Stu stu2 ;

    stu2 = move(stu1); //移动后,stu1 将不再拥有对数据的控制权

    return 0 ;
}

7. 调用运算符重载#

一般来说,可以使用对象来访问类中的成员函数,而对象本身是不能像函数一样被调用的,除非在类中重载了调用运算符。 如果类重载了函数调用运算符,则我们可以像使用函数一样使用该类的对象。在外面使用 对象(),实际上背后访问的是类中重载的调用运算符函数。

如果某个类重载了调用运算符,那么该类的对象即可称之为:函数对象 ,因为可以调用这种对象,所以才说这些对象行为 像函数一样

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include<iostream>
using namespace std;

class Calc{ 
public:
    int operator()(int val){
        return val <0 ? -val :val;
    }
};
int main(){
    Calc c ;
    int value = c(-10);
    return 0;
}
  • 标准库中的函数对象

在标准库中定义了一组算术运算符 、关系运算符、逻辑运算符的类,每个类都有自己重载的调用运算符。要想使用这些类,需要导入 #include , 后面要说的 lamdda表达式 正是一个函数对象

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include<functional>

int main(){

    plus<int > p; //加法操作
    int a = p(3 , 5);

    negate<int> n; //可以区绝对值
    cout <<n(-10) << endl;

    return 0 ;
}