320 likes | 529 Views
C++ 与 C #. 北航软件所 孙自安 北航如心楼 606 电话: 82316284 Email: sza@buaa.edu.cn. 第五章 模板. 参数个数、处理流程相同. 例:求两个数的最小值. int min( int a, int b ) { return a < b ? a : b; } double min( double a, double b ) { return a < b ? a : b; }. 需要为每一种数据类型 定义一个 min 函数. 5.1 函数模板.
E N D
C++与C# 北航软件所 孙自安 北航如心楼606 电话:82316284 Email: sza@buaa.edu.cn
参数个数、处理流程相同 例:求两个数的最小值 int min( int a, int b ) { return a < b ? a : b; } double min( double a, double b ) { return a < b ? a : b; } 需要为每一种数据类型 定义一个min函数
5.1 函数模板 函数参数类型或返回类型能否在函数定义时不明确指定,当调用时根据实参再确定? ? min( ? a, ? b ) { return a < b ? a : b; }
5.1.1 函数模板定义:将函数参数类型或返回类型参数化 • 关键字template 总是放在模板的定义与声明的最前面; • 关键字后面是用逗号分隔的模板参数表,它用尖括号<> 括起来; • 每个模板类型参数由关键字class或typename开头,参数名是用户自定义的标识符。 template< class Type > Type min( Type a, Type b ) { return a < b ? a : b; } 等同于 template< typename Parm > Parm min(Parma, Parmb ) { return a < b ? a : b; }
template< class Type > Type min( Type a, Type b ) { return a < b ? a : b; } void main ( ) { int ri = min( 10, 20 ); double rd = min ( 10.2, 20.1 ); } //int min ( int , int ); 模板实例化 // double min ( double , double );
template < class T1, class T2, class T3 > T1 sum ( T2 a, T3 b ) { T1 s; s = a+b; return s; } 模板类型参数在模板定义中的使用方式和一般类型相同 • 注意: • 函数参数类型可以被部分参数化,但模板参数表不能为空。
例:求一数组中的最小值。 int min ( const int array[ 10 ] ) { int min_val = array[0]; for (int i=1; i<10; i++) if(array[i]<min_val) min_val = array[i]; return min_val; } int min ( const int array[ ], int size ) { int min_val = array[0]; for (int i=1; i<size; i++) if(array[i]<min_val) min_val = array[i]; return min_val; } template < class Type > Typemin ( const Typearray[ ], int size ) { Typemin_val = array[0]; for (int i=1; i<size; i++) if(array[i]<min_val) min_val = array[i]; return min_val; }
注意: • 模板参数可以是一个模板类型参数,它代表了一种类型; • 也可以是一个模板非类型参数,模板非类型参数由一个普通的参数声明构成,它代表了一个常量表达式。 template <class Type, int size> Type min( const Type ( &r_array ) [size] ) { Type min_val = r_array[0]; for ( int i = 1; i < size; ++i ) if ( r_array[i] < min_val ) min_val = r_array[i]; return min_val; } int min( const int ( &r_array ) [10] ) { int min_val = r_array[0]; for ( int i = 1; i < 10; ++i ) if ( r_array[i] < min_val ) min_val = r_array[i]; return min_val; }
5.1.2 函数模板实例化 用函数实参的类型来决定模板实参的类型和值的过程被称为模板实参推演。(函数返回类型并不用于模板实参推演) template <class Type, int size> Type min( const Type ( &r_array ) [size] ); void main () { int ai [ ] = { 12, 7, 9, 39, 3 }; double ad [3] = { 2.3, 4.5, 1.8 }; int i = min ( ai ); double d = min ( ad ); int a = min ( ad ); } // Type为int , size为5 // Type为double , size为3 // Type为double , size为3
template < class T1, class T2, class T3 > T1 sum ( T2 a, T3 b ) { T1 s; s = a+b; return s; } 显式模板实参,可省略尾部模板实参 char a= ‘0’; int i = 1; char b = sum(a,i); char sum (char, int ); ? char c = sum < char, char, int > (a, i); char d = sum < char > (a, i); //省略T2,T3
5.1.3 模板编译模式 • 我们把函数模板定义放在哪里? • 放在头文件中,就好像对内联函数定义的做法一样,在使用函数模板实例的地方包含它们; • 或者我们只在头文件中给出函数模板声明,而把模板定义放在*.c中,就好像对非内联函数的做法一样。 C++支持两种模板编译模式:包含模式和分离模式。
包含编译模式:将整个模板定义放在头文件中。包含编译模式:将整个模板定义放在头文件中。 // model1.h template <typename Type> Type min( Type t1, Type t2 ) { return t1 < t2 ? t1 : t2; } 缺点:向用户暴露了模板实现细节 // 在使用模板实例之前包含模板定义 #include "model1.h" int i, j; double dobj = min( i, j );
分离编译模式: 函数模板的声明被放在头文件中,分离编译模式: 函数模板的声明被放在头文件中, 实现放在*.c文件中。 // model2.h template <typename Type> Type min( Type t1, Type t2 ); // user.C #include "model2.h" int i, j; double d = min( i, j ); // model2.C export template <typename Type> Type min( Type t1, Type t2 ) { …… } template 之前加上关键字export 来声明一个可导出的函数模板,export 不需要出现在头文件的模板声明中
例:定义一个队列类 class QueueItem { public: QueueItem( const int & ); private: int item; QueueItem *next; }; 能否定义通用 队列类:不限制 结点保存的 数据类型? class Queue { public: Queue() : front( 0 ), back ( 0 ) { } ~Queue(); int & remove(); void add( const int & ); bool is_empty() const { return front == 0; } private: QueueItem *front; QueueItem *back; };
5.2 类模板 将类定义中用到的数据类型或常量值参数化 template <class Type> class QueueItem { public: QueueItem( const Type & ); private: Type item; QueueItem *next; };
template <class Type> class Queue { public: Queue() : front( 0 ), back ( 0 ) { } ~Queue(); Type& remove(); void add( const Type & ); bool is_empty() const { return front == 0; } private: QueueItem<Type> *front; QueueItem<Type> *back; }; 类模板实例化
注意: • 函数模板实例化时,既可以提供显式模板实参,也可根据函数实参进行实例化; • 类模板无法根据上下文环境确定模板参数,所以必须提供显式模板实参进行实例化。 Queue qi; Queue<int> qi; Queue<string> qs[100]; Queue<double> *qp = new Queue<double>; 实例化后的类模板称为模板类,其使用方法与一般类相同
类模板成员函数定义:在类模板定义之外 Queue <int> qi; int a; …… …… qi.add(a); template <class Type> void Queue <Type> ::add( const Type &val ) { QueueItem<Type> *pt = new QueueItem<Type>( val ); if ( is_empty() ) front = back = pt; else { back->next = pt; back = pt; } } 当类模板被实例化时,类模板的成员函数并不自动被实例化,只有当一个成员函数被程序用到函数调用或取地址时,它才被实例化。
类模板编译模式: • 包含编译模式:类模板定义和成员函数定义等都放在头文件中。 • 分离编译模式:类模板定义放在头文件中,成员函数等定义放在程序文本文件*.c中。 • // ----- Queue.h ----- • export template <class Type> • class Queue { • ... • public: • Type& remove(); • void add( const Type & ); • .... • }; // ----- Queue.C ----- #include "Queue.h" template <class Type> void Queue<Type>::add( const Type &val ) { ... } template <class Type> Type& Queue<Type>::remove() { ... } export关键字用作类模板定义中
5.3 STL(标准模板库: Standard Template Library) 各种各样的 数据结构: 链表、堆栈、队列、向量 …… 各种各样的操作: 遍历、插入、删除、替换 …… C++标准库 提供许多复用组件 面向对象的最大好处:复用、复用、再复用
STL包括三个主要组件: • 容器(container):模板化的复杂数据类型; • 迭代器(iterator):用于迭代访问容器中的数据; • 泛型算法(gerneric algorithm):可应用在多种数据 类型(包括容器类型或内置数据类型)上的公共操作。 下面以vector容器为例介绍容器、迭代器、泛型算法的使用
5.3.1 顺序容器:vector 顺序容器:由单一类型元素组成、顺序存放的一个集合:vector、list、deque vector类表示具有连续内存单元的数据结构: • 类模板:模板参数表示所存储每个元素的数据类型 • 快速随机访问任何元素 • 在尾部快速插入、删除元素,在其它位置插入、删除元素效率很低 • 动态增长:存储空间的申请和释放由vector类负责
为了定义vector对象,必须包含库头文件: #include <vector> using namespace std; 容器对象的定义以容器类型的名字开始,后面是所包含的元素的实际类型,例如: vector < int > ivec; vector < string > svec; 初始为空:长度为0 可以是各种内置数据类型或用户自定义类 • 用户自定义类必须支持以下操作: • 元素类型必须支持等于操作符 • 元素类型必须支持小于操作符 • 元素类型必须支持复制构造函数
插入元素最简单的方法是push_back() ,将元素插入在容器的尾部: vector < string > svec; string text_word; while ( cin >> text_word ) { svec.push_back( text_word ); cout << svec.size() <<endl; } • vector支持其它一 • 些操作: • 比较操作 • 指定位置或范围的 插入、删除操作 …… 长度动态增长
5.3.2 迭代器(iterator) 迭代器:指向容器中的元素,用于对顺序或关联容器类型中的每个元素进行连续访问。 预定义迭代器 ++方向 功能 iterator 向前 读/写 const_iterator 向前 读 reverse_iterator 向后 读/写 const_reverse_iterator 向后 读 vector中定义的迭代器: vector<string>::iterator vector<int>::iterator
// vector<string> vec; vector<string>::iterator iter = vec.begin(); vector<string>::iterator iter_end = vec.end(); for( ; iter != iter_end; ++iter ) cout << *iter << endl; • begin()返回一个iterator:它指向容器的第一个元素 • end()返回一个iterator:它指向容器的末元素的下一个位置 迭代器可进行比较、自增/自减、解寻址等操作
const_iterator允许以只读方式访问容器的底层元素例如const_iterator允许以只读方式访问容器的底层元素例如 #include <vector > void even_odd( const vector<int> *pvec, vector<int> *pvec_even, vector<int> *pvec_odd ) { vector<int>::const_iterator c_iter = pvec->begin(); vector<int>::const_iterator c_iter_end = pvec->end(); for ( ; c_iter != c_iter_end; ++c_iter ) if ( *c_iter % 2 ) pvec_odd->push_back( *c_iter ); else pvec_even->push_back( *c_iter ); }
可以用标量算术运算使iterator 从当前位置偏移到某个位置上: vector<int>::iterator iter = vec.begin()+vec.size()/2; 将iter 指向vec 的中间元素,而 iter += 2; 将iter 向前移动两个元素 • 容器对象也可以用“由一对iterator 标记的起始元素和未元素下一位置之间的拷贝”来初始化: • vector<string> svec; • // ... • // 用svec 的全部元素初始化svec2 • vector<string> svec2( svec.begin(), svec.end() ); • // 用svec 的前半部分初始化svec3 • vector<string>::iterator it =svec.begin() + svec.size()/2; • vector<string> svec3( svec.begin(), it );
5.3.3 泛型算法 在迭代器的支持下,vector等容器类提供检索、插入、删除元素等独有操作。 另外还有一些操作,如:查找、排序、合并等操作,这些操作不是由每个容器类单独提供的,而是作为各种容器类、内置数组类型都需要的公共操作抽取出来,形成一个通用的算法集合,由库函数提供。 泛型算法:STL提供了70种左右的算法
为了使用泛型算法,必须包含库头文件: #include <algorithm> 所有泛型算法两个实参都是一对iterator , 它们标记出要操作的容器或内置数组中的元素范围 int ia[ 6 ] = { 0, 1, 2, 3, 4, 5 }; vector<string> svec; //…… vector<string>::iterator viter; int *pia; int i = 3; string st = “hello”; pia = find( &ia[0], &ia[6], i ); viter = find( svec.begin(), svec.end(),str ); 可应用在各种容器类型或内置数组类型上
作业(7月5日之前交): 写一个程序已知字符串 string line1 = "We were her pride of 10 she named us --"; string line2 = "Benjamin, Phoenix, the Prodigal" string line3 = "and perspicacious pacific Suzanne"; string sentence = line1 + ' ' + line2 + ' ' + line3; 统计句子中单词的个数,并找出最长的和最短的单词,如果不只有一个最长或最短单词,则把它们全部列出来。 请使用容器类实现该程序。