引子:如何交换两个变量的值 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 #define SWAP(t, a, b) \ do \ { \ t c = a; \ a = b; \ b = c; \ }while(0) void Swap (int & a, int & b) { int c = a; a = b; b = c; } void Swap (double & a, double & b) { double c = a; a = b; b = c; } void Swap (string& a, string& b) { string c = a; a = b; b = c; }
定义宏代码块 - 优点 : 代码复用 , 适合所有的类型 - 缺点 : 编译器不知道宏的存在,缺少类型检查
定义函数 - 优点 : 真正的函数调用 , 编译器对类型进行检查 - 缺点 : 根据类型重复定义函数,**无法代码复用
C+ + 中有没有解决方案集合两种方去的优点 ?
泛型编程 泛型编程的概念 不考虑具体数据类型的编程方式
对于 Swap 函数可以考虑下面的泛型写法
1 2 3 4 5 void Swap (T& a,T& b) { T t = a; a = b; b = t; }
Swap 泛型写法中T
的不是一个具体的数据类型,而是泛指任意的数据类型
C+ +中泛型编程 函数模板
一种特殊的函数可用不同类型进行调用
看起来和普通函数很相似 , 区別是类型可被参数化
1 2 3 4 5 6 template <typename T>void Swap (T& a,T& b) { T t = a; a = b; b = t; }
函数模板的语法规则
template 关键字 用于声明开始进行泛型编程
typename 关键字 用于声明泛指类型
函数模板的使用
1 2 3 4 5 6 7 int a = 0 ;int b = 1 ;Swap (a, b); float c = 2 ;float d = 3 ;Swap<float >(c, d);
小结
函数模板是泛型编程在 C+ + 中的应用方式之一
函数模板能够根据实参对参数类型进行推导
函数模板支持显示的指定参数类型
函数模板是 C+ + 中重要的代码复用方式
函数模板 函数模板深入理解
编译器从函数模板通过具体类型产生不同的函数
编译器会对函数模板进行两次编译 • 对模板代码本身进行编译 • 对参数替换后的代码进行编译
注意事项 : 函数模板本身不允许隐式类型转换 • 自动推导类型时 , 必须严格匹配 (严格匹配就不存在隐式类型转换了) • 显示类型指定时 , 能够进行隐式类型转换 (Swap<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 35 36 37 38 39 40 41 42 #include <iostream> #include <string> using namespace std;class Test { Test (const Test&); public : Test () { } }; template < typename T >void Swap (T& a, T& b) { T c = a; a = b; b = c; } typedef void (FuncI) (int &, int &) ;typedef void (FuncD) (double &, double &) ;typedef void (FuncT) (Test&, Test&) ;int main () { FuncI* pi = Swap; FuncD* pd = Swap; int a = 1 ;double b = 2 ; cout << "pi = " << reinterpret_cast <void *>(pi) << endl; cout << "pd = " << reinterpret_cast <void *>(pd) << endl; return 0 ; }
多参数函数模板 1 2 3 4 5 6 7 8 template <typename T1, typename T2, typename T3> T1 Add (T2 a, T3 b) { return static_cast <T1> (a + b) ; } int r = ADD<int ,float ,double >(0.5 ,0.8 );
对于多参数函数模板
无法自动 推导返回值类型
可以从左向右部分指定 类型参数
1 2 3 4 5 6 7 8 int r1 = Add<int >(0.5 , 0.8 );int r2 = Add<int ,float >(0.5 , 0.8 );int r3 = Add<int ,float ,float >(0.5 , 0.8 );
工程中将返回值参数 作为第一个类型参数 (因为函数参数类型可以由编译器自动推导,返回值参数要显示指定)
重载函数模板 函数模板可以像普通函数一样被重载
C++编译器优先考虑普通函数
如果函数模板可以产生一个更好的匹配 ,那么选择模板
可以通过空模板实参列表 限定编译器只匹配模板
1 2 int r1 = Max (1 ,2 );double r2 = Max<>(0.5 , 0.8 );
注意<>
限定了编译器只匹配函数模板,不要去考虑其他普通函数了。
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 #include <iostream> #include <string> using namespace std;template < typename T >T Max (T a, T b) { cout << "T Max(T a, T b)" << endl; return a > b ? a : b; } 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, 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 ; cout << Max (a, b) << endl; cout << Max<>(a, b) << endl; cout << Max (3.0 , 4.0 ) << endl; cout << Max (5.0 , 6.0 , 7.0 ) << endl; cout << Max ('a' , 100 ) << endl; return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 fengyun@ubuntu:~/share$ ./test int Max (int a, int b) 2 T Max (T a, T b) 2 T Max (T a, T b) 4 T Max (T a, T b, T c) T Max (T a, T b) T Max (T a, T b) 7 int Max (int a, int b) 100
类模板 C++STL库就包括许多的函数模板和类模板
类模板的概念和意义
一些类主要用于存储和组织数据元素
类中数据组织的方式和数据元素的具体类型无关
如 : 数组类,链表类,Stack 类,Queue 类,等
C++ 中将模板的思想应用于类 , 使得类的实现不关注数据元素的具体类型 , 而只关注类所需要实现的功能。
C++的类模板 C+ +中的类模板
以相同的方式处理不同的类型
在类声明前使用 template 进行标识
< typename T >用于说明类中使用的泛指类型 T
1 2 3 4 5 template <typename T>class Operator {public : T op (T a,T b) ; };
类模板的应用
只能显示指定具体类型 , 无法自动推导
使用具体类型 定义对象
1 2 3 4 5 6 Operator<int > op1; operator <string> op2;int i = op1.op (1 ,2 );string s = op2.op ("feng" ,"yun" );
声明的泛指类型 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 #include <iostream> #include <string> using namespace std;template <typename T>class Operator {public : T add (T a,T b) { return a + b; } T minus (T a,T b) { return a - b; } T multiply (T a,T b) { return a * b; } T divide (T a,T b) { return a / b; } }; int main () { Operator<int > op1; cout << op1.add (1 ,2 ) << endl; Operator<string> op2; cout << op2.add ("feng" ,"yun" ) << endl; cout << op2.minus ("feng" ,"yun" ) << endl; return 0 ; }
编译运行报错
1 2 3 test.cpp:13:18: error: no match for ‘operator-’ (operand types are ‘std::__cxx11::basic_string<char>’ and ‘std::__cxx11::basic_string<char>’) 13 | return a - b; | ~~^~~
这是因为string类内部没有实现operator -
两个string类的相减在C++中没有实现,必然报错。 类模板也是经过两次编译的,第一次是对类模板的语法进行检查, 第二次编译是分布编译的,针对每个独立成员函数来编译,包括构造函数和op2.add,op2.minus,针对minus的二次编译就过不了了。
因此我们应该重载string 类对象的operator -
操作符。 当编译器执行到op2.minus()发现可以使用重载函数。
类模板的工程应用
- 类模板必须在头文件中定义
- 类模板不能分开实现在不同的文件中
- 类模板外部定义的成员函数需要加上模板< > 声明
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 #ifndef _OPERATOR_H_ #define _OPERATOR_H_ template < typename T >class Operator { public : T add (T a, T b) ; T minus (T a, T b) ; T multiply (T a, T b) ; T divide (T a, T b) ; }; template < typename T >T Operator<T>::add(T a, T b) { return a + b; } template < typename T >T Operator<T>::minus(T a, T b) { return a - b; } template < typename T >T Operator<T>::multiply(T a, T b) { return a * b; } template < typename T >T Operator<T>::divide(T a, T b) { return a / b; } #endif
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <iostream> #include <string> #include "Operator.h" using namespace std;int main () { Operator<int > op1; cout << op1.add (1 , 2 ) << endl; cout << op1.multiply (4 , 5 ) << endl; cout << op1.minus (5 , 6 ) << endl; cout << op1.divide (10 , 5 ) << 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 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 #include <iostream> #include <string> using namespace std;template < typename T1, typename T2 > class Test { public : void add (T1 a, T2 b) { cout << "void add(T1 a, T2 b)" << endl; cout << a + b << endl; } }; template < typename T1, typename T2 > class Test < T1*, T2* > { public : void add (T1* a, T2* b) { cout << "void add(T1* a, T2* b)" << endl; cout << *a + *b << endl; } }; template < typename T > class Test < T, T > { public : void add (T a, T b) { cout << "void add(T a, T b)" << endl; cout << a + b << endl; } void print () { cout << "class Test < T, T >" << endl; } }; template < > class Test < void *, void * > { public : void add (void * a, void * b) { cout << "void add(void* a, void* b)" << endl; cout << "Error to add void* param..." << endl; } }; int main () { Test<int , float > t1; Test<long , long > t2; Test<void *, void *> t3; t1.add (1 , 2.5 ); t2.add (5 , 5 ); t2.print (); t3.add (NULL , NULL ); Test<int *, double *> t4; int a = 1 ; double b = 0.1 ; t4.add (&a, &b); 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 68 69 70 71 72 73 74 75 76 77 78 79 #include <iostream> #include <string> using namespace std;template < typename T1, typename T2 > class Test { public : void add (T1 a, T2 b) { cout << "void add(T1 a, T2 b)" << endl; cout << a + b << endl; } }; class Test_Void { public : void add (void * a, void * b) { cout << "void add(void* a, void* b)" << endl; cout << "Error to add void* param..." << endl; } }; template < typename T > bool Equal (T a, T b) { cout << "bool Equal(T a, T b)" << endl; return a == b; } template < > bool Equal<double >(double a, double b){ const double delta = 0.00000000000001 ; double r = a - b; cout << "bool Equal<double>(double a, double b)" << endl; return (-delta < r) && (r < delta); } bool Equal (double a, double b) { const double delta = 0.00000000000001 ; double r = a - b; cout << "bool Equal(double a, double b)" << endl; return (-delta < r) && (r < delta); } int main () { cout << Equal ( 1 , 1 ) << endl; cout << Equal<>( 0.001 , 0.001 ) << endl; return 0 ; }
当需要重载函数模板时,优先考虑使用模板特化 ; 当模板特化无法满足需求,再使用函数重载 !
小结
类模板可以定义任意多个不同的类型参数
类模板可以被部分特化和完全特化
特化的本质是模板的分开实现
函数模板只支持完全特化
工程中使用模板特化代替类( 函数 ) 重定义
模板参数可以是数值型参数(非类型参数)
数值型模板参数的限制
- 变量 不能作为模扳参数
- 浮点数 不能作为模板参数(浮点数在计算机内部表示不精确)
- 类对象 不能作为模板参数
本质 : 模板参数是在编译阶段被处理的单元 , 因此在编译阶段必须准确无误的唯一确定 。
最高效的方式求1+2+3+..+N的和 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;template < typename T, int N > void func () { T a[N] = {0 }; for (int i=0 ; i<N; i++) { a[i] = i; } for (int i=0 ; i<N; i++) { cout << a[i] << endl; } } template < int N > class Sum { public : static const int VALUE = Sum<N-1 >::VALUE + N; }; template < > class Sum < 1 >{ public : static const int VALUE = 1 ; }; int main () { cout << "1 + 2 + 3 + ... + 10 = " << Sum<10 >::VALUE << endl; cout << "1 + 2 + 3 + ... + 100 = " << Sum<100 >::VALUE << endl; return 0 ; }
编译时就能确定1+2+3+…+N的值。秀的头皮发麻。
typename和class 历史上的原因。 。。
早期的 C+ + 直接复用 class 关键字 来定义模板
但是泛型编程针对的不只是类类型
class 关键字的复用使得代码出现二义性
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 #include <iostream> #include <string> using namespace std;int a = 0 ;class Test_1 { public : static const int TS = 1 ; }; class Test_2 { public : struct TS { int value; }; }; template < class T > void test_class () { typename T::TS *a; } int main (int argc, char *argv[]) { test_class<Test_2>(); return 0 ; }
typename 诞生的直接诱因
自定义类类型内部的嵌套类型
不同类中的同一个标识符可能导致二义性
编译器无法辨识标识符究竟是什么
typename 的作用 :
在模板定义中声明泛指类型
明确告诉编译器其后的标识符为类型