08 函数

函数

模块化程序设计思想:

实现编写好一批常用的函数,需要使用时可直接调用,而不必重复在写,减少了程序的冗余,使得程序变得更加精炼,编写一次,就可以多次调用。

函数声明的作用:把有关函数的信息(函数名、函数类型、函数参数的个数与类型)通知编译系统,以便在编译系统对程序进行编译时,在进行到main函数中调用其它函数时,知道它们是定义的函数而不是变量或其他对象。

说明:

(1) 一个C程序由一个或多个程序模块组成,每一个程序模块作为一个源程序文件。对于较大的程序,一般不希望把所有的内容全放在一个文件中,而是将它们分别编写成若干个源文件中,由若干个源程序文件组成一个C程序。

(2) 一个源程序文件由一个或多个函数以及其他有关内容(如指令、数据声明与定义)组成。一个源程序文件是一个编译单位,在程序编译时是以源程序文件为单位进行编译的,而不是以函数为单位进行编译的。

(3) C程序的执行是从main函数开始的,如果在main函数中调用它其他函数,在调用后流程返回到main函数,在main函数找那个结束整个程序的运行。

(4) 所有函数都是平行的,即在定义函数时是分别进行的,是相互独立的;一个函数并不从属于另一个函数,即函数不能嵌套定义

(5) 从用户使用的角度看,函数有两种形式

​ ① 库函数

​ ② 用户自己定义的函数

(6) 从函数的角度看,函数有两种形式

​ ① 无参函数

​ ② 有参函数

一、函数的定义

一般来说,执行源程序就是执行主函数main,其他函数只能被主函数所调用,而其他函数之间也可以相互调用。

1.标准库函数:

分为:I/O函数,字符串,字符处理函数,数学函数,接口函数,时间转换和操作函数,动态地址分配函数,目录函数,过程控制函数,字符屏幕和图形功能函数。

这些库函数在不同的头文件中声明。比如:

<math.h>头文件中有:sin(x),cos(x),exp(x)(求e^x),fabs(x)(求x的绝对值)等库函数。

<stdio.h>头文件中有:scanf(),printf(),gets(),puts(),getchar(),putchar()等库函数。

<string.h>头文件中有:strcmp(),strcpy(),strcat(),strlen()等库函数。

2.函数的定义:

(1)定义无参函数:

类型名  函数名()

{undefined

函数体

}

or

类型名  函数名(void)

{undefined

函数体

}

(2)定义有参函数

类型名 函数名(形式参数表列)

{undefined

函数体

}

(3)定义空函数

类型名 函数名()

{    }

二、函数的调用

1.函数调用时的参数传递

函数间通过参数来传递数据,即通过主调函数中的实际参数( 实参 )向被调用函数中的形式参数( 形参 )进行传递。

实参形参 传递数据的方式: 实参 将值单向传递给 形参形参 的变化 不影响参值

例:

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int a, b;
	void swap(int a, int b);		//函数声明
	scanf("%d%d", &a, &b);		//键盘输入
	swap(a, b);		//函数调用
	printf("最终的a,b值:\n  a=%d b=%d\n", a, b);
	system("pause");
	return 0;
}
void swap(int a, int b)
{
	int t;
	if (a < b)
	{
		t = a;
		a = b;
		b = t;		//a中放大值,b中放小值
	}
	printf("自定义函数的a,b值:\n  a=%d b=%d\n", a, b);
}

运行结果为:

分析:形参交换了数据,而实参保持原数据不变。这是单向的值传递,所以形参的值改变后而实参的值没有改变。

(1)形参在函数中是变量名,在函数调用时,形参被临时分配相应的内存,调用结束后,形参单元被释放,而实参单元保留并维持原值。

(2)实参是表达式,负责向对应的形参标识的内存单元传递数据,实参向形参的数据传递是“值传递”。

(3)实参与形参必须个数相同

(4)对应的形参和实参的类型必须一致

2.函数的返回值

(1)函数的返回值通过函数中的return语句获得。

如果需要从调用函数带回一个函数值(供主函数使用),被调函数中需包含return语句。

例:

int max(int x,int y)
{
    return(x>y?x:y);
}

(2)在定义函数时要指定函数值的类型

int max(float x,float y)            //函数值为整型
char letter(char c1,char c2)            //函数值为字符型
double max(int x,int y)            //函数值为双精度型

(3)函数类型决定返回值的类型。

3.函数的嵌套

定义函数时不能定义另一个函数,但是可以进行嵌套调用函数。

例:

用函数嵌套找出4个数中的最大值。

#include<stdio.h>
#include<stdlib.h>
int max2(int a, int b)			//找出a和b中的最大值
{
	if (a >= b)
	{
		return a;			//	a作为返回值
	}
	return b;				//	b作为返回值
}
int max4(int a, int b, int c, int d)	//定义找4个数最大值的函数
{
	int m;		//存最大值
	m = max2(a, b);		//调用max2函数,将a,b中大的值放在m中
	m = max2(m, c);		//调用max2函数,将a,b,c中大的值放在m中
	m = max2(m, d);		//调用max2函数,将a,b,c,d中大的值放在m中
	return m;		//返回到主函数
}
int main()
{
	int a, b, c, d;
	int max;
	printf("请输入四个数:\n");
	scanf("%d%d%d%d", &a, &b, &c, &d);
	max = max4(a, b, c, d);			//调用max4函数
	printf("最大数位:%d\n", max);
	system("pause");
	return 0;
}

三、函数的递归

1.递归概念:

函数的递归调用是指:一个函数在他的函数体内直接或间接地调用它自身。分为:直接递归(函数直接调用自身)和间接递归(函数通过其他函数调用自身)。可分为“回溯”和“递推”两个阶段。

2.递归函数的一般形式:

反值类型   递归函数名(参数说明表)

{

if(递归终止条件)

返回值=递归终止值;

else

返回值=递归调用(...)的表达式;

return 返回值;

}

3.递归函数举例:

用递归求n的阶乘。

#include<stdio.h>
#include<stdlib.h>
int fac(int n)		//定义函数
{
	int f;
	if (n < 0)
		printf("数据错误\n");
	else if (n == 0 || n == 1)
		f = 1;
	else
		f = n* fac(n - 1);			//n!=n*(n-1)
	return f;			//返回主函数
}
int main()
{
	int n, y;
	printf("请输入n的值\n");
	scanf("%d", &n);
	y = fac(n);		//这里n为实参
	printf("%d!=%d\n", n, y);
	system("pause");
	return 0;
}

汉诺塔问题为经典的递归调用实例。代码如下:

#include<stdio.h>
#include<stdlib.h>
void move(char x, char y)	//输出移盘方案
{
	printf("%c->%c\n", x, y);
}
void hanoi(int n, char one, char two, char three)		//移盘
{
	if (n == 1)
		move(one, three);		//如果是1个盘,直接从第一个座移到第3个座上
	else
	{
		hanoi(n - 1, one, three, two);		
		move(one, three);
		hanoi(n - 1, two, one, three);
	}
}
int main()
{
	int n;
	printf("输入盘的个数\n");
	scanf("%d", &n);
	printf("移盘的步骤:\n");
	hanoi(n, 'A', 'B', 'C');
	system("pause");
	return 0;

分析:将n个盘子从A座上移到C座上需要三步:

(1)将A上的n-1个盘子借助C座先移到B座上;

(2)把A座上剩下的一个盘子移到C座上;

(3)将n-1个盘从B座上借助于A移到C座上。

四、数组函数

用数组元素来作为函数参数

另外,用数组名也可以作为实参和形参,传递的是数组的首地址

一、用数组元素作为函数实参

这与用变量作为实参一样,是单向传递,取“值传递”的方式。

二、用数组名作为函数参数

此时,实参和形参都要用数组名(或用指针)

注意:

1、形参数组名和实参数组名应该在自己所在的函数内部定义,不能只在一方定义;

2、实参数组和形参数组的类型应当一致;

3、不是“值传递”,不是“单向传递”,而是“地址传递”,两个数组共占一段内存单元:形参数组中各元素的值发生变化,会使实参数组中的元素的值通知变化。

a[0] a[1] a[2] a[3] a[4] a[5]
2 4 6 8 10 12
b[1] b[2] b[3] b[4] b[5] b[6]

三、用多维数组作为函数参数

可以用多维数组作为函数的实参和形参,对于在被调函数中对形参数组的定义,可以指定每一维的大小,也可以省略第一维的大小,但是不能把第二维解其他维的大小省略。

局部变量和全局变量

我们首先提出一个问题:在一个函数中定义的变量,在其他函数中能否被引用?在不同位置定义的变量,在什么范围内有效?

以上的问题就是该节讨论的变量的作用域的问题。每一个变量都有一个作用域问题,即它们在什么范围内有效。

局部变量

定义变量可能有3种情况:

(1)在函数的开头定义

(2)在函数内的符合语句内定义

(3)在函数的外部定义

在一个函数内部定义的变量只在本函数范围内有效,也就是说只有在本函数内才能引用它们,在此函数以外是不能使用这些变量的。在复合语句内定义的变量只在本复合语句范围内有效,只有在本复合语句内才能引用它们。在该复合语句以外是不能使用这些变量的,以上这些称为“局部变量”

输入图片说明

说明:

(1)主函数中定义的变量(如m,n)也只能在主函数中有效,并不因为在主函数中定义而在整个文件或程序中有效。主函数不能使用其他函数中定义的变量,如f1中的b,c;f2中的m,n都不能使用

(2)不同函数中可以使用同名的变量,它们代表不同的对象,互不干扰。如f1中定义的b,c也可在f2中定义

(3)形式参数也是局部变量

(4)在一个函数内部,可以在复合语句中定义变量,这些变量只在复合语句中有效,这些复合语句也称为“分程序”或“程序块”

输入图片说明

全局变量

程序的编译单位是源程序文件,一个源文件可以包含一个或若干个函数。

在函数内定义的变量是局部变量,而在函数之外定义的变量称为外部变量,外部变量是全局变量。全局变量可以为本文件中其他函数所共用,它的有效范围为从定义变量的位置开始到本源文件结束。

注意:在函数内定义的变量是局部变量,在函数外定义的变量是全局变量。
输入图片说明

P,q,c1,c2都是全局变量,它们的作用范围不同,在main函数和f2函数中可以使用全局变量p,q,c1,c2,但在函数f1中只能使用p,q而不能使用c1,c2.

说明:

设置全局变量的作用是增加了函数间数据联系的渠道。由于同一个文件中的所有函数都能引用全局变量的值,因此如果在一个函数中改变了全局变量的值,就能影响到其他函数中全局变量的值。相当于各个函数间有直接的传递通道。由于函数的调用只能待会一个函数返回值,一次有时可以利用全局变量来对增加函数建的联系渠道,通过函数调用能得到一个以上的值。

为了便于区别全局变量和局部变量,在C程序设计中有一个习惯(并非规定)。将全局变量名的第1个字母用大写表示。

说明:建议不再必要时不要使用全局变量,理由如下:

(1)全局变量在程序的全部执行过程中都占用存储单元,而不是仅在需要时才开辟单元。故消耗内存单元

(2)它使函数的通用性降低了,如果在函数中引用了全局变量,那么执行情况会受到有关的外部变量影响,如果有一个函数移到另一个文件中,还要考虑把有关的外部变量及其值一起转移过去。但若该外部变量与其他文件的变量同名时,就会出现问题。这就降低了程序的可靠性和通用性。在程序设计中,在划分模块时要求模块的“内聚性”强,与其他模块的“耦合性”弱。即模块的功能要单一,不相互影响或者影响较小。

(3)使用全局变量过多,会降低程序的清晰性,难以判断瞬间各个外部变量的值。由于在各个函数执行过程中都可能改变外部变量的值,故程序容易出错。

注意:如果在同一个源文件中,全局变量与局部变量同名,这时会出现什么情况呢?

答案是,在局部变量的作用范围内,局部变量有效,全局变量被“屏蔽”,即全局变量不起作用。简单说,就是局部变量在该范围内覆盖了全局变量。

变量的存储方式和生存期

1、变量的存储类型

​ 存储类型可分为4类,分别是:自动变量(automatic variable)、外部变量(extern variable)、静态变量(static variable)、寄存器变量(register variable)。其中:自动变量和寄存器变量只能是局部变量,采用动态存储方式;外部变量和静态变量具有全程生存期,可为全局变量,采用静态存储方式。

C语言中对变量存储类型的定义格式如下:

存储类型说明符	数据类型说明符	变量名称列表;

存储类别

存储类别指 数据在内存中的存储的方式(如静态、动态存储)

C语言的存储类别有4种:自动的(auto)、静态的(static)、寄存器的(register)、外部的(extend),根据变量的存储类别可以知道变量的作用域和生存期。

1、auto

函数中的局部变量如果不专门声明为static则都是auto存储类别。在调用函数时,系统会给变量分配存储空间,在函数调用结束时就自动释放这些存储空间。

自动变量在函数调用时对其赋值操作。

2、static

若函数中的局部变量的值在函数调用结束后不消失而继续保留原值,即其占用的存储单元不释放,再下一次再调用该函数时,该变量已有值(即上次调用结束时的值)

对静态局部变量在编译时赋初值。static静态变量只在声明时初始化一次)

3、register

局部变量的值放在CPU的寄存器中。对寄存器的存取速度远高于对内存的寄存速度,可以提高执行效率。 register说明的变量是建议编译器将变量的值保存在寄存器中,所以register没有地址不能进行地址运算!)

4、extern

全局变量都是存放在静态存储区中。因此他们生存期都是固定的,存在于整个运行过程。

它的作用域实从变量的定义处开始,到本程序文件的末尾。但是可扩展外部变量的作用域。

小结

数据定义需要指定两种属性:数据类型和存储类别。

extern可声明已定义外部变量。

1.作用域分为局部变量和全局变量

局部变量:自动变量(离开函数,值就消失)、静态局部变量(离开函数,值仍保留)

寄存器变量(离开函数,值就消失)、形参也为自动变量或寄存器变量

全局变量:静态外部变量(只限本文件引用)、外部变量(允许其他文件引用)

2.生存期分为动态存储和静态存储

动态存储:自动变量(本函数内有效)、寄存器变量(本函数内有效)、

形参(本函数内有效)

静态存储:静态局部变量(函数内有效)、静态外部变量(本文件内有效)、

外部变量(用extern声明后,其他文件可引用)

3.存放位置分为内存静态存储区、内存动态存储区和CPU寄存器

静态存储区:静态局部变量、静态外部变量(函数外部静态变量)、

外部变量(可为其他文件引用)

动态存储区:自动变量和形式参数

CPU寄存器:寄存器变量

4.作用域和生存期

作用域(空间):在此作用域内可以引用该变量,变量在作用域内“可见”(可见性)

生存期(时间):在某一时刻存在,变量在此时刻“存在”(存在性)

5.static对局部变量和全局变量的作用不同。(都是使作用域局限)

对局部变量,它使变量由动态存储方式改变为静态存储方式。对全局变量,它使变量局部化(局部于本文件)

变量存储类别 函数内 函数外
作用域 存在性 作用域 存在性
自动变量和寄存器变量 × ×
静态局部变量 ×
静态外部变量 √(只限本文件)
外部变量

关于变量的声明和定义

一个函数由两部分组成:声明部分和执行语句

声明部分的作用:对有关的标识符(变量、函数、结构体、共用体)的属性进行声明。

函数的声明是函数的原型,而函数的定义是对函数功能的定义。

对于变量而言,声明与定义的关系复杂一些。不过总结出来就是,建立存储空间的声明称定义,而把不需要建立存储空间的声明称为声明

内部函数和外部函数

函数本质上是全局的,因为定义一个函数的目的就是要被另外的函数调用。如果不加声明的话,一个文件中的函数既可以被本文件中其他函数调用,也可以被其他文件中的函数调用。但是,凡事都有特例,也可指定某些函数不能被其他文件调用。

内部函数

​ 如果一个函数只能被本文件中其他函数锁调用,它称为内部函数。在定义内部函数时,在函数名和函数类型的前面加static,即

static 类型名 函数名(形参表);

如:

  static int fun(int vara,int varb);

表示fun是一个内部函数,不能被其他文件调用

​ 内部函数又称为静态函数,因为它是用static声明的。被static声明的函数作用域只局限于所在的文件。

​ 通常把只能由本文件使用的函数和外部变量放在文件的开头,前面都冠以static使之局部化,其他文件不能引用。这就提高了程序的可靠性

外部函数

​ 如果在定义函数时,在函数首部的最左端加关键字extern,则此函数是外部函数,可供其他文件调用,即

static 类型名 函数名(形参表);

如:

  extern int function(int vara,int varb);

表示function是一个外部函数,可被其他文件调用。

C语言规定,如果在定义函数时省略extern,则默认为外部函数。

C语言常用库函数(含详细用法)

一、数学函数

调用数学函数时,要求在源文件中包下以下命令行:

#include <math.h>

函数原型说明 功能 返回值 说明
int abs( int x) 求整数x的绝对值 计算结果
double fabs(double x) 求双精度实数x的绝对值 计算结果
double acos(double x) 计算cos-1(x)的值 计算结果 x在-1~1范围内
double asin(double x) 计算sin-1(x)的值 计算结果 x在-1~1范围内
double atan(double x) 计算tan-1(x)的值 计算结果
double atan2(double x) 计算tan-1(x/y)的值 计算结果
double cos(double x) 计算cos(x)的值 计算结果 x的单位为弧度
double cosh(double x) 计算双曲余弦cosh(x)的值 计算结果
double exp(double x) 求ex的值 计算结果
double fabs(double x) 求双精度实数x的绝对值 计算结果
double floor(double x) 求不大于双精度实数x的最大整数
double fmod(double x,double y) 求x/y整除后的双精度余数
double frexp(double val,int *exp) 把双精度val分解尾数和以2为底的指数n,即val=x*2n,n存放在exp所指的变量中 返回位数x 0.5≤x<1
double log(double x) 求㏑x 计算结果 x>0
double log10(double x) 求log10x 计算结果 x>0
double modf(double val,double *ip) 把双精度val分解成整数部分和小数部分,整数部分存放在ip所指的变量中 返回小数部分
double pow(double x,double y) 计算xy的值 计算结果
double sin(double x) 计算sin(x)的值 计算结果 x的单位为弧度
double sinh(double x) 计算x的双曲正弦函数sinh(x)的值 计算结果
double sqrt(double x) 计算x的开方 计算结果 x≥0
double tan(double x) 计算tan(x) 计算结果
double tanh(double x) 计算x的双曲正切函数tanh(x)的值 计算结果

二、字符函数

调用字符函数时,要求在源文件中包下以下命令行:

#include <ctype.h>

函数原型说明 功能 返回值
int isalnum(int ch) 检查ch是否为字母或数字 是,返回1;否则返回0
int isalpha(int ch) 检查ch是否为字母 是,返回1;否则返回0
int iscntrl(int ch) 检查ch是否为控制字符 是,返回1;否则返回0
int isdigit(int ch) 检查ch是否为数字 是,返回1;否则返回0
int isgraph(int ch) 检查ch是否为ASCII码值在ox21到ox7e的可打印字符(即不包含空格字符) 是,返回1;否则返回0
int islower(int ch) 检查ch是否为小写字母 是,返回1;否则返回0
int isprint(int ch) 检查ch是否为包含空格符在内的可打印字符 是,返回1;否则返回0
int ispunct(int ch) 检查ch是否为除了空格、字母、数字之外的可打印字符 是,返回1;否则返回0
int isspace(int ch) 检查ch是否为空格、制表或换行符 是,返回1;否则返回0
int isupper(int ch) 检查ch是否为大写字母 是,返回1;否则返回0
int isxdigit(int ch) 检查ch是否为16进制数 是,返回1;否则返回0
int tolower(int ch) 把ch中的字母转换成小写字母 返回对应的小写字母
int toupper(int ch) 把ch中的字母转换成大写字母 返回对应的大写字母

三、字符串函数

调用字符函数时,要求在源文件中包下以下命令行:

#include <string.h>

函数原型说明 功能 返回值
char *strcat(char *s1,char *s2) 把字符串s2接到s1后面 s1所指地址
char *strchr(char *s,int ch) 在s所指字符串中,找出第一次出现字符ch的位置 返回找到的字符的地址,找不到返回NULL
int strcmp(char *s1,char *s2) 对s1和s2所指字符串进行比较 s1<s2,返回负数;s1= =s2,返回0;s1>s2,返回正数
char *strcpy(char *s1,char *s2) 把s2指向的串复制到s1指向的空间 s1 所指地址
unsigned strlen(char *s) 求字符串s的长度 返回串中字符(不计最后的'\0')个数
char *strstr(char *s1,char *s2) 在s1所指字符串中,找出字符串s2第一次出现的位置 返回找到的字符串的地址,找不到返回NULL

四、输入输出函数

调用字符函数时,要求在源文件中包下以下命令行:

#include <stdio.h>

函数原型说明 功能 返回值
void clearer(FILE *fp) 清除与文件指针fp有关的所有出错信息
int fclose(FILE *fp) 关闭fp所指的文件,释放文件缓冲区 出错返回非0,否则返回0
int feof (FILE *fp) 检查文件是否结束 遇文件结束返回非0,否则返回0
int fgetc (FILE *fp) 从fp所指的文件中取得下一个字符 出错返回EOF,否则返回所读字符
char *fgets(char *buf,int n, FILE *fp) 从fp所指的文件中读取一个长度为n-1的字符串,将其存入buf所指存储区 返回buf所指地址,若遇文件结束或出错返回NULL
FILE *fopen(char *filename,char *mode) 以mode指定的方式打开名为filename的文件 成功,返回文件指针(文件信息区的起始地址),否则返回NULL
int fprintf(FILE *fp, char *format, args,…) 把args,…的值以format指定的格式输出到fp指定的文件中 实际输出的字符数
int fputc(char ch, FILE *fp) 把ch中字符输出到fp指定的文件中 成功返回该字符,否则返回EOF
int fputs(char *str, FILE *fp) 把str所指字符串输出到fp所指文件 成功返回非负整数,否则返回-1(EOF)
int fread(char *pt,unsigned size,unsigned n, FILE *fp) 从fp所指文件中读取长度size为n个数据项存到pt所指文件 读取的数据项个数
int fscanf (FILE *fp, char *format,args,…) 从fp所指的文件中按format指定的格式把输入数据存入到args,…所指的内存中 已输入的数据个数,遇文件结束或出错返回0
int fseek (FILE *fp,long offer,int base) 移动fp所指文件的位置指针 成功返回当前位置,否则返回非0
long ftell (FILE *fp) 求出fp所指文件当前的读写位置 读写位置,出错返回 -1L
int fwrite(char *pt,unsigned size,unsigned n, FILE *fp) 把pt所指向的n*size个字节输入到fp所指文件 输出的数据项个数
int getc (FILE *fp) 从fp所指文件中读取一个字符 返回所读字符,若出错或文件结束返回EOF
int getchar(void) 从标准输入设备读取下一个字符 返回所读字符,若出错或文件结束返回-1
char *gets(char *s) 从标准设备读取一行字符串放入s所指存储区,用’\0’替换读入的换行符 返回s,出错返回NULL
int printf(char *format,args,…) 把args,…的值以format指定的格式输出到标准输出设备 输出字符的个数
int putc (int ch, FILE *fp) 同fputc 同fputc
int putchar(char ch) 把ch输出到标准输出设备 返回输出的字符,若出错则返回EOF
int puts(char *str) 把str所指字符串输出到标准设备,将’\0’转成回车换行符 返回换行符,若出错,返回EOF
int rename(char *oldname,char *newname) 把oldname所指文件名改为newname所指文件名 成功返回0,出错返回-1
void rewind(FILE *fp) 将文件位置指针置于文件开头
int scanf(char *format,args,…) 从标准输入设备按format指定的格式把输入数据存入到args,…所指的内存中 已输入的数据的个数

五、动态分配函数和随机函数

调用字符函数时,要求在源文件中包下以下命令行:

include <stdlib.h>

函数原型说明 功能 返回值
void *calloc(unsigned n,unsigned size) 分配n个数据项的内存空间,每个数据项的大小为size个字节 分配内存单元的起始地址;如不成功,返回0
void *free(void *p) 释放p所指的内存区
void *malloc(unsigned size) 分配size个字节的存储空间 分配内存空间的地址;如不成功,返回0
void *realloc(void *p,unsigned size) 把p所指内存区的大小改为size个字节 新分配内存空间的地址;如不成功,返回0
int rand(void) 产生0~32767的随机整数 返回一个随机整数
void exit(int state) 程序终止执行,返回调用过程,state为0正常终止,非0非正常终止
posted @ 2022-01-31 10:32  Wmic  阅读(181)  评论(0)    收藏  举报