04 一般运算符与赋值符的重载

04运算符重载——运算符与赋值符

1. 基本概念

  • 运算符重载的目的是:扩展C++中提供的运算符的适用范围,使之
    能作用于对象。

  • 运算符重载的实质是函数重载

因此,既可以重载为普通函数,又可以重载为成员函数

解读时:把含运算符的表达式转换成对运算符函数的调用,把运算符的操作数转换成运算符函数的参数。运算符被多次重载时,根据实参的类型决定调用哪个运算符函数。

2. 运算符重载的形式

1
2
3
4
返回值类型 operator 运算符 ( 形参表 )
{
    ......
}
  • 重载为成员函数时,参数个数为运算符目数减一(减掉的那个是作用的对象中前面一个)。

  • 重载为普通函数时,参数个数为运算符目数。

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
#include<iostream>
using namespace std;

class Complex
{
public:
double real, imag;
Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {}
Complex operator-(const Complex &c);
};
Complex operator+(const Complex &a, const Complex &b)
{
return Complex(a.real + b.real, a.imag + b.imag); //返回一个临时对象
}
Complex Complex::operator-(const Complex &c)
{
return Complex(real - c.real, imag - c.imag); //返回一个临时对象
}
int main()
{
Complex a(4, 4), b(1, 1), c;
c = a + b; //等价于c=operator+(a,b);
cout << c.real << "," << c.imag << endl;
cout << (a - b).real << "," << (a - b).imag << endl;
// a-b等价于a.operator-(b)
return 0;
}

程序输出:

1
2
5,5
3,3

注意:

c = a + b; 等价于c=operator+(a,b);

a-b 等价于a.operator-(b)

3. 赋值运算符的重载

  • 有时候希望赋值运算符两边的类型可以不匹配,比如,把一个int类型变量赋值给一个Complex对象,或把一个 char * 类型的字符串赋值给一个字符串对象,此时就需要重载赋值运算符“=”。

  • 赋值运算符“=”只能重载为成员函数

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>
#include<string.h>
using namespace std;
class String
{
private:
char *str;

public:
String() : str(new char[1]) { str[0] = 0; }
const char *c_str() { return str; };
String &operator=(const char *s);
~String() { delete[] str; }
};
String &String::operator=(const char *s)
{ //重载“=”以使得 obj = “hello”能够成立
delete[] str;
str = new char[strlen(s) + 1];
strcpy(str, s);
return *this;
}
int main()
{
String s;
s = "Good Luck,"; //等价于 s.operator=("Good Luck,");
cout << s.c_str() << endl;
// String s2 = "hello!"; //这条语句要是不注释掉就会出错,因为这句话需要用到复制构造函数
s = "Shenzhou 8!"; //等价于 s.operator=("Shenzhou 8!");
cout << s.c_str() << endl;
return 0;
}

输出结果:

1
2
Good Luck,
Shenzhou 8!

tips:

C++中有参构造函数,拷贝构造函数和赋值运算符重载的区别和实现

1.拷贝构造函数(复制构造函数)

强调:这里b对象是不存在的,是用a 对象来构造和初始化b的!!

1
2
3
A a;
A b(a);
//或:A b=a; 都是拷贝构造函数来创建对象b

2.有参构造函数

当创建一个类的对象时,它被调用来对类的数据成员进行初始化和分配内存

1
A a(2);

3.赋值运算符重载

当一个类的对象向该类的另一个对象赋值时,就会用到该类的赋值函数。

当没有重载赋值函数(赋值运算符)时,通过默认赋值函数来进行赋值操作

1
2
3
A a;
A b;
b=a;
  • 强调:这里a,b对象是已经存在的,是用a 对象来赋值给b的!!

    赋值运算的重载声明如下:

    1
       A& operator = (const A& other)

通常大家会对拷贝构造函数和赋值函数混淆,这儿仔细比较两者的区别:

1)拷贝构造函数是一个对象初始化一块内存区域,这块内存就是新对象的内存区,而赋值函数是对于一个已经被初始化的对象来进行赋值操作。

2)一般来说在数据成员包含指针对象的时候,需要考虑两种不同的处理需求:一种是复制指针对象,另一种是引用指针对象。拷贝构造函数大多数情况下是复制,而赋值函数是引用对象

3)实现不一样。拷贝构造函数首先是一个构造函数,它调用时候是通过参数的对象初始化产生一个对象。赋值函数则是把一个新的对象赋值给一个原有的对象,所以如果原来的对象中有内存分配要先把内存释放掉,而且还要检察一下两个对象是不是同一个对象,如果是,不做任何操作,直接返回。(这些要点会在下面的String实现代码中体现)

4.浅拷贝、深拷贝

如不定义自己的赋值运算符,那么S1=S2实际上导致 S1.str和 S2.str指向同一地方。

问题:

  1. 如果S1对象消亡,析构函数将释放 S1.str指向的空间,则S2消亡时还要释放一次,不妥。

  2. 另外,如果执行 S1 = “other”;会导致S2.str指向的地方被delete

另外,考虑下面语句:

1
2
3
String s;
s = "Hello";
s = s;

如果不判定字符串是否完全相同,则导致等号左右两侧同时被析构,导致赋值出错

解决方案:

1
2
3
4
5
6
7
8
9
String &operator=(const String &s)
{
if (this == &s)
return *this;
delete[] str;
str = new char[strlen(s.str) + 1];
strcpy(str, s.str);
return *this;
}

注意到,operator = 返回值类型为String&类型,那么这里能不能使用void或String呢?

考虑以下情况:a = b = c; 连等语句的撰写

当函数返回值为引用的时候才能放在等号左边,为了保留原风格的特性,于是使用引用。

则此时:

a = b = c;等价于a.operator=(b.operator=(c));

(a=b)=c;等价于(a.operator=(b)).operator=(c);

  • 引申:为了避免浅拷贝,于是我们写复制构造函数
1
2
3
4
5
String(String &s)
{
str = new char[strlen(s.str) + 1];
strcpy(str, s.str);
}

5.运算符重载为友元函数

  • 需要知道的是:一般情况下,将运算符重载为类的成员函数,是较好的选择。

  • 但有时,重载为成员函数不能满足使用要求,重载为普通函数,又不能访问类的私有成员,所以需要将运算符重载为友元。

考虑下面程序:

1
2
3
4
5
6
7
8
9
10
11
class Complex
{
double real, imag;
public:
Complex(double r, double i) : real(r), imag(i){};
Complex operator+(double r);
};
Complex Complex::operator+(double r)
{ //能解释 c+5
return Complex(real + r, imag);
}

经过上述重载后:

1
2
Complex c ;
c = c + 5; //有定义,相当于 c = c.operator +(5);

但是:

c = 5 + c; //编译出错

所以,为了使得上述的表达式能成立,需要将 + 重载为普通函数

1
2
3
4
Complex operator+(double r, const Complex &c)
{ //能解释 5+c
return Complex(c.real + r, c.imag);
}

但是普通函数又不能访问私有成员,所以,需要将运算符 + 重载为友元。

1
2
3
4
5
6
7
8
class Complex
{
double real, imag;
public:
Complex(double r, double i) : real(r), imag(i){};
Complex operator+(double r);
friend Complex operator+(double r, const Complex &c);
};

项目练习:动态数组

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
105
106
107
108
109
110
111
112
113
114
115
#include <iostream>
#include <string.h>
using namespace std;
//写一个动态内存数组
//析构函数
//构造函数
//复制构造函数
class CArray{
int len;
int *p;
public:

CArray(int s=0);//有参构造函数
~CArray();//析构函数
CArray(CArray &a);//复制构造函数
void push_back(int ele);//push_back函数
int length(); // length函数
CArray &operator=(const CArray &a);//=重载
int &operator[](int i);//[]重载
void display()
{
for(int i=0;i<len;i++)
{
cout<<p[i]<<" ";
}
cout<<endl;
}
};
CArray::CArray(CArray &a)
{
if(!a.p)
{
p=NULL;
len=0;
return;
}
len=a.len;
p=new int[len];
memcpy(p,a.p,sizeof(int)*len);
}
CArray :: CArray(int s)
{
len=s;
if(len==0)p=NULL;
else p=new int [len];
}
CArray ::~CArray()
{
if(p)delete[]p;
}
void CArray::push_back(int ele)
{
if(p)
{
int *tp = new int[len+ 1]; //重新分配空间
memcpy(tp, p, sizeof(int) * len); //拷贝原数组内容
delete[] p;
p = tp;
}
else
{
p=new int[1];
}

p[len]=ele;
++len;
}
int CArray::length()
{
return len;
}
int &CArray::operator[](int i)
{
return p[i];
}
CArray &CArray::operator=(const CArray &a)
{
if(p==a.p)
{
return *this;
}
if(a.p==NULL)
{
len = 0;
if(p)delete []p;
p=NULL;
return *this;
}
if(a.len>len)
{
if(p) delete[] p;
p = new int[a.len];
}
len=a.len;
memcpy(p,a.p,sizeof(int)*a.len);
return *this;
}
int main(void)
{ //要编写可变长整型数组类,使之能如下使用:
CArray a; //开始里的数组是空的
for (int i = 0; i < 5; ++i)
{
a.push_back(i);//push_back函数
}
a.display();
CArray a2, a3;
a2 = a;//赋值符号重载
a2.display();
a2 = a3; // a2是空的
a3.display();
a[3] = 100;//[]重载
CArray a4(a);
a4.display();
return 0;
}

输出结果:

1
2
3
4
0 1 2 3 4 
0 1 2 3 4

0 1 2 100 4