C++ 模板
C++ 模板
模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。
模板是创建泛型类或函数的蓝图或公式。库容器,比如迭代器和算法,都是泛型编程的例子,它们都使用了模板的概念。
每个容器都有一个单一的定义,比如 向量,我们可以定义许多不同类型的向量,比如 vector <int> 或 vector <string>。
您可以使用模板来定义函数和类,接下来让我们一起来看看如何使用。
函数模板
模板函数定义的一般形式如下所示:
template <class type> ret-type func-name(parameter list) { // 函数的主体 }
在这里,type 是函数所使用的数据类型的占位符名称。这个名称可以在函数定义中使用。
下面是函数模板的实例,返回两个数中的最大值:
#include <iostream>
#include <string>
using namespace std;
template <typename T>
inline T const& Max (T const& a, T const& b)
{
return a < b ? b:a;
}
int main ()
{
int i = 39;
int j = 20;
cout << "Max(i, j): " << Max(i, j) << endl;
double f1 = 13.5;
double f2 = 20.7;
cout << "Max(f1, f2): " << Max(f1, f2) << endl;
string s1 = "Hello";
string s2 = "World";
cout << "Max(s1, s2): " << Max(s1, s2) << endl;
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Max(i, j): 39 Max(f1, f2): 20.7 Max(s1, s2): World
类模板
正如我们定义函数模板一样,我们也可以定义类模板。泛型类声明的一般形式如下所示:
template <class type> class class-name { . . . }
在这里,type 是占位符类型名称,可以在类被实例化的时候进行指定。您可以使用一个逗号分隔的列表来定义多个泛型数据类型。
下面的实例定义了类 Stack<>,并实现了泛型方法来对元素进行入栈出栈操作:
#include <iostream>
#include <vector>
#include <cstdlib>
#include <string>
#include <stdexcept>
using namespace std;
template <class T>
class Stack {
private:
vector<T> elems; // 元素
public:
void push(T const&); // 入栈
void pop(); // 出栈
T top() const; // 返回栈顶元素
bool empty() const{ // 如果为空则返回真。
return elems.empty();
}
};
template <class T>
void Stack<T>::push (T const& elem)
{
// 追加传入元素的副本
elems.push_back(elem);
}
template <class T>
void Stack<T>::pop ()
{
if (elems.empty()) {
throw out_of_range("Stack<>::pop(): empty stack");
}
// 删除最后一个元素
elems.pop_back();
}
template <class T>
T Stack<T>::top () const
{
if (elems.empty()) {
throw out_of_range("Stack<>::top(): empty stack");
}
// 返回最后一个元素的副本
return elems.back();
}
int main()
{
try {
Stack<int> intStack; // int 类型的栈
Stack<string> stringStack; // string 类型的栈
// 操作 int 类型的栈
intStack.push(7);
cout << intStack.top() <<endl;
// 操作 string 类型的栈
stringStack.push("hello");
cout << stringStack.top() << std::endl;
stringStack.pop();
stringStack.pop();
}
catch (exception const& ex) {
cerr << "Exception: " << ex.what() <<endl;
return -1;
}
}
当上面的代码被编译和执行时,它会产生下列结果:
7 hello Exception: Stack<>::pop(): empty stack
《java编程思想 泛型》鸭子类型 - 简书 https://www.jianshu.com/p/db4274701be3
Java 泛型 | 菜鸟教程 https://www.runoob.com/java/java-generics.html
Java 重写(Override)与重载(Overload) | 菜鸟教程 https://www.runoob.com/java/java-override-overload.html
面试官问我:“泛型擦除是什么,会带来什么问题?” https://juejin.im/post/6844904083199918093
https://mp.weixin.qq.com/s/hihVKLi_2wZ3dF7O9NMauw
泛型趣谈
[2005.11710] Featherweight Go https://arxiv.org/abs/2005.11710
We describe a design for generics in Go inspired by previous work on Featherweight Java by Igarashi, Pierce, and Wadler. Whereas subtyping in Java is nominal, in Go it is structural, and whereas generics in Java are defined via erasure, in Go we use monomorphisation. Although monomorphisation is widely used, we are one of the first to formalise it. Our design also supports a solution to The Expression Problem.
Comparing Java and C# Generics - Jonathan Pryor's web log http://www.jprl.com/Blog/archive/development/2007/Aug-31.html
秒懂Java泛型_ShuSheng007的程序人生-CSDN博客 https://blog.csdn.net/ShuSheng0007/article/details/80720406
Java泛型原理解析
为什么人们会说Java的泛型是伪泛型呢,就是因为Java在编译时擦除了所有的泛型信息,所以Java根本不会产生新的类型到字节码或者机器码中,所有的泛型类型最终都将是一种原始类型,那样在Java运行时根本就获取不到泛型信息。
擦除
Java编译器编译泛型的步骤:
1.检查泛型的类型 ,获得目标类型
2.擦除类型变量,并替换为限定类型(T为无限定的类型变量,用Object替换)
3.调用相关函数,并将结果强制转换为目标类型。
ArrayList<String> arrayString=new ArrayList<String>();
ArrayList<Integer> arrayInteger=new ArrayList<Integer>();
System.out.println(arrayString.getClass()==arrayInteger.getClass());
- 1
- 2
- 3
上面代码输入结果为 true,可见通过运行时获取的类信息是完全一致的,泛型类型被擦除了!
如何擦除:
当擦除泛型类型后,留下的就只有原始类型了,例如上面的代码,原始类型就是ArrayList
。擦除类型变量,并替换为限定类型(T为无限定的类型变量,用Object替换),如下所示
擦除之前:
//泛型类型
class Pair<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
擦除之后:
//原始类型
class Pair {
private Object value;
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
因为在Pair<T>
中,T
是一个无限定的类型变量,所以用Object替换。如果是Pair<T extends Number>
,擦除后,类型变量用Number类型替换。
与其他语言相比较
相比较Java,其模仿者C#在泛型方面无疑做的更好,其是真泛型。
C#泛型类在编译时,先生成中间代码IL
,通用类型T
只是一个占位符。在实例化类时,根据用户指定的数据类型代替T并由即时编译器(JIT)生成本地代码,这个本地代码中已经使用了实际的数据类型,等同于用实际类型写的类,所以不同的封闭类的本地代码是不一样的。其可以在运行时通过反射获得泛型信息,而且C#的泛型大大提高了代码的执行效率。
那么Java为什么不采用类似C#的实现方式呢,答案是:要向下兼容!兼容害死人啊。关于C#与Java的泛型比较,可以查看这篇文章:Comparing Java and C# Generics