第七章 函数
1.函数的定义
函数的定义就是函数体的实现。
1.1函数定义的语法:
/*
类型
函数名( 形式参数 )
代码块
*/
void function_name(){
}
1.2 return语句
当执行流到达函数定义的末尾时,函数就将返回(return),也就是说,执行流返回到函数被调用的地方。return语句允许从函数体的任何位置返回,并不一定要在函数体的末尾。
只有当编译器可以通过寻常算术转换把表达式的类型转换为正确的类型时,才允许返回的类型与函数声明的返回类型不同的表达式。
2. 函数的声明
2.1 原型
原型总结了函数定义的起始部分的声明,向编译器提供有关该函数应该如何调用的完整信息。
使用原型最方便且最安全的方法是把原型置于一个单独的文件(调用使用#include指令包含该文件)。
int *find_int( int key, int array, int len );
//一个没有参数的函数的原型如下
int *func( void );
//关键字void提示没有任何参数,而不是表示它有一个类型为void的参数
3. 函数的参数
C语言的规则很简单:所有的参数都是传值调用。这意味着函数将获得参数值的一份拷贝,这样,函数就可以放心修改这个拷贝值,而不必担心会修改调用程序实际传递给它的参数。
但是,如果被传递的参数是一个数组名,并且在函数中使用下标引用该数组的参数,那么在函数中对数组元素进行修改时,实际上修改的是调用程序中的数组元素。
函数将访问调用程序的数组元素,数组并不会被复制。这个行为被称为“传值调用”。
下标引用实际上是间接访问的另一种形式,它可以对指针执行间接访问操作,访问指针指向的内存位置。
//交换调用程序中的两个整数
//没有效果
void swap( int x, int y ){
int temp;
temp = x;
x = y;
y = temp;
}
//有用的程序
void swap( int *x, int *y ){
int temp;
temp = *x;
*x = *y;
*y = temp;
}
声明数组参数时不指定它的长度是合法的,因为函数并不为数组元素分配内存。
但是,如果一个函数需要这个值,它必须作为参数显式地传递给函数。
void clear_array( int array[], int length ){
···
}
4. ADT和黑盒
C可以用于设计和实现抽象数据类型(ADT),因为它可以限制函数和数据定义的作用域。这个技巧也被称为黑盒设计。
抽象数据类型的基本想法是很简单的——模块具有功能说明和接口说明,前者说明模块所执行的任务,后者定义模块的使用。
限制对模块的访问是通过合理使用static(p45)关键字来实现的,它可以限制对那些并非接口的函数和数据的访问。
程序参考(p125)
//addrlist.h
/*
地址列表模块的声明
*/
#define NAME_LENGTH 30
#define ADDR_LENGTH 100
#define PHONE_LENGTH 11
#define MAX_ADDRESSES 1000
//接口函数
//给出一个名字,查找对应的地址
char const *lookup_address( char const *name );
//给出一个名字,查找对应的电话号码
char const *lookup_phone( char const *name )
/*
地址列表模块的实现
*/
#include"addrlist.h"
#include<stdio.h>
static char name[MAX_ADDRESSES][NAME_LENGTH];
static char address[MAX_ADDRESSES][ADDR_LENGTH];
static char phone[MAX_ADDRESSES][PHONE_LENGTH];
/*
这个函数在数组中查找一个名字并返回查找到的位置的下标
如果这个名字在数组中并不存在,函数返回-1
*/
static int find_entry( char const *name_to_find ){
int entry;
for( entry = 0; entry < MAX_ADDRESSES; entry += 1 )
if( strcmp( name_to_find, name[entry]) == 0 )
return entry;
return -1;
}
/*
接口函数
给定一个名字,查找并返回对应的地址。
如果名字没有找到,函数返回一个NULL指针。
*/
char const *lookup_address(char const *name){
int entry;
entry =find_entry( name );
if( entry == -1 )
return NULL;
else
return address[entry];
}
/*
接口函数
给定一个名字,查找并返回对应的电话号码。
如果名字没有找到,函数返回一个NULL指针。
*/
char const *lookup phone(char const *name ){
int entry;
entry = find_entry( name );
if( entry == -1)
return NULL;
else
return phone[entry];
}
5. 递归
C通过运行时堆栈支持递归函数的实现。递归函数就是直接或间接调用自身的函数。
/*
接受一个整型值(无符号),把它转换为字符并打印它。前导零被删除。
*/
#include<stdio.h>
void binary_to_ascii( unsigned int value ){
unsigned int quotient;
quotient = value / 10;
if( quotient != 0 )
binary_to_ascii( quotient );
putchar( value % 10 + '0' );
}
递归所需要的两个特性:a. 存在限制条件,当符合这个条件时递归便不再继续;b. 每次递归调用之后越来越接近这个限制条件。
递归的陷阱:递归函数调用将涉及一些运行时的开销——参数必须压到堆栈中、为局部变量分配内存空间、寄存器的值必须保存等。
6. 可变参数列表
可变参数列表是通过宏来实现的,这些宏定义于stdarg.h头文件,是标准库的一部分。
这个头文件声明了一个类型va_list和3个宏——va_start、va_arg和va_end。
v_list用于声明变量,用于访问参数列表的未确定部分。
va_start用于初始化,第1个参数是va_list变量的名字,第2个参数是省略号前最后一个有名字的参数。初始化过程把num变量设置为只想可变参数部分的第1个参数。
为了访问参数,需要使用va_arg,接受两个参数:va_list变量(num)和参数列表中下一个参数的类型。va_arg返回这个参数的值,并使num指向下一个可变参数。
#include<stdarg.h>
float average( int n_values, ··· ){
va_list num;
int count;
float sum = 0;
/*
准备访问可变参数
*/
va_start( num, n_values );
/*
添加取自可变参数列表的值
*/
for( count = 0; count < n_values; count +=1 ){
sum += va_arg( num, int );
}
/*
完成处理可变参数
*/
va_end( num );
return sum / n_values;
}