230 likes | 401 Views
C++ 程序设计(六 ). 常宝宝 北京大学计算机科学与技术系 chbb@pku.edu.cn. 模板. 为什么需要模板? 在 C++ 中引入 模板 最主要的目标仍然是为了使代码具有好的可重用特性。在 C++ 中,妨碍代码重用的原因之一在于函数以及类都和类型有很强的关联性,如果一个函数在定义时只能处理整型类型的输入参数,则该函数就不能处理字符型或其它程序员自定义的类型。类也具有相同的问题,类中的数据成员往往也具有确定的类型。模板的引入正是为了解决在这种情形下的代码重用问题。 在 C++ 中有两类模板 函数模板 类模板. 函数模板. 先看一个例子(交换两个变量的内容)。
E N D
C++程序设计(六) 常宝宝 北京大学计算机科学与技术系 chbb@pku.edu.cn
模板 • 为什么需要模板?在C++中引入模板最主要的目标仍然是为了使代码具有好的可重用特性。在C++中,妨碍代码重用的原因之一在于函数以及类都和类型有很强的关联性,如果一个函数在定义时只能处理整型类型的输入参数,则该函数就不能处理字符型或其它程序员自定义的类型。类也具有相同的问题,类中的数据成员往往也具有确定的类型。模板的引入正是为了解决在这种情形下的代码重用问题。 • 在C++中有两类模板函数模板类模板
函数模板 • 先看一个例子(交换两个变量的内容)。 swap(int&, int& b)只能交换两个整型变量的内容,不能交换两个double型的变量或两个student类型的对象的内容。如:int ix = 6, iy = 7;swap(ix, iy);double dx =6.5, dy=7.5;swap(dx, dy);//错误,类型不匹配student sx(“王二”,“10023009”, 98 ),sy(“陈清扬”,“10023010”, 63 );swap(sx, sy); //错误,类型不匹配 void swap(int& a , int& b) { int temp = a; a = b; b = temp; }
函数模板 • C++中提供了函数重载的能力,可以用重载的办法定义能处理其它类型的函数。 • 重载提供了函数的另外一种定义,但不是代码重用,程序员仍须复制原有函数的函数定义,并在原有函数定义的基础上进行修改,在修改过程中,又有可能引入错误。 void swap(double& a , double& b) { double temp = a; a = b; b = temp; } void swap(student& a , student& b) { student temp = a; a = b; b = temp; }
函数模板 • 在C++中,可以通过定义函数模板的方式解决因类型不同而导致的代码不能重用的问题。 • 在函数模板中,类型本身可被定义成模板参数,C++编译器根据函数调用时提供的参数的参数类型自动生成处理不同类型的正确函数。 • 通过函数模板定义的函数不是一个函数,而是一组函数。 • 定义函数模板使用保留字template,定义格式如下:template < 模板参数表 >返回类型 函数名( 函数型式参数表 ) { ... }
函数模板 • 模板参数表中的参数可以有多个,多个参数间用逗号间隔。模板参数通常代表一个类型(模板类型参数,注意和函数参数不同),模板类型参数形式如下:class 类型参数名 (或 typename 类型参数名) • 在模板函数的函数定义中,可以使用模板参数表中的类型参数,就象使用一个基本数据类型或用户已定义好的类型一样。 template <class T> void swap(T& a , T& b) { T temp = a; a = b; b = temp; }
void swap(int& a , int& b) { int temp = a; a = b; b = temp; } T = int void swap(double& a , double& b) { double temp = a; a = b; b = temp; } T = double 函数模板 • 在完成模板函数定义后,可以象调用一般函数那样调用模板函数。int ix = 6, iy = 7;swap(ix, iy); • C++ 编译器在遇到调用用模板方式定义的函数时,会根据调用函数的参数类型构造出一个个具体的函数。这个过程称为函数模板的例化(instaniation)。
函数模板 • 在调用函数模板时,C++编译器通常是根据函数实参的类型来例化函数模板,这个过程由C++编译器自动进行,称为模板参数推导(template argument deduction)。 • 程序员在调用模板函数时也可显式指定用以替换模板类型参数的模板实参,这样,C++编译器无需进行模板参数推导即可对函数模板进行例化。此时,模板函数的调用形式如下:模板函数名<模板实参表>(函数实参表);如:float fx = 6.5 , fy = 7.5;swap<float>(fx, fy); • 指定的模板实参应和函数实参类型匹配。如:int ix = 6, iy = 7;swap<float>(ix, iy);//错误
函数模板 • 若程序员不提供模板实参,C++编译器自动进行模板参数推导,有时候,作为函数返回类型的模板参数不能被推导出来。此时,需要显式指明模板实参。 • 程序员可以仅提供部分模板实参,其余的模板实参仍由编译器自动推导。且省略掉的实参必须是模板参数表尾部的参数对应的实参。 template <typename T1, typename T2, typename T3>T1 sum( T2 a, T3 b) { return a + b;}...int x = 6; float y = 9.9, sigma;sigma = sum(x, y);//错误sigma = sum<float,int,float>(x,y);//正确sigma = sum<float>(x,y);//正确sigma = sum<,int>(x,y);//错误
char* max(char* t1, char* t2) { return (t1>t2?t1:t2);} T = char* 函数模板 • 有时候,定义的模板函数并不能适合任何一种数据类型,对这些数据类型例化得到函数可能包含错误。 • 因为模板函数总是考虑到尽可能多的数据类型,因此对某一种具体数据类型而言,模板例化得到函数效率可能不高。 template <typename T>T max(T t1, T t2) { return (t1>t2?t1:t2);}...char* szx=“World”, *szy=“Hello”, *szmax;szmax = max(szx, szy);//结果是什么?// szmax = “Hello”
函数模板 • 当函数模板不适于某类数据类型时,此时可以通过特化的方式为该数据类型提供合适的函数定义,形式如下:template<>返回类型 函数名<模板实参表>(函数形参表) { ...} • 在进行函数模板特化时,保留字template、后面的<>不可缺少,函数名后的模板实参在编译器能够成功推导的情况下可以省略。 template <>char* max<char*>(char* t1, char* t2) { return ( strcmp(t1, t2) > 0 ? t1 : t2);}
类模板 • 和函数一样,类同样具有类型依赖性。在定义类的时侯,类中的数据成员、成员函数的返回值以及参数都必须有确定的类型,有时候这也会妨碍类代码的可重用性。 • 问题——定义动态数组元素个数应可动态变化元素类型应没有限制 • 例子: Stash.h Stash.cpp StashTest.cpp class Stash { int quantity; int next; int* storage; void inflate(int increase);public: Stash(); Stash(int initQuantity); ~Stash(); int add(int& element); int& fetch(int index) const; int& operator[](int index) const; int count() const;};
类模板 • 为了解决类依赖类型的问题,可以使用类模板,类模板的定义和函数模板的定义类似,使用保留字template,格式如下:template<模板参数表>class 类名 { …;}; • 模板参数表中的参数可以有多个,多个参数间用逗号间隔。模板参数通常代表一个类型,模板类型参数形式如下:class 类型参数名 (或 typename 类型参数名)
类模板 • 同函数模板一样,一个类模板实际上对应了一组类。 • 当定义一个模板类的对象时,程序员需要提供模板实参,C++编译器根据程序员提供的模板实参生成一个个具体的适合不同类型的类定义,形式如下:类名<模板实参> 对象名...Stash<int> intStash;Stash<float> floatStash; Template<class T>class Stash { int quantity; int next; T* storage; void inflate(int increase);public: Stash(); Stash(int initQuantity); ~Stash(); int add(T& element); T& fetch(int index) const; T& operator[](int index) const; int count() const;};
class Stash { int quantity; int next; int* storage; void inflate(int increase);public: Stash(); Stash(int initQuantity); ~Stash(); int add(int& element); int& fetch(int index) const; int& operator[](int index) const; int count() const;}; class Stash { int quantity; int next; student* storage; void inflate(int increase);public: Stash(); Stash(int initQuantity); ~Stash(); int add(student& element); student& fetch(int index) const; student& operator[](int index) const; int count() const;}; T=int Stash<int> T=student Stash<student> 类模板 • C++编译器根据模板实参生成类的过程被称为类模板例化。
类模板 • 模板参数可以分成两类类型参数:值是类型非类型参数:值是常量 • 有时候,类中需要一个常量,此时使用非类型参数,非类型参数形式如下:类型 参数名...template<class T, int inc>class Stash {...}; • 如果模板参数表中含有非类型参数,定义对象时,程序员需要提供作为实参的常量值。这相当于在程序中定义了一个常量,如:Stash<int, 50> intStash;//等同于const int inc = 50
类模板 • 对于非类型参数,可以有默认值(旧标准)。如:template <class T, int inc = 50 > class Stash { ...}; • 同函数默认值一样,默认值的指定必须按照从右向左的顺序进行。template <class T, int x=50, int y=100> //正确template <class T, int x, int y=100> //正确template <class T, int x=50, int y> //错误 • 如果指定了默认值,定义对象时,如果程序员没有提供模板实参,则表示使用默认值。如:stash<int> intStash; //等同于 stash<int, 50> intStash;
类模板 • 例化后的模板类和普通类一样可以出现在普通类可以出现的任何地方,如:void f1(Stash<int> & a) {... }Stash<int>& f2( int a) {...}Stash<int>* pIntStash = new Stash<int>;
类模板 • 类模板的成员函数可以在类内定义,也可以在类外定义。在类内定义比较简单,和普通类在类内定义成员函数基本相同,不同之处在于其中可能出现模板参数。 • 在类外定义模板类的成员函数要使用不同的语法,成员函数前必须加上保留字template及模板参数。且第一次出现类名的时侯也应有模板参数表,但此时只列参数名。形式如下:template <模板参数表>返回类型 类名<模板参数表>::成员函数名(函数形参表) {...} Template<class T>class Stash { ... public: ... T& fetch(int index) const; assert(index >= 0 ); assert(index < next ); return storage[index]; } ...};
类模板 template <typename T, int increment>Stash<T, increment>::Stash() : quantity(0),next(0), storage(0) {} template <typename T, int increment>Stash<T, increment>::~Stash() { if(storage != 0) delete []storage;} template <typename T, int increment>int Stash<T, increment>::add(T& element) { if(next >= quantity) inflate(increment); storage[next] = element; next++; return(next - 1);}
类模板 • 函数模板、类模板如果希望被多个程序文件(.cpp)使用,则应在头文件中定义类模板(包括成员函数定义)和函数模板。并在使用类模板或函数模板的程序文件中包含该头文件。因为模板在例化时,需要知道类模板和函数模板的完整定义。 • 例子:TStash.h (类模板定义、类模板成员函数定义)TStashTest.cpp • 什么时侯必须重载运算符?
其它 • 异常处理 第21章 • 名字空间(namespace) • 运行时刻类型识别(RTTI) • 流类库(iostream) 第19章 • 标准模板库(STL)
上机练习内容 • 《C++程序设计教程》p.450练习20.1 、20.2