普通类型实现:
class CComplex
{
public:
CComplex(int mr=0,int mi=0):mreal(mr),mimage(mi)
{
}
~CComplex()
{
}
private:
int mreal;//实部
int mimage;//虚部
};
int main()
{
CComplex comp1(10, 10);
CComplex comp2(20, 20);
//编译器会去找 comp1.operator+(comp2) 加法运算符的重载函数
CComplex comp3 = comp1 + comp2;
return 0;
}
首先编译器会去找 comp1.operator+(comp2) 加法运算符的重载函数,没有找到 报错。
加法运算符重载函数如下:
// 指导编译器怎么做CComplex类对象的加法操作
//加法运算符重载函数
CComplex operator+(const CComplex& src)
{
CComplex newCom;
newCom.mreal = this->mreal + src.mreal;
newCom.mimage = this->mimage + src.mimage;
return newCom;
}
正如在main函数里面那样:
//编译器会去找 comp1.operator+(comp2) 加法运算符的重载函数
CComplex comp3 = comp1 + comp2;
此时是对象comp1调用这个函数,this-> 便是指向的这个对象。comp1 comp2的值都没被改变,而是我生成了一个新的对象。(但是一般情况下,返回一个对象的代价太大,所以都是返回对象的引用:即指针)。但是就上面的函数而言:是绝对不能返回一个局部对象的指针或者引用的。
对象的优化 如下:
加法运算符重载函数的意义:对象做运算, 左边的对象即comp1,comp1.operator+(comp2) 加法运算符的重载函数(把右边的对象comp2作为实参传进去)。看似在做加法运算,实则是调用了相关的成员方法。
需求1:CComplex comp4 = comp1 + 20;
这个相当于comp1.operator+(20); 把20作为实参传入。但是函数的形参是CComplex& :复数类型的引用。 能不能把这个int转成CComplex类型的?这样的话,编译器会在CComplex类里面找 有没有提供一个带有int参数的构造函数?
即:CComplex(int) 这样就可以 int转成CComplex 。即生成临时对象。因为我们的构造函数提供了默认值(实部 虚部都是0)。生成临时对象此时的实部用传入的20,虚部用默认值0. 相当于可以构造3种不同的复数对象。
运行结果如下:
需求2:CComplex comp5 = 20+comp1 ;
原因分析:此时的20 不能完成一个临时对象的生成的。编译器做对象运算的时候,会调用对象的运算符重载函数(优先调用成员方法);如果没有成员方法,就在全局作用域找合适的运算符重载函数。 此时运算符 + 左边是int类型的 20,因此它无法调用到类的成员方法:加法运算符重载函数。然后去全局作用域找合适的运算符重载函数,没有找到 所以无法编译。
但是我们也可以::operator+(20, comp1)
全局作用域的加法运算符重载函数。全局函数是不需要对象来调的,20, comp1都作为实参传入。
全局函数(类外面的函数):访问对象的私有成员变量 不行
解决办法1:提供 get公有方法。
解决方法2:友元函数。(类外面的函数)访问对象的私有成员变量 行
运行结果如下:
先生成一个临时对象此时的实部用传入的20,虚部用默认值0. 整个调用的是全局的加法运算符的重载函数。
此时的全局的加法运算符的重载函数,非常强大。可以做了成员方法的工作。所以可以把成员方法给注释了。 此时运算符 + ,先去找匹配调用到类的成员方法:加法运算符重载函数。没有则然后去全局作用域找合适的运算符重载函数
需求3:
// ++ --单目运算符 operator++() 前置++ 和 operator++(int) 后置++
comp5 = comp1++;
comp1.show();
comp5.show();
// CComplex operator++()
comp5 = ++comp1;
comp1.show();
comp5.show();
++ --单目运算符
operator++() 前置++
operator++(int) 后置++
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
源代码如下:
CComplex& operator++()//前置++
{
mreal += 1;
mimage += 1;
return *this;
//*this 不是局部的,出了作用域还在
//可以返回 &(不用产生临时对象)
}
CComplex operator++(int)//后置++
{
//CComplex oldcomp = *this;//保存旧的
//mreal += 1;
//mimage += 1;
//return oldcomp;
//对象的优化如下:少了临时对象的 构 析
return CComplex(mreal++, mimage++);
}
运行截图如下:
需求4:comp1 += comp2;//复合运算符重载
void operator+=(const CComplex& src)//复合运算符重载
{
mreal += src.mreal;
mimage += src.mimage;
}
注:
运算符运算,底层都是相应的成员方法或者全局的(运算符重载函数)的调用。
需求5:输出运算符 << 重载
//comp1.show(); // 对象信息的输出
//cout ::operator<<(cout, comp1) void << endl;
//ostream& operator<<(ostream &out, const CComplex &src)
输出运算符 << 重载:对象也是可以用标准的输出流 来输出到屏幕(控制台应用程序)上的。但是输出运算符 << 重载函数的 对象不在左边,所以无法提供成为 成员函数(对象需要在左边,才可以调用 运算符重载函数的,把右边的函数作为实参传入)所以只能把 输出运算符重载函数通过成为 全局方法(不需要对象来调用)。如下所示: out就是cout的别名。 运行截图如下; 在C语言中,字符串常常是由字符数组来存储字符串的。char arr[]=“abcdefg”; 在C++里面专门有string类型 负责操作字符串。 运行截图如下: 此运行截图以及 main函数 和上面的一模一样: 对比两者: 提供运算符重载函数 使得操作字符串和操作其他类型(编译器的其他内置类型)一样的简单。需要掌握 运算符重载和String类型。 上节课的遗留问题: 怎么修改呢?版本三 这次 比上一个版本 少了一次new 一次delete 所以说 重点来了:一定得需要给string类型提供 右值引用参数的拷贝构造和赋值重载函数。(在对象的优化时候 咱们再见) String类的全部源代码如下: 运行截图: 现在string对象 str1(底层是用什么数据结构的方式存放字符不重要)里面有一组 字符串(对外不可见)。对于字符串底层的成员变量而言,是私有的。可是 我们现在想去 迭代这些字符啊 迭代器是怎么做到的呢? str1.begin();是容器的方法:返回的是底层首元素的 迭代器表示。那也即:it现在指向容器底层首元素。 迭代器指向的是一个位置,是对 char *_p;的封装。既然要指向一个位置,所以也需要 参数接受一个指针。(在iterator的构造函数里面,用用户传进去的指针去初始化这个迭代器成员变量 :这个指针char *_p;) 前置++的重载,返回的是一个引用。是对象本身,不产生临时量的。(效率高) iterator:针对所有容器,统一的(透明的)遍历所有容器的方式。不用去管容器的底层数据结构是什么样子, iterator配合着容器的begin end方法以及迭代器相应的运算符重载方法(比如 ++ != )等可以用 iterator把容器的元素全部的遍历一遍。不同容器(数据结构也不同),在数据结构上遍历元素的方式也不同。 泛型算法:一组用模板写的且与具体类型无关的函数。 迭代器有什么成员变量取决于容器底层的数据结构是什么,容器需要什么样的变量来遍历,那么iterator就采用什么样子的 变量作为其成员变量。 本节源代码如下:这里涉及了(vector、allocator、iterator)很重要 本节知识点 迭代器失效 运行结果: 或者看下面的代码: 分析:如果是奇数5,则在其前面添加一个比他小1 的偶数4,(在底层容器上,新的iterator已经指向了4)。如果我不接着进行一个it++,则在下一次 又遇到5了,重新操作。(陷入无尽的循环)。最后无论是奇数还是偶数,都要再进行it++,以跳过这个数 向下进行。(在使用iterator,连续insert操作 之后 要进行it++两次)。 运行截图: 因为容器vector底层是一个数组,所以用 _p 指针去遍历这个数组。而新增加的成员变量: _ptrVec 让迭代器需要知道,当前它在迭代到底是哪一个容器对象。 给迭代器的一些运算符重载函数 加上检查迭代器的有效性。 例如:++运算符重载。若迭代器底层的指针:指向容器是空的。表示迭代器失效了。 这他么一看就是个给容器提供的链表 嘛 (FUCK FUCK 心情不畅 难受 不想写了 不想写了 ) 唉 算了 学习!!! 这里用一个链表 把用户从容器里面获取的元素的iterator 给串起来(放在_head为头结点的链表里面)。把所有的迭代器都记录下来 有了这个记录以后,此时再通过哪个迭代器去 insert或者erase 的时候,就要把相应的某些iterator给 失效掉,让迭代器重新更新。 从迭代器的生成(或者 其构造函数)来看: 底层维护这么一个链表:把所有 用户从容器里面获取的元素的iterator 给串起来(放在_head为头结点的链表里面)。把所有的迭代器都记录下来。 每个iterator 对象都包含了两个东西: 遍历这个链表,发现某个记录迭代器迭代元素的地址,是在这个失效区间里面 则把这个迭代器失效一下。所谓的失效 就是把iterator的成员变量_ptrVec (指向容器的指针)为空。 意思就是:迭代器指向容器为空 标志这个iterator为失效的。 本节:insert erase的实现。 本节的代码极为重要。主要涉及到:allocator 类模板 运算符重载 嵌套类iterator 以及迭代器失效的处理和底层失效实现等 综合实现的vector。极为重要 本节源代码如下: new delete两个运算符的调用,实质上也是两个运算符重载函数的调用。 new和malloc的区别: free与delete的区别: new 就是operator new()的调用。 如何设计实现检测C++程序内存泄漏的程序?(也即new完了,没有delete) 分析上面的错误: 因此 下面这张也是错的: 第一版:Queue 给类提供new和delete运算符重载
把cout 和 comp对象作为实参传进去。而且其返回值不可以为void 否则的话,就会出现 void<ostream& operator<<(ostream &out, const CComplex &src)
重载函数代码如下:需要提供成友元函数ostream& operator<<(ostream& out, const CComplex& src)
{
out << "mreal: " << src.mreal << "mimage: " << src.mimage << endl;
return out;
}
istream& operator>>(istream& in, CComplex& src)
{
in >> src.mreal;
in >> src.mimage;
return in;
}
对象也可以由cout进行输出,在C++中 任何对象的运算都可以通过 合适的运算符重载函数就行。
本节源代码如下:/*
C++的运算符重载:使对象的运算表现得和编译器内置类型一样
template
第二节:String类的实现
不太方便的地方有:
1 定义数组 大小总是固定的。
2 数组在进行大小比较的时候;字符串连接的时候都需要调用相应的字符串操作函数。
3 内存是否够用的问题。
这个string类 可以实现下面的一些:
1 支持默认构造
2 说明string类 里面有string(const char参数)的构造函数
3 里面有 加法运算符的重载函数
4 里面有类内实现的:支持对象+ const char的
5 还要全局实现的:const char*+对象
此外还可以支持一下操作:
(为什么string 可以做到这些 是因为给string提供了 相应的 运算符重载函数)#include
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++自己实现String类(字符串对象) 支持(运算符重载函数)
这些运算符重载函数 都是正确的class String
{
public:
String(const char* str = nullptr)
{
if (str != nullptr)
{
_pstr = new char[strlen(str) + 1];
strcpy(_pstr, str);
}
else
{
_pstr = new char[1];
*_pstr = '\0';
}
}
~String()
{
delete[]_pstr;
_pstr = nullptr;
}
String(const String& src)
{
_pstr = new char[strlen(src._pstr) + 1];
strcpy(_pstr, src._pstr);
}
String& operator=(const String& src)
{
if (this == &src)
{
return *this;
}
delete[]_pstr;
_pstr = new char[strlen(src._pstr) + 1];
strcpy(_pstr, src._pstr);
return *this;
}
bool operator>(const String& src)const
{
return strcmp(_pstr, src._pstr) > 0;
}
bool operator<(const String& src)const
{
return strcmp(_pstr, src._pstr) < 0;
}
bool operator==(const String& src)const
{
return strcmp(_pstr, src._pstr) == 0;
}
int length()const//有效的长度 没有\0
{
return strlen(_pstr);
}
//支持读写 char ch=str1[1]; 和 str1='T';
char& operator[](int index)
{
return _pstr[index];
}
//支持读 char ch=str1[1]; 不能写
const char& operator[](int index)const
{
return _pstr[index];
}
const char* c_str()const
{
return _pstr;
}
private:
char* _pstr;
friend ostream& operator<<(ostream& out, const String& src);
friend istream& operator>>(istream& in, String& src);
friend String operator+(const String& des,const String& src);
};
String operator+(const String& des,const String& src)
{
char* newstr = new char[strlen(des._pstr) + strlen(src._pstr) + 1];
strcpy(newstr, des._pstr);
newstr = strcat(newstr, src._pstr);
return String(newstr);
}
ostream& operator<<(ostream& out, const String& src)
{
out << src._pstr;
return out;
}
istream& operator>>(istream& in,String &src)
{
in >> src._pstr;
return in;
}
#include
现在还欠缺一个问题:在加法运算符重载函数那里,那个new的 空间好像没有delete。String operator+(const String& des, const String& src)
{
char* newstr = new char[strlen(des._pstr) + strlen(src._pstr) + 1];
strcpy(newstr, des._pstr);
newstr = strcat(newstr, src._pstr);
String strtemp(newstr);
delete[]newstr;
return strtemp;
}
虽然 下面的那个解决了关于内存泄漏的事情,但是其效率实在是太低了。第三节:String字符串对象的迭代器 iterator实现
功能实现但是效率太低:原因在于 (在进入加法运算符重载函数的时候)用newstr开辟了一大块内存,然后进行字符串拷贝和字符串连接。紧接着作为实参 进行局部对象strtemp的构造。(去strtemp对象的构造函数的时候)然而在这个strtemp对象的构造函数里面又开辟了一块同样大小的内存进行 数据的拷贝。(第一次delete newstr指针指向的内存)(然后可以把这个刚开辟的newstr指向的堆内存进行释放了)。完毕之后,在(return strtemp;的地方)的时候,去(str4对象的拷贝构造函数)里面又开辟了一块同样大小的内存进行 数据的拷贝。完了之后,开始析构这个局部对象strtemp(在其析构函数里面 第二次进行delete)。然后 到此刻为止,
String str4 = str2 + str3;
这句话终于执行完了 (快把劳资累死了 fuck fuck fuck)
两次 new 两次 delete 太特么麻烦了!!!!!!!!!!!!!!!!!!!!!!!String operator+(const String& des, const String& src) //版本三
{
String strtemp;//直接定义一个局部对象
//直接给其底层指针 开辟空间
strtemp._pstr = new char[strlen(des._pstr) + strlen(src._pstr) + 1];
//直接整到 局部对象里面
strcpy(strtemp._pstr, des._pstr);
strcat(strtemp._pstr, src._pstr);
return strtemp;
//出了作用域 strtemp 就自动析构了
}
过程:(在进入加法运算符重载函数的时候)直接进行局部对象strtemp的构造(因为用的是默认的指针nullptr,所以少了一次new)。然后直接给这个局部对象底层的指针开辟空间(但是我感觉这里少了一个空间的释放,造成内存泄漏),然后 数据的拷贝 连接。完毕之后,在(return strtemp;的地方)的时候,去(str4对象的拷贝构造函数)里面又开辟了一块同样大小的内存进行 数据的拷贝。完了之后,开始析构这个局部对象strtemp(在其析构函数里面 第一次进行delete)。然后 到此刻为止
String str4 = str2 + str3;
这句话终于执行完了 (快把劳资累死了 真香 真香 真香。特码的腰疼 坐了一整天了)
但是这样还有些问题:为了带出这个局部对象strtemp,是一定要产生临时对象叫(temp吧)的。这个临时对象temp的拷贝构造 也会产生大量的内存的开辟 和 数据的拷贝。临时对象的析构也涉及到 内存的释放。class String
{
public:
String(const char* str = nullptr)
{
cout << "构造函数 " << endl;
if (str != nullptr)
{
_pstr = new char[strlen(str) + 1];
strcpy(_pstr, str);
}
else
{
_pstr = new char[1];
*_pstr = '\0';
}
}
~String()
{
cout << "析构函数" << endl;
delete[]_pstr;
_pstr = nullptr;
}
String(const String& src)
{
cout << "拷贝构造函数" << endl;
_pstr = new char[strlen(src._pstr) + 1];
strcpy(_pstr, src._pstr);
}
String& operator=(const String& src)
{
cout << "赋值运算符重载" << endl;
if (this == &src)
{
return *this;
}
delete[]_pstr;
_pstr = new char[strlen(src._pstr) + 1];
strcpy(_pstr, src._pstr);
return *this;
}
bool operator>(const String& src)const
{
return strcmp(_pstr, src._pstr) > 0;
}
bool operator<(const String& src)const
{
return strcmp(_pstr, src._pstr) < 0;
}
bool operator==(const String& src)const
{
return strcmp(_pstr, src._pstr) == 0;
}
int length()const//有效的长度 没有\0
{
return strlen(_pstr);
}
//支持读写 char ch=str1[1]; 和 str1='T';
char& operator[](int index)
{
return _pstr[index];
}
//支持读 char ch=str1[1]; 不能写
const char& operator[](int index)const
{
return _pstr[index];
}
const char* c_str()const
{
return _pstr;
}
private:
char* _pstr;
friend ostream& operator<<(ostream& out, const String& src);
friend istream& operator>>(istream& in, String& src);
friend String operator+(const String& des,const String& src);
};
//String operator+(const String& des,const String& src) //版本一
//{
//char* newstr = new char[strlen(des._pstr) + strlen(src._pstr) + 1];
// strcpy(newstr, des._pstr);
// newstr = strcat(newstr, src._pstr);
// return String(newstr);
//}
//String operator+(const String& des, const String& src) //版本二
//{
//char* newstr = new char[strlen(des._pstr) + strlen(src._pstr) + 1];
// strcpy(newstr, des._pstr);
// newstr = strcat(newstr, src._pstr);
// String strtemp(newstr);
// delete[]newstr;
// return strtemp;
//}
String operator+(const String& des, const String& src) //版本三
{
String strtemp;//直接定义一个局部对象
//直接给其底层指针 开辟空间
strtemp._pstr = new char[strlen(des._pstr) + strlen(src._pstr) + 1];
//直接整到 局部对象里面
strcpy(strtemp._pstr, des._pstr);
strcat(strtemp._pstr, src._pstr);
return strtemp;
//出了作用域 strtemp 就自动析构了
//所以不能把一个局部对象引用给返回
}
ostream& operator<<(ostream& out, const String& src)
{
out << src._pstr;
return out;
}
istream& operator>>(istream& in,String &src)
{
in >> src._pstr;
return in;
}
#include
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++本节的 String字符串对象的迭代器 iterator实现
从iterator的使用方式上来看,它是容器类的一个 嵌套类。#include
str1.end(); 表示:容器最后一个有效元素的后继位置迭代器。
最后一个元素也可以遍历到。it++ 向后走。(底层的数据结构是什么 劳资不管!!!! 你只要到下一个元素就行 。怎么过去的 是封装到++ 运算符重载里面了) it 相当于一个OO的指针一样,它指向容器底层的数据,解引用就可以访问到这个数据。
根本不需要你容器底层的数据结构是什么,只需要定义一个这个容器类型特定的 迭代器。这个迭代器当然不可能设置成统一的。不同容器的底层数据结构是不一样的,每一种容器都有自己的迭代器。所以这也是为什么 迭代器设置为容器的嵌套类型。那么容器的 begin end 方法,分别返回的是底层首元素的 迭代器表示、容器最后一个有效元素的后继位置迭代器。for循环 当前迭代器不等于(容器最后一个有效元素的**后继位置迭代器)作为条件。 怎么过去的 是封装到迭代器++ 运算符重载里面了。对于迭代器还需要 通过* 运算符重载函数来访问迭代器 迭代元素的值。)
在C++ STL的泛型算法里面:既然是给所有容器用的,容器底层的数据结构不一样。不知道将来会让这个泛型算法去处理什么类型的容器的元素。既然要处理容器的元素,所有需要容器的元素遍历一遍。
后置++的重载,返回的是一个对象。是要产生临时量的,先返回旧对象的值,再给当前对象++。
本节源代码:class String
{
public:
String(const char* str = nullptr)
{
cout << "构造函数 " << endl;
if (str != nullptr)
{
_pstr = new char[strlen(str) + 1];
strcpy(_pstr, str);
}
else
{
_pstr = new char[1];
*_pstr = '\0';
}
}
~String()
{
cout << "析构函数" << endl;
delete[]_pstr;
_pstr = nullptr;
}
String(const String& src)
{
cout << "拷贝构造函数" << endl;
_pstr = new char[strlen(src._pstr) + 1];
strcpy(_pstr, src._pstr);
}
String& operator=(const String& src)
{
cout << "赋值运算符重载" << endl;
if (this == &src)
{
return *this;
}
delete[]_pstr;
_pstr = new char[strlen(src._pstr) + 1];
strcpy(_pstr, src._pstr);
return *this;
}
bool operator>(const String& src)const
{
return strcmp(_pstr, src._pstr) > 0;
}
bool operator<(const String& src)const
{
return strcmp(_pstr, src._pstr) < 0;
}
bool operator==(const String& src)const
{
return strcmp(_pstr, src._pstr) == 0;
}
int length()const//有效的长度 没有\0
{
return strlen(_pstr);
}
//支持读写 char ch=str1[1]; 和 str1='T';
char& operator[](int index)
{
return _pstr[index];
}
//支持读 char ch=str1[1]; 不能写
const char& operator[](int index)const
{
return _pstr[index];
}
const char* c_str()const
{
return _pstr;
}
// 给String字符串类型提供迭代器的实现
//作为String的嵌套类,为了迭代字符串的底层字符串数组
class iterator
{
public:
iterator(char* p = nullptr) :_p(p) {}
//两个对于字符串的迭代器的不等于
//迭代器的不等于就是底层指针的不等于
//迭代器的指向 就是 字符串数组底层指针的指向
bool operator!=(const iterator& it)
{
return _p != it._p;
}
void operator++()
{
++_p;//给迭代器 成员变量++
}
char& operator*() { return *_p; }
private:
char* _p;
};
// begin返回的是容器底层 首元素 的迭代器的表示
iterator begin()
{
//用_pstr这个指向数组首元素地址的 指针
//构造一个iterator
return iterator(_pstr);
}
// end返回的是容器末尾元素后继位置(\0)的迭代器的表示
iterator end()
{
return iterator(_pstr + length());
}
private:
char* _pstr;
friend ostream& operator<<(ostream& out, const String& src);
friend istream& operator>>(istream& in, String& src);
friend String operator+(const String& des,const String& src);
};
//String operator+(const String& des,const String& src) //版本一
//{
//char* newstr = new char[strlen(des._pstr) + strlen(src._pstr) + 1];
// strcpy(newstr, des._pstr);
// newstr = strcat(newstr, src._pstr);
// return String(newstr);
//}
//String operator+(const String& des, const String& src) //版本二
//{
//char* newstr = new char[strlen(des._pstr) + strlen(src._pstr) + 1];
// strcpy(newstr, des._pstr);
// newstr = strcat(newstr, src._pstr);
// String strtemp(newstr);
// delete[]newstr;
// return strtemp;
//}
String operator+(const String& des, const String& src) //版本三
{
String strtemp;//直接定义一个局部对象
//直接给其底层指针 开辟空间
strtemp._pstr = new char[strlen(des._pstr) + strlen(src._pstr) + 1];
//直接整到 局部对象里面
strcpy(strtemp._pstr, des._pstr);
strcat(strtemp._pstr, src._pstr);
return strtemp;
//出了作用域 strtemp 就自动析构了
//所以不能把一个局部对象引用给返回
}
ostream& operator<<(ostream& out, const String& src)
{
out << src._pstr;
return out;
}
istream& operator>>(istream& in,String &src)
{
in >> src._pstr;
return in;
}
#include
第四节:vector容器的迭代器 iterator实现
方式都是一样的?
原因:容器提供begin 方法和 end方法 分别返回的是 底层首元素的迭代器表示、容器最后一个有效元素的后继位置迭代器。 for循环 当前迭代器不等于(容器最后一个有效元素的 后继位置迭代器)作为条件。(这样就可以实现全部元素的遍历 ) 怎么过去的 是封装到迭代器++ 运算符重载里面了。对于迭代器还需要 通过 运算符重载函数来访问迭代器 迭代元素的值。)
方式都是一样的,其原因正是在于:当我们遍历完当前元素的时候,从当前元素到下一个元素(底层数据结构的遍历方式都封装到迭代器的++运算符重载里面)。我们也会只需要知道给iterator进行++操作就行了。就可以获取到下一个元素。至于底层是什么数据结构(数组 链表 栈 队列 等)不重要。
在C++ STL的泛型算法(全局的函数)里面:既然是给所有容器都可以使用的,容器底层的数据结构不一样。不知道将来会让这个泛型算法去处理什么类型的容器的元素。既然要处理容器的元素,所以需要把容器的元素(以一种统一的方式)遍历一遍。
本节实现 vector容器的迭代器( iterator非常重要)。template<typename T>
struct MyAllocator //自定义的空间配置器,负责内存管理
{
T* allocate(size_t size)//负责开辟内存
{
return (T*)malloc(sizeof(T) * size);
}
void deallocate(void* p)//负责回收内存
{
free(p);
}
void construct(T* p, const T& val)//负责 对象构造
{
new(p)T(val);
}
void destroy(T* p)//负责对象析构
{
p->~T();
}
};
template<typename T=int,typename Allo= MyAllocator<T>>
class MyVector
{
public:
MyVector(int size = 2)
{
_first = _allocator.allocate(size);
_last = _first;
_end = _first + size;
}
~MyVector()
{
for (T* p = _first; p != _last; ++p)
{
_allocator.destroy(p);
}
_allocator.deallocate(_first);
_first = _last = _end = nullptr;
}
MyVector(const MyVector<T>& src)
{
int size = src._last - src._first;
int len = src._end - src._first;
_first = _allocator.allocate(len);
_last = _first + size;
for (int i=0;i<size;++i)
{
_allocator.construct(_first + i, src._first[i]);
}
_end = _first + len;
}
MyVector<T>operator=(const MyVector<T>& src)
{
if (this == &src)
{
return *this;
}
for (T* p = _first; p != _last; ++p)
{
_allocator.destroy(p);
}
_allocator.deallocate(_first);
int size = src._last - src._first;
_first = _allocator.allocate(size);
_last = _first + size;
for (int i = 0; i < size; ++i)
{
_allocator.construct(_first + i, src._first[i]);
}
int len = src._end - src._first;
_end = _first + len;
return *this;
}
void push_back(const T& val)
{
if (full())
{
resize();
}
_allocator.construct(_last, val);
_last++;
}
T getBack()const
{
return *(_last - 1);
}
void pop_back()
{
if (empty())
{
return;
}
_allocator.destroy(--_last);
}
bool full()const
{
return _last == _end;
}
bool empty()const
{
return _last == _first;
}
int getSize()const
{
return _last - _first;
}
T& operator[](int index)//myv[4]
{
if (index < 0 || index >= getSize())
throw"index 不合法";
return _first[index];
}
//迭代器一般实现成容器类的嵌套类
// 给vector类型提供迭代器的实现
//作为vector的嵌套类,为了迭代向量的底层数组
class iterator
{
public:
iterator(T* p = nullptr) :_p(p) {}
//两个对于字符串的迭代器的不等于
//迭代器的不等于就是底层指针的不等于
//迭代器的指向 就是 vector数组底层指针的指向
bool operator!=(const iterator& it)const
{
return _p != it._p;
}
//前置++ 不会产生临时量,就是修改当前对象的值 再返回
//后置++ 需要把原对象的值返回 再修改当前对象的值
void operator++()
{
++_p;//给迭代器 成员变量++
}
//常方法:普通对象 常对象都可以调用
const T& operator*()const { return *_p; }//只读 常方法
T& operator*() { return *_p; }//可读可写
private:
T* _p;
};
//需要给容器提供begin end方法
// begin返回的是容器底层 首元素 的迭代器的表示
iterator begin()
{
//用_first这个指向数组首元素地址的 指针
//构造一个iterator
return iterator(_first);
}
// end返回的是容器末尾(有效的)元素后继位置的迭代器的表示
iterator end()
{
return iterator(_last);
}
private:
T* _first;
T* _last;
T* _end;
Allo _allocator;
void resize()
{
cout << "resize()" << endl;
int size = _last - _first;
int newsize = size * 2;
T* newfirst = _allocator.allocate(newsize);
for (int i = 0; i < size; ++i)
{
_allocator.construct(newfirst + i, _first[i]);
}
for (T* p = _first; p != _last; ++p)
{
_allocator.destroy(p);
}
_allocator.deallocate(_first);
_first = newfirst;
_last = _first + size;
_end = _first + newsize;
}
};
int main()
{
MyVector<> myv;
for (int i = 0; i < 9; ++i)
{
myv.push_back(rand() % 15);
}
//要使用iterator
MyVector<>::iterator it = myv.begin();
for (; it != myv.end(); ++it)
{
cout << *it << " ";
}
cout << endl;
//使用iterator 是通用做法,提供[]只适合内存连续 有意义的
//使用[]运算符重载
for (int i = 0; i < myv.getSize(); ++i)
{
cout << myv[i] << " ";
}
cout << endl;
//使用C++11 的foeach
//其底层原理就是通过iterator来实现容器遍历的
for (int val : myv)
{
cout << val << " ";
}
cout << endl;
while (!myv.empty())//进行打印 OK的
{
cout << myv.getBack() << " ";
myv.pop_back();
}
cout << endl;
return 0;
}
第五节:容器的迭代器 iterator失效问题:
1 迭代器为什么会失效?
2 怎么处理失效问题?#include
出了什么问题了?迭代器失效了
在第一次调用myv.erase(it);之后,我们的it 就已经失效了。然后又++it。对一个失效的迭代器进行++运算符重载的函数调用操作,当然是运行出错。
只进行一次:程序正常
进行多次,程序崩溃(同样是iterator在第一次 insert之后,就已经失效了)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
迭代器为什么失效?
怎么解决这个失效问题呢?
对插入点 和 删除点的iterator做更新操作
原因分析:insert 和 erase完之后都会 重新返还一个iterator(这是指向当前 这个点 的新的iterator)。所以我们需要重新的 把我们的for循环变量it 更新一下(接收那个新的iterator 这个是一个有效的iterator 所以我们必须进行更新)。
因为erase之后,数组的元素要进行移动。此刻便不可以再进行++了,而是接着判断此时的it (现在正在指向的)指向的元素是否为奇数 。 //在容器里面,在容器的奇数前面 都添加上一个小于1的偶数
vector
迭代器失效以及解决的源代码如下:#include
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
现在给iterator 这个内部嵌套类,又增加了一个成员变量。
private:
T* _p;
MyVector
迭代器失效的另外一种方式:不同容器的迭代器是不可以进行比较运算的。 来判断两个迭代器是不是指向同一个容器的同一个位置。两个容器在内存上的位置肯定是不一样的,比较不同容器的迭代器是没有意义的。
所以在这里,给迭代器iterator添加的新的成员变量: _ptrVec 让迭代器需要知道,当前它在迭代到底是哪一个容器对象。(一个指向当前对象的指针 )
给其构造函数添加了一个新参数:MyVector //两个对于字符串的迭代器的不等于
//迭代器的不等于就是底层指针的不等于
//迭代器的指向 就是 vector数组底层指针的指向
bool operator!=(const iterator& it)const
{
// 检查迭代器的有效性
if (_ptrVec == nullptr || _ptrVec != it._ptrVec)
{
throw "iterator 不匹配!";
}
return _p != it._p;
}
//前置++ 不会产生临时量,就是修改当前对象的值 再返回
//后置++ 需要把原对象的值返回 再修改当前对象的值
void operator++()
{
// 检查迭代器的有效性
if (_ptrVec == nullptr )
{
throw "iterator 无效的!";
}
++_p;//给迭代器 成员变量++
}
//常方法:普通对象 常对象都可以调用
const T& operator*()const//只读 常方法
{
// 检查迭代器的有效性
if (_ptrVec == nullptr)
{
throw "iterator 无效的!";
}
return *_p;
}
T& operator*() //可读可写
{
// 检查迭代器的有效性
if (_ptrVec == nullptr)
{
throw "iterator 无效的!";
}
return *_p;
}
例如: !=运算符在对两个迭代器进行比较,
如果迭代器底层的指针(1 指向容器是空的)(2 _ptrVec不空,但是当前迭代器迭代的容器 和 传进来的迭代器it迭代的容器不一样)都表示迭代器失效了。
在begin end方法处:传入的 this指针(这两个方法是 容器 的方法,this指向当前容器对象。)把this 传给iterator的构造方法中。
其构造函数如下:
所以_ptrVec(通过容器创建迭代器) 肯定是不为空的。之所以_ptrVec在后面为空,是因为迭代器失效了。
那么现在是怎么实现的 把删除点和增加点 给失效掉呢?这个结构体是在 MyVector类里面添加的。
里面维护了一个:指向某个迭代器的指针( _curiterator;)。和指向下一个Iterator_Base节点的next地址。还有最后的那个_head 指针。
通过构造函数:this是当前新生成的 迭代器对象的地址。第二个参数 是next指针。每个节点的数据域里面放的是 iterator* _curiterator;
从容器内部获得的某一个位置元素的迭代器。(相当于 头插法)T* _p;//指向了容器 底层数组的某个元素
MyVector<T, Allo>* _ptrVec;//当前迭代器需要知道 它在迭代哪种容器对象
// 指向容器的指针
void checkIterator(T* first, T* last)// 检查迭代器失效
{
Iterator_Base* preIter = &this->_head;//指向头结点
Iterator_Base* curIter = this->_head._next;//指向第一个节点
while (curIter != nullptr)
{//迭代的地址 在这个失效区间里面 则把这个迭代器失效一下
if (curIter->_curiterator->_p > first &&
curIter->_curiterator->_p <= last)
{
// 迭代器失效,把iterator持有的容器指针置nullptr
curIter->_curiterator->_ptrVec = nullptr;
// 删除当前迭代器节点,继续判断后面的迭代器节点是否失效
preIter->_next = curIter->_next;
delete curIter;
curIter = preIter->_next;
}
else
{
preIter = curIter;
curIter = curIter->_next;
}
}
}
// 迭代器失效,把iterator持有的容器指针置nullptr
curIter->_curiterator->_ptrVec = nullptr;
容器底层迭代器的实现原理以及迭代器失效在底层是怎么设计实现的。
所以这种情况下:在增加和删除,之后把这个区间的iterator全部失效掉。(具体就是在 存储了所有的迭代器的链表上进行遍历。遍历这个链表,发现某个记录迭代器迭代元素的地址,是在这个失效区间里面 则把这个迭代器失效一下。所谓的失效 就是把iterator的成员变量_ptrVec (指向容器的指针)为空。)第六节:容器的迭代器 iterator失效问题:
template<typename T>
struct MyAllocator //自定义的空间配置器,负责内存管理
{
T* allocate(size_t size)//负责开辟内存
{
return (T*)malloc(sizeof(T) * size);
}
void deallocate(void* p)//负责回收内存
{
free(p);
}
void construct(T* p, const T& val)//负责 对象构造
{
new(p)T(val);
}
void destroy(T* p)//负责对象析构
{
p->~T();//~T()代表了T类型的析构函数
}
};
/*
容器底层内存开辟,内存释放,对象构造和析构
都通过allocator空间配置器来实现
*/
template<typename T=int,typename Allo= MyAllocator<T>>
class MyVector
{
public:
MyVector(int size = 2)
{
cout << "构造函数" << endl;
//需要把内存开辟 和 对象构造分开,不能用new
_first = _allocator.allocate(size);
_last = _first;
_end = _first + size;
}
~MyVector()
{
cout << "析构函数" << endl;
//先析构容器里面的有效的元素
for (T* p = _first; p != _last; ++p)
{
// 把_first指针指向的数组的有效元素进行析构操作
_allocator.destroy(p);
}
//然后释放_first 指向的堆内存
_allocator.deallocate(_first);
_first = _last = _end = nullptr;
}
MyVector(const MyVector<T>& src)
{
cout << "拷贝构造" << endl;
int size = src._last - src._first;//有效元素的个数
int len = src._end - src._first;//空间总长度
_first = _allocator.allocate(len);
_last = _first + size;
for (int i=0;i<size;++i)
{
_allocator.construct(_first + i, src._first[i]);
}
_end = _first + len;
}
MyVector<T>operator=(const MyVector<T>& src)
{
cout << "= 运算符重载" << endl;
if (this == &src)
{
return *this;
}
//先析构容器里面的有效的元素
for (T* p = _first; p != _last; ++p)
{
// 把_first指针指向的数组的有效元素进行析构操作
_allocator.destroy(p);
}
//然后释放_first 指向的堆内存
_allocator.deallocate(_first);
int size = src._last - src._first;//有效元素的个数
_first = _allocator.allocate(size);
_last = _first + size;
for (int i = 0; i < size; ++i)
{
_allocator.construct(_first + i, src._first[i]);
}
int len = src._end - src._first;//空间总长度
_end = _first + len;
return *this;
}
void push_back(const T& val)// 向容器末尾添加元素
{
if (full())
{
resize();
}
//_last指针指向的内存构造一个值为val的对象
_allocator.construct(_last, val);
_last++;
}
T getBack()const// 返回容器末尾的元素的值
{
return *(_last - 1);
}
void pop_back()// 从容器末尾删除元素
{
if (empty())
{
return;
}
//是从当前末尾元素进行删除的 需要把那个 失效一下
checkIterator(_last - 1, _last);
// 不仅要把_last指针--,还需要析构删除的元素
_allocator.destroy(--_last);
}
bool full()const
{
return _last == _end;
}
bool empty()const
{
return _last == _first;
}
int getSize()const
{
return _last - _first;
}
T& operator[](int index)//myv[4] 下标运算符的重载
{
//cout << "下标运算符的重载" << endl;
if (index < 0 || index >= getSize())
throw"index 不合法";
return _first[index];
}
//迭代器一般实现成容器类的嵌套类
// 给vector类型提供迭代器的实现
//作为vector的嵌套类,为了迭代向量的底层数组
class iterator
{
public:
friend class MyVector<T, Allo>;
iterator(MyVector<T, Allo>* ptrVec=nullptr,T*_p=nullptr)
:_p(_p),_ptrVec(ptrVec)
{
Iterator_Base* newIter =
new Iterator_Base(this, _ptrVec->_head._next);
_ptrVec->_head._next = newIter;
}
//两个对于字符串的迭代器的不等于
//迭代器的不等于就是底层指针的不等于
//迭代器的指向 就是 vector数组底层指针的指向
bool operator!=(const iterator& it)const
{
// 检查迭代器的有效性
if (_ptrVec == nullptr || _ptrVec != it._ptrVec)
{
throw "iterator 不匹配!";
}
return _p != it._p;
}
//前置++ 不会产生临时量,就是修改当前对象的值 再返回
//后置++ 需要把原对象的值返回 再修改当前对象的值
void operator++()
{
// 检查迭代器的有效性
if (_ptrVec == nullptr )
{
throw "iterator 无效的!";
}
++_p;//给迭代器 成员变量++
}
//常方法:普通对象 常对象都可以调用
const T& operator*()const//只读 常方法
{
// 检查迭代器的有效性
if (_ptrVec == nullptr)
{
throw "iterator 无效的!";
}
return *_p;
}
T& operator*() //可读可写
{
// 检查迭代器的有效性
if (_ptrVec == nullptr)
{
throw "iterator 无效的!";
}
return *_p;
}
private:
T* _p;
//当前迭代器需要知道 它在迭代哪种容器对象
MyVector<T, Allo>* _ptrVec;
};
//需要给容器提供begin end方法
// begin返回的是容器底层 首元素 的迭代器的表示
iterator begin()
{
//用_first这个指向数组首元素地址的 指针
//构造一个iterator
return iterator(this,_first);
}
// end返回的是容器末尾(有效的)元素后继位置的迭代器的表示
iterator end()
{
return iterator(this,_last);
}
void checkIterator(T* first, T* last)// 检查迭代器失效
{
Iterator_Base* preIter = &this->_head;//指向头结点
Iterator_Base* curIter = this->_head._next;//指向第一个节点
while (curIter != nullptr)
{//迭代的地址 在这个失效区间里面 则把这个迭代器失效一下
if (curIter->_curiterator->_p > first &&
curIter->_curiterator->_p <= last)
{
// 迭代器失效,把iterator持有的容器指针置nullptr
curIter->_curiterator->_ptrVec = nullptr;
// 删除当前迭代器节点,继续判断后面的迭代器节点是否失效
preIter->_next = curIter->_next;
delete curIter;
curIter = preIter->_next;
}
else
{
preIter = curIter;
curIter = curIter->_next;
}
}
}
//自定义实现vector容器的insert方法
iterator insert(iterator it, const T& val)
{
//1.不考虑扩容 verify(_first - 1, _last);
//2.不考虑it._ptr的指针合法性
checkIterator(it._p - 1, _last);
T* p = _last;
while (p > it._p)//元素依次后移
{
_allocator.construct(p, *(p - 1));
_allocator.destroy(p - 1);
--p;
}
_allocator.construct(p, val);
_last++;
return iterator(this, p);
//最后返回一个当前位置的新的迭代器
//把当前对象 this传进去
}
//自定义实现vector容器的erase方法
iterator erase(iterator it)
{
//1.不考虑扩容 verify(_first - 1, _last);
//2.不考虑it._ptr的指针合法性
checkIterator(it._p - 1, _last);
T* p = it._p;
while ( p<_last-1)//元素依次前移
{
_allocator.destroy(p);
_allocator.construct(p, *(p + 1));
++p;
}
_allocator.destroy(p);
_last--;
return iterator(this, it._p);
//最后返回一个当前位置的新的迭代器
//把当前对象 this传进去
}
private:
T* _first;// 指向数组起始的位置
T* _last;// 指向数组中有效元素的后继位置
T* _end;// 指向数组空间的后继位置
Allo _allocator;// 定义容器的空间配置器对象
void resize()
{
cout << "resize()" << endl;
int size = _last - _first;
int newsize = size * 2;
T* newfirst = _allocator.allocate(newsize);
for (int i = 0; i < size; ++i)
{
_allocator.construct(newfirst + i, _first[i]);
}
for (T* p = _first; p != _last; ++p)
{
_allocator.destroy(p);
}
_allocator.deallocate(_first);
_first = newfirst;
_last = _first + size;
_end = _first + newsize;
}
struct Iterator_Base // 容器迭代器失效增加
{
Iterator_Base(iterator* curiterator=nullptr,
Iterator_Base* next=nullptr)
{
_curiterator = curiterator;
_next = next;
}
iterator* _curiterator;
Iterator_Base* _next;
};
Iterator_Base _head;
};
int main()
{
MyVector<> myv(20);
for (int i = 0; i < 9; ++i)
{
myv.push_back(rand() % 15);
}
#if 0
//要使用iterator
MyVector<>::iterator it = myv.begin();
for (; it != myv.end(); ++it)
{
cout << *it << " ";
}
cout << endl;
//使用iterator 是通用做法,提供[]只适合内存连续 有意义的
//使用[]运算符重载
for (int i = 0; i < myv.getSize(); ++i)
{
cout << myv[i] << " ";
}
cout << endl;
//使用C++11 的foeach
//其底层原理就是通过iterator来实现容器遍历的
for (int val : myv)
{
cout << val << " ";
}
cout << endl;
while (!myv.empty())//进行打印 OK的
{
cout << myv.getBack() << " ";
myv.pop_back();
}
cout << endl;
#endif
for (int val : myv)
{
cout << val << " ";
}
cout << endl;
//在容器里面,在容器的奇数前面 都添加上一个小于1的偶数
MyVector<int>::iterator it1 = myv.begin();
while (it1 != myv.end())
{
if ((*it1) % 2 == 1)
{
it1 = myv.insert(it1, (*it1) - 1);
++it1;
}
++it1;
}
for (int val : myv)
{
cout << val << " ";
}
cout << endl;
//把容器中的奇数都删掉
MyVector<int>::iterator it2 = myv.begin();
while (it2 != myv.end())
{
if ((*it2) % 2 == 1)
it2 = myv.erase(it2);
else ++it2;//没有删除
}
for (int val : myv)
{
cout << val << " ";
}
cout << endl;
return 0;
}
第七节:new和delete的底层原理:
int main()
{
011318D0 push ebp
011318D1 mov ebp,esp
011318D3 sub esp,0E4h
011318D9 push ebx
011318DA push esi
011318DB push edi
011318DC lea edi,[ebp-0E4h]
011318E2 mov ecx,39h
011318E7 mov eax,0CCCCCCCCh
011318EC rep stos dword ptr es:[edi]
011318EE mov ecx,offset _FFC5961A_new_delete@cpp (0113C025h)
011318F3 call @__CheckForDebuggerJustMyCode@4 (01131262h)
int* p = new int;
011318F8 push 4
011318FA call operator new (0113132Fh)
011318FF add esp,4
01131902 mov dword ptr [ebp-0D4h],eax
01131908 mov eax,dword ptr [ebp-0D4h]
0113190E mov dword ptr [p],eax
delete p;
01131911 mov eax,dword ptr [p]
01131914 mov dword ptr [ebp-0E0h],eax
0113191A push 4
0113191C mov ecx,dword ptr [ebp-0E0h]
01131922 push ecx
01131923 call operator delete (01131253h)
01131928 add esp,8
return 0;
0113192B xor eax,eax
}
0113192D pop edi
0113192E pop esi
0113192F pop ebx
01131930 add esp,0E4h
01131936 cmp ebp,esp
01131938 call __RTC_CheckEsp (0113126Ch)
0113193D mov esp,ebp
0113193F pop ebp
01131940 ret
(1)malloc是按字节开辟内存的:malloc(sizeof(int)10);它不管内存上放什么数据的。开辟完内存之后返回的是void 需要人为的类型强转。而new开辟内存时需要指定类型,并直接给出开辟元素的个数。new int[10];实质上调用operator new(),返回的类型就是自动的就是你给定的类型指针。int* 。
(2)malloc只负责开辟内存,而new不仅仅具有malloc的功能,还具有数据的初始化操作。
(3)malloc开辟内存失败 返回nullptr;new的则是抛出bad_alloc类型的异常(不可以进行与nullptr的比较),需要把new开辟内存的代码放在 try catch里面。
(4)malloc开辟单个元素和数组的内存都是一样的(给字节数就行);new的 对于单个字符不需要加上[]的,对于数组需要加[]并指定个数就可以了。
delete 需要先进行调用析构函数,再free内存。但是对于 delete (int*)p,和free(p)是一样的。因为int 类型没有什么析构函数,只剩下内存释放。// delete p; 调用p指向对象的析构函数、再调用operator delete释放内存空间
void* operator new(size_t size)
{
void* p = malloc(size);
if (nullptr == p)
{
throw bad_alloc();
}
cout << "operator new:" << p << endl;
return p;
}
// delete p; 调用p指向对象的析构函数、再调用operator delete释放内存空间
void operator delete(void* p)
{
cout << "operator delete:" << p << endl;
free(p);
}
int main()
{
int* p = new int;
delete p;
return 0;
}
new delete 在内存管理的角度而言,和 malloc free没什么大区别。
如果要判断内存开辟失败,如下:把可能会发生异常的代码放在try块。catch 捕获异常。
源代码如下:// delete p; 调用p指向对象的析构函数、再调用operator delete释放内存空间
void* operator new(size_t size)
{
void* p = malloc(size);
if (nullptr == p)
{
throw bad_alloc();
}
cout << "operator new:" << p << endl;
return p;
}
// delete p; 调用p指向对象的析构函数、再调用operator delete释放内存空间
void operator delete(void* p)
{
cout << "operator delete:" << p << endl;
free(p);
}
void* operator new[](size_t size)
{
void* p = malloc(size);
if (nullptr == p)
{
throw bad_alloc();
}
cout << "operator new[]:" << p << endl;
return p;
}
void operator delete[](void* p)
{
cout << "operator delete[]:" << p << endl;
free(p);
}
int main()
{
int* p = new int;
delete p;
int* p1 = new int[10];
delete[]p1;
return 0;
}
一般对于 实时性要求比较高,小块内存频繁开辟 释放的情况,最好实现为内存池。如果要在全局实现内存池,则需要重写new delete的重载函数。
答:在全局里面重写这些函数,在new里面(用映射表记录一下),哪些内存被开辟了。delete的时候,把这个内存开辟标识删除掉。就是说,要我们自定义的new delete重载函数来负责整个应用的内存开辟与回收,并有所记录。(甚至还可以通过一些编译器既定的宏或者接口来把函数调用堆栈打开,可以显示出源文件的哪一行代码上 资源没有释放)
针对上图,int不存在什么析构函数。所以此时的new delete只剩下malloc 和 free了。
所以说,现在的这种混用是可以的,不存在资源没释放的问题。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
混用之后:
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
混用之后:
错误:
从上图看:构造了3次,只析构了一次。程序崩溃。
如上图所示:每个test对象占4个字节(一个整型的成员变量),共5个对象。
但实际上不是4*5=20 字节。为了标识这是一个数组,编译器自己多分配了4字节(用来存储对象的个数 也即5)。
在delete【】p2的时候,先调用析构函数(当然有this指针 用来指向当前对象。把正确的地址传到析构函数里面),但是【】表示有个数组 好几个对象。但是对象的个数是怎么给delete的,让它去释放内存的?所以说在开辟内存的时候,那个【5】里面的5是被存储下来的。共24字节。在开辟完之后,返回的指针 给p2的是(0x104,而不是0x100)。我们用户知道p2指向的是 数组中第一个对象的地址。
在执行delete []p2;
就知道释放的是一个对象数组。所以自动的去0x104上的4字节里面取出来数组对象的个数5. 然后就可以把这20字节分成5份了。每一个对象的起始地址也知道了,传给析构函数,就可以正确的析构了。delete 需要先调用析构函数,完了之后在释放内存的时候。从那4个字节开始释放。
开辟是从80开始,但是给t1的是84 。 delete t1; 相当于 人家只是释放一个对象的内存,从84开始释放了。
只new了一个对象,(p1也指向0x104)但是delete 【】p1;好像你要去释放一个数组一样。然后就从析构函数开始,然后又去0x104上面4字节取个数。但是这明显是错的。而且最关键的就是 在free(0x104-4)进行内存释放,直接程序崩溃。
第七节:new和delete重载实现的内存池:
源代码如下:#include
上面 入队 出队各需要10万次
每次入队都要 QueueItem *newitem = new QueueItem(val);(malloc)
每次出队都要 delete item;(free)
在短时间里面在 大量地进行着malloc内存和free内存操作
刚把一个节点释放了,入队的时候又需要一个new一个节点。
产生一个QueueItem的对象池:准备上10万个item节点(已经开辟好的)
需要节点的时候,直接从池里面取
释放的时候,返还给池。
这是给类提供new和delete运算符重载 版本的运行时间:
源代码如下:#include