运算符重载的概念
- C++中的表达式由运算符和操作数按照规则构成。例如,算术运算符包括加
+
、减-
、乘*
、除/
和取模%
。如果不做特殊处理,则这些算术运算符通常只能用于对基本数据类型的常量或变量进行运算,而不能用于对象之间的运算。 - 运算符重载,就是给已有的运算符赋予多重含义,使同一个运算符作用于不同类型的数据时产生不同的行为。运算符重载的目的是使得C++中的运算符也能够用来操作对象。
- 用于类运算的运算符通常都要重载。有两个运算符,系统提供了默认的重载版本:赋值运算符
=
和地址运算符&
。
可重载的运算符
运算符 | 可重载的运算符 |
---|---|
双目运算符 |
+ 加,- 减,* 乘,/ 除,% 取模 |
关系运算符 |
== 等于、!= 不等于,< 小于,> 大于,<= 小于等于,>= 大于等于 |
逻辑运算符 | 逻辑或,&& 逻辑与,! 逻辑非 |
单目运算符 |
+ 正,- 负,* 指针,& 取地址 |
自增自减运算符 |
++ 自增,-- 自减 |
位运算符 | 按位或,& 按位与,~ 按位取反,^ 按位异或,<< 左移,>> 右移 |
赋值运算符 |
= 赋值,+= 加法赋值,-= 减法赋值,*= 乘法赋值,/= 除法赋值,%= 取模赋值,&= 按位与赋值,按位或赋值,^= 按位异或赋值,<<= 左移赋值,>>= 右移赋值 |
空间申请与释放 |
new 创建对象,delete 释放对象,new[] 创建数组,delete[] 释放数组 |
其他运算符 |
() 函数调用,-> 成员访问,, 逗号,[] 下标 |
不可重载的运算符
运算符 | 不可重载的运算符 |
---|---|
成员访问运算符 | . |
成员指针访问运算符 |
.* ,->*
|
域运算符 | :: |
长度运算符 | sizeof |
条件运算符 | ?: |
预处理符号 | # |
重载运算符为类的成员函数/友元函数
//MyComplex.hpp
#ifndef MyComplex_hpp
#define MyComplex_hpp
#include <stdio.h>
class MyComplex {
private:
double real, imag;
public:
MyComplex(); //构造函数
MyComplex(double r, double i); //构造函数
void outCom(); //成员函数
//重载运算符为类的成员函数
MyComplex operator-(const MyComplex &c);
//重载运算符为友元函数
friend MyComplex operator+(const MyComplex &c1,
const MyComplex &c2);
};
#endif /* MyComplex_hpp */
//MyComplex.cpp
#include "MyComplex.hpp"
#include <iostream>
using namespace std;
MyComplex::MyComplex() {
real = 0;
imag = 0;
}
MyComplex::MyComplex(double r, double i) {
real = r;
imag = i;
}
void MyComplex::outCom() {
cout << "(" << real << ", " << imag << ")" << endl;
}
//重载运算符为类的成员函数
MyComplex MyComplex::operator-(const MyComplex &c) {
return MyComplex(this->real - c.real,
this->imag - c.imag);
}
//重载运算符为友元函数
MyComplex operator+(const MyComplex &c1,
const MyComplex &c2) {
return MyComplex(c1.real + c2.real,
c1.imag + c2.imag);
}
//main.cpp
#include <iostream>
#include "MyComplex.hpp"
using namespace std;
int main(int argc, const char * argv[]) {
MyComplex c1(1, 2), c2(3, 4), result;
//对象相加
c1.outCom();
cout << "oprator+" << endl;
c2.outCom();
cout << "=" << endl;
result = c1 + c2;
result.outCom();
cout << endl;
cout << "-----------" << endl;
cout << endl;
//对象相减
c1.outCom();
cout << "oprator-" << endl;
c2.outCom();
cout << "=" << endl;
result = c1 - c2;
result.outCom();
return 0;
}
/* 输出
(1, 2)
oprator+
(3, 4)
=
(4, 6)
-----------
(1, 2)
oprator-
(3, 4)
=
(-2, -2)
*/
重载运算符的规则
- 重载后运算符的含义应该符合原有的用法习惯。例如,重载
+
运算符,完成的功能就应该类似于做加法,在重载的+
运算符中做减法是不合适的。 - 运算符重载不能改变运算符原有的语义,包括运算符的优先级和结合性。
- 运算符重载不能改变运算符操作数的个数及语法结构。
-
重载运算符
()
、[]
、->
或者赋值运算符=
时,只能将它们重载为成员函数,不能重载为全局函数。 - 运算符重载不能改变该运算符用于基本数据类型对象的含义。
重载赋值运算符
C++中的赋值运算符=
要求左右两个操作数的类型是匹配的,或至少是赋值兼容的。有时希望=
两边的操作数的类型即使不赋值兼容也能够成立,这就需要对=
进行重载。C++规定,=
只能重载为成员函数。
若有类CL中定义了成员函数,重载了赋值运算符后,上述赋值语句将解释为函数调用的形式:
s1.operator=(s2);
重载赋值运算符示例
还用之前的那个MyComplex
类举🌰:
//MyComplex.hpp
#include <stdio.h>
#include <string>
using namespace std;
class MyComplex {
private:
double real, imag;
public:
MyComplex(); //构造函数
MyComplex(double r, double i); //构造函数
void outCom(); //成员函数
void outCom(string str);
//重载运算符为友元函数
friend MyComplex operator+(const MyComplex &c1,
const MyComplex &c2);
friend MyComplex operator+(const MyComplex &c, double r);
friend MyComplex operator+(double r, MyComplex &c);
friend MyComplex operator-(const MyComplex &c1,
const MyComplex &c2);
friend MyComplex operator-(const MyComplex &c, double r);
friend MyComplex operator-(double r, const MyComplex &c);
//赋值运算符`=`只能重载为类的成员函数
MyComplex & operator=(const MyComplex &c);
MyComplex & operator=(double r);
};
#endif /* MyComplex_hpp */
//MyComplex.cpp
#include "MyComplex.hpp"
#include <iostream>
using namespace std;
MyComplex::MyComplex() {
real = 0;
imag = 0;
}
MyComplex::MyComplex(double r, double i) {
real = r;
imag = i;
}
void MyComplex::outCom() {
cout << "(" << real << ", " << imag << ")" << endl;
}
void MyComplex::outCom(string str) {
cout << str << " = (" << real << ", " << imag << ")" << endl;
}
MyComplex operator+(const MyComplex &c1,
const MyComplex &c2) {
return MyComplex(c1.real + c2.real,
c1.imag + c2.imag);
}
MyComplex operator+(const MyComplex &c, double r) {
return MyComplex(c.real + r, c.imag);
}
MyComplex operator+(double r, MyComplex &c) {
return MyComplex(r + c.real, c.imag);
}
MyComplex operator-(const MyComplex &c1,
const MyComplex &c2) {
return MyComplex(c1.real - c2.real, c1.imag - c2.imag);
}
MyComplex operator-(const MyComplex &c, double r) {
return MyComplex(c.real - r, c.imag);
}
MyComplex operator-(double r, const MyComplex &c) {
return MyComplex(r - c.real, c.imag);
}
MyComplex & MyComplex::operator=(const MyComplex &c) {
this->real = c.real;
this->imag = c.imag;
return *this;
}
MyComplex & MyComplex::operator=(double r) {
this->real = r;
this->imag = 0;
return *this;
}
//main.cpp
#include <iostream>
#include "MyComplex.hpp"
using namespace std;
int main(int argc, const char * argv[]) {
MyComplex c1(1, 2), c2(3, 4), result;
c1.outCom("c1");
c2.outCom("c2");
result = c1 + c2;
result.outCom("相加后赋值给 result");
result = c1 + 5;
result.outCom("c1 + 5 后赋值给 result");
result = 6 + c2;
result.outCom("6 + c2 后赋值给 result");
return 0;
}
/* 输出:
c1 = (1, 2)
c2 = (3, 4)
相加后赋值给 result = (4, 6)
c1 + 5 后赋值给 result = (6, 2)
6 + c2 后赋值给 result = (9, 4)
*/
浅拷贝和深拷贝
同类对象之间可以通过赋值运算符=
互相赋值。如果没有经过重载,=
的作用就是将赋值号右侧对象的值,赋值给左侧的对象。这相当于值的拷贝,称为浅拷贝。
重载赋值运算符后,赋值语句的功能是将一个对象中指针成员变量指向的内容复制到另一个对象中指针成员变量指向的地方,这样的拷贝叫“深拷贝”。
#ifndef Pointer_hpp
#define Pointer_hpp
#include <stdio.h>
class Pointer {
public:
int a;
int *p;
Pointer() {
a = 100;
p = new int(10);
};
Pointer(const Pointer &tempP) {
if (this != &tempP) {
a = tempP.a;
p = tempP.p;
}
};
Pointer & operator=(const Pointer &tempP);
};
#endif /* Pointer_hpp */
//Pointer.cpp
#include "Pointer.hpp"
Pointer & Pointer::operator=(const Pointer &tempP) {
Pointer p;
p.a = tempP.a;
p.p = tempP.p;
return p;
}
#include <iostream>
#include "Pointer.hpp"
using namespace std;
int main(int argc, const char * argv[]) {
Pointer p1, p4; //构造函数
Pointer p2(p1); //构造函数重载
Pointer p3 = p1; //C++默认的复制运算符
p4 = p1; //赋值运算符重载(内部实现深拷贝)
cout << "初始化后---各对象的值及内存地址:" << endl;
cout << "对象名\t对象地址\t\t\ta的值\tp的值\t\t p指向的值\t\tp的地址" << endl;
cout << "p1:\t\t" << &p1 << "\t" << p1.a << "\t\t" << p1.p << "\t\t" << *p1.p << "\t\t" << &p1.p << endl;
cout << "p2:\t\t" << &p2 << "\t" << p2.a << "\t\t" << p2.p << "\t\t" << *p2.p << "\t\t" << &p2.p << endl;
cout << "p3:\t\t" << &p3 << "\t" << p3.a << "\t\t" << p3.p << "\t\t" << *p3.p << "\t\t" << &p3.p << endl;
cout << "p4:\t\t" << &p4 << "\t" << p4.a << "\t\t" << p4.p << "\t\t" << *p4.p << "\t\t" << &p4.p << endl;
p4.a = 104;
p3.a = 103;
p2.a = 102;
p1.a = 101;
*p4.p = 14;/* *p4.px修改后,其他对象.p的修改不影响p4 */
*p3.p = 13;/* p1,p2,p3中p的值相同,使用的是同一个p */
*p2.p = 12;
*p1.p = 11;
cout << "修改后---各对象的值及内存地址:" << endl;
cout << "对象名\t对象地址\t\t\ta的值\tp的值\t\t p指向的值\t\tp的地址" << endl;
cout << "p1:\t\t" << &p1 << "\t" << p1.a << "\t\t" << p1.p << "\t\t" << *p1.p << "\t\t" << &p1.p << endl;
cout << "p2:\t\t" << &p2 << "\t" << p2.a << "\t\t" << p2.p << "\t\t" << *p2.p << "\t\t" << &p2.p << endl;
cout << "p3:\t\t" << &p3 << "\t" << p3.a << "\t\t" << p3.p << "\t\t" << *p3.p << "\t\t" << &p3.p << endl;
cout << "p4:\t\t" << &p4 << "\t" << p4.a << "\t\t" << p4.p << "\t\t" << *p4.p << "\t\t" << &p4.p << endl;
return 0;
}
/*
初始化后---各对象的值及内存地址:
对象名 对象地址 a的值 p的值 p指向的值 p的地址
p1: 0x7ffeefbff440 100 0x1006a3420 10 0x7ffeefbff448
p2: 0x7ffeefbff420 100 0x1006a3420 10 0x7ffeefbff428
p3: 0x7ffeefbff410 100 0x1006a3420 10 0x7ffeefbff418
p4: 0x7ffeefbff430 100 0x1006a2fb0 10 0x7ffeefbff438
修改后---各对象的值及内存地址:
对象名 对象地址 a的值 p的值 p指向的值 p的地址
p1: 0x7ffeefbff440 101 0x1006a3420 11 0x7ffeefbff448
p2: 0x7ffeefbff420 102 0x1006a3420 11 0x7ffeefbff428
p3: 0x7ffeefbff410 103 0x1006a3420 11 0x7ffeefbff418
p4: 0x7ffeefbff430 104 0x1006a2fb0 14 0x7ffeefbff438
*/
重载流插入运算符和流提取运算符
在C++中,左移运算符<<
可以和cout
一起用于输出,故常被称为“流插入运算符”。右移运算符>>
和cin
一起用于输入,一般被称为流提取运算符。它们都是C++类库中提供的。在类库提供的头文件中已经对<<
和>>
进行了重载,使之分别作为流插入运算符和流提取运算符,能用来输出和输入C++基本数据类型的数据。cout
是ostream
类的对象,cin
是istream
类的对象,它们都是在头文件iostream
中声明的。因此,凡是用cout <<
和cin >>
对基本数据类型进行输入/输出的,都要用#include
指令把头文件iostream
包含到本程序文件中。
重载强制类型转换运算符
在C++中,类型的名字(包括类的名字)本身也是一种运算符,即强制类型转换运算符。强制类型转换运算符是单目运算符,也可以被重载,但只能重载为成员函数,不能重载为全局函数。经过适当重载后,(类型名)对象
这个对对象进行强制类型转换的表达式就等价于对象.operator 类型名()
,即变成对运算符函数的调用。
重载自增、自减运算符
自增运算符++
和自减运算符--
都可以被重载,但是它们有前置和后置之分。以++
为例,对于整数k,++k
和k++
的语义是不一样的。
当++
用于对象时,也应该如此。例如,obj
是一个类CDemo
的对象,那么++obj
和obj++
的含义应该是不一样的。按照自增运算符及自减运算符的本来定义,++obj
的返回值应该是obj
被修改后的值,而obj++
的返回值应该是obj
被修改前的值。
#ifndef CDemo_hpp
#define CDemo_hpp
#include <stdio.h>
/*
kSwitchCDemo:
0:自减运算符重载为友元函数
1:自减运算符重载为类的成员函数
*/
#define kSwitchCDemo (0)
class CDemo {
public:
CDemo(int i):n(i){};
operator int() {
return n;
};
CDemo & operator++();//用于前置形式
CDemo operator++(int);//用于后置形式
#if kSwitchCDemo == 1
CDemo & operator--();
CDemo operator--(int);
#else
friend CDemo & operator--(CDemo &);
friend CDemo operator--(CDemo &, int);
#endif
private:
int n;
};
#endif /* CDemo_hpp */
//CDemo.cpp
#include "CDemo.hpp"
CDemo & CDemo::operator++() {
n++;
return *this;
}
CDemo CDemo::operator++(int k) {
CDemo temp(*this);
n++;
return temp;
}
#if kSwitchCDemo == 1
//自减运算符重载为类的成员函数
CDemo & CDemo::operator--() {
n--;
return *this;
}
CDemo CDemo::operator--(int) {
CDemo temp(*this);
n--;
return temp;
}
#else
//自减运算符重载为友元函数
CDemo & operator--(CDemo &d) {
d.n--;
return d;
}
CDemo operator--(CDemo &d, int k) {
CDemo temp(d);
d.n--;
return temp;
}
#endif
#include <iostream>
#include "MyComplex.hpp"
#include "CDemo.hpp"
using namespace std;
int main(int argc, const char * argv[]) {
CDemo d(10);
cout << "d++ = " << d++ << endl; //d++ = 10
cout << "d = " << d << endl; //d = 11
cout << "++d = " << ++d << endl; //++d = 12
cout << "d = " << d << endl; //d = 12
cout << "d-- = " << d-- << endl; //d-- = 12
cout << "d = " << d << endl; //d = 11
cout << "--d = " << --d << endl; //--d = 10
cout << "d = " << d << endl; //d = 10
return 0;
}