C语言函数的深入探究

1.为什么会有函数

一个程序可以分成多个源文件,一个源文件内部通常包含多个函数,一个函数分成多个语句,这就是整个程序的组织形式。这样组织的好处在于:分化问题、便于编写程序和便于分工。

函数的出现是人:程序员和架构师的需要,而不是机器:编译器、CPU的需要。函数的目的就是实现模块化编程,说白了就是为了提供程序的可移植性。

2.函数的实质:数据处理器

函数是动词、变量是名词,在面向对象中的语言中它们也分别叫方法和成员变量。代码中的函数在编译时被编译成可执行代码段,变量经过编译后变成数据或者在运行时变成数据。一个程序的运行需要代码和数据两方向的结合才能完成。代码和数据需要彼此配合,代码是为了加工数据,数据必须借助代码来起作用。拿现实中的工厂来比喻:数据是原材料,代码是加工流水线。名词性的数据必须经过动词性的加工才能变成最终我们需要的产出的数据,这个加工的过程就是程序的执行过程。

程序的主体是数据,也就是说程序运行的主要目标是生成目标数据,我们写代码也是为了得到目标数据。那么如何才能得到目标数据呢?有2个必要因素:原材料+加工算法。原材料就是程序的输入数据,加工算法就是程序。

程序的编写和运行就是为了把原数据加工成目标数据,所以程序的实质就是一个数据处理器。而函数就是程序的一个缩影,函数的参数列表其实就是为了给函数输入原材料数据,函数的返回值和输出型参数就是为了向外部输出目标数据,函数的函数体里的那些代码就是加工算法。

函数在静止没有执行的时候乖乖的躺在硬盘里,就好象一台没有开动的机器,此时只占一些硬盘的存储空间而并不占用资源(CPU+内存),函数的每一次运行就好象机器的每一次开机运行,运行时需要耗费资源(CPU+内存),运行时可以对数据加工生成目标数据;函数运行完毕会释放占用的资源。整个程序的运行其实就是很多个函数相继运行的连续过程。

3.函数的使用规则

3.1 函数的基本使用

函数三要素:

  • 1.函数定义:函数的定义就是函数体,这是函数的根本。函数定义中的函数名表示了这个函数在内存中的首地址,所以可以用函数名来调用执行这个函数,这么做的实质是函数指针解引用访问。函数定义中的函数体是函数的执行关键,函数将来执行时主要就是执行函数体,所以一个函数没有定义就是无基之塔。
  • 2.函数声明:函数声明是函数原型,函数声明的主要作用是告诉编译器函数的原型。
  • 3.函数调用:函数调用就是调用执行一个函数。

函数编写时的一般规则:

  • 一个函数可以完成一个任务,函数不能太长也不宜太短,原则是一个函数只做一件事情。
  • 一个函数传参不宜过多:在ARM体系下,传参不宜超过4个,如果传参确实需要多则应当考虑结构体打包。
  • 一个函数应该尽量少碰全局变量,函数最好用传参返回值来和外部交换数据,不要用全局变量。

函数的一般语法为:

函数的返回类型 函数名(参数列表)
{
    
    
	//函数体
}

3.2 函数原型和作用

函数原型就是函数的声明,包括函数的函数名、返回值类型、参数列表。函数原型的主要作用就是给编译器提供原型,让编译器在编译程序时帮我们进行参数的静态类型检查。

需要注意的是:编译器在编译程序时是以单个源文件为单位的,所以一定要在哪个文件里调用就在那个文件里声明,而且编译器工作时已经经过预处理,头文件被展开处理了。编译器编译文件时是按照文件中语句的先后顺序执行的,编译器从源文件的第一行开始编译,遇到函数声明时就会收到编译器的函数声明表中,然后继续向后。当遇到一个函数调用时,就在本文件的函数声明表中去查这个函数,看有没有原型相对应的一个函数,这个相对应的函数有且只能有一个。如果没有或者只有部分匹配则会报错或报警告;如果发现多个则会报错或报警告,函数重复了,C语言中不允许2个函数原型完全一样,这个过程其实是在编译器遇到函数定义时完成的。

所以函数可以重复声明但是不能重复定义,最好函数声明在.h文件中,实现在同名的.c文件中。如果一个函数直接实现在.h文件中,则在多个.c文件中包含该头文件时,编译会报该函数重复定义的错误。

3.3 函数传参

函数传参可以参考 [C语言中指针的理解3:指针与函数传参]

4.递归函数

4.1 什么是递归函数

递归函数就是函数中调用了自己本身这个函数的函数。

递归函数解决问题的典型就是求阶乘:

int jiecheng(int n)
{
    
    
	if(n<1)
	{
    
    
		printf("n应该大于等于1.\n");
		return -1;
	}
	if(n==1)
		return 1;
	else
		return n*jiecheng(n-1);

4.2 函数的递归调用原理

实际上递归函数是在栈内存上递归执行的,每次递归执行一次就需要耗费一些栈内存。栈内存的大小是限制递归深度的重要因素。

4.3 使用递归函数的原则:收敛性、栈溢出

收敛性就是说:递归函数必须有一个终止递归的条件。当每次这个函数被执行时,我们判断一个条件决定是否继续递归,这个条件最终必须能够被满足。如果没有递归终止条件或者这个条件永远不能被满足,则这个递归没有收敛性,这个递归最终要失败。

因为递归是占用栈内存的,每次递归调用都会消耗一些栈内存。因此必须在栈内存耗尽之前递归收敛(终止),否则就会栈溢出。递归函数的使用是有一定风险的,必须把握好。

猜你喜欢

转载自blog.csdn.net/PecoHe/article/details/114408451