函数模板
函数模板允许我们定义一个函数,该函数可以接受不同类型的数据作为参数。这样,我们就可以使用同一个函数实现多种类型的操作,而不需要为每种数据类型编写一个单独的函数。
函数模板的基本语法如下:
template <typename T>//或template <class T>
void myFunction(T arg1, T arg2) {
// 函数体
}
在这里,template
关键字用来声明一个模板,<typename T>
是模板参数,T
是类型占位符,可以在函数体中使用。当调用函数时,编译器会根据传入的参数类型自动推导出 T
的类型。
例如,一个简单的交换两个整数的函数模板:
template <typename T>
void swap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
类模板
类模板允许我们定义一个类,该类可以处理不同类型的数据。类模板的基本语法如下:
template <typename T>
class MyClass {
public:
T value;
MyClass(T val) : value(val) {}
void setValue(T val) {
value = val;
}
T getValue() {
return value;
}
};
在这里,MyClass
是一个类模板,它可以创建一个名为 T
的类型参数的对象。我们可以使用不同的类型来实例化这个类模板。
例如,一个简单的类模板实现一个栈:
template <typename T>
class Stack {
private:
std::vector<T> elements;
public:
void push(const T& elem) {
elements.push_back(elem);
}
void pop() {
elements.pop_back();
}
T top() const {
return elements.back();
}
bool empty() const {
return elements.empty();
}
};
在C++中,模板的实例化有两种方式:隐式实例化和显示实例化。
隐式实例化和显示实例化
隐式实例化是编译器自动根据函数调用或对象创建时提供的实参类型来实例化模板的过程。当我们使用模板函数或模板类时,如果没有显式指定类型,编译器会根据上下文自动推导模板参数的类型。
例如,假设我们有一个函数模板:
template <typename T>
void print(T elem) {
std::cout << elem << std::endl;
}
当我们调用这个函数模板时:
print(10); // 隐式实例化,编译器推导出T为int
print("Hello, World!"); // 隐式实例化,编译器推导出T为const char*
在这里,编译器看到第一个调用传递了一个 int
类型的参数,因此它会隐式地实例化一个 print<int>
版本的函数。对于第二个调用,编译器看到传递了一个字符串字面量(一个 const char*
类型),因此它会隐式地实例化一个 print<const char*>
版本的函数。
显示实例化是程序员在代码中明确指定模板参数的类型,告诉编译器要实例化模板的特定版本。这可以通过在模板函数或模板类的名字后面加上模板参数列表来实现。
以下是一个显示实例化的例子:
template <typename T>
void print(T elem) {
std::cout << elem << std::endl;
}
// 显示实例化一个int版本的print函数
template
void print<int>(int elem);
// 显示实例化一个double版本的print函数
template
void print<double>(double elem);
当我们显示实例化模板时,我们必须在所有使用的文件中包含显示实例化的代码,或者将它们放在一个单独的头文件中,并确保这个头文件被所有需要实例化的文件包含。
显示实例化的优点包括:
- 可以确保即使函数或类从未被隐式实例化,也会生成特定的模板实例。
- 有助于控制模板实例化的过程,尤其是在大型项目中,可能需要控制编译时间和编译后的二进制大小。
缺点是,如果模板被显示实例化了多次,可能会导致代码冗余。因此,通常显示实例化用于特定的模板参数,这些参数在多个源文件中需要,或者需要特别优化。
类模板的实例化
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
// Vector类名,Vector<int>才是类型
Vector<int> s1;
Vector<double> s2;
模板的优势
- 代码复用:通过模板,我们可以编写一次代码,然后在多种数据类型上重用。
- 类型安全:模板在编译时对类型进行检查,可以避免许多运行时类型错误。
- 性能优化:编译器可以为每个特定的类型生成优化的代码,这可能会提高程序的执行效率。
模板的限制和注意事项
- 模板元编程的限制:模板在编译时进行类型推导和代码生成,因此它们不能处理所有运行时才能确定的事情。
- 复杂度:模板代码有时可能会变得非常复杂,尤其是涉及到模板特化和模板模板参数时。
- 调试困难:模板代码生成的错误消息可能非常难以理解,调试起来可能会比较困难。
在使用模板时,以下是一些需要注意的点:
- 模板参数命名:通常使用
T
作为类型模板参数的占位符,但也可以使用其他名称,以提供更多上下文信息。 - 模板参数默认值:与函数参数类似,模板参数也可以有默认值,这允许在实例化模板时省略某些参数。
- 模板非类型参数:模板不仅可以接受类型作为参数,还可以接受整型、浮点型等非类型参数。
- 模板递归:在处理某些算法时,模板可能会递归地实例化自己。