C++运算符的重载
一、运算符重载基本概念
-
1.运算符重载的需求
- C++预定义的运算符,只能用于基本数据类型的运算:整型、实型、字符型、逻辑型…
+、-、*、/、%、^、&、~、!、|、=、<<、>>、!=… - 在数学上,两个复数可以直接进行+、-等运算。但在C++中,直接将+或-用于复数对象是不允许的。
- 有时会希望,让对象也能通过运算符进行运算。这样代码更简洁,容易理解
- 例如:
complex_a和complex_b是两个复数对象;求两个复数的和, 希望能直接写:complex_a + complex_b
- C++预定义的运算符,只能用于基本数据类型的运算:整型、实型、字符型、逻辑型…
-
2.运算符重载概念
- 运算符重载,就是对已有的运算符(C++中预定义的运算符)赋予多重的含义,使同一运算符作用于不同类型的数据时导致不同类型的行为。
- 运算符重载的目的是:扩展C++中提供的运算符的适用范围,使之能作用于对象。
- 同一个运算符,对不同类型的操作数,所发生的行为不同。
-
3.运算符重载的形式
- 运算符重载的实质是函数重载
- 可以重载为普通函数,也可以重载为成员函数
- 把含运算符的表达式转换成对运算符函数的调用。
- 把运算符的操作数转换成运算符函数的参数。
- 运算符被多次重载时,根据实参的类型决定调用哪个运算符函数。
- 运算符重载的格式:
返回值类型 operator 运算符(形参表) { …… }
-
4.运算符重载示例
重载为成员函数时,参数个数为运算符目数减一。
重载为普通函数时,参数个数为运算符目数。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; }
输出:
5,5
3,3c = a + b; 等价于c=operator+(a,b);
a-b 等价于a.operator-(b)
二、赋值运算符的重载
-
1.赋值运算符 ‘=’重载
-
有时候希望赋值运算符两边的类型可以不匹配,比如,把一个int类型变量赋值给一个Complex对象,或把一个 char * 类型的字符串赋值给一个字符串对象,此时就需要重载赋值运算符“=”。
-
赋值运算符“=”只能重载为成员函数
-
代码示例:
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::~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; }
输出:
Good Luck,
Shenzhou 8! -
2.浅拷贝和深拷贝
-
浅拷贝:通过赋值运算符“=”,没有经过重载,“=”的作用是逐个字节的复制。
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) { delete[] str; str = new char[strlen(s) + 1]; strcpy(str, s); return *this; }; ~String() { delete[] str; } };
-
举例:
String S1, S2;
S1 = “this”;
S2 = “that”;
S1 = S2;
-
如不定义自己的赋值运算符,那么S1=S2实际上导致 S1.str和 S2.str指向同一地方。
-
如果S1对象消亡,析构函数将释放 S1.str指向的空间,则S2消亡时还要释放一次。
-
另外,如果执行 S1 = “other”;会导致S2.str指向的地方被delete
-
因此要在 class String里添加成员函数:
String &operator=(const String &s) { delete[] str; str = new char[strlen(s.str) + 1]; strcpy(str, s.str); return *this; }
-
出现问题:下列语句
String s;
s = “Hello”;
s = s; -
改进办法:
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; }
-
-
3.对 operator = 返回值类型的讨论
对比void
、String
、String &
考虑:
a = b = c; (a=b)=c; //会修改a的值
对运算符进行重载的时候,好的风格是应该尽量保留运算符原本的特性
a = b = c
; 等价于a.operator=(b.operator=(c))
;
(a=b)=c
; 等价于(a.operator=(b)).operator=(c)
;- 为 String类编写复制构造函数的时候,会面临和 = 同样的问题,用同样的方法处理:
String(String &s) { str = new char[strlen(s.str) + 1]; strcpy(str, s.str); }
- 为 String类编写复制构造函数的时候,会面临和 = 同样的问题,用同样的方法处理:
三、运算符重载为友元函数
-
1.运算符重载为友元函数需求
一般情况下,将运算符重载为类的成员函数,是较好的选择。 但有时,重载为成员函数不能满足使用要求,重载为普通函数,又不能访问类的私有成员,所以需要将运算符重载为友元。 -
2.运算符重载为友元函数示例
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); }
经过上述重载后:
Complex c ;
c = c + 5; //有定义,相当于 c = c.operator +(5);
但是:
c = 5 + c; //编译出错所以,为了使得上述的表达式能成立,需要将 + 重载为普通函数,需要将运算符 + 重载为友元。
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); }; Complex operator+(double r, const Complex &c) { //能解释 5+c return Complex(c.real + r, c.imag); }
四、流插入运算符和流提取运算符的重载
-
1.流插入运算符的重载
cout
是在iostream
中定义的ostream
类的对象。- “
<<
” 能用在cout
上是因为,在iostream
里对 “<<
” 进行了重载。 - 重载形式:
ostream &ostream::operator<<(int n) { …… //输出n的代码 return *this; } ostream &ostream::operator<<(const char *s) { …… //输出s的代码 return *this; }
-
2.例题
假定c是Complex复数类的对象,现在希望写“cout << c;”,就能以“a+bi”的形式输出c的值,写“cin>>c;”,就能从键盘接受“a+bi”形式的输入,并且使得c.real = a,c.imag = b。
#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"; //以"a+bi"的形式输出 return os; } istream &operator>>(istream &is, Complex &c) { string s; is >> s; //将"a+bi"作为字符串读入, “a+bi” 中间不能有空格 int pos = s.find("+", 0); string sTmp = s.substr(0, pos); //分离出代表实部的字符串 c.real = atof(sTmp.c_str()); //atof库函数能将const char*指针指向的内容转换成 float 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; }
运行结果:
13.2+133i 87↙
13.2+133i, 87
五、类型转换运算符和自增、自减运算符的重载
-
1.重载类型转换运算符
#include <iostream> using namespace std; class Complex { double real, imag; public: Complex(double r = 0, double i = 0) : real(r), imag(i){}; operator double() { return real; } //重载强制类型转换运算符 double }; int main() { Complex c(1.2, 3.4); cout << (double)c << endl; //输出 1.2 double n = 2 + c; //等价于 double n=2+c.operator double() cout << n; //输出 3.2 }
-
2.自增,自减运算符的重载
自增运算符++、自减运算符–有前置、后置之分,为了区分所重载的是前置运算符还是后置运算符,C++规定:-
前置运算符作为一元运算符重载
/*重载为成员函数:*/ T &operator++(); T &operator--(); /*重载为全局函数:*/ T1 &operator++(T2); T1 &operator—(T2);
-
后置运算符作为二元运算符重载,多写一个没用的参数:
/*重载为成员函数:*/ T operator++(int); T operator--(int); /*重载为全局函数:*/ T1 operator++(T2, int); T1 operator—(T2, int);
-
六、运算符重载的注意事项
- 1.C++不允许定义新的运算符 ;
- 2.重载后运算符的含义应该符合日常习惯;
- complex_a + complex_b
- word_a > word_b
- date_b = date_a + n
- 3.运算符重载不改变运算符的优先级;
- 4.以下运算符不能被重载:“.”、“.*”、“::”、“?:”、sizeof;
- 5.重载运算符()、[]、->或者赋值运算符=时,运算符重载函数必须声明为类的成员函数。