ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

C++-----3.模板

C++-----3.模板 一、 模板1.1 模板的概念模板就是建立通用的模具大大提高复用性。模板的特点模板不可以直接使用它只是一个框架模板的通用并不是万能的1.2 函数模板C另一种编程思想称为泛型编程主要利用的技术就是模板C提供两种模板机制函数模板和类模板。1.2.1 函数模板语法函数模板作用建立一个通用函数其函数返回值类型和形参类型可以不具体制定用一个虚拟的类型来代表。**语法** C templatetypename T 函数声明或定义 **解释** template --- 声明创建模板 typename --- 表面其后面的符号是一种数据类型可以用class代替 T --- 通用的数据类型名称可以替换通常为大写字母 **示例** C //交换整型函数 void swapInt(int a, int b) { int temp a; a b; b temp; } //交换浮点型函数 void swapDouble(double a, double b) { double temp a; a b; b temp; } //利用模板提供通用的交换函数 templatetypename T void mySwap(T a, T b) { T temp a; a b; b temp; } void test01() { int a 10; int b 20; //swapInt(a, b); //利用模板实现交换 //1、自动类型推导 mySwap(a, b); //2、显示指定类型 mySwapint(a, b); cout a a endl; cout b b endl; } int main() { test01(); system(pause); return 0; } 总结 * 函数模板利用关键字 template * 使用函数模板有两种方式自动类型推导、显示指定类型 * 模板的目的是为了提高复用性将类型参数化1.2.2 函数模板注意事项注意事项自动类型推导必须推导出一致的数据类型T,才可以使用模板必须要确定出T的数据类型才可以使用**示例** C //利用模板提供通用的交换函数 templateclass T //typename可替换为class void mySwap(T a, T b) { T temp a; a b; b temp; } // 1、自动类型推导必须推导出一致的数据类型T,才可以使用 void test01() { int a 10; int b 20; char c c; mySwap(a, b); // 正确可以推导出一致的T //mySwap(a, c); // 错误推导不出一致的T类型 } // 2、模板必须要确定出T的数据类型才可以使用 templateclass T void func() { cout func 调用 endl; } void test02() { //func(); //错误模板不能独立使用必须确定出T的类型 funcint(); //利用显示指定类型的方式给T一个类型才可以使用该模板 } int main() { test01(); test02(); system(pause); return 0; } 总结 * 使用模板时必须确定出通用数据类型T并且能够推导出一致的类型1.2.3 函数模板案例案例描述利用函数模板封装一个排序的函数可以对不同数据类型数组进行排序排序规则从大到小排序算法为选择排序分别利用char数组和int数组进行测试示例 C //交换的函数模板 templatetypename T void mySwap(T a, Tb) { T temp a; a b; b temp; } templateclass T // 也可以替换成typename //利用选择排序进行对数组从大到小的排序 void mySort(T arr[], int len) { for (int i 0; i len; i) { int max i; //最大数的下标 for (int j i 1; j len; j) { if (arr[max] arr[j]) { max j; } } if (max ! i) //如果最大数的下标不是i交换两者 { mySwap(arr[max], arr[i]); } } } templatetypename T void printArray(T arr[], int len) { for (int i 0; i len; i) { cout arr[i] ; } cout endl; } void test01() { //测试char数组 char charArr[] bdcfeagh; int num sizeof(charArr) / sizeof(char); mySort(charArr, num); printArray(charArr, num); } void test02() { //测试int数组 int intArr[] { 7, 5, 8, 1, 3, 9, 2, 4, 6 }; int num sizeof(intArr) / sizeof(int); mySort(intArr, num); printArray(intArr, num); } int main() { test01(); test02(); system(pause); return 0; } 总结模板可以提高代码复用需要熟练掌握1.2.4 普通函数与函数模板的区别普通函数与函数模板区别普通函数调用时可以发生自动类型转换隐式类型转换函数模板调用时如果利用自动类型推导不会发生隐式类型转换如果利用显示指定类型的方式可以发生隐式类型转换**示例** C //普通函数 int myAdd01(int a, int b) { return a b; } //函数模板 templateclass T T myAdd02(T a, T b) { return a b; } //使用函数模板时如果用自动类型推导不会发生自动类型转换,即隐式类型转换 void test01() { int a 10; int b 20; char c c; cout myAdd01(a, c) endl; //正确将char类型的c隐式转换为int类型 c 对应 ASCII码 99 //myAdd02(a, c); // 报错使用自动类型推导时不会发生隐式类型转换 myAdd02int(a, c); //正确如果用显示指定类型可以发生隐式类型转换 } int main() { test01(); system(pause); return 0; } 总结建议使用显示指定类型的方式调用函数模板因为可以自己确定通用类型T1.2.5 普通函数与函数模板的调用规则调用规则如下如果函数模板和普通函数都可以实现优先调用普通函数可以通过空模板参数列表来强制调用函数模板函数模板也可以发生重载如果函数模板可以产生更好的匹配,优先调用函数模板**示例** C //普通函数与函数模板调用规则 void myPrint(int a, int b) { cout 调用的普通函数 endl; } templatetypename T void myPrint(T a, T b) { cout 调用的模板 endl; } templatetypename T void myPrint(T a, T b, T c) { cout 调用重载的模板 endl; } void test01() { //1、如果函数模板和普通函数都可以实现优先调用普通函数 // 注意 如果告诉编译器 普通函数是有的但只是声明没有实现或者不在当前文件内实现就会报错找不到 int a 10; int b 20; myPrint(a, b); //调用普通函数 //2、可以通过空模板参数列表来强制调用函数模板 myPrint(a, b); //调用函数模板 //3、函数模板也可以发生重载 int c 30; myPrint(a, b, c); //调用重载的函数模板 //4、 如果函数模板可以产生更好的匹配,优先调用函数模板 char c1 a; char c2 b; myPrint(c1, c2);//可调用普通函数因为存在隐式类型转换char-int //最终调用函数模板 } int main() { test01(); system(pause); return 0; } 总结既然提供了函数模板最好就不要提供普通函数否则容易出现二义性1.2.6 模板的局限性局限性模板的通用性并不是万能的**例如** C templateclass T void f(T a, T b) { a b; } 在上述代码中提供的赋值操作如果传入的a和b是一个数组就无法实现了 再例如 C templateclass T void f(T a, T b) { if(a b) { ... } } 在上述代码中如果T的数据类型传入的是像Person这样的自定义数据类型也无法正常运行 因此C为了解决这种问题提供模板的重载可以为这些**特定的类型**提供**具体化的模板****示例** C #includeiostream using namespace std; #include string class Person { public: Person(string name, int age) { this-m_Name name; this-m_Age age; } string m_Name; int m_Age; }; //普通函数模板 templateclass T bool myCompare(T a, T b) { if (a b) { return true; } else { return false; } } //具体化显示具体化的原型和以template开头并通过名称来指出类型 //具体化优先于常规模板 template bool myCompare(Person p1, Person p2) { if ( p1.m_Name p2.m_Name p1.m_Age p2.m_Age) { return true; } else { return false; } } void test01() { int a 10; int b 20; //内置数据类型可以直接使用通用的函数模板 bool ret myCompare(a, b); if (ret) { cout a b endl; } else { cout a ! b endl; } } void test02() { Person p1(Tom, 10); Person p2(Tom, 10); //自定义数据类型不会调用普通的函数模板 //可以创建具体化的Person数据类型的模板用于特殊处理这个类型 bool ret myCompare(p1, p2); if (ret) { cout p1 p2 endl; } else { cout p1 ! p2 endl; } } int main() { test01(); test02(); system(pause); return 0; } 总结 * 利用具体化的模板可以解决自定义类型的通用化 * 学习模板并不是为了写模板而是在STL能够运用系统提供的模板1.3 类模板1.3.1 类模板语法类模板作用建立一个通用类类中的成员 数据类型可以不具体制定用一个**虚拟的类型**来代表。**语法** c templatetypename T 类 **解释** template --- 声明创建模板 typename --- 表面其后面的符号是一种数据类型可以用class代替 T --- 通用的数据类型名称可以替换通常为大写字母**示例** C #include string //类模板 templateclass NameType, class AgeType class Person { public: Person(NameType name, AgeType age) { this-mName name; this-mAge age; } void showPerson() { cout name: this-mName age: this-mAge endl; } public: NameType mName; AgeType mAge; }; void test01() { // 指定NameType 为string类型AgeType 为 int类型 Personstring, intP1(孙悟空, 999); P1.showPerson(); } int main() { test01(); system(pause); return 0; } 总结类模板和函数模板语法相似在声明模板template后面加类此类称为类模板1.3.2 类模板与函数模板区别类模板与函数模板区别主要有两点类模板没有自动类型推导的使用方式类模板在模板参数列表中可以有默认参数**示例** C #include string //类模板 templateclass NameType, class AgeType int class Person { public: Person(NameType name, AgeType age) { this-mName name; this-mAge age; } void showPerson() { cout name: this-mName age: this-mAge endl; } public: NameType mName; AgeType mAge; }; //1、类模板没有自动类型推导的使用方式 void test01() { // Person p(孙悟空, 1000); // 错误 类模板使用时候不可以用自动类型推导 Person string ,intp(孙悟空, 1000); //必须使用显示指定类型的方式使用类模板 p.showPerson(); } //2、类模板在模板参数列表()中可以有默认参数 void test02() { Person string p(猪八戒, 999); //类模板中的模板参数列表 可以指定默认参数 p.showPerson(); } int main() { test01(); test02(); system(pause); return 0; } 总结 * 类模板使用只能用显示指定类型方式 * 类模板中的模板参数列表可以有默认参数1.3.3 类模板中成员函数创建时机类模板中成员函数和普通类中成员函数创建时机是有区别的普通类中的成员函数一开始就可以创建类模板中的成员函数在调用时才创建**示例** C class Person1 { public: void showPerson1() { cout Person1 show endl; } }; class Person2 { public: void showPerson2() { cout Person2 show endl; } }; templateclass T class MyClass { public: T obj; //类模板中的成员函数并不是一开始就创建的而是在模板调用时再生成调用时候才确定T的数据类型 void fun1() { obj.showPerson1(); } void fun2() { obj.showPerson2(); } }; void test01() { MyClassPerson1 m; //确定T m.fun1(); //m.fun2();//编译会出错说明函数调用才会去创建成员函数 } int main() { test01(); system(pause); return 0; } 总结类模板中的成员函数并不是一开始就创建的在调用时才去创建1.3.4 类模板对象做函数参数学习目标 类模板实例化出的对象向函数传参的方式一共有三种传入方式指定传入的类型 --- 直接显示对象的数据类型参数模板化 --- 将对象中的参数变为模板进行传递整个类模板化 --- 将这个对象类型 模板化进行传递​ **示例** C #include string //类模板 templateclass NameType, class AgeType int class Person { public: Person(NameType name, AgeType age) { this-mName name; this-mAge age; } void showPerson() { cout name: this-mName age: this-mAge endl; } public: NameType mName; AgeType mAge; }; //1、指定传入的类型 void printPerson1(Personstring, int p) //类模板中的对象做函数中的参数 { p.showPerson(); } void test01() { Person string, int p(孙悟空, 100); printPerson1(p); } //2、参数模板化 template class T1, class T2 void printPerson2(PersonT1, T2p) { p.showPerson(); cout T1的类型为 typeid(T1).name() endl; cout T2的类型为 typeid(T2).name() endl; } void test02() { Person string, int p(猪八戒, 90); printPerson2(p); } //3、整个类模板化 templateclass T void printPerson3(T p) { cout T的类型为 typeid(T).name() endl; p.showPerson(); } void test03() { Person string, int p(唐僧, 30); printPerson3(p); } int main() { test01(); test02(); test03(); system(pause); return 0; } 总结 * 通过类模板创建的对象可以有三种方式向函数中进行传参 * 使用比较广泛是第一种指定传入的类型 ​1.3.5 类模板与继承当类模板碰到继承时需要注意一下几点当子类继承的父类是一个类模板时子类在声明的时候要指定出父类中T的类型如果不指定编译器无法给子类分配内存如果想灵活指定出父类中T的类型子类也需变为类模板**示例** C templateclass T class Base { T m; }; //class Son:public Base //错误c编译需要给子类分配内存必须知道父类中T的类型才可以向下继承 class Son :public Baseint //必须指定一个类型 { }; void test01() { Son c; } //类模板继承类模板 ,可以用T2指定父类中的T类型 templateclass T1, class T2 class Son2 :public BaseT2 { public: Son2() { cout typeid(T1).name() endl; cout typeid(T2).name() endl; } }; void test02() { Son2int, char child1; } int main() { test01(); test02(); system(pause); return 0; } 总结如果父类是类模板子类需要指定出父类中T的数据类型1.3.6 类模板成员函数类外实现**示例** C #include string //类模板中成员函数类外实现 templateclass T1, class T2 class Person { public: //成员函数类内声明 Person(T1 name, T2 age); void showPerson(); public: T1 m_Name; T2 m_Age; }; //类模板中构造函数 类外实现 templateclass T1, class T2//让编译器知道下面的T1 T2 PersonT1, T2::Person(T1 name, T2 age) { this-m_Name name; this-m_Age age; } //类模板中成员函数 类外实现 templateclass T1, class T2 void PersonT1, T2::showPerson() { cout 姓名: this-m_Name 年龄: this-m_Age endl; } void test01() { Personstring, int p(Tom, 20); p.showPerson(); } int main() { test01(); system(pause); return 0; } 总结类模板中成员函数类外实现时需要加上模板参数列表1.3.7 类模板分文件编写学习目标掌握类模板成员函数分文件编写产生的问题以及解决方式问题类模板中成员函数创建时机是在调用阶段导致分文件编写时链接不到解决解决方式1直接包含.cpp源文件解决方式2将声明和实现写到同一个文件中并更改后缀名为.hpphpp是约定的名称并不是强制**示例** person.hpp中代码 C #pragma once #include iostream using namespace std; #include string templateclass T1, class T2 class Person { public: Person(T1 name, T2 age); void showPerson(); public: T1 m_Name; T2 m_Age; }; //构造函数 类外实现 templateclass T1, class T2 PersonT1, T2::Person(T1 name, T2 age) { this-m_Name name; this-m_Age age; } //成员函数 类外实现 templateclass T1, class T2 void PersonT1, T2::showPerson() { cout 姓名: this-m_Name 年龄: this-m_Age endl; }类模板分文件编写.cpp中代码 C #includeiostream using namespace std; //#include person.h #include person.cpp //解决方式1包含cpp源文件 //解决方式2将声明和实现写到一起文件后缀名改为.hpp #include person.hpp void test01() { Personstring, int p(Tom, 10); p.showPerson(); } int main() { test01(); system(pause); return 0; } 总结主流的解决方式是第二种将类模板成员函数写到一起并将后缀名改为.hpp
返回列表