C

5. C语言教程-函数

5. function

Posted by xuepro on October 30, 2017

“教小白精通编程”系列之“C语言教程” (版权所有,不得转载,擅自抄袭转载将承担法律责任)

5. 函数function

例: 编写程序求2个整数m和n(假设m>=n)的最大公约数.

即:

 GCD(72,27) = GCD(27,18)
  	        = GCD(18,9)
	          = GCD(9,0)
	          = 9
            

思路: 不断重复: 用n作为新的m,而(m除以n得到余数)k作为新的n,直到n等于0为止,此时的m就是原来的两个数的最大公约数

#include <stdio.h>

void main(){
	int m=72,n=27;	
	while(n){
		int k = m%n;
		m = n; n = k;
	}	
	printf("GCD is %d\n",m);
}

假如需要多次求2个整数的最大公约数,怎么办? 可以“复制黏贴代码”,如下所示:

#include <stdio.h>

void main(){
	int m=72,n=27;	
	while(n){
		int k = m%n;
		m = n; n = k;
	}	
	printf("GCD is %d\n",m);

    m=36; n=24;
    while(n){
		int k = m%n;
		m = n; n = k;
	}	
	printf("GCD is %d\n",m);
}

随着复制黏贴的越来越多,代码会越来越长….呜呜呜

解决方法: 可以给求2个整数的最大公约数的代码语句块起一个名字,即转变成一个函数,然后多次通过这个函数名来调用其中的语句!

#include <stdio.h>

int GCD(int m, int n){  /*形式参数m,n将由调用该函数的代码提供实际的值*/
	while (n){
		int k = m%n;
		m = n; n = k;
	}
	return m;  /*用关键字return(返回) 返回一个值m给调用者,称为函数的“返回值” */
}

void main(){
    int a=72,b=27;  	
    
    printf("GCD is %d\n",GCD(a,b)); /*实际参数a,b的值被赋值给被调用函数GCD的形式参数m,n*/
                                    
    a=36; b=24;
    
    printf("GCD is %d\n",GCD(a,b));  /*将a=36和b=24传给被调用函数GCD的形式参数m和n*/
}

上面我们定义了一个函数”int GCD(int m, int n)”,其中函数名叫做GCD,它可以接受2个int类型的参数,即圆括号里的里的m,n,是所谓的“形式参数” 最左边面的int表示这个函数会返回一个int类型的值,也就是最大公约数的值。”int GCD(int m, int n)”称为函数规范,其后的左右花括号{}表示的语句块称为函数体

因此,一个函数是由函数规范函数体构成的。函数规范通过圆括号里的形参列表说明了这个函数可以接受那些参数,调用这个函数的语句(如GCD(a,b))会将一些实际参数的值传递给这些形式参数,即用实际参数对形式参数初始化,这称之为“参数传递”。

实际上,为了提高程序的开发效率,C语言标准库和其他第三方的库都已经提供了很多这样已经编写好的函数供我们调用,比如我们经常用的printf函数等等! 给程序员快速开发程序和软件带来了极大的方便!

参数传递: 函数调用时,实际参数的值赋值给形式参数。如

   上述程序中的

    printf("GCD is %d\n",GCD(a,b));  /*将a=36和b=24传给被调用函数GCD的形式参数m和n,
                                       然后开始执行被调用函数GCD中的程序语句。 */

比如我们前面的求和函数可以表示成一个函数

int sum(int start, int end){
   int s = 0;
   for (int i = start; i<=end; i++)
      s += i;
   return s;   
}

然后可以这样调用它

int main(){
  int s1 = sum(1,100);
  printf("1到100之间整数和为:%d\n",s1); 
  
  s1 = sum(5,700);
  printf("5到700之间整数和为:%d\n",s1); 
}

变量的作用域

  局部变量: 函数内部定义的变量(包括函数参数)称为局部变量(内部变量),其作用域在函数内部。     局部变量随函数执行而产生,函数结束而销毁。

    外部变量(全局变量): 函数外部定义变量称为全局变量(外部变量),其作用域在整个程序。 程序开始执行就产生,程序结束才销毁

    静态变量: 加static关键字的变量称为静态变量。如果是外部变量,则只在其所在文件里有效,如果是内部变量,则第一次初始化后就不再初始化,不随着函数执行完而销毁,而是始终存在!    

     int x=0;  /*全局变量*/
     void f(){
        int y = 0;  /*局部变量*/
	x++;
	y++;
	printf("%d  %d",x,y);
     }
     void main(){
        f();
	f();
     }
     
     int x=0;  /*全局变量*/
     void f(){
        static int y = 0; /*静态局部变量*/
	x++;
	y++;
	printf("%d  %d",x,y);
     }
     void main(){
        f();
	f();
     }
     

变量的存储区(类别)

静态存储区: 程序运行期间一直存在的固定分配的存储空间。如全局变量和静态变量。    

动态存储区: 分为 程序堆栈区 和堆存储区。

每个程序有一个自己的程序堆栈区,用以维护函数之间的调用关系,主要存放函数的局部变量(包括传值形式参数) 空间。函数的局部变量随着函数执行而“入栈”,随着函数执行完而“出栈”。

所有程序共享一个堆存储区,程序如果需要堆存储区的存储空间,可以用库函数malloc向操作系统申请,用完后要用free函数释放掉,还给操作系统。   待补充:此处省略了程序栈的说明及动画变化情况,以后再补充…..

递归函数 :函数代码中调用自身的函数

假如我们需要求n的阶乘即 n! = 1*2*3...*n,和求和一样,我们可以写成循环语句

int fact(int n){
   int fact_n = 0;
   for (int i = 1; i<=n; i++)
      fact_n *= i;
   return fact_n;   
}

我们还可以将n!阶乘如此看:

  n! = 1        当n==1时
  n! = n*(n-1)    当n>=2时

这就是一个if条件语句,因此可以写出这样的函数

int fact(int n){
    if(n==1) return 1;  //如果n等于1,直接返回1
    
    return n*fact(n-1); //否则,返回n和fact(n-1)的乘积
    
}

在这个函数fact中,当n>1时,又调用了这个fact函数去计算n-1的阶乘,如此继续下去… 比如fact(4)的计算过程实际运行情况如下

  fact(4)
     4 * fact(3)
     4 *  3 * fact(2)
     4 *  3 * 2 * fact(1)
     4 *  3 * 2 * 1
     4 *  3 * 2
     4 *  6
     24

同样,我们之前的最大公约数问题,也可以写成这种自己调用自身的“递归函数” 因为

  GCD(m ,n) = m             当n等于0时
  GCD(m ,n) = GCD(n, m%n)   当n不等于0时

因此,递归函数求最大公约数的程序如下

#include <stdio.h>

int GCD(int m, int n){  /*形式参数m,n将由调用该函数的代码提供实际的值*/

    if(n==0) return m;
    return GCD(n, m%n);	
}

void main(){
    int a=72,b=27;  	
    
    printf("GCD is %d\n",GCD(a,b)); /*实际参数a,b的值被赋值给被调用函数GCD的形式参数m,n*/
                                    
    a=36; b=24;
    
    printf("GCD is %d\n",GCD(a,b));  /*将a=36和b=24传给被调用函数GCD的形式参数m和n*/
}

是指用inline关键字修饰的函数。

函数规范前加了inline关键字的函数称为”内联函数“。

inline double distance(double x, double y) {     
    return x*x+y*y;
}

编译器在编译源代码时会直接用内联函数代码替换掉函数调用语句。称之为”内联展开“。 因为函数调用需要在调用函数和被调用函数之间进行相应的入栈出栈等维护函数调用关系的操作,需要消耗一定的时间,内联展开避免了函数调用开销,提高了程序执行效率。

因此,对于代码少的函数,一般都建议写成内联函数以提高程序执行效率。

当然,即使你声明一个函数为内联函数,编译器也不一定会对所有内联函数进行内联展开,有下列规则:

  • 递归函数不能定义为内联函数
  • 内联函数一般适合于不存在while和switch等复杂的结构的函数,否则编译系统将该函数视为普通函数。
  • 内联函数只能先定义后使用,否则编译系统也会把它认为是普通函数。
  • 对内联函数不能进行异常的接口声明。

结论

C程序是由一些函数构成,而每个函数包含一些变量(如局部变量、静态变量)和语句,其中一些是函数调用语句用于调用其它函数。如C程序执行主函数main(), 其中一些是调用其他函数的语句。

练习

  1. 编写一个函数,求解函数y=x2曲线介于区间[a,b]之间的面积,并在main函数中分别调用该函数,求解该曲线介于[-3.2, 4.5]和[0.5, 67]的面积

  2. (递归函数):假如一组人按照年龄大小排列,年龄最小的那个人说他10岁,后面每个人都说比前面的人大2岁,问第5个人多少岁? 请编程求解。

关注“教小白精通编程”博客微博“教小白精通编程”  


支付宝打赏 微信打赏

您的打赏是对我最大的鼓励!