您的位置:首页 > 编程语言 > C语言/C++

C++操作符重载

2016-05-16 22:12 351 查看
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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: