3运算符重载
运算符重载
运算符重载的好处:使运算符的表现和编译器内置类型一样
复数类的实现:
class CComplex
{
public:
CComplex(int r=0,int m=0)
:mimage_(m),mreal_(r)
{
cout<<"CComplex(int r=0,int m=0)"<<endl;
}
CComplex(const CComplex&another)
{
cout<<"CComplex(const CComplex&another)"<<endl;
this->mimage_ = another.mimage_;
this->mreal_ = another.mreal_;
}
CComplex& operator=(const CComplex& another)
{
cout<<"CComplex& operator=(const CComplex&another)"<<endl;
this->mimage_ = another.mimage_;
this->mreal_ = another.mreal_;
return *this;
}
CComplex operator+(const CComplex&another)
{
CComplex ret;
ret.mimage_ = this->mimage_ + another.mimage_;
ret.mreal_ = this->mreal_ + another.mreal_;
cout << "CComplex operator+(const CComplex&another)" << endl;
return ret;
}
CComplex operator++(int) //后置++
{
return CComplex(this->mreal_+1, this->mimage_+1);
}
CComplex& operator++() //前置++
{
this->mreal_++;
return *this;
}
void show()
{
cout<<"real:"<<mreal_<<endl<<"image:"<<mimage_<<endl;
}
friend CComplex operator+(const CComplex&lhs ,const CComplex& rhs);
friend ostream& operator<<(ostream& out,const CComplex& comp);
friend istream& operator>>(istream& in,CComplex& comp);
private:
int mreal_;
int mimage_;
};
CComplex operator+(const CComplex&lhs ,const CComplex& rhs) //全局 operator+
{
cout<<"CComplex operator+(const CComplex&lhs , CComplex& rhs)"<<endl;
return CComplex(lhs.mreal_+ rhs.mreal_,lhs.mimage_+rhs.mimage_);
}
ostream& operator<<(ostream& out,const CComplex& comp)
{
out<<"real:"<<comp.mreal_<<"image"<<comp.mimage_;
return out;
}
istream& operator>>(istream& in,CComplex& comp)
{
in >> comp.mreal_ >> comp.mimage_;
return in;
}
int main()
{
CComplex com2(1,2);
CComplex com4 = com2 + 30;
/*可以运行不报错的原因
* com2 operator+(30) --> com2 operator(int)
* 可是重载 + 的类型为 CComplex
* 编译器就要寻找 int 转const CCmplex临时对象
* 看构造函数 CComplex(int r=0,int m=0)
* 存在 int 转 const CComplex
* 所以可以成功编译
* */
com4.show();
}
模拟实现string类的代码
#include <iostream>
#include "typeinfo"
#include "string.h"
using namespace std;
class String
{
public:
String(const char* str= nullptr)
{
cout<<" String(const char* str= nullptr)"<<this<<endl;
if(str == nullptr)
pstr_ = new char('\0');
else
{
pstr_ = new char(strlen(str) + 1);
for(int i=0;i< strlen(str);i++)
{
pstr_[i] = str[i];
}
}
}
~String()
{
cout<<" ~String()"<<this<<endl;
delete[] pstr_;
pstr_ = nullptr;
}
String(const String&another)
{
cout<<" String(const String&another) this"<<this<<"another "<<&another<<endl;
this->pstr_ = new char[strlen(another.pstr_) +1];
strcpy(this->pstr_,another.pstr_);
}
int length() const
{
return strlen(this->pstr_);
}
const char* c_str() const
{
return pstr_;
}
String& operator=(const String& another)
{
cout<<" String& operator=(const String& another) this"<<this <<"another "<<&another<<endl;
if(this == &another)
return *this;
delete[] pstr_;
this->pstr_ = new char[strlen(another.pstr_) + 1];
strcpy(this->pstr_,another.pstr_);
return *this;
}
bool operator>(const String&another) const //这个方法只涉及读操作没有写操作,写为const方法
{
return strcmp(this->pstr_,another.pstr_) > 0;
}
bool operator<(const String&another) const //这个方法只涉及读操作没有写操作,写为const方法
{
return strcmp(this->pstr_,another.pstr_) < 0;
}
bool operator==(const String&another) const //这个方法只涉及读操作没有写操作,写为const方法
{
return strcmp(this->pstr_,another.pstr_) == 0;
}
char operator[](int index)
{
return (this->pstr_)[index];
}
const char& operator[](int index) const
{
return pstr_[index];
}
friend String operator+(const String& lhs,const String& rhs);
friend ostream& operator<<(ostream&out,String&another);
private:
char* pstr_;
};
ostream& operator<<(ostream&out,String&another)
{
cout<<"ostream& operator<<(ostream&out,String&another)"<<endl;
out<<another.pstr_;
return out;
}
String operator+(const String& lhs,const String& rhs)
{
/*
这种方法是低效的
char * tem =new char[strlen(lhs.pstr_) + strlen(rhs.pstr_) + 1];
strcpy(tem,lhs.pstr_);
strcat(tem,rhs.pstr_);
String ret(tem);
delete[] tem;
return ret;*/
cout<<"String operator+(const String& lhs,const String& rhs)"<<endl;
String tem;
delete[] tem.pstr_;
tem.pstr_ = new char[strlen(lhs.pstr_) + strlen(rhs.pstr_) + 1];
strcpy(tem.pstr_,lhs.pstr_);
strcat(tem.pstr_,rhs.pstr_);
return tem;
}
int main() {
String s1 = "abcdefg"; //隐式转换调用构造函数,生成临时对象
String s2 = s1 + "asdjpajsd";
cout << s2;
/*
* g++ -fno-elide-constructors //关闭优化
String(const char* str= nullptr)0x7ffff49f1490 字符串隐式转化
String(const String&another) this0x7ffff49f1478another 0x7ffff49f1490 拷贝复制给s1
~String()0x7ffff49f1490 析构临时对象
String(const char* str= nullptr)0x7ffff49f1488 隐式转化生成临时对象
String operator+(const String& lhs,const String& rhs) s1和临时对象相加调用operator+函数
String(const char* str= nullptr)0x7ffff49f1440 operator+函数内构造对象tem
String(const String&another) this0x7ffff49f1490another 0x7ffff49f1440 拷贝赋值给另一个临时对象,位于operator+栈外
~String()0x7ffff49f1440 operator+函数站内对象tem消失
String(const String&another) this0x7ffff49f1480another 0x7ffff49f1490 拷贝赋值给s2
~String()0x7ffff49f1490 临时对象消失
~String()0x7ffff49f1488 将字符串转换的临时对象析构
ostream& operator<<(ostream&out,String&another)
abcdefgasdjpajsd ~String()0x7ffff49f1480 析构s2
~String()0x7ffff49f1478 析构s1
* */
return 0;
}
string容器的迭代器:
先看看string本身的iterator
int main() {
string s1 = "abcdefg";
string::iterator it;
for(it=s1.begin();it!=s1.end();it++)
{
cout<<*it;
}
}
自实现:
#include <iostream>
#include "string"
#include "typeinfo"
#include "string.h"
using namespace std;
class String
{
public:
String(const char* str= nullptr)
{
//cout<<" String(const char* str= nullptr)"<<this<<endl;
if(str == nullptr)
pstr_ = new char('\0');
else
{
pstr_ = new char(strlen(str) + 1);
for(int i=0;i< strlen(str);i++)
{
pstr_[i] = str[i];
}
}
}
~String()
{
//cout<<" ~String()"<<this<<endl;
delete[] pstr_;
pstr_ = nullptr;
}
String(const String&another)
{
//cout<<" String(const String&another) this"<<this<<"another "<<&another<<endl;
this->pstr_ = new char[strlen(another.pstr_) +1];
strcpy(this->pstr_,another.pstr_);
}
int length() const
{
return strlen(this->pstr_);
}
const char* c_str() const
{
return pstr_;
}
String& operator=(const String& another)
{
//cout<<" String& operator=(const String& another) this"<<this <<"another "<<&another<<endl;
if(this == &another)
return *this;
delete[] pstr_;
this->pstr_ = new char[strlen(another.pstr_) + 1];
strcpy(this->pstr_,another.pstr_);
return *this;
}
bool operator>(const String&another) const //这个方法只涉及读操作没有写操作,写为const方法
{
return strcmp(this->pstr_,another.pstr_) > 0;
}
bool operator<(const String&another) const //这个方法只涉及读操作没有写操作,写为const方法
{
return strcmp(this->pstr_,another.pstr_) < 0;
}
bool operator==(const String&another) const //这个方法只涉及读操作没有写操作,写为const方法
{
return strcmp(this->pstr_,another.pstr_) == 0;
}
char operator[](int index)
{
return (this->pstr_)[index];
}
const char& operator[](int index) const
{
return pstr_[index];
}
friend String operator+(const String& lhs,const String& rhs);
friend ostream& operator<<(ostream&out,String&another);
class iterator
{
public:
iterator(char *p = nullptr)
{
p_ = p;
}
iterator(const iterator& another)
{
p_ = another.p_;
}
void operator++()
{
p_+=1;
}
void operator++(int)
{
this->p_ += 1;
}
char operator*()
{
return *p_;
}
bool operator!=(const iterator&another)
{
return this->p_ != another.p_;
}
private:
char* p_;
};
iterator begin()
{
return iterator(pstr_);
}
iterator end()
{
return iterator(pstr_+ strlen(pstr_));
}
private:
char* pstr_;
};
ostream& operator<<(ostream&out,String&another)
{
//cout<<"ostream& operator<<(ostream&out,String&another)"<<endl;
out<<another.pstr_;
return out;
}
String operator+(const String& lhs,const String& rhs)
{
/*
这种方法是低效的
char * tem =new char[strlen(lhs.pstr_) + strlen(rhs.pstr_) + 1];
strcpy(tem,lhs.pstr_);
strcat(tem,rhs.pstr_);
String ret(tem);
delete[] tem;
return ret;*/
//cout<<"String operator+(const String& lhs,const String& rhs)"<<endl;
String tem;
delete[] tem.pstr_;
tem.pstr_ = new char[strlen(lhs.pstr_) + strlen(rhs.pstr_) + 1];
strcpy(tem.pstr_,lhs.pstr_);
strcat(tem.pstr_,rhs.pstr_);
return tem;
}
int main() {
//迭代器的功能是通过统一的方式来透明的遍历容器
String s1 = "abcdefg";
String::iterator it;
/*for(it=s1.begin();it!=s1.end();it++)
{
cout<<*it;
}*/
//所谓的for-each,遍历容器其底层就是通过迭代器工作的
for(char c:s1){cout<<c<<" ";}
#if 0
String s1 = "abcdefg"; //隐式转换调用构造函数,生成临时对象
String s2 = s1 + "asdjpajsd";
cout << s2;
/*
*
String(const char* str= nullptr)0x7ffff49f1490 字符串隐式转化
String(const String&another) this0x7ffff49f1478another 0x7ffff49f1490 拷贝复制给s1
~String()0x7ffff49f1490 析构临时对象
String(const char* str= nullptr)0x7ffff49f1488 隐式转化生成临时对象
String operator+(const String& lhs,const String& rhs) s1和临时对象相加调用operator+函数
String(const char* str= nullptr)0x7ffff49f1440 operator+函数内构造对象tem
String(const String&another) this0x7ffff49f1490another 0x7ffff49f1440 拷贝赋值给另一个临时对象,位于operator+栈外
~String()0x7ffff49f1440 operator+函数站内对象tem消失
String(const String&another) this0x7ffff49f1480another 0x7ffff49f1490 拷贝赋值给s2
~String()0x7ffff49f1490 临时对象消失
~String()0x7ffff49f1488 将字符串转换的临时对象析构
ostream& operator<<(ostream&out,String&another)
abcdefgasdjpajsd ~String()0x7ffff49f1480 析构s2
~String()0x7ffff49f1478 析构s1
* */
return 0;
#endif
}
迭代器的功能是通过统一的方式来透明的遍历容器
auto it = container.begin(); //统一
for(;it!=containter.end();++it){cout<<*it<<endl;}
迭代器让作为使用者不必关心容器底层的数据结构
容器迭代器失效问题
先来看看STL中迭代器失效的情况:
int main()
{
std::vector<int> vi = {1,2,3,4,5,6,7,8};
for(auto i = vi.begin();i!=vi.end();++i)
{
if(*i % 2 == 0)
vi.erase(i);
}
return 0;
}
终端;
进程已结束,退出代码-1073740940 (0xC0000374) //程序执行出现错误,问题是迭代器失效了
迭代器为什么会失效?
- 当容器调用erase犯法后,从当前位置到容器的末尾元素的所有迭代器失效
- 当容器调用insert方法后,从当前位置到容器的末尾元素所有的迭代器全部失效,对于insert来收,如果引起了容器的扩容操作,就会使全部的迭代器失效
迭代器失效后问题要如何解决?
-
对插入删除点的迭代器进行更新操作
int main() //earse { vector<int> vi = {1,2,3,4,5,6,7,8}; auto it = vi.begin(); while (it!=vi.end()) { if(*it % 2 == 0) it = vi.erase(it); //earse()返回删除元素的当前位置 else { it++; } } return 0; } int main() { vector<int> vi = {1,2,3,4,5,6,7,8}; auto it = vi.begin(); for(;it!=vi.end();++it) { if(*it % 2 ==0 ) { it = vi.insert(it,*it -1); } } return 0; }
深入理解new和delete
先看一下new和delete的汇编实现
int main() { int *p = new int; delete p; return 0; } 反汇编: 0x00007ff6515c16e0 <+0>: push %rbp 0x00007ff6515c16e1 <+1>: mov %rsp,%rbp 0x00007ff6515c16e4 <+4>: sub $0x30,%rsp 0x00007ff6515c16e8 <+8>: call 0x7ff6515c1897 <__main> 0x00007ff6515c16ed <+13>: mov $0x4,%ecx 0x00007ff6515c16f2 <+18>: call 0x7ff6515c17c0 <operator new(unsigned long long)> 0x00007ff6515c16f7 <+23>: mov %rax,-0x8(%rbp) 0x00007ff6515c16fb <+27>: mov -0x8(%rbp),%rax 0x00007ff6515c16ff <+31>: test %rax,%rax 0x00007ff6515c1702 <+34>: je 0x7ff6515c1711 <main()+49> 0x00007ff6515c1704 <+36>: mov $0x4,%edx 0x00007ff6515c1709 <+41>: mov %rax,%rcx 0x00007ff6515c170c <+44>: call 0x7ff6515c17c8 <operator delete(void*, unsigned long long)> 0x00007ff6515c1711 <+49>: mov $0x0,%eax 0x00007ff6515c1716 <+54>: add $0x30,%rsp 0x00007ff6515c171a <+58>: pop %rbp 0x00007ff6515c171b <+59>: ret
可以发现new和delete的本质是一个 重载函数 operator new(unsigned long long)
#include <iostream>
using std::cout;
using std::endl;
void operator delete(void *ptr) {
cout << "void operator delete(void *ptr)" << endl;
free(ptr);
}
void* operator new(size_t size)
//typedef unsigned __int64 size_t #define __int64 long long
// size_t 等价与unsinged long long
{
cout<<"void* operator new(size_t size)"<<endl;
void* p = malloc(size);
if(p == nullptr)
throw std::bad_alloc(); //bad_alloc是一个构造函数,即将一个临时对象抛出
return p;
}
void* operator new[](size_t size)
{
void *p = malloc(size);
if(p == nullptr)
throw std::bad_alloc();
return p;
cout<<"void operator new[](size_t size)"<<endl;
}
void operator delete[](void *ptr)
{
cout<<"void operator delete[](void *ptr)"<<endl;
free(ptr);
}
class test
{
public:
test(){cout<<"test()"<<endl;}
~test(){cout<<"~test()"<<endl;}
private:
int a;
int b;
int c;
};
int main()
{
test* p = new test;
cout<<sizeof(*p)<<endl;
delete p;
return 0;
}
输出:
void* operator new(size_t size)
test()
12
~test()
void operator delete(void *ptr)
通过调试发现:
new和delete本质的操作,就是 new = operator new(malloc) + 构造器 delete = 析构 + operator delete(free) 。
在调用自实现的new操作时会先调用自实现new块中的代码(必须要有malloc(size),这里的size会更具传入的类型自行取sizeof),之后在调用构造函数。
在调用自实现的delete操作时会先调用析构函数,在来调用自实现的delete块中的代码(其中必要要free操作)
new和delete能混用吗?c++为什么区分单个元素和数组的内存内存空间分配和释放呢?
当开辟数组空间的时候为了让编译器知道开辟了多少个对象,在内存中会首先开辟4字节的空间用于存放对象的个数,delete[] 表示重复析构与 free ,次数是由第一个对象的前4个字节的数值决定
第一个:delete p;
第二个:delete (p+sizeof(test));
第三个:delete(p+2*sizeof(test));
分析如下代码找出错误原因:
#include <iostream>
using std::cout;
using std::endl;
void operator delete(void *ptr) {
cout << "void operator delete(void *ptr)" << ptr<<endl;
free(ptr);
}
void* operator new(size_t size)
//typedef unsigned __int64 size_t #define __int64 long long
// size_t 等价与unsinged long long
{
void* p = malloc(size);
cout<<"void* operator new(size_t size)"<<p<<endl;
if(p == nullptr)
throw std::bad_alloc(); //bad_alloc是一个构造函数,即将一个临时对象抛出
return p;
}
void* operator new[](size_t size)
//编译器显示这个是重载函数,只能是和c++自带的new[]重载
// 但是为什么不是重定义了? 我觉得这是重定义
{
void *p = malloc(size);
if(p == nullptr)
throw std::bad_alloc();
cout<<"void operator new[](size_t size)"<<p<<endl;
return p;
}
void operator delete[](void *ptr)
{
cout<<"void operator delete[](void *ptr)"<<ptr<<endl;
free(ptr);
}
class test
{
public:
test(){cout<<"test():"<< this<<endl;}
~test(){cout<<"~test():"<<this<<endl;}
private:
int a;
};
int main()
{
test* p = new test[3];
cout<<"p:"<<p<<endl;
delete p;
}
输出:
输出:
void operator new[](size_t size)0x7fffde11aeb0
test():0x7fffde11aeb8
test():0x7fffde11aebc
test():0x7fffde11aec0
p:0x7fffde11aeb8
~test():0x7fffde11aeb8
void operator delete(void *ptr)0x7fffde11aeb8
munmap_chunk(): invalid pointer
Aborted (core dumped)
吐核说明执行有问题。其实就是刚才讲到的内存开辟问题,void operator new[](size_t size) 0x7fffde11aeb0
这是开辟new开辟空间的真实地址 ,但是析构时传入的参数p是p:0x298916818b8
与真实地址相差 8字节 ,从这里delete就导致 8字节 空间泄露,所以报错。正常是要先delete掉用于计数的8字节的内存,在循环析构掉多个对象
若改用delete[]
void operator new[](size_t size)0x7fffd9455eb0
test():0x7fffd9455eb8
test():0x7fffd9455ebc
test():0x7fffd9455ec0
p:0x7fffd9455eb8
~test():0x7fffd9455ec0
~test():0x7fffd9455ebc
~test():0x7fffd9455eb8
void operator delete[](void *ptr)0x7fffd9455eb0
若改为:
int main()
{
test* p = new test;
cout<<"p:"<<p<<endl;
delete[] p;
}
也会导致吐核,因为delete[] 会首先free,p指针地址的之前4字节或8字节的位置,在讲这段内存中的值作为析构与free的次数进行循环删除对象和内存。这肯定是错误的
简单内存池的实现
#include <iostream>
using namespace std;
template<typename T>
class Queue
{
public:
Queue()
{
front_ = rear_ = new QueueItem;
}
~Queue()
{
QueueItem* it = front_;
while(it != nullptr)
{
QueueItem* tem = it->next_;
delete it;
it = tem;
}
}
void add(const T& val) //入队
{
QueueItem item = new QueueItem(val);
rear_->next_ = item;
rear_ = item;
}
void pop()
{
if(empty()) return;
QueueItem* first = front_->next_;
front_->next_ = first->next_;
if(front_->next_ == nullptr)
rear_ = front_;
delete first;
}
void push(const T& val)
{
auto* tem = new QueueItem(val);
rear_->next_ = tem;
rear_ = tem;
}
bool empty()
{
return (front_->next_ == nullptr);
}
private:
struct QueueItem{
explicit QueueItem(T data=T()) :data_(data),next_(nullptr){}
static void* operator new(size_t size)
{
if(itemPool == nullptr)
{
itemPool = (QueueItem *) malloc(sizeof(QueueItem) * POOL_Item_Size);
//_itemPool = (QueueItem*)new char[POOL_ITEM_SIZE*sizeof(QueueItem)];
auto tem = itemPool;
for(;tem < itemPool + POOL_Item_Size - 1;++tem)
{
tem->next_ = tem + 1;
}
tem->next_ = nullptr;
}
QueueItem *p = itemPool;
itemPool = itemPool->next_; //
return p;
}
static void operator delete(void* ptr)
{
auto* p = (QueueItem*)ptr;
p->next_ = itemPool;
itemPool = p;
}
static const int POOL_Item_Size = 3; //static修饰常量可以直接在类内赋值
static QueueItem* itemPool;
T data_;
QueueItem *next_;
};
QueueItem* front_; //队头
QueueItem* rear_; //队尾
static Queue* ite;
};
template<typename T>
typename Queue<T>::QueueItem* Queue<T>::QueueItem::itemPool = nullptr;
//这里typename的作用是告诉编译器Queue<T>::QueueItem*是一个类型,因为模板类还没有实例化
//因此编译器不知道QueueItem是什么类型。这里就要加上typename让编译器放心这是一个类型
int main() {
Queue<int> q;
for(int i=0;i<100000;i++)
{
q.push(5);
q.pop();
}
return 0;
}