「C++」初识模板
目录
函数模板
概念:
使用方法:
同名函数与模板函数的选择
类模板
泛型编程
模板原理
模板实例化
后记
哈喽大家好啊~这里是小鸥的C++频道~
在上一篇我们了解了内存管理在C++中的相关内容,今天我们将简单的来学习一下C++中模板相关的内容。
本期专栏:C++_海盗猫鸥的博客-CSDN博客
个人主页:海盗猫鸥-CSDN博客
欢迎大家关注喔~~~
函数模板
概念:
模板顾名思义,函数模板就是一个与类型无关的代码块,在使用时,编译器将模板中的类型替换为需要的类型,从而使一个模板可以为多种类型的参数使用
使用方法:
template<typename/class 类型名1, typename/class 类型名2, ...>
//函数模板
//关键字:template
//使用方法:template<typename/class 类型名1, typename/class 类型名2, ...>
template<typename T>
void Swap(T& x1, T& x2)
{T tmp = x1;x1 = x2;x2 = tmp;
}
void func1(const T1& x1, const T2& x2)
{cout << x1 << "\t" << x2 << endl;
}//使用函数模板,可以将结构相同,参数类型不同的函数统一化,省去不必要的重复劳动
//这种编程方法,称为范型编程int main()
{int a = 1;int b = 2;Swap(a, b);cout << a << "\t" << b << endl;char c = 'A';char d = 'B';Swap(c, d);cout << c << '\t' << d << endl;func1(1, 2.2);//直接传常值作为参数,又因为func函数参数为引用,所以必须加const,因为临时变量具有常性return 0;
}
typename是用来定义模板参数关键字,也可以使用class
同名函数与模板函数的选择
1. 一个非模板函数可以和一个同名的模板函数同时存在,且模板函数也可以实例化出与该非模板函数相同的函数;
2. 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个参数具有更好匹配性的函数, 那么将选择模板
3. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
//只用于int类型相加
int Add(int x1, int x2)
{return x1 + x2;
}template<class T>
//模板相加函数
T Add(const T& x1, const T& x2)
{return x1 + x2;
}template<class T1,class T2>
T1 Add(const T1& x1, const T2& x2)
{return x1 + x2;
}int main()
{Add(1, 2);//优先选择非模板函数Add<int>(1, 2);//选择模板函数Add(1, 2.0);//选择第二个模板函数return 0;
}
类模板
以栈部分为示例:
template<class T>
class Stack
{
public:Stack(int n = 4):_array(new T[n]),_size(0),_capacity(n){};~Stack(){delete[] _array;_array = nullptr;_size = _capacity = 0;}void Push(const T& x){//检测空间是否足够if (_size == _capacity){T* tmp = new T[_capacity * 2];memcpy(tmp, _array, sizeof(T) * _size);delete[] _array;_array = tmp;_capacity *= 2;}_array[_size++] = x;}//...此处省略~~~
private:T* _array;int _size;int _capacity;
};int main()
{//类模板只能显示实例化Stack<int> st1;st1.Push(1);st1.Push(1);st1.Push(1);Stack<double> st2;st2.Push(1.1);st2.Push(1.1);st2.Push(1.1);return 0;
}
由上述代码可知:
C++中的栈,就可以直接通过一个模板来实现,直接满足不同类型的栈的需求
C++中的扩容需要手动扩容。
注意(错误点):
不改变参数时,添加const是一个好习惯
由上述模板内容我们可以引出一个新的概念:
泛型编程
在不使用模板时,虽然函数重载就可以解决部分如上述如Swap函数那样,需要多个相同功能的函数的相关问题,但终究还是需要多次重复的书写出相同的代码。
重载函数实现Swap的问题:
- 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数
- 代码的可维护性比较低,一个出错可能所有的重载均出错
而我们使用模板后,就相当于有了一个模板,就像活字印刷术的发明一样,相同的东西不再需要我们重复的书写。
像模板这样通用的代码,我们称为泛型编程:
泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。
模板原理
上述不论函数模板还是类模板,都只是为一个蓝图,就像造房子需要蓝图。但对于模板来说,“造房子”的过程是交给编译器来完成的,不再需要我们动手。
我们只需让编译器知道我们要的“房子”是什么类型的即可~
而要告诉编译器我们需要的类型,则有两种不同的方法:
模板实例化
由于模板只是一个“蓝图”,我们是不能直接使用的,就像类需要实例化为对象使用一样,模板也需要实例化为实际的函数或者类,才能进行进一步的使用。
而模板实例化的方法有两种:
1. 隐式实例化:让编译器根据实参推演模板参数的实际类型
就如上文Swap函数一样,由于Swap函数的参数中带有我们定义的类型T,所以在调用Swap时,编译器就可以自动推导出T的类型,从而实例化出我们需要的类型的函数。
template<class T>
void Swap(T& x1, T& x2)
{T tmp = x1;x1 = x2;x2 = tmp;
}
int main()
{int a = 1;int b = 2;//Swap<int>(a,b)Swap(a, b);char ch1 = 'A';char ch2 = 'B';Swap(ch1, ch2);double d1 = 1.1;double d2 = 2.2;Swap(d1, d2);return 0;
}
2. 显式实例化:在函数名后的<>中指定模板参数的实际类型
在上文类模板中我们就提及,类模板是不能通过第一种方法来进行实例化的,只能进行显示实例化
而显示实例化的方法就是在类或者函数名的后面紧跟一个<类型名>;
template<class T>
class Stack
{
public:Stack(int n = 4):_array(new T[n]),_size(0),_capacity(n){};~Stack(){delete[] _array;_array = nullptr;_size = _capacity = 0;}void Push(const T& x){//检测空间是否足够if (_size == _capacity){T* tmp = new T[_capacity * 2];memcpy(tmp, _array, sizeof(T) * _size);delete[] _array;_array = tmp;_capacity *= 2;}_array[_size++] = x;}//...
private:T* _array;int _size;int _capacity;
};int main()
{//类模板只能显示实例化Stack<int> st1;st1.Push(1);st1.Push(1);st1.Push(1);Stack<double> st2;st2.Push(1.1);st2.Push(1.1);st2.Push(1.1);Stack<int>* pst = new Stack<int>;//类模板只能显示实例化return 0;
}
后记
那么本篇博客的内容就到这里结束啦~
欢迎大家指出我的错误与不足,我们下篇再见~