C++的学习心得和知识总结 第五章(完)

C++的运算符重载:使对象的运算表现得和编译器内置类型一样

文章目录

  • C++的运算符重载:使对象的运算表现得和编译器内置类型一样
    • 第一节:复数类的实现
    • 第二节:String类的实现
    • 第三节:String字符串对象的迭代器 iterator实现
    • 第四节:vector容器的迭代器 iterator实现
    • 第五节:容器的迭代器 iterator失效问题:
    • 第六节:容器的迭代器 iterator失效问题:
    • 第七节:new和delete的底层原理:
    • 第七节:new和delete重载实现的内存池:

template
T sum(T a, T b)
{
return a+b; // a.operator+(b)
}
T 现在是我们用类型参数定义的一个类型(可以是任意用户实例化的类型),如果T是编译器内置类型的话(编译器自己就可以完成a+b了),但是如果不是(或者是类类型。则a b是两个对象 )a+b,编译器自己就完成不了了。(需要 进行运算符重载,即a.operator+(b).a调用自己的加法函数,把b作为实参传进去)。因此 在编程的时候,我们不需要写成这样:a.operator+(b),只需要提供+运算符重载, 我们就可以依旧写成统一的a+b(这个T 无论是内置类型还是类类型都是可以直接使用这套代码的)。
operator+()就是+运算符重载的成员函数。

第一节:复数类的实现

普通类型实现:

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的值都没被改变,而是我生成了一个新的对象。(但是一般情况下,返回一个对象的代价太大,所以都是返回对象的引用:即指针)。但是就上面的函数而言:是绝对不能返回一个局部对象的指针或者引用的。

对象的优化 如下:
C++的学习心得和知识总结 第五章(完)_第1张图片
加法运算符重载函数的意义:对象做运算, 左边的对象即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种不同的复数对象。
在这里插入图片描述
运行结果如下:
C++的学习心得和知识总结 第五章(完)_第2张图片
需求2:CComplex comp5 = 20+comp1 ;
C++的学习心得和知识总结 第五章(完)_第3张图片
原因分析:此时的20 不能完成一个临时对象的生成的。编译器做对象运算的时候,会调用对象的运算符重载函数(优先调用成员方法);如果没有成员方法,就在全局作用域找合适的运算符重载函数。 此时运算符 + 左边是int类型的 20,因此它无法调用到类的成员方法:加法运算符重载函数。然后去全局作用域找合适的运算符重载函数,没有找到 所以无法编译。
但是我们也可以::operator+(20, comp1) 全局作用域的加法运算符重载函数。全局函数是不需要对象来调的,20, comp1都作为实参传入。
C++的学习心得和知识总结 第五章(完)_第4张图片
全局函数(类外面的函数):访问对象的私有成员变量 不行
解决办法1:提供 get公有方法。
解决方法2:友元函数。(类外面的函数)访问对象的私有成员变量 行
C++的学习心得和知识总结 第五章(完)_第5张图片
运行结果如下:
C++的学习心得和知识总结 第五章(完)_第6张图片
先生成一个临时对象此时的实部用传入的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++);
	}

运行截图如下:
C++的学习心得和知识总结 第五章(完)_第7张图片
需求4:comp1 += comp2;//复合运算符重载
C++的学习心得和知识总结 第五章(完)_第8张图片

	void operator+=(const CComplex& src)//复合运算符重载
	{
		mreal += src.mreal;
		mimage += src.mimage;
	}

C++的学习心得和知识总结 第五章(完)_第9张图片
注:
运算符运算,底层都是相应的成员方法或者全局的(运算符重载函数)的调用。

需求5:输出运算符 << 重载

//comp1.show();  // 对象信息的输出
//cout ::operator<<(cout, comp1)   void << endl;
//ostream& operator<<(ostream &out, const CComplex &src)

输出运算符 << 重载:对象也是可以用标准的输出流 来输出到屏幕(控制台应用程序)上的。但是输出运算符 << 重载函数的 对象不在左边,所以无法提供成为 成员函数(对象需要在左边,才可以调用 运算符重载函数的,把右边的函数作为实参传入)所以只能把 输出运算符重载函数通过成为 全局方法(不需要对象来调用)。如下所示:
在这里插入图片描述
把cout 和 comp对象作为实参传进去。而且其返回值不可以为void 否则的话,就会出现 void<

ostream& operator<<(ostream &out, const CComplex &src)

out就是cout的别名。
重载函数代码如下:需要提供成友元函数

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

运行截图如下;
C++的学习心得和知识总结 第五章(完)_第10张图片
对象也可以由cout进行输出,在C++中 任何对象的运算都可以通过 合适的运算符重载函数就行。
本节源代码如下:

/*
C++的运算符重载:使对象的运算表现得和编译器内置类型一样
template
T sum(T a, T b)
{
	return a+b; // a.operator+(b)
}
复数类
*/
//template
class CComplex
{
public:
	CComplex(int mr=0,int mi=0):mreal(mr),mimage(mi)
	{

	}
	~CComplex()
	{

	}

	// 指导编译器怎么做CComplex类对象的加法操作
	//加法运算符重载函数
	CComplex operator+(const CComplex& src)
	{
		/*CComplex newCom;
		newCom.mreal = this->mreal + src.mreal;
		newCom.mimage = this->mimage + src.mimage;
		return newCom;*/

		//这样做 少了临时对象的构造 析构
		return CComplex(this->mreal + 
		src.mreal, this->mimage + src.mimage);
	}
	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++);
	}
	void operator+=(const CComplex& src)//复合运算符重载
	{
		mreal += src.mreal;
		mimage += src.mimage;
	}
	void show() { cout << "real:" << mreal << 
	" image:" << mimage << endl; }
private:
	int mreal;//实部
	int mimage;//虚部
	friend CComplex operator+(const CComplex& src1
	, const CComplex& src2);
	friend ostream& operator<<(ostream& out, const CComplex& src);
	friend istream& operator>>(istream& in, CComplex& src);
};

//全局的加法运算符的重载函数
CComplex operator+(const CComplex& src1, const CComplex& src2)
{
	return CComplex(src1.mreal + src2.mreal
	, src1.mimage + src2.mimage);
}
ostream& operator<<(ostream& out, const CComplex& src)
{
	out << "mreal: " << src.mreal << "mimage: " << src.mimage;
	return out;
}
istream& operator>>(istream& in, CComplex& src)
{
	in >> src.mreal;
	in >> src.mimage;
	return in;
}
int main()
{
	CComplex comp1(10, 10);
	CComplex comp2(20, 20);

	//编译器会去找 comp1.operator+(comp2) 加法运算符的重载函数
	CComplex comp3 = comp1 + comp2;
	comp3.show();
	
	// comp1.operator+(20) int->CComplex  CComplex(int)
	CComplex comp4 = comp1 + 20; 
	comp4.show();

	//调用全局的加法运算符的重载函数
	CComplex comp5 = 20 + comp1;
	comp5.show();
	cout << "++++++++++++++++++++++++++++++++++++++++++++++" << endl;
	// CComplex operator++(int)
	// ++ --单目运算符  operator++() 前置++ 和 operator++(int) 后置++
	comp5 = comp1++; 
	comp1.show();
	comp5.show();
	// CComplex operator++()
	comp5 = ++comp1;
	comp1.show();
	comp5.show();
	cout << "++++++++++++++++++++++++++++++++++++++++++++++" << endl;

	comp1 += comp2;//复合运算符重载
	comp1.show();

	cout << comp1 << endl;
	cout << "++++++++++++++++++++++++++++++++++++++++++++++" << endl;
	cout << "请输入两个对象:" << endl;
	cin >> comp1 >> comp2;
	cout << comp1 << comp2 << endl;
	return 0;
}

第二节:String类的实现

在C语言中,字符串常常是由字符数组来存储字符串的。char arr[]=“abcdefg”;
不太方便的地方有:
1 定义数组 大小总是固定的。
2 数组在进行大小比较的时候;字符串连接的时候都需要调用相应的字符串操作函数。
3 内存是否够用的问题。

在C++里面专门有string类型 负责操作字符串。
这个string类 可以实现下面的一些:
C++的学习心得和知识总结 第五章(完)_第11张图片
1 支持默认构造
2 说明string类 里面有string(const char参数)的构造函数
3 里面有 加法运算符的重载函数
4 里面有类内实现的:支持对象+ const char

5 还要全局实现的:const char*+对象
此外还可以支持一下操作:
(为什么string 可以做到这些 是因为给string提供了 相应的 运算符重载函数)

#include
int main()
{
	string str1;//默认构造

	//说明string类 里面有string(const char*参数)的构造函数
	string str2 = "Tsinghua";

	string str3 = "University";
	string str4 = str2 + str3;

	string str5 = str2 + "Computing";

	string str6 = "Beijing" + str2;

	cout << "str6: " << str6 << endl;
	if (str5 > str6)
	{
		cout << str5 << ">" << str6 << endl;
	}
	else
	{
		cout << str6 << ">" << str5 << endl;
	}

	int len = str6.length();
	cout << "str6.length():" << len << endl;
	//对象作为数组名进行访问,字符串对象底层指定序号的字符
	//肯定是提供了【】运算符重载的函数
	for (int i = 0; i < len; ++i)
	{
		cout << str6[i] << " ";
	}
	cout << endl;

	//string----->char *
	char buff[1024] = { 0 };
	strcpy(buff, str6.c_str());
	//c_str()把(对象的)管理的字符串返回为 const char *类型
	cout << "buff:" << buff << endl;
	return 0;
}

运行截图如下:
C++的学习心得和知识总结 第五章(完)_第12张图片
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++自己实现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
int main()
{
	String str1;//默认构造
	//说明String类 里面有String(const char*参数)的构造函数
	String str2 = "Tsinghua";
	String str3 = "University";
	String str4 = str2 + str3;
	String str5 = str2 + "Computing";
	String str6 = "Beijing" + str2;
	cout << "str6: " << str6 << endl;
	if (str5 > str6)
	{
		cout << str5 << ">" << str6 << endl;
	}
	else
	{
		cout << str6 << ">" << str5 << endl;
	}
	int len = str6.length();
	cout << "str6.length():" << len << endl;
	//对象作为数组名进行访问,字符串对象底层指定序号的字符
	//肯定是提供了【】运算符重载的函数
	for (int i = 0; i < len; ++i)
	{
		cout << str6[i] << " ";
	}
	cout << endl;
	//String----->char *
	char buff[1024] = { 0 };
	strcpy(buff, str6.c_str());
	//c_str()把(对象的)管理的字符串返回为 const char *类型
	cout << "buff:" << buff << endl;
	return 0;
}

此运行截图以及 main函数 和上面的一模一样:
C++的学习心得和知识总结 第五章(完)_第13张图片
现在还欠缺一个问题:在加法运算符重载函数那里,那个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;
}

对比两者:
C++的学习心得和知识总结 第五章(完)_第14张图片
虽然 下面的那个解决了关于内存泄漏的事情,但是其效率实在是太低了。

第三节:String字符串对象的迭代器 iterator实现

提供运算符重载函数 使得操作字符串和操作其他类型(编译器的其他内置类型)一样的简单。需要掌握 运算符重载和String类型。

上节课的遗留问题:
功能实现但是效率太低:原因在于 (在进入加法运算符重载函数的时候)用newstr开辟了一大块内存,然后进行字符串拷贝和字符串连接。紧接着作为实参 进行局部对象strtemp的构造。(去strtemp对象的构造函数的时候)然而在这个strtemp对象的构造函数里面又开辟了一块同样大小的内存进行 数据的拷贝。(第一次delete newstr指针指向的内存)(然后可以把这个刚开辟的newstr指向的堆内存进行释放了)。完毕之后,在(return strtemp;的地方)的时候,去(str4对象的拷贝构造函数)里面又开辟了一块同样大小的内存进行 数据的拷贝。完了之后,开始析构这个局部对象strtemp(在其析构函数里面 第二次进行delete)。然后 到此刻为止,
String str4 = str2 + str3; 这句话终于执行完了 (快把劳资累死了 fuck fuck fuck)
C++的学习心得和知识总结 第五章(完)_第15张图片
两次 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 就自动析构了
}

这次 比上一个版本 少了一次new 一次delete
过程:(在进入加法运算符重载函数的时候)直接进行局部对象strtemp的构造(因为用的是默认的指针nullptr,所以少了一次new)。然后直接给这个局部对象底层的指针开辟空间(但是我感觉这里少了一个空间的释放,造成内存泄漏),然后 数据的拷贝 连接。完毕之后,在(return strtemp;的地方)的时候,去(str4对象的拷贝构造函数)里面又开辟了一块同样大小的内存进行 数据的拷贝。完了之后,开始析构这个局部对象strtemp(在其析构函数里面 第一次进行delete)。然后 到此刻为止
String str4 = str2 + str3; 这句话终于执行完了 (快把劳资累死了 真香 真香 真香。特码的腰疼 坐了一整天了)
但是这样还有些问题:为了带出这个局部对象strtemp,是一定要产生临时对象叫(temp吧)的。这个临时对象temp的拷贝构造 也会产生大量的内存的开辟 和 数据的拷贝。临时对象的析构也涉及到 内存的释放。

所以说 重点来了:一定得需要给string类型提供 右值引用参数的拷贝构造和赋值重载函数。(在对象的优化时候 咱们再见)

String类的全部源代码如下:

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
int main()
{
	String str1;//默认构造
	//说明String类 里面有String(const char*参数)的构造函数
	String str2 = "Tsinghua";
	String str3 = "University";
	String str4 = str2 + str3;
	String str5 = str2 + "Computing";
	String str6 = "Beijing" + str2;
	cout << "str6: " << str6 << endl;
	if (str5 > str6)
	{
		cout << str5 << ">" << str6 << endl;
	}
	else
	{
		cout << str6 << ">" << str5 << endl;
	}
	int len = str6.length();
	cout << "str6.length():" << len << endl;
	//对象作为数组名进行访问,字符串对象底层指定序号的字符
	//肯定是提供了【】运算符重载的函数
	for (int i = 0; i < len; ++i)
	{
		cout << str6[i] << " ";
	}
	cout << endl;
	//String----->char *
	char buff[1024] = { 0 };
	strcpy(buff, str6.c_str());
	//c_str()把(对象的)管理的字符串返回为 const char *类型
	cout << "buff:" << buff << endl;
	return 0;
}

运行截图:
C++的学习心得和知识总结 第五章(完)_第16张图片
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++本节的 String字符串对象的迭代器 iterator实现
从iterator的使用方式上来看,它是容器类的一个 嵌套类。

#include
int main()
{
	// 迭代器的功能:提供一种统一的方式,来透明的遍历容器
	 // str1叫容器吗?叫 底层放了一组char类型的字符
	string str1 = "hello world!";
	// 容器的迭代器类型
//用指针去遍历这个容器,可是char *p  接收不了人家的成员变量(不知道且私有)啊
	auto it = str1.begin();
	//string::iterator it = str1.begin();// 同上
	for (; it != str1.end(); ++it)
	{
		cout << *it << " ";
	}
	cout << endl;

现在string对象 str1(底层是用什么数据结构的方式存放字符不重要)里面有一组 字符串(对外不可见)。对于字符串底层的成员变量而言,是私有的。可是 我们现在想去 迭代这些字符啊 迭代器是怎么做到的呢? str1.begin();是容器的方法:返回的是底层首元素的 迭代器表示。那也即:it现在指向容器底层首元素。
str1.end(); 表示:容器最后一个有效元素的后继位置迭代器
C++的学习心得和知识总结 第五章(完)_第17张图片
最后一个元素也可以遍历到。it++ 向后走。(底层的数据结构是什么 劳资不管!!!! 你只要到下一个元素就行 。怎么过去的 是封装到++ 运算符重载里面了) it 相当于一个OO的指针一样,它指向容器底层的数据,解引用就可以访问到这个数据。
在这里插入图片描述
根本不需要你容器底层的数据结构是什么,只需要定义一个这个容器类型特定的 迭代器。这个迭代器当然不可能设置成统一的。不同容器的底层数据结构是不一样的,每一种容器都有自己的迭代器。所以这也是为什么 迭代器设置为容器的嵌套类型。那么容器的 begin end 方法,分别返回的是底层首元素的 迭代器表示、容器最后一个有效元素的后继位置迭代器。for循环 当前迭代器不等于(容器最后一个有效元素的**后继位置迭代器)作为条件。 怎么过去的 是封装到迭代器++ 运算符重载里面了。对于迭代器还需要 通过* 运算符重载函数来访问迭代器 迭代元素的值。)

C++的学习心得和知识总结 第五章(完)_第18张图片
在C++ STL的泛型算法里面:既然是给所有容器用的,容器底层的数据结构不一样。不知道将来会让这个泛型算法去处理什么类型的容器的元素。既然要处理容器的元素,所有需要容器的元素遍历一遍。

迭代器指向的是一个位置,是对 char *_p;的封装。既然要指向一个位置,所以也需要 参数接受一个指针。(在iterator的构造函数里面,用用户传进去的指针去初始化这个迭代器成员变量 :这个指针char *_p;)

前置++的重载,返回的是一个引用。是对象本身,不产生临时量的。(效率高)
后置++的重载,返回的是一个对象。是要产生临时量的,先返回旧对象的值,再给当前对象++。
C++的学习心得和知识总结 第五章(完)_第19张图片
本节源代码:

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
int main()
{
	// 迭代器的功能:提供一种统一的方式,来透明的遍历容器
	//(不需要知道数据结构)
	String str1 = "hello world!"; // str1叫容器吗?叫 
	//底层放了一组char类型的字符
	// 容器的迭代器类型
	//用指针去遍历这个容器,可是char *p  接收不了人家的成
	//员变量(不知道且私有)啊
	//auto it = str1.begin();
	String::iterator it = str1.begin();// 同上
	for (; it != str1.end(); ++it)
	{
		cout << *it << " ";
	}
	cout << endl;

	// c++11 foreach的方式来遍历容器的内部元素的值=>底层,
	//还是通过迭代器进行遍历的
	for (char ch : str1)
	{
		cout << ch << " ";
	}
	cout << endl;

	// vector allocator  提供迭代器iterator的实现

}

第四节:vector容器的迭代器 iterator实现

iterator:针对所有容器,统一的(透明的)遍历所有容器的方式。不用去管容器的底层数据结构是什么样子, iterator配合着容器的begin end方法以及迭代器相应的运算符重载方法(比如 ++ != )等可以用 iterator把容器的元素全部的遍历一遍。不同容器(数据结构也不同),在数据结构上遍历元素的方式也不同。
C++的学习心得和知识总结 第五章(完)_第20张图片
方式都是一样的?
原因:容器提供begin 方法和 end方法 分别返回的是 底层首元素的迭代器表示、容器最后一个有效元素的后继位置迭代器。 for循环 当前迭代器不等于(容器最后一个有效元素的 后继位置迭代器)作为条件。(这样就可以实现全部元素的遍历 ) 怎么过去的 是封装到迭代器++ 运算符重载里面了。对于迭代器还需要 通过
运算符重载函数来访问迭代器 迭代元素的值。)
方式都是一样的,其原因正是在于:当我们遍历完当前元素的时候,从当前元素到下一个元素(底层数据结构的遍历方式都封装到迭代器的++运算符重载里面)。我们也会只需要知道给iterator进行++操作就行了。就可以获取到下一个元素。至于底层是什么数据结构(数组 链表 栈 队列 等)不重要。

泛型算法:一组用模板写的且与具体类型无关的函数。
在C++ STL的泛型算法(全局的函数)里面:既然是给所有容器都可以使用的,容器底层的数据结构不一样。不知道将来会让这个泛型算法去处理什么类型的容器的元素。既然要处理容器的元素,所以需要把容器的元素(以一种统一的方式)遍历一遍。
C++的学习心得和知识总结 第五章(完)_第21张图片
本节实现 vector容器的迭代器( iterator非常重要)。

迭代器有什么成员变量取决于容器底层的数据结构是什么,容器需要什么样的变量来遍历,那么iterator就采用什么样子的 变量作为其成员变量。

本节源代码如下:这里涉及了(vector、allocator、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 

#include 
#include 
using namespace std;

int main()
{
	srand((unsigned)time(nullptr));
	vector<int> myv;
	for (int i = 0; i < 12; ++i)
	{
		myv.push_back(rand() % 15);
	}

	//把容器中的奇数都删掉
for (vector<int>::iterator it = myv.begin(); it != myv.end(); ++it)
	{
		if ((*it) % 2 == 1)
			myv.erase(it);
	}
	for (int val : myv)
	{
		cout << val << " ";
	}
	cout << endl;
	return 0;
}

运行结果:
C++的学习心得和知识总结 第五章(完)_第22张图片
出了什么问题了?迭代器失效了
在第一次调用myv.erase(it);之后,我们的it 就已经失效了。然后又++it。对一个失效的迭代器进行++运算符重载的函数调用操作,当然是运行出错。

或者看下面的代码:
C++的学习心得和知识总结 第五章(完)_第23张图片
只进行一次:程序正常
进行多次,程序崩溃(同样是iterator在第一次 insert之后,就已经失效了)
C++的学习心得和知识总结 第五章(完)_第24张图片
C++的学习心得和知识总结 第五章(完)_第25张图片
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
迭代器为什么失效?
在这里插入图片描述
怎么解决这个失效问题呢?
对插入点 和 删除点的iterator做更新操作
原因分析:insert 和 erase完之后都会 重新返还一个iterator(这是指向当前 这个点 的新的iterator)。所以我们需要重新的 把我们的for循环变量it 更新一下(接收那个新的iterator 这个是一个有效的iterator 所以我们必须进行更新)。
C++的学习心得和知识总结 第五章(完)_第26张图片
因为erase之后,数组的元素要进行移动。此刻便不可以再进行++了,而是接着判断此时的it (现在正在指向的)指向的元素是否为奇数

接下来,再看insert的情况:
C++的学习心得和知识总结 第五章(完)_第27张图片

	//在容器里面,在容器的奇数前面 都添加上一个小于1的偶数
	vector::iterator it1 = myv.begin();
	while( it1 != myv.end())
	{
		if ((*it1) % 2 == 1)
		{
			it1 = myv.insert(it1, (*it1) - 1);
			it1++;
		}
		it1++;
	}

分析:如果是奇数5,则在其前面添加一个比他小1 的偶数4,(在底层容器上,新的iterator已经指向了4)。如果我不接着进行一个it++,则在下一次 又遇到5了,重新操作。(陷入无尽的循环)。最后无论是奇数还是偶数,都要再进行it++,以跳过这个数 向下进行。(在使用iterator,连续insert操作 之后 要进行it++两次)。
迭代器失效以及解决的源代码如下:

#include 

#include 
#include 
using namespace std;

int main()
{
	srand((unsigned)time(nullptr));
	vector<int> myv;
	for (int i = 0; i < 12; ++i)
	{
		myv.push_back(rand() % 15);
	}
	for (int val : myv)
	{
		cout << val << " ";
	}
	cout << endl;

	//在容器里面,在容器的奇数前面 都添加上一个小于1的偶数
	vector<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;
	//把容器中的奇数都删掉
	vector<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;
}

运行截图:
C++的学习心得和知识总结 第五章(完)_第28张图片
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
C++的学习心得和知识总结 第五章(完)_第29张图片
现在给iterator 这个内部嵌套类,又增加了一个成员变量。
C++的学习心得和知识总结 第五章(完)_第30张图片

	private:
		T* _p;
		
		MyVector* _ptrVec;//当前迭代器需要知道 它在迭代哪种容器对象
		//让迭代器需要知道,当前它在迭代到底是哪一个容器对象

	};

因为容器vector底层是一个数组,所以用 _p 指针去遍历这个数组。而新增加的成员变量: _ptrVec 让迭代器需要知道,当前它在迭代到底是哪一个容器对象。
迭代器失效的另外一种方式:不同容器的迭代器是不可以进行比较运算的。 来判断两个迭代器是不是指向同一个容器的同一个位置。两个容器在内存上的位置肯定是不一样的,比较不同容器的迭代器是没有意义的。
所以在这里,给迭代器iterator添加的新的成员变量: _ptrVec 让迭代器需要知道,当前它在迭代到底是哪一个容器对象。(一个指向当前对象的指针 )

C++的学习心得和知识总结 第五章(完)_第31张图片
给其构造函数添加了一个新参数:MyVector* ptrVec=nullptr 。用来接收 外面传入的一个容器对象的地址。通过其构造函数把外面容器的地址传进来 给成员变量 _ptrVec 赋值。

		//两个对于字符串的迭代器的不等于
		//迭代器的不等于就是底层指针的不等于
		//迭代器的指向 就是 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;
		}

给迭代器的一些运算符重载函数 加上检查迭代器的有效性
例如: !=运算符在对两个迭代器进行比较,
C++的学习心得和知识总结 第五章(完)_第32张图片
如果迭代器底层的指针(1 指向容器是空的)(2 _ptrVec不空,但是当前迭代器迭代的容器 和 传进来的迭代器it迭代的容器不一样)都表示迭代器失效了。

例如:++运算符重载。若迭代器底层的指针:指向容器是空的。表示迭代器失效了。
C++的学习心得和知识总结 第五章(完)_第33张图片
在begin end方法处:传入的 this指针(这两个方法是 容器 的方法,this指向当前容器对象。)把this 传给iterator的构造方法中。
C++的学习心得和知识总结 第五章(完)_第34张图片
其构造函数如下:
C++的学习心得和知识总结 第五章(完)_第35张图片
所以_ptrVec(通过容器创建迭代器) 肯定是不为空的。之所以_ptrVec在后面为空,是因为迭代器失效了。
那么现在是怎么实现的 把删除点和增加点 给失效掉呢?这个结构体是在 MyVector类里面添加的。
C++的学习心得和知识总结 第五章(完)_第36张图片
里面维护了一个:指向某个迭代器的指针( _curiterator;)。和指向下一个Iterator_Base节点的next地址。还有最后的那个_head 指针。

这他么一看就是个给容器提供的链表 嘛 (FUCK FUCK 心情不畅 难受 不想写了 不想写了 )

唉 算了 学习!!!

这里用一个链表 把用户从容器里面获取的元素的iterator 给串起来(放在_head为头结点的链表里面)。把所有的迭代器都记录下来

有了这个记录以后,此时再通过哪个迭代器去 insert或者erase 的时候,就要把相应的某些iterator给 失效掉,让迭代器重新更新

从迭代器的生成(或者 其构造函数)来看:
C++的学习心得和知识总结 第五章(完)_第37张图片
通过构造函数:this是当前新生成的 迭代器对象的地址。第二个参数 是next指针。每个节点的数据域里面放的是 iterator* _curiterator; 从容器内部获得的某一个位置元素的迭代器。(相当于 头插法)

底层维护这么一个链表:把所有 用户从容器里面获取的元素的iterator 给串起来(放在_head为头结点的链表里面)。把所有的迭代器都记录下来。

每个iterator 对象都包含了两个东西:

T* _p;//指向了容器 底层数组的某个元素
		
MyVector<T, Allo>* _ptrVec;//当前迭代器需要知道 它在迭代哪种容器对象 
//  指向容器的指针

把有些iterator 给失效一下
C++的学习心得和知识总结 第五章(完)_第38张图片

	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的成员变量_ptrVec (指向容器的指针)为空。

// 迭代器失效,把iterator持有的容器指针置nullptr
curIter->_curiterator->_ptrVec = nullptr;

意思就是:迭代器指向容器为空 标志这个iterator为失效的。
容器底层迭代器的实现原理以及迭代器失效在底层是怎么设计实现的。
所以这种情况下:在增加和删除,之后把这个区间的iterator全部失效掉。(具体就是在 存储了所有的迭代器的链表上进行遍历。遍历这个链表,发现某个记录迭代器迭代元素的地址,是在这个失效区间里面 则把这个迭代器失效一下。所谓的失效 就是把iterator的成员变量_ptrVec (指向容器的指针)为空。)

第六节:容器的迭代器 iterator失效问题:

本节:insert erase的实现。

本节的代码极为重要。主要涉及到:allocator 类模板 运算符重载 嵌套类iterator 以及迭代器失效的处理和底层失效实现等 综合实现的vector。极为重要

本节源代码如下:

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

C++的学习心得和知识总结 第五章(完)_第39张图片

第七节:new和delete的底层原理:

C++的学习心得和知识总结 第五章(完)_第40张图片

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  

new delete两个运算符的调用,实质上也是两个运算符重载函数的调用。

new和malloc的区别:
(1)malloc是按字节开辟内存的:malloc(sizeof(int)10);它不管内存上放什么数据的。开辟完内存之后返回的是void 需要人为的类型强转。而new开辟内存时需要指定类型,并直接给出开辟元素的个数。new int[10];实质上调用operator new(),返回的类型就是自动的就是你给定的类型指针。int* 。
(2)malloc只负责开辟内存,而new不仅仅具有malloc的功能,还具有数据的初始化操作。
C++的学习心得和知识总结 第五章(完)_第41张图片
(3)malloc开辟内存失败 返回nullptr;new的则是抛出bad_alloc类型的异常(不可以进行与nullptr的比较),需要把new开辟内存的代码放在 try catch里面。
(4)malloc开辟单个元素和数组的内存都是一样的(给字节数就行);new的 对于单个字符不需要加上[]的,对于数组需要加[]并指定个数就可以了。

free与delete的区别:
delete 需要先进行调用析构函数,再free内存。但是对于 delete (int*)p,和free(p)是一样的。因为int 类型没有什么析构函数,只剩下内存释放。

new 就是operator new()的调用。

// 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 捕获异常。
C++的学习心得和知识总结 第五章(完)_第42张图片
源代码如下:

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

C++的学习心得和知识总结 第五章(完)_第43张图片
一般对于 实时性要求比较高,小块内存频繁开辟 释放的情况,最好实现为内存池。如果要在全局实现内存池,则需要重写new delete的重载函数。

如何设计实现检测C++程序内存泄漏的程序?(也即new完了,没有delete)
答:在全局里面重写这些函数,在new里面(用映射表记录一下),哪些内存被开辟了。delete的时候,把这个内存开辟标识删除掉。就是说,要我们自定义的new delete重载函数来负责整个应用的内存开辟与回收,并有所记录。(甚至还可以通过一些编译器既定的宏或者接口来把函数调用堆栈打开,可以显示出源文件的哪一行代码上 资源没有释放)
在这里插入图片描述
C++的学习心得和知识总结 第五章(完)_第44张图片
针对上图,int不存在什么析构函数。所以此时的new delete只剩下malloc 和 free了。
所以说,现在的这种混用是可以的,不存在资源没释放的问题。
C++的学习心得和知识总结 第五章(完)_第45张图片
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
C++的学习心得和知识总结 第五章(完)_第46张图片
混用之后:
C++的学习心得和知识总结 第五章(完)_第47张图片
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
C++的学习心得和知识总结 第五章(完)_第48张图片
混用之后:
C++的学习心得和知识总结 第五章(完)_第49张图片
错误:
从上图看:构造了3次,只析构了一次。程序崩溃。

C++的学习心得和知识总结 第五章(完)_第50张图片
如上图所示:每个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个字节开始释放。
在这里插入图片描述

分析上面的错误:
C++的学习心得和知识总结 第五章(完)_第51张图片
开辟是从80开始,但是给t1的是84 。 delete t1; 相当于 人家只是释放一个对象的内存,从84开始释放了。

因此 下面这张也是错的:
C++的学习心得和知识总结 第五章(完)_第52张图片
只new了一个对象,(p1也指向0x104)但是delete 【】p1;好像你要去释放一个数组一样。然后就从析构函数开始,然后又去0x104上面4字节取个数。但是这明显是错的。而且最关键的就是 在free(0x104-4)进行内存释放,直接程序崩溃。
在这里插入图片描述

第七节:new和delete重载实现的内存池:

第一版:Queue 给类提供new和delete运算符重载
源代码如下:

#include 
#include 
#include 
using namespace std;
/*
运算符的重载:成员方法和全局方法
提供成全局方法:整个涉及到内存的开辟和回收(new delete的地
方),都将由这里的全局重写的new delete运算符重载函数的负责。
但是很多时候,我们只是想 在局部(自定义的某些类型 对象的内存管理方式)里
面使用重写的new delete运算符重载函数。

内存池 进程池 对象池 线程池 连接池(都是为了提高某一模块的效率的)
*/
template<typename T=int>
class Queue//链式队列
{
public:
	Queue()//构造函数
	{
		//头结点作为 入口节点
		_front = _rear = new QueueItem();
	}
	~Queue()
	{
		QueueItem* curitem = _front;
		while (curitem != nullptr)
		{
			_front = _front->_next;
			delete curitem;
			curitem = _front;
		}
	}
	void addQueue(const T& val)//入队
	{
		//添加一个入队的新元素
		QueueItem *newitem = new QueueItem(val);
		_rear->_next = newitem;
		_rear = newitem;//rear要一直指向队尾元素
	}
	void delQueue()//从队头出队 
	{
		if (empty())
		{
			return;
		}
		else
		{
			//头删法 永远删除第一个节点
			QueueItem *item = _front->_next;
			_front->_next = item->_next;
			delete item;

			//只有一个节点(除了头结点)
			if (_front->_next == nullptr)
			{
				_rear = _front;
			}
		}
	}
	T getFront()const//返回首元素的值
	{
		return _front->_next->_data;
	}
	bool empty()const
	{
		return _front == _rear;
	}
private:
	struct QueueItem//节点类型
	{
		//0构造 相当于默认构造
		QueueItem(T data=T() ):_data(data),_next(nullptr){}
		T _data;//数据域
		QueueItem* _next;//指针域(指向下一个节点)
	};
	QueueItem* _front;//带头结点的链队列(头结点后面的是首元素)
	QueueItem* _rear;//队尾(链表的最后一个元素)
};

int main()
{
	srand((unsigned)time(nullptr));
	Queue<>myQue;
	for (int i = 0; i < 100000; ++i)
	{
		myQue.addQueue(rand() % 100);
		myQue.delQueue();
	}
	if (myQue.empty())
	{
		cout << "myQue is empty!" << endl;
	}
	return 0;
}

C++的学习心得和知识总结 第五章(完)_第53张图片
上面 入队 出队各需要10万次
每次入队都要 QueueItem *newitem = new QueueItem(val);(malloc)
每次出队都要 delete item;(free)
在短时间里面在 大量地进行着malloc内存和free内存操作
刚把一个节点释放了,入队的时候又需要一个new一个节点。
产生一个QueueItem的对象池:准备上10万个item节点(已经开辟好的)
需要节点的时候,直接从池里面取
释放的时候,返还给池。

C++的学习心得和知识总结 第五章(完)_第54张图片
这是给类提供new和delete运算符重载 版本的运行时间:
C++的学习心得和知识总结 第五章(完)_第55张图片
源代码如下:

#include 
#include 
#include 
using namespace std;
/*
运算符的重载:成员方法和全局方法
提供成全局方法:整个涉及到内存的开辟和回收(new delete的地
方),都将由这里的全局重写的new delete运算符重载函数的负责。
但是很多时候,我们只是想 在局部(自定义的某些类型 对象的内存管理方式)里
面使用重写的new delete运算符重载函数。

内存池 进程池 对象池 线程池 连接池(都是为了提高某一模块的效率的)
*/
template<typename T=int>
class Queue//链式队列
{
public:
	Queue()//构造函数
	{
		//头结点作为 入口节点
		_front = _rear = new QueueItem();
	}
	~Queue()
	{
		QueueItem* curitem = _front;
		while (curitem != nullptr)
		{
			_front = _front->_next;
			delete curitem;
			curitem = _front;
		}
	}
	void addQueue(const T& val)//入队
	{
		//添加一个入队的新元素
		QueueItem *newitem = new QueueItem(val);
		_rear->_next = newitem;
		_rear = newitem;//rear要一直指向队尾元素
	}
	void delQueue()//从队头出队 
	{
		if (empty())
		{
			return;
		}
		else
		{
			//头删法 永远删除第一个节点
			QueueItem *item = _front->_next;
			_front->_next = item->_next;
			delete item;

			//只有一个节点(除了头结点)
			if (_front->_next == nullptr)
			{
				_rear = _front;
			}
		}
	}
	T getFront()const//返回首元素的值
	{
		return _front->_next->_data;
	}
	bool empty()const
	{
		return _front == _rear;
	}
private:
	//准备一个对象池(10万个左右的 QueueItem)
	struct QueueItem//节点类型
	{
		//0构造 相当于默认构造
		QueueItem(T data=T() ):_data(data),_next(nullptr){}



		//给QueueItem提供 自定义的内存管理
		//这两个函数调用的时候 对象都不存在,本身就是静态方法
		void* operator new(size_t size)
		{
			if (_itemPool == nullptr)//池是空的
			{
				//10万个 QueueItem节点类型
_itemPool =(QueueItem*)new char[sizeof(QueueItem) * POOL_ITEM_SIZE];
				//需要连在一个链表上,并把最后一个节点的指针域置为空
				QueueItem* p = _itemPool;
				for (; p < _itemPool + POOL_ITEM_SIZE-1; ++p)
				{
					p->_next = p + 1;
				}
				p->_next = nullptr;//最后一个节点的指针域置为空
			}
			//这个p是要给用户的
			QueueItem* p = _itemPool;
			_itemPool = _itemPool->_next;
			return p;
		}
		void operator delete(void* p)
		{
			QueueItem* ptr = (QueueItem*)p;

			//把它放在池的第一个节点处
			ptr->_next = _itemPool;
			_itemPool = ptr;
		}
		T _data;//数据域
		QueueItem* _next;//指针域(指向下一个节点)
		static QueueItem* _itemPool;
		static const int POOL_ITEM_SIZE = 100000;//对象池的大小
	};
	QueueItem* _front;//带头结点的链队列(头结点后面的是首元素)
	QueueItem* _rear;//队尾(链表的最后一个元素)
};
/*
QueueItem 是一个嵌套类,需要加上类的作用域
编译器在编译的时候,从上向下。
到Queue::QueueItem*Queue::QueueItem:: _itemPool = nullptr;时
没有实例化任何Queue类,还不知道用什么类型来实例化Queue
所以前面需要加上typename,告诉编译器后面的嵌套类 作用域下的QueueItem是一个类型
*/

template<typename T>
typename Queue<T>::QueueItem*Queue<T>::QueueItem:: _itemPool = nullptr;
int main()
{
	srand((unsigned)time(nullptr));
	Queue<>myQue;


	clock_t start, finish;
	start = clock();
	for (int i = 0; i < 100000; ++i)
	{
		myQue.addQueue(rand() % 100);
		myQue.delQueue();
	}
	finish = clock();
	cout << finish - start << "/" << CLOCKS_PER_SEC <<
	 "  s" << endl;


	if (myQue.empty())
	{
		cout << "myQue is empty!" << endl;
	}
	return 0;
}

你可能感兴趣的:(C++的学习心得和知识总结)