第九天(函数进阶 · 二)

     后半部分费时较多,因为很多内容都是新的。昨晚写完之时已是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*);          //overloaded
void print(const char*);    //overloaded
const 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 10
double 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);                         // #1
float may(float, float = 3);           // #2
void may(char);                        // #3
char may(const char &);                // #4
template<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&Type
3Type[]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 转换少。

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值