模板的概念 模板 就像一个”代码模具”,可以根据不同的类型生成 相应的代码,大大提高复用性。
模板的优势在于:
代码重用 :一份代码适用于多种类型
类型安全 :编译期检查,避免运行时错误
性能优化 :编译期生成代码,无运行时开销
灵活性 :支持特化、偏特化等高级特性
需求 :假设要实现求最大值的函数,需要支持int、double、string等类型:
1 2 3 4 int maxInt (int a, int b) { return a > b ? a : b; }double maxDouble (double a, double b) { return a > b ? a : b; }string maxString (string a, string b) { return a > b ? a : b; }
问题 :通过以上内容,可以看出代码重复、维护困难、容易出错。虽然这几个函数只是参数类型不同,但函数内的实现逻辑都是相同的,可是还是需要单独实现
函数模板
C++另一种编程思想称为泛型编程,主要利用的技术就是模板
C++提高两种模板机制:函数模板 和类模板
语法:
1 2 tmplate<typename T> 函数声明或实现
解释:
template:声明创建模板
typename:表面后面的符号是一种数据类型,可以用class代替
T:通用的数据类型,名称可以替换,通常为大写字母
需求:利用函数将两个值进行交换
需求分析:因为不知道两个值的具体类型,所以设计函数时需要将C++中所有的数据类型都包含进去,采用传统的方式只能一一列举出来。这里就可以用到模板技术了
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 #include <iostream> #include <string> using namespace std;template <typename T>void SwapTwoValue (T& a, T& b) { T temp = a; a = b; b = temp; } int main () { cout << "Before Swap" << endl; string a = "hello" ; string b = "world" ; int x = 10 ; int y = 20 ; bool flag1 = true ; bool flag2 = false ; cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "x = " << x << endl; cout << "y = " << y << endl; cout << "flag1 = " << flag1 << endl; cout << "flag2 = " << flag2 << endl; cout << "After Swap" << endl; SwapTwoValue (a, b); SwapTwoValue (x, y); SwapTwoValue (flag1, flag2); cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "x = " << x << endl; cout << "y = " << y << endl; cout << "flag1 = " << flag1 << endl; cout << "flag2 = " << flag2 << endl; return 0 ; }
函数模板注意事项 注意事项:
自动类型推导,必须推导出一致的数据类型T才可以使用
模板必须要确定出T的数据类型才可以使用
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <iostream> #include <string> using namespace std;template <typename T> void ShowValue (T& a, T& b) { cout << a << " " << b << endl; } template <typename T>void func () { cout << "hello world" << endl; } int main () { int a = 10 ; string b = "hello world" ; ShowValue (a, b); func (); func <int >(); return 0 ; }
普通函数与函数模板的区别 普通函数与函数模板的区别:
普通函数调用时可以发生自动类型转换(隐式类型转换)
函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
如果利用显示指定类型的方式,可以发生隐式类型转换
普通函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <iostream> #include <string> using namespace std;int numAdd (int a, int b) { return a + b; } int main () { int a = 10 ; int b = 20 ; cout << numAdd (a, b) << endl; char c = 'a' ; cout << numAdd (a, c) << endl; return 0 ; }
函数模板:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <iostream> #include <string> using namespace std;template <typename T>T numAdd (T a, T b) { return a + b; } int main () { int a = 10 ; int b = 20 ; cout << numAdd <int >(a, b) << endl; char c = 'a' ; cout << numAdd <int >(a,c) << endl; return 0 ; }
普通函数与函数模板的调用规则 调用规则如下:
如果函数模板和普通函数都可以实现,优先调用普通函数
可以通过空模板参数列表 来强制调用函数模板
函数模板也可以发生重载
如果函数模板可以产生更好的匹配,优先调用函数模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <iostream> #include <string> using namespace std;void print (int a, int b) { cout << "普通函数" << endl; } template <typename T>void print (T a, T b) { cout << "函数模板" << endl; } template <typename T>void print (T a, T b,T c) { cout << "函数模板重载" << endl; } int main () { int a = 10 ; int b = 20 ; print (a, b); string c = "Hello" ; string d = "World" ; print (c, d); print<>(a, b); print (a,b,30 ); char e = 'a' ; char f = 'b' ; print (e, f); return 0 ; }
函数模板的局限性 模板的通用性并不是万能的
例如:
1 2 3 4 template <class T>void f (T a,T b) { a = b; }
在上述代码中提供的赋值操作,如果传入的a
和b
是一个数组,就无法实现了
再例如:
1 2 3 4 5 template <class T>void f (T a,T b) { if (a > b){ ... } }
在上述代码中,如果T的数据类型传入的是像Person这样的自定义数据类型,也无法正常运行
因此C++为了解决这种问题,提供模板的重载,可以为这些特定的类型提供具体化的模板
之前学习过在类中通过重写运算符 的方式实现了对象之间的比较 ,现在可以通过具体化模板实现自定义类型的通用化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 #include <iostream> #include <string> using namespace std;class Person {public : string name; int age; Person (string name, int age) { this ->name = name; this ->age = age; } }; template <typename T>bool is_equal (T a, T b) { return a == b; } template <> bool is_equal <Person>(Person a, Person b) { return a.name == b.name && a.age == b.age; } void test01 () { int a = 10 , b = 10 ; bool ret = is_equal (a, b); if (ret) { cout << "a == b" << endl; } else { cout << "a != b" << endl; } } void test02 () { Person p1 ("Tom" , 10 ) ; Person p2 ("Tom" , 10 ) ; bool ret = is_equal (p1, p2); if (ret) { cout << "p1 == p2" << endl; } else { cout << "p1 != p2" << endl; } } int main () { test01 (); test02 (); return 0 ; }
分析:通过具体化模板,对Person
进行了特殊处理,所以可以让Person
对象进行比较
总结:
利用具体化的模板,可以解决自定义类型的通用化
学习模板并不是为了写模板,而是在STL能够运用系统提供的模板
类模板 类模板语法 类模板作用:建立一个通用类,类中的成员、数据类型可以不具体制定,用一个虚拟的类型来代表
语法:
解释:
template:声明创建模板
typename:表明其后面的符号是一种数据类型,可以用Class代替
T:通用的数据类型,名称可以替换,通常为大写字母
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <iostream> #include <string> using namespace std;template <class NameType , class AgeType >class Person {public : NameType name; AgeType age; Person (NameType name, AgeType age) { this ->name = name; this ->age = age; } }; int main () { Person<string,int > p1 ("Tom" , 18 ) ; cout << p1.name << " " << p1.age << endl; return 0 ; }
类模板和函数模板区别 类模板与函数模板区别主要有两点:
类模板没有自动类型推导的使用方式
类模板在模板参数列表中可以有默认参数
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <iostream> #include <string> using namespace std;template <class NameType , class AgeType = int > class Person {public : NameType name; AgeType age; Person (NameType name, AgeType age) { this ->name = name; this ->age = age; } }; void test01 () { Person<string, int > p1 ("Tom" , 10 ) ; } void test02 () { Person<string> p1 ("Tom" , 10 ) ; Person<string, float > p2 ("Bob" ,10.5 ) ; } int main () { test01 (); test02 (); return 0 ; }
类模板中成员函数创建时机 类模板中成员函数和普通类中成员函数创建时机是有区别的:
普通类中的成员函数一开始就可以创建
类模板中的成员函数在调用时才创建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <iostream> #include <string> using namespace std;class Person1 {public : void ShowPerson1 () { cout << "Show Person1" << endl; } }; class Person2 {public : void ShowPerson2 () { cout << "Show Person2" << endl; } }; template <class T >class Person3 {public : T obj; void ShowP1 () { obj.ShowPerson1 (); } void ShowP2 () { obj.ShowPerson2 (); } }; int main () { Person3<Person1> p1; p1.ShowP1 (); p1.ShowP2 (); return 0 ; }
类模板对象做函数参数 一共有三种传入方式:
指定传入的类型 — 直接显示对象的数据类型
参数模板化 — 将对象中的参数变为模板进行传递
整个类模板化 — 将这个对象类型模板化进行传递
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 #include <iostream> #include <string> using namespace std;template <class T1 ,class T2 >class Person {public : T1 name; T2 age; Person (T1 name,T2 age) { this ->name = name; this ->age = age; } void showPerson () { cout << "姓名:" << this ->name << " 年龄:" << this ->age << endl; } }; void printPerson1 (Person<string,int > &p) { p.showPerson (); } void test01 () { Person<string,int > p1 ("Tom" ,10 ) ; printPerson1 (p1); } template <class T1,class T2>void printPerson2 (Person<T1,T2> &p) { p.showPerson (); } void test02 () { Person<string,int > p1 ("Bob" ,15 ) ; printPerson2 (p1); } template <class T>void printPerson3 (T &p) { p.showPerson (); } void test03 () { Person<string,int > p1 ("Alice" ,8 ) ; printPerson3 (p1); } int main () { test01 (); test02 (); test03 (); return 0 ; }
类模板与继承 当类模板碰到继承时,需要注意以下几点:
当子类继承的父类是一个类模板时,子类在声明的时候,需要指定出父类中T的类型
如果不指定,编译器无法给子类分配内存
如果想灵活指定出父类中T的类型,子类也需要变为类模板
类模板成员函数类外实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <iostream> #include <string> using namespace std;template <class T1 , class T2 >class Person {public : T1 name; T2 age; Person (T1 name, T2 age); void showPerson () ; }; template <class T1 , class T2 >Person<T1, T2>::Person (T1 name, T2 age) { this ->name = name; this ->age = age; } template <class T1 , class T2 >void Person<T1, T2>::showPerson () { cout << "name: " << name << " age: " << age << endl; } int main () { Person<string, int > p1 ("Tom" , 10 ) ; p1.showPerson (); return 0 ; }
类模板分文件编写 问题:
类模板中成员函数创建时机是在调用截断,导致分文件编写时链接不到
解决:
解决方式1:直接包含.cpp
源文件
解决方式2:将声明和实现写到同一个文件中,并更改后缀名为.hpp
,hpp
是约定的名称,并不是强制
Person.hpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #pragma once #include <iostream> #include <string> using namespace std;template <class T1 , class T2 >class Person {public : T1 name; T2 age; Person (T1 name, T2 age); void showPerson () ; }; template <class T1 , class T2 >Person<T1, T2>::Person (T1 name, T2 age) { this ->age = age; } template <class T1 , class T2 >void Person<T1, T2>::showPerson () { cout << "name: " << name << " age: " << age << endl; }
Main.cpp文件:
1 2 3 4 5 6 7 8 9 #include "Person.hpp" int main () { Person<string, int > p1 ("Tom" , 10 ) ; p1.showPerson (); return 0 ; }
类模板与友元 全局函数类内实现 - 直接在类内声明友元即可
全局函数类外实现 - 需要提前让编译器知道全局函数的存在
类内实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <iostream> #include <string> using namespace std;template <class T1 ,class T2 >class Person { friend void printPerson1 (Person<T1, T2>& p) { cout << "姓名: " << p.name << " 年龄: " << p.age << endl; }; private : T1 name; T2 age; public : Person (T1 name,T2 age) { this ->name = name; this ->age = age; } }; void test01 () { Person<string,int > p1 ("张三" ,18 ) ; printPerson1 (p1); } int main () { test01 (); return 0 ; }
类外实现(较复杂) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <iostream> #include <string> using namespace std;template <class T1 ,class T2 >class Person ;template <class T1 ,class T2 >void printPerson2 (Person<T1, T2>& p) { cout << "姓名:" << p.name << " 年龄:" << p.age << endl; } template <class T1 ,class T2 >class Person { friend void printPerson2<>(Person<T1, T2>& p); private : T1 name; T2 age; public : Person (T1 name,T2 age) { this ->name = name; this ->age = age; } }; void test02 () { Person<string,int > p ("Tom" , 18 ) ; printPerson2 (p); } int main () { test02 (); return 0 ; }
类模板案例 案例描述:实现一个通用的数组类,要求如下:
可以对内置数据类型以及自定义数据类型的数据进行存储
将数组中的数据存储到堆区
构造函数可以传入数组的容量
提供对应的拷贝构造函数以及operator=防止浅拷贝问题
提供尾插法和尾删法对数组中的数据进行增加和删除
可以通过下标的方式访问数组中的元素
可以获取数组中当前元素个数和数组容量
通用数组类 首先是通用数组类的定义(MyArray.hpp
):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 #pragma once #include <iostream> #include <string> using namespace std;template <typename T>class MyArray {private : T* pAddress; int size; int capacity; public : MyArray (int capacity) { cout << "构造函数被调用" << endl; this ->capacity = capacity; this ->size = 0 ; this ->pAddress = new T[capacity]; } ~MyArray () { cout << "析构函数被调用" << endl; if (pAddress != NULL ) { delete [] pAddress; pAddress = NULL ; } } MyArray (const MyArray &myArray) { cout << "拷贝构造函数被调用" << endl; this ->size = myArray.size; this ->capacity = myArray.capacity; this ->pAddress = new T[myArray.capacity]; for (int i = 0 ; i < myArray.size; i++) { this ->pAddress[i] = myArray.pAddress[i]; } } MyArray &operator =(const MyArray &myArray) { cout << "operator=" << endl; if (this ->pAddress != NULL ) { delete [] this ->pAddress; this ->pAddress = NULL ; this ->size = 0 ; this ->capacity = 0 ; } this ->size = myArray.size; this ->capacity = myArray.capacity; this ->pAddress = new int [this ->capacity]; for (int i = 0 ; i < this ->size; i++) { this ->pAddress[i] = myArray.pAddress[i]; } return *this ; } void Push_Back (const T& val) { if (this ->size == this ->capacity) { return ; } this ->pAddress[this ->size] = val; this ->size++; } void Pop_Back () { if (this ->size == 0 ) { return ; } this ->size--; } T& operator [](int index) { return this ->pAddress[index]; } int Get_Size () { return this ->size; } int Get_Capacity () { return this ->capacity; } };
内置数据类型测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include <iostream> #include <string> #include "MyArray.hpp" using namespace std;template <typename T>void PrintArray (MyArray<T>& arr) { for (int i = 0 ; i < arr.Get_Size (); i++) { cout << arr[i] << " " ; } cout << endl; } void test01 () { MyArray<int > arr1 (5 ) ; MyArray<int > arr2 (arr1) ; MyArray<int > arr3 (10 ) ; arr3 = arr2; } void test02 () { MyArray<int > arr1 (5 ) ; for (int i = 0 ; i < arr1.Get_Capacity (); i++) { arr1.Push_Back (i); } PrintArray (arr1); cout << "arr1的容量为: " << arr1.Get_Capacity () << endl; cout << "arr1的元素个数为: " << arr1.Get_Size () << endl; arr1.Pop_Back (); cout << "调用一次尾删方法后: " << endl; cout << "arr1的容量为: " << arr1.Get_Capacity () << endl; cout << "arr1的元素个数为: " << arr1.Get_Size () << endl; } int main () { test02 (); system ("pause" ); return 0 ; }
自定义数据类型测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 #include <iostream> #include <string> #include "MyArray.hpp" using namespace std;template <typename T>void PrintArray (MyArray<T>& arr) { for (int i = 0 ; i < arr.Get_Size (); i++) { cout << arr[i] << endl; } } class Person {public : string name; int age; Person () { this ->name = "" ; this ->age = 0 ; } Person (string name, int age) { this ->name = name; this ->age = age; } friend ostream& operator <<(ostream& out, const Person& p) { out << "姓名:" << p.name << " 年龄:" << p.age; return out; } }; void test1 () { MyArray<Person> arr (4 ) ; Person p1 ("张三" , 18 ) ; Person p2 ("李四" , 19 ) ; Person p3 ("王五" , 20 ) ; Person p4 ("赵六" , 21 ) ; arr.Push_Back (p1); arr.Push_Back (p2); arr.Push_Back (p3); arr.Push_Back (p4); PrintArray (arr); } int main () { test1 (); system ("pause" ); return 0 ; }