10、基础 | 模板与泛型编程
1. 模板函数
在C++中,模板与泛型编程是一种强大的编程范式,它允许程序员编写与类型无关的代码。这种类型无关的代码在编译时会被实例化,以支持特定的数据类型。下面是根据您提出的点,对模板函数及其相关概念的一个整理。
模板函数是允许程序员编写一个函数模板,该模板可以与多种数据类型一起工作,而不是仅限于一种数据类型。通过模板,我们可以定义一组在逻辑上相似但操作的数据类型不同的函数。
1.1 模板参数
模板参数是模板定义中用于表示类型或值的占位符。在函数模板中,我们通常使用类型参数来指定函数可以操作的类型。类型参数在模板声明中通过关键字class
或typename
后跟一个标识符来声明(在模板定义中两者可互换使用,但typename
在某些上下文中更为清晰)。
template <typename T>
void print(T value) {
std::cout << value << std::endl;
}
在上面的例子中,T
是一个模板参数,它可以被任何类型所替代。
1.2 函数形参
函数形参是函数模板中除了模板参数以外的参数,它们定义了函数需要接收的实参的类型和数量。在模板函数中,形参的类型可以是模板参数所代表的类型,也可以是其他非模板类型。
template <typename T>
T add(T a, T b) {
return a + b;
}
在这个例子中,a
和b
是函数add
的形参,它们的类型由模板参数T
决定。
1.3 成员模板
成员模板是类模板或普通类中的模板成员(可以是成员函数或成员变量)。成员模板允许我们在类的上下文中定义与类型无关的成员。
- 类模板中的成员函数模板:这种成员模板允许成员函数自身也接受模板参数,从而进一步增强了类的灵活性。
template <typename T>
class Box {
public:
T value;
// 成员函数模板
template <typename U>
void compare(U other) {
if (value < other) {
std::cout << "Box value is less than other." << std::endl;
} else {
std::cout << "Box value is not less than other." << std::endl;
}
}
};
在这个例子中,Box
是一个类模板,它有一个成员函数模板compare
,该函数接受一个不同类型的参数other
,并与Box
的value
成员进行比较。
- 非模板类中的成员函数模板:虽然较少见,但即使在一个非模板类中,也可以定义成员函数模板,以提供对多种类型数据的操作。
模板和泛型编程是C++中一个非常重要的概念,它们极大地增强了C++的表达能力,使得代码更加灵活和可重用。通过上面的介绍,您应该对模板函数及其相关概念有了一定的了解。
2. 类模板
在C++中,模板(Templates)是泛型编程的基石,它允许程序员编写与类型无关的代码。类模板(Class Templates)是模板的一种形式,用于定义类,其中类或类的成员在定义时其类型可以是未指定的,直到类被实例化时才确定。下面是对类模板及其相关概念的详细整理,每个部分都包含例子。
2.1 与模板函数的区别
- 模板函数处理的是函数级别的泛型编程,其函数体在编译时根据提供的类型参数实例化。
- 类模板则是对类进行泛型化,可以定义类级别的泛型编程,类的所有成员(包括成员函数和成员变量)都可以是模板化的。
例子:
// 模板函数例子
template<typename T>
T max(T a, T b) {
return (a > b) ? a : b;
}
// 类模板例子
template<typename T>
class Box {
public:
T value;
Box(T val) : value(val) {}
void print() { std::cout << value << std::endl; }
};
2.2 模板类名的使用
使用模板类时,需要在类名后加上尖括号<>
中的类型参数。
例子:
Box<int> intBox(42);
intBox.print(); // 输出 42
Box<std::string> stringBox("Hello");
stringBox.print(); // 输出 Hello
2.3 类模板的成员函数
类模板的成员函数可以在类内部定义(隐式模板),也可以在类外部定义(显式模板)。
内部定义:
template<typename T>
class Box {
public:
T value;
Box(T val) : value(val) {}
void print() { std::cout << value << std::endl; }
};
外部定义:
template<typename T>
class Box {
public:
T value;
Box(T val) : value(val) {}
void print();
};
template<typename T>
void Box<T>::print() {
std::cout << value << std::endl;
}
2.4 类型成员
类模板中可以包含类型成员,这些类型成员也可以是模板化的。
例子:
template<typename T, typename Alloc = std::allocator<T>>
class Vec {
public:
using value_type = T;
using allocator_type = Alloc;
// 其他成员...
};
2.5 类模板和友元
友元关系对于类模板可以很复杂,因为友元可以是另一个模板或者非模板。
例子:
template<typename T>
class Box;
template<typename T>
void printBox(const Box<T>& b);
template<typename T>
class Box {
T value;
friend void printBox<>(const Box<T>&);
public:
Box(T val) : value(val) {}
};
template<typename T>
void printBox(const Box<T>& b) {
std::cout << b.value << std::endl;
}
注意:在某些编译器中,友元模板函数的声明可能需要额外的语法(如使用template<>
),但上述简洁形式在现代编译器中通常是可接受的。
2.6 模板类型别名
使用using
关键字可以定义模板类型别名,简化模板类型的书写。
例子:
template<typename T>
using Ptr = T*;
Ptr<int> p = new int(10); // 相当于 int* p = new int(10);
2.7 类模板的 static 成员
类模板的static成员对于所有相同类型参数的实例是共享的。
例子:
template<typename T>
class Box {
private:
static int count;
public:
Box() { ++count; }
~Box() { --count; }
static int getCount() { return count; }
};
template<typename T>
int Box<T>::count = 0;
int main() {
Box<int> b1, b2;
std::cout << Box<int>::getCount() << std::endl; // 输出 2
b1.~Box(); // 显式调用析构函数,仅为演示
std::cout << Box<int>::getCount() << std::endl; // 输出 1
return 0;
}
通过这些例子和解释,你应该对C++中的类模板及其相关概念有了更深入的理解。
3. 模板编译
3.1 实例化声明
实例化声明(Instantiation Declaration)是模板使用的一个阶段,它指的是在编译过程中,编译器根据模板定义和特定的类型参数生成具体类型实例的过程。这个过程是隐式的,即用户不需要显式地编写实例化代码,编译器会根据上下文自动完成。
例子:
假设我们有一个函数模板compare
,用于比较两个值:
template <typename T>
int compare(const T& a, const T& b) {
if (a < b) return -1;
if (b < a) return 1;
return 0;
}
当我们在代码中调用compare(1, 2)
时,编译器会自动实例化一个int
类型的compare
函数版本。这个过程就是实例化声明,它不需要用户手动干预。
3.2 实例化定义
实例化定义(Instantiation Definition)是模板编译的另一个关键阶段,它指的是编译器实际生成模板具体类型实例的代码的过程。这个过程可能是隐式的,也可能是显式的。隐式实例化定义发生在编译器自动根据模板定义和类型参数生成具体类型实例时;而显式实例化定义则是用户通过特定的语法告诉编译器生成某个具体类型的实例。
隐式实例化定义例子:
继续上面的compare
函数模板例子,当调用compare(1.0, 2.0)
时,编译器会自动实例化一个double
类型的compare
函数版本,这就是隐式实例化定义。
显式实例化定义例子:
用户可以通过extern template
声明来显式地实例化模板的某些部分,但这主要用于控制模板实例的生成,以减少编译时间和优化链接过程。不过,更常见的显式实例化定义是直接通过模板实例化语法来完成的,如:
template int compare<int>(const int&, const int&); // 显式实例化int类型的compare函数
但请注意,上面的显式实例化定义语法在C++标准中并不直接支持用于函数模板的实例化(主要用于类或模板成员函数的实例化)。实际上,对于函数模板,我们通常不需要显式实例化定义,因为编译器会根据调用自动进行实例化。然而,为了说明概念,我们可以将其理解为一种“指导编译器生成特定类型实例”的方式。
在类模板的上下文中,显式实例化定义更加常见,例如:
template class SortedArray<int>; // 显式实例化SortedArray模板的int类型版本
这会告诉编译器生成SortedArray<int>
类型的具体类定义,即使该类在程序的其他部分没有被直接使用。
模板编译中的实例化声明和实例化定义是模板使用的核心过程。实例化声明是隐式的,由编译器根据上下文自动完成;而实例化定义可以是隐式的,也可以是显式的,其中显式实例化定义主要用于控制模板实例的生成,优化编译和链接过程。通过理解这些过程,可以更好地掌握C++模板编程的精髓。
4. 模板参数
4.1 默认模板实参
默认模板实参允许为模板参数指定默认值,这样在使用模板时,如果没有为这些参数提供具体的值,就会使用默认值。这个功能在定义模板时非常有用,特别是当模板的某些参数在大多数情况下都有相同的值时。
例子
#include <iostream>
#include <vector>
// 定义一个模板,具有默认模板实参
template<typename T, int N = 10>
class Buffer {
public:
T elem[N]; // 使用模板参数N作为数组大小
// 初始化数组
Buffer() {
for (int i = 0; i < N; ++i) {
elem[i] = T(); // 使用T的默认构造函数
}
}
// 打印数组
void pri
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
【C/C++面试必考必会】专栏,直击面试核心,精选C/C++及相关技术栈中面试官最爱的必考点!从基础语法到高级特性,从内存管理到多线程编程,再到算法与数据结构深度剖析,一网打尽。助你快速构建知识体系,轻松应对技术挑战。希望专栏能让你在面试中脱颖而出,成为技术岗的抢手人才。