C 语言外部变量简介

目录汇总:C 语言零基础入门教程

传递参数是给函数传送信息的一种方法。函数还可以通过外部变量(external variable)进行通信。外部变量是声明在任何函数体外的。

外部变量(有时称为全局变量)的性质不同于局部变量的性质。

  • 静态存储期。就如同声明为 static 的局部变量一样,外部变量拥有静态存储期。存储在外部变量中的值将永久保留下来。
  • 文件作用域。外部变量拥有文件作用域:从变量被声明的点开始一直到所在文件的末尾。因此,跟随在外部变量声明之后的所有函数都可以访问(并修改)它。

一、示例:用外部变量实现栈

为了说明外部变量的使用方法,一起来看看称为(stack)的数据结构。(栈是抽象的概念,它不是 C 语言的特性。大多数编程语言都可以实现栈。)像数组一样,栈可以存储具有相同数据类型的多个数据项。然而,栈操作是受限制的:只可以往栈中压入数据项(把数据项加在一端——“栈顶”)或者从栈中弹出数据项(从同一端移走数据项)。禁止测试或修改不在栈顶的数据项。

C 语言中实现栈的一种方法是把元素存储在数组中,我们称这个数组为 contents。命名为 top 的一个整型变量用来标记栈顶的位置。栈为空时,top 的值为 0。为了往栈中压入数据项,可以把数据项简单存储在 contents 中由 top 指定的位置上,然后自增 top。弹出数据项则要求自减 top,然后用它作为 contents 的索引取回弹出的数据项。

基于上述这些概要,这里有一段代码(不是完整的程序)为栈声明了变量 contentstop 并且提供了一组函数来表示对栈的操作。全部 5 个函数都需要访问变量 top,而且其中 2 个函数还都需要访问 contents,所以接下来把 contentstop 设为外部变量。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <stdbool.h>    /* C99 only */

#define STACK_SIZE 100

/* external variables */
int contents[STACK_SIZE];
int top = 0;

void make_empty(void)
{
  top = 0;
}

bool is_empty(void)
{
  return top == 0;
}

bool is_full(void)
{
  return top == STACK_SIZE;
}

void push(int i)
{
  if (is_full())
    stack_overflow();
  else
    contents[top++]  = i;
}

int pop(void)
{
  if (is_empty())
    stack_underflow();
  else
    return contents [--top];
}

二、外部变量的利与弊

在多个函数必须共享一个变量时或者少数几个函数共享大量变量时,外部变量是很有用的。然而在大多数情况下,对函数而言,通过形式参数进行通信比通过共享变量的方法更好,原因列举如下。

  • 在程序维护期间,如果改变外部变量(比方说改变它的类型),那么将需要检查同一文件中的每个函数,以确认该变化如何对函数产生影响。
  • 如果外部变量被赋了错误的值,可能很难确定出错的函数,就好像侦察大型聚会上的谋杀案时很难缩小嫌疑人范围一样。
  • 很难在其他程序中复用依赖外部变量的函数。依赖外部变量的函数不是“独立的”。为了在另一个程序中使用该函数,必须带上此函数需要的外部变量。

许多 C 程序员过于依赖外部变量。一个普遍的陋习是,在不同的函数中为不同的目的使用同一个外部变量。假设几个函数都需要变量 i 来控制 for 语句。一些程序员不是在使用变量 i 的每个函数中都声明它,而是在程序的顶部声明它,从而使得该变量对所有函数都是可见的。这种方式除了前面提到的几个缺点外,还会产生误导:以后阅读程序的人可能认为变量的使用彼此关联,而实际并非如此。

使用外部变量时,要确保它们都拥有有意义的名字。(局部变量不是总需要有意义的名字的,因为往往很难为 for 循环中的控制变量起一个比 i 更好的名字。)如果你发现为外部变量使用的名字就像 itemp 一样,这可能意味着这些变量其实应该是局部变量。

注意

把原本应该是局部变量的变量声明为外部变量可能导致一些令人厌烦的错误。思考下面的例子,我们希望它显示一个由星号组成的 10×10 的图形:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
int i;

void print_one_row(void)
{
 for (i = 1;  i <= 10;  i++)
   printf("*");
}

void print_all_rows(void)
{
 for (i = 1;  i <= 10;  i++)  {
   print_one_row();
   printf("\n");
 }
}

print_all_rows 函数不是显示 10 行星号,而是只显示 1 行。在第一次调用 print_one_row 函数后返回时,i 的值将为 11。然后,print_all_rows 函数中的 for 语句对变量 i 进行自增并判定它是否小于或等于 10。因为判定条件不满足,所以循环终止,函数返回。

程序 猜数

为了获得更多关于外部变量的经验,现在编写一个简单的游戏程序。这个程序产生一个 1~100 的随机数,用户尝试用尽可能少的次数猜出这个数。下面是程序运行时用户将看到的内容:

Guess the secret number between 1 and 100.

A new number has been chosen.
Enter guess: 55
Too low; try again.
Enter guess: 65
Too high; try again.
Enter guess: 60
Too high; try again.
Enter guess: 58
You won in 4 guesses!

Play again? (Y/N) y

A new number has been chosen.
Enter guess: 78
Too high; try again.
Enter guess: 34
You won in 2 guesses!

Play again? (Y/N) n

这个程序需要完成几个任务:初始化随机数生成器,选择神秘数,以及与用户交互直到选出正确数为止。如果编写独立的函数来处理每个任务,那么可能会得到下面的程序。

guess.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/* Asks user to guess a hidden number */

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define MAX_NUMBER 100

/* external variable */
int secret_number;

/* prototypes */
void initialize_number_generator(void);
void choose_new_secret_number(void);
void read_guesses(void);

int main(void)
{
 char command;

 printf("Guess the secret number between 1 and %d.\n\n", MAX_NUMBER);
 initialize_number_generator();
 do {
   choose_new_secret_number();
   printf("A new number has been chosen.\n");
   read_guesses();
   printf("Play again? (Y/N) ");
   scanf(" %c", &command);
   printf("\n");
 } while (command == 'y' || command == 'Y');

 return 0;
}

/************************************************************
* initialize_number_generator: Initializes the random      *
*                              number generator using      *
*                              the time of day.            *
************************************************************/
void initialize_number_generator(void)
{
 srand((unsigned) time(NULL));
}

/************************************************************
* choose_new_secret_number: Randomly selects a number      *
*                           between 1 and MAX_NUMBER and   *
*                           stores it in secret_number.    *
************************************************************/
void choose_new_secret_number(void)
{
 secret_number = rand() % MAX_NUMBER + 1;
}

/************************************************************
* read_guesses: Repeatedly reads user guesses and tells    *
*               the user whether each guess is too low,    *
*               too high, or correct. When the guess is    *
*               correct, prints the total number of        *
*               guesses and returns.                       *
************************************************************/
void read_guesses(void)
{
 int guess, num_guesses = 0;

 for (;;) {
   num_guesses++;
   printf("Enter guess: ");
   scanf("%d", &guess);
   if (guess == secret_number) {
     printf("You won in %d guesses!\n\n", num_guesses);
     return;
   } else if (guess < secret_number)
     printf("Too low; try again.\n");
   else
     printf("Too high; try again.\n");
 }
}

对于随机数的生成,guess.c 程序与 time 函数、srand 函数和 rand 函数有关,这些函数第一次用在 deal.c 程序(C 语言多维数组简介)中。这次将缩放 rand 函数的返回值使其落在 1~MAX_NUMBER 范围内。

虽然 guess.c 程序工作正常,但是它依赖一个外部变量。把变量 secret_number 外部化以便 choose_new_secret_number 函数和 read_guesses 函数都可以访问它。如果对 choose_new_secret_number 函数和 read_guesses 函数稍做改动,应该能把变量 secret_number 移入 main 函数中。现在我们将修改 choose_new_secret_number 函数以便函数返回新值,并将重写 read_guesses 函数以便变量 secret_number 可以作为参数传递给它。

下面是新程序,修改的部分用粗体标注出来。

guess2.c

/* Asks user to guess a hidden number */

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define MAX_NUMBER 100

/* prototypes */
void initialize_number_generator(void);
int new_secret_number(void);
void read_guesses(int secret_number);

int main(void)
{
 char command;
 int secret_number;

 printf("Guess the secret number between 1 and %d.\n\n", MAX_NUMBER);
 initialize_number_generator();
 do {
   secret_number = new_secret_number();
   printf("A new number has been chosen.\n");
   read_guesses(secret_number);
   printf("Play again? (Y/N) ");
   scanf(" %c", &command);
   printf("\n");
 } while (command == 'y' || command == 'Y');

 return 0;
}

/************************************************************ 
* initialize_number_generator: Initializes the random      *
*                              number generator using      *
*                              the time of day.            *
************************************************************/
void initialize_number_generator(void)
{
 srand((unsigned) time(NULL));
}

/************************************************************ 
* new_secret_number: Returns a randomly chosen number      *
*                    between 1 and MAX_NUMBER.             *
************************************************************/
int new_secret_number(void)
{
 return rand() % MAX_NUMBER + 1;
}

/************************************************************ 
* read_guesses: Repeatedly reads user guesses and tells    *
*               the user whether each guess is too low,    *
*               too high, or correct.  When the guess is   *
*               correct, prints the total number of        *
*               guesses and returns.                       *
************************************************************/
void read_guesses(int secret_number)
{
 int guess, num_guesses = 0;

 for (;;) {
   num_guesses++;
   printf("Enter guess: ");
   scanf("%d", &guess);
   if (guess == secret_number) {
     printf("You won in %d guesses!\n\n", num_guesses);
     return;
   } else if (guess < secret_number)
     printf("Too low; try again.\n");
   else
     printf("Too high; try again.\n");
 }
}

请参阅

(完)

comments powered by Disqus