在之前的文章中,我们简单介绍过运算符重载。运算符重载其实也算是一种函数重载,只是该函数可以用下边的两种形式实现调用:
operator x(argument);
... x ...;
上边的形式中,x 表示运算符。其中第二种形式严格来说不太对,应该你不知道运算符的类型,因此只表示一种形式。
运算符重载
定义
datatype operator x(argument)
{
statement;
}
规则
- 不能定义新的运算符
- 只能对已有的 C++ 运算符进行重载
- C++ 允许能够重载的运算符
+ | - | * | / | % | ^ | & |
| | ~ | ! | = | < | > | += |
-= | *= | /= | %= | ^= | &= | |= |
<< | >> | >>= | <<= | == | != | <= |
>= | && | || | ++ | -- | , | ->* |
-> | () | [] | new | delete | new [] | delete [] |
- C++ 不允许重载的运算符
. | .* | :: | ?: | sizeof |
- 只能重载为成员函数的运算符
= | [] | () | -> | ->* |
- 重载不能改变运算符操作数的个数
- 重载不能改变运算符的优先级
- 重载不能改变运算符的结合性
- 重载不能含有默认参数
- 重载的参数至少有一个是自定义类或枚举类型
- 不需要重载的运算符(=,&)
自定义类中的 = 默认调用 = 重载,因为会为类生成默认的 = 重载,因此无需重载,当然如果有必要的话也可以自定义 = 重载。
而 & 返回的一直是类对象的地址,也不必重载。
- 重载不应失去符号原有的意义
实例
成员 or 友元
#include <iostream>
using std::cout;
using std::endl;
class POINT
{
public:
POINT(int x = 0,int y = 0):x(x),y(y){}
void display()
{
cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<endl;
}
const POINT operator+(const POINT &obj)
{
cout<<"POINT & operator+(const POINT &obj)"<<endl;
return POINT(x + ,y + );
}
friend const POINT operator+(const POINT &obj1,const POINT & obj2);
private:
int x;
int y;
};
const POINT operator+(const POINT &obj1,const POINT & obj2)
{
cout<<"friend POINT & operator+(const POINT &obj1,const POINT & obj2)"<<endl;
return POINT( + , + );
}
int main()
{
POINT p1(10,20);
POINT p2(30,40);
();
();
POINT p3 = p1+p2;
POINT p4 = operator+(p1,p2);
();
();
();
();
return 0;
}
结果为:
The x is 10,The y is 20
The x is 30,The y is 40
POINT & operator+(const POINT &obj)
friend POINT & operator+(const POINT &obj1,const POINT & obj2)
The x is 10,The y is 20
The x is 30,The y is 40
The x is 40,The y is 60
The x is 40,The y is 60
上边的结果中:
- 同时存在成员函数和友元函数的形式,两者都是要实现 POINT 元素的相加,但是调用时有所差别:
- 成员函数是作为类对象的方法使用的,因此 p1 + p2 的形式更接近于 (p2) 的形式
- 而 operator=(p1,p2) 的形式不是通过对象调用的,会直接调用全局函数中的 + 重载
- 成员函数需要返回 const 类型,因为返回值还要进行 = 重载,而 = 重载的形参是 const 的引用,非 const 不能传给 const 的引用,因此返回值需要加 const
- 因为成员函数和友元函数都是采用了临时对象作为返回值,因此只能返回对象,而不能返回对象的引用。因为引用作为别名,是不能够在对象被析构后返回的。
- 友元函数类外定义,不需要加 friend 关键字
双目运算符重载
// global style
datatype operator x(arg1,arg2);
// class inner style
datatype operator x(arg2);
全局条件下,因为不存在调用该函数的对象,而双目运算符需要两个参数,因此需要两个参数 arg1 和 arg2,同样全局函数定义时只能通过友元实现。
类内定义时,重载的中 this 指针默认为调用该函数的对象,因此只需要一个参数 arg2。
#include <iostream>
using std::cout;
using std::endl;
class POINT
{
public:
POINT(int x = 0,int y = 0):x(x),y(y){}
void display()
{
cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<endl;
}
const POINT & operator+=(const POINT &obj)
{
x = x + ;
y = y + ;
return *this;
}
private:
int x;
int y;
};
int main()
{
POINT p1(10,20);
POINT p2(30,40);
();
();
p1 += p2;
();
();
return 0;
}
结果为:
The x is 10,The y is 20
The x is 30,The y is 40
The x is 40,The y is 60
The x is 30,The y is 40
此时 += 重载可以返回引用,因为是已经存在的对象。
此时尽量使用引用,因为返回引用和返回对象的实质不太一样,一个是返回了已创建对象的别名,一个是返回临时对象,再次拷贝构造的结果。
单目运算符重载
// global style
datatype operator x(arg);
// class inner style
datatype operator x();
与双目运算符重载处的解释差不多。
#include <iostream>
using std::cout;
using std::endl;
class POINT
{
public:
POINT(int x = 0,int y = 0):x(x),y(y){}
void display()
{
cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<endl;
}
const POINT operator-() const
{
return POINT(-x, -y);
}
private:
int x;
int y;
};
int main()
{
POINT p1(10,20);
();
p1 = -p1;
();
p1 = -(-p1);
();
return 0;
}
结果为:
The x is 10,The y is 20
The x is -10,The y is -20
The x is -10,The y is -20
上边的程序实现了 -(负号) 的重载,重载函数中含有两个 const:
const POINT operator-() const;
前一个 const 修饰返回值,表明返回的是 const POINT,后一个 const 修饰函数,const 类对象只能访问 const 方法。前一个 const 不能使下边的赋值形式成立:
-p1 = P2;
后一个 const 使返回的 const 对象能够继续调用该方法,并使下边的形式成立:
p1 = -(-p1);
#include <iostream>
using std::cout;
using std::endl;
class POINT
{
public:
POINT(int x = 0,int y = 0):x(x),y(y){}
void display()
{
cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<endl;
}
POINT & operator++()
{
x++;
y++;
return *this;
}
private:
int x;
int y;
};
int main()
{
POINT p1(10,20);
();
++p1;
();
++++p1;
();
return 0;
}
结果为:
The x is 10,The y is 20
The x is 11,The y is 21
The x is 13,The y is 23
上边的程序实现了 ++(前) 的重载,返回非 const 引用使下边的形式能够实现:
++++p1;
因为返回的是自己对象的引用,并且该引用没有用 const 修饰,因此可以对结果再次运算,并能够保证 p1 也被更改。
#include <iostream>
using std::cout;
using std::endl;
class POINT
{
public:
POINT(int x = 0,int y = 0):x(x),y(y){}
void display()
{
cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<endl;
}
const POINT operator++(int)
{
POINT tmp(*this);
x++;
y++;
return tmp;
}
private:
int x;
int y;
};
int main()
{
POINT p1(10,20);
();
p1++;
();
return 0;
}
结果为:
The x is 10,The y is 20
The x is 11,The y is 21
上边的程序实现了 ++(后) 的重载,函数形参中使用了 int,但是实际却并没有实参传递,这种使用方式叫做哑元,是 C++ 内部用来区分 i++ 和 ++i 的方式。
返回值为 POINT 类型是因为返回的是函数栈中创建的临时对象,因此不能采用引用的形式,也是为了实现 p1++ 的先执行再 ++ 的过程。
而返回值用 const 修饰则是为了屏蔽掉下边的形式:
p1++++;
因为返回的是临时对象,因此对临时对象再执行后 ++ 操作是没有意义的。
#include <iostream>
using std::cout;
using std::endl;
using std::ostream;
class POINT
{
public:
POINT(int x = 0,int y = 0):x(x),y(y){}
void display()
{
cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<endl;
}
friend ostream & operator<<(ostream &out, const POINT &obj);
private:
int x;
int y;
};
ostream & operator<<(ostream &out, const POINT &obj)
{
out<<"The x is "<<<<","<<"The y is "<<<<endl;
return out;
}
int main()
{
POINT p1(10,20);
();
cout<<p1<<endl;
return 0;
}
结果为:
The x is 10,The y is 20
The x is 10,The y is 20
上边的程序中实现了流输出运算符的重载,因为日常书写的流输出的形式总是:
cout<<var<<endl;
因此,该运算符只能重载为该类的友元,因为成员函数的第一个参数默认的是该类对象的指针,同样为了实现串联输出,需要返回引用。
同样,从该运算符的重载过程中也可以得知:
- 如果运算符是多目(多为双目)的话,运算符的左右操作数不一定是相同类型的对象,这就涉及该重载函数的定位问题,将之定义为谁的成员,谁的友元
- 通常情况下,由于存在 this 指针的问题,运算符被声明为哪个类的成员,通常取决于该函数的调用对象(一般为左操作数)
- 通常情况下,运算符被声明为哪个类的友元,通常取决于该函数的参数类型(一般为右操作数)
流输入运算符的重载也可以仿照此逻辑书写。
自定义类型转换
能够实现从源类型到目标类型的转化。
转换构造函数
定义
class targetclass
{
targetclass(const sourceclass &sourceclassvar)
{
statement;
}
}
性质
- 转换构造函数从本质上来说仍旧是构造函数
- 且只含有一个参数
- 如果同时含有多个参数,只能成为构造函数,而不是转换函数
#include <iostream>
using std::cout;
using std::endl;
class POINT
{
public:
POINT(int x = 0,int y = 0):x(x),y(y){}
void display()
{
cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<endl;
}
private:
int x;
int y;
friend class POINT3;
};
class POINT3
{
public:
POINT3(int x = 0,int y = 0,int z = 0):x(x),y(y),z(z){}
POINT3(const POINT &obj)
{
this->x = ;
this->y = ;
this->z = 0;
}
void display()
{
cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<","<<"The z is "<<this->z<<endl;
}
private:
int x;
int y;
int z;
};
int main()
{
POINT p1(10,20);
();
POINT3 p2(p1);
();
POINT3 p3 = p1;
();
return 0;
}
结果为:
The x is 10,The y is 20
The x is 10,The y is 20,The z is 0
The x is 10,The y is 20,The z is 0
上边的结果实现了从 POINT 到 POINT3 的转换,这就是转换构造函数的作用,当然也有友元的帮忙。
explicit
explicit 关键字主要用来修饰构造函数,强调该构造函数必须使用显示转换的方式进行转换。从这句描述来看,可知对于转换构造函数应该是有用的。
#include <iostream>
using std::cout;
using std::endl;
using std::ostream;
class POINT
{
public:
POINT(int x = 0,int y = 0):x(x),y(y){}
void display()
{
cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<endl;
}
private:
int x;
int y;
friend class POINT3;
};
class POINT3
{
public:
POINT3(int x = 0,int y = 0,int z = 0):x(x),y(y),z(z){}
explicit POINT3(const POINT &obj)
{
this->x = ;
this->y = ;
this->z = 0;
}
void display()
{
cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<","<<"The z is "<<this->z<<endl;
}
private:
int x;
int y;
int z;
};
int main()
{
POINT p1(10,20);
();
POINT3 p2(p1);
();
POINT3 p3 = static_cast<POINT3>(p1);
();
return 0;
}
此时将转换构造函数用关键字 explicit 声明,就只能使用下边的形式进行转换了。
POINT3 p3 = static_cast<POINT3>(p1);
类型转换操作符函数
定义
class sourceclass
{
operator targetclass()
{
statement;
}
}
性质
- 转换函数为类方法
- 转换函数无参数,无返回
- 该转换函数函数体一般为目标类的构造函数
- 该转换函数也可以用 explicit 关键字修饰
#include <iostream>
using std::cout;
using std::endl;
class POINT3
{
public:
POINT3(int x = 0,int y = 0,int z = 0):x(x),y(y),z(z){}
void display()
{
cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<","<<"The z is "<<this->z<<endl;
}
private:
int x;
int y;
int z;
};
class POINT
{
public:
POINT(int x = 0,int y = 0):x(x),y(y){}
operator POINT3()
{
return POINT3(x,y,0);
}
void display()
{
cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<endl;
}
private:
int x;
int y;
};
int main()
{
POINT p1(10,20);
();
POINT3 p2(p1);
();
POINT3 p3 = p1;
();
return 0;
}
结果为:
The x is 10,The y is 20
The x is 10,The y is 20,The z is 0
The x is 10,The y is 20,The z is 0
异同点
相同之处
- 都能够进行自定义类型的转换
- 该本类中使用这两种转换函数时,都需要提前定义(至少是声明)源类
不同之处
- 转换构造函数是将源类,转化为该类
- 类型转换操作符函数则是将该类转换为目标类
高级应用
仿函数
定义
class classname
{
datatype operator()(argument)
{
statement;
}
}
作用
仿函数能够使类像函数一样使用,其实是重载了 (),使之具有了类似函数的行为:
#include <iostream>
using std::cout;
using std::endl;
class POINT
{
public:
POINT(int x = 0,int y = 0):x(x),y(y){}
void operator()()
{
cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<endl;
}
private:
int x;
int y;
};
int main()
{
POINT p1(10,20);
p1();
return 0;
}
结果为:
The x is 10,The y is 20
上边的结果显示,借由 () 重载能够实现之前 display 成员函数的功能。当然也可以对仿函数进行重载。
new/delete
定义
void *operator new(size_t);
void operator delete(void *);
void *operator new[](size_t);
void operator delete[](void *);
作用
同样为了不改变运算符的基本含义,也是用来申请内存和释放内存的,只是可以在函数体内实现一些定制的功能,一般用不到。
只是此时的 size_t 参数不是由用户手动给出的,而是编译器自动计算传递的。
解引用
和 new/delete 一样,感觉一般用不到。形式为:
classname & operator*()
{
statement;
}
classname & operator->()
{
statement;
}
智能指针
这一部分不扩展来说明,同样智能指针存在好几种类型,这里只说明 auto_ptr。
RAII
资源获取即初始化(Resource Acquisition Is Initialization,RAII),是 C++ 的管理资源,避免泄露的方法。
auto_ptr
使用 auto_ptr 可以使资源能够自动的被释放。对于使用 new 申请的内存来说很有用。
#include <iostream>
#include <memory>
using std::cout;
using std::endl;
using std::auto_ptr;
class POINT
{
public:
POINT(int x = 0,int y = 0):x(x),y(y)
{
cout<<"POINT(int x = 0,int y = 0):x(x),y(y)"<<endl;
}
void display()
{
cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<endl;
}
~POINT()
{
cout<<"~POINT()"<<endl;
}
private:
int x;
int y;
};
void func()
{
auto_ptr<POINT> p(new POINT);
p->display();
}
int main()
{
func();
return 0;
}
结果为:
POINT(int x = 0,int y = 0):x(x),y(y)
The x is 0,The y is 0
~POINT()
从上边的结果可以看出,使用 auto_ptr 可以完成申请内存的自动释放。
其实智能指针的实现也是借用了运算符重载的概念,这里不做详细说明,以后再记。