一、函数的定义
1、定义格式
返回类型 函数名 (形式参数)
复合语句(函数体)
2、说明
- 函数定义的位置应该在
main
函数前面,C语言是按顺序执行代码的。若定义的函数想要放在main
函数后面,则需要在main
函数前声明。 - 函数可以有返回值。返回类型为
void
(空值)时,该函数没有返回值。 - 函数定义时,函数类型可单独成行。如:
double
average (double a, double b){
return (a+b)/2;
}
- 若函数没有形式参数(parameter),则括号内填写
void
,形参位置处不能空着。(main
函数除外,如int main(void)
与int main()
均合法) - 函数体内声明的变量专属此函数,其他函数不能对其进行检查或修改,即便定义的函数名相同,两变量的存储地址还是不同,不会互相影响。因此,调用函数时,永远只能传值给函数。
举例:
- 带返回值的函数
//函数定义
double average(double a, double b) {
return (a+b)/2; //此函数返回值为(a+b)/2
}
//函数调用
double x,y;
scanf("%lf%lf",&x,&y);
printf("Average of %f and %f is %f\n",x,y,average(x,y) );
- 不带返回值的函数
//函数定义
void average(double a, double b) {
printf("Average of %f and %f is %f\n",a, b, (a+b)/2 );
//没有return语句,无返回
}
//调用
double x,y;
scanf("%lf%lf",&x,&y);
average(x,y);
以上两段代码功能完全相同。
二、函数调用
1、调用格式
函数名(参数值)
2、说明
- 圆括号
()
为必需,即便调用的函数没有参数,也需要写圆括号。 - 若有参数,则需要给出正确的数量和顺序。按顺序依次初始化函数中的参数。
- 调用函数时,永远只能传值给函数(值传递),不能用变量名代替。每个函数有其独有的变量空间,参数也独立,和其他函数没关系,即便函数名相同,也互不影响。
举例:
void swap(int a, int b);
int main(){
int a = 2, b = 6;
swap(a,b);
printf("a = %d, b = %d\n",a,b);
return 0;
}
void swap(int a, int b)
{
int t;
t = a;
a = b;
b = t;
}
输出结果:
可见,a与b的顺序并未交换。
- 非
void
函数的调用会产生一个返回值,该值可被存储在变量中,也可以再传递给函数,也可丢弃。例如:printf
函数返回显示的字符个数,但一般被丢弃。
int a;
a = printf("Good morning!\n"); //'\n'是1个字符
printf("a = %d\n",a);
以上代码的输出结果为:
三、函数声明
- 任何一个函数被调用之前,必须先对其进行声明或定义。
- 函数声明的格式:
返回类型 函数名 (形式参数) ;
- 函数声明也称为函数原型。指明了函数名、函数参数和返回类型。目的是告诉函数长什么样。
- 函数声明(原型)可以不写参数名,但各个参数的类型必须要写。当函数参数含有指针时,
*
不可省略。例如可写成此形式:void f( int, int * );
,第一个参数为整型变量,第二个为指向整型变量的指针。
举例:
#include <stdio.h> //以'#'开头的是指令,而非语句,没有';'结尾。
double average(double a, double b); //函数声明
int main(){
//主函数体
}
//函数定义
double average(double a, double b){
return (a+b)/2;
}
- 函数声明写在另一个函数体内是合法的。这可以增加程序的可读性,便于读者理清函数间的调用关系。但不建议这么做,这可能增加程序修改难度。如:
int main(){
double average(double a, double b);
}
- 相同返回类型的函数,其声明可以合并。如:
void print_pun(void), print_count(int n);
- 函数的返回类型和变量类型一致时,函数声明与变量声明可以合并,但不建议这么写。
四、本地变量(局部变量)
1、定义
函数每次运行,就产生1个独立变量空间,在此空间中的变量,是函数此次运行所独有,称为本地变量。
- 定义在函数内部的变量为本地变量。
- (形式)参数也是本地变量。
2、变量的生存期和作用域
- 生存期:开始出现直至消亡的时期
- 作用域:可以访问该变量的代码范围
- 本地变量的生存期和作用域均是:大括号内——块
3、本地变量的规则
- 本地变量定义在块内(即
{}
内),可以是函数块或语句块 - 程序进入这个块之前,其中的变量不存在,离开这个块,其中的变量就消失了。
- 在块外面定义的变量在里面仍然有效。
- 块里面定义的变量则会覆盖外面的同名变量,在块结束后又不再覆盖。例如:
int a = 5;
{
int a = 1;
printf("块内 a = %d\n",a); //覆盖后,a=1
}
printf("块外 a = %d\n",a); //块结束后,a仍等于最初的5
运行结果为:
- 不能在同一个块内定义同名变量。
- 本地变量不会被默认初始化。
- (形式)参数在进入函数时已经被初始化,被实际参数(值)初始化。
不同块内的(本地)变量作用域(对应的
块
内)完全不同,生存期也完全不同,不会同时起作用,不会互相影响,即便它们同名。
利用此特性,可以在程序中新建一个块,用来调试显示程序运行中的某些内容,并且不会影响程序的正常运行。
四、实际参数(值)
实际参数(argument)是值传递的。
1、实际参数的转换
- 函数调用前有定义或声明:实参的值被隐式转换为相应形参类型。如实参为
int
型,调用的函数形参为double
型,则该int
型实参被自动转换为double
型。 - 函数调用前未遇到定义或声明:执行默认实参提升。①
float
型实参转换提升成double
型;②整值提升。char
、short
型提升成int
型。注意:是默认提升,不是强制类型转换,不会默认自动降阶。
2、数组型实际参数
数组作为函数的参数时:
(1) 形参是一维数组时,可以不用给出数组长度;
int f( int a[] ) //不必给出数组长度
{
//函数体
}
(2) 不能再利用sizeof
来计算数组长度
不能再利用`sizeof`来计算数组长度,必须用另一个参数来传入数组的大小。
例如:
int f( int a[] )
{
int len = sizeof(a)/sizeof(a[0]); //错误
}
int g( int b[], int n ) //正确写法,单独用另一个参数来传入数组长度
{
//函数体
}
(3) 形式参数的名称可省略,包括数组型形式参数。
int sum_array( int [], int ); //作为形式参数的数组的数组名被省略
调用时:
int b[lenth], total;
total = sum_array(b, length);
(4) 把数组名传递给函数时,不要加[]
例如:
total sum_array(b[], length); //错误写法
(5) 可通过函数改变作为形式参数的数组内的元素
如:
void store_zero( int a[], int n )
{
int i;
for(i=0; i<n; i++) {
a[i] = 0; //数组元素全置0
}
}
(6) 二维数组作为形式参数
定义二维数组时,可以不指出行的数量,但必须指明列的数量(从数组的存储方式理解)。
#define LENGTH 10
int sum_array( int a[][LENGTH], int n ) //省略行数,必须指明列数
{
int i, j;
int sum = 0;
for(i=0; i<LENGTH; i++) {
for(j=0; j<LENGTH; j++) {
sum += a[i][j];
}
}
return sum;
}
3、可变长度数组形式参数(C99)
固定长度数组:
int sum_array( int a[], int n )
{
...
}
可变长度数组:
int sum_array( int n, int a[n] ) //数组a的长度由n决定。n必须在a[]之前
{
...
}
注意:
- 形式参数为可变长度数组时,数组长度参数必须在数组之前,顺序不能反。
int sum_array( int a[n], int n ) //错误, 非法定义
{
...
}
- 其他合法写法
int sum_array(int n, int a[n] ); //原始写法
int sum_array(int n, int a[*] ); //数组长度用*代替
int sum_array(int, int [*] ); //数组长度用*代替,省略参数名
int sum_array(int n, int a[] ); //方括号[]内为空亦合法,但不建议如此,增加阅读难度
int sum_array(int, int a[] ); //省略参数名
4、在数组参数声明中使用static
数组参数声明使用关键字static
,不会 对程序有任何影响,但可以加快编译器对数组的访问速度。
举例:
int sum_array( a[static 3], int n ) //表明数组a的长度至少是3
{
...
}
- 对于多维数组,
static
仅适用于第一维(如二维数组的行数)。
5、复合字面量
复合字面量通过指定其包含的元素而创建的没有名字的数组。
例如:
常规写法:
int b[] = {3, 0, 3, 4, 1};
total = sum_array(b,5);
复合字面量写法:
total = sum_array( (int [5]) {3, 0, 3, 4, 1}, 5);
total = sum_array( (int []) {3, 0, 3, 4, 1}, 5); //方括号[]中的数组长度可被省略
复合字面量格式:
( 类型名 ) 初始化器
说明:
- 复合字面量中可以在初始化器中使用指示器。如:
(int [5] ) {1,2}
(前2个元素是1和2,其余为0); - 初始器内的元素可以是常量,也可以包含任意表达式。如:
total = sum_array( (int []) {i, j, i+j}, 3);
; - 符合字面量为左值,其元素值可以改变。
- 只读型复合字面量,使用类型限定符
const
,如(const int [] ) {1,2}
五、函数返回
1、格式
return 表达式 ;
2、说明
- 非
void
函数必须使用return
语句来指定返回值。 return
语句中表达式的类型与函数返回类型不匹配,会被隐式转换为返回类型。- 若没有明确指明函数返回类型,则C89编译器默认为
int
型,C99则非法。 - 函数知道是哪里调用它,会返回到正确的地方。
- 一个函数可以有多个
return
语句,但任一函数调用只能执行其中一个(任一函数最多只能有1个返回值)。到达return
语句(或函数体执行完最后一条语句)后,函数会返回调用点(功能类似于break
)。但最好是单一出口。 return
命令:return
停止函数的执行,并送回一个值;return;
return 表达式;
(表达式:计算值的公式,本身存储结算的结果)
3、没有返回值的函数
- 格式:
void 函数名(参数表)
。返回类型为void
。 - 不能使用带值的
return
(例如可以写return ;
),也可以没有return
。 - 调用时不能做返回值的赋值,否则会报错void value not ignored as it ought to be。
六、程序终止
main
函数的返回值是状态码,0——正常终止、非0——异常终止。- 终止
main
函数的方法:①执行return
语句;②调用exit
函数。 exit(EXIT_SUCCESS)
或exit(0)
表示正常终止,exit(EXIT_FAILURE)
表示异常终止。return 表达式 ;
等价于exit(表达式) ;
七、递归
定义:函数调用它本身,则此函数就是递归的(recursive)。
- 为避免无限递归,需要确定递归的终止条件。
递归函数举例:
1、求n!
n! = n×(n-1)×(n-2)×…×2×1
int fact(int n) //阶乘factorial
{
if( n<=1 ) return 1;
else return n*fact(n-1);
}
解析:
取n=4,
fact(4),4大于1,返回4*fact(3);
3大于1,返回3*fact(2);
2大于1,返回2*fact(1);
fact(1),返回1;
fact(2)返回2*1;
fact(3)返回3*fact(2),即3*(2*1)
fact(4)返回4*( 3*(2*1) ) = 24
2、求xn
xn = x • x • … • x
int power(int x, int n )
{
if( n==0 ) return 1;
else return x*power(x, n-1);
}
解析:
取n=3,
3!=0, power(x,3)返回x*power(x, 2), n=2;
2!=0, power(x,2)返回x*power(x, 1), n=1;
1!=0, power(x,1)返回x*power(x,0), n=0;
n=0, power(x,0)返回1;
power(x,1)返回x;
power(x,2)返回x*x;
power(x,3)返回x*x*x。
精简版:
int power(int x, int n )
{
return n==0 ? 1 : x*power(x, n-1);
}
3、快速排序算法
八、其他
- 函数没有参数时,应该用
void f(void);
。void f()
表示函数参数表未知,而非没有参数。系统会默认int
型 - 调用函数时
f(a,b)
内的逗号,
是标点符号,而非逗号运算符。f( (a,b) )
内的,
为逗号运算符,等价于f(b)
。 - C语言不允许函数的嵌套定义。即,一个函数内不能定义其他函数。
main
函数:
int main()
也是一个函数,但最好写明int main(void)
main
函数有时有2个参数,通常名为argc
和argv
main
函数的返回值是状态码,0——正常终止、非0——异常终止。- 不建议将
main
函数设定为void
返回类型,为了可能测试程序终止时的状态,最好把它声明为有返回值的函数,如int
类型。