第10章 C程序的组织形式

作者: 橡树人 | 来源:发表于2020-02-29 07:29 被阅读0次

英文原版:P219

本章来讨论一个程序包含多个函数时产生的几个问题。

思考:
当一个程序包含多个函数时,会出现哪些问题?

本章的主要内容有:

  • 10.1节介绍局部变量。
  • 10.2节介绍全局变量。
  • 10.3节介绍程序块(包含有声明的复合语句)。
  • 10.4节解决用于局部名、全局名、在程序块内声明的名字的作用域规则问题;
  • 10.5节给出如何组织C程序里的函数原型、函数定义、变量声明、以及其他部分的建议;

10.1 局部变量

有3种局部变量

  • 局部非静态变量
  • 局部静态变量
  • 形式参数

什么是局部变量

在函数体内声明的变量就是该函数的局部变量

例1 局部非静态变量

int sum_digits(int n)
{
    int sum = 0; //非静态局部变量

    while(n>0){
     sum += n%10;
     n /= 10;
    }

    return sum;
}

非静态局部变量有哪些性质?

  • 自动存储期限
    一个局部非静态变量的存储期限是自动的,意味着当包含该变量的函数被调用时,该变量的存储单元是自动分配的;当该函数返回时,该变量的存储单元是自动销毁的;这两个过程并不需要程序员显式地参与;
  • 块作用域
    从该变量的声明处开始,到包含该变量的函数体末尾结束,该变量都是可见的

注:

  1. 什么是一个变量的存储期限?
    一个变量的存储期限就是从给变量分配存储单元开始,到销毁该存储单元时结束。
    一个局部非静态变量的存储期限就是从包含该变量的函数被调用时开始,到该函数返回时结束。
    一个局部非静态变量的存储期限是自动的,意味着当包含该变量的函数被调用时,该变量的存储单元是自动分配的;当该函数返回时,该变量的存储单元是自动销毁的;这两个过程并不需要程序员显式地参与
  2. 什么是变量的作用域?
    变量的作用域就是程序中引用到该变量的文本部分,从变量声明处开始,到最后一次引用处结束。
    一个局部变量具有块作用域,意味着从该变量的声明处开始,到包含该变量的函数体末尾结束,该变量都是可见的
    由于一个局部变量的作用域不会超出包含它的函数,所以在不同的函数里是可以有相同名字的局部变量的。

静态局部变量

静态局部变量有哪些性质?

  • 静态存储期限;
  • 块作用域;

解释:

  • 静态局部变量具有静态存储期限,意味着:只要程序没退出执行,即使定义该变量的函数返回了,该变量仍拥有存储单元;
  • 静态局部变量具有块作用域,意味着该变量对其他函数是不可见的;

总而言之,静态局部变量就是一个位置,该位置对其他函数是不可见的,是为相同函数的将来调用保存数据的。

例1 静态局部变量

void f()
{
    static int i;
}

解释:
由于局部静态变量i具有静态存储期限,所以当函数f退出后,i的值并不会丢失。

形式参数

形式参数有哪些性质?

  • 自动存储期限;
  • 块作用域;

形式参数跟局部非静态变量的区别就是:

  • 形式参数是当函数调用时自动赋予初始值的;
  • 局部变量是由程序员编程赋予初始值的;

10.2节 全局变量

函数间通信的方式有两种:

  • 函数调用
  • 全局变量

全局变量不在任何函数体内部声明,是在所有函数体的外部声明的。

全局变量有哪些性质?

  • 静态存储期限;
  • 文件作用域;
  • 全局变量可被跟在其声明后的所有函数使用;

解释:

  • 全局变量具有静态存存储期限,意味着只要程序不退出执行,全局变量的存储单元就一直保留着。
  • 全局变量具有文件作用域,意味着全局变量起作用的范围是从声明处开始,到文件末尾处结束;

例1 使用全局变量来实现栈

#include <stdbool.h>

#define STACK_SIZE 100

//全局变量
int contents[STACK_SIZE];
int top = 0;

void make_empty(vid)
{
    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_overflow();
    }
    else 
    {
        return contents[--top];
    }
}

全局变量的优缺点

在大部分情形里,最好使用函数调用来进行函数间通信,而不是共享变量,理由有3个:

  1. 我们如果在程序维护期间修改了外部变量,则需要检查同一个文件里的每个函数,确认这个修改是如何影响该函数。
  2. 如果一个外部变量被赋了一个不正确的值,有可能很难确认受影响的函数。
  3. 依赖外部函变量的函数很难被其他程序复用。如果其他程序要复用改函数,则需要抽取它需要使用的外部变量。

哪些情形比较适合使用全局变量来进行函数间通信?

程序示例:猜数字

样例输出

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 number: 60
Too high; Try again.
Enter number: 58
You won in a 4 guess!

Play agin? (Y/N) y

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

Player again? (Y/N) n

功能实现:

  • 初始化随机数生成器;
  • 选择一个秘密数;
  • 跟用户交互直到用户猜对了数;

外部变量版本:
guess.c

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

#define MAX_NUMBER 100

//外部变量
int secret_number;

//函数原型
void initialize_number_generator(void);

void choose_secret_number(void);

void read_guess(void);

int main(void)
{
    char command;

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

    return 0;
}

void initialize_number_generator(void)
{
    srand((unsigned)time(NULL));
}

void choose_secret_number(void)
{
    secret_number = rand()%MAX_NUMBER + 1;
}

void read_guess(void)
{
    int guess, number_guess = 0;

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

}

不使用外部变量实现:
guess2.c

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

#define MAX_NUMBER 100

//函数原型
void initialize_number_generator(void);

int new_secret_number(void);

void read_guess(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 choosen.\n");
        read_guess(secret_number);
        printf("Player again? (Y/N) ");
        scanf(" %c", &command);
        printf("\n");
    }while(command == 'y'|| command == 'y');

    return 0;
}

void initialize_number_generator(void)
{
    srand((unsigned)time(NULL));
}

int new_secret_number(void)
{
    return rand()%MAX_NUMBER + 1;
}

void read_guess(int secret_number)
{
    int guess, number_guess = 0;

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

}

10.3 程序块

程序块和复合语句

C语言里的程序块的一般格式为:

{多条声明 多条语句}

程序块都用于哪些场景?

  • 函数体;
  • 函数体内部,用于存放临时变量,比如例1展示的变量temp等;

程序块都有哪些性质?
默认情况下,

  • 在程序块内声明的一个变量是具有自动存储期限的:当进入该程序块时,分配存储单元;当退出该程序块时,解构该存储单元。
  • 在程序块内声明的静态变量具有静态存储期限。
  • 在程序块内声明的一个变量具有块作用域:该变量不能引用块外部的内容。

例1 程序块

if (i > j){
  int temp = i;
  i = j;
  j = temp;
}

10.4 作用域

在C语言中,相同的标识符可能有若干种不同的含义。

C语言的作用域规则使得程序员和编译器能判断在程序中的某个点该使用同名标识符的哪一种含义

最重要的作用域规则:
当程序块内的某条声明命名了一个标识符,如果该标识符已经是已可见时,则新的声明将临时隐藏旧的声明,直到程序块的末尾处。出了程序块,该标识符就恢复原来的含义

例1 作用域规则示例

int i;//声明1:i是一个全局变量,有文件作用域

void f(int i)//声明2:i是一个形式参数,具有块作用域
{
  i = 1;//这里的i使用的是声明2里的i的含义
}

void g(void)
{
  int i = 2;//声明3:i是一个局部非静态变量,具有块作用域;
  if (i > 0) {//这里的i使用的是声明3的i的含义
    int i;//声明4:i是一个局部非静态变量,具有块作用域

    i = 3; //这里的i使用的是声明4里的i的含义
  }
  
  i = 4;//这里的i使用的是声明3里的i的含义
}

void h(void)
{
  i = 5;//这里的i使用的是声明1里的i的含义
}

解释:

  • 声明2的i会暂时隐藏声明1的i的含义直到函数f的末尾处。出了函数fi就会恢复声明1的含义,因为在函数f里声明i时,由于此时i已经可见了(声明1里的i具有文件作用域)。
  • 声明3的i会暂时隐藏声明1的i的含义,直到函数g的末尾处,因为此时i已经可见(声明1里的i具有文件作用域),且声明2的i已经出了作用域范围。
  • 声明4的i会暂时隐藏声明3的含义,直到if语句的末尾处,因为声明3里的i已经可见。

相关文章

网友评论

    本文标题:第10章 C程序的组织形式

    本文链接:https://www.haomeiwen.com/subject/zwhuhhtx.html