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]
24681012
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非正常终止
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值