operator是C++的关键字,它和运算符一起使用,表示一个运算符函数,理解时应将operator=整体上视为一个函数名。
这是C++扩展运算符功能的方法,虽然样子古怪,但也可以理解:一方面要使运算符的使用方法与其原来一致,另一方面扩展其功能只能通过函数的方式(c++中,“功能”都是由函数实现的)。
一、为什么使用操作符重载?
对于系统的所有操作符,一般情况下,只支持基本数据类型和标准库中提供的class,对于用户自己定义的class,如果想支持基本操作,比如比较大小,判断是否相等,等等,则需要用户自己来定义关于这个操作符的具体实现。比如,判断两个人是否一样大,我们默认的规则是按照其年龄来比较,所以,在设计person 这个class的时候,我们需要考虑操作符==,而且,根据刚才的分析,比较的依据应该是age。那么为什么叫重载呢?这是因为,在编译器实现的时候,已经为我们提供了这个操作符的基本数据类型实现版本,但是现在他的操作数变成了用户定义的数据类型class,所以,需要用户自己来提供该参数版本的实现。
二、如何声明一个重载的操作符?
A: 操作符重载实现为类成员函数
重载的操作符在类体中被声明,声明方式如同普通成员函数一样,只不过他的名字包含关键字operator,以及紧跟其后的一个c++预定义的操作符。
可以用如下的方式来声明一个预定义的==操作符:
class person{
private:
int age;
public:
person(int a){
this->age=a;
}
inline bool operator == (const person &ps) const;
};
实现方式如下:
inline bool person::operator==(const person &ps) const
{
if (this->age==ps.age)
return true;
return false;
}
调用方式如下:
#include
using namespace std;
int main()
{
person p1(10);
person p2(20);
if(p1==p2) cout<<”the age is equal!”< return 0;
}
这里,因为operator ==是class person的一个成员函数,所以对象p1,p2都可以调用该函数,上面的if语句中,相当于p1调用函数==,把p2作为该函数的一个参数传递给该函数,从而实现了两个对象的比较。
B:操作符重载实现为非类成员函数(全局函数)
对于全局重载操作符,代表左操作数的参数必须被显式指定。例如:
#include
#include
using namespace std;
class person
{
public:
int age;
public:
};
bool operator==(person const &p1 ,person const & p2)
//满足要求,做操作数的类型被显示指定
{
if(p1.age==p2.age)
return true;
return false;
}
int main()
{
person rose;
person jack;
rose.age=18;
jack.age=23;
if(rose==jack)
cout<<"ok"< return 0;
}
C:如何决定把一个操作符重载为类成员函数还是全局名字空间的成员呢?
①如果一个重载操作符是类成员,那么只有当与他一起使用的左操作数是该类的对象时,该操作符才会被调用。如果该操作符的左操作数必须是其他的类型,则操作符必须被重载为全局名字空间的成员。
②C++要求赋值=,下标[],调用(), 和成员指向-> 操作符必须被定义为类成员操作符。任何把这些操作符定义为名字空间成员的定义都会被标记为编译时刻错误。
③如果有一个操作数是类类型如string类的情形那么对于对称操作符比如等于操作符最好定义为全局名字空间成员。
D:重载操作符具有以下限制:
(1) 只有C++预定义的操作符集中的操作符才可以被重载;
重载不能改变操作符的优先级
如果一个内建操作符是一元的,那么所有对它的重载仍是一元的。如果是二元的重载后也是二元的
头文件Complex.h:
#include
using namespace std;
class Complex {
public:
private:
};
Complex::Complex(){
}
Complex::Complex(double re) {
};
Complex::Complex(double re,double im) {
}
void Complex::write() const {
};
Complex Complex::operator + (const Complex &u) const {
}
Complex Complex::operator - (const Complex &u) const {
}
Complex Complex::operator* (const Complex &u) const {
}
Complex Complex::operator / (const Complex &u) const {
}
测试文件:Complex.cpp和测试结果:
一个被重载的操作符,就是一个用户自定义的函数,只不过它可以享受操作符语法所带来的便利
除了内存管理操作符new、new[]、delete、delete[]之外,一个以顶层函数形式被重载的操作符必须在它的参数列表中包含一个类的对象
下标操作符[]、赋值操作符=、函数调用操作符()和指针操作符->必须以类成员函数的形式进行重载(这样可以保证第一个操作数是类的对象,不然9[x]、6.32=x,不被接受)
操作符(如%)要么以成员函数被重载,要么以顶层函数被重载。对于后者将至少带一个对象参数,这是因为操作符以顶层函数实现时,如果连一个参数都没有,那么对如下表达式
顶层函数重载操作符,类名和域解析符都没有了,因为它不是一个类成员函数
被重载的操作符,要么是一个类成员函数,要么在它的参数列表中包含一个类成员!!
下标操作符[]和函数调用操作符()只能以成员函数的形式被重载,不能成为顶层函数!!
eg:
修正后:
在上面那种情况,如果想要第二个表达式成功的话,就需要定义一个顶层函数重载+了
有一点要明白,当定义顶层函数时,函数里面不能含有对象的私有成员操作,否则编译通不过,下面有三种方法可以解决:
1,将私有成员设计为public成员,但是这种方法违背了类的信息隐藏原则
2,在Complex中加入用于访问real和imag的公有成员函数,但是容易造成接口混淆
3,将操作符重载函数声明为类的friend,但是不符合面向对象原则,应少用,建议仅在操作符重载时使用
类的私有成员只能被该类的成员函数和该类的friend函数访问
类的保护成员只能被该类的或其派生类的成员函数和该类的friend函数访问
class C {
friend int f();
//...
};
该声明赋予f访问C的私有和保护成员的权力,因为f不是成员函数,该声明可以放在C中的private、protected或public的任意部分,不受访问控制符的影响、限制
程序员可对操作符>>进行重载,以支持用户自定义数据类型。>>的第一个操作数是系统类的对象(如cin是系统类istream的对象),而这些重载函数时以类成员函数的形式实现的。
如果在对用户自定义的类型重载>>时,就必须对系统类的源代码进行修改,而这显然是非常不明智的做法,因此只能将>>重载函数设计为顶层函数
输入流对象总是以引用方式传递,这是因为系统为了接受输入数据,需要更新输入流对象的某些信息
如果被重载>>的函数中和类的私有数据或者保护数据打交道,则需要将重载操作符声明为friend
拷贝构造函数和赋值操作符(=),都是用来拷贝一个类的对象给另一个相同类型的对象。
拷贝构造函数将一个对象拷贝到另一个新的对象(因为拷贝-构造);赋值操作符将一个对象拷贝到另一个已经存在的对象
如果类的作者没有提供拷贝构造函数,也没有重载赋值操作符,编译器将会给这个类提供一个拷贝构造函数和一个赋值操作符。编译器提供的拷贝构造函数和赋值操作符的运转机制是:将源对象中的每个数据成员拷贝到目标对象相应的数据成员中
看例子:
如果类的作者定义了指针成员,且该指针指向一块动态分配的存储空间,就应该为这个类设计拷贝构造函数,并重载赋值操作符
注意不要返回临时变量,重载里面错综复杂,小心一点
下标操作符[]必须要以成员函数的形式进行重载!
returntype & operator[] (paramtype);
};
或者:
class C {
const returntypr & operator[] (paramtype) const;
};
例子:
头文件:test.h
测试文件和结果:
上面可以不提供const版本的重载,但是就不能处理非const的对象,所以,有时候要考虑这一点,加上const重载!
函数调用操作符()必须要以成员函数的形式重载。
class C {
returntype operator()(paramtypes);
};
看例子:
头文件inttwoarray.h
#include
#include
using namespace std;
class intTwoArray {
public:
private:
};
int & intTwoArray::operator() (int i,int j) {
}
const int & intTwoArray::operator() (int i,int j) const {
}
intTwoArray::intTwoArray(int s1,int s2) {
}
测试程序和结果:
自增,自减也可以重载,但是前置,后置一共四种,前后置通过一个int参数区分,但是这个参数没有什么实际用途,仅仅起到区分的作用
例子:
#include
#include
using namespace std;
/
class Entry;
class Dict;
ostream & operator<<(ostream &,const Entry &);
ostream & operator<<(ostream &,const Dict &);
class Entry {
public:
private:
};
void Entry::operator=(const string &str) {
}
void Entry::operator=(const char * str) {
}
ostream & operator<<(ostream &out,const Entry &e) {
}
void Entry::add(const string &w,const string &d) {
}
bool Entry::match(const string &key) const {
}
enum {MaxEntries = 100};
class Dict {
public:
private:
};
ostream & operator<<(ostream &out,const Dict &d) {
}
Entry &Dict::operator[](const string &k) {
}
Entry & Dict::operator[](const char *k) {
}
void Dict::remove(const string &w)
{
}
测试程序和结果:
内存管理操作符new、new[]、delete和delete[]既可以用成员函数也可以用顶层函数重载
嵌入式内存有限,应用程序经常需要直接管理内存
new操作符重载:
void * C::operator new(size_t size) {
//...
}
和
void *
}
返回值void *
new和new[]操作符重载函数的第一个参数必须是size_t类型,其数值等于被创建对象大小,其他参数是可选的
delete操作符重载:
void C::operator delete( void * objPtr) {
//...
}
和
void operator delete(void *objPtr) {
}
delete和delete[]操作符重载函数的第一个参数必须是void*类型,返回值必须是void,而其他参数则是可选的
看例子:
头文件frame.h:
#include
#include
using namespace std;
const int
const int
class Frame {
public:
void delete(void *);
private:
};
Frame * allFrames = 0;
unsigned char framePool[MaxFrames * sizeof(Frame)];
bool alloc[MaxFrames];
Frame::Frame(const string &n,const void *d, unsigned bsize) {
}
void Frame::print() const {
}
void * Frame::operator new(size_t size) {