尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

【C++内功修炼】模板初阶超详解———泛型编程从入门到精通

【C++内功修炼】模板初阶超详解———泛型编程从入门到精通 前言为什么要学模板因为模板是泛型编程的基础而泛型编程是C的核心思想之一。我们平时用的vector、list、map这些STL容器底层全都是用模板实现的。可以说不理解模板就不算真正学会C。这篇博客我会从函数模板讲起再到类模板把模板初阶的所有知识点全部覆盖。代码都是我自己亲手写的保证你看完就能用。一、泛型编程先思考一个问题如何实现一个通用的交换函数在C语言或者C没有模板的时候我们只能用函数重载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; }这样写有什么问题第一代码复用率极低。除了类型不一样函数体完全一样就是在重复造轮子。每多一种类型就要多写一份重载。第二可维护性差。如果交换逻辑有bug所有重载的函数都要改改漏一个就是隐患。那能不能告诉编译器一个模子让编译器根据不同的类型自动生成对应的函数代码呢这就是泛型编程的思想编写与类型无关的通用代码是代码复用的一种手段。而模板就是泛型编程的基础。打个比方模板就像一个月饼模具你往里面灌绿豆沙出来就是绿豆月饼灌五仁出来就是五仁月饼。模具本身不是月饼但它能根据不同的材料类型生成不同的月饼具体类型的代码。二、函数模板2.1函数模板的概念和格式函数模板代表了一个函数家族它和类型无关使用的时候根据实参类型生成特定类型的函数版本。语法格式templatetypename T1, typename T2, ..., typename Tn注意typename是用来定义模板参数的关键字也可以用class代替。但是不能用struct代替class这个要记住选择题常考。我们用一个模板来写一个通用的Swap函数就这么短短几行就可以交换任意类型的变量int、double、char、甚至自定义类型全部搞定。2.2函数模板的原理函数模板本身不是函数它是一个蓝图是编译器用来生成具体函数的模具。在编译阶段编译器会根据你传入的实参类型推导出T的类型然后自动生成一份对应类型的函数。比如你传int编译器就生成一份Swap你传double编译器就生成一份Swap。所以模板的本质就是把本来应该我们手动写的重复代码交给编译器自动生成。2.3函数模板的实例化用不同类型的参数使用函数模板就叫做函数模板的实例化。实例化分为两种隐式实例化和显式实例化。1隐式实例化隐式实例化就是让编译器根据实参自己推演模板参数的实际类型在我们打印的时候我们会发现哪怕是double的返回类型打印时也会是30。这是因为cout打印double时候如果小数部分是0默认就只打印整数30。我们可以通过typeidxxx.name来检验类型。这里有个坑如果两个实参类型不一样编译器就懵了。比如Adda1d1a1是intd1是double。编译器从a1推T是int从d1推T是double但模板参数只有一个T编译器不知道该听谁的直接报错。注意在模板中编译器一般不会进行类型转换操作。因为一旦转换出问题编译器就要背黑锅所以它干脆不转。那怎么办呢有三种解决办法第一种用户自己强制转换Add(a1, (int)d1);第二种写一个更通用模板第三种用显示实例化。2显式实例化显式实例化就是在函数名后面的中手动指定模板参数的类型这样写的话编译器就知道T是intd2会自动隐式转换成int。如果转换不了编译器才会报错。2.4模板参数的匹配原则这个知识点也很重要面试可能会问。一个非模板函数可以和一个同名的函数模板同时存在而且该函数模板还可以被实例化为这个非模板函数时应该优先调用哪个。比如对于非模板函数和同名函数模板如果其他条件都相同调用时会优先调用非模板函数而不会从模板产生实例。如果模板可以产生一个更好匹配的函数那么选择模板。所以上面的Add(10, 20)两个int完全匹配普通函数就优先调用普通函数不用模板实例化。而Add(10, 20)你显式指定了用模板那就调用模板实例化出来的版本。再看一个例子// 普通函数两个int int Add(int left, int right) { return left right; } // 模板两个不同类型 templateclass T1, class T2 T1 Add(T1 left, T2 right) { return left right; } void Test() { Add(1, 2); // 调用普通函数完全匹配 Add(1, 2.0); // 调用模板模板能生成更匹配的版本 }Add(1, 2.0) 这个调用普通函数需要把double转成int有精度损失。而模板可以生成Addint, double的版本完全匹配不需要转换所以优先选模板。第三模板函数不允许自动类型转换但普通函数可以进行自动类型转换。这个前面已经提到过了模板很傲娇不会帮你做类型转换能匹配就匹配不能匹配就报错。三、类模板函数模板是针对函数的那如果我想让一个类也能泛型化呢比如栈我既想要存int的栈又想要存double的栈难道要写两个Stack类吗当然不用这就轮到类模板登场了。3.1类模板的定义格式templateclass T1, class T2, ..., class Tn class 类模板名 { // 类内成员定义 };我们来写一个栈的类模板注意类模板的成员函数如果要在类外面定义必须加上模板参数列表而且类名后面要跟。这是很多初学者容易写错的地方我把Push函数在类外实现一下划重点类外定义模板成员函数必须满足两个条件第一函数前面必须重新写 template不能省略。第二类名后面必须加 也就是 StackT::Push。为什么因为Stack本身不是一个具体的类它只是一个类模板。StackT才是一个具体的类。所以类外定义的时候必须用Stack来表示这是Stack模板的某个实例化版本的成员函数。还有一个非常重要的注意点模板不建议声明和定义分离到.h和.cpp两个文件中会出现链接错误。具体原因我们后面进阶篇再讲初阶阶段你只要记住模板的声明和实现都写在头文件里就对了。3.2类模板的实例化类模板的实例化和函数模板不一样。函数模板可以靠实参推演类型类模板不行必须在类模板名字后面跟手动指定类型。注意Stack是类模板名不是真正的类。Stackint、Stackdouble 才是真正的类型。这一点一定要搞清楚不然后面学STL会很懵。四、总结模板初阶需要掌握的核心点1. 泛型编程的思想编写与类型无关的代码提高复用性2. 函数模板template 函数定义编译器根据实参推演类型自动生成3. 隐式实例化 vs 显式实例化隐式靠编译器推显式手动指定类型4. 匹配原则普通函数优先模板更匹配则选模板模板不做自动类型转换5. 类模板类名后面必须加类型才能实例化Stack才是真正的类型6. 类外定义模板成员函数必须重新写template类名后加T7. 模板不能声明和定义分离到.h和.cpp都写在头文件里模板是C非常重要的特性也是STL的基础。把模板搞懂了后面学vector、list这些容器的时候就不会觉得它们很神秘了。
返回列表