1.基本概念
返回值类型 operator 运算符(形参表) { …… }
- 在程序编译时:
- 把含运算符的表达式 $\to$ 对运算符函数的调用
- 把运算符的操作数 $\to$ 运算符函数的参数
- 运算符被多次重载时,根据实参的类型决定调用哪个运算符函数
- 运算符可以被重载为普通函数,也可以被重载为成员函数
- 运算符重载为普通函数时,参数个数为运算符目数(如+为2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Complex { public: Complex(double r = 0.0, double i = 0.0) { real = r; imaginary = i; } double real; double imaginary; }; Complex operator+(const Complex &a, const Complex &b) { return Complex(a.real+b.real,a.imaginary+b.imaginary) } int main() { Complex a(1, 2), b(2, 3), c; c = a + b; 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
| class Complex { public: Complex(double r = 0.0, double i = 0.0):real(r),imaginary(i) { } Complex operator+(const Complex &); Complex operator-(const Complex &); private: double real; double imaginary; };
Complex Complex::operator+(const Complex &operand2) { return Complex(real + operand2.real, imaginary + operand2.imaginary) }
Complex Complex::operator-(const Complex &operand2) { return Complex(real - operand2.real, imaginary - operand2.imaginary) }
int main() { Complex x(4.3,8.2),y(3.3,1.1),z; z = x + y; z = x - y; return 0; }
|
2.赋值运算符’=’重载
2.1.基本实现
当类和对象这个新概念对引入的时候,原先一些传统的运算符并不能直接作用在我们自己定义的类型的对象上,但是唯有赋值运算符’=’是可以直接使用的,它会会按对象的数据成员一一完成赋值。当我们对’=’有更多的要求时,比如两边类型可以不匹配,或者除了完成普通的赋值外还要实现其他功能,就需要重载运算符’=’。
例子:编写一个长度可变的字符串类String,包含一个char *
类型的成员变量 $\to$ 指向动态分配的存储空间,该存储空间用于存放'\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
| #include<iostream> using namespace std; class String { private: char *str; public: String():str(NULL){} const char * c_str() { return str; } char * operator=(const char * s); ~String(); };
char * String::operator=(const char *s) { if (str) delete[] str; if (s) { str = new char[strlen(s) + 1]; strcpy(str, s); } else str = NULL; return str; }
String::~String() { if (str) delete[] str; }
int main() { String s; s = "Good Luck"; cout << s.c_str() << endl; s = "C++"; cout << s.c_str << endl; return 0; }
|
2.2.重载赋值运算符的意义—浅复制和深复制
比如利用我们上节定义的String
类,它有两个对象S1
和S2
,利用我们已经重载的复制运算符可以实现直接将一个字符串赋值给一个String
对象,如果想进一步将S2
直接赋值给S1
,在语法上也是没问题的,它会实现浅复制,即将S2
对象中的内容逐字节地复制给S1
,实际上就是S1.str = S2.str
,两个指针就指向了同一块地址,但这会引发一些问题。
1 2 3 4
| String S1, S2; S1 = "this"; S2 = "that"; S1 = S2;
|
当执行了S1 = S2
后,S1.str
和S2.str
两个指针指向了同一块地址,这引发了两个问题:第一个是存放”this
“字符串的内存没有任何指针来对它进行控制,成为了一个内存垃圾;当S1
和S2
同时消亡的时候,存放”that
“的内存会被释放两次,这会导致严重的内存错误。
- 深复制/深拷贝:将一个对象中指针变量指向的内容 $\to$ 复制到另一个对象中指针成员对象指向的地方
1 2 3 4 5 6
| String & operator=(const String &s) { if (str) delete[] str; str = new char[strlen(s.str) + 1]; strcpy(str, s.str); return *this; }
|
通过上面的赋值运算符深拷贝的实现,我们是否已经完全实现了String对象的赋值呢?仔细考虑一下,还会一点小疏漏,就是当执行s = s
,即把当前对象赋值给其自身,那么在刚才的重载函数中就会出现一些小问题,当执行strcpy(str, s.str)
时会发现我们已经把原来s.str
所指的内存空间中的内容删掉了,所以我们要在原来代码基础上加一条if语句。
1 2 3 4 5 6 7
| String & operator=(const String &s) { if (str == s.str) return *this; if (str) delete[] str; str = new char[strlen(s.str) + 1]; strcpy(str, s.str); return *this; }
|
那么上面定义的String
类还有其他问题嘛?
- 需要注意的是:为
String
类编写复制构造函数时,会面临和’=’同样的问题,如果采用浅拷贝或者调用默认的复制构造函数,就会出现问题,为此我们也要采用深拷贝的方式
1 2 3 4 5 6 7 8
| String(String &s) { if (s.str) { str = new char[strlen(s.str) + 1]; strcpy(str, s.str); } else str = NULL; }
|
3.运算符重载为友元函数
- 通常,将运算符重载为类的成员函数
- 重载为友元函数的情况:
- 成员函数不能满足要求
- 普通函数又不能访问类的私有成员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Complex { public: Complex(double r,double i):real(r),imag(i) {} Complex operator+(double r); private: double real, imag; }; Complex Complex::operator+(double r) { return Complex(real + r, imag); } int main(){ Complex c; c = c + 5; return 0; }
|
上面的例子中,能实现c = c + 5
,相当于c = c.operator +(5)
,但不能实现c = 5 + c
,如果要实现后者,就需要将’+’重载为普通函数,且要能访问Complex
类的私有成员real
,因此要将’+’重载为友元函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Complex { public: Complex(double r,double i):real(r),imag(i) {} Complex operator+(double r); friend Complex operator+(double r, const Complex &c); private: double real, imag; }; Complex Complex::operator+(double r) { return Complex(real + r, imag); } Complex operator+(double r, const Complex &c) { return Complex(c.real + r, c.imag); }
|
4.实例-长度可变的整型数组类
C++中的数组的大小(size)是固定的,不能按存放元素的多少自动调整容量,为此我们想自己定义一个长度可变的整型数组类,可以实现下面的功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| int main() { CArray a; for (int i = 0; i < 5; ++i) a.push_back(i); CArray a2, a3; for (int i = 0; i < a.length(); ++i) cout << a[i] << " "; cout << endl; a2 = a3; for (int i = 0; i < a2.length(); ++i) cout << a2[i] << " "; cout << endl; a[3] = 100; CArray a4(a); for (int i = 0; i < a4.length(); i++) cout << a4[i] << " "; cout << endl; return 0; }
|
程序的输出结果是:
分析一下需要实现的功能:
- 要用动态分配的内存来存放数组元素,需要一个指针成员变量
- 要重载’=’
- 要重载’[]’,即实现
a2[i]
,取下标
- 有复制构造函数
这个长度可变的整型数组类可以有下面的代码实现,还是有一些需要注意的点的。
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
| class CArray { int size; int *ptr; public: CArray(int s = 0); CArray(CArray &a); ~CArray(); void push_back(int v); int length() { return size; } CArray & operator=(const CArray & a); int & operator[](int i) { return ptr[i]; } };
CArray::CArray(int s) :size(s) { if (s == 0) ptr = NULL; else ptr = new int[s]; }
CArray::CArray(CArray &a) { if (!a.ptr) { ptr = NULL; size = 0; return; } ptr = new int[a.size]; memcpy(ptr, a.ptr, sizeof(int)*a.size); size = a.size; }
CArray::~CArray() { if (ptr) delete[] ptr; }
CArray & CArray::operator=(const CArray &a) { if (ptr == a.ptr) return *this; if (a.ptr == NULL) { if (ptr) delete[] ptr; ptr = NULL; size = 0; return *this; } if (size < a.size) { if (ptr) delete[] ptr; ptr = new int[a.size]; } memcpy(ptr, a.ptr, sizeof(int)*a.size); size = a.size; return *this; }
void CArray::push_back(int v) { if (ptr) { int *tmpPtr = new int[size + 1]; memcpy(tmpPtr, ptr, sizeof(int)*size); delete[] ptr; ptr = tmpPtr; } else ptr = new int[1]; ptr[size++] = v; }
|
5.流插入和流提取运算符的重载
例子:假设c是Complex
复数类的对象,实现cout<<c;
能输出”a+bi”形式,cin>>c
能从键盘接受”a+bi”形式的输入。
为此我们需要把<<和>>重载成全局函数,因为它们已经是ostream
和istream
的成员函数了;又因为它们需要访问Complex
类的私有成员,因此要声明成Complex
类的友元函数。
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
| #include<iostream> #include<string> #include<cstdlib> using namespace std; class Complex { double real, imag; public: Complex(double r = 0, double i = 0) :real(r), imag(i) { }; friend ostream & operator<<(ostream &os, const Complex &c); friend istream & operator>>(istream &is, Complex &c); };
ostream & operator<<(ostream & os, const Complex &c) { os << c.real << "+" << c.imag << "i"; return os; }
istream & operator>>(istream & is, Complex &c) { string s; is >> s; int pos = s.find("+", 0); string sTmp = s.substr(0, pos); c.real = atof(sTmp.c_str()); sTmp = s.substr(pos + 1, s.length() - pos - 2); c.imag = atof(sTmp.c_str()); return is; }
int main() { Complex c; int n; cin >> c >> n; cout << c << "," << n; return 0; }
|
运行结果示例:
6.自加/自减运算符的重载
例子:我们希望设计一个CDemo对象来实现下面的功能:
1 2 3 4 5 6 7 8 9 10 11 12
| int main() { CDemo d(5); cout << (d++) << ","; cout << d << ","; cout << (++d) << ","; cout << d << endl; cout << (d--) << ","; cout << d << ","; cout << (--d) << ","; cout << d << endl; return 0; }
|
程序运行结果:
我们首先需要设计CDemo
对象的自加自减运算符,再注意cout<<d
语句它会将CDemo
对象直接输出为整型数,而cout
并没有这样的功能,它不支持任意自定义类型的输出,因此要设计一个强制类型转换符的一个运算符重载。
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
| class CDemo { private: int n; public: CDemo(int i=0):n(i){ } CDemo operator++(); CDemo operator++(int); operator int() { return n; } friend CDemo operator--(CDemo &); friend CDemo operator--(CDemo &, int); };
CDemo CDemo::operator++() { n++; return *this; } CDemo CDemo::operator++(int k) { CDemo tmp(*this); n++; return tmp; } CDemo operator--(CDemo & d) { d.n--; return d; } CDemo operator--(CDemo &d,int k) { CDemo tmp(d); d.n--; return tmp; }
|
注意语句operator int() { return n; }
:
- 此时Int作为一个类型强制转换运算符被重载(而不是整型类型了)
- 类型强制转换运算符重载时:
- 不能写返回值类型
- 实际上返回值类型——类型强制转换运算符代表的类型
运算符重载的注意事项:
- C++不允许定义新的运算符
- 重载后运算符的含义应该符合日常习惯
- 运算符重载不改变运算符的优先级
- 一下运算符不能被重载:’
.
‘,’.*
‘,’::
‘,’?:
‘,sizeof
- 重载运算符
()
,[]
,->
,=
时,重载函数必须声明为类的成员函数