C++ PART3 模版和泛型
泛型编程(Generic Programming)
泛型编程是一种编程范式,旨在编写与类型无关的代码。C++中的模板是实现泛型编程的工具。通过泛型编程,程序员可以编写更通用、更可重用的代码。
特点
- 类型无关:通过模板,代码可以适用于多种数据类型,而无需为每种类型编写单独的实现。
- 编译时多态:模板通过在编译时生成特定类型的代码,提供了一种编译时的多态性,与运行时多态(如虚函数)不同。
- 提高代码重用性:模板使得同一段代码可以用于多个数据类型,从而提高代码的重用性。
模板(Template)
模板是C++中的一种机制,用于创建通用的函数和类。模板允许在编写代码时不指定具体的数据类型,而是在使用时再指定。这使得同一段代码可以处理不同的数据类型。
函数模板
函数模板用于定义可以接受任意类型参数的函数。以下是一个简单的函数模板示例:
1 | template <typename T> |
在这个例子中,add
函数是一个函数模板,可以用于不同的数据类型,如 int
和 double
。
类模板
类模板用于定义可以处理任意类型数据的类。以下是一个简单的类模板示例:
1 | template <typename T> //。模板参数前有个typename关键字,这里可以写成typename,也可以写成class(这里的class显然不是用来定义类的),这是固定写法. |
在这个例子中,Box
是一个类模板,可以用于不同的数据类型。
类型参数和非类型参数
类型参数(Type Parameter)
类型参数用于指定模板中的数据类型。类型参数使得模板能够处理不同的数据类型,而无需为每种类型编写单独的代码。
使用方法
类型参数通常使用 typename
或 class
关键字来声明。二者在这个上下文中是等价的。以下是一些例子:
- 函数模板:
1 | template <typename T> |
在这个例子中,T
是一个类型参数,表示函数 add
可以接受任何类型的参数,只要这些参数支持 +
操作。
- 类模板:
1 | template <class T = string> // 这里可以指定默认值 |
在这个例子中,T
是一个类型参数,表示 Box
类可以存储任何类型的值。
非类型参数(Non-Type Parameter)
非类型参数用于指定模板中的常量值。这些参数在实例化模板时必须是已知的常量表达式。
使用方法
非类型参数通常用于指定数组大小、常量值等。非类型参数可以是整型、枚举、指针或引用类型。以下是一些例子:
- 类模板:
1 | template <typename T, int Size> |
在这个例子中,Size
是一个非类型参数,表示数组的大小。Array
类可以用于创建不同大小的数组。
-
函数模板:
非类型参数在函数模板中较少使用,但可以用于固定某些常量值的计算。
非类型参数的限制
- 必须是一个常量表达式。
- 通常是整型、枚举、指针或引用类型。
- 不能是浮点数或类类型。
typename特殊情况
虽然 typename
和 class
在模板参数列表中是等价的,但在其他上下文中,typename
有一个特定的用途:
- 嵌套依赖类型:当你需要在模板中访问依赖于模板参数的嵌套类型时,必须使用
typename
来告诉编译器该名称是一个类型。例如:
1 | template <typename T> |
在这个例子中,typename
用于指明 T::NestedType
是一个类型,而不是一个静态成员或其他实体。
模版的实例化
主要作用
-
生成具体类型或函数:模板实例化的主要作用是从通用的模板定义生成特定的类或函数。这使得同一段代码可以适用于多种数据类型,而无需重复编写。
-
代码重用:通过模板实例化,程序员可以编写一次代码,并在多个不同的上下文中重用。这大大提高了代码的重用性和维护性。
-
类型安全:模板实例化在编译时进行类型检查,这意味着任何类型不匹配的问题都会在编译时被捕获,从而提高了程序的安全性和可靠性。
模板实例化的过程
模板实例化可以是显式的,也可以是隐式的。
- 隐式实例化:当你使用模板时,编译器会自动为你实例化模板。例如:
1 | template <typename T> |
在这个例子中,当 add(3, 4)
被调用时,编译器会自动实例化 add<int>
。
- 显式实例化:有时你可能希望提前实例化模板,以便在某些情况下减少编译时间或避免代码膨胀。可以通过显式实例化来实现:
1 | template int add<int>(int, int); // 显式实例化,只在cpp文件里定义一次 |
这行代码告诉编译器为 int
类型显式实例化 add
函数模板。
其他cpp文件引用:
1 | extern template template int add<int>(int, int); |
using 和typedef的区别
基本用法
typedef
:用于为已有类型定义一个新的名称。
1 | typedef unsigned long ulong; |
在这个例子中,ulong
是 unsigned long
的别名。
using
:在C++11中引入,提供与typedef
相同的基本功能。
1 | using ulong = unsigned long; |
这行代码与上面的typedef
示例等效。
主要区别
-
语法和可读性:
using
的语法更接近于变量和函数的声明方式,通常被认为更易读。typedef
的语法在处理复杂类型(如指针、函数指针等)时可能显得不够直观。
-
模板别名:
using
支持模板别名,这是typedef
无法实现的。
1 | template <typename T> |
在这个例子中,Vec
是 std::vector
的模板别名,可以用于任何类型 T
。
- 作用域:
using
可以用于引入命名空间中的名称到当前作用域。
1 | namespace A { |
typedef
不具备这种功能。
- 类型转换:
- 在类型转换的上下文中,
using
和typedef
的行为是相同的,因为它们都只是类型的别名,不会影响实际的类型转换规则。
- 在类型转换的上下文中,
何时使用 using
和 typedef
-
简单类型别名:在为简单类型定义别名时,
using
和typedef
都可以使用,但using
通常更具可读性。 -
模板别名:当需要为模板创建别名时,必须使用
using
,因为typedef
不支持模板别名。 -
复杂类型:在处理复杂类型(如函数指针、复杂模板类型等)时,
using
通常比typedef
更易于理解和使用。
全特化和偏特化
全特化(Full Specialization)
全特化是指为某个具体类型提供模板的完整实现。在全特化中,所有模板参数都被具体化,即你为模板指定了特定的类型或值。
使用场景
全特化通常用于为某些特定类型提供特殊的实现,比如优化性能或处理特定类型的特殊逻辑。
示例
1 |
|
在这个例子中,MyClass<int>
是对 MyClass
的全特化,为类型 int
提供了一个特定的实现。
偏特化(Partial Specialization)
偏特化是指为某些模板参数提供部分具体化,而其他参数仍然保持通用性。偏特化通常用于类模板,因为C++不支持函数模板的偏特化。
使用场景
偏特化用于处理模板参数的某些组合情况,而不需要为所有可能的参数组合提供特化版本。它在处理复杂类型组合和条件时非常有用。
示例
1 |
|
在这个例子中,MyPair<T, T>
,MyPair<T, int>
是对 MyPair<T, U>
的偏特化,处理当两个模板参数类型相同时的情况。
总结
- 全特化:为特定的类型或值提供完全实现。适用于需要对某个特定类型进行特殊处理的情况。
- 偏特化:为部分模板参数提供具体化,而其他参数保持通用。适用于需要处理某些参数组合或条件的情况。
模版模版参数
模板模板参数(Template Template Parameters)是C++的一项高级特性,允许模板参数本身也是模板。这种功能使得编写更通用和灵活的代码成为可能,特别是在设计需要处理多个模板类型的库或类时。
基本概念
模板模板参数允许你定义一个模板,其参数是另一个模板。例如,你可以编写一个模板类,它的一个参数是一个容器模板(如std::vector
或std::list
),而不关心容器中存储的具体类型。
语法
模板模板参数的语法稍微复杂一些,因为你需要指定模板参数本身是一个模板。以下是一个简单的语法结构:
1 | template <template <typename> class Container> |
在这个例子中,Container
是一个模板模板参数,表示一个接受单个类型参数的模板类。
示例
以下是一个完整的示例,展示如何使用模板模板参数:
1 |
|
在这个例子中,MyClass
是一个接受模板模板参数的类模板。它可以与任何符合标准库容器接口的模板类一起使用,如std::vector
和std::list
。
注意事项
-
参数数量:模板模板参数的模板参数数量必须匹配。例如,如果你定义了一个模板模板参数要求一个模板有两个参数,那么传入的模板必须有两个参数。
-
可变参数模板:为了兼容更多的标准库容器(如
std::vector
,其构造函数接受可变数量的模板参数),通常会使用可变参数模板(typename...
)来定义模板模板参数。 -
复杂性:模板模板参数增加了模板的复杂性,可能会导致更难以理解和调试的代码。因此,应在需要时谨慎使用。
可变参数模板(Variadic Templates)是C++11引入的一项功能,允许模板接受可变数量的模板参数。这为编写更通用和灵活的代码提供了强大的支持,特别是在需要处理不定数量的参数时。
可变参数模版
可变参数模板可以用于函数模板和类模板,允许它们接受任意数量的模板参数。它们通过使用模板参数包(parameter pack)来实现,这是一种可以包含零个或多个模板参数的特殊类型。
语法
可变参数模板使用省略号(...
)来表示参数包。以下是基本的语法结构:
1 | template <typename... Args> |
在这个例子中,Args...
是一个模板参数包,args...
是一个函数参数包。Args
和 args
可以包含任意数量的参数。
示例
以下是一些使用可变参数模板的示例:
函数模板
一个简单的示例是实现一个可以接受任意数量参数的打印函数:
1 |
|
在这个例子中,print
函数使用递归来处理参数包,直到所有参数都被打印。
类模板
可变参数模板也可以用于类模板,例如实现一个简单的元组类:
1 |
|
在这个例子中,MyTuple
类模板通过继承来递归处理参数包,实现了一个简单的元组。
注意事项
-
递归展开:处理参数包时,通常使用递归展开的方法,即通过递归调用来逐个处理参数。
-
基准案例:递归展开时需要定义一个基准案例(如上例中的单参数
print
和空MyTuple
特化),以终止递归。 -
编译器支持:可变参数模板是C++11标准的一部分,因此需要确保编译器支持这一特性。