変数の有効範囲とポインタ,関数のデータ受け渡し
前回、画面に出力(printf) /キーボードから入力(scanf)/変数 で、Cにおける変数とポインタについては、
簡単に説明してみました。旧BASICに比べると、
煩雑だと感じると思いますが、更に変数の有効範囲(スコープ)と関数へのデータの渡しかた(参照渡し、
値渡し)について、簡単に触れたいと思います。また、引数を使い関数とデータをやり取りする方法に加え、
returnを使って関数からの戻り値を得る方法について最後にふれます。
なお、Visual Basic、QBasicにおける変数の有効範囲、および関数へのデータの渡しかたに関して、
関連ページの「(!)パソ活研5番街/EXCELの研究/変数のスコープ」
および「(!)パソ活研5番街/BASICの研究/Basicから機械語へ3」にまとめてあります。
参考になると思いますのでご覧下さい。
1 変数の有効範囲
旧BASICには変数の有効範囲などという問題はありませんでした。例えばプログラム中の変数xはプログラム
のどこからでも参照できました。
Cにおいては、大別するとプログラムのどこからでも参照できるグローバル変数と、その変数の宣言された
関数内または、ブロック内のみで有効はローカル変数とにわかれます。
グローバル変数にするには関数の外で宣言します。
例えば次のようです。
int gx,gy; <−これは関数外で宣言されてるのでグローバル変数(プログラム全域で有効) main(){ int x,y; <−このx, yはmain関数の中だけで有効なローカル変数・・・・(A) swap(); } void swap(){ int x,y; <−このx, yはswap関数の中だけで有効なローカル変数。(A)のx, yとは独立している } |
通常ローカル変数はスタック領域につくられ、有効範囲(関数、ブロック)が終了すると、その値は破棄され
ます。従って、上の例でmain のx, yと swapのx, yは別々に作られ、互いに独立しています。
Cに限らず、大概の言語、特に近年作られた言語はみな変数の有効範囲というものがあります。
2 ポインタと関数へのデータの渡しかた
ポインタはその変数の格納されたアドレスをさすものでした。BASIC流にいうと Varptr( 変数 )でしたね。
これはよろしいでしょうか。
さて、Cのプログラムはある意味で関数の集合です。
Cにおける関数を、BASIC的に言うと、かなり性格はことなりますが、まあサブルーチンに近いものです。
どちらも、メインルーチン(main関数)から呼び出し、リターンするものですから。
しかし、変数の有効範囲という点があるので、Cで関数を呼ぶ(関数にデータを渡す)ということと、旧BASIC
でサブルーチンを呼ぶ(Gosub)ということは、かなり違います。
と、文章で書いても実感がわかないと思いますので、変数x, yの値を入れ替えるプログラムで考えて
みたいと思います。BASICでいうと swap(x,y)というやつです。
とりあえず、無骨にBASICで書くと
10 input "2個の整数入力",x,y
20 tmp=x
30 x=y
40 y=tmp
50 print x,y
というやつです。
無意味ですが変数の入れ替えの部分をむりやりサブルーチン化してみます
10 input "2個の整数入力",x,y
20 gosub 40 'swapxy
30 print x,y: end
40 'swapxy:
50 tmp=x
60 x=y
70 y=tmp
80 return
こんどは変数入れ替えをCで書いてみます。Cでも変数入れ替え部分は関数にしてみます。
グローバル変数はgx, gyだけで、他は全てローカル変数です。
/* 変数のスコープのテスト coded by Tsuyoshi Kasai for TurboC, LSI-C */ #include <stdio.h> int gx,gy; /* gx,gyはグローバル変数*/ void swap(void); void swap1(int,int); void swap2(int *, int *); void swap3(void); main() { int x,y; /* x,yはローカル変数*/ printf("整数を2個入力 数字と数字の間はスペースを入れて下さい :"); scanf ("%d %d",&x,&y); gx=x;gy=y; /* ローカル変数は独立していることのテスト */ swap(); printf("\nmainのx、yの表示 %d %d",x,y); /* ローカル変数x,yを値渡し */ swap1(x,y); printf("\nローカル変数の値渡し %d %d\n",x,y); /* ローカル変数x,yの参照(ポインタ)渡し */ swap2(&x,&y); printf("ローカル変数の参照(ポインタ)渡し %d %d\n",x,y); /* グローバル変数gx,gyで渡す */ swap3(); printf("グローバル変数で渡す %d %d",gx,gy); } void swap(){ int x,y,tmp; tmp=x; x=y; y=tmp; printf("swap関数の中のx, y %d %d",x,y); } void swap1(int a,int b){ int tmp; tmp=a; a=b; b=tmp;} void swap2(int *a, int *b){ int tmp; tmp=*a; *a=*b; *b=tmp;} void swap3(){ int tmp; tmp=gx; gx=gy; gy=tmp; } |
不正な例も含めて4例のswap関数を作ってみました。
とりあえず、実行結果もまずあげておきましょう。0と5を入力して交換させてみた例です。
C:\C>swap 整数を2個入力 数字と数字の間はスペースを入れて下さい :0 5 swap関数の中のx,y 4402 5 ・・・・・・・・(a) mainのx,yの表示 0 5 ・・・・・・・・(b) ローカル変数の値渡し 0 5 ・・・・・・・・(c) ローカル変数の参照(ポインタ)渡し 5 0 (d) グローバル変数で渡す 5 0 ・・・・・・・・(e) |
(1) 不正な例 関数swap
関数swapはある意味、上のBASICのコードをほとんどそのまま書いたような感じです。
一番旧BASICぽい書き方です。
しかし、まずswap関数はLSICでコンパイルすると、変数x,yが未定義(初期値が未設定)だという
警告がでます。すなわち、これは不完全なコードです。
Basicでは未定義の変数がいきなりでてきても、たいがいは0を入れておいてくれますが(おりこうさんです)
Cでは未定義の変数には、どんな値がはいるかわかりません(たいがいメモリー上のゴミが入る。中には
0をいれてくれるコンパイラもあるようだが)
でも、致命的エラーではないので、コンパイルできてしまいます。ここは実験なので警告を無視してコンパイル
してみました。
swap内の変数x,yはローカル変数ですから、main関数のx,yとは独立しています。従ってmain関数で
x,yに0, 5を入力しても、swap関数のx,yとはまったく無関係です。swap関数でのx,y交換の後の
swap関数内のx,yの値は(a)ですが、xの方は4402というわけのわからないゴミが入ってます。
(b)はmain関数のx,yの値ですが、0と5のままです。swap関数でx,yを交換しましたが、main関数
のx,yにはまったく影響がありません。これで、互いのx,yがローカル変数で別物のx,yだということが
わかります。
(2) 関数swap1 値渡し
swap1は値渡しによる関数呼びだしの例です。値渡しでは、変数の値をコピーして関数に渡します。
すなわち、下記のようにmain関数のデータを、swap1用のデータ領域にコピーして渡します。
main関数のxのデータ | 0 |
main関数のyのデータ | 5 |
以下swap1用データ領域 | |
swap1関数のaのデータ | 0 |
swap1関数のbのデータ | 5 |
swap1関数では、コピーされたswap1用のデータを使いますので、main関数のデータにはまったく影響
がありません。swap1でa,bを交換すると下記のようになります。
main関数のxのデータ | 0 |
main関数のyのデータ | 5 |
以下swap1用データ領域 | |
swap1関数のaのデータ | 5 |
swap1関数のbのデータ | 0 |
swap1のa,bの値は交換されますが、main関数のx,yはまったく影響をうけません。
swap1実行後のmain関数のx,yの値は実行結果の(c)ですが、0と5のままです。
これが値渡しというものです。
(3) 関数swap2 データの参照渡し
参照渡しというのは、データの格納されているアドレス(すなわちポインタ)を関数に渡す方法です。
この場合swap2関数はmain関数のx,yの格納されているアドレスをうけとり、swap2関数内の
a,bの格納アドレス(ポンイタ)として参照します。
すなわち「main関数のxのポインタ = swap2関数のaのポインタ」ということです。y,bについても同様です。
従って、swap2関数内でa,bの値を交換すると、これはすなわちmain関数内のx,yの値の交換ということ
につながります。
実行結果は(d)ですが、main関数のx,yの値が5, 0と交換されています。
(4) 関数swap3 グローバル変数
swap3ではグローバル変数として宣言されたgx,gyを交換しています。グローバル変数なので、
もちろんgx,gyの値は交換されて5, 0となっています。結果(e)です。
これが、ただしくは一番BASICぽいコーディングといえるでしょう。
全変数をグローバル変数として宣言してしまえば、かなり旧BASICぽいコードの書き方ができます。
旧Basiscのプログラミングになれていると、ついなんでもグローバル変数にしてしまいがちです(という
のはおじさんのことですが)
補足
ともかく個人使用のプログラムだから、動けばいい、コードはどんなきたなくても構わないというのであれば
旧BASICの経験のある人は、なんでもグローバル変数にしてしまうのが、慣れていて楽かもしれません。
でも、ローカル変数にしておくと、変数の独立性が高められるという利点があります。
もちろん、なんでもグローバル変数するのは、どのプロのCプログラマからも、またどの書籍からも
邪道と言われています。
変数の有効範囲の問題と関数へのデータの渡しかたの問題とを一挙にまとめて、とりあげてしまったので、
ちょっとわかりにくいかもしれません。
変数の有効範囲の問題は特に(1)swap (4)swap3
関数へのデータの渡しかたは (2)swap1、(3)swap2
に関連する問題です。分けて考えた方がわかりやすいでしょう。
3 関数のデータ受け渡し
上のところで、既に関数へデータを渡すという点について見てみました。また、関数から結果のデータを
受け取る方法についても、グローバル変数を使う方法、ポインタでやりとりする方法を既にみました。
関数からの戻り値が1つの場合は、更にreturn を使って関数値として結果を受ける方法があります。
これが、一番関数らしいやり方で、また変数の独立性も高くすることができます。
以下は、2つの整数を入力し、加算した結果を返すものです。
例によってBASICで書いてみます。
10 input "2つの整数を入力",x,y
20 gosub 40 'add
30 print z: end
40 'add:
50 z=x+y
60 return
ではCで同じことを3つの方法でやってみます
/* 関数のデータ受け渡しのテスト coded by Tsuyoshi Kasai for TurboC,LSIC */ #include <stdio.h> void add1(void); void add2(int*,int*,int*); int add3(int,int); int gx,gy,gans; main(){ int x,y,ans,result; printf("整数を2つ入力 2つの数字の間はスペースで区切って下さい"); scanf("%d %d",&x,&y); gx=x;gy=y; /* グローバル変数で結果を得る */ add1(); printf("\nグローバル変数で結果を得る %d\n",gans); /* ポインタ(参照渡し)で結果を受ける */ add2(&x,&y,&ans); printf("ポインタ(参照渡し)で結果を受ける %d\n",ans); /* 関数値として結果をリターン */ result=add3(x,y); printf("関数値としてリーターン %d",result); } void add1(){ gans=gx+gy; } void add2(int *a, int *b, int *z){ *z=*a+*b; } int add3(int a, int b){ int c; c=a+b; return (c); } |
実行結果です。2と8を入力し結果として10を得ています
C:\C>addtest 整数を2つ入力 2つの数字の間はスペースで区切って下さい2 8 グローバル変数で結果を得る 10 ポインタ(参照渡し)で結果を受ける 10 関数値としてリーターン 10 C:\C> |
関数add1はグローバル変数を用いたやり方です。一番旧BASIC流のやりかたです。
関数add2はポインタを使ったやり方です。
関数add3はreturn で結果を呼び出し元(main関数)に返す方法です。main関数内では
result=add3(x,y); で結果を変数resultに受けています。