1.输入和输出操作符
对于输入和输出操作符,对其进行重载需要将操作符重载函数定义为非成员函数,否则操作符的左操作数只能是该类类型的对象。为了保证操作符重载函数能够正常访问类中定义的私有成员,需要将操作符重载函数定义为类的友元函数。下面是重载输入和输出操作符的一个简单例子。
#include <iostream> using namespace std; class point{ private: int x; int y; public: point(int x,int y){ this->x=x; this->y=y; } friend ostream &operator<<(ostream &os,const point &p); friend istream &operator>>(istream &is,point &p); }; ostream & operator<<(ostream &os, const point &p) { os<<p.x<<" "<<p.y; return os; } istream & operator >>(istream &is,point &p){ is>>p.x>>p.y; return is; } int main() { point * p = new point(1,2); cout<<*p<<endl; cin>>*p; cout<<*p<<endl; return 0; }
运行截图:
2.算术操作符和关系操作符
一般,将算术和关系操作符定义为非成员函数。如下定义了上述类point的加法操作符重载函数。
#include <iostream> using namespace std; class point{ private: int x; int y; public: point(int x,int y){ this->x=x; this->y=y; } friend ostream &operator<<(ostream &os,const point &p); friend istream &operator>>(istream &is,point &p); //定义了友元函数,用于重载加法运算符号 friend point operator+(const point &p1,const point &p2); }; ostream & operator<<(ostream &os, const point &p) { os<<p.x<<" "<<p.y; return os; } istream & operator >>(istream &is,point &p){ is>>p.x>>p.y; return is; } //非成员函数,重载加法运算符 point operator+(const point &p1,const point &p2){ auto p = new point(p1.x+p2.x,p1.y+p2.y); return *p; } int main() { //在堆区实例化类成员 point * p1 = new point(1,2); //在栈区实例化类成员 point p2(3,4); auto p = *p1 +p2; cout<<p<<endl; return 0; }
输出结果:
4 6
3.赋值操作符
对于赋值操作符的重载函数,必须要返回对*this的引用,返回赋值之后的对象。以下定义了类point的赋值重载函数。
#include <iostream> using namespace std; class point{ private: int x; int y; public: point(int x,int y){ this->x=x; this->y=y; } friend ostream &operator<<(ostream &os,const point &p); friend istream &operator>>(istream &is,point &p); //定义了成员函数,用于重载赋值操作符 point &operator=(const point & p){ x=p.x; y=p.y; return *this; } }; ostream & operator<<(ostream &os, const point &p) { os<<p.x<<" "<<p.y; return os; } istream & operator >>(istream &is,point &p){ is>>p.x>>p.y; return is; } int main() { //在堆区实例化类成员 point * p1 = new point(1,2); point p2 = *p1; cout<<p2<<endl; return 0; } //输出 : 1 2
4.下标操作符
定义下标操作符比较复杂,需要保证它在用作赋值的左右操作数的时候都能够表现正常。下标操作符出现在左边,必须生成左值,可以指定引用作为返回类型而作为左值。
可以分为两种情况,const对象和非const对象来定义下标操作符重载函数。引用于const对象的时候,返回的值为const引用,不能被赋值。以下是一个简单的例子。
#include<iostream> #include<vector> using namespace std; class fun { private: vector <int> data; public: void push(int x){ data.push_back(x); } //可以用作左值 int &operator[](const size_t index){ return data[index]; } //类的成员函数之后使用const进行修饰,表明这个函数不会对这个类对象的数据成员 //(准确地说是非静态数据成员)作任何改变。 //用于右值 const int & operator[](const size_t index) const{ return data[index]; } void print(){ for(auto a:data){ cout<<a<<" "<<endl; } } }; int main() { fun *test =new fun(); for(int i=0;i<3;i++) test->push(i); //右值 int value1 = (*test)[1]; //左值 (*test)[2]=-2; test->print(); return 0; }
输出:
0
1
-2
5.成员访问操作符重载
如下,我们实现了一个简单的采用引用计数方式实现的智能指针类。指针指向的真实数据对象是类别Screen。类Screen中的数据成员x和y表示像素点的位置坐标。为了实现类Screen的智能管理,我们进一步定义了类Scrptr和ScreenPtr。类Scrptr中的数据成员分别是指向Screen对象的指针和Screen对象的引用计数,如果引用计数等于0,就销毁Screen对象。每引用一次Screen对象,就对引用计数的值加上1。类Scrptr指定类ScreenPtr为友元类,类ScreenPtr可以自由访问类ScrPtr中的私有成员。我们不通过类ScrPtr来直接访问类Screen,这是因为在下述情况下:
class ScrPtr{ public: //constructor use Screen* Scrptr(Screen * ptr){ this->s_ptr=ptr; use =0; } //constructor use ScrPtr & Scrptr(ScrPtr & org){ ++org.use; this->s_ptr =org.s_ptr; use =org.use; } private: Screen * s_ptr; int use; } Screen * screen = new Screen(1,2); ScrPtr p1 (screen); ScrPtr p2(p1); ScrPtr p3(p1); //在这种情况之下,如何更新p2的引用计数成为了问题 //可以在p1中将计数增量并且复制到p3,但是怎样更新p2中的计数
p1、p2和p3的背后只有一个Screen对象,但是计数器的值却有两种,这样无法追踪真实的对象到底被引用了几次。这种解决方式无法跟踪对象的实际引用情况,没有实现对象和引用计数的绝对绑定。
通过使用间接的友元类来访问类ScrPtr来间接的访问类Screen,能够解决上述问题,实现了对象和引用计数的绝对绑定,可以确定的追踪每一个对象的实际引用情况。
#include<iostream> #include<vector> using namespace std; class Screen{ private: int x; int y; public: Screen(int x, int y) : x(x), y(y) { } int getX() const { return x; } void setX(int x) { Screen::x = x; } int getY() const { return y; } void setY(int y) { Screen::y = y; } void print(){ cout<<x<<endl<<y<<endl; } }; class ScrPtr{ friend class ScreenPtr; Screen *sp; size_t use; ScrPtr(Screen *p):sp(p),use(1){} ~ScrPtr(){delete sp;} }; class ScreenPtr{ private: ScrPtr *ptr; public: ScreenPtr(Screen *p):ptr(new ScrPtr(p)){} ScreenPtr(const ScreenPtr &orig):ptr(orig.ptr){++ptr->use;} ScreenPtr &operator=(const ScreenPtr & rhs) { //右值的引用计数加1 ++rhs.ptr->use; //左值的引用计数减1 delete this; //完成赋值操作 ptr = rhs.ptr; //返回赋值之后对象的引用 return *this; } Screen &operator*(){ return *(ptr->sp); } Screen *operator->(){ return ptr->sp; } const Screen &operator*() const{ return *(ptr->sp); } const Screen *operator->() const{ return ptr->sp; } ~ScreenPtr(){if(--ptr->use==0) delete ptr; } }; int main() { vector<ScreenPtr *> v ; Screen *a = new Screen(1,2); Screen *b = new Screen(3,4); ScreenPtr *ptr_a = new ScreenPtr(a); ScreenPtr *ptr_b = new ScreenPtr(b); v.push_back(ptr_a); v.push_back(ptr_b); *ptr_a=*ptr_b; for(int i=0;i<v.size();i++){ auto ptr =v[i]; (*ptr)->print(); } }
因为采取这种间接的方式访问Screen对象,通过操作符*与->并不能够直接访问类Screen,为此我们可以重载这两个成员访问操作符,调用真实的Screen对象。上述标红代码就调用了重载之后的成员访问操作符函数。
6.重载自增和自减操作符
重载自增和自减操作符分为两种情况,分别是前缀操作符重载和后缀操作符重载,可以使用下述例子进行简要的说明。
重点:对于后缀自增和自减操作符,编译器会自动传入一个int类型的数0作为参数进行调用,从而与前缀自增和自减操作符进行区分。
#include<iostream> using namespace std; class Value{ private: int a; public: Value (int a){ this->a =a ; } Value & operator ++ () { a++; return *this; } Value & operator ++ (int) { Value ret(a); ++a; return ret; } Value & operator -- (){ a--; return *this; } Value &operator -- (int){ Value ret(a); --a; return ret; } void print(){ cout<<a<<endl; } }; int main(){ Value a(1); //后缀自增 Value b = a++; a.print(); b.print(); //前缀自增 b = ++a; a.print(); b.print(); return 0; }
输出:
2
1
3
3
7.转换操作符重载
转换操作符重载能够方便的利用类型转换来简化操作,如下所示:
#include<iostream> using namespace std; class SmallInt{ private: int value; public: SmallInt(int value):value(value){ if(value <0||value>256){ throw out_of_range("bad smallint initializer!"); } } //转换操作符重载函数无需指明返回值的类型 operator int() const { return value; } }; int main(){ SmallInt a (100); //自动调用转换操作符,采用int数的方式输出SmallInt对象 cout<<a<<endl; return 0; }
但是转换操作符重载可能会引起二义性问题。如下所示,我们如果同时定义了SmallInt类型和double和int之间的转换关系,在发生下述操作的时候,就会引起二义性问题:
#include<iostream> using namespace std; class SmallInt{ private: int value; public: SmallInt(int value):value(value){ if(value <0||value>256){ throw out_of_range("bad smallint initializer!"); } } //转换操作符重载函数无需指明返回值的类型 operator int() const { return value; } operator double() const { return value; } }; int main(){ long double a =1.0; SmallInt b(1); cout<<a+b<<endl; return 0; }
报错:
error: use of overloaded operator '+' is ambiguous (with operand types 'long double' and 'SmallInt')
错误在于从SmallInt转换成为long double类型是首先将SmallInt转换成int类型还是首先转换成double类型?这就存在歧义了!
参考:C++ Primer 4th