【C++】模版

发布时间:2026/6/27 10:37:20

【C++】模版 目录一、泛型编程二、模板一函数模板函数模板的实例化1.隐式实例化2.显示实例化模板函数的匹配原则二类模板类模板的实例化三、模板参数的分类一类型参数二非类型参数三模板模板参数四、模板的特化一函数模板的特化二类模板的特化1.全特化2.偏特化五、模板的分离编译一函数模板的分离编译二类模板的分离编译六、class和typename使用场景的区别两者相同的场景只能用 typename 的场景只能用class的场景一、泛型编程概念编写与类型无关的通用代码是代码复用的一种手段。在 C/C 中泛型编程主要通过 C 的模板Template机制实现而标准 C 语言本身不直接支持泛型通常需要通过宏或代码生成来模拟。二、模板一函数模板函数模板代表了一个函数家族与类型无关在使用时根据实参类型产生函数的特定类型版本。函数模板格式:template 模板参数列表 返回值类型 函数名(参数列表) { // 函数体通用逻辑 }示例templatetypename T void Swap( T left, T right) { T temp left; left right; right temp; }注意①typename是用来定义模板参数的关键字也可以使用class(但不能使用struct代替class)②typename与class使用场景上具有一些差别后面单独列出。如何理解函数模板函数模板本身并不是函数是编译器用来产生特定类型函数的模具。就像炼钢厂的模具一样向模具中浇铁水就是铁铸件浇铝水就是铝铸件同理编译器根据传给函数模板的参数类型自动生成对应类型的函数省去了我们重复定义的过程。在编译器编译阶段对于模板函数的使用编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如当用double类型使用函数模板时编译器通过对实参类型的推演将T确定为double类型然后产生一份专门处理double类型的代码其它类型也是如此。函数模板的实例化之前说过类的实例化这个是类似的类实例化出对应的对象函数模板实例化出对应类型的函数。1.隐式实例化让编译器根据实参推演模板参数的实际类型头文件测试文件但是下面这种情况就无法通过编译该语句不能通过编译因为在编译期间当编译器看到该实例化时需要推演其实参类型以第11行代码为例编译器通过实参a1将T推演为int通过实参d1将T推演为double类型但模板参数列表中只有一个T此处编译器无法确定到底该将T确定为int 还是 double注意在模板中编译器一般不会进行类型转换操作此时有三种方式可以解决①增加一个模板参数②用户自己进行强制转换这里强制转换的类型要跟被推导参数的类型相匹配。③显式实例化2.显式实例化在函数名后的 中指定模板参数的实际类型在 中显式实例化会将模板参数T设置为指定类型例如Addint表示将模板参数设为int类型Adddouble表示将模板参数设为double类型。这样在调用模板函数时函数的参数类型会按照指定的类型进行处理。如果类型不匹配编译器会尝试进行隐式类型转换如果无法转换成功编译器将会报错。模板函数的匹配原则①一个非模板函数可以和一个同名的函数模板同时存在而且该函数模板还可以被实例化为这个非模板函数且如果其他条件都相同在调动时会优先调用非模板函数如果模板可以产生一个具有更好匹配的函数 那么将选择模板。②函数模板在进行参数推导时不允许自动类型转换要求参数类型与模板参数严格匹配。普通函数调用时允许自动类型转换如隐式类型转换、提升等二类模板语法形式template 模板参数列表 class 类名 { // 类成员成员变量、成员函数等 };示例类模板的实例化注意事项三、模板参数的分类一类型参数这是最常用的模板参数类型用于表示一个未知的数据类型用typename或class声明。头文件测试文件;特点代表一种数据类型如int、std::string或自定义类可用于声明变量、函数参数、返回值、成员类型等实例化时必须传入具体类型二非类型参数表示一个编译期已知的常量值而非类型。只能是特定类型的常量。头文件测试文件static constexpr int getSize()函数的作用是获取类模板中定义的数组大小Size并且由于它是静态和constexpr函数调用它不会依赖于类的对象实例且在编译期就能确定结果。特点必须是编译期可确定的常量值在编译时已知支持的类型有限整数类型int、long等、枚举、指针对象 / 函数指针、引用对象 / 函数引用、std::nullptr_t实例化时必须传入常量值三模板模板参数表示一个模板本身作为参数即参数是模板而非具体类型头文件测试文件上述代码解释template typename class Container表示一个接受单个类型参数的类模板这里举的例子并不十分正确std::vector、std::list等标准容器并不止一个参数比如std::vector的实际模板参数列表是template class T, class Allocator std::allocatorT class vector这意味着std::vector有两个类型参数第一个是存储的元素类型第二个是分配器类型而Storage中的Container只期望一个类型参数的模板。所以在上述头文件中又定义了一个模板来修正特点参数是模板而非类型或值声明时需指定其模板参数结构如参数数量、是否带默认值通常用于适配不同容器如标准库中的std::stack可适配std::vectorstd::list等四、模板的特化使用模板可以实现泛型编程但处理一些特殊类型时可能会得到一些错误的结果这时就需要特殊处理也就是对模板进行特化即在原模板类的基础上针对特殊类型进行特殊化比如以下场景就会处理错误头文件测试文件按重载的比较方法d1显然是比d2大的但得出的结果却是d1 d2这时就需要特化处理。一函数模板的特化函数模板的特化步骤①必须要先有一个基础的函数模板②template后面接一对尖括号 中隐去被特化的模板参数保留未被特化的模板参数③函数名后跟一对尖括号尖括号中指定需要特化的类型语法格式// 1. 通用模板 template typename T 返回类型 函数名(参数列表) { // 通用实现 } // 2. 特化版本注意特化必须在通用模板之后声明 template // 空模板参数列表表示这是特化 返回类型 函数名特化类型(参数列表) { // 针对特化类型的定制实现 }示例①头文件测试文件示例②注意事项①特化版本必须在通用模板之后声明②特化之后如果出现多重定义错误可以试着将特化函数修饰为内联函数。③特化版本的形参类型要和指定的类型一致④编译器在处理函数模板调用时会先推导模板参数类型然后检查是否存在特化版本可以匹配如果特化版本的参数类型与实际调用的参数类型匹配根据实际调用的参数类型推导出的模板参数类型要与特化版本的类型相一致就会优先使用特化版本。上面报错的原因去掉引用之后报错消失一般情况下如果函数模板遇到不能处理或者处理有误的类型为了实现简单通常都是将该函数直接给出即函数模板并不建议特化二类模板的特化1.全特化全特化是将模板参数列表中所有的参数都确定化头文件测试文件2.偏特化①部分特化头文件测试文件②参数进行更进一步的限制头文件测试文件五、模板的分离编译什么是分离编译一个程序项目由若干个源文件共同实现而每个源文件单独编译生成目标文件最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。对于声明和定义分离的普通函数来说编译阶段只有声明声明是一种承诺此时即便没有定义编译器检查声明的返回值、函数名、参数如果可以对上仍然可以通过编译阶段在链接阶段用修饰后的函数名去其它文件符号表中查找若函数定义存在则可以查到反之发生链接错误。一函数模板的分离编译函数模板的声明与定义可以分离但为了避免链接错误定义通常需放在头文件中。二类模板的分离编译类模板的声明与成员函数定义可以分离但必须放在同一个头文件中不能像普通类的成员函数那样将声明放在 .h 文件、定义放在 .cpp 文件中。C 模板采用编译期实例化机制编译器需要在使用模板时看到模板的完整定义才能生成具体类型的代码。如果将类模板的成员函数定义放在.cpp文件中编译器在处理其他文件中的模板使用时无法访问到这些定义会导致链接错误未定义的引用。六、class和typename使用场景的区别两者相同的场景在声明模板类型参数时typename和class功能相同可以互换包括声明模板模板参数早期仅class可用在声明 “模板的模板参数” 时C 早期标准仅支持classC11 后typename也支持只能用 typename 的场景当编译器无法确定一个依赖于模板参数的名称是否为类型时必须用 typename 明确告知编译器 “这是一个类型”。具体来说只要满足以下两个条件就必须使用typename名称依赖于模板参数即 “依赖名称”该名称表示一个类型而非变量、函数等。这里采用前面的例子还需要说明一点只要模板中出现了使用依赖于模板参数的类型无论是用在参数、返回值了还是定义模板参数别名甚至是用在模板参数的限定符都需要用typename①在模板参数列表中如果参数是带默认值的类型参数且默认值是依赖类型时必须用 typename 声明②当模板函数的返回类型是依赖于模板参数的类型时必须用 typename 标识③在使用using定义模板类型别名时如果别名指向依赖类型必须用typename④当模板参数的限定符如基类是依赖类型时需用 typename只能用class的场景class可用于定义普通类或类模板typename不能。感谢阅读本文如有错漏之处烦请各位斧正。

相关新闻