初识C++(3)

一、对象拷贝时的编译器优化

现代编译器会为了尽可能提高程序的效率,在不影响正确性的情况下会尽可能减少一些传参和传返回值过程中可以省略的拷贝。
如何优化C++标准并没有严格规定,各个编译器会根据情况自行处理。当前主流的相对新一点的编译器对于连续一个表达式步骤中的连续拷贝会进行合并优化,有些更新更"激进"的编译还会进行跨行跨表达式的合并优化。

例1:

#include<iostream>
using namespace std;

class A
{
public:
	A(int a = 0)
		:_a1(a)
	{
		cout << "A(int a)" << endl;
	}

	A(const A& aa)
		:_a1(aa._a1)
	{
		cout << "A(const A& aa)" << endl;
	}

	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
			_a1 = aa._a1;
		}
		return *this;
	}

	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a1 = 1;
};

void f1(A aa)
{

}

A f2()
{
	A aa;
	return aa;
}

int main()
{
	A aa1 = 1;//单参数类型转换
	//在不优化的情况下是一个构造加拷贝构造
	//但是实际的结果却不是这样
	//而是直接就是一个构造
	return 0;
}
A(int a)
~A()

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 41824)已退出,代码为 0。
按任意键关闭此窗口. . .

单参数类型转换,在不优化的情况下是一个构造加拷贝构造,但是实际的结果却不是这样,而是直接就是一个构造,中间原本会出现的临时对象就会消失。

例2:

#include<iostream>
using namespace std;

class A
{
public:
	A(int a = 0)
		:_a1(a)
	{
		cout << "A(int a)" << endl;
	}

	A(const A& aa)
		:_a1(aa._a1)
	{
		cout << "A(const A& aa)" << endl;
	}

	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
			_a1 = aa._a1;
		}
		return *this;
	}

	~A()
	{
		cout << "~A()" << endl;
	}
	
private:
	int _a1 = 1;
};

void f1(A aa)
{

}

A f2()
{
	A aa;
	return aa;
}

int main()
{
	const A& aa2 = 1;
	return 0;
}
A(int a)
~A()

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 22832)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .

这种情况下,中间的临时对象就省略不了了,不能合二为一了,因为中间没有走拷贝构造,走的是引用。

例一和例二在结果上看都是只有一次构造,但是区别是,例二是正常的,引用的是构造的临时变量,而例一是构造加拷贝构造优化的结果。

例3:

#include<iostream>
using namespace std;

class A
{
public:
	A(int a = 0)
		:_a1(a)
	{
		cout << "A(int a)" << endl;
	}

	A(const A& aa)
		:_a1(aa._a1)
	{
		cout << "A(const A& aa)" << endl;
	}

	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
			_a1 = aa._a1;
		}
		return *this;
	}

	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a1 = 1;
};

void f1(A aa)
{

}

A f2()
{
	A aa;
	return aa;
}

int main()
{
	A aa1;
	f1(aa1);
	return 0;
}
A(int a)
A(const A& aa)
~A()
~A()

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 16632)已退出,代码为 0。
按任意键关闭此窗口. . .

f1是传值传参,会调用拷贝构造,所以f1(aa1),实参传给形参,就会调用拷贝构造,这里编译器就不会优化了,编译器优化的规则是在一个连续的步骤中会进行优化

例4:

#include<iostream>
using namespace std;

class A
{
public:
	A(int a = 0)
		:_a1(a)
	{
		cout << "A(int a)" << endl;
	}

	A(const A& aa)
		:_a1(aa._a1)
	{
		cout << "A(const A& aa)" << endl;
	}

	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
			_a1 = aa._a1;
		}
		return *this;
	}

	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a1 = 1;
};

void f1(A& aa)
{

}

A f2()
{
	A aa;
	return aa;
}

int main()
{
	A aa1;
	f1(aa1);
	return 0;
}
A(int a)
~A()

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 35780)已退出,代码为 0。
按任意键关闭此窗口. . .

之前我们讲过,为了减少拷贝构造,我们可以用引用,这时候形参就是实参的别名,就没有拷贝构造了。

例4:

#include<iostream>
using namespace std;


class A
{
public:
	A(int a = 0)
		:_a1(a)
	{
		cout << "A(int a)" << endl;
	}

	A(const A& aa)
		:_a1(aa._a1)
	{
		cout << "A(const A& aa)" << endl;
	}

	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
			_a1 = aa._a1;
		}
		return *this;
	}

	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a1 = 1;
};

void f1(A aa)
{

}

A f2()
{
	A aa;
	return aa;
}

int main()
{
	A aa1(1);
	f1(aa1);
	cout << endl;

	f1(A(1));//和例1一样,也被优化了,因为是连续的
	cout << endl;

	f1(1);//因为有单参数的构造函数,所以可以类型转换,也和例1一样,连续的,所以优化
	cout << endl;
	return 0;
}
A(int a)
A(const A& aa)
~A()

A(int a)
~A()

A(int a)
~A()

~A()

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 37176)已退出,代码为 0。
按任意键关闭此窗口. . .

例5:

#include<iostream>
using namespace std;

class A
{
public:
	A(int a = 0)
		:_a1(a)
	{
		cout << "A(int a)" << endl;
	}

	A(const A& aa)
		:_a1(aa._a1)
	{
		cout << "A(const A& aa)" << endl;
	}

	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
			_a1 = aa._a1;
		}
		return *this;
	}

	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a1 = 1;
};

void f1(A aa)
{

}

A f2()
{
	A aa;
	return aa;
}

int main()
{
	f2();
	cout << endl;
	return 0;
}
A(int a)
A(const A& aa)
~A()

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 13608)已退出,代码为 0。
按任意键关闭此窗口. . .

A f2()传值传参返回,会产生一个临时对象,也会调用拷贝构造,所以还是一个构造加一个拷贝构造

例6:

#include<iostream>
using namespace std;

class A
{
public:
	A(int a = 0)
		:_a1(a)
	{
		cout << "A(int a)" << endl;
	}

	A(const A& aa)
		:_a1(aa._a1)
	{
		cout << "A(const A& aa)" << endl;
	}

	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
			_a1 = aa._a1;
		}
		return *this;
	}
	void Print()
	{
		cout << "A::Print->" << _a1 << endl;
	}

	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a1 = 1;
};

A f2()
{
	A aa(1);
	return aa;
}

int main()
{
	f2().Print();
	cout << endl;
	return 0;
}
A(int a)
A(const A& aa)
~A()
A::Print->1
~A()


C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 11324)已退出,代码为 0。
按任意键关闭此窗口. . .

第一个~A()析构的是aa,因为aa是局部变量,出了f2就没了,所以是临时对象调用了Print(),
第二个~A()析构的是临时对象。

例7:

#include<iostream>
using namespace std;

class A
{
public:
	A(int a = 0)
		:_a1(a)
	{
		cout << "A(int a)" << endl;
	}

	A(const A& aa)
		:_a1(aa._a1)
	{
		cout << "A(const A& aa)" << endl;
	}

	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
			_a1 = aa._a1;
		}
		return *this;
	}
	void Print()
	{
		cout << "A::Print->" << _a1 << endl;
	}

	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a1 = 1;
};

A f2()
{
	A aa(1);
	return aa;
}

int main()
{
	A ret = f2();
	ret.Print();
	cout << endl;
	return 0;
}
A(int a)
A(const A& aa)
~A()
A::Print->1

~A()

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 30576)已退出,代码为 0。
按任意键关闭此窗口. . .

第一个~A()析构的是aa,因为aa是局部变量,出了f2就没了,
第二个~A()析构的是ret。

例8:

#include<iostream>
using namespace std;

class A
{
public:
	A(int a = 0)
		:_a1(a)
	{
		cout << "A(int a)" << endl;
	}

	A(const A& aa)
		:_a1(aa._a1)
	{
		cout << "A(const A& aa)" << endl;
	}

	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
			_a1 = aa._a1;
		}
		return *this;
	}
	void Print()
	{
		cout << "A::Print->" << _a1 << endl;
	}

	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a1 = 1;
};

A f2()
{
	A aa(1);
	return aa;
}

int main()
{
	A ret; 
	ret = f2();
	ret.Print();
	cout << endl;
	return 0;
}
A(int a)
A(int a)
A(const A& aa)
~A()
A& operator=(const A& aa)
~A()
A::Print->1

~A()

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 31428)已退出,代码为 0。
按任意键关闭此窗口. . .

解析:
1、A ret; 是一个已经存在的对象,
2、ret = f2();左右两边都是已经存在的对象,然后进行赋值操作,第一个~A()是aa析构了,
第二个~A()是临时对象析构了,
最后是ret析构了。

二、C/C++内存管理

1、C/C++内存分布

栈:函数调用,建立栈帧,非静态局部变量,函数参数,返回值
堆:动态内存分配
数据段:全局数据和静态数据
代码段:只读常量和编译好的指令

例1:

int globalvar =1;
static int staticGlobalVar = 1;
void Test()
{
static int staticvar = 1;
int localVar = 1;

int num1[10] = { 1, 2,3, 4 };
char char2[] = "abcd";

const char* pchar3 = “abcd”;
int* ptr1 = (int*)malloc(sizeof(int) * 4);
int* ptr2 = (int*)calloc(4, sizeof(int));
int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
free(ptr1);
free(ptr3);
}
选项:A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
globalvar在哪里? C
staticGlobalvar在哪里? C
staticvar在哪里? C
localvar在哪里? A
num1 在哪里? A

char2在哪里? A
*char2在哪里? A
pchar3在哪里? A
*pchar3在哪里? D
ptr1在哪里? A
*ptr1在哪里? B

pchar3虽然前面有const,但是也在栈上,因为它是一个指针,中存的是常量abcd的首地址,所以pchar3就是常量的首地址,所以pchar3在常量区。
*ptr1是动态开辟的数组的首地址,所以在堆上。

其实分区分的是生命周期,每个区的生命周期不同。

2、C++内存管理方式

(1)、new/delete操作内置类型

    int* p1 = new int;
	int* p2 = new int[10];

	delete p1;
	delete[] p2;
	//申请对象+初始化
	int* p3 = new int(0);
	int* p4 = new int[10] {0};
	int* p5 = new int[10] {1, 2, 3, 4, 5};//初始化前五个
	
	delete p3;
	delete[] p4;
	delete[] p5;

为什么要设计这个??

#include<iostream>
using namespace std;

class A
{
public:
	A(int a = 0)
		:_a1(a)
	{
		cout << "A(int a)" << endl;
	}

	A(const A& aa)
		:_a1(aa._a1)
	{
		cout << "A(const A& aa)" << endl;
	}

	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
			_a1 = aa._a1;
		}
		return *this;
	}
	void Print()
	{
		cout << "A::Print->" << _a1 << endl;
	}

	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a1 = 1;
};

A f2()
{
	A aa(1);
	return aa;
}

int main()
{
	A* p1 = new A;
	A* p2 = new A(1);
	delete p1;
	delete p2;
	return 0;
}
A(int a)
A(int a)
~A()
~A()

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 7996)已退出,代码为 0。
按任意键关闭此窗口. . .

new对于自定义类型,会自动调用构造函数;delete会自动调用析构函数
这样不仅仅可以申请空间和销毁空间,还调用了构造函数和析构函数,非常方便。

有默认构造,不需要显示给值
	#include<iostream>
using namespace std;

class A
{
public:
	A(int a1 = 0, int a2 = 0)
		:_a1(a1)
		,_a2(a2)
	{
		cout << "A(int a1, int a2)" << endl;
	}

	A(const A& aa)
		:_a1(aa._a1)
	{
		cout << "A(const A& aa)" << endl;
	}

	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
			_a1 = aa._a1;
		}
		return *this;
	}
	void Print()
	{
		cout << "A::Print->" << _a1 << endl;
	}

	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a1 = 1;
	int _a2 = 2;
};

A f2()
{
	A aa(1);
	return aa;
}

int main()
{
	A* p1 = new A(1);
	A* p2 = new A(2, 2);
	A* p3 = new A[3];
	
	return 0;
}
A(int a1, int a2)
A(int a1, int a2)
A(int a1, int a2)
A(int a1, int a2)
A(int a1, int a2)

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 34556)已退出,代码为 0。
按任意键关闭此窗口. . .
没有默认构造,要显示给值
#include<iostream>
using namespace std;

class A
{
public:
	A(int a1, int a2 = 0)
		:_a1(a1)
		, _a2(a2)
	{
		cout << "A(int a1, int a2)" << endl;
	}

	A(const A& aa)
		:_a1(aa._a1)
	{
		cout << "A(const A& aa)" << endl;
	}

	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
			_a1 = aa._a1;
		}
		return *this;
	}
	void Print()
	{
		cout << "A::Print->" << _a1 << endl;
	}

	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a1 = 1;
	int _a2 = 2;
};

A f2()
{
	A aa(1);
	return aa;
}

int main()
{
	A* p1 = new A(1);
	A* p2 = new A(1, 1);
	cout << endl;

	//1、有名构造:这样比较繁琐,还需要定义对象
	A aa1(1, 1);
	A aa2(2, 2);
	A aa3(3, 3);
	A* p3 = new A[3]{ aa1, aa2, aa3 };
	//原本有默认构造不用定义对象,这里没有了所以要定义对象。
	cout << endl;

	//2、匿名构造:不用定义对象,匿名即可
	A* p4 = new A[3]{A(1, 1), A(2, 2), A(3, 3)};
	cout << endl;

	//3、多参数隐式类型转换
	A* p5 = new A[3]{{1, 1}, {2, 2}, {3, 3}};
	//构造加拷贝构造,编译器优化为构造
	return 0;
}
A(int a1, int a2)
A(int a1, int a2)

A(int a1, int a2)
A(int a1, int a2)
A(int a1, int a2)
A(const A& aa)
A(const A& aa)
A(const A& aa)

A(int a1, int a2)
A(int a1, int a2)
A(int a1, int a2)

A(int a1, int a2)
A(int a1, int a2)
A(int a1, int a2)
~A()
~A()
~A()

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 13676)已退出,代码为 0。
按任意键关闭此窗口. . .

(2)、operator new与operator delete函数(重点)

new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。
operator new 实际也是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的。

(3)、new和delete的实现原理

内置类型:
如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:
new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。
自定义类型:
new的原理
1.调用operator new函数申请空间,
为什么用这个??因为malloc失败是返回空的,但是我们不想,想返回异常,并进行处理,所以用这个。
2.在申请的空间上执行构造函数,完成对象的构造
new就是一个加强版的malloc
new去调用operator new,operator new去调用malloc,开空间成功后,再执行构造函数。
delete的原理
1.在空间上执行析构函数,完成对象中资源的清理工作
2.调用operator delete函数释放对象的空间
new T[N]的原理
1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请
2.在申请的空间上执行N次构造函数
delete[]的原理
1.在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
2.调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间

(4)、注意:一定要匹配使用,不要错配!!!

new配delete
new[ ]配delete[ ]
对于内置类型还不怎么明显,但是对于自定义类型来说有的时候就会出错
原因:
编译器在开连续的空间的时候,会在前面额外去开辟字节去存放delete[ ],[ ]中应填的值,如果说该自定义类型没有析构函数,用new[ ] 配delete没有问题,但是该自定义类型如果有析构函数,就会出错,因为在释放空间的时候,没有释放delete[ ] 那一部分的空间,这就属于是从中间销毁空间,没有从头销毁,就会崩溃。

(5)、malloc/free和new/delete的区别

malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地方是:
1.malloc和free是函数,new和delete是操作符
2.malloc申请的空间不会初始化,new可以初始化
3.malloc申请空间时,需要手动计算空阁大小并传递,new只需在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数即可
4.malloc的返回值为void*,在使用时必须强转,new不需要,因为new后跟的是空间的类型5.malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
5.申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new 在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理释放

三、模版

1、函数模版

template<typename T>
//或
template<class T>
//返回值类型 函数名(参数列表){} 

模版参数列表<class 类型1,class类型2…>
函数参数列表(类型 变量1,类型 变量2…)

2、函数模版实例化

用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化。
1.隐式实例化:让编译器根据实参推演模板参数的实际类型

//用函数模版生成对应函数,叫做模版实例化
template<class T>
T Add(const T& left, const T& right)
{
    return left + right;
}
int main()
{
    int al = 10, a2 = 20;
    double d1 = 10.0, d2 = 20.0;
    Add(a1, a2);
    Add(d1, d2);
}    

Add(a1, d2);
该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型,
通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有一个T,
编译器无法确定此处到底该将T确定为int 或者 double类型而报错

解决方法一:强制类型转换

//推导实例化(隐式实例化):让编译器根据实参推演模板参数的实际类型
Add(a1, (int)d2);
Add(d1, (double)a2);
//用参数类型推导函数类型

//显示实例化
Add(a1, d1);
//显示的写出函数类型

解决方法二:再定义一个模版

template<class T1class T2>
T Add(const T1& left, const T2& right)
{
    return left + right;
}

必须用显示实例化的场景

template<class T>
T* func(int n)
{
	return new T[n];
}

这个就没有办法根据参数类型去看函数类型了,就不能用推导实例化,必须用显示实例化
double* d1 = func(10)

例1:

#include<iostream>
using namespace std;

template<class T>
void Swap(T& x, T& y)
{
	T tmp = x;
	x = y;
	y = tmp;
}

int main()
{
	int i = 1;
	int j = 2;
	double m = 1.1;
	double n = 2.2;
	cout << "i:" << i << ",j:" << j << endl;
	cout << "m:" << m << ",n:" << n << endl;
	Swap(i, j);
	Swap(m, n);
	cout << "i:" << i << ",j:" << j << endl;
	cout << "m:" << m << ",n:" << n << endl;
	return 0;
}
i:1,j:2
m:1.1,n:2.2
i:2,j:1
m:2.2,n:1.1

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 6140)已退出,代码为 0。
按任意键关闭此窗口. . .

例2:

当有一个函数模版和一个具体函数同时存在的时候,编译器会优先调用具体函数。

#include<iostream>
using namespace std;

template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}

int Add(const int& x, const int& y)
{
	return (x + y) * 10;
}

int main()
{
	int a1 = 10;
	int a2 = 20;
	cout << Add(a1, a2) << endl;
	return 0;
}
300

C:\Users\Lenovo\source\repos\初识C++\x64\Debug\初识C++.exe (进程 2608)已退出,代码为 0。
按任意键关闭此窗口. . .

PS:template中的T只能表示一种类型,也就是说
void Swap(T& x, T& y)中的x和y是同一个类型的,如果不同类型就会报错,比如说一个int,一个double
而template<class T1, class T2>T1和T2可以是相同类型,也可以是不同类型
这样的话,就可以实现int和double交换了。

3、类模版

template <class T1, class T2, class T3...class Tn>
class 类模版名
{
    //类内成员定义
};

4、类模版实例化

例3:

#include<iostream>
using namespace std;

template<class T>
class Stack
{
public:
	Stack(int n = 4)
		:_array(new T[n])
		, _size(0)
		, capacity(0)
	{

	}

	~Stack()
	{
		delete[] _array;
		_array = nullptr;
		_size = _capacity = 0;

	}

	void Push(const T& x)
	{
		if (_size == _capacity)
		{
			//C++没有和C中realloc一样扩容的,所以需要手动扩
			T* tmp = new T[_capacity * 2];//手动异地扩容
			memcpy(tmp, _array, sizeof(T) * _size);
			delete[] _array;

			_array = tmp;
			_capacity *= 2;
		}
		_array[_size++] = x;
	}

private:
	T* _array;
	size_t _capacity;
	size_t _size;
};

int main()
{
    //类模版都是显示实例化!!!
	Stack<int> st1;//int
	st1.Push(1);
	st1.Push(2);
	st1.Push(3);

	Stack<double> st2;//double
	st2.Push(1.1);
	st2.Push(2.2);
	st2.Push(3.3);
	return 0;
}

C中的typedef不是也能改变类型吗?
为什么要设计这个类模版??

因为C中的typedef只能存在一种类型,如果我想要两种类型,就做不到了。

例4:声明和参数分离时

#include<iostream>
using namespace std;

template<class T>
class Stack
{
public:
	Stack(int n = 4)
		:_array(new T[n])
		, _size(0)
		, capacity(0)
	{

	}

	~Stack()
	{
		delete[] _array;
		_array = nullptr;
		_size = _capacity = 0;

	}

	void Push(const T& x);

private:
	T* _array;
	size_t _capacity;
	size_t _size;
};

template<class T>
void Stack<T>::Push(const T& x)
{
	if (_size == _capacity)
	{
		//C++没有和C中realloc一样扩容的,所以需要手动扩
		T* tmp = new T[_capacity * 2];//手动异地扩容
		memcpy(tmp, _array, sizeof(T) * _size);
		delete[] _array;

		_array = tmp;
		_capacity *= 2;
	}
	_array[_size++] = x;
}

int main()
{
	Stack<int> st1;//int
	st1.Push(1);
	st1.Push(2);
	st1.Push(3);

	Stack<double> st2;//double
	st2.Push(1.1);
	st2.Push(2.2);
	st2.Push(3.3);
	return 0;
	//st1和st2是在栈上的
}

void Stack::Push(const T& x)这样指定类域不行
定义的模版只能给当前的类或函数用,每个函数或类都要定义自己的模版,所以声明和定义分离时,要再写一次template
而且在模版这里是不支持把声明和定义放在两个文件里。
注意:
模版参数只是一个代称!!
例:
这样写,会调用delete
Stack* pst = new Stack//pst也在栈上,pst指向的在堆上
//…
delete pst;
上面的Stack st1;和Stack st2;不用调用delete
为什么会调用delete??
pst指向一个栈对象(12个字节,T* _array;size_t _capacity;size_t _size;)这个是operator new出来的,operator new之后会调用构造函数,构造函数会再new一个数组给array。
delete pst;先调用析构函数,把开辟的数组里的资源清理了,释放数组,然后再调用operator delete释放栈对象。

  • 8
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值