这里写目录标题
1. 缺省参数
1.1缺省参数函数调用
(1) 一般情况下,函数调用时的实参个数应与形参相同,但为了更方便地使用函数,C++也允许定义具有缺省参数的函数,这种函数调用时,实参个数可以与形参不相同。
(2)缺省参数指在定义函数时为形参指定缺省值(默认值)。
(3)这样的函数在调用时,对于缺省参数,可以给出实参值,也可以不给出参数值。如果给出实参,将实参传递给形参进行调用,如果不给出实参,则按照缺省值进行调用。
缺省参数的函数调用:
缺省实参并不一定是常量表达式,可以是任意表达式,甚至可以通过函数调用给出。如果缺省实参是任意表达式,则函数每次被调用时该表达式被重新求值,但表达式必须有意义:
例 :
void fun(int a, int b = 0, int c = 100)
{
cout << " a = " << a << " b = " << b << " c = " << c << endl;
}
int main()
{
fun(10);//1
fun(10, 20);//2
fun(10, 20, 30);//3
fun(10, , 30);//4 error
return 0;
}
分析:先看fun函数,fun函数中,形参个数有3个分别为a,b,c,但是在定义函数时给了b和c的值,如果下面调用这个fun函数的话,要么实参只给a给值,要么给a和b一起给值,要么给a,b,c一起给值,切记不要跨越给值,类似代码中的4。当然,在定义函数时,想要给形参给默认值,也不要跨越给值,这在C++中是不允许的。
注意:在定义函数时,给形参给值,只是给了一个默认值,在后面的主函数中调用时,这个值是可以改变的,比如fun(10, 20, 30);
上述代码的运行结果:
1.2 缺省参数在多文件中(.h,.cpp)
首先创建一个头文件,C.h,写入函数fun的声明,在.cpp中定义后进行运行,即:
//C.h文件
void fun(int a, int b = 20, int c = 100);
//void fun(int, int = 20,int = 100);
//.cpp文件
void fun(int a, int b = 0, int c = 10)
{
cout << " a = " << a << " b = " << b << " c = " << c << endl;
}
int main()
{
fun(10);
fun(10, 20);
fun(10, 20, 30);
return 0;
}
这种情况在C++中是不允许的,因为在.cpp中,加的头文件是#include<C.h>,说明我们在.h中已经给了b和c的值,如果在.cpp中再给,那么在主函数中调用时,应该用那个形参的默认值,这就产生了矛盾,因此可以选择在其中一个中指定缺省参数值,即定义或者声明中二选一,那么正确的代码为:
//C.h文件
//函数声明
void fun(int a, int b = 20, int c = 100);
//void fun(int, int = 20,int = 100);
//.cpp文件
void fun(int a, int b , int c )
{
cout << " a = " << a << " b = " << b << " c = " << c << endl;
}
int main()
{
fun(10);
fun(10, 20);
fun(10, 20, 30);
return 0;
}
运行结果:
注意:习惯上,缺省参数在公共头文件包含的函数声明中指定,不要在函数的定义中指定;如果在函数的定义中指定缺省参数值,在公共头文件包含的函数声明中不能再次指定缺省参数值。
1.3 缺省实参可以使用任意表达式
例:先定义一个产生随机值的函数,有
int my_rand()
{
int ra = rand() % 100;
return ra;
}
再定义一个函数,去调用上面产生随机值的函数
void fun(int a, int b = my_rand())
{
cout << " a = " << a << " b = " << b << endl;
}
主函数中进行调用
int main()
{
fun(12);
fun(12, 13);
return 0;
}
运行结果:
总结:说明缺省实参不一定是我们常见的常量表达式,也可以使用任意表达式;而且,在主函数中进行调用时也是可以两个值都给,也可以只给出a的值,这些调用程序执行都是OK的。
注意:当缺省实参是一个表达式时,在函数调用时该表达式被求值。
C语言是不支持缺省参数这种形式。
2. 函数重载
2.1 C中函数调用实现
int max_i(int a, int b)
{
return a > b ? a : b;
}
double max_d(double a, double b)
{
return a > b ? a : b;
}
char max_c(char a, char b)
{
return a > b ? a : b;
}
int main()
{
int s1 = max_i(10, 20);
double s2 = max_d(10.11, 20.22);
char s3 = max_c('a', 'b');
printf("s1 = %d,s2 = %lf,s3 = %c\n", s1, s2, s3);
return 0;
}
运行结果:
分析:这三个函数都执行了相同的操作,都是返回两个形参中的最大值,从用户的角度来看,只有一种操作,就是判断最大值,至于具体怎么完成的细节,用户一点也不关心;其实,这种词汇上的复杂性不是“判断参数中的最大值”问题本身固有的,而是反映了C中的一种局限性:在同一个域中出现的函数名字必须指向一个函数体,也就是在同一个.c文件中不能出现相同的函数名字,从侧面来说,C中定义函数时,函数名是不能重复的,并且函数调用时只看函数名来调用
注意:那么我们想想,程序很复杂,很多时,会出现非常多的函数名,在主函数中调用时,程序员必须一个一个的去找函数名,这会很麻烦,因此,为了解决这一问题,在C++中出现了“函数重载”这一概念。
1.2 函数重载
1.2.1 函数重载定义
在C++中可以为两个或者两个以上的函数提供相同的函数名称,只要参数类型不同,或参数类型相同而参数个数不同,称为函数的重载。
通过如下的代码更深一步了解函数重载的定义:
int my_max(int a, int b)
{
return a > b ? a : b;
}
double my_max(double a, double b)
{
return a > b ? a : b;
}
char my_max(char a, char b)
{
return a > b ? a : b;
}
int main()
{
int s1 = my_max(10, 20);
double s2 = my_max(10.11, 20.22);
char s3 = my_max('a', 'b');
cout << " s1 = " << s1 << " s2 = " << s2 << " s3 = " << s3 << endl;
return 0;
}
运行结果:
总结:上述程序中每个同名函数的参数是唯一的,也就是,当一个函数名在同一个域中被声明多次时,编译器会按照如下的步骤解释第二个的声明,如果两个函数的参数表中的个数或类型或顺序不同,则认为这两个函数是重载,
1.2.2 判断函数重载的规则
(1)如果两个函数的参数表相同,但是返回类型不同,会被标记为编译错误:函数的重复申明
(2)参数表的比较过程与形参名无关
运行后会报错:
总结:其实上面两个函数就是定义的同一个函数,只是将变量名字换了。
(3)如果在两个函数的参数表中,只有缺省参数不同,则第二个声明被视为第一个的重复声明,如
这说明,上面两个print函数是一样的。
(4)typedef名为现有的数据类型提供了一个替换名,它并没有创建一个新类型,因此,如果两个函数参数表的区别只在于一个使用了typedef,而另一个使用了与typedef相应的类型,则该参数表被视为相同的参数列表,如:
(5)当一个形参类型有const或volatile修饰时,如果形参是按值传递方式定义,在识别函数声明是否相同时,并不考虑const和volatile修饰符,如:
(6)当一个形参类型有const或volatile修饰时,如果形参定义指针或引用时,在识别函数声明是否相同时,就要考虑const和volatile修饰符,如:
(7)注意函数调用的二义性:
如果两个函数的参数表中,形参类型相同,而形参个数不同,形参默认值将会影响函数的重载,如:
在函数调用时,fun(10),应该调用函数1和3哪个?fun(10,20)应该调用2和3哪个函数?这就有了二义性。
(8)函数重载解析的步骤
①确定函数调用考虑的重载函数的集合,确定函数调用中实参表的属性;
②从重载函数集合中选择函数,该函数可以在(给出实参个数和类型)的情况下可以调用函数;
③选择与调用最匹配的函数。
1.2.3 名字粉碎(名字修饰)
C或者C++函数在内部(编译和链接)通过修饰名识别,修饰名是编译器在编译函数定义或者原型时生成的字符串;修饰名由函数名、类名、调用约定、返回类型、参数等共同决定。
(1)调用约定:
_stdcall是Pascal程序的缺省调用方式,通常用于win 32 Api中,函数采用从右到左的压栈方式,自己在退出时清空堆栈。
C调用约定(即用_cdecl关键字说明)按从右至左的顺序压参数入栈,由调用者把参数弹出栈,对于传递参数的内存栈是由调用者来维护的(正因为如此,实现可变参数的函数只能使用该调用约定)。
_fastcall调用约定是“人”如其名,它的主要特点是快,因为它是通过寄存器来传递参数的(实际上,它用ECX和EDX传送前两个双字(DWORD)或更小的参数剩下的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈),在函数名修饰约定方面,它与前两者均不同。
thiscall仅仅应用于“C++”类的成员函数,this指针存放于ECX寄存器,参数从右到左压,thiscall不是关键词,因此不能被程序指定。
在C/C++中,一个程序需要运行起来,需要经历以下几个阶段:预编译(预处理)、编译、汇编、链接。
(2)C语言编译时函数名修饰约定规则:
C语言的名字修饰规则非常简单,_cdecl是C/C++的缺省调用方式,调用约定函数名字前面添加了下划线前缀。
例:格式:_functionname
例:@functionname@number;
_fastcall调用约定在输出函数名前加上一个"@"符号,函数名后面也是一个“@”符号和其他参数的字节数。
(3)C++编译时函数名修饰约定规则:
_cdecl调用约定:
①以“?”标识函数名的开始,后跟函数名;
②函数名后面以“@@YA”标识参数表的开始,后跟参数表;
③参数表以代号表示;
X——void
D——char
E——unsigned char
F——short
H——int
I——unsigned int
J——long
K——unsigned long
M——float
N——double
_N——bool
PA——表示指针,后面的代号表明指针类型,如果相同类型的指针连续出现,以“0”代替,一个“0”代表一次重复。
参数表的第一项为该函数的返回类型,其后依次为参数的数据类型,指针标识在其所指数据类型前。
参数表后以“@Z”标识整个名字的结束,如果该函数无参数,则以“Z”标识结束。
例:
C++函数是重载
例如我们刚开始写的函数:
关键字
extern “C”:函数名以C的方式修饰约定规则;
extern “C++”:函数名以C++的方式修饰约定规则;
总结:在C++中函数可以重载,是因为名字粉碎操作。