6.変数の取り扱い
Copyright(C)
8Feb2003 coskx
このページでは変数の取り扱いについて解説している。
ここで修得してほしい内容は以下の通りである。
1.代入による変数の型変換に付いて理解する。
2.混合演算における,型についての注意点,自動型変換のルール,明示的な型変換について理解する。
3.グローバル変数とオート変数の振る舞いの違いを理解する。
4.関数内のstatic変数の振る舞いについて理解する。
変数の型を変換する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 **************************/ |
型の異なる変数同士の演算においては次の規則による。
混合演算 |
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が代入される。 |
次のプログラムの実行結果を予想しなさい。
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; } |
|
次のプログラム(三角形の面積を求めようとしている)の実行結果を予想しなさい。
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; } |
|
これまでの変数は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を表示 |
同じ関数が何回も呼び出される場合,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ということで, 最善の擬似乱数発生を行なっていること が検証された *************/ |
次のプログラムの実行結果を予想しなさい。
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; } |
|
次の仕様の関数を作り,与えられた検証プログラムで検証する。
ただし,先に次のヒントを読みなさい。 ヒント
(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)
ただし,次のキーワードは必ず含むこと
変数のキャスト,オート変数,グローバル変数,スタティック変数