大纲 函数模板和类模板 C++ 提供了函数模板(function template)。所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表,这个通用函数就称为函数模板 。凡是函数体相同的函数都可以用这个模板来代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时,系统会根据实参的类型来取代模板中的虚拟类型,从而实现不同函数的功能。
C++ 提供两种模板机制:函数模板、类模板。
模板又称之为 泛型编程
。 模板将函数或类要处理的数据类型参数化,表现为参数的多态性,称为类属。 模板用于表达逻辑结构相同,但具有数据元素类型不同的数据对象的通用行为。 类属 —— 类型参数化,又称参数模板,使得程序(算法)可以从逻辑功能上抽象,将被处理的对象(数据)类型作为参数传递。 函数模板 函数模板的定义
模板声明的语法为:template < 类型形式参数表 >
,例如 template <typename T>
类型形式参数表的语法为:typename T1 , typename T2 , …… , typename Tn
或者 class T1 , class T2 , …… , class Tn
typename
关键字和 class
关键字都可以用来定义模板参数类型,也就是说 template <typename T>
与 template <class T>
的功能和效果是完全相同的,C++ 标准允许这两种方式是为了向后兼容,并提供灵活性。函数模板的调用 myswap(a, b);
:自动数据类型推导myswap<float>(a, b);
:显式指定类型调用(推荐)函数模板的简单使用 函数模板使用案例一
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 #include <iostream> using namespace std;template <typename T>void myswap (T &a, T &b) { T temp; temp = a; a = b; b = temp; } int main () { int x = 1 , y = 2 ; myswap (x, y); printf ("x = %d, y = %d\n" , x, y); double n = 0.5 , m = 0.8 ; myswap (n, m); printf ("n = %f, m = %f\n" , n, m); char i = 'h' , j = 'e' ; myswap<char >(i, j); printf ("n = %c, m = %c\n" , i, j); return 0 ; }
程序运行输出的结果如下:
1 2 3 x = 2, y = 1 n = 0.800000, m = 0.500000 n = e, m = h
函数模板使用案例二 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 #include <iostream> using namespace std;template <typename T1>void arraySort (T1* array, int size, bool asc = true ) { if (array == NULL || size == 0 ) { return ; } T1 tmp; for (int i = 0 ; i < size; i++) { for (int j = i + 1 ; j < size; j++) { if (asc) { if (array[i] > array[j]) { tmp = array[i]; array[i] = array[j]; array[j] = tmp; } } else { if (array[i] < array[j]) { tmp = array[i]; array[i] = array[j]; array[j] = tmp; } } } } } template <typename T2>void printArray (T2* array, int size) { for (int i = 0 ; i < size; i++) { cout << array[i] << " " ; } cout << endl; } int main () { int array[] = { 32 , 16 , 29 , 9 , 43 , 53 , 23 }; int size = sizeof (array) / sizeof (*array); cout << "排序之前: " ; printArray<int >(array, size); arraySort<int >(array, size, false ); cout << "排序之后: " ; printArray<int >(array, size); cout << "------------------------------" << endl; char array2[] = { 'c' , 'z' , 'h' , 'i' , 'q' , 'm' }; int size2 = sizeof (array2) / sizeof (*array2); cout << "排序之前: " ; printArray<char >(array2, size2); arraySort<char >(array2, size2); cout << "排序之后: " ; printArray<char >(array2, size2); return 0 ; }
程序运行输出的结果如下:
1 2 3 4 5 排序之前: 32 16 29 9 43 53 23 排序之后: 53 43 32 29 23 16 9 ------------------------------ 排序之前: c z h i q m 排序之后: c h i m q z
函数模板与普通函数 函数模板和普通函数的区别:
(a) 普通函数调用时,可以发生自动类型转换(隐式类型转换)。 (b) 函数模板调用时,如果利用自动类型推导,不会发生自动类型转换(隐式类型转换)。 函数模板和普通函数的调用规则:
(a) 当函数模板和普通函数都符合调用时,C++ 编译器优先选择调用普通函数 (b) 如果函数模板可以产生一个更好的匹配,那么 C++ 编译器会选择调用函数模板 (c) 如何希望强制调用函数模板,可以使用空模板参数列表(空参数列表)的语法来限制 C++ 编译器只使用函数模板匹配,比如 mySwap<>(a, b)
,完整的示例代码如下: 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 #include <iostream> using namespace std;template <typename T>void myswap (T& a, T& b) { T tmp; tmp = a; a = b; b = tmp; cout << "模板函数被调用" << endl; } void myswap (int a, char b) { cout << "a = " << a << ", b = " << b << endl; cout << "普通函数被调用" << endl; } int main () { int a = 65 ; char c = 'z' ; myswap (a, c); myswap (c, a); myswap (a, a); myswap<>(a, a); return 0 ; }
程序运行输出的结果如下:
1 2 3 4 5 6 a = 65, b = z 普通函数被调用 a = 122, b = A 普通函数被调用 模板函数被调用 模板函数被调用
函数模板与函数重载 (a) 函数模板可以像普通函数一样被重载 (b) 如何出现重载,C++ 编译器优先选择调用普通函数 (c) 如果函数模板可以产生一个更好的匹配,那么 C++ 编译器会选择调用函数模板 (d) 如何希望强制调用函数模板,可以使用空模板参数列表(空参数列表)的语法来限制 C++ 编译器只使用函数模板匹配,比如 mySwap<>(a, b)
,完整的示例代码如下: 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" using namespace std;int Max (int a, int b) { cout << "int Max(int a, int b)" << endl; return a > b ? a : b; } template <typename T>T Max (T a, T b) { cout << "T Max(T a, T b)" << endl; return a > b ? a : b; } template <typename T>T Max (T a, T b, T c) { cout << "T Max(T a, T b, T c)" << endl; return Max (Max (a, b), c); } int main () { int a = 1 ; int b = 2 ; Max (a, b); Max ('a' , 100 ); Max<>(a, b); Max (3.0 , 4.0 ); cout << "--------------------" << endl; Max (5.0 , 6.0 , 7.0 ); return 0 ; }
程序运行输出的结果如下:
1 2 3 4 5 6 7 8 int Max(int a, int b) int Max(int a, int b) T Max(T a, T b) T Max(T a, T b) -------------------- T Max(T a, T b, T c) T Max(T a, T b) T Max(T a, T b)
函数模板的局限性 假设有如下的函数模板:
1 2 3 4 5 template <class T >void f (T a, Tb) { ... }
如果代码实现时定义了赋值操作 a = b
,但是 T 为数组,那么这种假设就不成立了。同样,如果代码里面的语句为判断语句 if(a > b)
,但 T 如果是结构体,该假设也不成立。另外,如果传入的是数组,由于数组名是地址,因此它比较的是地址,而这也不是预期所希望的操作。总之,编写的函数模板很可能无法处理某些类型。另一方面,有时候通用化是有意义的,但 C++ 语法不允许这么做。为了解决这种局限性问题,可以提供模板的重载,为这些特定的类型提供具体化的模板。
具体化模板的使用语法
语法:template<> 返回值 函数名<具体类型>(函数参数)
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 #include "iostream" using namespace std;class Person {public : Person (string name, int age) { this ->m_name = name; this ->m_age = age; } string m_name; int m_age; }; template <class T>bool myCompare (T &a, T &b) { return a == b; } void test01 () { int a = 10 ; int b = 20 ; bool resutlt = myCompare (a, b); cout << resutlt << endl; } template <> bool myCompare<Person>(Person &a, Person &b) { return a.m_age == b.m_age; } void test02 () { Person p1 ("Tom" , 23 ) ; Person p2 ("Jim" , 23 ) ; bool resutlt = myCompare (p1, p2); cout << resutlt << endl; } int main () { test01 (); test02 (); return 0 ; }
程序运行输出的结果如下:
函数模板的底层原理 编译器并不是根据函数模板,产生能够处理任意参数的函数。 编译器本质上是根据具体的调用类型,从函数模板产生不同的函数(通常称为模板函数)。 编译器会对函数模板进行两次编译,在声明的地方对函数模板代码本身进行第一次编译,在调用的地方对参数替换后的函数模板代码进行第二次编译。 类模板 类模板与函数模板的定义和使用类似,在实际项目开发中,经常有两个或多个类,其功能是相同的,仅仅是数据类型不同,为了不重复定义功能相同的类,可以使用类模板来解决这类问题。
类模板的定义
类模板用于实现类所需数据的类型参数化。 类模板在表示如数组、链表、图等数据结构显得特别重要,这些数据结构的表示和算法不受所包含的元素类型的影响。 在下述的所有代码中,template <typename T>
等价于 template <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 #include <iostream> using namespace std;template <typename T>class A {public : A (T t) { this ->t = t; } T& getT () { return this ->t; } private : T t; }; void printA (A<int >& a) { cout << a.getT () << endl; } int main () { A<int > a (100 ) ; cout << a.getT () << endl; A<int > a2 (50 ) ; printA (a2); 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 66 67 #include <iostream> #include <typeinfo> using namespace std;template <class T1 , class T2 >class Person {public : Person (T1 name, T2 age) { this ->m_Name = name; this ->m_Age = age; } void show () { cout << "name = " + this ->m_Name + ", age = " << this ->m_Age << endl; } T1 m_Name; T2 m_Age; }; void doWork1 (Person<string, int > &p) { p.show (); } void test01 () { Person<string, int > p ("Jim" , 18 ) ; doWork1 (p); } template <class T1, class T2>void doWork2 (Person<T1, T2> &p) { p.show (); } void test02 () { Person<string, int > p ("Tom" , 20 ) ; doWork2 (p); } template <class T>void doWork3 (T &p) { p.show (); } void test03 () { Person<string, int > p ("Peter" , 22 ) ; doWork3 (p); } int main () { test01 (); test02 (); test03 (); return 0 ; }
程序运行输出的结果如下:
1 2 3 name = Jim, age = 18 name = Tom, age = 20 name = Peter, age = 22
类模板与继承的使用 普通类继承类模板 在 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 #include <iostream> using namespace std;template <typename T>class A {public : A (T a) { this ->a = a; } T& getA () { return this ->a; } public : T a; }; class B : public A<int > {public : B (int a, int b) : A<int >(a) { this ->b = b; } void printB () { cout << "a = " << a << ", b = " << b << endl; } public : int b; }; int main () { A<int > a (100 ) ; cout << a.getA () << endl; B b (1 , 3 ) ; b.printB (); 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 #include <iostream> using namespace std;template <typename T>class A {public : A (T a) { this ->a = a; } T& getA () { return this ->a; } public : T a; }; template <typename T>class B : public A<T> {public : B (T a, T b) : A (a) { this ->b = b; } T& getB () { return this ->b; } private : T b; }; int main () { A<int > a (3 ) ; cout << a.getA () << endl; B<double > b (3.2 , 4.5 ) ; cout << b.getA () << endl; cout << b.getB () << endl; return 0 ; }
程序运行输出的结果如下:
类模版与友元函数的使用 特别注意
除了重载运算符 <<
、>>
必须使用友元函数之外,其他运算符的重载尽量都使用类成员函数来实现。千万不要滥用友元函数,尤其类模板与友元函数一起使用的时候,这是因为需要使用怪异的语法来解决 C++ 编译器出现的错误,且不同的 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 #include <iostream> using namespace std;template <class T1 , class T2 >class Person {public : Person (T1 name, T2 age) { this ->m_Name = name; this ->m_Age = age; } friend void printPerson (Person<T1, T2> &p) { cout << "name: " + p.m_Name + ", age: " << p.m_Age << endl; } private : T1 m_Name; T2 m_Age; }; void test01 () { Person<string, int > p ("Tom" , 20 ) ; printPerson (p); } int main () { test01 (); return 0 ; }
程序运行输出的结果如下:
友元函数的类外实现 在使用类模板和友元函数时,如果友元函数是在类外实现,则需要使用怪异的语法来解决 C++ 编译器出现的错误,且不同的 C++ 编译器表现行为不一定一致。解决 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 #include <iostream> using namespace std;template <class T1 , class T2 > class Person ;template <class T1, class T2> void printPerson (Person<T1, T2> &p) ;template <class T1 , class T2 >class Person {public : Person (T1 name, T2 age) { this ->m_Name = name; this ->m_Age = age; } friend void printPerson<>(Person<T1, T2> &p); private : T1 m_Name; T2 m_Age; }; template <class T1, class T2>void printPerson (Person<T1, T2> &p) { cout << "name: " + p.m_Name + ", age: " << p.m_Age << endl; } int main () { Person<string, int > p ("Tom" , 20 ) ; printPerson (p); return 0 ; }
程序运行输出的结果如下:
类模板函数的三种写法 值得一提的是,企业项目开发中,建议使用第一种或者第三种方式,STL 库一般都采用第一种方式。
所有的类模板函数写在类的内部(第一种) 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> using namespace std;template <typename T>class Complex {public : Complex (T a, T b) { this ->a = a; this ->b = b; } void print () { cout << "a = " << this ->a << ", b = " << this ->b << endl; } Complex operator +(Complex& c2) { Complex tmp (this ->a + c2.a, this ->b + c2.b) ; return tmp; } friend ostream& operator <<(ostream& out, Complex& c1) { cout << "a = " << c1.a << ", b = " << c1.b; return out; } friend Complex sub (Complex& c1, Complex& c2) { Complex tmp (c1.a - c2.a, c1.b - c2.b) ; return tmp; } private : T a; T b; }; int main () { Complex<int > c1 (1 , 4 ) ; Complex<int > c2 (3 , 6 ) ; c1.print (); c2.print (); Complex<int > c3 = c1 + c2; cout << c3 << endl; Complex<int > c4 = sub (c1, c2); cout << c4 << endl; return 0 ; }
程序运行输出的结果如下:
1 2 3 4 a = 1, b = 4 a = 3, b = 6 a = 4, b = 10 a = -2, b = -2
所有的类模板函数写在类的外部(第二种) 所有的类模板函数写在类的外部(写在同一个 .cpp
源文件中),当使用友元函数重载了 <<
、>>
运算符时,需要注意声明友元函数的写法 friend ostream& operator<< <T>(ostream& out, Complex& c1);
。特别注意,除了重载运算符 <<
、>>
必须使用友元函数之外,其他运算符的重载尽量都使用类成员函数来实现。千万不要滥用友元函数,尤其类模板与友元函数一起使用的时候,这是因为需要使用怪异的语法来解决 C++ 编译器出现的错误,且不同的 C++ 编译器表现行为不一定一致。 假设在类模板中滥用了友元函数,解决 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 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 #include <iostream> using namespace std;template <typename T> class Complex ;template <typename T> Complex<T> sub (Complex<T>& c1, Complex<T>& c2) ;template <typename T>class Complex {public : Complex (T a, T b); void print () ; Complex operator +(Complex& c2); friend Complex sub<T>(Complex& c1, Complex& c2); friend ostream& operator << <T>(ostream& out, Complex& c1); private : T a; T b; }; template <typename T>Complex<T>::Complex (T a, T b) { this ->a = a; this ->b = b; } template <typename T>void Complex<T>::print () { cout << "a = " << this ->a << ", b = " << this ->b << endl; } template <typename T>Complex<T> Complex<T>::operator +(Complex<T>& c2) { Complex<T> tmp (this ->a + c2.a, this ->b + c2.b) ; return tmp; } template <typename T>ostream& operator <<(ostream& out, Complex<T>& c1) { cout << "a = " << c1.a << ", b = " << c1.b; return out; } template <typename T>Complex<T> sub (Complex<T>& c1, Complex<T>& c2) { Complex<T> tmp (c1.a - c2.a, c1.b - c2.b) ; return tmp; } int main () { Complex<int > c1 (3 , 8 ) ; Complex<int > c2 (9 , 5 ) ; c1.print (); c2.print (); Complex<int > c3 = c1 + c2; cout << c3 << endl; Complex<int > c4 = sub (c1, c2); cout << c4 << endl; return 0 ; }
程序运行输出的结果如下:
1 2 3 4 a = 3, b = 8 a = 9, b = 5 a = 12, b = 13 a = -6, b = 3
所有的类模板函数写在类的外部(第三种) 所有的类模板函数写在类的外部(分开写在 .h
和 .hpp
源文件中),这里除了重载运算符 <<
、>>
必须使用友元函数之外,千万不要滥用友元函数;因为 C++ 编译器会出现编译错误,且没有很好的解决方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #pragma once #include <iostream> using namespace std;template <typename T>class Complex {public : Complex (T a, T b); void print () ; Complex operator +(Complex& c2); friend ostream& operator << <T>(ostream& out, Complex& c1); private : T a; T b; };
complex.hpp,这里的 .hpp
文件与 .cpp
文件本质上没有区别,为了方便区分意图,只是文件的后缀不一样而已 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 "complex.h" template <typename T>Complex<T>::Complex (T a, T b) { this ->a = a; this ->b = b; } template <typename T>void Complex<T>::print () { cout << "a = " << this ->a << ", b = " << this ->b << endl; } template <typename T>Complex<T> Complex<T>::operator +(Complex<T>& c2) { Complex<T> tmp (this ->a + c2.a, this ->b + c2.b) ; return tmp; } template <typename T>ostream& operator <<(ostream& out, Complex<T>& c1) { cout << "a = " << c1.a << ", b = " << c1.b; return out; }
main.cpp,特别注意,这里引入的是 .hpp
或者 .cpp
文件,而不是 .h
头文件,否则 C++ 编译器会编译失败 1 2 3 4 5 6 7 8 9 10 11 12 13 #include "complex.hpp" int main () { Complex<int > c1 (6 , 13 ) ; Complex<int > c2 (23 , 34 ) ; c1.print (); c2.print (); Complex<int > c3 = c1 + c2; cout << c3 << endl; return 0 ; }
程序运行输出的结果如下:
1 2 3 a = 6, b = 13 a = 23, b = 34 a = 29, b = 47
上面提到的,引入的是 .hpp
或者 .cpp
文件,而不是 .h
头文件,否则 C++ 编译器会编译失败。这是因为由于类模版的成员函数是在运行阶段才去动态创建的,也就是说使用 #include
指令包含 .h
头文件时,编译器不会创建成员函数的具体实现,最终导致编译器出现无法解析外部命令的错误。常用的解决方案有以下两种,约定俗成使用第一种方案(推荐)。
解决方案一:类模板函数的声明和实现分开写,但都写在同一个源文件中,并将源文件的后缀名改为 .hpp
,然后在需要使用的地方通过 #include
指令包含 .hpp
源文件,推荐使用此方式。 解决方案二:类模板函数的声明和实现分开写,而且是分开写在不同的源文件中,然后在需要使用的地方通过 #include
指令包含 .cpp
源文件,而不是包含 .h
头文件,不推荐使用此方式。 类模板中的 static 关键字 从类模板实例化的每种数据类型模板类都有自己的类模板数据成员,该数据类型的模板类的所有对象共享同一个 static
数据成员 和非模板类的 static
数据成员一样,模板类的 static
数据成员也应该在源文件范围内定义和初始化 每种数据类型的模板类都有自己单独一份的类模板的 static
数据成员副本,详见 图解分析 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 #include <iostream> using namespace std;const double pi = 3.14 ;template <typename T> class Circle {public : Circle (T radius = 0 ) { this ->m_radius = radius; this ->m_total++; } void setRadius (T radius) { this ->m_radius = radius; } T getRadius () { return this ->m_radius; } double getGirth () { return 2 * pi * this ->m_radius; } double getArea () { return pi * this ->m_radius * this ->m_radius; } static int getTotal () { return m_total; } private : T m_radius; static int m_total; }; template <typename T> int Circle<T>::m_total = 0 ;int main () { Circle<int > c1 (4 ) , c2 (6 ) ; cout << "m_total = " << Circle<int >::getTotal () << endl; cout << "radius = " << c1.getRadius () << ", girth = " << c1.getGirth () << ", area = " << c1.getArea () << endl; cout << "radius = " << c2.getRadius () << ", girth = " << c2.getGirth () << ", area = " << c2.getArea () << endl; Circle<float > c3 (3.2 ) , c4 (4.3 ) , c5 (6.2 ) ; cout << "m_total = " << Circle<float >::getTotal () << endl; cout << "radius = " << c3.getRadius () << ", girth = " << c3.getGirth () << ", area = " << c3.getArea () << endl; cout << "radius = " << c4.getRadius () << ", girth = " << c4.getGirth () << ", area = " << c4.getArea () << endl; cout << "radius = " << c5.getRadius () << ", girth = " << c5.getGirth () << ", area = " << c5.getArea () << endl; return 0 ; }
程序运行输出的结果如下:
1 2 3 4 5 6 7 m_total = 2 radius = 4, girth = 25.12, area = 50.24 radius = 6, girth = 37.68, area = 113.04 m_total = 3 radius = 3.2, girth = 20.096, area = 32.1536 radius = 4.3, girth = 27.004, area = 58.0586 radius = 6.2, girth = 38.936, area = 120.702
类模板与函数模板的区别 类模板支持默认类型,而函数模板不支持默认类型。 类模板不支持自动类型推导,而函数模板支持自动类型推导。 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 #include "iostream" using namespace std;template <class T1 , class T2 = int >class Person {public : Person (T1 name, T2 age) { this ->m_Name = name; this ->m_Age = age; } void show () { cout << "name = " + this ->m_Name + ", age = " << this ->m_Age; } T1 m_Name; T2 m_Age; }; int main () { Person<string, int > p1 ("Jim" , 20 ) ; p1.show (); Person<string> p2 ("Tom" , 20 ) ; p2.show (); return 0 ; }
程序运行输出的结果如下:
数组模板类的实战案例 下面将编写数组模板类,模拟 STL 容器的实现,同时贯穿上面所讲的 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 #pragma once #include <iostream> using namespace std;template <class T >class MyVector {public : MyVector (int size = 0 ); ~MyVector (); MyVector (const MyVector& obj); public : int getSize () ; public : T& operator [](int index); MyVector& operator =(const MyVector& obj); friend ostream& operator << <T>(ostream& out, MyVector& obj); private : T* m_space; int m_size; };
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 #include "MyVector.h" template <typename T>MyVector<T>::MyVector (int size) { this ->m_size = size; this ->m_space = new T[size]; } template <typename T>MyVector<T>::~MyVector () { if (this ->m_space) { delete [] this ->m_space; this ->m_size = 0 ; this ->m_space = NULL ; } } template <typename T>MyVector<T>::MyVector (const MyVector<T>& obj) { this ->m_size = obj.m_size; this ->m_space = new T[obj.m_size]; for (int i = 0 ; i < obj.m_size; i++) { this ->m_space[i] = obj.m_space[i]; } } template <typename T>int MyVector<T>::getSize () { return this ->m_size; } template <typename T>T& MyVector<T>::operator [](int index) { return this ->m_space[index]; } template <typename T>MyVector<T>& MyVector<T>::operator =(const MyVector<T>& obj) { if (this ->m_space) { delete [] this ->m_space; this ->m_size = 0 ; this ->m_space = NULL ; } this ->m_size = obj.m_size; this ->m_space = new T[obj.m_size]; for (int i = 0 ; i < obj.m_size; i++) { this ->m_space[i] = obj.m_space[i]; } return *this ; }; template <typename T>ostream& operator <<(ostream& out, MyVector<T>& obj) { for (int i = 0 ; i < obj.m_size; i++) { cout << obj.m_space[i] << ", " ; } return out; }
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 #pragma once #include <iostream> using namespace std;class Teacher {public : Teacher (); Teacher (int age, const char * name); Teacher (const Teacher& obj); ~Teacher (); public : Teacher& operator =(const Teacher& obj); friend ostream& operator <<(ostream& out, Teacher& obj); public : int getAge () ; char * getName () ; void setAge (int age) ; void setName (const char * name) ; private : int m_age; char * m_name; };
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 #include "Teacher.h" Teacher::Teacher () { this ->m_age = 0 ; this ->m_name = (char *)malloc (1 ); if (this ->m_name) { strcpy (this ->m_name, "" ); } } Teacher::Teacher (int age, const char * name) { this ->m_age = age; this ->m_name = (char *)malloc (strlen (name) + 1 ); if (this ->m_name) { strcpy (this ->m_name, name); } } Teacher::Teacher (const Teacher& obj) { this ->m_age = obj.m_age; this ->m_name = (char *)malloc (strlen (obj.m_name) + 1 ); if (this ->m_name) { strcpy (this ->m_name, obj.m_name); } } Teacher::~Teacher () { if (this ->m_name) { free (this ->m_name); } } Teacher& Teacher::operator =(const Teacher& obj) { if (this ->m_name) { free (this ->m_name); this ->m_name = NULL ; } this ->m_age = obj.m_age; this ->m_name = (char *)malloc (strlen (obj.m_name) + 1 ); if (this ->m_name) { strcpy (this ->m_name, obj.m_name); } return *this ; } ostream& operator <<(ostream& out, Teacher& obj) { cout << "age = " << obj.m_age << " name = " << obj.m_name; return out; } int Teacher::getAge () { return this ->m_age; } char * Teacher::getName () { return this ->m_name; } void Teacher::setAge (int age) { this ->m_age = age; } void Teacher::setName (const char * name) { if (this ->m_name) { free (this ->m_name); this ->m_name = NULL ; } this ->m_name = (char *)malloc (strlen (name) + 1 ); if (this ->m_name) { strcpy (this ->m_name, name); } }
特别注意
这里需要引入 Teacher.hpp
和 MyVector.hpp
,而不是 Teacher.h
和 MyVector.h
头文件,否则 C++ 编译器会编译失败,本质原因是由于 C++ 编译器会对模板进行两次编译导致的,详见 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 #include "Teacher.hpp" #include "MyVector.hpp" int main () { MyVector<int > v (5 ) ; for (int i = 0 ; i < v.getSize (); i++) { v[i] = i + 1 ; } cout << v << endl; MyVector<int > v2 = v; cout << v2 << endl; MyVector<int > v3 (2 ) ; v3 = v2; cout << v3 << endl; MyVector<Teacher> teachers (3 ) ; for (int i = 0 ; i < teachers.getSize (); i++) { Teacher t (i + 20 , "Jim" ) ; teachers[i] = t; } cout << teachers << endl; MyVector<Teacher*> points (4 ) ; for (int i = 0 ; i < points.getSize (); i++) { points[i] = new Teacher (25 + i, "Tom" ); } for (int i = 0 ; i < points.getSize (); i++) { Teacher* obj = points[i]; cout << "age = " << obj->getAge () << " name = " << obj->getName () << ", " ; } return 0 ; }
程序运行输出的结果如下:
1 2 3 4 5 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, age = 20 name = Jim, age = 21 name = Jim, age = 22 name = Jim, age = 25 name = Tom, age = 26 name = Tom, age = 27 name = Tom, age = 28 name = Tom,
函数模板与类模板的使用总结 模板是 C++ 类型参数化的多态工具,C++ 为此提供了函数模板和类模板 模板定义以模板声明开始,类属参数必须在模板定义中至少出现一次 同一个类属参数可以用于多个模板 类属参数可用于函数的参数类型、返回值类型和声明函数中的变量 模板由编译器根据实际的数据类型进行实例化,生成可执行代码 模板中的函数称为模板函数,实例化的类模板称为模板类 类模板可以在类层次中使用(即可以被继承) 函数模板可以使用多种方式重载