6.変数の取り扱い

Copyright(C) 8Feb2003 coskx

このページでは変数の取り扱いについて解説している。
ここで修得してほしい内容は以下の通りである。
1.代入による変数の型変換に付いて理解する。
2.混合演算における,型についての注意点,自動型変換のルール,明示的な型変換について理解する。
3.グローバル変数とオート変数の振る舞いの違いを理解する。
4.関数内のstatic変数の振る舞いについて理解する。


 6.1 代入による変数の型変換

変数の型を変換する1つの方法は代入である。次のプログラムではint型からdouble型に変換している。int型からdouble型に代入して型変換を行なう時,値のとりうる範囲はint型よりdouble型の方が広いので問題なく行なえる。 変数について補足説明

List 6.1.1 代入による型変換

#include <stdio.h>

int main()
{
    int a=3;
    double p;
    p=a;
    printf("a=%d p=%.2f\n",a,p);
    return 0;
}

/******* 実行結果 ********
a=3 p=3.00
**************************/

次の例はdouble型からint型への代入による型変換を行なっているも のである。値のとりうる範囲の広さが逆の関係になるので,うまく変換できないことがあり,この点についてコンパイラが警告(warning)を発生するこ とがある。(同様なことがchar型からint型への変換とその逆向き変換の場合にも生ずる。)またint型に変換すると小数点以下が失われるが,実行結 果よりわかるように,これは四捨五入ではなく切り捨てになる。

List 6.1.2 代入による型変換

#include <stdio.h>

int main()
{
    double p;
    int a;
    p=3.14159;
    a=p;
    printf("p=%.3f a=%d\n",p,a);
    p=-p;
    a=p;
    printf("p=%.3f a=%d\n",p,a);
    p=2.718;
    a=p;
    printf("p=%.3f a=%d\n",p,a);
    p=-p;
    a=p;
    printf("p=%.3f a=%d\n",p,a);
    return 0;
}

/******* 実行結果 ********
p=3.142 a=3
p=-3.142 a=-3
p=2.718 a=2
p=-2.718 a=-2
**************************/

 

 6.2 混合演算と明示的な変数の型変換

型の異なる変数同士の演算においては次の規則による。

混合演算

C言語での解釈

int型とdouble型の混合演算

int型をdouble型に変換してから演算

int a=3,c;
double p=1.5,q;
の時
q=a+p;  →qは4.5になる。
c=a+p; →演算では4.5になるが,
     代入時に小数点以下が失われてcは4になる。
int型とchar型の混合演算 char型をint型に変換してから演算 int a=3,c;
char b=5;
の時
c=a+b; →cは8になる。

一般に異なる型の変数間の混合演算では,劣勢な方の型は優勢な方の型に変換されてから,演算が行なわれると考えてよい。(実際は少し違うが,結果としてこのように考えてよい)型の優勢,劣勢は次のようになっている。

劣勢 char型(1byte) < short int型(2byte) < long int型(4byte) < float型(4byte) < double型(8byte) 優勢
(同じバイト数の型の場合 劣勢 signed < unsigned 優勢 となっている)

数式内で型変換を確実にプログラマが行ないたい場合は,型キャストを行なう。型キャストは変数の前にかっこを付け,カッコの中に型名を書く。

例 int a=7,b=3; double p; の時

C言語での記述

C言語での解釈

p = a / b; 「a/b」が整数演算の割り算なので小数点以下が失われて,「a/b」は整数の2になる。
これをdouble型変数pに代入してもpには2.000が代入されるだけである。
p = (double)a / (double)b; (double)aのところはdouble型7.00と解釈され,
(double)bのところはdouble型3.00と解釈される。
「(double)a / (double)b」は2.333333になる。
これをdouble型変数pに代入するのでpには2.33333が代入される。
p = a / (double)b; (double)bのところはdouble型3.00と解釈される。
そのためint型とdouble型の混合演算と解釈され,aもdouble型に変換され,
「p = a / (double)b;」は2.333333になる。
これをdouble型変数pに代入するのでpには2.33333が代入される。
p = (double)a / b; 上の例と同様にpには2.33333が代入される。
p = ( a + b + 6 ) / (double)b (a+b+6)の部分が先に演算される。(a+b+6)は整数演算が行なわれ,16になる。次の段階で16/3.0が行なわれる直前に型変換が行なわれ,16.0/3.0が演算されることになる。
pには5.333333が代入される。

定数の場合は小数点の有無で整数型と実数型に区別される。
例えば「5」は整数(int型)とみなされ,「5.0」(または「5.」)は実数型(double型)とみなされる。

例 double x; の時

C言語での記述

C言語での解釈

x=3/4; 整数同士の整数演算なので「3/4」は0になり,double型変数xには0.00が代入される。
x=3.0/4.0;
x=3./4.;
実数同士の実数演算なので「3.0/4.0」は0.75になり,double型変数xには0.75が代入される。
x=3/4.0;
x=3/4.;
x=3.0/4;
x=3./4;
実数同士の実数演算に自動的に変換されるので「3.0/4.0」となり,double型変数xには0.75が代入される。
x=(1+2)/4.;
x=(1+2)/4.0;
先に1+2が行なわれ,3が得られる。次に3/4.0が演算される直前に実数同士の実数演算に自動的に変換されるので「3.0/4.0」となり,double型変数xには0.75が代入される。

x=(1+2.0)/4.0;
x=(1.0+2)/4.0;

先に()中が実数同士の足し算に変換され,和は3.0になる。次に「3.0/4.0」が計算され,double型変数xには0.75が代入される。

練習6.1

次のプログラムの実行結果を予想しなさい。
List 6.2.1

#include <stdio.h>

int main()
{
    int i;
    double x=-1.5;
    int k;
    for (i=0;i<4;i++) {
        k=(int)(x+i);
        printf("i=%d k=%d\n",i,k);
    }
    return 0;
}

練習6.2

次のプログラム(三角形の面積を求めようとしている)の実行結果を予想しなさい。
List 6.2.2

#include <stdio.h>

int main()
{
    double base=20.0; /*底辺[cm]*/
    double height=10.0; /*高さ[cm]*/
    double area; /*面積[cm2]*/
    printf("base=%.1f height=%.1f\n",base,height);
    area=1/2*base*height;
    printf("(1) area=%.1f\n",area);
    area=(1/2)*base*height;
    printf("(2) area=%.1f\n",area);
    area=(1./2.)*base*height;
    printf("(3) area=%.1f\n",area);
    area=(1./2)*base*height;
    printf("(4) area=%.1f\n",area);
    area=base*height*1/2;
    printf("(5) area=%.1f\n",area);
    area=base*height*1.0/2.0;
    printf("(6) area=%.1f\n",area);
    area=base*height*(1/2);
    printf("(7) area=%.1f\n",area);
    return 0;
}

 6.3 グローバル変数とオート変数

これまでの変数はmain()(実はmain()も関数の1つである)や関 数の中で用いられていた。main()内部の変数は他の関数内部からは見ることが出来ないし,(引数で渡された場合のみ値を知ることが出来る)他の関数内 部の変数はmain()内部から見ることが出来なかった。引数で渡さない変数で,main()やすべての関数から見える変数を作ることが出来る。これがグローバル変数である。

次 のプログラムはグローバル変数テスト用である。グローバル変数はint型の watchme と seeme の2つである。このプログラムの動作を追跡すると,グローバル変数はmain(),functionA(),functionB()のすべてから見えてい て,その値の変更も各関数から出来ていることがわかる。グローバル変数は,そのグローバル変数を使用する関数より前で,関数の外部に定義する。

ただし,グローバル変数を利用すると関数の独立性が損なわれ,その関数を他のプログラムに流用できなくなることに注意しよう。

なお,これまで使用してきた関数内の変数はオート変数と呼ばれる。

オート変数が宣言される時に値(初期値)が代入されているように記述されている時は,その関数が実行されるたびに初期値の代入(これは「変数の初期化」と呼ばれる)が行なわれるが,グローバル変数では宣言される時に値(初期値)が代入されているように記述されている時は,プログラム起動時に一度だけ初期値の代入(変数の初期化)が行なわれる。

List 6.3.1 グローバル変数テスト

#include <stdio.h>

int watchme=0; /*ここに宣言されるのがグローバル変数*/
int seeme;     /*ここに宣言されるのがグローバル変数*/

void functionA(void) /*引数無し,返す値無しの関数functionA*/
{
    printf("functionA watchme=%d seeme=%d\n",watchme,seeme);
    watchme++; seeme++;
}

void functionB(void) /*引数無し,返す値無しの関数functionB*/
{
    printf("functionB watchme=%d seeme=%d\n",watchme,seeme);
    watchme++; seeme++;
    functionA();
}

int main()
{
    seeme=0;
    printf("main      watchme=%d seeme=%d\n",watchme,seeme);
    watchme++; seeme++;
    functionA();
    functionB();
    functionA();
    printf("main      watchme=%d seeme=%d\n",watchme,seeme);
    return 0;
}

/******* 実行結果 ********
main      watchme=0 seeme=0
functionA watchme=1 seeme=1
functionB watchme=2 seeme=2
functionA watchme=3 seeme=3
functionA watchme=4 seeme=4
main      watchme=5 seeme=5
**************************/

もしグローバル変数と同じ名前のオート変数が関数内部で宣言されていると,その関数内からはグローバル変数が見えなくなる。
これを変数の「スコープ規制」という。次のプログラム例では,グローバル変数 watchme と seeme が使われているが,functonAでオート変数watchmeがあるので,functionA中からはグローバル変数のwatchmeが見えない。プログラムを追跡すること。

List 6.3.2 グローバル変数と同じ名前のオート変数がある場合

#include <stdio.h>

int watchme=0;  /*グローバル変数のwatchme*/
int seeme;

void functionA(void) /*引数無し,返す値無しの関数functionA*/
{
    int watchme=1000;  /*オート変数のwatchme*/
    printf("functionA watchme=%d seeme=%d\n",watchme,seeme);
    watchme++; seeme++;
}

void functionB(void) /*引数無し,返す値無しの関数functionB*/
{
    printf("functionB watchme=%d seeme=%d\n",watchme,seeme);
    watchme++; seeme++;
    functionA();
}

int main()
{
    seeme=0;
    printf("main      watchme=%d seeme=%d\n",watchme,seeme);
    watchme++; seeme++;
    functionA();
    functionB();
    functionA();
    printf("main      watchme=%d seeme=%d\n",watchme,seeme);
    return 0;
}

/******* 実行結果 ********
main      watchme=0 seeme=0
functionA watchme=1000 seeme=1
functionB watchme=1 seeme=2
functionA watchme=1000 seeme=3
functionA watchme=1000 seeme=4
main      watchme=2 seeme=5
**************************/

時間の経過

 →   →    →    →    →    →

処理

プログラム
起動時

main

functionA

functionB

functionA

functionA

main

グローバル変数
watchme

0

0を表示
0→1

1だが
見えない

1を表示
1→2

2だが
見えない

2だが
見えない

2を表示

オート変数
watchme

存在しない

存在しない

1000を表示
1000→1001

存在しない

1000を表示
1000→1001

1000を表示
1000→1001

存在しない

グローバル変数
seeme

値は不定

0を表示
0→1

1を表示
1→2

2を表示
2→3

3を表示
3→4

4を表示
4→5

5を表示



 6.4 オート変数とstatic変数

同じ関数が何回も呼び出される場合,2回目以降の呼び出しの時,前回の関数 処理終了時の変数の値を覚えていてほしいことがある。このような場合用いられるのがstatic変数である。static変数を使う時は,変数宣言の時に 「static」を付け加えればよい。static変数の宣言時に値を代入する記述(変数の初期化)はプログラム起動時に1回だけ行なわれる。

次の例はstatic変数のテストプログラムである。

List 6.4.1 static変数のテストプログラム
左側のプログラムでは変数watchmeがstatic変数だが,右側
のプログラムの変数watchmeはオート変数

#include <stdio.h>

/*引数無し,返す値無しの関数function*/
void function(void)
{
    static int watchme=0;
    printf("watchme=%d ",watchme);
    switch (watchme) {
    case 0:
        printf("Hello world.\n");
        break;
    case 1:
        printf("Morning.\n");
        break;
    case 2:
        printf("Yes.\n");
        break;
    default:
        printf("too many!\n");
        break;
    }
    watchme++;
}

int main()
{
    function();
    function();
    function();
    function();
    function();
    return 0;
}

/******* 実行結果 ********
watchme=0 Hello world.
watchme=1 Morning.
watchme=2 Yes.
watchme=3 too many!
watchme=4 too many!
**************************/

#include <stdio.h>

/*引数無し,返す値無しの関数function*/
void function(void)
{
    int watchme=0;
    printf("watchme=%d ",watchme);
    switch (watchme) {
    case 0:
        printf("Hello world.\n");
        break;
    case 1:
        printf("Morning.\n");
        break;
    case 2:
        printf("Yes.\n");
        break;
    default:
        printf("too many!\n");
        break;
    }
    watchme++;
}

int main()
{
    function();
    function();
    function();
    function();
    function();
    return 0;
}

/******* 実行結果 ********
watchme=0 Hello world.
watchme=0 Hello world.
watchme=0 Hello world.
watchme=0 Hello world.
watchme=0 Hello world.
**************************/

次のプログラムは,static変数を用いた関数で平均値を求めようとするものである。
関数にデータを与えるたびに,これまでに与えたデータの和とデータの個数が関数内部のstatic変数に保存される。関数はその時得られている平均値を返すようになっている。

List 6.4.2 平均値を与える関数

#include <stdio.h>

/*dataを入力するたびにそれまでの入力データの平均値を出力する関数*/
double averager(double data)
{
    static double sum=0.0;
    static int counter=0;
    double average;
    sum+=data;
    counter++;
    average=sum/counter;
    return average;
}

int main()
{
    double weight[10]={
        62.4, 58.6, 53.5, 75.4, 64.5,
        83.6, 57.6, 58.0, 63.4, 52.4
    };
    int i;
    double average;
    for (i=0;i<10;i++) {
        average=averager(weight[i]);
        printf("data=%.1f average=%.2f\n",weight[i],average);
    }
    return 0;
}

/******* 実行結果 ********
data=62.4 average=62.40
data=58.6 average=60.50
data=53.5 average=58.17
data=75.4 average=62.48
data=64.5 average=62.88
data=83.6 average=66.33
data=57.6 average=65.09
data=58.0 average=64.20
data=63.4 average=64.11
data=52.4 average=62.94
**************************/


次のプログラムは,素数を判定しながら,素数であったら表示し,5つの素数を表示する毎に改行するものである。
5つの素数毎に改行するという作業をmain()で行うのか,関数におまかせするのかの違いだが,関数におまかせするほうが,main()のプログラムが楽になる。関数側は,改行するかしないかを考えるための変数をstatic変数として持つことが求められる。

List 6.4.3 static変数のテストプログラム
左のプログラム 
5つの素数を表示する毎の改行をmain()で行っている
右のプログラム 
5つの素数を表示する毎の改行をprint5f()で行っている

#include <stdio.h>

/*与えられた整数numberが素数だったら1,そうでなかったら0を返す関数*/
int isPrimenumber(int number)
{
    int j,ret;
    j=2;
    while (number%j!=0) j++;
    if (j==number) ret=1;/*素数だった*/
    else ret=0; /*素数ではなかった*/
    return ret;
}

int main()
{
    int j,counter;
    counter=0;
    for (j=10000 ;j<=10100; j++) {
        if (isPrimenumber(j)) {
            counter++;
            printf("%8d",j);
            if (counter%5==0) putchar('\n');
        }
    }
    if (counter%5!=0) putchar('\n');
    printf("%d個見つかりました\n",counter);
    return 0;
}

/**************実行結果******************
   10007   10009   10037   10039   10061
   10067   10069   10079   10091   10093
   10099
11個見つかりました
****************************************/

#include <stdio.h>

/*与えられた整数numberが素数だったら1,そうでなかったら0を返す関数*/
int isPrimenumber(int number)
{
    int j,ret;
    j=2;
    while (number%j!=0) j++;
    if (j==number) ret=1;/*素数だった*/
    else ret=0; /*素数ではなかった*/
    return ret;
}

/*引数valueの値を表示し,5回表示毎に改行する関数*/
int print5f(int value)
{
    static int cnt=0;
    int rtlf=1;  /*改行を行なったら0,そうでなかったら1*/
    printf("%8d",value);
    cnt++;
    if (cnt%5==0) {
        putchar('\n');
        rtlf=0;
    }
    return rtlf;
}

int main()
{
    int j,counter;
    int requestrtlf; /*改行要求*/
    counter=0;
    for (j=10000 ;j<=10100; j++) {
        if (isPrimenumber(j)) {
            counter++;
            requestrtlf=print5f(j);
        }
    }
    if (requestrtlf==1) putchar('\n');
    /*↑print5f内で改行したばかりのときは改行しない*/
    printf("%d個見つかりました\n",counter);
    return 0;
}

/**************実行結果******************
   10007   10009   10037   10039   10061
   10067   10069   10079   10091   10093
   10099
11個見つかりました
****************************************/


次のプログラムは擬似乱数発生関数を作ったところである。大きな数に別の大きな数を掛け,ある数を加えて,32768で割ったあまりを乱数として関数の返す値としている。なお18397と35977は素数を選んでいる。この関数 は標準ライブラリ関数のrand()と同じ0?32767の範囲の乱数を発生する。

List 6.4.4 乱数発生関数

#include <stdio.h>

int myrand(void)
{
    static unsigned int rnd=12345;
    rnd=18397*rnd+35977;
    rnd%=32768;
    return (int)rnd;
}

int main()
{
    int i;
    for(i=0;i<10;i++) {
        printf("%6d\n",myrand());
    }
    return 0;
}

/***実行結果***
 31934
 28303
  9980
  6165
 10666
 10827
 23624
 11953
 29270
  6855
***************/

#include <stdio.h>

int myrand(void)
{
    static unsigned int rnd=12345;
    rnd=18397*rnd+35977;
    rnd%=32768;
    return (int)rnd;
}

int main()
{
    int i;
    int one=myrand();
    int two;
    for(i=0;i<40000;i++) {
        two=myrand();
        if (one==two) {
            printf("%6d\n",i);
            break;
        }
    }
    return 0;
}

/**実行結果**
 32767
***コメント**
実行結果が32767になったということは
乱数の周期が32768ということで,
最善の擬似乱数発生を行なっていること
が検証された
*************/

練習6.3

次のプログラムの実行結果を予想しなさい。
List 6.4.5

#include <stdio.h>

int watchme=0;
int seeme;

void functionA(void)
{
    static int watchme=1000;
    printf("functionA watchme=%d seeme=%d\n",watchme,seeme);
    watchme++; seeme++;
}

void functionB(void)
{
    int watchme=1000;
    printf("functionB watchme=%d seeme=%d\n",watchme,seeme);
    watchme++; seeme++;
    functionA();
}

int main()
{
    seeme=0;
    printf("main      watchme=%d seeme=%d\n",watchme,seeme);
    watchme++; seeme++;
    functionA();
    functionB();
    functionA();
    functionB();
    printf("main      watchme=%d seeme=%d\n",watchme,seeme);
    return 0;
}

 課題6

次の仕様の関数を作り,与えられた検証プログラムで検証する。
ただし,先に次のヒントを読みなさい。 ヒント
(1)関数int delay2(int var)は1つのint型の引数varを持ち,関数の返す値は,2回前にこの関数が呼ばれた時のvarである。ただし,最初の2回の呼び出し時の返す値は0とする。 関数deley2()中で大きな配列を使ってはならない。  (p06ex01.c)

List (1)検証用プログラム

#include <stdio.h>

int delay2(int var)
{
    ここに入るプログラムを作成する
}

int myrand(void)
{
    static unsigned int rnd=12345;
    rnd=18397*rnd+35977;
    rnd%=32768;
    return (int)rnd;
}

int main()
{
    int i;
    int x,y;
    for(i=0;i<100000;i++) {
        x=myrand();
        y=delay2(x);
        if (i<10 || 99990<i) printf("%8d x=%6d y=%6d\n",i,x,y);
    }
    return 0;
}

/****実行結果例****
       0 x= 31934 y=     0
       1 x= 28303 y=     0
       2 x=  9980 y= 31934
       3 x=  6165 y= 28303
       4 x= 10666 y=  9980
       5 x= 10827 y=  6165
       6 x= 23624 y= 10666
       7 x= 11953 y= 10827
       8 x= 29270 y= 23624
       9 x=  6855 y= 11953
   99991 x= 26145 y=  3643
   99992 x= 24070 y= 12920
   99993 x= 25015 y= 26145
   99994 x= 10372 y= 24070
   99995 x=  8829 y= 25015
   99996 x= 32114 y= 10372
   99997 x= 30195 y=  8829
   99998 x= 17488 y= 32114
   99999 x= 13721 y= 30195
*******************/

(2)関数int delay20(int var)は1つのint型の引数varを持ち,関数の返す値は,20回前にこの関数が呼ばれた時のvarである。ただし,最初の20回の呼び出し時の返す値は0とする。   (p06ex02.c)

List (2)検証用プログラム

#include <stdio.h>

int delay20(int var)
{
    ここに入るプログラムを作成する
}

int myrand(void)
{
    static unsigned int rnd=12345;
    rnd=18397*rnd+35977;
    rnd%=32768;
    return (int)rnd;
}

int main()
{
    int i;
    int x,y;
    for(i=0;i<25;i++) {
        x=myrand();
        y=delay20(x);
        printf("%8d x=%6d y=%6d\n",i,x,y);
    }
    return 0;
}

/****実行結果例****
       0 x= 31934 y=     0
       1 x= 28303 y=     0
       2 x=  9980 y=     0
       3 x=  6165 y=     0
       4 x= 10666 y=     0
       5 x= 10827 y=     0
       6 x= 23624 y=     0
       7 x= 11953 y=     0
       8 x= 29270 y=     0
       9 x=  6855 y=     0
      10 x= 23380 y=     0
      11 x= 12301 y=     0
      12 x=  8898 y=     0
      13 x= 23555 y=     0
      14 x= 20512 y=     0
      15 x=  6185 y=     0
      16 x= 18158 y=     0
      17 x= 18943 y=     0
      18 x=  9900 y=     0
      19 x=  8965 y=     0
      20 x= 10970 y= 31934
      21 x=   187 y= 28303
      22 x=  2808 y=  9980
      23 x= 19617 y=  6165
      24 x= 23174 y= 10666
*******************/


(3)関数double movingAverage(int var)は1つのint型の引数varを持ち,関数の返す値は,現在のvarを含み,最新の4回の呼び出し時に与えられたvarの平均値を返すものとする。ただし,最初の3回の呼び出し時の返す値の計算では,それ以前にはすべて0が入力されていたように振る舞うこと。最後の方の移動平均は当然ながら10.0が続くはずである。
(p06ex03.c)
ヒント

List (3)検証用プログラム
#include <stdio.h>

double movingAverage(int var)
{
    ここに入るプログラムを作成する
}

int main()
{
    int x;
    double y;
    for(x=0;x<10;x++) {
        y=movingAverage(x);
        printf("x=%6d y=%10.2f\n",x,y);
    }
    for(;x<20;x++) {
        y=movingAverage(10);
        printf("x=%6d y=%10.2f\n",x,y);
    }
    return 0;
}

/*******実行結果例******
x=     0 y=      0.00
x=     1 y=      0.25
x=     2 y=      0.75
x=     3 y=      1.50
x=     4 y=      2.50
x=     5 y=      3.50
x=     6 y=      4.50
x=     7 y=      5.50
x=     8 y=      6.50
x=     9 y=      7.50
x=    10 y=      8.50
x=    11 y=      9.25
x=    12 y=      9.75
x=    13 y=     10.00
x=    14 y=     10.00
x=    15 y=     10.00
x=    16 y=     10.00
x=    17 y=     10.00
x=    18 y=     10.00
x=    19 y=     10.00
************************/



文章課題)変数に関するこのページの内容で重要なポイントをまとめてレポートにしなさい。ただし,初めの2行(ファイル名,ID,出席番号,氏名)は,これまでのプログラムと同様な書式で書くこと。 (p06.txt)

ただし,次のキーワードは必ず含むこと

変数のキャスト,オート変数,グローバル変数,スタティック変数