1
五、函数重载 [function overloading]
六、默认参数 [default argument values]
2
五、函数重载 [function overloading] (C++特有 )
1.重载函数与名称细分函数重载是 C++新引进的。
在同一作用范围中为多个函数定义 (其功能通常是相近的 )指定一个共同的函数名,委托编译器根据每一个单独函数的形参个数、类型和位置的差异在幕后进行名称细分并选择合适的函数调用匹配的现象称为函数重载。
重载函数是函数名相同入口参数类型、位置或个数有所不同的多个函数或其中的一个函数。
3
函数是有类型的,函数的类型不单单指函数的返回类型,函数的形参列表构成的入口类型是函数重载意义上更重要的类型,编译器根据函数的入口类型细分重载函数并由此确定每一个函数定义代码段入口地址的唯一性。
系统不根据函数的返回类型确定函数重载的调用匹配,
也无法区别引用形参和数值形参在重载意义上的差异。
实际上对于单独调用的同名函数,程序员自身也难以区别调用的是哪个函数版本。
4
例如,如下两个函数版本:
int f (int k) { return k; }
//int型的数值入口,int型的数值返回
int* f (int& r) { return &r; }
//int&型的引用入口,int*型的数值返回
void main (void)
{ int n=2; //定义 int型的变量 n
f (n); //函数调用是模糊的
} //编译器无法鉴别函数调用 f(n)匹配上面函数中的哪个版本。对于上面两个重载函数 f只有定义没有调用并不出现
ambiguous call to overloaded function提示,也就是说引用形参和数值形参可以用于名称细分,但不足以解决调用点处的模糊性。
5
typedef 声明并未引进新的数据类型,只是为已经存在的类型提供一个类名同义词,因此不构成名称细分的独立条件。
例如:
typedef float real;
float funct (real x) { return x+1 ; }
real funct (float y) { return y -1; }
编译器不对上面的 funct函数进行名称细分,两者拥有相同的类型参量。
6
[例 ] 参数类型和位置不同的重载函数
#include<stdio.h>
long& max (long& s,long& l)
{ printf ("max(long&);");
return s>l?s:l;
}
double& max (double& s,double& l)
{ printf ("max (double&);\n");
return s>l?s:l;
}
long min (short s,long l)
{ printf ("min (short,long);"); return s<l?s:l; }
7
long min (long l,short s)
{ printf ("min(long,short);"); return s<l?s:l; }
void main(void)
{ long l='l';
long L='L';
short s='s';
double d= 'd';
double D='D';
max(L,l)--; max(D,d)++;
printf ("min=%c\n",min(s,l));
printf ("min=%c\n",min(L,s));
}
8
说明,上面定义了两对函数:
一对函数是返回引用形参两者极大值之一的引用的函数;
另一对函数是返回数值形参中两者极小值的函数,函数原型分别为 [long min (short,long);]
和 [long min (long,short );],
这一对函数完成相同的功能不同的只是将两个形参的位置互换。在函数调用 min (s,l)匹配的是函数 min (short,long )。
函数 max (long&,long& )采用的是引用输入输出,函数对引用形参的操作就是对相关变量的直接操作。变量 l在函数调用,[max (L,l)--;] 后其值减少一个单位,因此在函数 min (s,l)表达式的结果为 min=k。
9
2.函数虚实结合类型匹配在同一作用范围中函数 void vf (int a,int* p) { p++; }与函数 void vf (int b,int* q) {b++; } 视为相同的函数,两者具有同样的参量类型列表 void vf (int,int*)。
函数重载时,编译器根据函数的入口参数类型对同名函数进行名称细分,重载函数的名称细分之后依存在具体函数代码选取的问题,选择原则是根据调用函数的实参的数据类型对当前范围中的函数进行形参的最佳匹配。
如果找到一个恰当的函数匹配,就启动该函数的入口地址。重载函数必须在形参类型、个数或参数位置顺序上有所区分。
10
函数在调用时本身就存在强制的或隐含的类型转换即从实参类型到形参类型的转换,重载函数也不例外。编译器幕后为重载函数的参量建立一个备用函数集,备用函数是在某个位置实参可以转换为形参类型的函数。
如果备用函数集的交集不是唯一的,则函数重载就是有二义的,从而函数调用就导致错误。
函数调用过程中参数匹配和转换的次序分为下面五个阶段或步骤:
a,精确匹配,实参的数据类型与形参的类型一对一的严格匹配属于精确匹配,例如 long型形参匹配 long型实参 ;long
型的数值形参与 long &型的引用形参可以用 long型变量作为实参匹配。但 long型的数值形参实质匹配 long型的右值。
11
b,参量提升匹配,实参从低进度数据类型如 short到形参的高精度数据类型如 double的数据匹配。
c,类型转换匹配,实参的数据类型允许通过强制类型转换或隐含类型转换变为形参的数据类型。如 int实参到
short形参的数据匹配。
d,用户自定义的转换匹配,用户自定义的类型转换主要为构造函数转换和运算符转换,此种匹配用于成员函数重载的过程中。
e,可变参量匹配 可变参量匹配也就是采用省略号 "...“
的匹配,这是最差的匹配。
12
[例 ] 指针形参和引用形参强制类型转换
#include <stdio.h>
void f (short v) { printf ("%d\t",v); }
void f (short * p) { printf ("%d\t",*p); }
void f (int & r) { printf ("%d\t",r); }
void main ()
{ long v;
v=10; f ( v);
v=20; f ((short* )&v);
v=30; f ((int &)v);
}
//输出,10 20 30
13
说明:
算术型的数值形参存在隐含的类型转换规则,当源操作数 (实参 )的类型和目标操作数 (形参 )的类型不一致时可以不提供显式类型转换,当长精度数据转换为短精度数据时会提出警告。
而指针和引用的入口形参当源操作数 (实参 )的类型和目标操作数 (形参 )的类型不一致时须提供显式类型转换,返回的时候也是这样。
14
[例 ] 函数重载时的参量提升匹配和精确匹配
#include <stdio.h>
void f (long v) { printf ("long %d;",v); }
void f(unsigned long n){ printf ("unsigned long %d;",n);}
void f (double d) { printf ("double %3.1f;",d); }
void f(char* v) { printf("char*%s;",v); }
void f(const char * p) { printf("const char*%s\t",p); }
void main()
{ f (1L); f (2Lu); f (3.0f); f (4.0) ;
char* p="aa"; f (p); const char* r="bb"; f (r);
} //输出,long 1;unsigned long 2;double 3.0;
double 4.0 ; char*aa ;const char* bb
15
函数 f(char*)不匹配只读指针实参,而函数 f(const char*)
可安全地匹配 char*型的实参,char*型的实参优先启动
f (char*),如果 f (char*)不存在,则启动 f (const char *)函数,
long型数据和 unsigned long型数据混合算术类型上是平级的,float型数据则高其一个级别。 float型实参从低进度数据类型提升到 double类型的高精度形参数据类型,因此
f (3.0f)启动 f (double)函数。
表达式 1L是 long型数据,2Lu是 unsigned long型表达式 ;
f (1L)严格匹配 f (long)
f (2Lu)严格匹配 f (unsigned long)
函数调用 f(‘1’*‘c’)中的实参‘ 1’*‘c’为 int型。 int型数据可以提升到 long型也可以提升到 double型,因此函数调用
f ('1'*'c')导致歧义。
16
3.连接 C语言中的程序模块
extern关键字另一个用法是沿用根据 C语言规则编写的程序。这个特点使得 C语言的模块不必进行改写就可直接索引。以下例子给出了具有 C连接名称的声明方法,
extern "C" int printf (const char *format,...);
//声明 printf具有 C连接
extern "C"
{ #include <cfile.h> }
//头文件 "cfile.h中的所有内容具有 C连接
//声明具有 C连接的两个函数 ShowMoney和 GetMoney
extern "C" { int ShowMoney (int count);
float GetMoney (void); }
17
//定义具有 C连接的函数 max
extern "C" long max (long x,long y)
{ return x>y? x:y ; }
//声明一个具有 C连接的全局变量 errorflag:extern "C" int errorflag;
//C连接不涉及重载时函数名称的细分,下面的说明导致错误,
extern "C" int f (void); // C连接的函数名简单地就是 f
extern "C" int f (int);
// error:second C linkage of overloaded function 'f' not allowed
18
将第一个 "C"划去改为,
extern int f (void);
// C++连接对于这个函数进行名称细分,可认为 f初步处理为 f_v
extern "C" int f (int);
//C连接的函数名简单地就是 f则编译通过。因此将双引号括
//起来的 "C"划去可回归到 C++语言的连接约定。
C连接最大的特点是函数名在编码时不根据形参列表中的类名进行名称细分。
19
六、默认参数 [default argument values]
1,默认参数的函数默认参数也称为缺省参量,函数调用时常需提供一组默认的参数值,作为程序的原始出发点供软件的用户使用。
函数定义中的每一个参数都可以拥有一个默认值,如果在函数调用中没有为对应默认值的参量提供实参数据,系统就直接使用默认值。默认值是形参列表中设置的初始值,格式为:
int f (int x,long x=10,const double y=20);
这样就可以采用几种等价地调用形式:
f (3); f (3,10); f (3,10,20);
20
[例 ] 默认参数值的函数
#include<stdio.h>
int d;
int f3 (int n=3) {return n;} // 默认值为 3
void f (int i=1,long l=2,int f = f3())
{ d=i+l+f; }
void main (void)
{ f (); printf ("f() sets d=%d\n",d);
f (2); printf ("f(2) sets d=%d\n",d);
f (2,3); printf ("f(2,3) sets d=%d\n",d);
f (2,3,4); printf ("f(2,3,4) sets d=%d\n",d);
}
21
无默认值的参量需要实参匹配。
例如函数定义,
void funct (int i,long l=2,float f=3) {d=i+l+f;}
是正确的但函数调用 funct ()导致:
error,'funct',function does not take 0 parameters
默认值可以使用常数、全局变量或函数调用,默认值必须编译时静态确定。
默认参数不影响函数的虚实结合的匹配转换规则。
22
使用默认参数受到如下的限制,
1) 默认参量的位置必须从形参列表的最右边依次从右到左排放,其数量不限。
默认值不能在形参列表的中间遗失。如函数定义
void f (int i=1,long l,float f=3){d=i+l+f;}
导致:
error,'f',missing default parameter for parameter 2
2) 默认值或在函数原型中提供或在函数定义的标题头中指定,当既有原型又存在定义时仅在函数原型中设置默认值。
23
2,默认参数与函数重载默认参数不是函数形参的类型抽象,因此不用于细分重载的函数。
系统根据形参类型进行名称细分,然后由实参的类型在细分的重载函数备用集中选择恰当的函数。
下面的例子是重载函数中使用默认参数时模糊引起的语法错误。
[例 ] 默认参数导致的语法模糊
#include<stdio.h>
double d;
void funct (int i,long =2,float f=3);
//函数原型中默认值可省去形参名
void funct (int i,long l) { d=i+l; }
24
void main (void)
{ funct(2);
//调用 void funct(int i,long =2,float f=3);
funct(2,3); // error take place here,
funct(2,3,4); //调用 void funct (int,long,float);
}
void funct (int i,long l,float f) { d=i+l+f; }
funct (2,3)可以匹配 3个形参的 funct (int i,long=2,float f=3)
也可以匹配 2个形参的 funct(int i,long l)。因此程序在编译时出现提示:
error,'funct',ambiguous call to overloaded function。
25