【CPP】CPP的模板(前篇)

这里是oldking呐呐,感谢阅读口牙!先赞后看,养成习惯!
个人主页:oldking呐呐
专栏主页:深入CPP语法口牙

11 C++的模板(前篇)

11.1 什么是模板
  • 模板是分割C和C++的重要的分水线,决定了C语言注定不能取代C++

  • 设计模板一般咱称作为泛型编程,泛型编程即通过数据类型编写代码,使得同一段代码能够不断复用,提高了代码的复用性

  • 模板一般分为

    • 函数模板
    • 类模板
11.2 函数模板
  • 假设我们需要写很多函数重载,如以下场景
void Swap(int& x, int& y)
{
	int tmp = x;
	x = y;
	y = tmp;
}

void Swap(double& x, double& y)
{
	double tmp = x;
	x = y;
	y = tmp;
}

//...

  • 虽然函数重载避免了函数重名的问题,但开发起来还是很麻烦,还是要手动一个个挫,所以,模板横空出世!
template<class T>//模板的开头
void Swap(T& x, T& y)//T是模板内的类型,未来会被替换成其他类型
{
	T tmp = x; //其他地方像写正常函数一样写就行
	x = y;
	y = tmp;
}
  • 模板就是一个蓝图,蓝图上有一些细节内容(即需要被替换的类型)需要手动编辑,大体框架不变
  • 即,此时我想让这个函数变成交换int类型的函数,只要替换掉模板里面占位的类型就行,我们称替换掉占位的过程称为"XX类型推演",比方在这个例子中,就是"int类型推演"
  • 此时这个函数就构建完了,就已经能够正常调用了
int main()
{
	int x = 1, y = 2;
	double m = 1.1, n = 2.2;

	cout << x << " " << y << endl;
	
	Swap(x, y);
	cout << x << " " << y << endl;
	
	cout << m << " " << n << endl;
	
	Swap(m, n);
	cout << m << " " << n << endl;

	return 0;
}

请添加图片描述

  • 咱如果此时打开反汇编
    请添加图片描述

请添加图片描述

  • 不难发现,这俩函数实际在调用的时候根本就不是同一个函数
11.2.1 函数模板的格式
//函数模板一般就是template后加尖括号,尖括号里放变量类型
//C++从C98开始支持关键字 typename
//以下把class替换成 typename 也可以,两者使用没有差异
//类型可以不止一个,可以有很多很多个
template<class Type1, class Type2>
void func1(const Type1& a, Type2* pb)
{
	//...
}

int main()
{
	int x = 1;
	double m = 2;

	//调用上,按咱写的模板传值就行了
	//编译器会自定识别传进去的参数的类型
	func1(x, &m);

	return 0;
}
  • 其实也不难发现,函数模板其实像是生成函数的函数,区别在于,函数括号里放的是形参,传进来的是具体的参数,模板括号里放的是类型,传进来的是类型
11.2.2 模板实例化
  • 我们写出一个模板出来,编译器实际使用了这个模板并且生成了一个函数出来,我们就称这个过程为模板实例化,而实例化又有两种分类

  • 对于上面写的Swap()函数,因为类型只有一个,所以调用的时候两个参数的类型必须要相同
    请添加图片描述

  • 现在传了两种类型的参数进来,编译器不知道应该用哪个类型,所以报错

  • 但如果我们一定要传两个不同类型的值进去,为了解决这个问题,我们可以试图这么办

  • (以下是错误代码!!!)

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

int main()
{
	int x = 1;
	double m = 2;

	Swap((double)x, m);

	return 0;
}
  • 咱们传不了,强转嘛,但强转也会伴随着问题,强转之后生成的对象是临时对象,临时对象具有常性,所以要把模板里函数的参数加上const,但加上const又会面临不能修改的问题,就很头疼

请添加图片描述

  • 我们一般称这种为了适配模板而被迫强转形参类型,然后通过模板成功生成函数的过程称为推导实例化(通过模板生成函数依旧是编译器自动执行生成的)

  • 所以说,推导实例化一般用在只读的场景,比方说以下场景

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

int main()
{
	int x = 1;
	double m = 1.1;

	cout << Add((double)x, m) << endl;

	return 0;
}

请添加图片描述

  • 还有一种实例化方式,我们还是拿上面Add()的例子
int main()
{
	int x = 1;
	double m = 1.1;

	cout << Add<int>(x, m) << endl;

	return 0;
}

请添加图片描述

  • 这种实例化方式也是可以的

  • 我们之前说,模板是通过判断传进来的参数类型来生成函数的,在Add()的例子中,编译器因为传进来两个不同类型的参数,不能判断应该用哪个类型生成函数,于是我们可以手动指定编译器生成指定的函数,就是以上的方式

  • 在尖括号中像填参数一样填入指定的类型,如果有多个类型就填多个类型,编译器会根据你填的参数来生成函数

  • 通过尖括号手动填参数指定编译器生成函数的操作称为显示实例化

  • 如果实在不想使用显式实例化或者推导实例化,也可以直接在写模板的时候写两个类型,这样生成函数的时候就不会判断有没有冲突了

  • 有些时候,是必须要用显式实例化的,例如以下这个模板

template<class T>
T Func1(int x)
{
	return (T)x;
}
  • 在这个模板里,编译器没法根据传进来的参数判断T的类型是什么,如果直接调用就会报错

请添加图片描述

  • 所以此时一定要显式实例化
int main()
{
	int x = 1;

	Func1<double>(x);

	return 0;
}
11.2.3 模板与函数同名
  • 如以下情景
int Add(const int& x, const int& y)
{
	cout << "int Add(const int& x, const int& y)" << endl;
	return (x + y);
}

template<class T>
T Add(const T& x, const T& y)
{
	cout << "T Add(const T& x, const T& y)" << endl;
	return (x + y);
}

int main()
{
	int x = 1, y = 2;
	double m = 1.1, n = 2.2;

	cout << Add(x, y) << endl; //调用函数
	cout << Add(m, n) << endl; //用模板生成函数

	return 0;
}
  • 有现成的函数,编译器就会调现成的函数,没有现成的函数,编译器就会用模板生成

请添加图片描述

11.2.4 函数模板的意义
  • 函数重载,是为了让函数调用起来更加多元,更加灵活,各个重载之间大体方向差不多,或者说功能类似,咱用函数重载

  • 但模板就不太一样,两个不同参的函数调用之间的差别仅有参数类型不同,函数功能,执行方向完全一样

  • 可以说灵活度提升,意味着开发效率,代码量的增加,要代码量少,开发效率快,就必然要在灵活程度上做取舍(建立在同等效率,稳定性相同的情况下)

11.3 类模板
11.3.1 类模板书写格式
  • 类模板和函数模板书写方式差不多,比方这里我就写了一个Stack模板
template<class T>
class Stack
{
public:
	Stack(int n = 4)
		:_arr(new T[n])
		,_size(0)
		,_capa(n)
	{}

	~Stack()
	{
		delete[] _arr;
		_arr = nullptr;

		_size = 0;
		_capa = 0;
	}

	bool isEmpty()
	{
		return _size == 0 ? true : false;
	}

	void Push(const T& x)
	{
		if (_size == _capa)//CPP里扩容需要手动扩容
		{
			T* tmp = new T[_capa * 2];
			memcpy(tmp, _arr, _capa * 2 * sizeof(T));

			delete[] _arr;
			_arr = tmp;

			_capa *= 2;
		}

		_arr[_size++] = x;
	}

	const T& Top()
	{
		if (isEmpty())
		{
			cout << "Stack is empty!" << endl;
		}
		return _arr[_size - 1];
	}

	void Pop()
	{
		if (!isEmpty())
		{
			_size--;
		}
		else
		{
			cout << "Stack is empty!" << endl;
			return;
		}
	}
	//...

private:
	T* _arr;
	int _capa;
	int _size;
};

int main()
{
	Stack<int> st1; //类模板只能显式实例化
	Stack<double> st2;

	//Stack<int>是一个类型,Stack<double>是另一个类型,这俩不是同一个类型

	st1.Push(1);
	st1.Push(2);
	st1.Push(3);
	st1.Push(4);

	cout << st1.Top() << endl;
	st1.Pop();
	cout << st1.Top() << endl;

	st2.Push(1.1);
	st2.Push(2.2);
	st2.Push(3.3);
	st2.Push(4.4);

	cout << st2.Top() << endl;
	st2.Pop();
	cout << st2.Top() << endl;
	st2.Pop();
	st2.Pop();
	st2.Pop();
	cout << st2.Top() << endl;

	return 0;
}

请添加图片描述

  • 类模板只能显示实例化,因为编译器不知道该怎么判断你要的类型,干脆就你自己手动传类型进来吧
11.3.1 类模板内成员函数的声明和定义的拆分
template<class T>
class Stack
{
public:
	Stack(int n = 4)
		:_arr(new T[n])
		,_size(0)
		,_capa(n)
	{}

	~Stack()
	{
		delete[] _arr;
		_arr = nullptr;

		_size = 0;
		_capa = 0;
	}

	bool isEmpty()
	{
		return _size == 0 ? true : false;
	}

	void Push(const T& x);

	const T& Top()
	{
		if (isEmpty())
		{
			cout << "Stack is empty!" << endl;
		}
		return _arr[_size - 1];
	}

	void Pop()
	{
		if (!isEmpty())
		{
			_size--;
		}
		else
		{
			cout << "Stack is empty!" << endl;
			return;
		}
	}
	//...

private:
	T* _arr;
	int _capa;
	int _size;
};

template<class T>
void Stack<T>::Push(const T& x) //尖括号里要注明Stack的T类型和这个T类型是同一个类型(话句话说,既然这里的T和Stack的T不是同一个T,那我换成别的什么X啥的都是没问题的,当然这种换名字的操作肯定不建议哈)
{
	if (_size == _capa)
	{
		T* tmp = new T[_capa * 2];
		memcpy(tmp, _arr, _capa * 2 * sizeof(T));

		delete[] _arr;
		_arr = tmp;

		_capa *= 2;
	}

	_arr[_size++] = x;
}
  • 一个模板声明,其声明的类型只能在一个函数或者类里用,所以函数声明和定义分离只能另声明模板

  • 模板不支持声明和定义分离到两个文件,这么写会报链接错误

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值