资讯详情

【C++修炼】模板初阶:泛型编程、函数模板、类模板

📅 2026/10/16 2:24:13 | 华诺云谱 👁 阅读
【C++修炼】模板初阶:泛型编程、函数模板、类模板
目录模板初阶一、泛型编程二、函数模板2.1 函数模板概念2.2 函数模板格式2.3 函数模板的原理2.4 函数模板的实例化2.5 模板参数的匹配原则三、类模板3.1 类模板的定义格式3.2 类模板的实例化模板初阶一、泛型编程代码示例 1.1// C语言实现多种参数类型的同一交换函数Swap // 函数名不能相同传地址需要创建指针变量 void SwapI(int* left, int* right) { int temp *left; *left *right; *right temp; } void SwapD(double* left, double* right) { double temp *left; *left *right; *right temp; } void SwapC(char* left, char* right) { char temp *left; *left *right; *right temp; } // C实现多种参数类型的同一交换函数Swap // 函数同名可以重载可以使用引用无需再创建变量 void Swap(int left, int right) { int temp left; left right; right temp; } void Swap(double left, double right) { double temp left; left right; right temp; } void Swap(char left, char right) { char temp left; left right; right temp; }代码示例1.1中是我们在C语言中会碰到的经典问题在C中得到了一定的改善无需为函数名烦恼也无需为了传地址额外创建指针变量引用即是对象本身不额外开辟空间。但使用函数重载仍然显得冗余还有几个不好的地方1. 重载的函数仅仅是类型不同函数体基本一致代码复用率低只要有新类型出现用户就得手动增加对应的函数。2. 代码的可维护性低一个出错可能所有的重载均出错。能否告诉编译器一个模子让编译器根据不同的类型利用该模子来生成代码像钢水浇筑工艺一个通用的模板函数体逻辑浇筑不同的材料参数类型生成不同材料的铸件具体类型的代码。在C中将其称为泛型编程编写与类型无关的通用代码是代码复用的一种手段。模板是泛型编程的基础。二、函数模板2.1 函数模板概念函数模板代表了一个函数家族该函数模板与类型无关使用时根据实参类型确定参数的具体类型生成具体函数。需要强调有的地方也会称呼函数模板为模板函数但需要严格区分函数模板是模板而不是函数。2.2 函数模板格式代码示例 2.2.1// 1、在函数头上加 template 表明这是一个函数模板 // 2、在尖括号内可以声明一些通用类型给函数使用 // 3、typename和class都是定义模板参数类型的关键字不可使用struct) // 4、class是早期使用的关键字typename是后面加的两者没有区别 // 5、模板参数类型的名称随意Type的缩写是T所以用TT1、T2等也有用Key的缩写K的 // 6、T1、T2、Tn、K等模板参数类型不是int、char等具体类型使用时根据情况替换 templatetypename T1, class T2, ......, typename Tn, typename K 返回值类型 函数名( 函数参数列表 ) { // ... } // 例如Swap交换函数模板 // 1、template表明下方定义的是函数模板 // 2、尖括号内是通用类型声明不是具体类型使用时具体类型代替通用类型 // 3、若实际调用Swap时实参是int类型则int类型替换T类型 templatetypename T void Swap(T left, T right) { T temp left; left right; right temp; } // 或是Add加法函数模板 // 可以定义多个模板参数类型T1、T2 templatetypename T1, typename T2 T1 Add(T1 left, T2 right) { return left right; }2.3 函数模板的原理函数模板是蓝图它本身不是函数是编译器在实际使用时根据实参类型产生相应有具体类型的函数代码的模板。所以模板就是将本应我们做的重复工作交给了编译器。在编译阶段对于函数模板编译器会根据传入的实参进行推演生成对应类型的函数代码以供调用。比如当用double类型使用函数模板时编译器通过对实参类型的推演将T确定为double类型然后产生一份专门处理double类型的函数代码再调用该函数完成运算对于字符等其他类型也是如此。2.4 函数模板的实例化用不同类型的参数使用函数模板生成具体函数代码称为函数模板的实例化。模板参数类型的推演过程称为模板参数实例化模板参数可以隐式实例化也可以显式实例化。1、隐式实例化让编译器根据实参类型推演模板参数类型代码示例 2.4.1templateclass T T Add(const T left, const T right) { return left right; } int main() { // 隐式实例化 int a1 10, a2 20; double d1 10.0, d2 20.0; // a1、a2都为int推演出T为int Add(a1, a2); // d1、d2都为double推演出T为double Add(d1, d2); // a1为intd1为double只有一个模板参数类型T // Add(a1, d1); // 1、手动进行强制类型转换 // Add(a, (int)d); // Add((double)a, d); return 0; }Add(a1, d1);不能通过编译。因为在编译期间函数模板实例化时编译器需要推演实参类型通过实参a1推演T为int通过实参d1推演T为double但模板参数列表中只有一个T无法确定T是int还是double存在歧义而报错。在模板中编译器不会擅自进行类型转换操作无法自主选择将a1类型转换成double或是将d1类型转换成int。除开定义多个模板参数类型此时有两种处理方式用户自己进行强制类型转化如上面的代码示例2.4.1显式实例化模板参数如下方的代码示例2.4.2。2、显式实例化调用函数时在函数名后使用尖括号指定模板参数的实际类型代码示例 2.4.2templateclass T T Add(const T left, const T right) { return left right; } int main() { int a 10; double b 20.0; // 显式实例化函数名后使用尖括号指定模板参数T的类型为int Addint(a, b); return 0; } // 若函数模板的参数列表有多个参数如 // templatetypename T1, typename T2, typename T3 // 显式实例化时格式如下 // FunctionNameint, double, char(a, b, c); // 依次指定参数类型即可指定了模板参数类型如果再发生类型不匹配编译器就有依据对实参进行隐式类型转换但如果实参无法类型转换成指定的模板参数类型编译器将会报错。必须显式实例化的情况代码示例 2.4.3#includeiostream using namespace std; // 存在部分必须显式实例化的情况 // 例如func函数n为元素个数创建n个元素的连续空间并将其地址作为返回值 templateclass T T* func(int n) { // T为元素的类型 return new T[n]; } int main() { // 需要存储5个int元素可以通过func动态申请一块空间通过指针接收空间地址 // 此处必须显式实例化因为不可以从实参推断模板参数T的具体类型 int* ptr funcint(5); for (int i 0; i 5; i) ptr[i] i 1; for (int i 0; i 5; i) { cout ptr[i] endl; } return 0; }运行结果2.5 模板参数的匹配原则1.有具体类型的函数可以和它的函数模板同时存在而且该函数模板还可以被实例化为这个非模板函数。代码示例 2.5.1#includeiostream using namespace std; // 专门处理int的Add加法函数非模板函数 int Add(int left, int right) { cout int Add(int left, int right) endl; return left right; } // Add加法函数模板 templateclass T T Add(T left, T right) { cout T Add(T left, T right) endl; return left right; } // 特化函数模板实例化为有具体类型的函数称为特化 int main() { // 编译器处理函数与模板特化的原则是有现成的用现成的有更匹配的用更匹配的 // 与非模板函数匹配编译器不需要特化 cout Add(1, 2) endl; // 通过显式实例化调用编译器特化的Add版本 cout Addint(3, 4) endl; return 0; }运行结果2. 对于非模板函数和函数模板的特化如果其他条件都相同优先调用非模板函数而非模板的特化。如果模板可以产生一个更匹配的函数那么将选择模板的特化。代码示例 2.5.2#includeiostream using namespace std; // 专门处理int的Add加法函数非模板函数 int Add(int left, int right) { cout int Add(int left, int right) endl; return left right; } // Add加法函数模板 templatetypename T1, typename T2 T1 Add(T1 left, T2 right) { cout T1 Add(T1 left, T2 right) endl; return left right; } int main() { // 与非模板函数完美匹配编译器无需特化 cout Add(1, 2) endl; // 函数模板可以生成更匹配的版本编译器根据实参生成更加匹配的Add函数 // int Add(int left, double right) cout Add(2, 3.1) endl; return 0; }一句话总结有现成的用现成的有更匹配的用更匹配的。如果现成的就能匹配上编译器就不会对函数模板进行特化只会调用现成的。如果现成的不是特别匹配但函数模板可以生成更匹配的实例编译器就会对函数模板进行特化调用生成的这个更匹配的函数。3. 函数模板在使用时不允许自动类型转换但普通函数可以进行自动类型转换。代码示例 2.5.3#includeiostream using namespace std; //// Add加法函数模板 //templatetypename T1, typename T2 //T1 Add(T1 left, T2 right) //{ // cout T1 Add(T1 left, T2 right) endl; // return left right; //} // 专门处理int的Add加法函数非模板函数 int Add(int left, int right) { cout int Add(int left, int right) endl; cout left: left right: right endl; return left right; } int main() { // 函数模板使用时类型不匹配只能1、手动类型强转或2、显式实例化 // 1、Add(a, (int)d); 2、Addint(a, b); // 普通函数调用时编译器无需判断参数类型可以自动进行类型转换 // 函数形参left、right都是int则double类型的实参3.1自动转换为int类型 cout Add(2, 3.1) endl; return 0; }运行结果三、类模板类模板代表一个类家族该类模板与类型无关使用时编译器根据需要生成有具体类型的类的代码。由于类是类型和函数不同不需要传参无从推演模板参数的类型因此使用时模板参数必须显式实例化。需要强调有的地方也会称呼类模板为模板类但需要严格区分类模板是模板而不是类。3.1 类模板的定义格式代码示例 3.1.1// 1、在头上写template表明下方是类模板的定义 // 2、typename和class两个关键字都可以定义模板参数类型 // 3、类的定义格式照常除了不能用具体类型而用模板参数的通用类型代替 // 4、和函数模板一样T1、T2、Tn只是名称只是代号命名是随意的但用T命名更大众化 templatetypename T1, class T2, ..., typename Tn class 类模板名称 { // 类内成员定义 };代码示例 3.1.2#includeiostream using namespace std; // 类模版是模板不是类 // 模板参数T为栈中存储的数据的类型 templatetypename T class Stack { public: // 默认构造 Stack(size_t capacity 4) :_arr(new T[capacity]) ,_capacity(capacity) ,_top(0) {} // 析构 ~Stack() { delete[] _arr; _arr nullptr; _top _capacity 0; } // 类模板中成员函数的声明与定义分离压栈方法的声明 void Push(const T data); void Print() { for (size_t i 0; i _top; i) { cout _arr[i] ; } cout endl; } private: T* _arr; // 栈的存储空间 size_t _capacity; // 空间大小 size_t _top; // 栈顶 }; // 使用模版就不建议成员函数的声明与定义分离到两个文件.h头文件和.cpp源文件会出现链接错误 // 类模板中成员函数的声明与定义分离压栈方法函数模板的定义 templateclass T void StackT::Push(const T data) { // 扩容 if (_top _capacity) { T* tmp new T[2 * _capacity]; memcpy(tmp, _arr, sizeof(T) * _top); delete[] _arr; _arr tmp; _capacity * 2; } // 数据入栈同时更新栈顶 _arr[_top] data; } int main() { Stackint st1; // int st1.Push(1); st1.Push(2); st1.Push(3); st1.Push(4); st1.Print(); Stackdouble st2; // double st2.Push(1.1); st2.Push(2.1); st2.Push(3.1); st2.Push(4.1); st2.Print(); return 0; }运行结果3.2 类模板的实例化类模板的实例化与函数模板的实例化不同。类模板实例化必须显式实例化在类模板名字后跟尖括号括号内指定模板参数类型。单纯类模板的名字如Stack不是真正的类只有有具体类型完成实例化的结果如Stackint、Stackdouble才是真正的类。// Stack是类模板名是模板的名称 // Stackint、Stackdouble才是类类的名称类类型的名称 Stackint st1; // int Stackdouble st2; // double
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑