第九天(函数进阶 · 二)

来源:互联网 发布:网络接口 编辑:程序博客网 时间:2024/05/17 06:26

     后半部分费时较多,因为很多内容都是新的。昨晚写完之时已是23:15,来不及编辑排版了,今早上传。


2011-10-11(Adventures In Functions II)
1、默认参数(default arguments)。C++提供设置默认参数的功能,当程序中摸个参数缺失时,自动使用该默认参数。如有一个add()函数,功能是将两个数相加后将结果返回,如果程序中这样调用:
                          int a = add(5);
缺失了第二个参数,而如果这是事先设置了第二个参数为0,则返回的是5+0 = 5。如此减少不必要的麻烦。要设置默认参参数,要从函数原型下手:
                          int add(int a, int b = 0);
这样,当程序中部提供第二个参数时,b = 0。有参数是会覆盖默认值。设置默认参数有个原则:要位某个参数设置默认参数,它的右边所有参数都要提供默认值,即如下情况是不合法的:
                                   int ksyou(int a, int b = 4, int c);
有此规定是避免出现下面状况:
                                                         ksyou(5 , , 6);
这么下不是摆明程序是特意不写参数的么?违背了设置这个功能初衷。
2、函数重载(Function Overloading)。
①如下函数视为同一函数:
                         double doubleValue(double x);
                         double doubleValue(double& x);

②返回类型不能作为区别函数的依据:
                         double create(int, double);
                         int create(int, double); //this two guys are incompatible
③参数是否有const可作为依据,但仅限于引用和指针类型参数:

void print(char*);          //overloadedvoid print(const char*);    //overloadedconst char str1[10] = "C++ test";char char str2[10] = "C++ test";print(str1);                //print(const char*)print(str2);                //print(char*)
④模棱两可的情况不允许:
                        int add(int a, int b, int c = 5);
                        int add(int a, int b);

如果主函数中如此调用:add(5,6);如此无法确定调用的是哪个函数。
3、函数模板(Function Templates)。试想,如果上述函数add()函数中的参数和返回类型改成double或long,是否每一次的需求都要重新编写。C++中,这是不需要的,因为有函数模板。简单的说是根据某个算法编写成一个模板,变的只是算法里面的数据类型。
使用关键字template,大概形式如下(以add()函数为例):
                        template <class/teyename T>
                        T add(T a, T b){ return a + b; }

上面,class和typename均为关键字,可使用任意一个。T可以为任意合法名字。今后,程序会根据参数类型,将根据这模板来具体化函数,如: 
int a = b =5;add(a, b);        //return 10double x = y = 5.36;add(x, y);        //return 10.72
再举一例: 
#include <iostream>const int ArrSize = 5;using namespace std;template <class T>T max5(T [], int);int main(){    int intType[ArrSize] = {56,56,-54,0,-3001};    double doubleType[ArrSize] = {56.1,-65.64,210.3,100,-1.5};    cout << max5(intType, ArrSize) << endl;    cout << max5(doubleType, ArrSize) << endl;    return 0;}template <typename Any>Any max5(T a[], int size){    Any max = a[0];    for(int i = 0; i< ArrSize; i++)    if(a[i] > max)    max = a[i];    return max;}
4、模板的重载。模板也可以重载,不过注意要定义可共存的函数,即函数的特征标要不同。如:
                  template <class T>
                  T add(T, T);


                  template <class Any>
                  Any add(Any ,Any );  

视为视为同一模板;
                  template <class T>
                  T add(T, T);

                  template <class Any>
                  Any add(Any [],Any []);

视为视为不同模板;
5、模板的显式具体化。在C++中,如果是按值传递,结构体与基本数据类型无异。如此问题就出来了,结构体是有“内涵”的,模板的一般化处理可能不适用与结构体。比如,定义一个模板函数show()用作打印传过去参数的信息,显然,无法从广泛意义上打印所有参数类型信息。所以有模式具体化一说。 
#include <iostream>#include <string>using namespace std;struct Student{    string name;    int number;};template <class T>void show(T);void show(Student);template <> void show<Student>(Student);int main(){    Student stu= {"Tom", 23};    int a = 10;    show(a);    show(stu);    return 0;}template <class T>void show(T a){    cout << a << endl;}void show(Student stu){    cout << "Name: " << stu.name << "; Number: " << stu.number << endl;    cout << "Type1" << endl;}template <> void show<Student>(Student stu){    cout << "Name: " << stu.name << "; Number: " << stu.number << endl;    cout << "Type2" << endl;}
                             10
                             Name: Tom; Number: 23
                             Type1

12行,可简写成:template <> void show(Student);在函数前加上template <>即可将其具体化,并且重新编写代码。当然也可以更加具体地定义一个普通函数,专门用于打印结构体信息。如此出现了三类函数:非模板函数、模板函数和具体化的原型。它们的优先级顺序是:非模板函数>具体化的原型>模板函数。
6、深入理解和术语。模板是一个生成函数的方案,非定义。编译器使用模板根据需要生成特定函数定义,这个函数的定义称为实例(instantiation),这个是过程称为实例化(区别于之前的具体化)。实例化分隐式实例化(implicit instantiation)和显式实例化(explicit instantiation)。隐式实例化是编译器自动完成的。显式实例化可用下面语句进行:
                          template void show<double>(double);
这个语句写在主函数中,不需要重新编写代码,这是编译器根据模板生成的函数,已经是一个实例了。以后如果有:
                          double a;
                          show(a);

编译器将直接调用这个实例不需要再经历一次隐式实例化生成实例。如此显式实例化的好处是,编译器不需要在每次需要用的模板的时候重新生成函数,提高效率。

至此,显式实例化和显式具体化区别已经明了:一个是为了提高效率,一个是为了适应更多的类型。隐式实例化、显式实例化和显式具体化统称具体化(specialization)。

7、函数的匹配。目前为止,学了重载、模板,这二样结合在一起,会产生多种多样的函数,匹配问题需要考虑。一个函数(或模板)重载了多次,而重载后的差别很少(比如仅多了个const修饰,多了引用符号&、多了指针符号*)时,会产生匹配问题,比如:
void may(int);                         // #1float may(float, float = 3);           // #2void may(char);                        // #3char may(const char &);                // #4template<class T> void may(const T &); // #5
与may('B');进行匹配,如此,便有优先顺序问题。一般的优先顺序如下(高至底):
①完全匹配,但常规函数优于模板;
②提升转换(char和short转为int,float转为double);
③标准转换(int转为char,long转为double)

④用户自定义转换。
如此,#1优先于#2(因为②);#3#4#5优于#1#2(因为①);#3#4优于#5(因为#5是模板,#3#4是常规函数),剩下的是在#3#4中选。

完成匹配之前,需要说下完全匹配:指类型对口(int对int,char对char等),就是说不需要转换。但是,某些种类的转换对完全匹配无影响,如表:

序号实参 形参1Type Type&2Type&Type3Type[]Type*4Type(argList)Type(*)(argList)5Typeconst Type

举例讲,char匹配const char &是完全匹配(如上面提到的may('B')匹配#4)。第4是指函数指针匹配问题,具体是讲:用作实参的函数名只有与用作形参的函数指针的返回类型和参数类别一直,就是完全匹配。
但是,对于非const的数据的指针、引用将优先与非const指针和引用参数匹配。
综合上面原则可知,#3#4产生了二意,不能匹配。
接下来讲考虑无完全匹配的情况,或者在多个完全匹配函数再匹配的情况。这是后遵循的是“最具体”原则,意思是:转换最少的那种为优。如
          template <class Type> void recycle (Type t);   // #1
          template <class Type> void recycle (Type * t); // #2

与recycle(&i)(i是整型)匹配。如果与#1进行匹配,Type将视作int*;与#2匹配Type将视作int。显然是选择#2,因为前者(int转int*)比后者(int直接匹配int转换少。

如果上述原则都不能正确匹配,会产生“二意性”错误。

……话说回来,不需太深入了解,如果产生二意性,编译器自然会提醒的。