C++学习笔记(九)——运算符重载
C++内部定义的数据类型(int , float, …)的数据操作可以用运算符号来表示,其使用形式是表达式;用户自定义的类型的数据的操作则用函数表示,其使用形式是函数调用。为了使对用户自定义数据类型的数据的操作与内置的数据类型的数据的操作形式一致,C++提供了运算符的重载,通过把C++中预定义的运算符重载为类的成员函数、友元函数或普通函数,使得对用户的自定义数据类型的数据—对象的操作形式与C++内部定义的类型的数据一致。
运算符是一种通俗、直观的函数,比如:int x=2+3;语句中的“+”操作符,系统本身就提供了很多个重载版本,如:
int operator + (int,int);
double operator + (double,double);
C++支持重载的运算符有:
- 双目运算符+ - * / %
- 关系运算符== != < > <= >=
- 逻辑运算符|| && +
- 单目运算符+ - * &
- 自增自减运算符++ –
- 位运算符| & ~ ^ << >>
- 赋值运算符= += -= *= /= %= &= |= ^= <<= >>=
- 空间申请和释放new delete new[] delete[]
- 其他运算符() -> ->* , []
C++不支持重载的运算符有: - 成员访问符 .
- 成员指针访问运算符 .*
- 域运算符 ::
- 长度运算符 sizeof
- 条件运算符号 ?:
即C++不支持重载的运算符都与"."有关,一个点的成员访问符和长度运算符,两个点的成员指针访问运算符,三个点的条件运算符号,四个点的域运算符。所以比较好记忆。
C++运算符重载规则: - 重载操作符的形参类型必须是类类型或者是枚举类型,如:
int operator+(int, int);//不能重载
; - 操作符的优先级、结合性或操作数个数不能改变
- 不再具备短路求值特性
- 重载操作符并不保证操作数的求值顺序 && || ,
- 不能臆造并重载一个不存在的运算符,如@, #,$等。
运算符的重载形式有三种: - 以普通函数重载;
- 以友元函数重载;
- 以成员函数重载;
不同的重载形式所实现的效果各有不同,需要根据情况选择何时的重载方式。另外运算符对重载的形式有不同的要求。以下将通过代码来详细说明这些问题。另外需要说明的一点就是运算符的调用与运算符重载的参数对应关系,例如:
//定义一个+重载函数
Complex operator+(Complex lhs, Complex rhs)
{
实现代码
}
//使用该重载运算符
Complex c1;
Complex c2;
Complex c3 = c1+c2; //这里就是我要说的重点,运算符的调用与普通函数的调用不一样
//普通函数的实参与形参的形式一致都是放到括号里面
//例如:int sum(int, int),调用时sum(4,5)
//而运算符调用时没有括号,而是依据该运算符传统操作所需要的操作数的个数与位置而定的。这里的+调用时c1见传值给了lhs,c2就传值给了rhs。
以普通函数重载
普通函数形式重载运算符,要求待操作的属性必须是public类型的。C++允许的重载运算符都可以以普通函数重载。
实例代码如下(代码9-1):
///
/// @file Complex2.cc
/// @date 2019-02-14 15:52:21
///
#include <iostream>
using std::cout;
using std::endl;
//采用普通函数的重载形式实现
class Complex
{
public:
Complex(double real=0 , double imag=0)
:_dreal(real)
,_dimag(imag)
{}
void print() const
{
cout << _dreal;
if(_dimag != 0)
cout << "+" << _dimag << "i" << endl;
else
cout << endl;
}
double getReal() const //由于是采用普通函数重载的,所以必须要用Pulic里的getReal获取其private区的变量
{
return _dreal;
}
double getImage() const //由于是采用普通函数重载的,所以必须要用Pulic里的getImage获取其private区的变量
{
return _dimag;
}
private:
double _dreal;
double _dimag;
};
Complex operator+(const Complex & lhs, const Complex & rhs)
{
return Complex(lhs.getReal()+rhs.getReal(),lhs.getImage()+rhs.getImage());
}
int main()
{
int a = 3, b = 4;
int c = a+b;
Complex c1(0,0);
c1.print();
Complex c2(3,-4);
c2.print();
Complex c3 = c1+c2;
c3.print();
c3 = c2+c; //编译报错,需要给构造函数的形参以初始值,这样就可以实现隐式转换了
return 0;
}
以友元函数重载
示例代码(代码9-2):
///
/// @file complex1.cc
/// @date 2019-02-14 18:20:08
///
#include <iostream>
using std::cout;
using std::endl;
class Complex
{
public:
Complex(double ix = 0, double iy = 0)
:_ix(ix)
,_iy(iy)
{}
void print() const
{
cout << "_ix : "<< _ix << endl;
cout << "_iy: " << _iy << endl;
}
friend Complex operator+(Complex lhs, Complex rhs);//友元函数的声明
private:
double _ix;
double _iy;
};
Complex operator+(Complex lhs,Complex rhs) //友元函数的实现
{
return Complex(lhs._ix + rhs._ix,lhs._iy + rhs._iy);
}
int main()
{
Complex c1(1,2);
c1.print();
Complex c2(3,4);
c2.print();
Complex c3 = c1+5;//要实现这个隐式转换,构造函数必须要有初始值
c3.print();
return 0;
}
从这里可以看出,与普通函数的实现相比,这里不用再单独定义getReal和getImage了。
输入>>输出<<的重载
>>
和<<
运算符只能重载为友元函数形式。对操作符<<
的重载friend ostream& operator<<(ostream& os,const Complex& C1) { os<<C1.real<<"+i*"<<C1.imag<<endl; return os; }
对操作符>>的重载friend istream& operator>>(istream& is,Complex& C1) { is>>C1.real; while (is.get()!='*‘); is>>C1.imag; return is; }
实例代码(代码9-3)
///
/// @file reOs.cc
/// @date 2019-02-16 20:18:29
///
#include <iostream>
#include <ostream>
#include <istream>
using std::cout;
using std::endl;
using std::ostream;
using std::istream;
using std::cin;
class Point
{
public:
Point(int ix=0, int iy=0)
:_ix(ix)
,_iy(iy)
{}
friend ostream & operator<<(ostream &, Point &);//输入输出流不能够复制,所以其返回值必须为引用
friend istream & operator>>(istream &, Point &);
private:
int _ix;
int _iy;
};
ostream & operator<<(ostream &os, Point & pt)
{
os << "( " << pt._ix << " , " << pt._iy << " )" << endl;
return os;
}
istream & operator>>(istream &is, Point & pt)
{
is >> pt._ix;
is >> pt._iy;
return is;
}
int main()
{
Point pt1(3,4);
cout << pt1;
cin >> pt1;
cout << pt1;
return 0;
}
以成员函数重载
实例代码(代码9-4):
///
/// @file memberComplex.cc
/// @date 2019-02-14 15:52:21
///
#include <iostream>
using std::cout;
using std::endl;
class Complex
{
public:
Complex(double real=0 , double imag = 0)
:_dreal(real)
,_dimag(imag)
{}
void print() const
{
cout << _dreal;
if(_dimag != 0)
cout << "+" << _dimag << "i" << endl;
else
cout << endl;
}
Complex operator+( Complex rhs)//+重载
{
return Complex(this->_dreal + rhs._dreal,this->_dimag + rhs._dimag);
}
Complex & operator++()//重载前向++(++a)
{
++_dreal;
++_dimag;
return *this;
}
Complex & operator+=(Complex hs)
{
this->_dreal += hs._dreal;
this->_dimag += hs._dimag;
return *this;
}
Complex operator++(int)//重载后向++(a++)
{
Complex temp(this->_dreal, this->_dimag);
++_dreal;
++_dimag;
return temp;
}
/* Complex operator +=(Complex hs)
{
_dreal += hs._dreal; //在类中可以是由该对象的private成员
_dimag += hs._dimag;
return *this;
}*/
private:
double _dreal;
double _dimag;
};
int main()
{
int a = 3, b = 4;
int c = a+b;
Complex c1(0,0);
c1.print();
Complex c2(3,-4);
c2.print();
Complex c3 = c1+c2;
c3.print();
// c3++;
// c3.print();
// ++c3;
// c3.print();
// c3 += c2;
// c3.print();
// c3 = 5 + c2;//成员函数重载时这样写会报错
c3 = c2+5;
c3.print();
c2 += c1;
c2.print();
return 0;
}
=的成员函数重载
赋值运算是一种很常见的运算,如果不重载赋值运算符,编译器会自动为每个类生成一个缺省的赋值运算符重载函数,先看下面的语句:
对象1=对象2;
实际上是完成了由对象2各个成员到对象1相应成员的复制,其中包括指针成员,这和复制构造函数和缺省复制构造函数有些类似,如果对象1中含指针成员,并且牵扯到类内指针成员动态申请内存时,问题就会出现。
注意下述两个代码的不同:
类名 对象1=对象2; //复制构造函数
类名 对象1(对象2); //复制构造函数
和
类名 对象1; //默认构造函数
对象1=对象2; //赋值运算符函数,实际测试时既运行了复制构造函数也运行了赋值函数,但是没有赋值函数也可以实现。
代码示例(代码9-5)
///
/// @file abouteuqal1.cc
/// @author XuHuanhuan(1982299154@qq.com)
/// @date 2019-02-16 19:00:42
///
#include <iostream>
using std::cout;
using std::endl;
class Point
{
public:
Point()
{}
Point(int ix , int iy)
:_ix(ix)
,_iy(iy)
{
cout << "Point()" << endl;
}
void print()
{
cout << "( " << _ix <<
" , " << _iy << " )"
<< endl;
}
Point & operator=(Point hs) //赋值函数
{
this->_ix = hs._ix;
this->_iy = hs._iy;
cout << "operator= " << endl;
return *this;
}
Point(const Point & hs)
{
cout << "Point(const Point & )" << endl;
this->_ix = hs._ix;
this->_iy = hs._iy;
}
private:
int _ix;
int _iy;
};
int main()
{
cout << "should Point()" << endl;
Point pt1(1,2);
pt1.print();
cout << "should Point(const Point &)" << endl;
Point pt2 = pt1;
pt2.print();
cout << "should Point(const Point &)" << endl;
Point pt3(pt1);
pt3.print();
Point pt4;
cout << "should operator= " << endl;
pt4 = pt1; //这里确实运行了复制构造函数和赋值函数
pt4.print();
return 0;
}
()的重载
括号是没有参数限制的(个数和类型)。一个类如果重载了函数调用operator(),就可以将该类对象作为一个函数使用,这样的类对象也称为函数对象。函数也是一种对象,这是泛型思考问题的方式。
代码示例(代码9-6)
///
/// @file Kuohao.cc
/// @date 2019-02-16 19:23:56
///
#include <string.h>
#include <iostream>
#include <string>
using std::cout;
using std::endl;
using std::string;
class findK
{
public:
~findK()
{
delete [] _pstr;
}
string & operator()(const char * pstr)
{
static string find = "find K!";
static string no = "no K";
_pstr = new char[sizeof(pstr)+1]();
strcpy(_pstr,pstr);
for(size_t i = 0; i <= sizeof(_pstr); i++)
{
if(_pstr[i] == 'K')
{
return find;
}
}
return no;
}
private:
char * _pstr = NULL;
};
int main()
{
findK fin;
cout << fin("asdfasdf") << endl;//看这里调用形式与函数调用并没有区别
cout << fin("asdfhgjkK") << endl;
return 0;
}
[]的重载
下标运算符是个二元运算符,C++编译器将表达式sz[x];
解释为sz.operator[](z);
一般情况下,下标运算符的重载函数原型如下:
返回类型& operator[ ](参数类型);
下标运算符的重载函数只能有一个参数,不过该参数并没有类型限制,任何类型都可以,如果类中未重载下标运算符,编译器将会给出下标运算符的缺省定义,此时,参数必须是int型,并且要声明数组名才能使用下标变量,如computer com[3];
则com[1]等价于com.operator[](1)
,如果[]中的参数类型非int型,或者非对象数组要使用下标运算符时,需要重载下标运算符[]。
实例代码(代码9-7)
///
/// @file operator[].cc
/// @date 2019-02-14 20:33:56
///
#include <iostream>
using std::cout;
using std::endl;
class CharArr
{
public:
CharArr(const char * str)
:_size(sizeof(str))
,_buf(new char[_size])
{}
int getSize() const
{
return _size;
}
char & operator[](int idx)//[]重载
{
static char nullchar = '\0';
if(idx >=0&&idx < _size)
{
return _buf[idx];
}else
{
cout << "over idx" << endl;
return nullchar;
}
}
private:
int _size;
char * _buf;
};
int main()
{
const char * str = "hello, world";
CharArr cstr(str);
cout << str << endl;
cout << cstr[1] << endl;
cout << cstr[100] << endl;
return 0;
}
指针运算符->的重载
实例代码(代码9-8)
///
/// @file reArrow.cc
/// @author XuHuanhuan(1982299154@qq.com)
/// @date 2019-02-16 20:46:53
///
#include <iostream>
using std::cout;
using std::endl;
class note
{
public:
int getNote()
{
return 5;
}
bool geNote()
{
return false;
}
};
class WanNote
{
public:
WanNote()
{
_pnote = new note();
}
~WanNote()
{
delete _pnote;
}
note * operator->()
{
cout << "operator->" << endl;
return _pnote;
}
private:
note * _pnote;
};
int main()
{
WanNote wanx;
cout << wanx->getNote() << endl;//通过->来直接访问到wan子类的成员函数。这等价于(wanx.operator->())->getNote();
return 0;
}
以友元函数重载和以成员函数重载的比较
对于绝大多数可重载操作符来说,两种重载形式都是允许的。但对下标运算符[] 、赋值运算符=、函数调用运算符()、指针运算符->,只能使用成员函数形式。另外,用成员函数重载双目运算符时,左操作数无须用参数输入,而是通过隐含的this指针传入,这种做法的效率比较高。此外,操作符还可重载为友元函数形式,这将没有隐含的参数this指针。对双目运算符,友元函数有2个参数,对单目运算符,友元函数有一个参数。
对于如下代码:
complex c1(1.0, 2.0), cRes;
cRes = c1 + 5; //#1
cRes = 5 + c1; //#2
若以友元函数形式重载的都是合法的,可转换成:
cRes = operator+(c1, 5); //#1 合法
cRes = operator+(5, c1); //#2 合法
但成员函数形式的重载,只有语句#1合法,语句#2非法
cRes = c1.operator+(complex(5)); //#1 可能合法
cRes = 5.operator+(c1); //#2 非法,5不会隐式转换成complex
需要注意的是运算重载的语意要尽量与原意保持一致,不然容易给人误用。
类对象之间的类型转换
转换场合有:
- 赋值转换;
- 表达式中的转换;
- 显式转换;
- 函数调用,传递参数时的转换;
转换方向有: - 由定义类向其他类型的转换;
- 由其他类型向定义类的转换;
由其他类型向定义类的转换
由其他类型(如int、double)等向自定义类的转换是由构造函数来实现的,只有当类的定义和实现中提供了合适的构造函数时,转换才能通过。什么样的构造函数才是合适的构造函数呢?主要有以下几种情况,为便于说明,假设由int类型向自定义point类转换:
- point类的定义和实现中给出了仅包括只有一个int类型参数的构造函数 Point pt1 = 5;
- point类的定义和实现中给出了包含一个int类型参数,且其他参数都有缺省值的构造函数;
- point类的定义和实现中虽然不包含int类型参数,但包含一个非int类型参数如float类型,此外没有其他参数或者其他参数都有缺省值,且int类型参数可隐式转换为float类型参数;
- 在构造函数前加上关键字explicit可以关闭隐式类型转换
上面的几段代码都有示例。
由其他类型向定义类的转换
通过operator int()
这种类似操作符重载函数的类型转换函数来实现由自定义类型向其他类型的转换。如将point类转换成int类型等。在类中定义类型转换函数的形式一般为:operator 目标类型名();
有以下几个使用要点:
- 转换函数必须是成员函数,不能是友元形式;
- 转换函数不能指定返回类型,但在函数体内必须用return语句以传值方式返回一个目标类型的变量;
- 转换函数不能有参数。
示例代码如下(代码9-7):
///
/// @file turnOther.cc
/// @date 2019-02-17 16:25:31
///
//将Point类转为int和anotherPoint类
#include <iostream>
using std::cout;
using std::endl;
class anotherPoint
{
public:
anotherPoint(int ix, int iy)
:_ix(ix)
,_iy(iy)
{}
void print() const
{
cout << "anotherPoint()" << endl;
cout << "( " << _ix
<< " , " << _iy
<< " )" << endl;
}
private:
int _ix;
int _iy;
};
class Point
{
public:
Point(int ix, int iy)
:_ix(ix)
,_iy(iy)
{}
void print() const
{
cout << "Point()" << endl;
cout << "( " << _ix
<< " , " << _iy
<< " )" << endl;
}
operator int()
{
cout << "Point()->int()" << endl;
return _ix;
}
operator anotherPoint()
{
cout << "Point()->anotherPoint()" << endl;
return anotherPoint(_ix, _iy);
}
private:
int _ix;
int _iy;
};
int main()
{
Point pt1(2,3);
int i1 = pt1;
cout << "i1 = " << i1 << endl;
anotherPoint ant1(2,3);
ant1.print();
ant1 = pt1;
ant1.print();
return 0;
}