c++类——运算符重载(二)

经典例子:

我们以两个类,二维向量vector2和二维新式向量newvector2来说明各种运算符的重载。

vector全部使用友元函数重载,newvector全部使用成员函数重载,两个都可以时同时重载。

其成员为x和y,主要操作为x(如自增自减等等),次要操作为y。

(一)重载++和--:

++和--的重载分为前置和后置两种情况,另外,虽然修改了类对象的状态,但是可以使用友元函数传入引用来实现++和--的类外重载。

前置++或--的成员函数不需要传入参数,在代码块内不需要拷贝临时变量返回,只通过this指针进行自增/自减。

后置++或--需要传入一个int类型的参数,可以不指定名称,因为一般在代码块内部不会使用它,这个参数只是为了占位置,用于和前置++/--运算符的区别。在代码块内需要先拷贝一个临时变量返回。只通过this指针自增/自减。

前置++或--的友元函数需要传入一个类对象的引用,不需要拷贝临时变量返回,直接对传入参数进行操作(因为引用可以对其有效修改)。

后置++或--的友元函数需要传入两个参数,一个是类对象的引用,且为左操作数,一个是占位的int类型参数(和上面逻辑相同),需要先拷贝临时变量返回,直接对传入参数进行操作。

详细代码如下:

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
//vector2.h
#include<iostream>
using namespace std;
class vector2
{
friend vector2 operator ++(vector2& v); //前置++重载友元
friend vector2 operator ++(vector2& v, int); //后置++重载友元
friend vector2 operator --(vector2& v); //前置--重载友元
friend vector2 operator --(vector2& v, int); //后置--重载友元
private:
double x;
double y;
public:
vector2(double vx = 0, double vy = 0);
vector2(const vector2 &v);
void print();
~vector2();
};

class newvector2
{
private:
double x;
double y;
public:
newvector2(double vx = 0, double vy = 0);
newvector2(const newvector2& v);
newvector2 operator++(); //前置++重载成员
newvector2 operator++(int vx); //后置++重载成员
newvector2 operator--(); //前置--重载成员
newvector2 operator--(int vy); //后置--重载成员
void print();
~newvector2();
};
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
//vector2.cpp
#include"vector2.h"
#include<iostream>
using namespace std;
vector2::vector2(double vx,double vy):x(vx),y(vy)
{
cout << "创建了一个二维向量" << endl;
}

vector2::vector2(const vector2& v)
{
this->x = v.x;
this->y = v.y;
cout << "调用拷贝构造函数创建了一个二维向量" << endl;
}

vector2::~vector2()
{
cout << "析构了一个二维向量" << endl;
}


vector2 operator ++(vector2& v)
{
++v.x;
return v;
}

void vector2::print()
{
cout << "x:" << x << ",y:" << y << endl;
return;
}

vector2 operator ++(vector2& v, int)
{
vector2 temp(v);
v.x++;
return temp;
}

vector2 operator --(vector2& v)
{
--v.x;
return v;
}

vector2 operator --(vector2& v, int)
{
vector2 temp(v);
v.x--;
return temp;
}



newvector2::newvector2(double vx, double vy) :x(vx), y(vy)
{
cout << "创建了一个新式二维向量" << endl;
}

newvector2::newvector2(const newvector2& v)
{
this->x = v.x;
this->y = v.y;
cout << "调用拷贝构造函数创建了一个二维向量" << endl;
}

newvector2::~newvector2()
{
cout << "析构了一个新式二维向量" << endl;
}

void newvector2::print()
{
cout << "x:" << x << ",y:" << y << endl;
return;
}

newvector2 newvector2::operator++()
{
++this->x;
return *this;
}

newvector2 newvector2::operator++(int)
{
newvector2 temp(*this);
this->x++;
return temp;
}

newvector2 newvector2::operator--()
{
--this->x;
return *this;
}

newvector2 newvector2::operator--(int)
{
newvector2 temp(*this);
this->x--;
return temp;
}
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
//main.cpp
#include"vector2.h"
int main()
{
vector2 v(2, 4);
vector2 vv;
cout << "开始的v: ";
v.print();
vv=v++;
cout << "v++后: " << endl;
cout << "v:";
v.print();
cout << "vv: ";
vv.print();
vv=++v;
cout << "++v后: " << endl;
cout << "v: ";
v.print();
cout << "vv: ";
vv.print();
vv=v--;
cout << "v--后: " << endl;
cout << "v: ";
v.print();
cout << "vv: ";
vv.print();
vv=--v;
cout << "--v后: " << endl;
cout << "v: ";
v.print();
cout << "vv: ";
vv.print();

newvector2 newv(2, 4);
newvector2 newvv;
cout << "开始的newv: ";
newv.print();
newvv = newv++;
cout << "newv++后: " << endl;
cout << "newv:";
newv.print();
cout << "newvv: ";
newvv.print();
newvv = ++newv;
cout << "++nwev后: " << endl;
cout << "newv: ";
newv.print();
cout << "newvv: ";
newvv.print();
newvv = newv--;
cout << "newv--后: " << endl;
cout << "newv: ";
newv.print();
cout << "newvv: ";
newvv.print();
newvv = --newv;
cout << "--newv后: " << endl;
cout << "newv: ";
newv.print();
cout << "newvv: ";
newvv.print();
return 0;
}
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/*运行结果:
创建了一个二维向量
创建了一个二维向量
开始的v: x:2,y:4
调用拷贝构造函数创建了一个二维向量
调用拷贝构造函数创建了一个二维向量
析构了一个二维向量
析构了一个二维向量
v++后:
v:x:3,y:4
vv: x:2,y:4
调用拷贝构造函数创建了一个二维向量
析构了一个二维向量
++v后:
v: x:4,y:4
vv: x:4,y:4
调用拷贝构造函数创建了一个二维向量
调用拷贝构造函数创建了一个二维向量
析构了一个二维向量
析构了一个二维向量
v--后:
v: x:3,y:4
vv: x:4,y:4
调用拷贝构造函数创建了一个二维向量
析构了一个二维向量
--v后:
v: x:2,y:4
vv: x:2,y:4
创建了一个新式二维向量
创建了一个新式二维向量
开始的newv: x:2,y:4
调用拷贝构造函数创建了一个二维向量
调用拷贝构造函数创建了一个二维向量
析构了一个新式二维向量
析构了一个新式二维向量
newv++后:
newv:x:3,y:4
newvv: x:2,y:4
调用拷贝构造函数创建了一个二维向量
析构了一个新式二维向量
++nwev后:
newv: x:4,y:4
newvv: x:4,y:4
调用拷贝构造函数创建了一个二维向量
调用拷贝构造函数创建了一个二维向量
析构了一个新式二维向量
析构了一个新式二维向量
newv--后:
newv: x:3,y:4
newvv: x:4,y:4
调用拷贝构造函数创建了一个二维向量
析构了一个新式二维向量
--newv后:
newv: x:2,y:4
newvv: x:2,y:4
析构了一个新式二维向量
析构了一个新式二维向量
析构了一个二维向量
析构了一个二维向量
*/

(二)重载=:

赋值运算符的重载用于对象数据的复制,且必须重载为成员函数,返回类型为对象的引用(为了能够连续操作,否则会有临时变量作为左值出现)。

赋值运算符是用已存在的对象给另一个对象赋新的值,该对象原来是有值的,所以重载的赋值运算符只能被已经存在了的对象调用,而不能凭空产生。

  • 和拷贝构造函数的区别:

    1. 拷贝构造函数生成新的类对象,而重载的赋值运算符是给已有的类对象进行赋值。

      ——因此,如果是一个类对象初始化使用"="运算符,右侧操作数即使也是这个类的对象,调用的仍然是拷贝构造函数而不是重载的赋值运算符,毕竟其还未被初始化完毕。

    2. 拷贝构造函数不必检测源对象是否与新建对象相同,而赋值运算符则需要。

    3. 如果赋值运算符中被赋值的对象有内存分配,需要先把内存释放掉再进行赋值。(要不原来的分配的内存就一直不会被释放了,另外此处的赋值他也是在堆上进行的)

1
2
3
4
5
6
7
//vector2.h
//为newvector2添加一个新的char指针成员。
char* p;
//添加了一个重载赋值运算符
newvector2& operator=(const newvector2& v);
//构造函数参数修改:
newvector2(double vx = 0, double vy = 0,const char*vp="\0");
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
//vector2.cpp
//构造函数修改:
newvector2::newvector2(double vx, double vy,const char*vp) :x(vx), y(vy)
{
p = new char[strlen(vp) + 1];
strcpy_s(p, strlen(vp)+1, (char*)vp);
cout << "创建了一个新式二维向量" << endl;
}
//复制构造函数修改:
newvector2::newvector2(const newvector2& v)
{
this->x = v.x;
this->y = v.y;
p = new char[strlen(v.p) + 1];
strcpy_s(p, strlen(v.p) + 1, v.p);
cout << "调用拷贝构造函数创建了一个新式二维向量" << endl;
}

//析构函数修改:
newvector2::~newvector2()
{
delete[]p;
p = NULL;
cout << "析构了一个新式二维向量" << endl;
}

//定义重载赋值运算符
newvector2& newvector2::operator=(const newvector2& v)
{
this->x = v.x;
this->y = v.y;
delete[](this->p);
this->p = new char[strlen(v.p) + 1];
strcpy_s(this->p,strlen(v.p)+1,v.p);
return *this;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//main.cpp
#include"vector2.h"
int main()
{
newvector2 v1(2, 3, "first");
newvector2 v2 = v1;
newvector2 v3, v4;
v4 = v3 = v1;
return 0;
}
//运行结果:
创建了一个新式二维向量
调用拷贝构造函数创建了一个新式二维向量
创建了一个新式二维向量
创建了一个新式二维向量
调用赋值运算符为一个对象赋值
调用赋值运算符为一个对象赋值
析构了一个新式二维向量
析构了一个新式二维向量
析构了一个新式二维向量
析构了一个新式二维向量

(三)重载+ (- | * | / | %相同):

一般建议使用友元函数重载,但是也可以使用全局函数或者成员函数重载。

本类型中尽量不要使用引用进行传递,否则会无法进行隐性的类型转化。

当然一般情况下建议使用引用进行传递,不过这样在不加其他函数的情况下不可以进行隐式类型转化。

成员函数的弊端是不能只把基本类型放在左操作数上(不发生隐形类型转化,有限制),不过可以让两个均为基本类型(均发生隐形类型转化)

是比较简单的二元运算符重载。

1
2
3
4
5
//vector2.h
//vector2加一个友元函数:
friend vector2 operator +(vector2 v1, vector2 v2);
//newvector2加一个成员函数:
newvector2 operator+(newvector2 v);
1
2
3
4
5
6
7
8
9
10
11
//vector2.cpp
//vector2的+运算符重载定义
vector2 operator+(vector2 v1,vector2 v2)
{
return vector2(v1.x + v2.x, v1.y + v2.y);
}
//newvector2的+运算符重载定义
newvector2 newvector2::operator+(newvector2 v)
{
return newvector2(this->x + v.x, this->y + v.y, this->p);
}
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
//main.cpp
#include"vector2.h"
int main()
{
vector2 v1(1, 2);
vector2 v2(3, 4);
vector2 v3(4, 8);
vector2 v4 = v1 + v2 + v3;
v4.print();
vector2 v5 = 1.0 + v3; //发生隐性类型转化
v5.print();
vector2 v6 = v3 + 2.0; //同
v6.print();
vector2 v7 = 3.0 + 4.0; //同
v7.print();

newvector2 vv1(1, 2);
newvector2 vv2(2, 3);
newvector2 vv3(3, 4);
newvector2 vv4 = vv1 + vv2 + vv3;
vv4.print();
newvector2 vv6 = vv3 + 2.0; //发生隐性类型转化
vv6.print();
//newvector2 vv5 = 1.0 + vv3; 成员函数的弊端:必须把左操作数设置为类对象
newvector2 vv7 = 3.0 + 4.0; //同转化
vv7.print();
return 0;
}

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
//运行结果
创建了一个二维向量
创建了一个二维向量
创建了一个二维向量
调用拷贝构造函数创建了一个二维向量
调用拷贝构造函数创建了一个二维向量
调用拷贝构造函数创建了一个二维向量
创建了一个二维向量
析构了一个二维向量
析构了一个二维向量
创建了一个二维向量
析构了一个二维向量
析构了一个二维向量
x:8,y:14
调用拷贝构造函数创建了一个二维向量
创建了一个二维向量
创建了一个二维向量
析构了一个二维向量
析构了一个二维向量
x:5,y:8
创建了一个二维向量
调用拷贝构造函数创建了一个二维向量
创建了一个二维向量
析构了一个二维向量
析构了一个二维向量
x:6,y:8
创建了一个二维向量
x:7,y:0
创建了一个新式二维向量
创建了一个新式二维向量
创建了一个新式二维向量
调用拷贝构造函数创建了一个新式二维向量
调用拷贝构造函数创建了一个新式二维向量
创建了一个新式二维向量
析构了一个新式二维向量
创建了一个新式二维向量
析构了一个新式二维向量
析构了一个新式二维向量
x:6,y:9
创建了一个新式二维向量
创建了一个新式二维向量
析构了一个新式二维向量
x:5,y:4
创建了一个新式二维向量
x:7,y:0
析构了一个新式二维向量
析构了一个新式二维向量
析构了一个新式二维向量
析构了一个新式二维向量
析构了一个新式二维向量
析构了一个新式二维向量
析构了一个二维向量
析构了一个二维向量
析构了一个二维向量
析构了一个二维向量
析构了一个二维向量
析构了一个二维向量
析构了一个二维向量

可以仔细想想每个构造和析构函数出现的原因(当思考题了😋)

(四)重载<和> (其他比较运算符性质相同):

此处只通过友元函数重载,且使用const引用。

注意,此要返回bool值,尽可能不要返回其他类型。

1
2
3
4
//vector2.h
//vector2增加友元函数
friend bool operator <(const vector2& v1, const vector2& v2);
friend bool operator >(const vector2& v1, const vector2& v2);
1
2
3
4
5
6
7
8
9
10
11
//vector2.cpp
//增加两个友元函数的定义
bool operator <(const vector2& v1, const vector2& v2)
{
return bool(v1.x * v1.x + v1.y + v1.y < v2.x* v2.x + v2.y * v2.y);
}

bool operator >(const vector2& v1, const vector2& v2)
{
return bool(v1.x * v1.x + v1.y + v1.y > v2.x* v2.x + v2.y * v2.y);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//main.cpp
#include"vector2.h"
int main()
{
vector2 v1(1, 2);
vector2 v2(2, 2);
if (v1 < v2) { cout << "v1到0距离小于v2" << endl; }
else if (v1 > v2) { cout << "v1到0距离大于v2" << endl; }
return 0;
}
//运行结果:
创建了一个二维向量
创建了一个二维向量
v1到0距离小于v2
析构了一个二维向量
析构了一个二维向量

(五)重载[]和():

是二元运算符,但是均只能用成员函数重载。

  • 重载下表运算符[]:

    用于访问数据对象的元素,这种写法更符合我们的习惯。

    x[y]等价于x.operator(y)

    参数类型和返回类型可以自定。

    但是需要我们自己增加下标越界检查,这样[]的使用更为安全。

    1
    2
    3
    4
    5
    6
    //vector2.h
    //newvector2增加新成员函数:
    double operator[](int i);
    //增加新数据成员:
    double darray[20];
    //构造函数保持不变
    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
    43
    44
    45
    46
    47
    48
    49
    50
    //vector2.cpp
    //构造函数改为:
    newvector2::newvector2(double vx, double vy,const char*vp) :x(vx), y(vy)
    {
    for (int i = 0; i < 20; i++) {
    darray[i] = i+1;
    }
    p = new char[strlen(vp) + 1];
    strcpy_s(p, strlen(vp)+1, (char*)vp);
    cout << "创建了一个新式二维向量" << endl;
    }
    newvector2::newvector2(const newvector2& v)
    {
    this->x = v.x;
    this->y = v.y;
    for (int i = 0; i < 20; i++) {
    this->darray[i] = v.darray[i];
    }
    p = new char[strlen(v.p) + 1];
    strcpy_s(p, strlen(v.p) + 1, v.p);
    cout << "调用拷贝构造函数创建了一个新式二维向量" << endl;
    }
    //赋值运算符改为
    newvector2& newvector2::operator=(const newvector2& v)
    {
    this->x = v.x;
    this->y = v.y;
    for (int i = 0; i < 20; i++)
    {
    this->darray[i] = v.darray[i];
    }
    delete[](this->p);
    this->p = new char[strlen(v.p) + 1];
    strcpy_s(this->p,strlen(v.p)+1,v.p);
    cout << "调用赋值运算符为一个对象赋值" << endl;
    return *this;
    }
    //增加[]重载运算符的定义:
    double newvector2::operator[](int i)
    {
    //越界要检查
    if (i < 20 and i >= 0) {
    return darray[i];
    }
    else
    {
    cout << "越界!将输出限定为0" << endl;
    return 0.0;
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //main.cpp
    #include"vector2.h"
    int main()
    {
    newvector2 v(1, 2, "1");
    cout << v[5] << endl;
    cout << v[222] << endl;
    return 0;
    }
    //运行结果:
    创建了一个新式二维向量
    6
    越界!将输出限定为0
    0
    析构了一个新式二维向量
  • 重载函数调用符:

    用于函数调用。

    x(y1,y2,y3,...)等价于x.operator()(y1,y2,y3,...)

    这样,我们可以像使用函数一样使用类的对象,因为其可以存储类的状态,所以相较于函数这种写法更加灵活。

    调用运算符的参数表是不唯一的,可以根据需要确定个数及类型。

    返回类型也没有限定。

    这里的函数调用符用于计算传入两个值作为一个点到(0,0)的距离。

    1
    2
    3
    //vector2.h
    //newvector2增加新成员函数:
    double operator()(double xx, double yy);
    1
    2
    3
    4
    5
    6
    //vector2.cpp
    //增加()重载运算符的定义:
    double newvector2::operator()(double xx, double yy)
    {
    return sqrt(xx * xx + yy * yy);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //main.cpp
    #include"vector2.h"
    int main()
    {
    newvector2 v;
    cout << v(2, 3) << endl;
    return 0;
    }
    //运行结果:
    创建了一个新式二维向量
    3.60555
    析构了一个新式二维向量

(六)重载<<和>>:

istream和ostream是c++的预定义流类。

cin是istream的对象,cout是ostream的对象。

运算符<<由ostream重载为插入操作,用于输出基本类型数据,我们可以对其重载,使输出为自定义的数据类型。

运算符>>由istream重载为提取操作,用于输入基本类型数据,我们可以对其重载,使输入为自定义的数据类型。

只能重载为友元函数。(因为cout<<的左操作数只能为cout,右操作数才是需要输出的对象,cin>>同理)

返回类型限定为ostream& 和istream&,第一个参数强制为ostream&和istream&,第二个参数为用户自定义的类型。(引用绝对不可以省略!)

1
2
3
4
//vector2.h
//vector2增加新友元函数:
friend ostream& operator <<(ostream& output, const vector2& v);
friend istream& operator >>(istream& input, vector2& v);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//vector2.cpp
//增加<<和>>重载运算符的定义:
ostream& operator <<(ostream& output, const vector2& v)
{
output << "x:" << v.x << ",y:" << v.y << endl;
return output;
}

istream& operator >>(istream& input, vector2& v)
{
cout << "请分别输入这个二维向量的x和y:" << endl;
input >> v.x;
input >> v.y;
return input;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//main.cpp
#include"vector2.h"
int main()
{
vector2 v;
cin >> v;
cout << v;
return 0;
}
//输出结果:
创建了一个二维向量
请分别输入这个二维向量的x和y:
5 9
x:5,y:9
析构了一个二维向量

c++类——运算符重载(二)
http://example.com/2023/03/24/c++类——运算符重载(二)/
作者
07xiaohei
发布于
2023年3月24日
许可协议