本文大纲:
6.1 函数概述
C语言中,模块用函数来实现
函数分类以下两种:
1)标准库函数:用户不需要定义可直接使用。如scanf()、printf()等
2)用户自己定义的函数
例子:
从键盘输入两个正整数m和n,求m!/(m-n)!的值
函数的作用:
1)可方便地使用其他人已经编写的代码,就像调用系统提供的库函数
2)可以在后续程序中使用自己编写的代码
3)实现结构化程序设计的基本思想
结论:
1)一个完整的C程序可以由若干个函数组成,其中必须有一个且只能有一个主函数。从主函数开始执行,而其他函数只能被调用
2)完整的C程序中的所有函数可以放在一个文件中,也可以放在多个文件中
6.2 函数的定义与声明
先定义-->后声明-->再使用
首先定义函数的数据类型、存储类型、函数体
然后才使用
3个概念:函数定义、函数调用、函数声明
函数定义:定义函数的功能(未经定义的函数不能使用)。可分为库函数和用户自定义函数两种。
函数调用:执行一个函数;调用函数时,如果有参数首先传参数,程序由主调函数跳转到被调函数体内的第一条语句开始执行,执行完被调用函数体内的最后一条语句或中途遇到return语句时,又返回到主调函数继续向下执行
函数声明:通知编译系统该函数已经定义过了;库函数不需要写出函数声明,只需要在程序前面用#include包含具有该库函数原型的头文件即可;用户自定义函数,如果函数定义的位置在函数调用之后,则前面必须有函数声明;如果函数定义放在函数调用之前,则可省略函数声明
6.2.1
函数定义:
函数定义的一般形式:
[函数类型] 函数名字([形式参数表])
{
[声明部分]
[执行语句]
}
说明:一个函数(定义)由函数头(函数首部)和函数体两部分组成。
(1)函数头(首部):说明函数类型、函数名称、参数
1)函数类型:函数返回值的数据类型;基本类型或构造类型;默认int;不返回值则定义为void类型
2)函数名:给函数取的名字,以后用这个名字调用;命名规则与标识符相同
3)函数名后面是形式参数表,也可以是没有参数,但()不能省略,这是格式的规定。形式参数表是说明形式参数的类型和形式参数的名称,各个形式参数之前用,分隔。
(2)函数体:函数头下方用一对{}括起来的部分。如果函数体内有多个{},最外层是函数体的范围。函数体一般包括声明部分和执行部分。
1)声明部分:定义本函数所使用的变量和进行的有关声明(如函数声明)
2)执行部分:程序段,即由若干条语句组成的命令序列(可以在其中调用其他函数)
函数不能单独运行,函数可以被主函数或其他函数调用,也可以调用其他函数,但不能调用主函数
6.2.2 函数的参数和返回值
函数的参数分为形式参数和实际参数
形式参数(形参,形参的本质就是变量):函数定义时设定的参数。
例:int max3(int x,int y,int z)中的x,y,z就是形参,都是整型
实际参数(实参):调用函数时所使用的实际的参数。
例:主函数中调用max3函数的语句namx=max3(n1,n2,n3)中,n1,n2,n3就是实际参数,都是整型
形参和实参的功能:数据传递
只有发送函数调用时,主调函数把实参的值传递给被调函数的形参(也就是实参给形参赋值),从而实现主调函数向被调函数的数据传递。保证实参与形参的类型一致、个数一致、顺序一致
C语言可以从函数(被调函数)返回值给调用函数。通过return语句返回值,使用return语句能够返回一个值或不返回值(此时函数类型是 void)。
return语句格式
return [表达式];
return(表达式)
注意:
1)main是函数,必须有返回值,默认返回int类型。exit(0)或return(0)
2)函数的类型就是返回值的类型,return语句中表达式的类型与函数类型一致,如果不一致,一函数类型为准
3)如果函数没有返回值,函数类型应该说明void(空类型)
函数声明:
函数定义的位置随意。
1)函数定义位置在前,函数调用在后,不必声明,编译程序产生正确的调用格式
2)函数定义在调用它的函数之后或函数在其他源程序模块中,且函数类型不是整型,在函数使用前对函数进行声明
函数声明格式:
函数类型 函数名([形式参数表]);
6.3 函数的调用
一个函数调用另外一个函数称为函数调用,其调用者称为主调函数,被调用者称为被调函数。
三种方式:
1)函数语句形式
只进行某些操作而不返回函数值,这时的函数调用可作为一条独立的语句
2)函数表达式形式
函数作为表达式的一项,出现在表达式中,以函数返回值参与表达式的运算,这种方式要求函数必须有返回值
3)函数实参形式
函数作为另一个函数调用的实际参数出现。这种情况是把函数的返回值作为实参进行传送,因此要求该函数必须有返回值
函数语句形式:
函数表达式形式:
函数实参形式:
6.3.2 函数参数的传递形式
实参与形参的传递方式有两种:
值传递和地址传递
值传递:参数传递的是数据本身;数值只能由实参传递给形参,形参不能反过来传递给实参,即传值是单向的。形参的任何变化不会影响到实参。
过程:
1)发生函数调用时,系统临时创建形参变量
2)实参将其数值复制一份给形参变量
3)函数调用过程中,形参的任何改变只发生在被调函数内部,不会影响到实参
4)当被调函数运行结束返回主调函数时,形参的存储空间被自动释放
例:swap()函数
#include "stdio.h"
void swap(int a,int b) //形参a、b
{
int t;
printf("(2)子函数开始时:a=%d,b=%d\n",a,b); //输出子函数中交换操作前的数值
t=a;
a=b;
b=t;
printf("(3)子函数结束时:a=%d,b=%d\n",a,b); //输出子函数中交换操作后的数值
}
main()
{
int x=2,y=4;
printf("(1)子函数调用前:x=%d,y=%d\n",x,y); //输出swap()函数调用前的数值
swap(x,y);
printf("(4)子函数调用后:x=%d,y=%d\n",x,y); //输出swap()函数调用后的数值
return 0;
}
--实参向形参传值是单向传递
例:定义函数,求极值
#include "stdio.h"
main()
{
int n;
void s(int n);
printf("请输入n的值:\n");
scanf("%d",&n);
s(n);
printf("n=%d\n",n); //实参的值不随形参的变化而变化
}
void s(int n)
{
int i;
for(i=n-1;i>=1;i--)
{
n=n+i;
}
printf("n=%d\n",n);
}
6.3.3 函数的嵌套调用
C语言中函数定义都是互相平行、独立的,也就是说在定义函数时,一个函数内不能包含另外一个函数。
C语言不能嵌套定义函数,但可以嵌套调用函数,也就是说,在调用一个函数的过程中,又调用另外一个函数。
例:计算机平方阶层
#include "stdio.h"
long f1(int p)
{
int k;
long r;
long f2(int q); //声明
k=p*p;
r=f2(k);
return r;
}
long f2(int q)
{
long c=1;
int i;
for(i=1;i<=q;i++)
{
c=c*i;
}
return c;
}
main()
{
int i; //i是数字
long s=0;
for(i=2;i<=3;i++)
{
s=s+f1(i);
}
printf("s=%ld\n",s);
}
6.3.4 函数的递归调用
一个函数在它的函数体内调用它自身的过程称为递归调用。
直接递归调用或间接递归调用
例:有5个人做在一起,问第5个人多少岁?
#include "stdio.h"
main()
{
int age(int n);
printf("%d\n",age(5));
return 0;
}
int age(int n)
{
int c; //用c作为存放函数的返回值的变量
if(n==1)
{
c=10;
}
else
{
c=age(n-1)+2;
}
printf("\t%d\n",c);
return(c);
}
例:求n!
#include "stdio.h"
main()
{
int n;
long ff(int n);
long y;
printf("请输入n的值:\n");
scanf("%d",&n);
if(n<0)
{
printf("n<0!input error!");
}
else
{
y=ff(n);
printf("%d!=%ld",n,y);
}
return 0;
}
long ff(int n)
{
long f;
if(n==0||n==1)
{
f=1;
}
else
{
f=n*ff(n-1);
}
return (f);
}
一个递归问题可分为:回溯 和 递推 两个阶段;且必须要有一个递归过程的边界条件。
两个阶段:
1)递推阶段:将原问题不断地分解成为新的子问题,逐渐从未知的向已知的方向推测,最终达到自己已知的条件,即递归结束条件,这时递推阶段结束。
2)回归阶段:从已知条件出发,按照“递推”的逆过程,逐一求值回归,最终到达“递推”的开始处,结束回归阶段,完成递归调用
要有递归的终止条件! 递归=递归方式+递归条件
6.4 局部变量和全局变量
函数中:形参变量只在被调用期间才分配内存单元,调用结束后立即释放。表明形参变量只有在函数内才有效,离开该函数就不能再使用了。
这种变量有效性地范围称为变量地作用域。变量说明方式不同,作用域也不同。
按作用域范围可分为:局部变量 和 全局变量。
6.4.1 局部变量
局部变量也称为内部变量。局部变量是在函数内作定义和说明。作用域仅限于函数内,离开该函数后再使用这种变量就是非法的。
注意:
- 主函数中定义的变量只能在主函数中使用,不能在其他函数中使用。同时,主函数也不能使用其他函数中定义的变量。主函数也是一个函数,与其他函数是平行关系。
- 形参变量是属于被调函数的局部变量,实参变量是属于主调函数的局部变量
- 允许在不同的函数中使用相同的变量名,它们代表不同的对象,分配不同的单元,互不干扰,也不会发生混淆
- 在复合语句中也可以定义变量,其作用域只在复合语句范围内。
6.4.2 全局变量
全局变量也称为外部变量,在函数外部定义的变量。
不属于某一个函数,而属于一个源程序文件,其作用域是从定义变量的位置开始到本源文件结束
如果全局变量在文件的开头定义,则在整个文件范围内均可以使用该全局变量;如果不在文件的开头定义,又想在定义点之前使用该全局变量,需要用exterm进行声明。
变量的声明:说明变量的性质,并不分配存储空间
变量的定义:即为变量分配存储空间
例:输入正方体的长、宽、高,分别为l,w,h,求体积及三个面的面积。
#include "stdio.h"
int s1,s2,s3; //作用域为整个程序
main()
{
int v,l,w,h;
int vs(int a,int b,int c);
printf("请输入长,宽,高的值:\n");
scanf("%d %d %d",&l,&w,&h);
v=vs(l,w,h);
printf("v=%d,s1=%d,s2=%d,s3=%d\n",v,s1,s2,s3);
return 0;
}
int vs(int a,int b,int c)
{
int v;
v=a*b*c;
s1=a*b;
s2=b*c;
s3=a*c;
return v;
}
说明:
- 对于局部变量的定义和说明,可以不加区分。全局变量定义必须在所有的函数之外,且只能定义一次。
全局变量的定义:
类型说明符 变量名,变量名……
全局变量的声明:
extern 说明符 变量名,变量名……
全局变量在定义时就分配了内存单元,全局变量在定义时也可作初始赋值,但不能在声明时赋初始值,全部变量的声明只是表明在函数内要使用该全局变量。
例:编写程序,输入两个数,调用函数找出最大值
#include "stdio.h"
int a,b; //全局变量的定义
main()
{
extern int a,b; //全局变量的声明
int mymax(int x,int y);
printf("请输入a,b的值:\n");
scanf("%d %d",&a,&b);
printf("max=%d\n",mymax(a,b));
return 0;
}
int mymax(int x,int y)
{
int z;
if(x>y)
{
z=x;
}
else
{
z=y;
}
return z;
}
- 全局变量只在所有函数之外定义一次;全局变量可以声明多次,哪个函数内要用到在其后面定义的变量,就需要在该函数内对该全局变量进行声明。
在同一个源文件中,允许全局变量与局部变量同名。在局部变量的作用范围内,全局变量被屏蔽,即它不起作用。
例:全局变量与局部变量同名。
在局部变量的作用范围内,全局变量被“屏蔽”,即全局变量不起作用
- 如果没有全局变量,函数只能通过参数与外界发生数据关系,有了全局变量以后,增加了一条与外界传递数据的渠道。
联系太多,降低模块的独立性
6.5 变量的存储属性
1. 用户程序的存储分配
程序区 |
静态存储区 |
动态存储区 |
程序区:存放程序;
静态存储区:在程序开始执行时就分配的固定存储单元,如全局变量
动态存储区:在函数调用过程中进行动态分配的存储单元,如函数形参、自动变量
2. 变量的存储类型
变量和函数有两个属性:操作属性和存储属性
操作属性:数据类型
存储属性:变量的存储类型、变量的生存期和变量的作用域
变量划分:空间角度,全局变量和局部变量;生存期角度,永久存储和动态存储;
永久存储:从程序的开始到结束;动态存储:在程序的过程中
按存储属性四个分类:register(寄存器)、auto(主存)、static(主存)、extern(主存)
在定义一个变量时,除了指定其数据类型外,还可以执行其存储属性。
6.5.1 自动变量(auto)
自动变量为局部变量。说明符:auto
当程序的一个局部要使用某些自动变量时,说明形式:
[auto] 数据类型 变量名[= 初值表达式],…;
[]表示可以省略。
说明:
- 自动变量是局部变量。自动变量的作用域仅限于定义该变量的个体内。在函数中定义的自动变量在函数内有效,复合语句中的在复合语句中有效
- 自动变量属于动态存储,使用时,定义该变量的函数被调用才分配存储单元,函数调用结束,释放存储单元。函数调用结束后,自动变量的值不能保留
- 自动变量的作用域和生存期都局限于定义它的个体内(函数和复合语句内);同名变量不会混淆
- 使用未赋初值的自动变量
#include "stdio.h"
main()
{
int x=1;
{
int prt(); //声明
int x=3;
prt();
printf("2nd x=%d\n",x);
}
printf("1st x=%d\n",x);
return 0;
}
int prt()
{
int x=5;
printf("3th x=%d\n",x);
return 0;
}
6.5.2 寄存变量(register)
寄存器变量具有与自动变量完全相同的性质。为了提高效率,C语言允许将局部变量的值放在CPU寄存器中,这种变量叫“寄存器变量”,用关键字register表示。
例:求阶层
说明:
只有局部自动变量和形式参数可以作为寄存器变量
一个计算机系统中的寄存器数目有限,不能定义任意多个寄存器变量
#include "stdio.h"
main()
{
int fac(int n);
int i;
for(i=0;i<=5;i++)
{
printf("%d!=%d\n",i,fac(i));
}
return 0;
}
int fac(int n) //将实参i给形参n
{
register int i,f=1; //形参-由于频繁使用变量i,故将它放在寄存器中
for(i=1;i<=n;i++)
{
f=f*i;
}
return (f);
}
6.5.3 静态变量(static)
函数中的局部变量的值在函数调用结束后不消失而保留原值,这时就应该指定局部变量为“静态局部变量”,关键字static声明
格式:
static 数据类型 变量名[=初始化常量表达式].…;
说明:
- 静态变量的存储空间在程序的整个运行期间是固定的。一个变量被指定为静态,在编译时就为其分配存储空间,程序一开始执行便被建立,直到该程序执行结束都是存在的
- 静态变量的初始化是在编译时进行,在定义时只能使用常量或常量表达式进行显示初始化。未显示初始化时,编译时将它们初始化为0(int)或0.0(float)
自动变量没有初始化的问题,只有静态变量和外部变量有初始化问题。
对自动变量称为“赋初值”
对静态变量和外部变量称为“初始化”
- 函数多次被调用的过程中,静态局部变量的值具有可继承性
#include "stdio.h"
main()
{
int f(int a);
int a=2,i;
for(i=0;i<3;i++)
{
printf("%d\n",f(a));
}
return 0;
}
int f(int a)
{
auto int b=0;
static int c=3; //值可以继承;静态局部变量
b=b+1;
c=c+1;
return(a+b+c);
}
6.5.4 外部变量
外部变量即为全局变量是在函数的外部定义的,其作用域为从变量定义处开始,到程序文件末尾。
如果外部变量不在文件的开头定义,其有效的作用范围只限于定义处到文件的末尾。
如果在定义点之前的函数想引用外部变量,则应该在引用之前用关键字extern对该变量作“外部变量声明”,表示该变量是应该已经定义的外部变量。
例:外部变量代码
- 可将外部变量的作用域扩充到其他文件,这时在需要用到这些外部变量的文件中,对变量用extern作声明即可。
- 限定本文件的外部变量只在本文件中使用。如果有的外部变量只允许本文件使用而不允许其他文件使用,则可以在此外部变量前加一个static,使其有限局部化,称为静态外部变量
#include "stdio.h"
main()
{
int mymax(int x,int y);
extern a,b; //外部变量声明从而合法的引用
printf("%d\n",mymax(a,b));
return 0;
}
int mymax(int x,int y)
{
int z;
z=x>y?x:y;
return (z);
}
int a=13,b=1;
//在本程序中最后一行定义了外部变量a,b;外部变量定义的位置在main函数之后
6.6 编译预处理
编译预处理是在编译前对源程序进行的一些预处理。预处理由编译系统中的预处理程序按源程序中的预处理命令进行。
C语言预处理命令均已“#”开头,末尾不加分号,以来区别C语句
可出现在程序中的任何位置,作用域是自出现点到所在源程序的末尾。如:#define和#include
6.6.1 宏定义
C语言允许用一个标识符来表示一个字符串,称为“宏”。
被宏定义的标识符称为“宏名”。
在编译预处理时,对程序中出现的“宏名”。都用宏定义中的字符串去代换,这称为“宏代换”、“宏展开”
宏定义是由源程序中的宏定义命令完成的,宏代换是预处理程序自动完成的。
“宏”分为有参数和无参数两类。
1.无参数宏定义
无参宏的宏名后不带参数,一般形式为:#define 宏名 字符串
宏名的标识符习惯上用有意义且容易理解的大写字母来表示。
“字符串”可以是常数、表达式、格式串等
一般写在文件开头函数体的外面,有效范围是从定义宏命令之后到遇到终止宏命令#undef为止,否则其作用域将一直到源文件结束。
例:#define PI 3.14
定义了宏名PI代表3.14,在预编译处理时,系统将把该命令之后作用之内的所有PI都自动用3.14代换,即进行宏展开。
减少字符串的重复书写、修改重复使用的字符串的工作变得简单
注意:
- 宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一个简单的替换,字符串中可以含任何字符,常数、表达式,预处理程序对它不作任何检查,如有错误,只能在编译已被宏展开后的源程序时所发现。
- 如果在一行中写不下整个宏定义,需要用两行或更多行来书写时,只需要在每行的最后一个字符的后面加上反斜杠“\”,并在下一行的最开始接着写即可
- 宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用#undef命令
- 宏名在源程序中若用引号括起来,则预处理程序不对其作宏代换
例:printf("PI=",PI);
在预处理时,将只对第二个PI进行代换,对第一个双引号中的PI,系统不对其作代换
- 宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名。在宏展开时,由预处理程序层层代换
例子:
#define R 5.6
#define PI 3.14
#define L 2*PI*R
#define S PI*R*R //PI、R是已经定义的宏名
2.带参数宏定义
C语言允许宏带有参数。在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数。
参数宏:调用中,不仅要进行宏展开,而且要用实参去代换形参
形式:
#define 宏名(形参表列) 字符串
在字符串中含有各个形参
带参宏调用的一般形式为:
宏名(实参表列);
例如:
#define M(y) y*y+3*y //宏定义
k=M(5) //宏调用
例:求两个数中较大者
#include "stdio.h"
#define MAX(a,b)(a>b)?a:b
main()
{
int x,y,mymax;
printf("请输入两个数:");
scanf("%d %d",&x,&y);
mymax=MAX(x,y); //宏调用
printf("max=%d\n",mymax);
return 0;
}
- 带参宏定义中,宏名和形参表之间不能有空格出现
- 在带参数宏定义中,形式参数不分配内存单元,因此不必作类型定义。而宏调用中的实参有具体的值,要用它们去代换形参,因此必须作类型说明,与函数的情况不同。函数调用中,形参与实参是两个不同的量,作用域不同,存在值传递;而带宏参数中只是符号代换,不存在值传递的问题。
- 宏定义中形参是标识符,宏调用中的实参可以是表达式
- 宏定义中,字符串内的形参通常要用括号括起来以避免出错。除了将宏定义字符串中的参数都用括号括起来,还需要将整个字符串部分也用括号括起来。
- 同一表达式用函数处理的结果与宏定义处理的结果不一定相同
例如:求函数n的平方
#include "stdio.h"
#define SQ(y)((y)*(y))
main()
{
int i=1;
while(i<5)
{
printf("%d\n",SQ(i++));
}
}
6.6.2 文件包含
文件包含是C语言预处理程序的另一个重要功能。
形式:
#include "文件名"
例如:
#include "stdio.h"
文件包含命令的功能就是把指定的文件插入该命令位置取代该命令,从而把指定的文件和当前的源程序文件连成一个源文件。
说明:
- 文件包含命令中的文件名可以用双引号括起来,也可以用尖括号括起来
尖括号:表示在包含文件目录中查找(包含目录是由用户在设置环境时设置的),而不再源文件目录查找;
双引号:首先在当前的源文件目录中查找,若未找到才到包含目录中查找
- 一个include命令只能指定一个被包含文件,若有多个文件要包含,则需用多个include命令
- 文件包含允许嵌套,即在一个被包含的文件中又可以包含另一个文件
预处理程序提供了条件编译的功能。可以按不同的条件按去编译不同的程序部分,因而产生不同的目标代码文件。
1)形式1:
#ifdef 标识符
程序段1
#else
程序段2
#endif
标识符是指已用宏命令#define定义的宏名,而程序段可以是编译预处理命令行,也可以是C语言组。如果标识符已被#define命令定义过则对程序段1进行编译;否则对程序段2进行编译。
#include "stdio.h"
#define REAL float
main()
{
#ifdef REAL
REAL a;
printf("输入一个实数: ");
scanf("%f",&a);
printf("这个实数是:%f\n",a);
#else
float a;
printf("输入一个单精度浮点数: ");
scanf("%f",&a);
printf("这个单精度浮点数是:%f\n",a);
#endif
}
2)形式2:
#ifndef 标识符
程序段1
#else
程序段2
#endif
功能:如果标识符未被#define命令定义过则对程序段1进行编译,否则对程序段2进行编译。
3)形式3:
#if 表达式
程序段1
#else
程序段2
#endif
功能:如果表达式的值为真(非0),则对程序段1进行编译,否则对程序段2进行编译。
6.7 应用举例
例子:编写一个程序完成“菜单”功能:提供三种选择路径。
代码太长,望读者谅解;关于C代码实例可进我主页下载。嘻嘻嘻嘻嘻~~~~
谢谢各位~~~~
print—是函数,可以返回一个值,只能有一个参数;
println—与print唯一的区别是println换行输出;
printf—函数,把文字格式化后输出,直接调用系统调用进行IO,非缓冲