第七章:函数
1. const类型函数参数
当一个函数的参数为const的引用或指针类型时,在函数的作用域中必须保证参数不改变:
extern fun1(int &);
void fun(const int & xx)
{
fun1(xx); 错误,无法保证在fun1中xx不被改变
}
上面的错误有三种修改方案:
(1) extern fun1(int); 把fun1的参数声明为按值传递,这样可以保证fun中的xx的值不被改变。
(2) extern fun1(const int &); 把fun1的参数声明为传递常引用,这样也可以保证xx不被修改。
(3) int x = xx; fun1(x); 定义一个临时变量,拷贝xx的值,这样fun1修改的就是临时变量的值。
上面三中修改方案都是可以接受的。
2. 参数类型的选择
默认情况下位引用和指针参数都是按值传递。一般通过以下准则来选择参数传递类型:
(1) 对于基本数据类型,且不需要在函数中改变实参值,推荐使用值传递。
(2) 引用变量一旦定义就必须用一个有效的对象初始化,它不允许引用一个空值。指针参数可以指向空地址。所以在函数调用时,如果实参可能为空,就不能选引用传递了而必须用指针传递了。
注意:由于指针的这一特性,所以在函数中使用指针之前,必须判断指针所指对象是否有效,因为对一个空地址进行解引用是非法的。使用引用参数就可以直接使用,而无需判断对象是否有效,这个工作由调用时完成。
(3) 引用一旦初始化,就不能在引用其他的对象了,因此如果在函数中需要形参指向其他位置或引用其他对象,就必须使用指针传递了。
(4) 引用的效率和指针相同,它比指针的好处是在使用中和变量保持一致,所以在不涉及到上面两点的情况下,一般推荐使用引用传递参数。
(5) 如果一个函数中不需要改变实参的值,建议将行参声明为const类型。(值传递就无所谓了,因为值传递本身就不改变实参的值。)
3. 一维数组参数
数组作为参数,它传递的是数组第一个元素的指针。编译器不检查数组的大小,只检查其类型。
void putvalue(int *); 声明了一个参数为int指针的函数
void putvalue(int []); 声明了一个参数为int指针的函数
void putvalue(int [10]); 声明了一个参数为int指针的函数
上面三种函数声明的含义是一致,调用函数时,编译器只检查参数是否为int*,其它的不检查。
int i, j[2];
putvalue(&i); 正确,但是在函数中会隐含错误,数组极可能越界
putvalue(j); 正确,但是还是无法保证数组是否越界
为了解决数组越界的问题,先有三种处理方式:
(1) 在数组参数后面加上一个数组长度的参数,在函数用此参数类限制数组长度。上面的函数声明换成:
void putvalue(int[], int size);
int i, j[2];
putvalue(&i, 1); 在函数中限制访问i的长度为1,就不会越界
putvalue(j, 2); 在函数中限制访问j的长度为2,就不会越界
这中方式是C程序设计中贯用的一种方法。
(2) 将参数数组声明为数组的引用。数组的引用包含数组的长度,编译器会检查数组参数的长度。
void putvalue(int (&array)[10]);
定义中array是一个包含10个元素的的一维数组的引用。在函数调用中,实参必须是一个包含10个元素的一维数组才能正确调用。
putvalue(&i); 错误,实参长度不为10
putvalue(j); 错误,实参长度不为10
注意:
int &array[10] 含义是定义了一个一维数组array,数组中的元素时整形引用。这个定义是错误的,C++中不允许定义引用数组。
int (&array)[10] 含义是array是一个引用,引用的类型是int[10],即array引用一个包含10个元素的一维数组。
(3) 使用容器作为参数
标准C++中,容器是可以随时取得自身长度的,所以可以在函数中进行限制。
4. 多维数组参数
多维数组参数必须指明除第一维外的所有维的长度,在传参时,编译器只检查除
第一维外的所有维的长度是否匹配。
void putvalue(int (*array)[10]);
void putvalue(int array[][10]);
void putvalue(int array[20][10]);
上面三种声明是一致的,调用时,编译器只会检查实参是否是int (*array)[10]类型的,只要
求实参的第二位长度为10,对第一维的长度不作限制。
注意:
int *array[10] 含义是定义了一个包含10个int型指针的一维数组array。
int (*array)[10] 含义是array首先是个指针,它指向的是一个包含10个元素
的一维数组,即array指向一个数组,数组本身就是一个指针,那么array就是一个指向指针的指针,即array是一个第二维长度为10的二维数组。
5. 函数缺省参数
同一个函数只能定义一次,但是可以声明多次。函数的同一个参数只能声明一次缺省的参数。
void ff(int a, int b, int c = 0); 正确,声明一个函数,其参数c是有缺省参数的。
void ff(int a, int b, int c = 0){} 错误,定义这个函数,由于参数c已经指定了缺省参数,这里就不能再次指定了。
说明:我们一般把函数的缺省参数定义在函数的声明中,而在定义中不指定缺省
参数。
void ff(int a, int b, int c); 正确,再次声明这个函数,此时函数的c参数是带
有默认值的。
void ff(int a, int b, int c = 0); 错误,再次声明函数不能声明相同的缺省参数。
void ff(int a = 0, int b, int c); 错误,为a指定了缺省参数,那么a右边的参数必须全部都有缺省参数。
void ff(int a, int b = 0, int c); 正确,再次声明为参数b指定了缺省参数,这样在调用此函数时,b,c都有缺省参数了。
注意:上面这中形式的声明并没有违反缺省参数的声明规则,由于c已经指定了
缺省参数,所以可以直接为b指定缺省参数,如果c没有指定缺省参数,那么此
声明就是错误的。
void ff(int a, int b = 0, int c = 0); 错误,不能重复指定参数的缺省值。
void ff(int a, int b = Default(), int c); 正确,缺省的参数可以是任何合法的表达式。
6. 省略号参数列表
C++中还保留了一个C方法的函数声明方式:函数的参数不定。
int printf(const char *, …); 这是C的格式化输出库函数,在调用时编译器只
检查第一个实参是否是const char * 类型,其他的
参数不检查,由编写值自己把握。
void fun(…); 函数调用时,编译器不检查函数的实参是否合法。
这种声明方法躲避了编译器的参数类型安全检查,不建议使用。
7. extern “C链接指示器”
extern “C”有三种用法:
extern “C” void foo(); 声明单条语句,函数foo用C方式进行编译和链接
extern “C”{ void foo(); void fun(); } 声明一个语句段,语句段中的函数都是采用C方式进行编译和链接的。
extern “C”{ #include <math.h> } 声明一个头文件,文件中的函数都是采用C方式进行编译和链接的。
注意:链接指示器不允许出现在函数定义中。
8. 函数指针
给一个函数指针赋值,必须保证赋值运算符左边和右边的函数的返回值和参数完
全一致。
带省略号的函数指针和不带省略号的相同函数的函数指针不相同。
void printf(const char *, …);
void printff(const char *);
void (*pfun)(const char *) = printf; 错误,pfun和printf类型不一致
void (*pfun)(const char *) = printff; 正确,类型一致
C函数和C++函数指针也不一致。
extern “C” void printf(const char *);
void (*pfun)(const char *); 错误,C函数和C++函数类型不一致
extern “C” void (*pfun)(const char *); 正确,函数指针也是C类型函数
extern “C” typedef void (*Fun)(const char *);
Fun fun = printf; 正确,注意这里extern “C”和typedef的位置
注意:下面两个定义含义不一致。
void (*pfun)(const char *); 定义了一个函数指针变量pfun,类型是
void (*)(const char *)
typedef void (*pfun)(const char *); 定义了一个函数指针类型pfun,可以用此类型来定义函数指针变量。如:
pfun p; 用函数指针类型pfun来定义一个变量p,此变量可以指向相同类型
的函数。