第12章の1 有効範囲

1. 記憶クラスとは

第11章でユーザ関数についての説明をしましたので、今度は関数間での変数の扱いについて説明したいと思います。
いわゆる「記憶クラス」というものなのですが、初心者にとってはわかりづらいものかもしれません。
このホームページでは分割コンパイルは扱わない予定になっておりますので、初心者がユーザ関数を同一ファイルで扱うときに必要となる記憶クラスのみを説明することにします。

2. 有効範囲

「有効範囲」とはその変数を参照できる範囲のことです。 C言語では、変数の宣言をソースプログラムのどこに書くかによって「ローカル変数」と「グローバル変数」に分けられ、この有効範囲が異なってきます。
この有効範囲は「記憶クラス」と密接な関係がありますので、まずは有効範囲から理解してください。

(1) ローカル変数(局所変数)

  • 関数内で定義され、その関数内でのみ使用できます。
  • 複数の関数が同一の変数名を用いたとしても衝突しません。

※ 前章までの学習で使用していた変数はすべてこのローカル変数に該当します。

(2) グローバル変数(広域変数)

  • 関数外で定義され、定義以降のどの関数からでも使用できます。
  • システムでグローバル変数としては1つの定義しか許されません。

※ 仮にある関数のローカル変数にグローバル変数と同一の名前が存在するときにはその関数内ではローカル変数が優先します。

(3) ローカル変数とグローバル変数の有効範囲

  • main関数の「a」 と goukei関数の「a」はそれぞれの関数で宣言された「ローカル変数」です。そのため、名前は同一でも別の変数となり、有効範囲も宣言された関数の中のみとなります。
  • 「グローバル変数」はどの関数からも参照できるので、一見便利そうですが、変数の衝突が起こりやすく、どこで変数の値が変えられるかわかりにくいため、安全性が低くくなります。ですから、グローバル変数をあえて使うのは以下にとどめ、通常は「ローカル変数」を用いて、関数間でのやり取りは「引数」を使うようにしましょう。
  • グローバル変数が適する変数
    • プログラム全体を統括する変数
    • プログラム全体の状況を記憶する変数

〇 演習問題

問1

以下のプログラムはグローバル変数に定義され、初期化された 10個の int型データの合計、平均、分散、標準偏差を求めて表示するプログラムである。
このプログラムを以下のような関数を用いて書き換えなさい。

return型 関数名 引数 機能
int get_goukei なし 合計を求めて返却する
double get_bunsan 平均値 分散を求めて返却する

なお、n件のデータの分散と標準偏差を求める式は以下である。

  分散 = { (要素0 – 平均値)2 + (要素1 – 平均値)2 +・・・+
      (要素n-2 – 平均値)2 + (要素n-1 – 平均値)2 } / n
  標準偏差 = √分散

#include <stdio.h>
#include <math.h>

#define N 10
int data[N] = {80, 76, 59, 87, 66, 54, 40, 78, 94, 61};

int main(void)
{
    double heikin, bunsan = 0.0, hensa;
    int goukei = 0;
    
    for (int i = 0; i < N; i++) {
        goukei = goukei + data[i];
    }
    
    heikin = (double) goukei / N;

    for (int i = 0; i < N; i++) {
        bunsan = bunsan + ( data[i] - heikin ) * ( data[i] - heikin );
    }
    bunsan = bunsan / N;

    hensa = sqrt(bunsan);
    
    printf("合計点  : %d\n", goukei);
    printf("平均点  : %f\n", heikin);
    printf("分散   : %f\n", bunsan);
    printf("標準偏差 : %f\n", hensa);
    
    return 0;
}

問2

【問1】のグローバル変数を main()関数のローカル変数に書き換えてプログラムを作り直しなさい。get_goukei() と get_bunsan() の return型と引数は都合のよいように書き換えなさい。

【問1と問2の実行結果例】
合計点  : 695
平均点  : 69.500000
分散   : 243.650000
標準偏差 : 15.609292

解答例

// 問1
#include <stdio.h>
#include <math.h>

#define N 10
int data[N] = {80, 76, 59, 87, 66, 54, 40, 78, 94, 61};

int get_goukei( void );
double get_bunsan( double avg );

int main(void)
{
    double heikin, bunsan, hensa;
    int goukei;
    
    goukei = get_goukei();
    heikin = (double) goukei / N;
    bunsan = get_bunsan(heikin);
    hensa = sqrt(bunsan);
    
    printf("合計点  : %d\n", goukei);
    printf("平均点  : %f\n", heikin);
    printf("分散   : %f\n", bunsan);
    printf("標準偏差 : %f\n", hensa);
    
    return 0;
}

/***
配列の合計を求める関数
引数:なし
返却値:合計値
***/
int get_goukei( void )
{
    int sum = 0;
    for (int i = 0; i < N; i++) {
        sum = sum + data[i];
    }
    return sum;
}

/***
分散を求める関数
引数:double avg;    配列要素の平均値
返却値:分散
***/
double get_bunsan(double avg)
{
    double disp = 0.0;

    for (int i = 0; i < N; i++) {
        disp += ((data[i] - avg) * (data[i] - avg));
    }
    return disp / N;
}
// 問2
#include <stdio.h>
#include <math.h>

#define N 10

int get_goukei(int *p);
double get_bunsan(int *p, double avg);

int main(void)
{
    double heikin, bunsan, hensa;
    int goukei;
    int data[N] = {80, 76, 59, 87, 66, 54, 40, 78, 94, 61};

    goukei = get_goukei(data);
    heikin = (double) goukei / N;
    bunsan = get_bunsan(data, heikin);
    hensa = sqrt(bunsan);
    
    printf("合計点  : %d\n", goukei);
    printf("平均点  : %f\n", heikin);
    printf("分散   : %f\n", bunsan);
    printf("標準偏差 : %f\n", hensa);
    
    return 0;
}

/***
配列の合計を求める関数
引数:int *p;    配列の先頭要素へのポインタ
返却値:合計値
***/
int get_goukei(int *p)
{
    int sum = 0;
    for (int i = 0; i < N; i++) {
        sum = sum + *p;
        p++;
    }
    return sum;
}

/***
分散を求める関数
引数:int *p;        配列の先頭要素へのポインタ
      double avg;    配列要素の平均値
返却値:分散
***/
double get_bunsan(int *p, double avg)
{
    double disp = 0.0;

    for (int i = 0; i < N; i++) {
        disp += ((*p - avg) * (*p - avg));
        p++;
    }
    return disp / N;
}

グローバル変数を用いて各関数共通で配列を参照している「問1」と、 ローカル変数を用いて引数で配列のアドレスを渡している「問2」では、 「問2」の方が関数の独立性が高く、配列data も安全なものになっています。

コメント