运算符重载(三)
- 必须是成员函数,不能是友元函数。
- 没有参数(操作数是什么?)。
- 不能指定返回类型(其实已经指定了)。
- 函数原型:operator 类型名();
下面用代码来说明,先新建工程,将上次实现的Integer类添加至项目中来:
Integer.h:
#ifndef _INTEGER_H #define _INTEGER_H class Integer { public: Integer(int n); ~Integer(); void display() const; //Integer& operator ++();//++运算符重载 friend Integer& operator ++(Integer& i);//友元方式重载前置++ friend Integer operator ++(Integer& i, int n);//友元方式重载后置++ //Integer operator ++(int i);//后置++运算符重载,其中的参数并没啥用,只是为了和前置++形成重载 private: int n_; }; #endif //_INTEGER_H
Integer.cpp:
#include "Integer.h" #include <iostream> using namespace std; Integer::Integer(int n) : n_(n) { } Integer::~Integer() { } //Integer& Integer::operator ++() { // ++n_; // return *this; //} Integer& operator ++(Integer& i) { ++i.n_; return i; } //Integer Integer::operator ++(int i) { // Integer tmp(n_);//构造一个临时对象 // n_++;//紧接着再来修改n_ // return tmp;//这时返回的是对象,而不是引用了,因为如果是引用,临时对象会被回收,则指向了一个无效的对象了 //} Integer operator ++(Integer& i, int n) { Integer tmp(i.n_);//构造一个临时对象 i.n_++;//紧接着再来修改n_ return tmp;//这时返回的是对象,而不是引用了,因为如果是引用,临时对象会被回收,则指向了一个无效的对象了 } void Integer::display() const{ cout<<n_<<endl; }
测试代码:
#include "Integer.h" #include <iostream> using namespace std; int main(void) { Integer n(100); n = 200;//可以将一个整型转换成类类型,因为实现了转换构造函数 n.display(); return 0; }
上面的输出的写法及输出结果显而易见,就不多说了,接下来实现加法运算的函数:
很遗憾,这样的写法目前还不支持:
这时需要重写类型转换运算符了,下面来实现一下:
再次编译运行:
实际上用一个更加直观的例子更能看明白:
结果:
这是隐式转换,其实也可以使用显示转换:
直接来代码,这里为了方便直接将类写在main函数文件中,举一个数据库相关的例子:
#include <iostream> using namespace std; class DB { public: DB() { cout<<"DB ..."<<endl; } ~DB() { cout<<"~DB ..."<<endl; } void open() {//数据库打开 cout<<"~open ..."<<endl; } void close() {//数据库关闭 cout<<"~close ..."<<endl; } void query() {//数据库查询 cout<<"~query ..."<<endl; } }; int main(void) { DB* db = new DB(); db->open(); db->query(); db->close(); delete db; return 0; }
编译运行:
但是说到数据库的关闭,可能在实际的程序中并没有这么简单,因为不知道它何时释放,我们想当数据库对象销毁的时候自动关闭,那首先能想到的办法是:
#include <iostream> using namespace std; class DB { public: DB() { cout<<"DB ..."<<endl; open(); } ~DB() { cout<<"~DB ..."<<endl; close(); } void open() {//数据库打开 cout<<"~open ..."<<endl; } void close() {//数据库关闭 cout<<"~close ..."<<endl; } void query() {//数据库查询 cout<<"~query ..."<<endl; } }; int main(void) { DB* db = new DB(); //db->open(); db->query(); //db->close(); delete db; return 0; }
其运行结果也是一样的,但是这种方案也不是很可取,一是构造里面的数据库打开比较耗时,在构造函数中不宜做太多事,另外在实际中也能难找到合适的delete销毁对象的地方,也就不能调用close()方法了,我们希望在对象的生命周期结束的时候能够被自动释放,可以采取另外一种方案,具体如下:
#include <iostream> using namespace std; class DBHelper {//将原来的类改名了 public: DBHelper() { cout<<"DB ..."<<endl; } ~DBHelper() { cout<<"~DB ..."<<endl; } void open() {//数据库打开 cout<<"~open ..."<<endl; } void close() {//数据库关闭 cout<<"~close ..."<<endl; } void query() {//数据库查询 cout<<"~query ..."<<endl; } }; class DB {//新声明一个类 public: DB() { db_ = new DBHelper(); } ~DB() { delete db_; } DBHelper* operator->() {//重写指针运算符 return db_; } private: DBHelper* db_; }; int main(void) { DB db; db->open();//为啥对象可以直接用指针调用,也就是由于DB重写了指针运算符了 db->query(); db->close(); return 0; }
编译运行:
这样写有什么好处呢?可以很灵活的释放动态对象,利用的是确定性析构的办法,DB对象在生命周期开始时一定会调用构造函数,而当生命周期结束之后一定会调用析构函数,从而释放了所包装的DBHelper对象,使得我们对内存的控制更加方便,实际上这也是智能指针实现的一个技巧,就相当于DB是一个智能指针【smart pointer:关于智能指针之后会仔细学习,巧妙的使用智能指能能够避免内存泄漏】,它包装了DBHelper
先回顾一下new的三种用法:new operator、operator new、placement new
下面来用代码说明以上三种new的用法:
#include <iostream> using namespace std; class Test { public: Test(int n): n_(n) { cout<<"Test(int n): n_(n)"<<endl; } Test(const Test& other) { cout<<"Test(const Test& other)"<<endl; } ~Test() { cout<<"~Test()"<<endl; } private: int n_; }; int main(void) { Test* p1 = new Test(10);//它是new operator=operator new + 构造函数的调用 return 0; }
实际上可以debug一下既可看出是否是如上面所说的那样:
按F11跟踪进去:
跳出该方法,继续按F11跟踪,会发现调用了构造函数了:
通过这个过程可以清楚的了解到new operator操作的内部执行过程。
所以对于"new operator、operator new"的这两种new的用法已经清楚了,那接下来说明一下它的最后一种用法:“placement new”:
编译运行:
从结果打印来看是一样的,为了进一步说明,可以debug一下:
其中operator new是可以被重载的,但是new operator是不能的,下面来看下相关的语法:
- void* operator new(size_t size)
- void operator delete(void* p)
- void operator delete(void* p, size_t size)
- void* operator new(size_t size, const char* file, long line)
- void operator delete(void* p, const char* file, long line)
- void* operator new[](size_t size)
- void operator delete[](void* p)
- void operator delete[](void* p, size_t size)
一旦new运算符被重载了,对应的delete运算符也随之要被重载,是相匹配的,下面来实现下:
#include <iostream> using namespace std; class Test { public: Test(int n): n_(n) { cout<<"Test(int n): n_(n)"<<endl; } Test(const Test& other) { cout<<"Test(const Test& other)"<<endl; } ~Test() { cout<<"~Test()"<<endl; } void* operator new(size_t size) {//重载operator new cout<<"void* operator new(size_t size)"<<endl; void* p = malloc(size); return p; } void operator delete(void* p) { cout<<"void operator delete(void* p)"<<endl; free(p); } int n_; }; int main(void) { Test* p1 = new Test(10);//它是new operator=operator new + 构造函数的调用 delete p1; char chunk[10]; Test* p2 = new(chunk) Test(200);//placement new:是基于已有的地址来创建对象的,不会分配内存 cout<<p2->n_<<endl; //Test* p3 = (Test*)chunk; Test* p3 = reinterpret_cast<Test*>(chunk); cout<<p3->n_<<endl; return 0; }
编译运行:
这是为啥呢?因为下面有个placement new还没有重载,所以为了说明问题先将下面的语句注释掉,之后再来让其编译通过:
再次编译运行:
另外operator delete有两种方式,如下:
那能否共存呢?
编译运行:
可以看出可以共存,默认是调用第一个参数的operator delete,那如果将一个参数的注释掉,程序还会编译通过么?
看结果:
也就是说这两个operator delete都是与operator new相匹配的。
另外operator new分为局部重载和全局重载,像刚才的是属于局部重载,只针对Test类有关,而如果像下面这种new operator呢?
它会不会调用Test类中的operator new运算符呢?答案当然不会,因为这是属于全局的,我们也可以重载全局的operator new,如下:
#include <iostream> using namespace std; class Test { public: Test(int n): n_(n) { cout<<"Test(int n): n_(n)"<<endl; } Test(const Test& other) { cout<<"Test(const Test& other)"<<endl; } ~Test() { cout<<"~Test()"<<endl; } void* operator new(size_t size) {//重载operator new cout<<"void* operator new(size_t size)"<<endl; void* p = malloc(size); return p; } /*void operator delete(void* p) { cout<<"void operator delete(void* p)"<<endl; free(p); }*/ void operator delete(void* p, size_t size) { cout<<"void operator delete(void* p, size_t size)"<<endl; free(p); } int n_; }; void* operator new(size_t size) { cout<<"global void* operator new(size_t size)"<<endl; void* p = malloc(size); return p; } void operator delete(void* p) { cout<<"global void operator delete(void* p)"<<endl; free(p); } int main(void) { Test* p1 = new Test(10);//它是new operator=operator new + 构造函数的调用 delete p1; char* str = new char; delete str; char chunk[10]; /*Test* p2 = new(chunk) Test(200);//placement new:是基于已有的地址来创建对象的,不会分配内存 cout<<p2->n_<<endl; //Test* p3 = (Test*)chunk; Test* p3 = reinterpret_cast<Test*>(chunk); cout<<p3->n_<<endl;*/ return 0; }
编译运行:
那如果是new一个数组呢?
可以重载带数组的函数,如下:
#include <iostream> using namespace std; class Test { public: Test(int n): n_(n) { cout<<"Test(int n): n_(n)"<<endl; } Test(const Test& other) { cout<<"Test(const Test& other)"<<endl; } ~Test() { cout<<"~Test()"<<endl; } void* operator new(size_t size) {//重载operator new cout<<"void* operator new(size_t size)"<<endl; void* p = malloc(size); return p; } /*void operator delete(void* p) { cout<<"void operator delete(void* p)"<<endl; free(p); }*/ void operator delete(void* p, size_t size) { cout<<"void operator delete(void* p, size_t size)"<<endl; free(p); } int n_; }; void* operator new(size_t size) { cout<<"global void* operator new(size_t size)"<<endl; void* p = malloc(size); return p; } void operator delete(void* p) { cout<<"global void operator delete(void* p)"<<endl; free(p); } void* operator new[](size_t size) { cout<<"global void* operator new[](size_t size)"<<endl; void* p = malloc(size); return p; } void operator delete[](void* p) { cout<<"global void operator delete[](void* p)"<<endl; free(p); } int main(void) { Test* p1 = new Test(10);//它是new operator=operator new + 构造函数的调用 delete p1; char* str = new char[100]; delete[] str; char chunk[10]; /*Test* p2 = new(chunk) Test(200);//placement new:是基于已有的地址来创建对象的,不会分配内存 cout<<p2->n_<<endl; //Test* p3 = (Test*)chunk; Test* p3 = reinterpret_cast<Test*>(chunk); cout<<p3->n_<<endl;*/ return 0; }
再次编译运行:
可见这次就调用了我们重载的全局数组的operator new函数了。
下面还有这种重载形式没有使用:
那它的作用是什么呢?下面来使用一下:
#include <iostream> using namespace std; class Test { public: Test(int n): n_(n) { cout<<"Test(int n): n_(n)"<<endl; } Test(const Test& other) { cout<<"Test(const Test& other)"<<endl; } ~Test() { cout<<"~Test()"<<endl; } void* operator new(size_t size) {//重载operator new cout<<"void* operator new(size_t size)"<<endl; void* p = malloc(size); return p; } /*void operator delete(void* p) { cout<<"void operator delete(void* p)"<<endl; free(p); }*/ void operator delete(void* p, size_t size) { cout<<"void operator delete(void* p, size_t size)"<<endl; free(p); } void* operator new(size_t size, const char* file, long line) { cout<<file<<":"<<line<<endl; void* p = malloc(size); return p; } void operator delete(void* p, const char* file, long line) { cout<<file<<":"<<line<<endl; free(p); } int n_; }; void* operator new(size_t size) { cout<<"global void* operator new(size_t size)"<<endl; void* p = malloc(size); return p; } void operator delete(void* p) { cout<<"global void operator delete(void* p)"<<endl; free(p); } void* operator new[](size_t size) { cout<<"global void* operator new[](size_t size)"<<endl; void* p = malloc(size); return p; } void operator delete[](void* p) { cout<<"global void operator delete[](void* p)"<<endl; free(p); } int main(void) { Test* p1 = new Test(10);//它是new operator=operator new + 构造函数的调用 delete p1; char* str = new char[100]; delete[] str; char chunk[10]; /*Test* p2 = new(chunk) Test(200);//placement new:是基于已有的地址来创建对象的,不会分配内存 cout<<p2->n_<<endl; //Test* p3 = (Test*)chunk; Test* p3 = reinterpret_cast<Test*>(chunk); cout<<p3->n_<<endl;*/ return 0; }
另外解决一下placement new还没重载造成下面的测试代码编译不过的问题:
#include <iostream> using namespace std; class Test { public: Test(int n): n_(n) { cout<<"Test(int n): n_(n)"<<endl; } Test(const Test& other) { cout<<"Test(const Test& other)"<<endl; } ~Test() { cout<<"~Test()"<<endl; } void* operator new(size_t size) {//重载operator new cout<<"void* operator new(size_t size)"<<endl; void* p = malloc(size); return p; } /*void operator delete(void* p) { cout<<"void operator delete(void* p)"<<endl; free(p); }*/ void operator delete(void* p, size_t size) { cout<<"void operator delete(void* p, size_t size)"<<endl; free(p); } void* operator new(size_t size, const char* file, long line) { cout<<file<<":"<<line<<endl; void* p = malloc(size); return p; } void operator delete(void* p, const char* file, long line) { cout<<file<<":"<<line<<endl; free(p); } void* operator new(size_t size, void* p) { return p; } int n_; }; void* operator new(size_t size) { cout<<"global void* operator new(size_t size)"<<endl; void* p = malloc(size); return p; } void operator delete(void* p) { cout<<"global void operator delete(void* p)"<<endl; free(p); } void* operator new[](size_t size) { cout<<"global void* operator new[](size_t size)"<<endl; void* p = malloc(size); return p; } void operator delete[](void* p) { cout<<"global void operator delete[](void* p)"<<endl; free(p); } int main(void) { Test* p1 = new Test(10);//它是new operator=operator new + 构造函数的调用 delete p1; char* str = new char[100]; delete[] str; char chunk[10]; Test* p2 = new(chunk) Test(200);//placement new:是基于已有的地址来创建对象的,不会分配内存 cout<<p2->n_<<endl; //Test* p3 = (Test*)chunk; Test* p3 = reinterpret_cast<Test*>(chunk); cout<<p3->n_<<endl; return 0; }
编译:
new和delete操作是需要匹配的,所以再重载一下delete方法:
#include <iostream> using namespace std; class Test { public: Test(int n): n_(n) { cout<<"Test(int n): n_(n)"<<endl; } Test(const Test& other) { cout<<"Test(const Test& other)"<<endl; } ~Test() { cout<<"~Test()"<<endl; } void* operator new(size_t size) {//重载operator new cout<<"void* operator new(size_t size)"<<endl; void* p = malloc(size); return p; } /*void operator delete(void* p) { cout<<"void operator delete(void* p)"<<endl; free(p); }*/ void operator delete(void* p, size_t size) { cout<<"void operator delete(void* p, size_t size)"<<endl; free(p); } void* operator new(size_t size, const char* file, long line) { cout<<file<<":"<<line<<endl; void* p = malloc(size); return p; } void operator delete(void* p, const char* file, long line) { cout<<file<<":"<<line<<endl; free(p); } void* operator new(size_t size, void* p) { return p; } void operator delete(void*, void* p) {//关于它的函数原形可能通过debug内部跟踪得到 } int n_; }; void* operator new(size_t size) { cout<<"global void* operator new(size_t size)"<<endl; void* p = malloc(size); return p; } void operator delete(void* p) { cout<<"global void operator delete(void* p)"<<endl; free(p); } void* operator new[](size_t size) { cout<<"global void* operator new[](size_t size)"<<endl; void* p = malloc(size); return p; } void operator delete[](void* p) { cout<<"global void operator delete[](void* p)"<<endl; free(p); } int main(void) { Test* p1 = new Test(10);//它是new operator=operator new + 构造函数的调用 delete p1; char* str = new char[100]; delete[] str; char chunk[10]; Test* p2 = new(chunk) Test(200);//placement new:是基于已有的地址来创建对象的,不会分配内存 cout<<p2->n_<<endl; //Test* p3 = (Test*)chunk; Test* p3 = reinterpret_cast<Test*>(chunk); cout<<p3->n_<<endl; return 0; }
再次编译运行:
修改代码如下:
再次编译运行,看这回构造与虚构是否匹配了:
好了,解决了placement new编译通过的问题之后,则下面来使用一下带多个参数的operator new的重载方法,如下:
下面来看下结果:
照理应该是调用与之匹配的它:
具体原因这里也不太清楚,反正如果不重写这个delete会给出警告,这个待之后再做出考证。对于上面这种写法其实可以定义成宏,如下: