运算符重载
产生缘由
C++预定义的运算符,只能用于基本数据类型的运算:整型、实型、字符型、逻辑型。对于对象间的运算无法进行,于是就产生了运算符重载
(一).定义
对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
解释:同一个运算符,对不同类型的操作数,所发生的行为不同
(二).目的
扩展C++中提供的运算符的适用范围,使之能作用于对象
(三).实质
运算符重载的实质就是函数重载(可为普通函数或成员函数)
(四).使用
(1)含运算符的表达式转换对运算符函数的调用
(2)把运算符的操作数转换成运算符函数的参数
(五).形式
返回值类型 operator 运算符(形参表)
{
.......
}
应用实例:’
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;}
运行结果为
常见运算符的重载
(一)赋值运算符重载
产生原因和目的
传统的赋值运算符的操作数两边是“同基本数据类型的”或“可以通过强制类型转换实现赋值”,在引入对象后,我们希望可以将基本类型赋值给一个对象,于是引入了“赋值运算符重载”
注意:赋值运算符重载只能为成员函数
实例如下:
using namespace std;
class String {
private:
char* str;
public:
//构造函数
String() :str(new char[1]) { str[0] = 0; }
const char* c_str() { return str; };
String(String& s);
//重载函数
String& operator = (const char* s);
String& operator = (const String& s);
//析构函数
~String() { delete[]str; };
void Print() { cout << " " << str<<endl; };
};
//通过字符串赋值
String& String ::operator=(const char* s) {
delete [] str;
str = new char[strlen(s)+1];
strcpy(str, s);
return *this;
}
//同类对象的赋值
String& 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;
}
//通过复制构造函数初始化
String::String(String& s)
{
str = new char[strlen(s.str) + 1];
strcpy(str, s.str);
}
int main() {
String s1;
String s2;
s1 = "this";
s2 = "that";
String s3(s1);
s1 = s2;
s1.Print();
s3.Print();
return 0;
}
运行结果:
浅拷贝和深拷贝的区别参考:
https://www.cnblogs.com/apex-wzw/p/14754383.html
补充:运算符重载为友元函数
一般情况下,将运算符重载为类的成员函数或普通函数,有时使用成员函数,不能满足要求,而使用普通函数,不能访问对象的私有成员,于是便提出了友元函数。
使用实例:
using namespace std;
class Complex {
double real, imag;
public:
Complex(double r, double i) :real(r), imag(i) {};
Complex operator+(double r);
void Print() {
cout << "real= " << real << " imag= " << imag << endl;
}
};
Complex Complex::operator+(double r) {
return Complex(real + r, imag);
}
int main() {
Complex c2(1.0, 3.0);
c2.Print();
c2 = 10.0+ c2;//报错
c2.Print();
return 0;
}
运行结果如下:
通过友元函数来解决问题:
using namespace std;
class Complex {
double real, imag;
public:
Complex(double r, double i) :real(r), imag(i) {};
Complex operator+(double r);
//声明为友元函数
friend Complex operator +(double g, const Complex& r);
void Print() {
cout << " real= " << real << " imag= " << imag << endl;
}
};
Complex Complex::operator+(double r) {
return Complex(real + r, imag);
}
//友元函数
Complex operator+(double g,const Complex &r)
{
return Complex(r.real + g, r.imag);
}
int main() {
Complex c2(1.0,3.0);
c2.Print();
c2 = 10.0 + c2;
c2.Print();
return 0;
}
输出结果为:
(二)可变长数组
在c语言中,可以直接定义定长数组也可以定义动态数组,对于动态数组,使用后必须销毁,为了方便使用数组,于是便提出了可变长数组,其会根据实际存储元素改变数组长度
代码如下:
using namespace std;
class CArray {
private:
int size;//数组元素个数
int* ptr;//指向动态分配的数组
public:
//构造函数
CArray(int s = 0);//s代表数组元素的个数
//复制构造函数
CArray(CArray& a);
//析构函数
~CArray();
void push_back(int v);//用于在数组尾部添加一个元素v
//函数重载
CArray& operator=(const CArray& a);//用于数组对象间的赋值
//记录存储元素总数
int length() { return size; }//返回数组的个数
//注意:如果函数声明在类中,不需要引用域(::)
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)//防止a=a这样的
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;//加入新的数组元素
}
int main() {
CArray a;
for (int i = 0; i < 5; i++)
a.push_back(i);
CArray a2, a3;
a2 = a; //调用重载函数
cout << " ";
for (int i = 0; i < a.length(); ++i)
cout << a2[i] << " ";
a2 = a3;
cout << " ";
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] << " ";
return 0;
}
运行结果为:
(三)流插入和流提取运算符的重载
I.流插入运算符(cout)
定义:
cout 是在iostream中定义的,ostream类的对象
疑问提出:<<运算符为什么可以和cout放在一起用呢?
解答:
通过重载,我们可以让它完成其他的功能
重载方式:
void ostream::operator<<(int n){
......//输出重载的函数
return;
}
如何实现对于不同操作数的输出呢?
ostream & ostream::operator<<(int n)
{
.....//输出n的代码
return *this;
}
ostream & ostream::operator<<(const char*s){
......//输出s的代码
return*this;
}
解释:通过返回对象(对象的引用),返回值为cout,可以对下一个操作数进行同样的输出操作
II.流提取运算符(cin)
具体使用方法和流插入运算符类似
使用实例如下:
要求:可以通过输入端输入复数
using namespace std;
class Complex {
double real, imag;
public:
Complex(double r = 0, double i = 0) :real(r), imag(r) {};
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;
cout << " ";
cin >> c >> n;
cout <<" " << c << "," << n;
return 0;
}
运行结果:
(四)类型转换重载
在c语言中可以通过强制类型转换来实现不同基本类型间的转换,到我们如何让一个对象强制转换一个基本类型?为了解决这个问题,我们可以使用重载来解决。
实例如下(以double为例):
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(3.2,4.5);
cout <<" " << (double)c << endl;//输出为3.2
double n = 2 + c;//等价于double n= 2+c.operator double()
cout << " " << n;//输出为5.2
return 0;
}
运行结果如下:
(五).自增自减运算符的重载
自增自减运算符有前置和后置之分,C++中为了区分,规定前置运算符作为一元运算符重载,后置运算符作为二元运算符重载,多写一个没用的参数
使用格式如下:
前置运算符:
(1)重载为成员函数
T & operator++();
T & operator--();
(2)重载为全局函数
T1 & operator++(T2);
T1 & operator--(T2);
后置运算符:
(1)重载为成员函数
T operator++(int);
T operator--(int);
(2)重载为全局函数
T1 operator++(T2,int);
T2 operator--(T2,int);
注意:对于前置运算符,返回值为对象的引用,可以通过赋值改变其值;后置运算符,返回值为操作前的临时对象
using namespace std;
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) {
//后置--
CDemo tmp(d);
d.n--;
return tmp;
}
int main() {
CDemo d(5);
cout << (d++) << ",";//等价于d.operator++(0);
cout << d << ",";
cout << (++d) << ",";//等价于d.operator++();
cout << d << endl;
cout << (d--) << ",";//等价于operator--(d,0);
cout << d << ",";
cout << (--d) << ",";//等价于operator--(d);
cout << d << endl;
return 0;
}
运行结果如下:
运算符重载注意:
(1)C++不允许定义新的运算符
(2)运算符重载并不会改变运算符间的优先级
(3)以下运算符不可以被重载:
“ . ” |" .* "|“ :: ”|" ?: "|"sizeof"
(4)重载运算符()、[]、->或者赋值运算符=时,运算符重载函数必须声明为类的成员函数