目录
1、函数的定义、调用、声明
1.1函数的定义
当 C编译系统提供的标准数据库不满足用户需要时,用户可以自己定义函数
C语言中函数的基本格式为:
函数类型 函数名 (类型1 形式参数1,类型2 形式参数2,……类型n 形式参数n)
{
说明部分
语句部分
}
说明:
(1)一般将函数类型 函数名 (类型1 形式参数1,类型2 形式参数2,……类型n 形式参数n)称为函数的首部,或函数头;大括号内称为函数体。
(2)函数类型,即:函数值的类型
(3)函数名用合法标识符命名,最好能够有见名知意的效果,函数名后紧跟一对小括号,中间不要加空格
(4)形式参数简称形参,指函数中的自变量,它们是从函数外部向函数内部运送值的数据通道。形式参数根据需要可以有1个、多个或没有。定义时每个形参(自变量)都应指出各自的类型
(5)函数体主要包括变量说明和可执行语句,两者可以根据情况缺省。若都为空,也合法,称为空函数
(6)变量说明部分主要是定义运算所需要的变量。因为这些变量在函数的内部定义,又称为局部变量。只在函数内部有效,不能在函数外使用
(7)语句部分主要是利用已定义的变量进行运算,得到结果
(8)有些函数末尾会写return语句,相当于是从函数内部向函数外部运送值的数据通道。返回值类型需要跟函数类型匹配,即:赋值兼容。若程序不需要计算结果,那就将此函数类型写为void。
注意:
一个函数中可以有多个return语句
return语句后面可以是变量、常量、表达式,也可以用小括号将它括起来
若无return语句,遇到函数结尾的“}”时,程序自动返回调用函数
函数类型最好与return语句返回的值类型保持一致,若不一致,函数结果以函数类型为准,由C编译系统自动完成类型转换。
例如
int f(int a,int b)
{
int s;
s=a+b;
return s;
}
1.2 函数的调用
函数定义完毕后,需要使用一下,验证函数定义是否正确。
使用C程序的函数,称为函数调用,函数调用的格式:
函数名(实际参数1,实际参数2,……,实际参数n);
实际参数简称实参,可以是常量、变量、表达式。实参的个数、类型应该跟形参一一对应匹配。实参主要是用于给形参一一对应传送数据。
1.3函数调用的形式
使用函数调用的形式有:
(1)函数调用作为一条语句;
(2)函数有明确的返回值时,函数调用可以当作普通变量使用,作为表达式的一部分。
(3)函数有明确的返回值时,函数调用可以作为其他函数的实参。
1.3.1代码演示
题目1:编写函数是吸纳打印输出以下图形的功能
/*
*
***
*****
*******
*/
#include <stdio.h>
void printStar(){
int i,j,k;
for(i=0;i<=3;i++){//行数
for(j=0;j<=2-i;j++)//空格
{
printf(" ");
}
for(k=0;k<=2*i;k++){
printf("*");
}
printf("\n");
}
}
int main(){
printStar();
return 0;
}
题目2:定义pow函数,实现求的功能。只考虑x和y均为正整数的情况
#include<stdio.h>
int pow(int x,int y){
int i,p=1;
for(i=1;i<=y;i++){
p*=x;
}
return p;
}
int main(){
printf("%d的%d次方是%d\n",5,3,pow(5,3));
return 0;
}
题目3:定义bigger函数,实现求2个整数中较大值的功能。利用此函数求3个整数中的最大值
//定义bigger函数,实现求2个整数中较大值的功能。利用此函数求3个整数中的最大值
#include<stdio.h>
int bigger(int a,int b){
return a>b?a:b;//求a,b中较大值作为结果返回
}
int main(){
int x,y,z,max;
printf("请输入三个需要比较的数:");
scanf("%d%d%d",&x,&y,&z);
max=(bigger(bigger(x,y),z));//三个数需要调用两次
printf("最大值为:%d\n",max);
return 0;
}
1.4函数的声明
为了防止C编译系统会提示找不到函数,可以在函数调用前面加上函数声明。
1.4.1函数声明的格式
函数声明由函数首部加上分号组成。例如:函数f的函数声明为:int f(int a,int b);也可以将参数名省略,例如:int f(int,int);。
函数声明使用
#include<stdio.h>
int f(int a,int b);//函数声明
int main(){
int c;
c=f(3,5);
printf("c=%d\n",c);
return 0;
}
int f(int a,int b){
int s;
s=a+b;
return s;
}
2、函数参数的传值
2.1 单向传递
当基本类型的常量或变量作函数的实参时,形参与实参可以同名,它们占用不同的内存空间。从实参到形参的数据传递是单向的,数据只是从实参传给形参,而不能由形参传递给实参。即:形参值的改变不会影响到实参,因此这种传递称为:“单向值传递”
代码演示:
2.2双向地址传递
当地址常量或地址变量(例如数组名)作为函数的实参时,从实参到形参传递的是地址,形参与实参使用相同的内存空间。形参值的改变会影响到实参,这种传递也称为“双向地址传递”
代码演示:有一个已按从小到大的顺序排列的整型数组,从键盘输入一个整数,按照数组原来的排列规律将此数插入到数组中。写一个函数insertNumber实现此功能,并测试。
#include<stdio.h>
#define N 10
void insertNumber(int a[N],int num);
void insertNumber(int a[N],int num){
int i,j;
if(num>a[N-2]){
a[N-1]=num;
}
else{
for(i=0;i<=N-2;i++){
if(num<a[i]){
for(j=N-2;j>=i;j--){
a[j+1]=a[j];
}
a[i]=num;
break;
}
}
}
}
int main(){
int a[N]={-9,5,12,33,45,47,66,76,89};
int i,num;
printf("插入num前的数组\n:");
for(i=0;i<=N-2;i++){
printf("%2d\n",a[i]);
}
printf("请输入要插入的数字:");
scanf("%d",&num);
insertNumber(a,num);
printf("插入num后的数组:\n");
for(i=0;i<=N-1;i++){
printf("%d\n",a[i]);
}
return 0;
}
3、嵌套调用和递归调用
3.1嵌套调用
一个函数调用另一个函数,另一个函数又调用其他函数,称为函数的嵌套调用。C规定函数定义不可嵌套,但可以嵌套调用函数。
编写程序计算
#include<stdio.h>
int fac(int k){
int i,mul=1;
for(i=1;i<=k;i++){
mul*=i;
}
return mul;
}
int sum(int n){
int i,total=0;
for(i=1;i<=n;i++){
total+=fac(i);
}
return total;
}
int main(){
int n;
printf("请输入正整数n:");
scanf("%d",&n);
printf("s=%d\n",sum(n));
return 0;
}
3.2递归调用
一个函数直接调用自己本身或者通过调用其他函数来间接调用自己,也称递归函数
递归问题:
有5个人坐在一起,问第5个人多少岁?他说比第4个人大5岁。问第4个人岁数,他说比第3个人大5岁。问第3个人,又说比第2个人大5岁。问第2个人,说比第1个人大5岁。最后问第1个人,他说他10岁。问第5个人多大?
#include<stdio.h>
int age(int n){
if(n==1){
return 10;
}
else{
return age(n-1)+5;
}
}
int main(){
printf("第5个人的年龄是%d岁。\n",age(5));
return 0;
}
用递归调用求n的阶乘
求阶乘的数学公式:
#include<stdio.h>
int fac(int n){
int f;
if(n<0){
printf("n<0,数据出错!");
}
else if(n==0||n==1){
f=1;
}
else{
f=fac(n-1)*n;
return f;
}
}
int main(){
int n,y;
printf("请输入一个正整数:");
scanf("%d",&n);
y=fac(n);
printf("%d的阶乘等于%d\n",n,y);
return 0;
}
4、变量的使用范围和存储类别
编译连接程序时,有可能会遇到这样的问题,明明需要的变量、函数都已经定义了,可编译器还是会跳出一些诸如某变量未定义、某函数找不到的错误信息。这不是因为编译器有问题,而是可能会涉及到变量、函数的作用域、生命期等要素。
4.1 局部变量与全局变量
根据其使用范围不同,可以划分为局部变量和全局变量。
局部变量(也称为内部变量):在函数内部定义,有效范围是从定义处开始,到本函数结束为止。
全局变量(也称为外部变量):在函数外部定义,不作特殊声明的情况下,有效使用范围是从定义处开始,到所在源文件末尾为止。当局部变量缺少的时候才使用全局变量
说明:
(1)不同函数中的同名变量,是两个不同的变量,占用不同内存单元。
(2)函数的形式参数属于局部变量
(3)若全局变量与局部变量同名,则在局部变量的有效范围内,全局变量被屏蔽
(4)若未给局部变量赋值,其值为随机值;若未给全局变量赋值,其值为0。
局部变量的有效区域:
因为a和b定义在main函数中,只在main函数中生效,如果超过此范围程序就会认为其没有定义。
全局变量的有效范围:
a和b定义在外部,所以是全局变量,又因为main函数和fun函数内部没有定义a和b,所以就会使用全局变量。
不同函数中的同名局部变量:
不同函数中断的局部变量同名,它们互不相关。
局部变量和全局变量同名:
fun函数没有局部变量,所以使用全局变量,而main函数有局部变量,所以优先选择局部变量。
4.2变量的存储类别
用户内存区
静态数据存储区 |
动态数据存储区 |
程序存储区 |
一个变量完整的定义除了变量的数据类型之外,还应加上存储类型的限制,格式如下:
[存储类型] 数据类型 变量名;
C语言中,变量的存储类型可以分为四类:auto(自动型)、static(静态型)、register(寄存器型)、extern(外部型)。
变量 | 有效范围 | 生命周期 | 默认值 |
局部变量(auto) | 变量定义开始到函数结束 | 函数调用开始到调用结束 | 随机值 |
static局部变量 | 变量定义开始到函数结束 | 整个程序执行开始到整个程序结束 | 该数据类型里面表示假的值 |
static全局变量 | 变量定义开始到 定义所在文件结束 | 整个程序执行开始到整个程序结束 | 该数据类型里面表示假的值 |
extern全局变量 (不能扩展static全局变量) | 整个程序 | 整个程序执行开始到整个程序结束 | 该数据类型里面表示假的值 |
变量的生命周期指的是在程序运行过程中,变量从创建到撤销的一段时间,下面将分情况说明不同的存储类型定义变量的生命周期、有效范围的影响。
4.2.1auto类型的局部变量
形参和局部变量,不加任何修饰时,默认为auto类型的局部变量。这类变量存放在内存中的动态存储区,生命周期与所在函数的代码块共存亡。
局部变量的生命周期:
4.2.2static类型的局部变量、全局变量
static修饰的全局变量和局部变量称为静态全局变量和静态局部变量。这类变量存放在内存中的静态存储区,生命周期与程序共存亡。在程序开始执行前给它们分配内存,在程序执行完毕时释放内存。在程序执行过程中它们占据固定的存储空间。生命周期是到整个程序结束,有效范围是到所在的源文件结束。生命周期比有效范围长。
静态局部变量的生命周期:
4.2.3全局变量
普通的全局变量存放在内存中的静态存储区,生命周期到整个程序结束。它的有效范围可以通过将其声明为extern类型扩展到整个程序结束。
虽然全局变量A的生命期是整个程序,在整个程序中都占有内存,但是它的有效区域是test1.c文件,在test2.c文件中不能使用test1.c
4.2.4 extern类型的全局变量
extern类型的全局变量存放在内存中的静态存储区,生命周期到整个程序结束,它的有效范围是到整个程序结束,两者一致。
extern int A还可以写成 extern A,这不是重新定义变量A,而是将定义的全局变量A的有效区域扩展到test2.c文件中。
用extern扩展static类型全局变量的有效区域
静态全局变量A的有效区域是其定义所在文件test1.c,不能扩展它的有效范围到程序中的其他文件。
4.2.5register类型的全局变量
有些使用频繁的变量,为了节省存取变量时花费的时间,提高程序效率,可以将其设置为register类型的变量,直接从寄存器中存取。例如:在一个函数中执行100000此循环,每次循环都要用到某个局部变量i,就可以将其说明为register类型。
5、多文件程序
5.1C源文件的划分
C源文件可以分为两类,一类是包含实际代码的基本程序文件,另一类是为基本程序文件提供信息的辅助文件。基本程序文件通常以.c为扩展名,称为头文件、head文件、或h文件。
若决定采用多文件方式实现程序,应该将哪些内容放入头文件,哪些放入程序文件?多文件程序的内容安排需要遵循以下原则:
(1)头文件中只写可执行语句以外、不分配内存空间的描述,包含:文件包含命令、公共类型的定义、结构/联合/枚举说明:函数声明;变量extern说明;公用的宏定义等。
(2)程序文件中定义所有的全局变量、函数和只在一个文件中使用的类型等。
(3)只用#include包含头文件;不用它来包含程序文件。
(4)通过头文件解决文件间信息传递的问题。将多个文件有关的函数声明、全局变量的声明写入头文件。在定义和使用它们的程序文件中都包含此头文件,就可以正确使用它们。
5.2C源文件的格式
一般情况下,源程序中所有的行都参加编译。但是有时希望按照不同的条件去编译不同的程序部分,这就是“条件编译”。
条件编译的一种常用格式:
#indef 标识符
程序段1
#else
程序段2
#endif
功能:如果标识符未被#define命令定义过,则对程序段1进行编译,否则对程序段2进行编译,本格式中的#else可以省略
在编写大型程序时,通常将所用到的函数原型、外部变量、全局类型的声明,头文件的包含命令统一编写在一个头文件中,将函数实现统一编写在一个源程序文件(文件名与头文件同名)中实现。主函数main放在主函数文件main.c中。
(1)student.h的一般形式:
/*student.h 头文件 */
#ifdef_STUDENT_H__
/*如果未用#define定义标识符__STUDENT_H__,则编译如下程序段 */
#define _STUMENT_H__
头文件包含命令
全局类型声明
全局变量声明
函数声明
#endif
(2)student.c的一般形式:
/*student.c源程序文件,写函数的定义*/
#include "student.h"
……
(3)main.c 一般形式
/*main.c源程序文件,写函数的调用
#include "student.h"
int main()
{
……
}
代码演示:
题目1:输入4个学生的5门课的成绩,分别用函数实现下列功能:
(1)计算每个学生的平均成绩
(2)计算每门课程的平均成绩
(3)查找最高成绩,输出最高成绩对应的学生序号和课程序号。
student.h文件
#ifndef _STUDENT_H__
#define _STUDENT_H__
#define N 4
#define M 5
extern float score[N][M];
extern float max;
extern int s_num,c_num;
float aver_stu(int n);
float aver_course(int m);
void high_score();
#endif
student.c文件
#include<stdio.h>
#include "student.h"
float aver_stu(int n){
int i;
float sum=0.0f;
for(i=0;i<=M-1;i++)
{
sum+=score[n][i];
}
return sum/M;
}
float aver_course(int m){
int i;
float sum=0.0f;
for(i=0;i<=N-1;i++)
{
sum+=score[i][m];
}
return sum/N;
}
void high_score(){
int i,j;
max=score[0][0];
for(i=0;i<=N-1;i++){
for(j=0;j<=M-1;j++)
{
if(max<score[i][j])
{
max=score[i][j];
s_num=i+1;
c_num=j+1;
}
}
}
}
main.c文件
#include<stdio.h>
#include "student.h"
float score[N][M];
float max;
int s_num,c_num;
int main()
{
int i,j;
printf("请分别输入%d名学生的%d门成绩:\n",N,M);
for(i=0;i<=N-1;i++)
{
for(j=0;j<=M-1;j++)
{
scanf("%f",&score[i][j]);
}
}
for(i=0;i<=N-1;i++)
{
printf("输出第%d个学生的平均成绩:",i+1);
printf("%.1f\n",aver_stu(i));
}
for(j=0;j<=M-1;j++)
{
printf("输出第%d门课程的平均成绩:",j+1);
printf("%.1f\n",aver_course(j));
}
high_score();
printf("最高分是第%d个学生的第%d门成绩:%.1f\n",s_num,c_num,max);
return 0;
}
输出结果:
6、用带参数的宏代替公式型函数
我们在之前学习过使用宏定义来定义符号常量,例如 #define PI 3.14159,这属于无参数的宏定义。C语言允许宏定义中包含参数。带参数的宏定义的一般形式为:
#define 宏名(形参列表) 宏体
说明:
①宏名一般用大写字母表示
②形参列表中的形参多于1个时,各个参数之间用逗号隔开。形参在这里是标识符,不分配内存单元,因此不必作类型说明。
③宏体是被替换用的字符串,宏体中的字符串是由参数表中的各个参数组成的表达式。
④宏定义属于预编译命令的一种,不是C语言中的语句,因此结尾不需要加分号。
带参数宏调用的一般形式为:
宏名(实参序列);
利用带参数的宏求两个整数的较大值
用带参数的宏求圆的面积