C++模板初阶
目录
一、函数模板
1.函数模板的概念
2.函数模板的定义
3.函数模板的原理
4.函数模板的实例化
①隐式实例化
②显式实例化
5.非模板函数与同名的函数模板同时存在
6.模板参数的匹配原则
二、类模板
1.类模板的定义格式
2.采用类模板的类外函数的定义格式
3.类模板的使用(以顺序表[部分功能]为例)
4.类模板的实例化
一、函数模板
1.函数模板的概念
- 函数模板不是一个实在的函数,编译器不能为其生成可执行代码。定义函数模板后只是一个对函数功能框架的描述,当它具体执行时,将根据传递的实际参数决定其功能。
- 函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
template<typename T>
T Add(T a, T b) {
return a + b;
}
2.函数模板的定义
template<typename T1, typename T2,…,typename Tn>
注意: typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替class)。
示例如下:
//...
void swap(T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}
//...
int a = 10;
int b = 20;
swap(a, b);
std::cout << a << " " << b << std::endl;
double c = 1.6;
double d = 2.4;
swap(c, d);
std::cout << c << " " << d << std::endl;
char e = 'a';
char f = 'b';
swap(e, f);
std::cout << e << " " << f << std::endl;
结果:
20 10
2.6 1.2
b a
3.函数模板的原理
在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此。
4.函数模板的实例化
函数模板实例化是指在使用函数模板时,将其具体化为一个实际的函数。在函数模板的定义中,我们使用了一些通用的类型或值作为函数参数,这些类型或值在函数被调用时才被具体指定,从而形成实际的函数。
函数模板参数实例化可以分为两种方式:隐式实例化和显式实例化。
①隐式实例化
隐式实例化:一种让编译器根据函数模板调用时的实参类型来推导模板参数类型的机制。
注:隐式类型转换和强制类型转换不会改变原有的类型,只是生成了一个临时变量。
代码如下:
template<class T>
void print(const T& input)
{
std::cout << input << std::endl;
}
int main()
{
print(42);
print("Hello World!");
return 0;
}
输出:
42
Hello World!
注
由于模板实例化,所以不同参数类型所调用函数的地址各不同。
②显式实例化
显式实例化:代码中显式地要求编译器实例化某个函数模板的特定实例。
如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
int main()
{
std::cout << Add(1, 2) << std::endl;
std::cout << Add<int>(1, 2.2) << std::endl; //2.2被隐式类型转换为2
std::cout << Add<int>(1, "123") << std::endl; //"123"无法被隐式转换,会直接报错
return 0;
}
5.非模板函数与同名的函数模板同时存在
对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板。
// 专门处理int的加法函数
int Add(int left, int right)
{
return left + right;
}
// 通用加法函数
template<class T>
T Add(T left, T right)
{
return left + right;
}
void Test()
{
std::cout << Add(1, 2) << std::endl; // 与非模板函数匹配,编译器不需要特化
std::cout << Add<int>(1, 2) << std::endl; // 调用编译器特化模板的Add版本
Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数
}
int main()
{
Test();
return 0;
}
6.模板参数的匹配原则
-
精确匹配:如果实参类型与模板参数类型相同,则精确匹配成功。
-
隐式转换匹配:如果实参类型可以隐式转换为模板参数类型,则匹配成功。具体而言,如果实参类型与模板参数类型之间存在以下一种或多种转换关系,则匹配成功:
-
数组或函数类型指针可以转换为相应的指针类型。
-
派生类指针可以转换为基类指针。
-
除非模板参数是引用类型,否则指针或整数类型可以相互转换。
-
枚举类型可以转换为整数类型,反之亦然。
-
单精度浮点类型可以转换为双精度浮点类型,反之亦然。
-
实参类型可以通过用户定义的类型转换函数转换为模板参数类型。
-
-
非类型模板参数匹配:如果模板参数是一个非类型参数(例如整数常量),则实参可以是一个常量表达式,其值可以在编译时计算得出,并且与模板参数类型匹配。
二、类模板
1.类模板的定义格式
template<class T1, class T2, …, class Tn>
class 类模板名
{
// 类内成员定义和声明
};
2.采用类模板的类外函数的定义格式
template<class T1, class T2, …, class Tn>
返回类型 类模板名<T1, T2, …, Tn>::函数名称(参数)
{
// 类外函数的定义
}
3.类模板的使用(以顺序表[部分功能]为例)
template<class T>
class SeqList
{
public:
SeqList(size_t capacity = 10)
: _capacity(capacity)
, _size(0)
, _array(new T[capacity])
{}
// [fist, last]区间中包含的元素要放置到顺序表中
SeqList(T* first, T* last);
~SeqList();
void PushBack(const T& x);
void PopBack();
size_t Capacity() const;
size_t Size() const;
private:
void CheckFull()
{
if (_size == _capacity && _capacity != 0)
{
T* tmp = new T[_capacity * 2];
memcpy(tmp, _array, sizeof(T) * _capacity);
delete[](_array);
_array = tmp;
_capacity *= 2;
}
}
bool CheckEmpty() const
{
return _size == 0;
}
private:
size_t _capacity;
size_t _size;
T* _array;
};
template<class T>
SeqList<T>::SeqList(T* first, T* last)
{
T* it = first;
size_t count = 0;
while (it++ != last + 1) count++;
_size = count;
_capacity = count;
_array = new T[_capacity];
for (int i = 0; i < _size; ++i)
{
_array[i] = *(first++);
}
}
template<class T>
SeqList<T>::~SeqList()
{
if (_array)
{
delete[] _array;
_array = nullptr;
_size = 0;
_capacity = 0;
}
}
template<class T>
void SeqList<T>::PushBack(const T& x)
{
CheckFull();
_array[_size++] = x;
}
template<class T>
void SeqList<T>::PopBack()
{
if (CheckEmpty())
{
std::cout << "The SeqList is empty!" << std::endl;
return;
}
_size--;
}
template<class T>
size_t SeqList<T>::Capacity() const
{
return _capacity;
}
template<class T>
size_t SeqList<T>::Size() const
{
return _size;
}
int main()
{
int a[10]{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
SeqList<int> s1(&a[0], &a[9]);
std::cout << s1.Capacity() << std::endl;
std::cout << s1.Size() << std::endl;
s1.PushBack(1);
s1.PushBack(1);
s1.PushBack(1);
s1.PopBack();
std::cout << s1.Capacity() << std::endl;
std::cout << s1.Size() << std::endl;
return 0;
}
输出:
10
10
20
12
4.类模板的实例化
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
// Vector类名,Vector才是类型
Vector s1;
Vector s2;