【C基础】07 函数

本文详细介绍了C语言中的函数,包括函数的定义、调用、声明、本地变量、实际参数、函数返回、程序终止、递归等核心概念。特别强调了函数调用时的值传递特性,数组作为参数的处理方式,以及递归函数的实现,如求阶乘和指数运算。此外,还讨论了函数的返回值在程序控制和异常终止中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、函数的定义

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型;②整值提升。charshort型提升成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、快速排序算法

【C语言补充】快速排序算法(递归)

八、其他

  • 函数没有参数时,应该用void f(void);void f()表示函数参数表未知,而非没有参数。系统会默认int
  • 调用函数时f(a,b)内的逗号,是标点符号,而非逗号运算符。f( (a,b) )内的,为逗号运算符,等价于f(b)
  • C语言不允许函数的嵌套定义。即,一个函数内不能定义其他函数。

main函数:

  • int main()也是一个函数,但最好写明int main(void)
  • main函数有时有2个参数,通常名为argcargv
  • main函数的返回值是状态码,0——正常终止、非0——异常终止。
  • 不建议将main函数设定为void返回类型,为了可能测试程序终止时的状态,最好把它声明为有返回值的函数,如int类型。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值