chap07:函数


函数就是一段封装好的,可以重复使用的代码,它使得我们的程序更加模块化,不需要编写大量重复的代码。

函数可以提前保存起来,并给它起一个独一无二的名字,只要知道它的名字就能使用这段代码。函数还可以接收数据,并根据数据的不同做出不同的操作,最后再把处理结果反馈给我们。

一、定义函数

C 语言中的函数定义的一般形式如下:

return_type function_name( parameter list )
{
   body of the function
}

在 C 语言中,函数由一个函数头和一个函数主体组成。下面列出一个函数的所有组成部分:

  • 返回类型: 一个函数可以返回一个值。return_type 是函数返回的值的数据类型。有些函数执行所需的操作而不返回值,在这种情况下,return_type 是关键字 void
  • 函数名称: 这是函数的实际名称。函数名和参数列表一起构成了函数签名。
  • 参数: 参数就像是占位符,定义时被称为形式参数,简称形参。当函数被调用时,您向参数传递一个值,这个值被称为实际参数,简称实参。参数列表包括函数参数的类型、顺序、数量。参数是可选的,也就是说,函数可能不包含参数,此类函数可称为无参函数
  • 函数主体: 函数主体包含一组定义函数执行任务的语句。

实例:

以下是 max() 函数的源代码。该函数有两个参数 num1 和 num2,会返回这两个数中较大的那个数:

/* 函数返回两个数中较大的那个数 */
int max(int num1, int num2) 
{
   /* 局部变量声明 */
   int result;
 
   if (num1 > num2) {
      result = num1;
   } else {
      result = num2;
   }
   return result; 
}

强调一点:C语言不允许函数嵌套定义;也就是说,不能在一个函数中定义另外一个函数

二、函数声明

C语言代码由上到下依次执行,原则上函数定义要出现在函数调用之前,否则就会报错。但在实际开发中,经常会在函数定义之前使用它们,这个时候就需要提前声明。

所谓声明(Declaration),就是告诉编译器我要使用这个函数,你现在没有找到它的定义不要紧,请不要报错,稍后我会把定义补上。

函数声明格式:

return_type function_name( parameter list );

针对上面定义的函数 max(),以下是函数声明:

int max(int num1, int num2);

在函数声明中,参数的名称并不重要,只有参数的类型是必需的,因此下面也是有效的声明:

int max(int, int);

函数声明给出了函数名、返回值类型、参数列表(重点是参数类型)等与该函数有关的信息,称为函数原型(Function Prototype)

初学者编写的代码都比较简单,顶多几百行,完全可以放在一个源文件中。对于单个源文件的程序,通常是将函数定义放到 main() 的后面,将函数声明放到 main() 的前面,这样就使得代码结构清晰明了,主次分明。

在实际开发中,往往都是几千行、上万行、百万行的代码,将这些代码都放在一个源文件中简直是灾难,不但检索麻烦,而且打开文件也很慢,所以必须将这些代码分散到多个文件中。对于多个文件的程序,通常是将函数定义放到源文件(.c文件)中,将函数的声明放到头文件(.h文件)中,使用函数时引入对应的头文件就可以,编译器会在链接阶段找到函数体。

三、调用函数

所谓函数调用(Function Call),就是使用已经定义好的函数。

创建 C 函数时,会定义函数做什么,然后通过调用函数来完成已定义的任务。

当程序调用函数时,程序控制权会转移给被调用的函数。被调用的函数执行已定义的任务,当函数的返回语句被执行时,或到达函数的结束括号时,会把程序控制权交还给主程序。

调用函数时,传递所需参数,如果函数返回一个值,则可以存储返回值或直接输出。

在C语言中,函数调用的方式有多种,例如:

//函数作为表达式中的一项出现在表达式中
z = max(x, y);
m = n + max(x, y);
//函数作为一个单独的语句
printf("%d", a);
scanf("%d", &b);
//函数作为调用另一个函数时的实参
printf( "%d", max(x, y) );
total( max(x, y), min(m, n) );

四、作用域规则

C 语言中有三个地方可以声明变量:

  1. 在函数或块内部的局部变量
  2. 在所有函数外部的全局变量
  3. 形式参数的函数参数定义中,也是局部变量

所谓作用域(Scope),就是变量的有效范围,就是变量可以在哪个范围以内使用。

1、在函数内部定义的变量(局部变量)

在函数内部定义的变量,它的作用域也仅限于函数内部,出了函数就不能使用了,我们将这样的变量称为局部变量(Local Variable)

函数的形参也是局部变量,也只能在函数内部使用。请看下面的例子:

#include <stdio.h>
int sum(int m, int n){
    int i, sum=0;
    //m、n、i、sum 都是局部变量,只能在 sum() 内部使用
    for(i=m; i<=n; i++){
        sum+=i;
    }
    return sum;
}
int main(){
    int begin = 5, end = 86;
    int result = sum(begin, end);
    //begin、end、result 也都是局部变量,只能在 main() 内部使用
    printf("The sum from %d to %d is %d\n", begin, end, result);
    return 0;
}

m、n、i、sum 是局部变量,只能在 sum() 内部使用;begin、end、result 也是局部变量,只能在 main() 内部使用。

对局部变量的两点说明:

  • main() 也是一个函数,在 main() 内部定义的变量也是局部变量,只能在 main() 函数内部使用。
  • 形参也是局部变量,将实参传递给形参的过程,就是用实参给局部变量赋值的过程,它和a=b; sum=m+n;这样的赋值没有什么区别。

2、在函数外部定义的变量(全局变量)

C语言允许函数的外部定义变量(通常是在程序的顶部),这样的变量称为全局变量(Global Variable)

全局变量的默认作用域是整个程序,也就是所有的代码文件。

如果给全局变量加上 static 关键字,它的作用域就变成了当前文件,在其它文件中就无效了。

例如:

int a, b;  //全局变量
void func1(){
    //TODO:
}
float x,y;  //全局变量
int func2(){
    //TODO:
}
int main(){
    //TODO:
    return 0;
}

a、b、x、y 都是在函数外部定义的全局变量。C语言代码是从前往后依次执行的,由于 x、y 定义在函数 func1() 之后,所以在 func1() 内无效;而 a、b 定义在源程序的开头,所以在 func1()、func2() 和 main() 内都有效。

3、块级变量:在代码块内部定义的变量

C语言允许在代码块内部定义变量(属于局部变量),这样的变量具有块级作用域;换句话说,在代码块内部定义的变量只能在代码块内部使用,出了代码块就无效了。

#include <stdio.h>
int main(){
    int n = 22;  //编号①
    //由{ }包围的代码块
    {
        int n = 40;  //编号②
        printf("block n: %d\n", n);
    }
    printf("main n: %d\n", n);
   
    return 0;
}

这里有两个 n,它们位于不同的作用域,不会产生命名冲突。{ } 的作用域比 main() 更小,{ } 内部的 printf() 使用的是编号为②的 n,main() 内部的 printf() 使用的是编号为①的 n。作用域小的屏蔽作用域大的。

4、变量不初始化时的初值

在 C 语言中,如果变量没有显式初始化,那么它的默认值将取决于该变量的类型和其所在的作用域。

对于全局变量静态变量(在函数内部定义的静态变量和在函数外部定义的全局变量),它们的默认初始值为零。

以下是不同类型的变量在没有显式初始化时的默认值:

  • 整型变量(int、short、long等):默认值为0。
  • 浮点型变量(float、double等):默认值为0.0。
  • 字符型变量(char):默认值为’\0’,即空字符。
  • 指针变量:默认值为NULL,表示指针不指向任何有效的内存地址。
  • 数组、结构体、联合等复合类型的变量:它们的元素或成员将按照相应的规则进行默认初始化,这可能包括对元素递归应用默认规则。

局部变量(在函数内部定义的非静态变量)不会自动初始化为默认值,它们的初始值是未定义的(包含垃圾值)。因此,在使用局部变量之前,应该显式地为其赋予一个初始值。

五、存储类别

存储类定义 C 程序中变量/函数的的存储位置、生命周期和作用域。

这些说明符放置在它们所修饰的类型之前。

下面列出 C 程序中可用的存储类:

  • auto
  • register
  • static
  • extern

1、auto 存储类

auto 存储类是所有局部变量默认的存储类。

定义在函数中的变量默认为 auto 存储类,这意味着它们在函数开始时被创建,在函数结束时被销毁。

{
   int mount;
   auto int month;
}

上面的实例定义了两个带有相同存储类的变量,auto 只能用在函数内,即 auto 只能修饰局部变量。

2、register 存储类

register 存储类用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个字),且不能对它应用一元的 ‘&’ 运算符(因为它没有内存位置)。

register 存储类定义存储在寄存器,所以变量的访问速度更快,但是它不能直接取地址,因为它不是存储在 RAM 中的。在需要频繁访问的变量上使用 register 存储类可以提高程序的运行速度。

{
   register int  miles;
}

寄存器只用于需要快速访问的变量,比如计数器。还应注意的是,定义 ‘register’ 并不意味着变量将被存储在寄存器中,它意味着变量可能存储在寄存器中,这取决于硬件和实现的限制。

3、static 存储类

static 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。

static 修饰符也可以应用于全局变量。当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内。

全局声明的一个 static 变量或方法可以被任何函数或方法调用,只要这些方法出现在跟 static 变量或方法同一个文件中。

静态变量在程序中只被初始化一次,即使函数被调用多次,该变量的值也不会重置。

以下实例演示了 static 修饰全局变量和局部变量的应用:

实例:

#include <stdio.h>
 
/* 函数声明 */
void func1(void);
 
static int count=10;        /* 全局变量 - static 是默认的 */
 
int main(){
  while (count--) {
      func1();
  }
  return 0;
}
 
void func1(void){
/* 'thingy' 是 'func1' 的局部变量 - 只初始化一次
 * 每次调用函数 'func1' 'thingy' 值不会被重置。
 */                
  static int thingy=5;
  thingy++;
  printf(" thingy 为 %d , count 为 %d\n", thingy, count);
}

实例中 count 作为全局变量可以在函数内使用,thingy 使用 static 修饰后,不会在每次调用时重置。

当上面的代码被编译和执行时,它会产生下列结果:

 thingy 为 6 , count 为 9
 thingy 为 7 , count 为 8
 thingy 为 8 , count 为 7
 thingy 为 9 , count 为 6
 thingy 为 10 , count 为 5
 thingy 为 11 , count 为 4
 thingy 为 12 , count 为 3
 thingy 为 13 , count 为 2
 thingy 为 14 , count 为 1
 thingy 为 15 , count 为 0

4、extern 存储类

extern 存储类用于定义在其他文件中声明的全局变量或函数。当使用 extern 关键字时,不会为变量分配任何存储空间,而只是指示编译器该变量在其他文件中定义。

extern 存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。当您使用 extern 时,对于无法初始化的变量,会把变量名指向一个之前定义过的存储位置。

当您有多个文件且定义了一个可以在其他文件中使用的全局变量或函数时,可以在其他文件中使用 extern 来得到已定义的变量或函数的引用。可以这么理解,extern 是用来在另一个文件中声明一个全局变量或函数。

extern 修饰符通常用于当有两个或多个文件共享相同的全局变量或函数的时候,如下所示:

第一个文件:main.c

#include <stdio.h>
 
int count ;
extern void write_extern();
 
int main(){
   count = 5;
   write_extern();
}

第二个文件:support.c

#include <stdio.h>
 
extern int count;
 
void write_extern(void){
   printf("count is %d\n", count);
}

在这里,第二个文件中的 extern 关键字用于声明已经在第一个文件 main.c 中定义的 count

当程序被执行时,它会产生下列结果:

count is 5

六、递归函数

一个函数在它的函数体内直接或间接调用它自身称为递归调用,这种函数称为递归函数。执行递归函数将反复调用其自身,每调用一次就进入新的一层,当最内层的函数执行完毕后,再一层一层地由里到外退出。

下面我们通过一个求阶乘的例子,看看递归函数到底是如何运作的。阶乘 n! 的计算公式如下:

在这里插入图片描述

注:

  1. 上述的公式称为递归公式;
  2. 公式中有将问题规模大的问题转换为规模小的问题的通式;
  3. 要有递归出口(一个从函数退出的条件),否则会进入死循环。

根据公式编写如下的代码:

#include <stdio.h>
//求n的阶乘
long factorial(int n) {
    if (n == 0 || n == 1) {
        return 1;    // 递归出口
    }
    else {
        return factorial(n - 1) * n;  // 递归调用
    }
}
int main() {
    int a;
    printf("Input a number: ");
    scanf("%d", &a);
    printf("Factorial(%d) = %ld\n", a, factorial(a));
    return 0;
}
  • 35
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值