C语言
- 知识基础
- 控制语句
- 函数
- 输入输出
- 指针和数组
- 用户自定义数据类型
- 文件操作
程序的结构化可以通过三种结构:顺序结构、选择结构、循环结构实现,理论上我们已经可以实现所有想要的功能,但在实际应用中,开发更加复杂的程序我们只在main
中实现所有功能步骤,将设置大量变量并且出现大量重复代码,导致程序变得臃肿,结构不清晰。以上问题我们可以通过函数
实现程序的模块化设计
,想要某种功能只需要调用相应的函数即可以实现;C程序就是若干个函数以主函数为入口,通过调用各种函数实现目标功能的代码。
一、函数的定义
函数:完成特定操作的一段程序代码;
实际上,main
就是一个特殊的函数,称为主函数
,主函数是整个程序的入口,它的定义由C语言的语法规定:
- 主函数格式
int main(){ 函数体 return 0;}
- 一个程序有且只能有一个主函数;(也被称为入口函数)
- C程序由主函数开始执行,在主函数中调用其他函数,最终返回主函数结束;
自定义函数,有以下特点:
- 格式
函数返回值类型 函数名(形参类型 形参名){函数体 return 返回值;}
- 所有的函数都是平行的,不存在归属关系,函数间可以相互调用,但不能调用主函数。
- 返回值类型:在
return
语句中返回的值,可以是任意数据类型; - 函数名:要做到见名知义;
形参
:作为本函数的输入,在函数定义中并没有实际的值,只有在被调用时会被传递参数值,称为实参
,形参可以为空;- 函数体:实现特定的操作,只有被调用之后才有可能执行。
二、函数的声明
讲解函数调用之前,理解预处理指令和库函数的意义
1.预处理指令
我们可以发现,在之前编写的程序中除了包含有函数的定义,还有一些指令(注意:不是语句)#include ...
、#define ...
,它们不是函数,为什么是合法的呢?实际上它们称为预处理指令
;
顾名思义,在预处理阶段这些指令就会被处理,#include ...
实际上是链接库函数
和其它源文件的指令,可以想象这一条语句会变成若干个函数,这些函数定义在其他文件中,早已被系统定义好,我们可以直接调用;这些特定的可链接文件被称为头文件(.h)
。
#define
是一些宏定义
,在编译阶段会对符合宏定义的部分按照规则进行替换,本质上它并不存在,只是制定的一些规则。
综上所述,预处理指令是完全合法的指令,程序本质上还是由一个主函数和若干平行的函数构成的!
2.声明的方法(函数声明语句)
声明中还有一种变量声明,决定了变量的作用范围、类型等,我们《计算机组成原理》课程中讲解,能更好的理解
C程序中所有函数都是平行的关系,那么当函数需要相互调用时,怎么知道我要调用哪一个函数,那个函数又定义在哪里呢?
函数声明:指明了函数的名字,参数等全部信息,可以标识唯一一个函数;
一般形式:函数返回值 函数名(形参类型 形参名);
,在声明中形参可以只给出形参类型,不给出形参名。
如何使用函数声明,让平行的函数能够相互“感知”的到呢?
- 同一源文件中,将声明加入“函数体外”,则该函数声明之后,所有的函数都可以直接调用该函数。
- 同一文件中,函数a的定义在函数b之前,无需声明,b可以直接调用a;
- 将函数声明直接加入函数体,本函数获得调用该函数的权限,对其他函数没有影响。
#include<stdio.h>
#include"my.h"//自定义的头文件
extern char function_my();//其他文件中定义的函数my
int function_d();//d函数声明,之后所有函数可以调用d
//可调用:d、c
int function_a(){//a无需声明,可以被之后所有函数调用
int function_c();//函数c的声明
printf("调用了a!\n");
function_c();
function_d();
return 0;
}
//可以调用d、a
void function_b(){//可以被之后的函数调用,不能被a调用
printf("调用了b!\n");
function_a();
function_d();
return;
}
//可以调用d、a、b
int function_c(){
printf("调用了c!");
return 1;
}
//可以调用a、b、c
int function_d(){
printf("调用了d!");
return 3;
}
声明其他文件中函数的方法:
- 预处理指令,链接头文件;
- 使用
extern 函数声明
,在文件最开始声明。
三、函数的调用
1.函数调用语句
C程序由主函数开始执行,想要进入特定函数执行必须通过调用
这种方式,调用的具体实现就是函数调用语句
;
一般形式:函数名(实参名);
由于可以被调用的函数一定已经被声明过了,所以会根据函数名,程序可以确定该调用那一部分函数来执行,需要注意的是实参名要满足:
- 在“调用者”中被定义存在的实际量;
- 实参的数据类型与声明中形参类型必须一一对应;
- 实参传递给函数的是实参的
值
,被调用函数会另外开辟空间复制一份实参的值
,对该值进行操作。
2.函数定义、函数声明语句和调用语句的区别
函数的定义是具体的实现功能代码,类似于一台“机器”,给他需要的材料可以生产出你想要的零件,“机器”已经规定了可以处理的材料的性质(虽然还没有拿到材料,也就是形参);
函数的声明表达的是函数的全部特点,能够区分所有的函数,类似于机器的“说明书”,只有拿到说明书的部分能够使用机器!
函数的调用语句相当于使用机器的过程,首先要根据说明书的要求准备好具体的材料(实参),然后将材料按照说明书的要求依次放入对应的机器当中,我们并不关心机器内部是如何处理的,只需要等待机器加工完成(得到返回值或者想要的输出)。
3.函数的嵌套调用和递归调用
-
嵌套调用
C程序中函数之间相互平行,所以程序之间不能够嵌套定义,但是在调用时,可以嵌套调用(也就是在一个函数正常执行的过程中调用执行另一个函数,执行完调用函数之后又回到调用者继续执行的过程);
这样的嵌套可以是多层的,对多个不同函数的嵌套符合栈的数据结构特点:先进先出(最后调用的函数最先返回);
最后,对于函数调用语句,存在函数调用表达式,它的形式为:函数名(实参名)
,也就是函数调用语句不加分号,我们知道表达式代表的同样是一个具体的数据:
(1) 这个数据的数据类型为被调用函数的返回值类型;
(2) 这个数据的值为被调用函数根据实参得到的返回值 -
递归调用
递归调用是指在调用一个函数的过程中又直接或间接的调用了该函数本身;
C语言的特点之一就是允许递归调用,但是可以想象递归调用是十分危险的操作,很容易陷入无终止的循环之中,所以递归调用必须使用if
控制语句来控制,只有满足一定的条件,才能进行递归调用!
至所以允许递归的操作,是由于在解决现实问题的时候,有一些问题我们可以通过不断简化分化出子问题来解决,递归的程序一般简洁明了,容易理解,能帮助我们解决一些复杂问题;而对于递归调用高时间\空间复杂度的问题,可以了解到,所有递归的程序都可以按照特定的步骤转化为非递归的程序。
练习
编程语言练习 / C语言