Presentation is loading. Please wait.

Presentation is loading. Please wait.

第五章 模 板.

Similar presentations


Presentation on theme: "第五章 模 板."— Presentation transcript:

1 第五章 模 板

2 主要内容 模板的概念 函数模板和模板函数 类模板和模板类

3 怎么改进如下代码?

4 5.1 模板的概念 [引例]: 可以看出,这些函数版本的功能都是相同的,只是参数类型和函数返回类型不同。
int max( int x, int y) { return (x>y) ? x: y; } double max( double x, double y) char max( char x, char y) 可以看出,这些函数版本的功能都是相同的,只是参数类型和函数返回类型不同。 那么能否为这些函数只写出一套代码呢? C++解决这个问题的一个方法就是使用模板。

5

6 函数模板 函数模板的定义和模板函数的生成 1) 定义函数模板的一般形式:
template <class 类型参数名1, class 类型参数名 2, …> 函数返回值类型 函数名(形参表) {    //函数体 } 说明函数模板的关键字。 关键字class(或typename)后面的类型参数名是模板形参,它可以代表基本数据类型,也可以代表类。

7 函数模板

8 两种解决方法 1、强制类型转换 2、显示给出模板实参

9 第三种解决方法

10 字符串比较大小?

11 函数模板特化

12 类模板? C++STL库 标准模板库 (STL,Standard Template Library)

13 STL

14 vector push_back()可以将一个元素添加到容器的末尾

15 类模板的定义 定义类模板,包含两方面内容: 定义类; 在类定义体外定义体内未定义的成员函数。 1) 定义类的一般形式:
template<class T1, class T2,……> //模板声明 class Name { … //类的定义 };

16 向量类模板定义 template<class T> class Vector { T *data; int size;
public: Vector(int i) { data=new T[i]; } ~Vector() { delete[] data; } T &operator[](int i) { return data[i]; } };

17 既可以从类模板派生出类模板,也可以派生出普通类(非模板类)。主要有以下几种形式:
类模板的派生 既可以从类模板派生出类模板,也可以派生出普通类(非模板类)。主要有以下几种形式: 从类模板派生出类模板; 从类模板派生出普通类; 从普通类派生出类模板。

18 1) 从类模板派生出类模板 从类模板派生出新的类模板的格式如下所示: template <class T> class Base
{ …… }; template <class T > class Derived: public Base <T> 与定义一般派生类的格式类似,只是在指出它的父类时要加上模板参数。如,Base <T>。 [例]:见例exec11.6。 从单链表类模板派生出一个集合类模板

19 2) 从类模板派生出普通类 从类模板派生出一个普通类的格式如下所示: template <class T> class Base
{ …… } ; class Derived: public Base<int> 首先,作为新派生出来的普通类的父类,必须是类模板实例化后生成的模板类。例如上面的Base<int>; 其次,在派生类定义之前不需要模板声明语句template <class T>。 [例]:见例exec11.7。

20 template <class T> class Derived:public Base { T data; …… }
3) 从普通类派生出类模板 [例]: class Base { …… }; template <class T> class Derived:public Base { T data; …… }

21 利用模板机制可以显著减少冗余信息,能大幅度地节约程序代码,进一步提高面向对象程序的可重用性和可维护性 模板概念是对类属概念的继承与发展。

22 1. 模板的概念 在C++中,模板是实现代码重用机制的一种工具,它可以实现类型参数化,即把类型定义为参数,从而实现代码的可重用性。
C++程序由类和函数组成,C++中的模板也分为类模板和函数模板。 [例]: template<class T> T max( T x, T y) { return (x>y) ? x : y; }

23 2. 函数模板 所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型(部分或全部)不具体指定,用一个虚拟的类型(如:T)来代替,这个通用函数就称为函数模板。 T max( T x, T y) { } 3. 模板函数 在定义了一个函数模板后,当编译系统发现有一个对应的函数调用时(如:max(1,2)),将根据实参中的类型来确认是否匹配函数模板中对应的形参,然后生成一个重载函数,该函数的定义与函数模板的函数定义体相同,称之为模板函数。 int max( int x, int y) { }

24 函数模板是模板的定义,定义中用到通用类型参数。 模板函数是实实在在的函数定义,它由编译系统在遇到具体函数调用时所生成,具有程序代码。
[比较]:函数模板和模板函数的区别 函数模板是模板的定义,定义中用到通用类型参数。 模板函数是实实在在的函数定义,它由编译系统在遇到具体函数调用时所生成,具有程序代码。 同样,类模板是模板的定义,不是一个实实在在的类,其定义也用到通用类型参数。在定义了一个类模板后,可以创建类模板的实例,即生成模板类。

25 4. 模板、模板函数、模板类和对象之间的关系 模板 (函数模板和类模板) 模板函数 模板类 对象 实例化

26 5.2 函数模板和模板函数 函数模板的定义和模板函数的生成 1) 定义函数模板的一般形式:
template <class 类型参数名1, class 类型参数名 2, …> 函数返回值类型 函数名(形参表) {    //函数体 } 说明函数模板的关键字。 关键字class(或typename)后面的类型参数名是模板形参,它可以代表基本数据类型,也可以代表类。

27 2. 函数模板的使用 模板类型并不具有隐式的类型转换
例如在int与char之间、float与int之间、float与double之间等的隐式类型转换。

28 由于这3条语句的实参的类型与形参不一致,而max模板参数T的各实参之间必须保持一致的类型,因此出现编译错误。
[分析]: 由于这3条语句的实参的类型与形参不一致,而max模板参数T的各实参之间必须保持一致的类型,因此出现编译错误。 例如,max(i1,c1) 系统找不到与max(int, char)相匹配的函数定义。 //错误

29 [解决方法]: 采用强制类型转换。 [例]:将调用语句max(i1, c1)改写为 max(i1, int(c1))
显式给出模板实参,强制生成对特定实例的调用。具体地说,就是在调用格式中要插入一个模板实参表。 [例]: cout<<max<int>(i1,x1)<<endl; 其中,紧跟在函数名max后的<int>就是模板实参表,通过它通知编译系统生成对形如int max(int a, int b)的函数实例的调用。这样,在调用过程中,double型的参数x1将被自动转换成int型。

30 [函数模板、重载的普通函数同时存在时函数调用顺序] 编译程序在处理这种情况时: 首先寻找参数完全匹配的普通函数;若无转下步
然后再寻求参数完全匹配的函数模板,使其实例化为模板函数供调用;若无转下步 最后通过类型自动转换看是否能达到与普通函数的参数完全匹配。类型自动转换:隐式类型转换规则 C/C++中自动类型转换的规则 2007年9月16日 23:04 in C/C++ , 5517 阅读 C++定义了一组内置类型对象之间的标准转换,在必要时它们被编译器隐式地应用到对象上。 隐式类型转换发生在下列这些典型情况下: 1. 在混合类型的算术表达式中 在这种情况下最宽的数据类型成为目标转换类型,这也被称为算术转换,例如: int ival = 3; double dval = ; //ival 被提升为double类型: 3.0 ival+dval; 2.用一种类型的表达式赋值给另一种类型的对象 在这种情况下目标转换类型是被赋值对象的类型。例如在下面第一个赋值中文字常量0的类型是int。它被转换成int*型的指针表示空地址。在第二个赋值中double类型的值被截取成int型的值。 // 0被转换成int*类型的空指针值 int *pi = 0; //dval被截取为int值 3 ival = daval; 3.  把一个表达式传递给一个函数,调用表达式的类型与形式参数的类型不相同 在这种情况下目标转换类型是形式参数的类型。例如: extern double sqrt( double); //2 被提升为 double类型2.0 cout<< "The square root of 2 is "<< sqrt(2) <<endl; 4.从一个函数返回一个表达式的类型与返回类型不同 在这种情况下返回的表达式类型自动转换成函数的返回类型。例如: double difference(int ival1, int ival2) {     //返回值被提升为 double类型    return ival1- ival2; } 算术转换保证了二元操作符,如加法或乘法的两个操作数被提升为共同的类型,然后再用它表示结果的类型。两个通用的直到原则如下: (1) 为防止精度损失,如果必要的话,类型总是被提升为较宽的类型 (2) 所有含有小于整型的有序类型的算术表达式在计算之前其类型都会被转换成整型。 规则的定义如上面所述,这些规则定义了一个类型转换层次结构。我们从最宽的类型long double开始。 如果一个操作数的类型是long double,那么另一个操作数无论是什么类型都要被转换成long double。例如在下面的表达式中,字符常量小写字符a将被提升为long double,它的ASCII码值为97,然后再被加到long double型的文字常量上: L + 'a'; 如果两个操作数都不是long double型,那么若其中一个操作数的类型是double 型,则另一个就将被转换成double型,例如: int ival; float fval; double dval; //在计算加法前fval 和 ival都被转换成double dval + faval + ival; 类似地,如果两个操作数都不是double型而其中一个操作数是float 型,则另一个被转换成float型。例如: char cval; int ival; float fval; //在计算加法前ival和cval都被转换成double cval+fval+ival;   否则如果两个操作数都不是3种浮点类型之一,它们一定是某种整值类型。在确定共同的目标提升类型之前,编译器将在所有小于int的整数类型上施加一个被称为整值提升(integral promotion)的过程。 在进行整值提升时类型char、signed char、unsigned char和short int都被提升为类型 int。如果机器上的类型空间足够表示所有unsigned short 型的值,这通常发生在short用半个字而int用一个字表示的情况下,则unsigned short int也被转换成int,否则它会被提升为unsigned int。wchar_t和枚举类型被提升为能够表示其底层类型(underlying type)和所有值的最小整数类型。例如已知如下枚举类型: enum status {bad, ok}; 相关联的值是0和1。这两个值可以但不是必须存放在char类型的表示中。当这些值实际上被作为char类型来存储时,char代表了枚举的底层类型,然后status的整体提升将它的题曾类型转换为int。在下列表达式中: char cval; bool found; enum mumble{m1,m2,m3} mval; unsigned long ulong; cval + ulong ; ulong+found; mval+ulong; 在确定两个操作数被提升的公共类型之前,cval found和mval都被提升为int类型。 一旦整体提升执行嗯完毕,类型比较就又一次开始。如果一个操作数是unsigned long型,则第二个转换也被转换成unsigned long型。在上面的例子中所有被加到ulong上的3个对象都被提升为unsigned long型。如果两个操作数的类型都不是unsigned long而其中一个操作数是long型,则另一个也被转换成long型。例如: char cval; long lval; // 在计算加法前cval和1024都被提升为long型 cval+1024+lval; long类型的一般转换有一个例外。如果一个操作数是long型而另一个是unsigned int型,那么只有在机器上的long型的长度足以存放unsigned int的所有值时(一般来说,在32位操作系统中long型和 int 型都用一个字长表示,所以不满足这里的假设条件),unsigned int才会被转为long型,否则两个操作数都被提升为 unsigned long 型。若两个操作数都不是long型而其中一个是unsigned int型,则另一个也被转换成unsigned int型,否则两个操作数一定都是int型。

31

32 :隐式类型转换规则:为防止精度损失,如果必要的话,类型总是被提升为较宽的类型

33 实现二分查找 如果想该函数适用于所有类型的数组? 用函数模板实现!

34 模板函数

35 不用具体的数组名称和尺寸,用指针来指出第一个元素和最后元素的指针
*first、*last指针的类型为T,现在想进一步:让*first、*last指针的类型为T无关,如何做?

36 使用STL中的迭代器(Iterator, [ɪtə‘reɪtə] )-泛化型指针 进一步对. first、
使用STL中的迭代器(Iterator, [ɪtə‘reɪtə] )-泛化型指针 进一步对*first、*last进行抽象,它们可以为任意类型的指针,即它们和类型T无任何关系

37 测试代码: int main() { int x[10]={1,2,3,4,5,6,7,8,9,10}; int search_value=16; int *i= binarySearch(&x[0],&x[10],search_value); if (i==&x[10]) cout <<"值未找到!"; else cout <<"值已找到!"; return 0; }

38 将这个算法用到STL提供的容器vector(向量)
int main() { int x[10]={1,2,3,4,5,6,7,8,9,10}; int search_value=5; vector<int> vec(x,x+10); vector<int>::iterator iter; iter= binarySearch(vec.begin(),vec.end(), search_value); if (iter==vec.end()) cout <<"值未找到!"; else cout <<"值已找到!"; return 0; } 事实上,STL 中,算法就是按照以上方法定义的

39 函数模板总结 函数模板是对一组函数的描述,它不是一个实实在在的函数,编译系统并不产生任何执行代码。
当编译系统在程序中发现有与函数模板中相匹配的函数调用时,便生成一个重载函数,该重载函数的函数体与函数模板的函数体相同。这个根据函数模板生成的重载函数称为模板函数。

40 1. 类模板的定义 定义类模板,包含两方面内容: 定义类; 在类定义体外定义体内未定义的成员函数。 1) 定义类的一般形式:
template<class T1, class T2,……> //模板声明 class Name { … //类的定义 };

41 2) 在类定义体外定义成员函数 如果在类定义体外定义成员函数时,如果该成员函数中有模板参数,则需先进行模板声明,而且用类模板名,而不是用类名来限定函数名,即在函数名前的类名后缀上“<T>”。其一般形式为: template <class 类型参数名1, class 类型参数名2, …> 函数返回值类型 类名<类型参数名1, 类型参数名 2, …>::成员函数名(形参表) { … //函数体 }

42 [例2]:一个单链表类模板的定义。

43 [例]:类模板List中成员函数Add()和Remove()在类体外定义。
template<class T> void List<T>::Add(T& t) // 定义增加结点的成员函数 { Node* temp=new Node; // 向堆内存申请一个结点的内存空间 temp->data=t; // 将数据t赋给新结点的data数据成员 temp->pNext=pHead; // 将链表头指针赋给新结点的pNext pHead=temp; // 将新结点的地址赋给头指针 } void List<T>::Remove(T& t) // 定义删除结点的成员函数 ……

44 2. 类模板的使用 (1)使用类模板时首先把它实例化成一个具体的类(即模板类)。 把类模板实例化为模板类的格式如下: 类名<具体类型名> (2)然后再说明模板类的对象并使用这些对象完成所需要的功能。

45 与函数模板不同的是:函数模板的实例化是由编译程序在处理函数调用时自动完成的,而将类模板实例化为对象时必须由程序员在程序中显式地指定。其实例化为对象的一般形式是:
类名 <实际的数据类型1, 实际的数据类型2, …> 对象名;

46 表示将类模板List的类型参数T全部替换成int 型,从而创建一个具体的整型链表类,并生成该具体类的一个对象intList。
[例]: List<int> intList; 表示将类模板List的类型参数T全部替换成int 型,从而创建一个具体的整型链表类,并生成该具体类的一个对象intList。 List<char> charList; 表示将类模板List的类型参数T全部替换成char 型,从而创建一个具体的字符链表类,并生成该具体类的一个对象charList。

47 类模板的特化 10>5 c<a CHINA>WUHAN (error!)

48 Note. 类模板全特化时,需要特化类模板的所有成员函数
分析 Why? 10>5,正确 c<a,正确 CHINA>WUHAN (error!) 因为:obj3的T参数为char *,Compare此时为char *的比较,即地址的比较! 解决方案? 当模板参数为char *时,对类模板特化 template<> class MyClass<char *> { private: char * m_data; public: MyClass(char * str): { …p283…} void Compare(T x) { if (strcmp(m_data,x)>0) cout<< m_data<<“>”<<x<<endl; else if (strcmp(m_data,x)=0) cout<< m_data<<“=”<<x<<endl; else cout<< m_data<<“<”<<x<<endl; } } Note. 类模板全特化时,需要特化类模板的所有成员函数

49 5.4 类模板和友元函数模板 在类模板中可以说明友元函数模板。 三种方式: 在模板类内部声明友元的函数模板
在模板类内部声明对应版本的友元函数模板实例化。 需要前置声明。 这种方式是最为合理的方式 在模板类内部直接声明友元函数,不涉及函数模板

50 1)在模板类内部声明友元的函数模板 //使用自定义<U>实例化,即:参数可以不同。可以:

51 2)在模板类内部声明对应版本的友元函数模板实例化
//使用<T>实例化,在类外实现

52 3)在模板类内部直接声明友元函数 template <typename T> class C { private:
T m[5]; public: friend void foo(const C<T>& t) for (int i = 0; i < 5; ++i) cout << t.m[i] << endl; } };

53 5.4 string 类字符串处理

54 问题 如何从下面代码中提取“个人网站”的网址?

55 代码

56 1 string类对象的定义 字符串是C++中一种很重要的数据,C++标准类库提供了字符串类string,为字符串处理提供了大量的操作。
C++提供了类模板basic_string, 通过: typedef basic_string<char> string; 将类模板basic_string具体化成字符串类string。 类模板basic_string定义: template < class CharType, class Traits=char_traits<CharType>, class Allocator=allocator<CharType> > class basic_string { // }

57 string类封装了字符串的属性与方法,使对字符串处理变得方便。
使用string 类需要包括头文件string: #include <string>

58 字符串类构造函数的原型与功能列表 String类构造函数原型 string (const string& rhs,
构造函数的原形 作 用 string ( ) 默认构造函数,建立长度为0的字符串 string (const string& rhs) 拷贝构造函数,利用已存在的串rhs初始化新串 string (const string& rhs, unsigned pos,unsigned n) 将存在的串rhs的从位置pos开始,取n个字符初始化新串(位置编号从0开始) string (const char *) 用字符数组s初始化新串 string (const char * s,unsigned n) 用字符数组s前n个字符初始化新串 string (unsigned n,char c) 将字符c重复n次作为新串的值

59 string类成员函数 string类提供了丰富的成员函数,每个成员函数又有多种重载形式。 string类常用成员函数 成员函数的原型
功 能 unsigned length ( ) const 返回本字符串对象的长度 unsigned size( ) const 返回本字符串对象的大小 string& append(const char *s);string& append(const char *s, unsigned n);string& append(const string& str, unsigned pos, unsigned n); 将字符串s附加到本串尾 将字符串s的n个字符附加到本串尾 将字符串对象str从pos开始的的n个字符附加到本串尾 string& assign(const string& str, unsigned pos, unsigned n); 将字符串对象str从pos开始的的n个字符赋给本串 int compare(const string& str) const 本串与str比较,本串<str返回负数;本串==str返回0;本串> str返回正数 string& insert(unsigned p0, const string& str, unsigned pos, unsigned n); 将字符串对象str从pos开始的的n个字符插入到本串p0处 string substr(unsigned pos=0,unsigned n=npos) const 返回从pos开始n个字符构成的字符串对象 unsigned find(const string& str,unsigned pos = 0) const 在本串中查找str, 返回第一次出现的位置;未找到,返回string::npos string& replace(unsigned p0, unsigned n0, const string& str); 用str替换本串中从位置p0开始的n0个字符 void swap(string& str) 交换本串与str

60 /**************************************************************
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 23 24 25 /************************************************************** *        p12_2.cpp           * *  string类的成员函数的使用,字符串查找与替换   * **************************************************************/ #include <string> #include <iostream> using namespace std; int main() { string text("I like c++, I use c++ programming."); //文本 string newstr; //新串 string sstr; //待查串 int pos; //存放查找到串的位置 cout<<"Input string and new string:"; cin>>sstr>>newstr; if((pos=text.find(sstr))==string::npos) //未查找到 cout<<sstr<<" not found in \""<<text<<"\""<<endl; else { cout<<"old string: "<<text<<endl; text.replace(pos,sstr.length(),newstr); cout<<"new string: "<<text<<endl; } return 0; 2.链表类模板 运行结果: Input string and new string:c++ Java↙ old string: I like c++, I use c++ programming new string: I like Java, I use c++ programming

61 string类重载了多个操作符用于对字符串进行操作
string类操作符operators 操作符 示 例 功 能 + S+T 将S与T连成一个新串 = T=S 以S更新T += T+=S T=T+S ==, !=, <, <=, >, >= T==S, T!=S ,T<S, T<=S, T>S, T>=S 将T串与S串进行比较 [ ] S[ i ] 存取串中第i个元素 << cout<<S 将串S插入出流对象 >> cin>>S 从输入流对象提取串给S

62 /******************************************* * p12_3.cpp *
2.链表类模板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 23 24 /******************************************* * p12_3.cpp * * string类的操作符将字符串排序 * *******************************************/ #include <string> #include <iostream> using namespace std; int main() { const int n=6; string line[]={"A","above","a","an","about","1234"}; for (int i=0;i<n-1;i++) for (int j=i+1;j<n;j++) if (line[i]>line[j]) //如果前面字串比后面大 { // line[i].swap(line[j]); 交换 string temps; temps=line[i]; line[i]=line[j]; line[j]=temps; } for(i=0;i<n;i++) cout<<line[i]<<endl; return 0; 运行结果: 1234 A a about above an

63 ok C++ is ok! C++ is ok! str3<str5 str5:powerful C++ is powerful!
2.链表类模板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 23 24 /************************************************* * 程序名:p12_4.cpp * * 功 能:string类的字符串运算符的应用 * *************************************************/ #include <iostream> #include <string> using namespace std; int main(){ string str1("C++"); string str2("is"); string str3,str4; string str5("powerful"); cin>>str3; //字符串输入 cout<<str1<<" "<<str2<<" "<<str3<<"!"<<endl; //string对象输出 str4=str1+" "+str2+" "+str3+"!"; //字符串连接 cout<<str4<<endl; if(str3<str5) //字符串比较 cout<<"str3<str5"<<" str5:"<<str5<<endl; string str6(str5); //字符串赋值 str6=str1+" "+str2+" "+str6+"!"; cout<<str6<<endl; return 0; } 运行结果: ok C++ is ok! C++ is ok! str3<str5 str5:powerful C++ is powerful!

64 12.4 string类串位置指针 字符串可以看成是字符序列。在泛型编程中,对序列进行遍历的访问使用迭代器进行。 指针与迭代器的区别:
指针:有类型,类型与指向的元素相同。通过指针可以访问指向的元素 迭代器:迭代器是对指针的泛化,可以指向不同数据类型构成的序列中的元素。迭代器提供对一个容器中的对象的访问方法,并且定义了容器中对象的范围。迭代器就如同一个指针。事实上,C++的指针也是一种迭代器。只要定义了一个指向容器(或序列)的迭代器,则它就可以指向该容器(或序列)中的任何元素,而不管这个元素的数据类型如何。 string类提供了指向string类串中字符的位置指针(迭代器iterator), 用于访问单个字符、向前和向后遍历字符串。迭代器可以进行加、减运算,但是不检查指向的位置是否越界。

65 string类串位置指针(迭代器)的类型有:
string::iterator //指针类型,用于指向串对象 string::const_iterator    //常指针类型,用于指向常串对象 string::reverse_iterators  //反向指针类型,用于指向串对象 string::const_reverse_iterators //常反向指针类型,用于指向常串对象

66 string类串位置指针 string类的迭代器的成员函数 成员函数的原型 功 能
功 能 const_iterator begin() const;iterator begin(); 返回常串对象第1个字符的位置 返回第1个字符的位置 const_iterator end() const;iterator end(); 返回常串对象最后一个字符的位置 返回最后一个字符的位置 const_reverse_iterator rbegin() const;reverse_iterator rbegin(); 逆向返回常串对象第1个字符的位置 逆向返回第1个字符的位置 const_reverse_iterator rend() const;reverse_iterator rend(); 逆向返回常串对象最后一个字符的位置 逆向返回最后一个字符的位置

67 ABCDEFGHI ABCDEFGHI IHGFEDCBA
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 /************************************************** * p12_5.cpp * * string类指针(迭代器)的使用,将一个串颠倒    * **************************************************/ #include <string> #include <iostream> using namespace std; int main() { const string string1=("ABCDEFGHI"); string string2(string1); int length=string1.length(); cout<<string1<<endl; cout<<string2<<endl; string::const_reverse_iterator itr1=string1.rbegin(); string::iterator itr2=string2.begin(); for(int i=0;i<length; i++) itr2[i]=itr1[i]; // *(itr2+i)=*(itr1+i); cout<<itr2<<endl; return 0; } 2.链表类模板 ABCDEFGHI ABCDEFGHI IHGFEDCBA 运行结果:

68 [例]:将求最大值的函数max()定义成函数模板。
template <class T> //模板声明 T max(T x, T y) //模板定义体 { return (x>y) ? x:y; } 其中,T是模板形参,它既可以取系统预定义的数据类型,也可以取用户自定义的类型(class)。

69 2) 模板函数的生成: 定义了函数模板后,程序中并没有得到真正的函数代码。只有用一个具体的数据类型来代替上面的类型参数T以后,即将模板具体化后,系统才会生成特定于具体数据类型的程序代码(模板函数)。

70 [例]: 当程序中有如下语句时, int i; i=max(2, 30); 编译系统就会自动生成具有整型参数和返回值的函数代码:
int max( int x, int y) { return (x>y) ? x: y; } 然后将它插入到程序中,这个生成的函数称为模板函数。 如果再有一句对整型参数的函数调用,系统就不会再次生成函数代码,而是直接使用已经生成的函数代码了。

71 同样,如果程序中又有了这么一句函数调用:
double x=max(12.3,48.5); 编译系统会先看有没有已经从模板生成了函数代码,如果没有,就生成具有双精度浮点型参数的函数代码: double max(double x , double y) { return (x>y)?x:y; }

72 [说明]: 一般来说,当编译程序看到对模板函数的调用语句时,就会根据函数模板生成对应于该特定数据类型的具体函数代码。
但是,也可以通过声明函数原型来告诉编译程序实例化函数模板。例如: float max(float,float); 当编译器看到函数原型时,会先根据函数模板生成相应的函数代码。

73 [例]:采用上面两种方法对例exec11.2进行修改的exec11.3。
…… int main() { int i1=50,i2=40; char c1='A',c2='B'; double x1=45.6,x2=2.5e2; cout<<max(i1,i2)<<endl; cout<<max(c1,c2)<<endl; cout<<max(x1,x2)<<endl; cout<<max(i1,int(c1))<<endl; //正确 cout<<max<int>(i1,x1)<<endl; //正确 cout<<max<double>(c1,x1)<<endl; //正确 return 0; }

74 [问题]: 比较两个字符串哪个大,能不能用前面exec11.3的函数模板直接比较两个字符指针的大小? [分析]: max(s1,s2)
因为:s1,s2是指针,比较两个字符指针的大小需要用strcmp()函数来进行,故不能使用上述的方法。 因此,需要重新定义max()来比较两个字符串。 那么怎样利用函数模板来实现这一目的呢?

75 允许函数模板参与重载,即允许用普通函数重载一个同名的函数模板。 具体方法:
[解决方法]: 允许函数模板参与重载,即允许用普通函数重载一个同名的函数模板。 具体方法: 定义一个普通函数,该函数的原型与函数模板相同,该函数的实现代码与函数模板的实现代码不一样。 在定义重载函数模板的非模板、普通函数时,给出这个重载的非模板、普通函数的函数体。 定义多个重载的普通函数时要注意它们不能出现二义性

76 [例exec11.4]:在exec11.3中增个重载的普通函数
template <class T> T max (T a,T b) { if (a>b) return a; else return b; } char * max (char *a,char *b) { if (strcmp(a,b)>0) return a; else return b; } int main() { cout<<max (15,20)<<endl; cout<<max ('A','B')<<endl; cout<<max ("China","Beijing")<<endl; return 0; } <<<调用模板 <<<调用模板 <<<调用重载函数:char * max 有char * max (…)函数的运行结果 无char * max (…)函数的运行结果

77 模板的特化

78 分析模板max template <class T> T max (T a,T b)
{ if (a>b) return a; else return b; } int main() { cout<<max (15,20)<<endl; cout<<max ('A','B')<<endl; cout<<max (“China”,“Beijing”)<<endl;//调用模板,输出错误(应该输出China) return 0; } <<<调用模板 <<<调用模板 解决办法:对T max (T a,T b)模板全特化,即:将参数全被指定为确定的具体类型 cout<<max (“China”,“Beijing”)<<endl; 将调用特化的模板版本,输出正确(China) template <> char * max (char *a,char *b) { if (strcmp(a,b)>0) return a; else return b; }

79 cout<<maxv(15,20)<<endl;
示例exam11-4C #include <iostream> #include <string.h> using namespace std; template <class T> T maxv(T a,T b) { cout<<"call泛化模板T maxv "; if (a>b) return a; else return b; } template <> int maxv(int a,int b) cout<<"call特化int maxv "; else return b; char* maxv(char *a,char *b) cout<<"call特化char* maxv "; if (strcmp(a,b)>0) return a; int main() { cout<<maxv(15,20)<<endl; cout<<maxv('A','B')<<endl; cout<<maxv("China","Beijing")<<endl; return 0; }

80 例exec11-1改造 不同数据类型数组中的元素求和
#include <iostream> using namespace std; template <class T> T Sum(T *array, int size=0) { T total=0; for (int i=0;i<size;i++) total+=array[i]; return total; } int main() int int_array[]={1,3,5,7,9,11,13,15,17,19}; double double_array[]={1.1,2.2,3.3,4.4,5.5,6.6,5.7,8.8,9.9,10.01}; char * str_array[]={“WUHAN”,”China”}; cout<<"The summary of integer array are "<<Sum(int_array,10)<<endl; cout<<"The summary of double array are "<<Sum(double_array,10)<<endl; cout<<"The summary of double array are "<<Sum(str_array,2)<<endl; return 0; template <> char* Sum<char*>(char* a[], int size=0) { char* s = NULL; int length = 0; for(int i=0; i<numb; ++i) length += strlen(a[i]); s = new char[length+1];//给s分配内存空间 s[0] = '\0'; for(int i=0; i<size; ++i) strcat(s, a[i]); return s; }

81 template <> char* sum<char*>(char* a[], int numb) { char* s = NULL; int length = 0; for(int i=0; i<numb; ++i) length += strlen(a[i]); s = new char[length+1]; s[0] = '\0'; for(int i=0; i<numb; ++i) strcat(s, a[i]); return s; }

82 用函数模板实现的算法举例 --二分查找法

83 编译系统遇到模板函数调用时,将生成可执行代码。
函数模板与模板函数的区别如下: (1)函数模板不是一个函数,而是一组函数的模板,在定义中使用了参数类型。 (2)模板函数是一种实实在在的函数定义,它的函数体与某个函数模板的函数体相同。 编译系统遇到模板函数调用时,将生成可执行代码。 函数模板是定义重载函数的一种工具。一个函数模板只为一种原型函数生成一个模板函数,不同原型的模板函数是重载的。这样就使得一个函数只需编码一次就能用于某个范围的不同类型的对象上。 因此,可以说函数模板是提供一组重载函数的样板。

84 5.3 类模板和模板类 使用类模板(也称为类属类或类生成类)机制,用户可以为类定义一种模式,使得类中的某些数据成员、某些成员函数的参数和某些成员函数的返回值,可以是任意类型的。 类模板不再代表一个具体的、实际的类,而是代表一类类。

85 [例1]:向量类模板定义。 template<class T> class Vector { T *data;
int size; public: Vector(int i) { data=new T[i]; } ~Vector() { delete[] data; } T &operator[](int i) { return data[i]; } };

86 [比较]:类模板与模板类的区别。 [例]: 见例exec11.5。 类模板是模板的定义,不是一个实实在在的类,定义中用到通用类型参数。
模板类是实实在在的类定义,是类模板的实例化。类定义中参数被实际类型所代替。 [例]: 见例exec11.5。

87 模板的派生甚至可以继承一个未知的基类,也就是说,继承哪个基类由模板参数决定。
[例]: 见例exec11.8。 …next page

88 template <class T> class Derived:public T { public:
Derived():T() {cout<<"Construct Derived……"<<endl;} }; int main() Derived<Base_1> x; Derived<Base_2> y; Derived<Base_3<int,10>> z; return 0; }

89 示例: stack 设计一个支持保存以下任意类型值(如int,float,string等)的栈类stack。
template <class T> void stack<T>::push(const T&item){ if(top==Max-1) {cout<<"栈满溢出"<<endl;} top++; data[top]=item; } T stack<T>::pop(){ T temp; if(top==-1) {cout<<"栈为空不能出栈操作"<<endl;} temp=data[top]; top--; return temp; } int stack<T>::stackempty()const{ return top==-1; } const int Max=20; //栈的元素个数 template<class T> class stack{ T data[Max]; int top; public: stack(){ top=-1;} void push(const T &item); T pop(); int stackempty()const; };

90 int main(){ stack <int> st1 int a[]={4,8,3,2}; cout<<"整数栈"<<endl; cout<<"入栈序列:"; for(int i=0;i<4;i++) { cout<<a[i]<<" "; st1.push(a[i]); } cout<<endl<<“出栈序列:"; while(!st1.stackempty()) cout<<st1.pop()<<" “; cout<<endl; 还可以设计为指针栈

91 类模板的偏特化 示例类模板: 偏特化:局部特化模板参数没有被全部指定为确定的类型,而要编译器在编译时进行确定
template <typename T1,typename T2 > class YourClass { public: YourClass(): {cout<< “使用YourClass<T1,T2>”<<endl;} }

92 类模板的偏特化之一: template <typename T> class YourClass <T,T> //两个参数具有相同类型 { public: YourClass( ): {cout<< “使用YourClass<T,T>”<<endl;} }

93 类模板的偏特化之二: template <typename T> class YourClass <T, int> //第二个参数类型为int { public: YourClass( ): {cout<< “使用YourClass<T,T>”<<endl;} }

94 类模板的偏特化之三: template<typename T1,typename T2> class YourClass<T1 *,T2 *>//两个参数具有相同类型:指针 { public: YourClass( ): {cout<< “使用YourClass<T,T>”<<endl;} }

95 调用测试: Yourclass<int,float> m1; Yourclass<float,float> m2;
Yourclass<float,int> m3; Yourclass<int *,float *> m4; 调用哪个模板? 使用YourClass<T1,T2>实例化; 使用YourClass<T,T>实例化; 使用YourClass<T,int>实例化; 使用YourClass<T1*,T2*>实例化; Note. 偏特化后,要防止二义性的声明。如:Yourclass<int,int> m3;同等匹配YourClass<T,T>和 YourClass<T,int>,产生二义性!

96 模板的特化是非常有用的。它像一个在编译期的条件判断。当编译器在编译时找到了符合的特化实现,就会使用这个特化实现。这就叫编译器多态(或者叫静态多态)。
模板的特化对编写c++基础库是很有用的。这也就是为何c++的基础库大量使用了模板技术,而且大量使用了特化,特别是偏特化。 在泛型中,利用特化类得到类的新特性,以便找到最适合这种特性的实现。而这一切都是在编译时完成。

97 c++综述 c++是一个语言族   1. 结构化编程的c , 包括程序块、语句、预处理、内建数据类型、数组和指针   2. 面向对象编程的c++, 包括类、继承、多态、虚函数等   3. 泛型编程的 c++模板,而且衍生出一个元编程的实现-TMP模板元编程(编译器使用模板产生暂时性的源码,然后再和剩下的源码混合并编译)   4. 算法和数据结构库 STL,引入了concept, containers, iterator, algorithms, function object 等概念,指针和迭代器是跨越不同的编程范围的概念。

98 小 结 模板的概念: 模板的分类: 函数模板和模板函数 类模板和模板类 5. 模板的特化
是实现代码重用机制的一种工具,它可以实现类型参数化,即把类型定义为参数,从而实现代码的可重用性。 模板的分类: 函数模板和类模板 函数模板和模板函数 函数模板的定义和使用方法; 函数模板与模板函数的区别。 类模板和模板类 类模板的定义和使用方法; 类模板与模板类的区别; 类模板的派生(3种形式)。 5. 模板的特化

99 思考题: 1.已知 void Sort(int a[],int size); void Sort(double a[],int size);
是一个函数模板的两个实例,其功能是将数组a中的前size个元素按从小到大顺序排列。试设计这个函数模板。

100

101 试将此类声明改为类模板声明,使得数据成员data1和data2可以是任何类型。
2.设有如下的类声明: class Test { public: void SetData1(int val) { data1=val; } void SetData2(double val) {data2=val: } int GetData1() { return data1; } double GetData2() { return data2; } private: int data1; double data2; } 试将此类声明改为类模板声明,使得数据成员data1和data2可以是任何类型。

102

103 设计一个动态数组类模板Array<T>,数组可以在运行时指定大小


Download ppt "第五章 模 板."

Similar presentations


Ads by Google