今天我们来谈谈C++中有关于模板的知识😊😊😊,对于C++模板来说,我们首先得了解以下几个术语
- 函数模板
- 模板函数
- 模板实例化
- 模板特例化
- 模板的实参推演
- 模板的非类型参数
- 非模板函数
- 类模板
- 模板类
- 选择性实例化
下面,我们将对这几个有关于模板的术语进行详述的论述
浅谈C++的模板—— 这一篇就够了
- 一、函数模板
- 二、模板实例化、实参推演、非模板函数
- 三、模板特例化、非类型参数
- 四、类模板、模板类、选择性实例化
- 五、模板代码的使用方法
一、函数模板
函数模板是一种通用的函数定义,可以用来表示一类相似的函数。它可以使用泛型编程技术来编写通用的代码,使得函数能够适应不同的数据类型。通过在函数体中使用模板参数,可以实现对不同数据类型进行相同的操作,具体使用方式如下代码:
template<typename T>
返回类型 函数名(参数列表)
{ //函数体
}
其中,template
是关键字,也可以用 class
代替,用于声明一个函数模板,<typename T>
是模板参数列表,可以在其中指定一个或多个类型参数,用于指定将要使用的数据类型,T
是类型参数的名称,可以根据需要自定义。
📢📢📢需要注意:函数模板定义仅仅只是模板的声明,不是实际的函数定义,实际的函数定义是在函数模板被实例化时,根据具体的模板参数生成的,编译时由于参数类型不确定,编译器是不会对它进行编译的
优点:
- 提高代码的重用性和可维护性,减少代码冗余。它可以通过一次定义和实现,实现对多种数据类型的支持。
缺点:
- 函数模板会增加编译时间,并且对于复杂的模板参数,可能存在性能损失的问题。因此,在使用函数模板时需要权衡其优缺点,并选择合适的方式来应用。
使用函数模板时,可以通过显示实例化或隐式实例化来生成具体函数
- 显示实例化是指在编译时将模板参数替换为具体的类型或值,即
compare<指定的类型>(各种参数)
- 隐式实例化是在调用函数时由编译器自动推断参数类型,即
compare(各种参数)
二、模板实例化、实参推演、非模板函数
模板实例化: 指编译器根据特定的类型参数将函数模板生成一个具体的函数或者类的过程
实参推演: 在函数调用过程中,根据函数参数的类型和传递给函数的实际参数的值推断出函数模板参数或自动推导模式类型的过程
非模板函数: 没有使用template<...>
下面给出一段代码:
template<typename T> //定义一个模板参数列表 typename/class也可以
bool compare(T a, T b) //compare是一个函数模板
{cout << "template compare" << endl;return a > b;
}//template<typename T>
//bool compare<int>(int a, int b)
//{
// cout << "template compare" << endl;
// return a > b;
//}int main()
{compare<int>(10, 20);return 0;
}
当主函数执行到compare函数的调用点时,编译器会根据用户指定的类型,从原函数模板实例化出来一个函数代码,也就是代码中被注释的模板函数,供主函数正常使用
但是在这里,我们会有个疑问:如若我们不指定对应的类型<T>
呢 ?
template<typename T>
.... compare bool compare(const char* a, const char* b)
{
... //非模板函数 -》 普通函数
}int main()
{compare(10, 40.2); //编译器会找不到对应的模板参数列别,报错compare<int>(10.5, 20.6); //指定参数int,强制转化(显示)//没有指定参数列表,自动推导出 const char*,但存在非模板函数的对应函数定义,直接使用非模板函数compare("aaa", "bbb");return 0;
}
三、模板特例化、非类型参数
当我们给上述的函数模板传入的类型为 const char*
(这里仅作演示,其他类型某些也可以),准备让它实例化一个模板函数用于字符串的比较时,经过程序的执行,会直接比较内存地址的大小,并不是我们想要的字符串比较,即处理逻辑有误,此时我们就需要针对原函数模板,额外提供一个const char*
类型的特例化版本,以独立的完成正常的处理逻辑,代码如下
// 模板的非类型参数
template<typename T, int SIZE>
void func(T *a){} //针对函数模板,提供const char* 类型的特例化版本
template<>
bool compare<const char*>(const char* a, const char* b>
{cout << "compare<const char*>" << endl;return strcmp(a, b) > 0;
}
此时当程序再次想要使用对应的传入参数为const char*
的模板函数时,编译器会直接找到模板的特例化版本,执行正确的字符串比较处理逻辑
四、类模板、模板类、选择性实例化
C++类模板: 根据不同的数据类型生成具体的类
模板类: 通过类模板创建的具体类实例
选择性实例化: 在编译时只生成使用过的具体类实例,而不生成未使用的实例
下面给出实现栈的代码:
#include<iostream>
using namespace std;
//类模板进行选择性实例化 -> 模板类 template<typename T = int>
class SeqStack //模板名称 + 类型参数列表 = 类名称
{
public://构造函数、析构函数可以不加<T>SeqStack(int size = 10) :_pstack(new T[size]), _top(0), _size(size){cout << this << "SeqStack()" << endl;}~SeqStack() {cout << this << "~SeqStack()" << endl;delete[]_pstack;_pstack = nullptr;}SeqStack(const SeqStack<T>& stack) //拷贝构造:_top(stack._top), _size(stack._size){_pstack = new T[_size];for (int i = 0; i < _top; i++){_pstack[i] = stack._pstack[i];}}SeqStack<T>& operator=(const SeqStack<T>& stack) //赋值重载{if (&stack == this) return *this; delete[] _pstack;_top = stack._top;_size = stack._size;_pstack = new T[_size];for (int i = 0; i < _top; i++){_pstack[i] = stack._pstack[i];}}void push(const T& val);void pop(){ if (empty()) return;--_top;}T top() const{if (empty())throw "stack is empty !!!"; return _pstack[_top - 1];}bool full() const { return _top == _size; }bool empty() const { return _top == 0; }private:int* _pstack;int _top;int _size;void expand(){T* Ptmp = new T[_size * 2];for (int i = 0; i < _top; i++){Ptmp[i] = _pstack[i];}delete[]_pstack;_pstack = Ptmp;_size *= 2;}
};template<typename T>
void SeqStack<T>::push(const T& val) //类外定义类模板的成员方法
{if (full()) expand();_pstack[_top++] = val;
}int main()
{//类模板的选择性实例化SeqStack<int> s(10);s.push(10);s.pop();SeqStack<> s1(10); //使用默认模板类型参数return 0;
}
五、模板代码的使用方法
一般来说,模板的定义常常是放在头文件中的,因为模板的实现需要在编译时进行,并非在链接时。当我们在源文件中包含头文件时,编译器会将头文件的内容复制到源文件中,根据需要实例化模板
但是如果我们在源文件中直接进行模板的声明而不是使用头文件,那么编译器将无法找到模板定义的实现,导致链接错误
- 模板代码不能在一个文件中定义,在另一个文件使用的
- 在模板代码被调用之前,必须能够找到模板定义的位置,才能进行正常的实例化
如果直接在定义文件中声明,告诉编译器,就能直接指定类型的模板实例化,但是不推荐!!!
🌻🌻🌻以上就是有关浅谈C++的模板—— 这一篇就够了的内容,如果聪明的你浏览到这篇文章并觉得文章内容对你有帮助,请不吝动动手指,给博主一个小小的赞和收藏 🌻🌻🌻