有人问我为什么前两天写的文章中没有综合性的案例,我在这里解释一下,在案例编写方面,教辅资料和网上有很多,分析的也很详细,既然前人之述备矣,我就不做重复的工作了.
分而治之
我们在编写程序时,一个完整的程序代码量可能会达到上万行,如果没有章法的开发,那我们很难下手,而且对于后期代码的维护也是极其困难的
我们采用分而治之的技术来进行开发,意思是将一个完整的程序拆分成若干个独立的部分单独开发,然后整合起来,实现模块化的开发,即我们从一些实现特定功能的小的程序片段开始开发,因为这些片段功能简单,所以便于管理, 然后将这些片段封装成函数再被其他片段调用,最终形成完整的程序
函数
函数的特点
对于函数的定义是子程序。子程序是一个大型程序中的某部分代码,由一个或多个语句块组成,他负责完成某项特定的任务,而且相较于其他的代码,具备相对的独立性。
一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。这些代码通常被集成为软
件库。
函数有很好的复用性
对于函数的通俗理解就是:
函数是自己或他人封装好的,需要特定输入的,可以实现特定功能并返回运行结果的,且运输时完全独立的程序.
为什么说函数是封装好的呢?
当我们需要使用手机的时候,我们并没有在现场生产一台手机,而是直接使用,这台手机很明显是工厂组装好的,不过如果我们有能力的话,也可以使用自己组装的手机.同样的在我们打印Helloworld的时候,printf函数是被我们直接调用的,并没有在我们的main函数中编写printf函数的源码.在调用函数时只需要通过名称和参数调用即可
函数是需要输入的
以printf为例,我们在调用时,需要将我们想要打印的文本作为参数传递给函数,函数被执行时从入口接收这个参数信息以保证程序的正常运行和功能的实现
函数可以实现特定功能
函数的作用有很多,调用时相当于一个独立的程序,能实现包括运算,拷贝,输入,输出,删除在内的很多功能
函数在运行时是一个完全独立的程序
#include <stdio.h>
void function(int a);
int main(void ) {
int a = 20;
printf("%d\n",a);
function(a);
printf("%d\n",a);
}
void function(int a){
a++;
}
在这段程序中,我调用了自己编写的程序,程序的内容是把变量a自增,但是在主函数中,我的a的值没有发生改变,这就是函数与被调用函数的独立性
抽象点来说,main函数中的a和我的function函数中的a不是一个a,他们只是名字相同,值也相同.我在初学这里时也是很迷惑的,但是如果理解了背后的原理就很容易了.
main函数本身就是一个函数,函数会产生一个实例,在运行时会为实例在内存中开辟空间来存储变量,函数实例开辟的空间都是彼此独立且不被重复使用的,如果一片内存被实例a执行时占用,则实例b就无法使用,直至实例a执行完毕释放掉这块内存,上面的代码中,这两个a就是这种关系,二者的内存地址不同,只是值和变量名相同,好比隔壁小区有一个和你同名的人,你借出100元对隔壁小区的同名者没有任何影响,同理改变function函数中的a的值对main中的a没有影响,如果我们想要改变main中的a的值则需要将a的地址作为参数传递到被调用的函数中,被调函数通过地址来找到a并进行修改,即使在被调函数中的名称不一样,也可以修改变量值
如果当年官兵是按照家庭住址来抓人,鲁迅绝对没得跑.
函数的结构
使用一个函数需要进行函数声明、函数定义、调用函数。
我先进行一个简单举例
#include <stdio.h>
void function();//函数声明
int main(void ) {
function();//函数调用
}
void function(){//函数定义
printf("我被调用了\n");
}
函数头
提供了函数的名称、返回类型和参数。
函数返回值类型 函数名(形参);
return_type function_name(ParameterList);
//函数类型 函数名 (形参列表);
int max(int num1, int num2);
//参数列表包括参数的类型、顺序、数量。参数是可选的,函数可不包含参数。
int max(int, int);
//函数声明中,参数的名称并不重要,只有参数的类型是必需的
C语言中默认情况下,只有后面的函数可以调用前面声明过的函数,如果函数定义在main函数之后时,要需要在main函数之前进行函数声明,如果函数定义在main函数之前则不需要函数声明
当在一个源文件中定义函数且在另一个文件中调用函数时,函数声明是必需的。在这种情况下,在调用函数的文件顶部声明函数。
函数定义
一个函数的定义包括两部分,即函数头部的定义和函数体的定义。
函数头部
定义函数头部的一般形式为:
数据类型 函数名 (形参列表)
其中,数据类型规定了函数的返回值类型;函数名是一个合格的标识符;形参形参列表是由逗号分隔的变量声明。函数头部的形参列表可以为空,即形参列表不包括任何参数,此类函数称为无参函数。函数头部的形参列表也可以包括一个或多个参数,此类函数称为有参函数。
参数列表的一般形式为:
数据类型 参数名,数据类型 参数名,数据类型 参数名,…,数据类型 参数名
为了节省存储空间,编译器在定义函数时通常不为函数分配存储空间,在函数调用时才进行内存分配。(和那些我们自定义的数据类型同理)
在函数头部部分,分别定义了函数的类型、函数的名字、形参列表。该部分定义的是函数的基本特征,是用函数使用者(包括函数定义者)必须遵循的标准,也称为“接口”。其中:函数的数据类型指的是函数该函数计算结果的数据类型,也就是函数返回值的数据类型。而函数返回值的数据类型,可以是任意一种数据类型(例如int、float等),也可以是空类型“void”。
注意:
①定义无参函数时,函数名后面的圆括号不能省略。
②定义有参函数时,如果包括多个参数,参数之间必须用逗号一一分隔。对每一个参数都必须单独定义其数据类型(即参数的类型相同时也不例外)。换句话说就要对每一个参数进行类型说明,然后参数之间用逗号分隔。
函数体
定义函数体的一般形式为:
{
变量的定义和声明
语句序列
返回值
}
函数体是函数功能的具体实现过程,相当于一个具有特定功能的复合语句。一般包括变量的定义、声明部分,语句序列部分和返回值。
double perimeter ( double radius )
{
/*变量的定义和声明部分*/
int num1,num = 4;
char ch = 'A';
double num3 ;
…
/*语句序列部分*/
num = 3 * num2 ;
…return result;
}
函数体中的变量只能在本函数的内部使用,称为该函数的“局部变量”。参数列表中定义的变量用于接收实际参数的值,也只能在该函数内部使用。因此形式函数也属于局部变量。
如果函数有返回值,在函数体的最后要有返回语句,返回的值的类型必须与要求的值对应,若没有返回值,则可以写 return;或不写
return语句是函数的出口,当函数运行到return语句时会退出结束函数运行,没有return语句的函数在运行到函数体的尾部时也会结束运行.
注意:
C语言要求函数体中变量的定义和说明(包括变量的初始化部分),必须放在各语句之前。
函数调用
通过调用函数来完成已定义的任务。
函数调用一般形式:
函数名(实际参数表);
函数调用的几种方式:
表达式:函数作为表达式中的一项出现在表达式中,以函数返回值参与表达式的运算。这种方式要求函数是有返回值的。例如 z=max(x,y);
函数语句:函数调用的一般形式加上分号构成函数语句printf("%d",a);
函数实参:参数作为另一个函数调用的实际参数出现。这种情况把该函数的返回值作为实参进行传送,要求该函数的有返回值,printf("%d",max(x,y))
函数调用是参数的传递:
函数间通过参数来传递数据,即通过主调函数中的实参向被调用函数中的形参进行传递。
实参将值单向传递给形参,形参的变化不影响实参值。
函数的返回值:
函数的返回值通过函数中的return语句获得。如果需要从调用函数带回一个函数值(供主函数使用),被调函数中需包含return语句。
在定义函数时要指定函数值的类型,否则会默认为int类型。
函数类型决定返回值的类型。
函数调用过程
堆栈
我们或多或少都见过手枪弹夹,最后被压入的子弹会被先打出,被打出的总是最顶端的子弹
堆栈(Stack)是一种常见的数据结构,它是一种“后进先出”(Last In First Out,LIFO)的数据结构。栈可以看做是一种特殊的线性表,只能在栈顶进行插入和删除操作。栈顶是允许操作的,而栈底是固定的。
函数调用堆栈/程序执行堆栈
一个函数可以调用也可以被调用,而我们的程序一次只能控制一个函数,所以需要使用函数调用堆栈来保证程序的控制以合理的次序在函数间传递
函数在被调用时,会产生一个实例,实例在运行过程中需要空间来存储变量,也需要保存程序运行结束后的函数返回地址,根据上面的两类信息可以记录和还原函数实例的运行状态,所以将这些信息封装成堆栈帧.
每当一个函数调用另外一个函数,就会产生一个被调函数的实例,同时生成对应的堆栈帧,并把堆栈帧压人堆栈。这个实体包含了被调函数为了将控制返回给主调函数所需要的返回地址以及当前函数的有关变量
若被调函数运行结束,函数调用堆栈的栈顶的堆栈帧将被弹出,控制转移到被弹出堆栈中保存的函数返回地址处。
每个被调函数都能够在调用堆栈的顶部找到它所需的返回到主调函数的信息。如果一个函数调用了另外一个函数,则针对新的函数调用的堆栈帧将被压人调用堆栈。这样,新的被调函数为返回到主调函数所需的返回地址就位于堆栈的顶部。
我录了一个视频,但还在审核,后面我会修改并给出链接
函数的使用
文件的顺序
- 头文件
- 函数头
- 函数定义
头文件
在使用函数时我们发现,并不是所有的函数都是自己写的,或者是需要调用的函数不再当前的文件中,那么编译器要如何知道函数的内容是什么呢?
在编写第一个程序的时候我们在程序的开头总是写着
#include <stdio.h>
这个语句我之前说是预处理语句,他的作用是将标准输入输出头文件(standard input/output header)导入源程序文件中
当我们在使用一个函数时,应该把对应函数库的头文件导入到当前文件中去编译器才能认识我们所调用的程序,如果你去除#include <stdio.h>语句,你的打印就会报错了,因为编译器不认识这个函数了.
对于导入头文件,语法是这样的
#include<标准函数库.h>
#include"自定义函数库"
函数头
对于程序来说,可以说是典型的顾头不顾腚,意思就是说前面出现过的我认识,没有出现的就装作陌生人了
在我们想要在函数中使用被调函数时,如果被调函数的定义出现在主调函数前面,铁子,你用着没毛病,但要是出现在后面,那可就不行了 ,那该怎么办呢
我不吃牛肉!
不好意思,串台了
这时候就有了函数头,函数头就是写在前面,告诉主调函数我有一个这样的被调函数可以供你使用,
出现两个函数互相调用的时候,物理学告诉我左脚踩右脚(函数定义)是飞不起来的,除非你踩的是别人的脚(函数头)
至于怎么写,回看上面函数的结构部分
函数定义
这个部分写的是函数的逻辑代码,看上面这么写吧
结束了!掰掰