20、模板初探
学C++的朋友应该都知道多态,多态分为运行时多态和编译时多态,其中模板可以粗浅理解为是编译时多态的其中一种方式。
模板是一种通用的代码框架,允许在编写代码时使用占位符(通常是类型或值),在实际使用时再指定具体的类型或值。使用模板进行泛型编程,可以认为是用一种无关于特定类型的方式来编写代码。
模板大体分为函数模板与类模板,其中还有些主要的特性,比如全特化、偏特化、SFINAE等。
模板的声明以关键字template
开头,后跟模板参数列表。模板参数可以是类型参数(typename
或class
)或非类型参数(如整数、指针等)。
这篇文章主要介绍下函数模板与类模板的使用。
函数模板
函数模板与类模板的声明比较类似,都是使用template
关键字,还是直接看示例代码吧。
示例
template <typename T>
T Max(T const& a, T const& b) {
return a > b ? a : b;
}
int main() {
std::cout << Max(1, 2) << std::endl;
std::cout << Max(1.2, 2.3) << std::endl;
std::cout << Max(std::string("1.2"), std::string("2.3")) << std::endl;
}
T
起到一个类似占位符的作用,使用时传入的是什么类型,它就会规定T
就是这个类型。
template
用于声明这是一个模板,typename
用来表明T
是一个泛型类型。也有很多模板的声明不是使用typename
,而是使用的class
,区别不大。
大体语法如下:
template<typename type>
ret-type funcname(parameter list) {
// function
}
类模板
再举一个类模板的示例:
template <typename T>
class Queue {
public:
void Push(const T& t) { elems.emplace_back(t); }
T& Front() { return elems.front(); }
void PopFront() { elems.erase(elems.begin()); }
size_t Size() { elems.size(); }
bool Empty() { return elems.empty(); }
private:
std::vector<T> elems;
};
int main() {
Queue<int> q;
q.Push(1);
q.Push(2);
while (!q.Empty()) {
std::cout << q.Front() << std::endl;
q.PopFront();
}
std::cout << "=========== \n";
Queue<float> q2;
q2.Push(1.1);
q2.Push(2.1);
while (!q2.Empty()) {
std::cout << q2.Front() << std::endl;
q2.PopFront();
}
}
觉得上面的示例复杂,可以看下面,这个示例可能更简单一些:
template <typename P, typename Q>
class A {
public:
A(P p, Q q) : p_(p), q_(q) {}
void Print() { std::cout << "p " << p_ << " q " << q_ << std::endl; }
private:
P p_;
Q q_;
};
int main() {
A<int, double> a(1, 3.3);
a.Print();
A<int, std::string> b(2, "dsds");
b.Print();
}
这里可以看到,类模板的格式类似于下面这样:
template <typename type>
class classname {};
再看下默认模板参数:
就像函数可以有默认参数一样,模板也可以有默认参数,比如:
#include <iostream>
#include <string>
template <typename P, typename Q = int>
class A {
public:
A(P p, Q q) : p_(p), q_(q) {}
void Print() { std::cout << "p " << p_ << " q " << q_ << std::endl; }
private:
P p_;
Q q_;
};
int main() {
A<int, double> a(1, 3.3);
a.Print();
A<int, std::string> b(2, "dsds");
b.Print();
A<int> c(1, 2);
c.Print();
}
模板实例化
模板的实例化是指编译器根据模板生成具体的函数或类的过程。模板实例化可以是隐式的(由编译器自动完成)或显式的(由程序员指定)。
隐式实例化
int a = max(3, 5); // 编译器隐式实例化max<int>
double b = max(3.5, 2.5); // 编译器隐式实例化max<double>
显式实例化
template int max<int>(int, int); // 显式实例化max<int>
template double max<double>(double, double); // 显式实例化max<double>
模板特化
模板的特化是指为特定的类型或值提供特殊的实现。特化分为全特化和偏特化。
- 全特化是指为模板的所有参数指定具体的类型或值。
- 偏特化是指为模板的部分参数指定具体的类型或值。
看模板全特化和偏特化的示例代码。
函数模板全特化:
template <typename T>
T Max(T const& a, T const& b) {
std::cout << "Max a b \n";
return a > b ? a : b;
}
template <>
int Max(int const& a, int const& b) {
std::cout << "Max a b full specialization \n";
return a > b ? a : b;
}
int main() {
std::cout << Max(1, 2) << std::endl;
std::cout << Max(1.2, 2.3) << std::endl;
std::cout << Max(std::string("1.2"), std::string("2.3"));
}
输出:
Max a b full specialization
2
Max a b
2.3
Max a b
2.3
类模板全特化
template <>
class A<std::string, std::string> {
public:
A(std::string p, std::string q) : p_(p), q_(q) {}
void Print() { std::cout << "std::string p " << p_ << " q " << q_ << std::endl; }
private:
std::string p_;
std::string q_;
};
int main() {
A<int, double> a(1, 3.3);
a.Print();
A<int, std::string> b(2, "dsds");
b.Print();
A<std::string, std::string> c("hello", "dsds");
c.Print();
}
类模板偏特化
template <typename P>
class A<P, std::string> {
public:
A(P p, std::string q) : p_(p), q_(q) {}
void Print() { std::cout << "partial specialization p " << p_ << " q " << q_ << std::endl; }
private:
P p_;
std::string q_;
};
注意:
类模板可以全特化,也可以偏特化
函数模板只能全特化,不可以偏特化,想要达到偏特化效果,直接使用函数重载就好了。
编译器优先匹配特化的类型,然后才会找通用模板做匹配。
这里只介绍了基础的模板操作,模板还有很多高级玩法,比如成员函数模板,内部类模板等,不过这些都不常见,感兴趣的可以看https://en.cppreference.com/w/cpp/language/template_specialization
还有个有意思的知识点,不只可以为普通类型使用using起别名,其实还可以给模板起别名,也是使用using,具体可以看https://en.cppreference.com/w/cpp/language/type_alias
除了函数模板和类模板,在C++14中还引入了变量模板,但是不常用,感兴趣的可以看https://en.cppreference.com/w/cpp/language/variable_template
推荐阅读
参考链接
- https://en.cppreference.com/w/cpp/language/partial_specialization
- https://en.cppreference.com/w/cpp/language/template_specialization
- https://en.cppreference.com/w/cpp/language/templates
练习
- 实现一个通用的
Pair
类模板,包含两个成员变量first
和second
,并提供构造函数和访问方法。 - 实现一个通用的
Stack
类模板,支持push
、pop
和top
操作。 - 实现一个通用的
sort
函数模板,支持对任意类型的数组进行排序。
