后半部分费时较多,因为很多内容都是新的。昨晚写完之时已是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;
}
10Name: 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)。
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等),就是说不需要转换。但是,某些种类的转换对完全匹配无影响,如表:
序号 | 实参 | 形参 |
1 | Type | Type& |
2 | Type& | Type |
3 | Type[] | Type* |
4 | Type(argList) | Type(*)(argList) |
5 | Type | const Type |
但是,对于非 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 ) 转换少。
如果上述原则都不能正确匹配,会产生“二意性”错误。
……话说回来,不需太深入了解,如果产生二意性,编译器自然会提醒的。