运算符重载(三)_#include


  • 必须是成员函数,不能是友元函数。
  • 没有参数(操作数是什么?)。
  • 不能指定返回类型(其实已经指定了)。
  • 函数原型:operator 类型名();

下面用代码来说明,先新建工程,将上次实现的Integer类添加至项目中来:

运算符重载(三)_ios_02

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;
}


上面的输出的写法及输出结果显而易见,就不多说了,接下来实现加法运算的函数:

运算符重载(三)_ios_03

很遗憾,这样的写法目前还不支持:

运算符重载(三)_#include_04

这时需要重写类型转换运算符了,下面来实现一下:

运算符重载(三)_#include_05

运算符重载(三)_#include_06

再次编译运行:

运算符重载(三)_#include_07

实际上用一个更加直观的例子更能看明白:

运算符重载(三)_#include_08

结果:

运算符重载(三)_构造函数_09

这是隐式转换,其实也可以使用显示转换:

运算符重载(三)_#include_10

运算符重载(三)_ios_11

运算符重载(三)_构造函数_12

 直接来代码,这里为了方便直接将类写在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;
}


编译运行:

运算符重载(三)_ios_13

但是说到数据库的关闭,可能在实际的程序中并没有这么简单,因为不知道它何时释放,我们想当数据库对象销毁的时候自动关闭,那首先能想到的办法是:


#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;
}


编译运行:

运算符重载(三)_ios_14

这样写有什么好处呢?可以很灵活的释放动态对象,利用的是确定性析构的办法,DB对象在生命周期开始时一定会调用构造函数,而当生命周期结束之后一定会调用析构函数,从而释放了所包装的DBHelper对象,使得我们对内存的控制更加方便,实际上这也是智能指针实现的一个技巧,就相当于DB是一个智能指针【smart pointer:关于智能指针之后会仔细学习,巧妙的使用智能指能能够避免内存泄漏】,它包装了DBHelper

运算符重载(三)_ios_15

先回顾一下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一下既可看出是否是如上面所说的那样:

运算符重载(三)_ios_16

按F11跟踪进去:

运算符重载(三)_#include_17

跳出该方法,继续按F11跟踪,会发现调用了构造函数了:

运算符重载(三)_构造函数_18

运算符重载(三)_构造函数_19

通过这个过程可以清楚的了解到new operator操作的内部执行过程。

所以对于"new operator、operator new"的这两种new的用法已经清楚了,那接下来说明一下它的最后一种用法:“placement new”:

运算符重载(三)_构造函数_20

编译运行:

运算符重载(三)_#include_21

从结果打印来看是一样的,为了进一步说明,可以debug一下:

运算符重载(三)_ios_22

其中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;
}


编译运行:

运算符重载(三)_ios_23

这是为啥呢?因为下面有个placement new还没有重载,所以为了说明问题先将下面的语句注释掉,之后再来让其编译通过:

运算符重载(三)_ios_24

再次编译运行:

运算符重载(三)_构造函数_25

另外operator delete有两种方式,如下:

运算符重载(三)_构造函数_26

那能否共存呢?

运算符重载(三)_ios_27

编译运行:

运算符重载(三)_#include_28

可以看出可以共存,默认是调用第一个参数的operator delete,那如果将一个参数的注释掉,程序还会编译通过么?

运算符重载(三)_构造函数_29

看结果:

运算符重载(三)_构造函数_30

也就是说这两个operator delete都是与operator new相匹配的。

另外operator new分为局部重载和全局重载,像刚才的是属于局部重载,只针对Test类有关,而如果像下面这种new operator呢?

运算符重载(三)_构造函数_31

它会不会调用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;
}


编译运行:

运算符重载(三)_ios_32

那如果是new一个数组呢?

运算符重载(三)_#include_33

运算符重载(三)_构造函数_34

可以重载带数组的函数,如下:

运算符重载(三)_ios_35


#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;
}


再次编译运行:

运算符重载(三)_#include_36

可见这次就调用了我们重载的全局数组的operator new函数了。

下面还有这种重载形式没有使用:

运算符重载(三)_#include_37

那它的作用是什么呢?下面来使用一下:


#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;
}


编译:

运算符重载(三)_#include_38

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;
}


再次编译运行:

运算符重载(三)_构造函数_39

运算符重载(三)_构造函数_40

运算符重载(三)_构造函数_41

修改代码如下:

运算符重载(三)_#include_42

再次编译运行,看这回构造与虚构是否匹配了:

运算符重载(三)_构造函数_43

好了,解决了placement new编译通过的问题之后,则下面来使用一下带多个参数的operator new的重载方法,如下:

运算符重载(三)_#include_44

下面来看下结果:

运算符重载(三)_ios_45

照理应该是调用与之匹配的它:

运算符重载(三)_ios_46

具体原因这里也不太清楚,反正如果不重写这个delete会给出警告,这个待之后再做出考证。对于上面这种写法其实可以定义成宏,如下:

运算符重载(三)_构造函数_47