第2週 ローカル変数とグローバル変数
INDEX

目標

  • グローバル変数とローカル変数を違いを区別できる。
  • グローバル変数とローカル変数を使い分けることができる。
  • グローバル変数を使わないでプログラムを作成できる。

予習・復習

 以下のスライドを利用して、予習と復習をしよう。復習では、自分の理解度を確認するために、実際にプログラムを作成し、意図する結果が得られるか確認しよう。
  1. ローカル変数とグローバル変数

本日の講義・演習予定

  1. グローバル変数とローカル変数
  2. 演習問題
  3. 提出課題
内 容
  1. ローカル変数とグローバル変数

ローカル変数とグローバル変数

同じ変数名を使う・ローカル変数とスコープ

 ある関数で定義した変数名を別の関数でも利用したいときがあります。例えば、答えを意味するansであったり、値を意味するvalueなどは多くの関数で利用される可能性がある変数名です。では、一つのプログラムの中で同じ変数名を利用することはできるのでしょうか。
 次のプログラムは、main関数で定義されている変数名aと同じ変数名が別の関数funcでも定義されている例です。どちらの変数も関数のコードブロック{}内で定義されています。このように、コードブロック内で定義される変数は、その有効範囲がそのコードブロック内に限定されます。このような変数をローカル変数(local variable)と呼びます。また、この有効範囲のことをスコープ(scope)と呼びます。
samename.c
#include <stdio.h>

void func(void);

int main(void)
{
	int a = 222;

	printf("main:a = %d\n", a);
	func();

	return 0;
}

void func(void)
{
	int a = 333;
	printf("func:a = %d\n", a);
}
このプログラムを実行して、それぞれローカル変数aが別の変数として扱われていることを確認しましょう。

ブロック内変数(ローカル変数)

 関数で定義されている変数名と同じ変数名がその中のコードブロック{}内で定義されている場合はどうなるでしょう。
 次のプログラムは、main関数で定義されているローカル変数aと同じ変数名が、同じ関数内のfor文のコードブロック内で定義されている例です。for文内で定義されている変数aのスコープはfor文のコードブロック内のみとなり、main関数で定義されている変数aよりも優先されます。
samename2.c
#include <stdio.h>

void func(void);

int main(void)
{
	int i;
	int a = 222;	//ローカル変数

	printf("main:a = %d\n", a);
	func();

	for(i=0; i<3; i++){
		int a = 444;	//ブロック内変数(ローカル変数)
		printf("for: a = %d\n",a);
	}
	return 0;
}

void func(void)
{
	int a = 333;	//ローカル変数
	printf("func:a = %d\n", a);
}

関数の引数はローカル変数

 関数のコードブロック内ではなく、その引数に同じ変数名が使われている場合はどうなるでしょうか。
 次のプログラムは関数func2の引数に、呼び出し側であるmain関数で定義されている変数と同じ変数名aが使われている例です。引数はその定義されている関数内でのみ有効となるローカル変数として扱われます。したがって、下記のプログラムを実行すると、main関数で呼び出された関数func2()は引数aで受け取った値555をローカル変数であるaとして表示していることが確認できます。
samename3.c
#include <stdio.h>

void func(void);
void func2(int a);

int main(void)
{
	int i;
	int a = 222;	//ローカル変数

	printf("main:a = %d\n", a);
	func();

	for(i=0; i<3; i++){
		int a = 444;	//ブロック内変数(ローカル変数)
		printf("for: a = %d\n",a);
	}

	func2(555);

	return 0;
}

void func(void)
{
	int a = 333;	//ローカル変数
	printf("func:a = %d\n", a);
}

void func2(int a){	//引数もローカル変数
	printf("func2:a = %d\n",a);
}

グローバル変数

 関数の外で宣言された変数をグローバル変数(global variable)と言います。ローカル変数のスコープがコードブロック内に限定されるのに対して、グローバル変数のスコープはプログラム全体(一つのファイル内)となります。
globaAndLocal.c
#include <stdio.h>

/* プロトタイプ宣言 */
void func1(void);
void func2(void);

int glb;		//グローバル変数

/* メイン関数 */
int main(void)
{
	int loc;	//ローカル変数
	
	glb = 999;
	loc = 888;
	 
	printf("main glb = %d¥n", glb);
	printf("main loc = %d¥n", loc);
	func1();
	func2();

	return 0;
}

/* ユーザ定義関数 */
void func1(void)
{
	int loc;	//ローカル変数
	
	loc = 111;
	
	printf("func1 glb = %d¥n", glb);
	printf("func1 loc = %d¥n", loc);
}

void func2(void)
{
	int loc;	//ローカル変数
	
	loc = 222;
	
	printf("func2 glb = %d¥n", glb);
	printf("func2 loc = %d¥n", loc);
}
 上記のプログラム例では、変数glbがグローバル変数で、変数locがローカル変数になります。ローカル変数locはmain関数、func1関数、func2関数の各関数で定義されていますが、それぞれ宣言された関数の中だけで有効となります。
  1. グローバル変数はプログラム全体を通して有効である。
  2. ローカル変数は宣言されたそのブロック内だけで有効である。
  3. グローバル変数とローカル変数が同一の名前だった場合は、そのブロック内で宣言されたローカル変数が優先される。
  4. 仮引数はローカル変数として扱われる。

グローバル変数は危険な変数

 グローバル変数はプログラムのどこからでも利用することができるため、大変都合が良い変数です。しかし、グローバル変数とローカル変数は適切に使い分けなければ、大きなトラブルの元となります。
  • グローバル変数と同じ名前をブロック内で付けてしまい、ローカル変数なのかグルーバル変数なのか区別せずに利用してしまう。
  • グローバル変数はプログラムのどこからでも書き換えが可能である。もし、間違った書き換えが行われた際、それがどこで生じたことなのかをプログラム全体から探し出すことは大変な作業になる。
グローバル変数は原則禁止!ほとんどの問題はローカル変数で対応できる!

expand_lessBack to TOP

演習問題

  1. 次のプログラムの実行結果を予測しなさい。
  2. ex9-1.c
    #include <stdio.h>
    
    int add0(void);
    int add2(int x, int y);
    
    int x=2, y=3;
    
    int main(void)
    {
    	int x=3,y=5,ans;
    
    	ans = add0();
    	printf("ans = %d\n", ans);
    	ans = add2(x,y);
    	printf("ans = %d\n", ans);
    
    	return 0;
    }
    
    int add0(void)
    {
    	int ans = x + y;
    	return ans;
    }
    
    int add2(int x, int y)
    {
    	int ans = x + y;
    	return ans;
    }
    

  3. 次のプログラムの実行結果を予測しなさい。
  4. ex9-2.c
    #include <stdio.h>
    
    void func1(int a);		//プロトタイプ宣言
    void func2(void);
    
    int a=10;		//グローバル変数
    int b=20;
    
    /* メイン関数 */
    int main(void)
    {
    	int a=0;
    	
    	printf("main_1 a = %d\n", a);
    	func1(a);
    	printf("main_2 a = %d\n", a);
    	func2();
    	printf("main_3 b = %d\n", b);
    
    	{				//ブロック
    		int a=30;
    		printf("main-loc a = %d\n", a);		
    	}
    	
    	return 0;
    }
    
    /* ユーザ定義関数 */
    void func1(int a)
    {
    	a += 1000;
    	printf("func1 a = %d\n", a);
    }
    
    void func2(void)
    {
    	b += 2000;
    	printf("func2 b = %d\n", b);
    }
    

  5. 次は、始まりと終わりの二つの整数を入力して、その間のすべての整数を4列で表示して、最後にその整数の個数も表示するプログラムです。正しく表示するように修正しなさい。ただし、入力する整数の範囲は0から99までとする。
  6. ex9-3.c
    #include <stdio.h>
    
    int count = 4;
    
    int main(void)
    {
    	int i, start,end,count=0;
    
    	printf("始まりの数>> ");	scanf("%d",&start);
    	printf("終わりの数>> ");	scanf("%d",&end);
    
    	for(i=start; i<=end; i++){
    		if(i!=0 && i%count==0) printf("\n");
    		printf("%3d",i);
    		count++;
    	}
    	printf("\n表示した整数の数 %d個\n", count);	
    	return 0;
    }
    

  7. 次は、点数を入力してその合計を求めるプログラムです。グローバル変数を使わないで正しい結果が得られるように変更しなさい。
  8. ex9-4.c
    #include <stdio.h>
    
    int sumof(void);
    
    int size,score[10];	//点数の数、点数
    
    int main(void)
    {
    	int i=0;
    
    	printf("入力する点数の個数(最大10個)>> ");
    	scanf("%d", &size);
    
    	while(i<size){
    		printf("点数>> ");
    		scanf("%d", &score[i]);
    		i++;	
    	}
    	printf("合計: %d\n",sumof());
    
    	return 0;
    }
    
    int sumof(void)
    {
    	int i,sum=0;
    
    	for(i=0; i<size; i++){
    		sum += score[i];
    	}
    	return sum;
    }
    

  9. 西暦と月を入力してその月の日数を返すプログラムを作成しなさい。ただし、西暦と月を引数としその月の日数を戻り値とする関数daysを定義し、利用すること。閏年についても考慮すること。(ヒント:あらかじめ各月の日数を配列{0,31,28,31,30,31,30,31,31,30,31,30,31}として用意しておく。この日数は定数なのでグローバル変数として宣言する。)

  10. 西暦年月日を入力して、その年の元旦(1月1日)からのその日までの日数を求めるプログラムを作成しなさい。ただし、西暦年月日を引数としその年の元旦(1月1日)からの日数を戻り値とする関数numberOfDaysを定義して利用すること。加えて、西暦年を引数として閏年ならば1をそうでなければ0を戻り値とする関数isLeapを定義して、利用すること。

  11. 次のデータ{78, 82, 65, 73, 88, 56, 90, 92, 70, 68}を対象として、順位を知りたい番号を入力してそのを順位を表示するプログラムを作成しなさい。ただし、順位を調べる関数ranking()を定義して利用すること。また、グローバル変数を使わずに実現すること。順位は一番大きな値を1位として降順とすること。(考え方:自身の順位を1位として、自身よりも大きな値があった場合は1を加える)

  12. 次のデータ{78, 82, 65, 73, 88, 56, 90, 92, 70, 68}を対象として、各値の順位を表示するプログラムを作成しなさい。ただし、各データの順位を求める関数ranking()を定義して利用すること。また、グローバル変数を使わずに実現すること。順位は一番大きな値を1位として降順とすること。


演習問題解答

OPEN ANSWER
  1. 回答省略

  2. 回答省略

  3. 回答例 7,15,17行目のローカル変数名count、もしくは3,13行目のグローバル変数名countのどちらかを別の名前に変更する。13行目のif文の条件を (i-start)!=0 && (i-start)%count==0 へ修正

  4. 回答例 ex2-4.c
  5. code/2/ex2-4.c

  6. 回答例 ex2-8.c
  7. code/2/ex2-8.c

expand_lessBack to TOP

確認テスト


OPEN ANSWER
1.
(1) 6 5 10
(2) 10 50 10 20
(3)
Local | Global
S01 =  1,  1
S02 =  3,  3
S03 =  6,  6
S04 = 10, 10
S05 = 15, 15
S06 = 21, 21
S07 = 28, 28
S08 = 36, 36
S09 = 45, 45
S10 = 55, 55
							
(4)
func1: m[] = {2, 20}
func1: x[] = {1, 2}
main : x[] = {2, 20}
func2: m[] = {2, 20}
func2: x[] = {20, 200}
main : x[] = {2, 20}
							

本日の提出課題

以下の各プログラムを作成しなさい。各問題に記載されている要件を満たすこと。
  • 課題1
  • 繰り返し整数を入力して、その合計を求めるプログラム。ただし以下の要件を満たすこと。
    (要件)
    • 整数9999が入力されると入力を終了する。整数9999は合計の計算には含めない。
    • 合計値を格納する変数はグローバル変数として定義する。
    • 合計値は、加える整数を引数aとして戻り値を持たない関数add(int a)を定義して利用すること。
    • 最後に合計値と関数add()の呼び出し回数を表示する
    • 配列は利用しないこと。
    (実行例: ideone.comの場合)
    --- stdin ---
    1 2 3 4 5 6 9999
    
    --- stdout ---
    呼び出し回数: 6回 合計: 21
    			

  • 課題2
  • 繰り返し整数を入力して、入力されるたびにそれまで入力された整数の平均値(小数点数)を表示するプログラム。ただし、以下の要件を満たすこと。
    (要件)
    • 整数9999が入力されると入力を終了する。整数9999は平均の計算には含めない。
    • 入力値を引数aとして、入力された全ての整数に対する平均値を戻り値とする関数avgup(int a)を定義して利用すること。
    • 合計値を格納する変数はグローバル変数として定義する。
    • 配列は利用しないこと。
    (実行例: ideone.comの場合)
    --- stdin ---
    1 2 3 4 5 9999
    
    --- stdout ---
    1.0
    1.5
    2.0
    2.5
    3.0
    			

  • 課題3
  • 西暦年と月を入力してその月の日数を返すプログラムを作成しなさい。ただし、西暦年yと月mを引数としその月の日数を戻り値とする関数days(int y, int m)と、西暦年yを引数として閏年ならば1をそうでなければ0を戻り値とする関数is_leap(int y)を定義して利用すること。
    • ヒント1:
      あらかじめ各月の日数で初期化した配列{0,31,28,31,30,31,30,31,31,30,31,30,31}をグローバル変数として宣言する。
    • ヒント2:
      閏年の判定条件は、西暦年が4で割り切れて100で割り切れない、もしくは400で割り切れる
    (実行例: ideone.comの場合)
    --- stdin ---
    2020/2
    
    --- stdout ---
    29日
    

  • 課題4(発展課題)
  • 入力する整数の個数nとn個の整数を入力した後、何番目の整数かを入力してその整数が大きい方から何番目の整数かを表示するプログラム。ただし、以下の要件を満たすこと。
    (要件)
    • 整数の配列a[]とその要素数nと順位を知りたい要素の位置idxを引数として、大きい順の順位を戻り値とする関数rank(int a[], int n, int idx)を定義して利用する。一番大きな値の順位を1番とする。
    • グローバル変数は使わない。
    (ヒント) 順位は最初に自身の順位を1位として、自身よりも大きな値があった場合は1を加えることで求めることができる。
    (実行例: ideone.comの場合)
    --- stdin ---
    10
    78 82 65 73 88 56 90 92 70 68
    6
      	
    --- stdout ---
    順位 2
    

  • 課題5(発展課題)
  • 西暦年と月日を入力して、その年の元旦(1月1日)からその日までの日数を求めるプログラムを作成しなさい。ただし、以下の要件を満たすこと。
    (要件)
    • 西暦年y、月m、日dをを引数としてその年の元旦からの日数を関数numberofdays(int y, int m, int d)を定義して利用する。
    • 西暦年yを引数として閏年ならば1をそうでなければ0を戻り値とする関数is_leap(int y)を定義して利用する。
    (ヒント)課題3を参照のこと
    (実行例: ideone.comの場合)
    --- stdin ---
    2021/1/1
    
    --- stdout ---
    0日
    			
 n個の整数値の平均と標準偏差を求めるプログラムをローカル変数のみで実現するプログラムkadai2.cを作成しなさい。但し、以下の要件を満たすこと。
[要件]
    1. 下記のソースコードに修正、追加・変更することでプログラムを完成させなさい。
    2. 関数averageはデータとデータ数を受け取り、平均値を戻り値とする関数へ変更します
    3. 関数sdeviationはデータとデータ数と平均を受け取り、標準偏差を戻り値とする関数へ変更します。
    4. 平均と標準偏差の値は小数点を含む値として取り扱います。
    5. 平方根の計算には標準関数sqrtが利用できるものとします。

[ソースコード]
#inlude <stdio.h>
#inlude <math.h>

int data[] = {80,70,60,95,55,75,90,85,50,65};	//データ
int n = 10;				// データ数
double avg, sdev;			//平均値、標準偏差

// プロトタイプ宣言
void average(void);
void sdeviation(void);

/* 平均 */
void average(void)
{
	// 省略
}
/* 標準偏差 */
void sdeviation(void)
{
	// 省略
}

int main(void)
{
	average();
	sdeviation();

	printf("平均値  : %f\n", avg);
	printf("標準偏差: %f\n", sdev);
	return 0;
}

[参考]
平均 \[ \mu_{x} = \frac{1}{n} \sum_{i=1}^{n}x_{i} \] 標準偏差 \[ \sigma = \sqrt{\frac{1}{n}\sum_{i=1}^{n} (x_{i} - \mu_{x})^2} \]
  • [提出方法]
    1. ideone.comを利用して作成した課題プログラムのURLをCoursePowerへ提出のこと。
    2. 提出するideone.comの課題プログラムには、実行結果が表示されていること。
    3. プログラムの正当性についてどのようにして検証したか簡単に記すこと。
    4. 詳細はスライド「ideoneの使い方」参照のこと。

  • [評価について]
  • プログラムの内容の評価とは別に以下の要件を満たすことを評価の前提とする。
    1. C言語で記述されていること。
    2. ソースコードファイルのコンパイル結果に、ワーニングやエラーが出力されていないこと。
    3. 所定の実行結果が得られるていること。
    4. ソースコードは、インデントや適当な改行が施された見やすい状態であること。

  • [提出期限]
  • 2023年 6月26日(月)までとする。ただし、以降の提出も受け付ける。