欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

第六章 函数

程序员文章站 2022-05-07 17:25:04
...
  • 引入代码示例:

    • 编写代码实现strcpy函数,将str2中的字符全部赋值到str1中。

      #include <stdio.h>
      
      int char_copy(char str1[100], char str2[100]);
      
      int main() {
      	char a[100], b[100];
          printf("%s", char_copy(a, b));
          return 0;
      }
      
      int char_copy(char str1[100], char str2[100]) {
          int i;
          gets(str2);
          for (i = 0; str2[i] != '\0'; i++)
              str1[i] = str2[i];
          str1[i] = '\0';
          printf("%s\n", str1);
          return *str1;
      }

第一节 函数的作用

  • 函数是用来完成一定的功能,所谓函数名就是给该功能起一个名字。

  • 为什么要使用函数?

    • 事先编好一批常用的函数来实现各种不同的功能,把它们保存在函数库中。需要用时,直接可以调用,可以实现模块化程序设计。大大减少重复编写代码的工作量,提高程序可读性。

    • 说明:

      • (1). 一个C程序由一个或多个程序模块组成,每一个程序模块作为一个源程序文件。对于较大的程序,一般不希望把所有内容全放在一个文件中,而是把它们分别放在若干个源文件中,由若干个源程序文件组成一个C程序。这样便于分别编写和编译,提高调试效率。一个源程序文件可以为多个C程序公用。

        第六章 函数

      • (2). C程序的执行是从main函数开始的,如果在main函数中调用其他函数,在调用后流程返回到main函数,在main函数中结束整个程序的运行。

      • (3). 一个C程序可由一个主函数和若干个其他函数构成。由主函数调用其他函数,其他函数也可以互相调用。同一个函数可以被一个或多个函数调用任意多次。

    • 代码示例:使用函数打印菱形

      #include <stdio.h>
      int print_star();
      int main() {
          printf("打印菱形:\n");
      	print_star();
      	printf("再次打印菱形:\n");
      	print_star();
      	printf("最后一次打印菱形:\n");
      	print_star();
      	return 0;
      }
      
      int print_star() {
          char a[5][5] = {{' ', ' ', '*'},{' ', '*', ' ', '*'},{'*', ' ', ' ',' ', '*'},{' ', '*', ' ', '*'},{' ', ' ', '*'}};
          int i, j;
          for ( i = 0; i <= 4; i++) {
              for (j = 0; j <= 4; j++)
                  printf("%c", a[i][j]);
              printf("\n");
          }
      }

第二节 函数的定义

  • C语言要求,在程序中用到的所有函数,必须“先定义,后使用”。

  • 定义函数应包括以下几个内容:

      1. 指定函数的名字,以便以后按名调用。
      2. 指定函数的类型,即函数返回值的类型。
      3. 指定函数参数的名字和类型,以便在调用函数时向它们传递数据,对无参函数不需要这一项。
      4. 指定函数应当完成什么操作,也就是函数是做什么的,即函数的功能。这是最重要的,是在函数体中解决的。
  • 定义无参函数:函数名后面的括号中是空的,没有任何参数,定义无参函数的一般形式为:

    	类型名 函数名() {
        	函数体;
    	}
    或
        类型名 函数名 (void) {//void表示“空”,即函数没有参数
        	函数体;
    	}

    函数体包括声明部分语句部分。在定义函数时要用“类型标识符”(即类型名)指定函数值的类型,即指定函数带回来的值的类型。

  • 定义有参函数:有参函数的一般形式为:

    类型名 函数名(形式参数表列) {
        函数体;
    }

    函数体包括声明部分语句部分,如:

    int max (int x, int y) {
        int z;					//声明部分
        z = x > y ? x : y;		//执行语句部分
        return z;
    }

    声明部分包括对函数中用到的变量进行定义以及对要调用的函数进行声明等内容,return z;的作用是指定将z的值作为函数值(称函数返回值)带回到主调函数。

  • 定义空函数:在程序设计中有时会用到空函数,它的形式为:

    类型名 函数名 () {
        
    }
    如:
        void dummy () {
        
    }

    函数体是空的,调用此函数时,什么工作也不做,没有任何实际作用。

    • 为什么要定义空函数?
      • 在程序设计中往往根据需要确定若干个模块,分别由一些函数来实现。

第三节 调用函数

  • 定义函数的目的是为了调用此函数,以得到预期的结果。

    print_star();		//调用无参函数
    c = max(a, b);		//调用有参函数

    函数调用的一般形式为:函数名 (实参表列);。如果是调用无参函数,则“实参表列”可以没有,但括号不能省略,如 果实参表列包含多个实参,则各参数间用逗号隔开。

  • 按函数调用在程序中出现的形式和位置来分,可以由以下3种函数调用方式:

    1. 函数调用语句:把函数调用单独作为一个语句,如:

      print_star();

      这时不要求函数带回值,只要求函数完成一定的操作。

    2. 函数表达式:函数调用出现在另一个表达式中,如:

      c = max (a, b);

      max (a, b);是一次函数调用,它是赋值表达式中的一部分。这时要求函数待会一个确定的值可以参加表达式的运算,例如:

      c = 2 * max (a, b);
    3. 函数参数:函数调用作为另一个函数调用时的实参,例如:

      m = max (a, max (b, c));
    • 说明:调用函数并不一定要求包括分号(如print_star();),只有作为函数调用语句才需要有分号。如果作为函数表达式或函数参数,函数调用本身是不必有分号的。不能写成:

      printf("%d", max (a, b););		//max (a, b)后面多了一个分号
  • 函数调用时的数据传递

      1. 形式参数和实际参数:在调用有参函数时,主调函数和被调函数之间有数据传递关系。在定义函数时函数名后面的括号中的变量名称为“形式参数”(简称“形参”)或“虚拟参数”。在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”(简称“实参”)。实际参数可以是常量、变量、表达式
      2. 在调用函数过程中,系统会把实参的值传递给被调用函数的形参。或者说,形参从实参得到一个值。该值在函数调用期间有效,可以参加该函数中的运算。在调用函数过程中发生的实参与形参间的数据传递称为“虚实结合”。
    • 说明:

      1. 实参可以是常量、变量或表达式,但要求它们必须有确定的值。
      2. 实参与形参的类型应相同或赋值兼容,如果出现不同类型输出传递,会进行强制转换。
    • 代码示例:输入两个整数,要求输出其中值较大者。要求用函数来找到大数。

      #include <stdio.h>
      int max (int x, int y);
      int main() {
          int a, b;
          printf("请输入两个数字:\n");
          scanf("%d %d", &a, &b);
       	printf("max = %d\n", max(a, b));
          return 0;
      }
      int max (int x, int y) {
          return x > y ? x : y;
      }
    • 函数调用的过程:

      1. 在定义函数中指定的形参,在未出现函数调用时,并不占内存中的存储单元。发生函数调用时,函数max的形参才被临时分配内存单元。
      2. 将实参的值传递给对应形参。
      3. 在执行max函数期间,由于形参已经有值,就可以利用形参进行有关的运算。
      4. 通过return语句将函数值带回到主调函数,应当注意返回值的类型与函数类型一致,如果函数不需要返回值,则不需要return语句。这时函数的类型应定义为void类型。
      5. 调用结束,形参单元被释放。注意:实参单元仍保留并维持原值,没有改变。

      注意:实参向形参的数据传递是“值传递”,单向传递,只能由实参传给形参,而不能由形参传给实参。实参和形参在内存中占有不同的存储单元,实参无法得到形参的值。

    • 函数的返回值:通过函数调用使主调函数能得到一个确定的值,这就是函数值(函数的返回值)。

      1. 函数的返回值是通过函数中的return语句获得的,如果需要从被调用函数带回一个函数值(供主调函数使用),被调用函数中必须包含return语句。return后面的值也可以是一个表达式。
      2. 函数值的类型,在定义函数时指定函数值的类型。
      3. 在定义函数时指定的函数类型一般应该和return语句中的表达式类型一致,如果函数值的类型和return语句中表达式的值不一致,则以函数类型为准。对数值型数据,可以自动进行类型转换,即函数类型决定返回值的类型
      4. 对于不带回值的函数,应当定义函数为“void类型”(或称“空类型”)。这样,系统就保证不使函数带回任何值,即禁止在调用函数中使用被调用函数的返回值。此时在函数体中不得出现return语句。

第四节 对被调用函数的声明和函数原型

  • 在一个函数中调用另一个函数(即被调用函数)需要具备如下条件:

    1. 首先被调用的函数必须是已经定义的函数(是库函数或用户自己定义的函数)。
    2. 如果使用库函数,应该在本文件开头用#include指令将调用有关库函数时所需用到的信息“包含”到本文件中来。
    3. 使用用户自己定义的函数,而该函数的位置在调用它的函数(即主调函数)的后面(在同一个文件夹中),应该在主调函数中对被调用的函数作声明。声明的作用是把函数名、函数参数的个数和参数类型等信息通知编译系统,以便编译系统能正确识别函数并检查调用是否合法。
  • 函数的声明和函数定义中的第1行(函数首部)基本上是相同的,只差一个分号(函数声明比函数定义中的首行多一个;),因此写函数声明时,可以简单的照写已定义的函数的首行,再加一个分号,就成了函数的“声明”。函数的首行(即函数首部)称为函数原型,函数的首部包含了函数名、函数值类型、参数个数、参数类型和参数顺序,在检查函数调用时要求函数名、函数类型、参数个数和参数顺序必须与函数声明一致,实参类型必须与函数声明中的形参类型相同。

  • 使用函数原型做声明是C的一个重要特点。用函数原型来声明函数,能减少编写程序时可能出现的错误。在函数声明中的形参名可以省写,而只写形参的类型,如:

    float add (float, float);			//不写参数名,只写参数类型

    编译系统只关心和检查参数个数和参数类型,而不会检查参数名,因为在调用函数时只要求保证实参类型与形参类型一致,函数声明的一般形式有两种,分别为:

    函数类型 函数名 (参数类型1 形参名1, 参数类型2, 形参名2, ···, 参数类型n, 参数名n);
    void print (int num, char sex, float score);
    函数类型 函数名 (参数类型1, 参数类型2, ···, 参数类型n);
    void print (int, char, float);
  • 注意:函数的定义是指对函数功能的确立,包括指定函数名、函数值类型、形参及其类型以及函数体等,是一个完整的、独立的函数单位。而函数的声明的作用则是把函数的名字、函数类型以及形参的类型、个数和顺序通知编译系统,以便在调用该函数时系统按此进行对照检查。如果已在文件的开头(在所有函数之前)对本文件中所调用的函数进行了声明,则在各函数中不必对其所调用的函数再作声明。

  • 由于在文件的开头(在函数的外部)已对要调用的函数进行了声明(这些称为外部声明),因此在程序编译时,编译系统已从外部声明中知道了函数的有关信息,所以不必在主调函数中再重复进行声明。写在所有函数前面的外部声明在整个文件范围中有效。

第五节 函数的嵌套调用

  • C语言的函数定义是互相平行、独立的,不能嵌套定义,但是可以嵌套调用函数,如:

    第六章 函数

  • 代码示例:输入4个整数,找出其中最大的数,用函数的嵌套调用来处理。

    #include <stdio.h>
    //int max_1(int x, int y);
    int max_2(int a, int b, int c, int d);
    int main() {
    	int a, b, c, d, max;
    	printf("请输入4个整数:\n");
    	scanf("%d %d %d %d", &a, &b, &c, &d);
    	max = max_2(a, b, c, d);
    	printf("max is %d\n", max);
    	return 0;
    }
    
    int max_1 (int x, int y) {
    	return x > y ? x : y;
    }
    
    int max_2(int a, int b, int c, int d) {
    	int n;
    	n = max_1(a, b);
    	n = max_1(n, c);
    	n = max_1(n, d);
    	return n;
    }

第六节 函数的递归调用

  • 在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归调用,语言的特点之一就在于允许函数的递归调用,例如:

    int f (int x) {
        int y, z;
        z = f (y);		//在执行f函数的过程中又要调用f函数
        return 2 * z;
    }

    第六章 函数

    可以看到,上图两种递归调用都是无终止的自身调用,程序中只应出现有限次数的、有终止的递归调用,可以用if语句来控制,只有在某一条件成立时才继续执行递归调用;否则就不再继续。

    • 代码示例1:函数递归无限循环

      #include <stdio.h>
      int fun (int i);
      int main() {
          int num;
          printf("请输入一个数字:\n");
          scanf("%d", &num);
          fun(num);
          return 0;
      }
      
      int fun (int i) {
          printf("%d\n", i);
          return fun (i + 1);
      }
    • 代码示例2:求n的阶乘

      #include <stdio.h>
      int func(int n);
      int main() {
          int i;
          printf("请输入一个数:\n");
          scanf("%d", func(i));
          return 0;
      }
      int func (int n) {
          int i;
          if (n == 1)
              c = 1;
          else
              c = func(n - 1) * n;
          return c;
      }

第七节 数组作为函数参数

  • 调用有参函数时,需要提供实参。实参可以是常量、变量、表达式。数组元素也可以用作函数实参,数组名也可以作实参和形参,传递的是数组第一个元素的地址。

  • 数组元素可以用作函数实参,但是不能用作形参。因为形参是在函数被调用时临时分配存储单元的,不可能为一个数组元素单独分配存储单元(数组是一个整体,在内存中占连续的一段存储单元)。在用数组元素作函数实参时,把实参的值传给形参,是“值传递”方式。数据传递的方向是从实参传到形参,单向传递

    • 将数组的元素当作实参传递和变量的传递类似,实参使用数组名[下标];形参使用对应元素的类型即可。

    • 代码示例:输入10个数,要求输出其中值最大的元素和该数是第几个数。

      #include <stdio.h>
      int main() {
          int a[10], m, n, i;
          printf("请输入10个数字:\n");
          for (i = 0; i < 10; i++)
              scanf("%d", &a[i]);
          printf("\n");
          for ( i = 1, m = a[0], n = 0; i < 10; i++) {
              if (max(m, a[i]) > m) {
                  m = max(m, a[i]);
                  n = i;
              }
          }
          printf("最大值是%d,是第%d个元素", m, n + 1);
          return 0;
      }
      
      int max(int x, int y) {
          return x > y ? x : y;
      }
  • 除了可以用数组元素作为函数参数外,还可以用数组名作函数参数(包括实参和形参)。用数组元素作实参时,向形参变量传递的是数组元素的值,而用数组名作函数实参时,向形参(数组名或指针变量)传递的是数组首元素的地址。

    第六章 函数

    num和a使用同一个内存空间,一旦通过a[0]改变了值,对应的num[0]也改变值内容。

    • 代码示例:有一个一维数组score,内放10个学生成绩,求平均成绩。

      #include <stdio.h>
      float average(float array[10]);
      int main() {
      	float score[10], aver;
      	int i;
      	printf("请输入10个成绩:\n");
      	for (i = 0; i < 10; i++)
      		scanf("%f", &score[i]);
      	printf("\n");
      	aver = average(score);
      	printf("平均分是:%5.2f\n", aver);
      	return 0;
      }
      
      float average(float array[10]) {
      	int i;
      	float aver, sum = array[0];
      	for (i = 1; i < 10; i++)
      		sum += array[i];
      	aver = sum / 10;
      	return aver;
      }
  • 可以用多维数组名作为函数的实参和形参,在被调用函数中对形参数组定义时可以指定每一维的大小,也可以省略第一维的大小说明,例如:

    int ayyar[3][10];int array[][10];

    二者都合法且等价,但是不能把第二维以及其他高维的大小说明省略。二维数组是由若干个一维数组组成的,在内存中,数组是按行存放的,在定义二维数组时,必须指定列数(即一行中包含几个元素),由于形参数组与实参数组类型相同,所以它们是具有相同长度的一维数组所组成的。在第二维大小相同的前提下,形参数组的第一维可以与实参数组不同。这是因为C语言编译系统不检查第一维的大小。

    • 代码示例:有一个3×4的矩阵,求所有元素中的最大值。

      #include <stdio.h>
      int main() {
      	int max_value(int array[][4]);
      	int a[][4] = { {1,3,5,7},{2,4,6,8},{15,17,34,12} };
      	printf("最大值是:%d\n", max_value(a));
      	return 0;
      }
      
      int max_value(int array[][4]) {
      	int i, j, max;
      	max = array[0][0];
      	for (i = 0; i < 3; i++)
      		for (j = 0; j < 4; j++)
      			if (array[i][j] > max)
      				max = array[i][j];
      	return max;
      }

第八节 局部变量和全局变量

  • 在一个函数中定义的变量能否在其他函数中使用?
    • 通常变量在函数内部定义,只在本函数范围内有效,这就是变量的作用域。

第六章 函数

  • 局部变量:在一个函数内部定义的变量只在本函数范围内有效;在复合语句内定义的变量只在本复合语句范围内有效。

    • 定义变量可能有3种情况:
      1. 在函数的开头定义;
      2. 在函数内的复合语句内定义;
      3. 在函数的外部定义。
    • 说明:
      1. 主函数中定义的变量(如m,n)也只在主函数中有效,并不因为在主函数中定义而在整个文件或程序中有效。主函数也不能使用其他函数中定义的变量。
      2. 不同函数中可以使用同名的变量,它们代表不同的对象,互不干扰。
      3. 形式参数也是局部变量。
      4. 在一个函数内部,可以在复合语句中定义变量,这些变量只在本复合语句中有效,这种复合语句也称为“分程序”或“程序块”。
      5. 程序执行完复合语句或调用函数,系统会将对应占用的内存单元释放。
  • 全局变量:程序的编译单位是源程序文件,一个源文件可以包含一个或若干个函数。在函数内定义的变量是局部变量,而在函数之外定义的变量称为“外部变量”,外部变量是“全局变量”。全局变量可以为本文件中其他函数所共用。它的有效范围从定义变量的位置开始到本源文件结束。

    • 注意:在函数内定义的变量是局部变量,在函数外定义的变量是全局变量。

    • 在一个函数中既可以使用本函数中的局部变量,也可以使用有效的全局变量。

    • 设置全局变量的作用是增加了函数间数据联系的渠道。在一个函数中改变了全局变量的值,就能影响到其他函数中全局变量的值,相当于各个函数间有直接的传递通道。由于函数的调用只能带回一个函数返回值,因此可以利用全局变量来增加函数间的联系渠道,通过函数调用能得到一个以上的值。通常将全局变量名的第一个字母用大写表示(习惯并非规定)。

    • 代码示例:有一个一维数组,内放10个学生成绩,写一个函数,当主函数调用此函数后,能求出平均分、最高分和最低分。

      #include <stdio.h>
      float Max = 0, Min = 0;
      int main() {
      	float average(float array[], int n);
      	float ave, score[10];
      	int i;
      	printf("请输入10个数:\n");
      	for (i = 0; i < 10; i++)
      		scanf("%f", &score[i]);
      	ave = average(score, 10);
      	printf("max = %6.2f\nmin = %6.2f\naverage = %6.2f\n", Max, Min, ave);
      	return 0;
      }
      float average(float array[], int n) {
      	int i;
      	float ave, sum = array[0];
      	Max = Min = array[0];
      	for (i = 0; i < n; i++) {
      		if (array[i] > Max)
      			Max = array[i];
      		else if (array[i] < Min)
      			Min = array[i];
      		sum += array[i];
      	}
      	ave = sum / n;
      	return ave;
      }
    • 建议不在必要时不要使用全局变量:

      1. 全局变量在程序的全部执行过程中都占用存储单元;
      2. 全局变量使函数的通用性降低了,因为如果在函数中引用了全局变量,那么执行情况会受到有关的外部变量的影响。若该外部变量与其他文件的变量同名时,就会出现问题,这就降低了程序的可靠性和通用性。
        • 在程序设计中,在划分模块时要求模块的“内聚性”强、与其他模块的“耦合性”弱。即模块的功能要单一(不要把许多互不相干的功能放到一个模块中),与其他模块的相互影响要尽量少,全局变量是不符合这个原则的。
      3. 使用全局变量过多,会降低程序的清晰性,由于在各个函数执行时都可能会改变外部变量的值,程序容易出错。

第九节 变量的存储方式和生存期

  • 动态存储方式与静态存储方式

    • 从变量的作用域(即从空间)的角度来观察,变量可以分为全局变量局部变量。还可以从变量值存在的时间(即生存期)来观察,变量的存储有两种不同的方式:静态存储方式动态存储方式。静态存储方式是指在程序运行期间由系统分配固定的存储空间的方式,而动态存储方式则是在程序运行期间根据需要进行动态的分配存储空间的方式。内存*用户使用的存储空间分为3部分:

      1. 程序区;
      2. 静态存储区;
      3. 动态存储区。

      第六章 函数

      数据分别存放在静态存储区和动态存储区中。全局变量全部存放在静态存储区中,在程序开始执行时给全局变量分配存储区,程序执行完毕就释放。在程序执行过程中它们占据固定的存储单元,而不是动态的进行分配和释放。

      在动态存储区中存放以下数据:

      1. 函数形式参数。在调用函数时给形参分配存储空间。
      2. 函数中定义的没有用关键字static声明的变量,即自动变量。
      3. 函数调用时的现场保护和返回地址等。
      

      对于以上数据,在函数调用开始时分配动态存储空间,函数结束时释放这些空间。在程序执行过程中,这种分配和释放是动态的,如果在一个程序中两次调用同一函数,而在此函数中定义了局部变量,在两次调用时分配给这些局部变量的存储空间的地址可能是不相同的。

      如果一个程序中包含若干个函数,每个函数中的局部变量的生存期并不等于整个程序的执行周期,它只是程序执行周期的一部分。在程序执行过程中,先后调用各个函数,此时会动态的分配和释放存储空间。

    • 在C语言中,每一个变量和函数都有两个属性:数据类型数据的存储类别。存储类别指的是数据在内存中存储的方式(如静态存储和动态存储)。

    • C的存储类别包括4种:自动的(auto)、静态的(static)、寄存器的(register)、外部的(extern)

  • 局部变量的存储类别:

    1. 自动变量(auto变量):不专门声明为static(静态)存储类别,都是动态的分配存储空间的,数据存储在动态存储区中。在调用该函数时,系统会给这些变量分配存储空间,在函数调用结束时就自动释放这些存储空间。因此这类局部变量称为自动变量。自动变量用关键字auto作存储类别的声明,关键字auto可以省略,不写auto则隐含指定为“自动存储类别”,它属于动态存储方式。程序中大多数变量属于自动变量。

    2. 静态局部变量(static局部变量):函数中局部变量的值占用的存储单元不释放,指定该局部变量为“静态局部变量”,用关键字static进行声明。

      • 说明:

        1. 静态局部变量属于静态存储类别,在静态存储区内分配存储单元,在整个程序运行过程中都不释放。而自动变量(即动态局部变量)属于动态存储类别,分配在动态存储区空间,函数调用结束后即释放。
        2. 对静态局部变量是在编译时赋初值的,即只赋初值一次,在程序运行时它已有初值。以后每次调用函数时不再重新赋初值,而是保留上次函数调用结束时的值作为初值。
        3. 如果在定义局部变量时不赋初值,则对静态变量来说,编译时自动赋初值0(数值型变量)或空字符'\0'(对字符变量)。而对自动变量来说,它的值是一个不确定的值。
        4. 静态局部变量在函数调用结束后仍然存在,但其他函数是不能引用它的。因为它是局部变量,只能被本函数引用,而不能被其他函数引用。、
      • 代码示例

        #include <stdio.h>
        int main() {
            int fc();
            fc();		//a = 3	b = 3
            fc();		//a = 3	b = 6
            fc();		//a = 3	b = 9
            return 0;
        }
        int fc() {
            auto int a = 0;	//新赋值
            static int b = 0;//使用上一次函数结束后的值
            a += 3;
            b += 3;
            printf("a = %d\tb = %d\n", a, b);
        }
    3. 寄存器变量(register变量):寄存器的存取速度远远高于内存的读取速度,因此一般把需要反复读取的变量冠上register,代表寄存器变量。但现代计算机读取速度非常快,性能非常高,编译系统能够识别反复需要读取的变量,从而自动的将对应的变量存放在寄存器中。在实际编写代码中,无需再对变量进行register声明。

    注意:3种局部变量的存储位置是不同的:自动变量存储在动态存储区;静态局部变量存储在静态存储区;寄存器变量存储在CPU中的寄存器中。

  • 全局变量的存储类别:全局变量都是存放在静态存储区中的,生存周期是固定的,存在于程序的整个运行过程。外部变量是在函数的外部定义的全局变量,它的作用域是从变量的定义处开始,到本程序文件的末尾。在此作用域内,全局变量可以为程序中各个函数所引用。但有时程序设计人员希望能扩展外部变量的作用域,有以下几种情况:

    1. 在一个文件内扩展外部变量的作用域:如果外部变量不在文件开头定义,其有效的作用范围只限于定义处到文件结束。在定点之前的函数不能引用该外部变量。如果由于某种考虑,在定义点之前的函数需要引用该外部变量,则应该在引用之前用关键字extern对该变量作"外部变量声明",表示把该外部变量的作用域扩展到此位置。用extern声明外部变量时,类型名可以写也可以省写,例如:

      	extern int A,B,C;
      或者
          extern A,B,C;

      因为它不是定义变量,也可以不指定类型,只需写出外部变量名即可。

    2. 将外部变量的作用域扩展到其他文件:如果一个程序包含多个文件,假如a文件定义了外部变量,b文件要想使用a文件中的外部变量,需要用extern进行变量声明。

    3. 将外部变量的作用域限制在本文件中:有时在程序设计中希望某些外部变量只限于被本文件引用,而不能被其他文件引用,这时可以在定义外部变量时加一个static声明。这种加上static声明、只能用于本文件的外部变量称为静态外部变量。如果已确认其他文件不需要引用本文件的外部变量,就可以对本文件中的外部变量都加上static,成为静态外部变量,以免被其他文件误用。

    • 提示:
    • (1)全局变量也是静态存储。
    • (2)提倡将外部变量的定义放在引用它的所有函数之前,可以避免在函数中多加一个extern声明。
  • 说明:对于局部变量来说,声明存储类型的作用是指定变量存储的区域(静态存储区或动态存储区)以及由此产生的生存期的问题,而对于全局变量来说,由于都是在编译时分配内存的,都存放在静态存储区,声明存储类型的作用是变量作用域的扩展问题。

    • 用static声明一个变量的作用是:
      1. 对局部变量用static声明,把它分配在静态存储区,该变量在整个程序执行期间不释放,其所分配的空间始终存在。
      2. 对全局变量用static声明,则该变量的作用域只限于本文件模块(即被声明的文件中)。
    • 注意:用auto、register和static声明变量时,是在定义变量的基础上加上这些关键字,而不能单独使用。
  • 存储类别小结:

    • 对一个数据的定义,需要指定两种属性:数据类型存储类别,分别使用两个关键字,例如:

      static int a;	//静态局部整型变量或静态外部整型变量
      auto char c;	//自动变量,在函数内定义
      register int d;	//寄存器变量,在函数内定义
      extern b; 		//用extern声明已定义的外部变量,将已定义的外部变量b的作用域扩展至此
    • 从作用域角度分,有局部变量和全局变量,采用的存储方式如下:

      第六章 函数

    • 从变量存在的时间(生存期)来区分,有动态存储变量和静态存储变量两种类型。静态存储是程序整个运行时间都存在,而动态存储则是在调用函数时临时分配单元。

      第六章 函数

    • 从变量值存放的位置来划分,可分为:

      第六章 函数

    • 关于作用域和生存期的概念:对一个变量的属性可以从两个方面分析,一是变量的作用域,一是变量值存在时间的长短,即生存期。前者是从空间的角度,后者是从时间的角度。如果一个变量在某个文件或函数范围内是有效的,就称该范围为该变量的作用域,在此作用域内可以引用该变量,在专业书中称变量在此作用域内“可见”,这种性质称为变量的可见性。如果一个变量值在某一时刻是存在的,则认为这一时刻属于该变量的生存期,或称该变量在此时刻“存在”。自动变量和寄存器变量离开函数后,值不能被引用,值也不存在。静态外部变量和外部变量在离开函数后变量值仍然存在,且可被引用,而静态局部变量的可见性和存在性不一致,离开函数后,变量值存在,但不能被引用。

    • static对局部变量和全局变量的作用不同。对局部变量来说,它使变量由动态存储方式变为静态存储方式。而对全局变量来说,它使变量局部化(局部于本文件),但仍为静态存储方式。从作用域角度看,凡有static声明的,其作用域都是局限的,或者局限于本函数内(静态局部变量),或者局限于本文件内(静态外部变量)。

第十节 关于变量的声明和定义

  • 一个函数一般由两部分组成:声明部分执行语句。声明部分的作用是对有关的标识符(如变量、函数、结构体、共用体等)的属性进行声明。对于函数而言,函数的声明是函数的原型,而函数的定义是对函数功能的定义。对被调用函数的声明是放在主调函数的声明部分中的,而函数的定义是一个独立的模块。

  • 在声明部分出现的变量有两种情况:一种是需要建立存储空间的(定义性声明,如 int a;),另一种是不需要建立存储空间的(引用性声明,如 extern a;)。广义的说,声明包括定义,但并非所有的声明都是定义。一般为了叙述方便,把建立存储空间的声明称定义,而把不需要建立存储空间的声明称为声明。显然这里指的声明是狭义的,即非定义性声明,例如:

    int main() {
        extern A;	//是声明,不是定义。声明将已定义的外部变量A的作用域扩展到此
        ···
        return 0;
    }
    int A;		//是定义,定义A为整型外部变量

    外部变量定义和外部变量声明的含义是不同的。外部变量的定义只能有一次,它的位置在所有函数之外。在同一文件中,可以有多次对同一外部变量的声明,它的位置可以在函数之内(哪个函数要用就在哪个函数中声明),也可以在函数之外。系统根据外部变量的定义(而不是根据外部变量的声明)分配存储单元。对外部变量的初始化只能在“定义”时进行,而不能在声明中“进行”。所谓“声明”,其作用是声明该变量是一个已在其他地方定义的外部变量,仅仅是为了扩展该变量的作用范围而作的“声明”。

  • 注意:在函数中出现的对变量的声明(除了用extern声明的以外)都是定义。在函数中对其他函数的声明不是函数的定义。

第十一节 内部函数和外部函数

  • 函数本质上是全局的,因为定义一个函数的目的就是要被另外的函数调用,根据函数能否被其他源文件调用,将函数区分为内部函数外部函数

  • 内部函数:一个函数只能被本文件中其他函数所调用,它称为内部函数。在定义内部函数时,在函数名和函数类型的前面加static,即

    static 类型名 函数名(形参表);
    static int fun(int a, int b);	//函数的首行,表示fun是一个内部函数,不能被其他文件调用。

    内部函数又称静态函数,因为它是用static声明的。使用内部函数,可以使函数的作用域只局限于所在文件。这样,在不同的文件中即使有同名的内部函数,也互不干扰。通常把只能由本文件使用的函数和外部变量放在文件的开头,前面都冠以static使之局部化,其他文件不能引用,提高程序的可靠性。

  • 外部函数:在定义函数时,在函数首部的最左端加关键字extern,则此函数是外部函数,可供其他文件调用,如函数首部可以为

    extern int fun(int a, int b)

    这样,函数fun就可以为其他文件调用。C语言规定,如果在定义函数时省略extern,则默认为外部函数。在需要调用此函数的其他文件中,需要对此函数作声明(即使在本文件中调用一个函数,也要用函数原型进行声明)。在对此函数作声明时,要加关键字extern,表示该函数“是在其他文件中定义的外部函数”。

    • 代码示例:设置密码

      • file1.c

        #include <stdio.h>
        //密码提示
        extern int mima_init();
        extern int mima_yz();
        
        int mima;
        int main() {
            mima_init();
            mima_yz();
            return 0;
        }
      • file2.c

        #include <stdio.h>
        #include <windows.h>
        
        extern int mima;
        int mima_init() {
            printf("设置6位数的密码:");
            scanf("%d", &mima);
            printf("密码设置成功!\n");
        }
        
        int mima_yz() {
            int mima_tem, i = 0;
            while(1) {
                Sleep(10000);
                printf("验证密码:");
                scanf("%d", &mima_tem);
                if (mima_tem == mima)
                    printf("密码输入正确,请继续使用\n");
                else {
                    printf("密码不正确\n");
                    i++;
                }
                if (i == 3) {
                    printf("3次密码验证不通过,请等待24h\n");
                    printf("正在退出,请稍后");
                    break;
                }
            }
        }