C言語発展編

C言語 「発展編」

C言語を極めたい人のためのページ。(俺もまだ極めたないっけ)

※無断転載は禁止。転載したい時は僕にメール等で一声かけて下さい。


〜目次〜

0.復習 〜カレンダーの製作〜

1.データ型と食うメモリの関係等等

2.演算子一挙公開

3.演算子の優先順位、型変換

4.ソートしてみる

5.変数の有効範囲

6.静的変数



0.復習 〜カレンダーの製作〜


■万年カレンダーを作ろう

みなさん、こんにちは。C言語講座も発展編に突入しました。
発展編ではコンピューターの内部について触れる事も多くなります。
データ型と食うメモリの関係、演算子、ポインタのポインタ等。
ムダ知識(ト○ビア)も紹介するかも知れません。

それはさておき、今回は復習と言う事で、「基礎編」で学んだ知識を活かし、
「万年カレンダー」を作ってみましょう。
こんな仕様です。

○現在の年、月を入力すると、一ヶ月分のカレンダーを表示する。

○閏年(うるうどし)もきちんと数える。
(現在の年が、
4で割れるなら閏年で、2月は29日となる。
しかし100で割れる年は、閏年ではない。
ただし例外として、400で割れる年は、閏年である。)

○2、4、6、9、11(西向く侍)以外の月は、31日。
4、6、9、11の月は30日。
2月は閏年でないなら28日。

まずは自力で作ってみて下さい。

どーしても分からなかったら解答を見てください。

■解答編

さて、解答編です。

まずは、現在の年、月を入力させるプログラムを書くとしましょう。
今回は「発展編」最初と言う事で、文法講座のおさらいも兼ねています。
なので、文法の説明も加えながらの解説となります。
#include <stdio.h>

int main(){
				
				long month,year;

    printf("万年カレンダー  ver 0.01\n\n\n");

				printf("現在の年を入力して下さい\n");
				scanf("%d",&year);
				printf("\n\n現在の月を入力して下さい\n");
				scanf("%d",&month);

				printf("今は%d年%d月です。", year, month);
				
				return 0;
}

これだけ作っても役に立たないのですが...
ステップバイステップ。一歩一歩作りましょう。

#include <stdio.h>

インクルード。括弧で囲まれたファイルを読み込みます。
stdio.h というファイルにはprintfとかscanfとかいう、
「標準入出力」の関数が含まれている事はかなり前に話したと思います。

printf("現在の年を入力して下さい\n");
scanf("%d",&year);

現在の年をキーボードから入力させます。
scanfで文字を書く事はできないので、はじめにprintfで入力させる事を書いておきます。
\nは改行の印。scanfで代入したい変数の頭に「&」をつける事を忘れないで下さい。
つまりここではメモリ上の変数yearの位置(つまり変数yearへのポインタ)を示す訳です。

printf("今は%d年%d月です。", year, month);

入力された値を表示します。
%dは、その後の引き数の値を持ってこさせる事を意味し、ここでは数をあらわします。
つまり、最初の%dにはyearの値、次の%dにはmonthの値が入る事になります。
				//前のソースコードから続けて
				long days,nowdays;
				long uruu;
    long week;
				//暫定日数計算
				days = 365*(year-1);
    
    //閏年
				uruu = year/4;
				days += uruu;

				//100で割れる年
				uruu = year/100;
				days -= uruu;

				//400で割れる年
				uruu = year/400;
				days += uruu;

				//現在の月までの日数計算
				for (int x=1 ; x <= month-1 ; x++){								
								days += getdays(year, x);
				}

				nowdays = getdays(year, month);

				//その月の始まりの曜日。1年1月1日を日曜日とする。
				week = (days + 1) % 7;

				printf("今%d年%d月で、初めの曜日は%dで、%d日あります。", year, month, week, nowdays);

				return 0;

}


現在の一ヶ月前の総日数計算と、月のはじめの曜日を計算しています。
				
				long days,nowdays;
				long uruu;
    long week;
				//暫定日数計算
				days = 365*(year-1);
    
    //閏年
				uruu = year/4;
				days += uruu;

				//100で割れる年
				uruu = year/100;
				days -= uruu;

				//400で割れる年
				uruu = year/400;
				days += uruu;

その年の分を除外した、これまでの日数を計算します。
次に、4年に一度が閏年なので、year/4で閏年の数を計算し、その分の日数を足します。
100年に一度は閏年ではないので、さっき足した内のその分の日数を引きます。
400年に一度は閏年なので、さっき引いた内のその分の日数を足します。
これでその年の分を除外した、これまでの総日数を計算できました。
				
				//現在の月までの日数計算
				for (int x=1 ; x <= month-1 ; x++){								
								days += getdays(year, x);
				}

				nowdays = getdays(year, month);

				//その月の始まりの曜日。1年1月1日を月曜日とする。
				week = (days + 1) % 7;

				printf("今%d年%d月で、初めの曜日は%dで、%d日あります。", year, month, week, nowdays);

				return 0;

ここは現在の年の現在の一ヶ月前までの総日数計算、またそれからはじめの曜日を算出しています。
forループを使って、前の月の日数を総日数に加算しています。
getdays()は、年と月を渡すと、日数が返ってくる自作関数です。後で解説します。
nowdaysに現在の月の日数を代入します。
week = (days + 1) % 7;
weekは何曜日かを表します。
1なら月、2なら火、3なら水、4なら木、5なら金、6なら土、0なら日です。
days に1足しているのは、これまでの日数に現在の月の始まりの一日を足すためです。
	int getdays (int year, int month){			

				if (month == 2){
										//順番重要!
										if (year % 400)				return 29;
									 if (year % 100)				return 28;
										if (year % 4)				return 29;
					} else if (month == 4 || month == 6 || month == 9 || month == 11) {
										return 30:
					} else {
										return 31;
				}
}

出ました。自作関数getdays。
最初の閏年の順番が重要です。もしこの3つを逆にしてしまうと、
「100で割れて400で割れないのに、先に4で割れる判定がされて、29が返された」
と、いうことが起こってしまうからです。
さて、前ソースをページで見せるのもめんどくさいので、
【サンプルソースのダウンロード】 見ておいて下さい。Windows専用です。ソースだけなのでMacでも無理矢理見ようと思えばOKかも。


■開発環境を手に入れよう


最後に、主な開発環境を紹介して、今回は終わりにします。

○Borland C .... Windowsで有名なコンパイラ。ただで入手可能。

○Visual C++ .... 市販のコンパイラ。Win対応。

○MPW     .... フリーのMac対応コンパイラ。

Borland Cの場合、テキストエディタがないので、有名なものを紹介します。

○CPad .... コンパイラのパスを渡すとと自動でコンパイルしてくれる

○BCC developer .... 普通はこちらを使いましょう。多機能で便利。

○ボーランド帳   .... CRenさん作のエディタ。ホームページから入手可。(リンクページからどうぞ)



1.データ型と食うメモリの関係等等


■メモリを節約


「基礎編」第2章で、データ型のお話をしたかと思います。
データ型というのはそれぞれ入れられる数値が決まっているところまでは話しました。

では、それぞれのデータ型がメモリ上でどのぐらいの位置を占めるか?

型名使える範囲食うメモリ
char-128〜+1271バイト
short-32768〜+327672バイト
int -2147483648〜+21474836474バイト
long-2147483648〜+21474836474バイト

つまり、使う変数の範囲が、-50〜20程度なら、shortを使うよりcharを使った方がお得だって言う事です。
1バイトは8ビットです。1ビットは「0」か「1」のどちらかが入ってます。
charの場合、8ビットだから、一つのビットごとに2種類の値が存在するので、
2の8乗で256種類です。また、初めのビットは、符号ビットと呼ばれ、
0なら正、1なら負というふうになってます。
数値はこのようにしてメモリ上に存在するのです。

ちなみに、floatは4バイト、doubleは8バイトです。
doubleの方がfloatよりも、小数部分にかけるビットが多いので、
doubleの方がより高精度な小数の計算ができます。


■符号付けるの付けないの

いままで変数を宣言する時、
int x;

というふうに、
データ型 変数名;
と宣言してきました。これをもう少し正確に書くと、
signed int x;

「signed」っていう訳の分からんものが出てきました。
signedは、デフォルトで省略してもOKなので今まであつかってきませんでしたが、
実は、「符号付きの」 という意味です。
では、符号付けないとどうなるの?
unsigned int x;

signedの頭にunがついてunsigned。
「符号無しの」 という意味です。
ただ符号が消えただけでなく、あるメリットがあります。
それは、『つかえる値の範囲が増える』 ということ。
前のステップで話しましたが、初めのビットは「符号ビット」というのがついてました。
unsignedで宣言すると、必ず正なので、「符号ビット」が要らなくなりました。
そこで「符号ビット」の所を数を表す部分に加えたので、正の部分で使える値の範囲が増えた訳です。
型名(unsigned)使える範囲食うメモリ
char0〜2551バイト
short0〜65,5352バイト
int 0〜4,294,967,2954バイト
long0〜4,294,967,2954バイト

でも実際使える数の種類の総数は変わりません。
あともちろん食うメモリも変わりません。


2.演算子一挙公開


■算術演算子

今回はC言語で使う演算子を解説。
今回は表ばっか出てきます。

○2項演算子
演算子意味用法
+足し算a = b + 5;
-引き算a = 8 - 4;
*掛け算a = 6 * b;
/割り算a = b / 3;
%余りa = 7 % 4;

これはすでにみなさんは知っていると思います。何回も使ってきたからね。
%はC言語独特です。

○単項演算子
演算子意味用法
++インクリメント、+1a++; または ++a;
--デクリメント、-1b--; または --b;
-符号反転a = -b;

インクリメントとデクリメントは変数の値を+1、-1したりします。
前置演算と後置演算があります。
符号反転は講座では初登場ですね。

○代入演算子
演算子意味用法
=右辺から左辺に代入a = b;
+=左辺に右辺の値を加えたものを代入a += 3;
-=左辺から右辺の値を減算したものを代入b -= a;
*=左辺に右辺の値を掛けたものを代入a *= 3;
/=左辺を右辺の値で割ったものを代入a /= 3;
%=左辺を右辺の値で割ったときの余りを代入b %= 7;

a += 3は、a = a + 3 と同じ意味です。aを2つ書かなくて済むので重宝します。(大袈裟


■論理演算子

論理演算子は、真(1)または偽(0)を論理判断の結果を返します。
if文でも使えますし、()や代入演算子と併用すれば論理判断の結果を変数に代入したりできます。

○関係演算子
演算子意味用法用法の意味
>〜より大きいa = (b > 4)bが4より大きいならば1が、そうでなければ0がaに代入される。
<〜より小さいa = (b < 4)bが4より小さいならば1が、そうでなければ0がaに代入される。
>=〜以上a = (b >= 4)bが4以上ならば1が、そうでなければ0がaに代入される。
<=〜以下a = (b <= 4)bが4以下ならば1が、そうでなければ0がaに代入される。
==等しいa = (b == 4)bが4ならば1が、そうでなければ0がaに代入される。
!=等しくないa = (b != 4)bが4でないなら1が、4ならば0がaに代入される。

○論理演算子
演算子意味用法用法の意味
&&且つ(AND)a = (b == 4 && c == 5)bが4、且つcが5の場合、aに1が代入される
||または(OR)a = (b < 4)bが4、またはcが5の場合、aに1が代入される
!反転(NOT)a = !(c >= 4)cが4以上でないなら1がaに代入される。(つまり a = (c < 4) と同じ)
<=〜以下a = (b <= 4)bが4以下ならば1が、そうでなければ0がaに代入される。
==等しいa = (b == 4)bが4ならば1が、そうでなければ0がaに代入される。
!=等しくないa = (b != 4)bが4でないなら1が、4ならば0がaに代入される。

論理演算子において、偽なら0、真なら1を示しますが、
実際は0以外なら真とみなされます。 なので、
if (a % b) {

				printf ("割り切れませーん!");

}

こんな使い方ができてしまいます。
a % b 、例えば、a = 5、b = 3とすると、a % b = 2です。
これは0でないので、真とみなされ、printf ("割り切れませーん!");が実行されます。


■その他...

○三項演算子(条件演算子)
演算子意味用法用法の意味
? :条件式a = ( b ? 3 : 5)bの値が真の時、aに3が代入され、偽の時、aに5が代入される


つまり、上の例でいうと、a = (b ? 3 : 5) は

if (b != 0){
				a = 3;
}else{
				a = 5;
}

と、同じ事になります。
? : ... 簡単な条件式の役目をはたす。
条件 ? 真の時実行 : 偽の時実行

○カンマ演算子
演算子意味用法用法の意味
,区切られた順に左から計算を実行するa = (b = 5 , c *= 7 , d %= 3)b = 5 , c *= 7 , d %= c の順に計算され、その結果、dの値(則ち2)がaに代入される。

これらの演算子に共通して言える事は、代入文を使う時、
最後に実行した結果が、その演算子の計算結果になる事です。


■ビット演算子

さてここからが重要です。第1回に話したビットの話にしばらく戻ります。

では、6を2進数で表してみて下さい。(1バイトで。01010101のように8桁で。)
では、その2倍の12を2進数で表してみて下さい。
そして2つの数を縦に並べてみよう。何か気付く事はありませんか?
00000110
00001100

そう、1ビット分だけ左にずれているのです!

1ビット分だけ左にずらす(左にシフトするといいます)ことは、2倍する事を意味します。
nビット左にシフトすると、2のn乗倍になります。
反対にnビット右にシフトすると、2のn乗分の1倍になります。

そこで、1ビット左や右にシフトする演算子があります。

演算子意味用法用法の意味
<<ビットを左にシフト。a = b << 3bを3ビット左にずらし、その値をaに代入する。 (a = b * 8)
>>ビットを右にシフト。a = b >> 3bを3ビット右にずらし、その値をaに代入する。 (a = b / 8)

ビットシフトは数が大きい時、符号ありの時は要注意です。なのでビットシフトの例をもう一つ。
今度は、unsigned 204を2進数で表します。「11001100」です。
これを、signedとunsignedの場合に分けて、左と右に2ビットずつずらすという4パターンをやってみます。
ちょっと問題。11001100はsignedだといくつ?8桁目が1なので負の数。
64+8+4で、-76です。...と言いたいところですが、実は違って、
頭の符号の1は、マイナスと考えずに、「2の(ビットの数-1)乗を引く」と考える方をしているらしいです。
つまり負の場合は、数を表すビットが1が多いほど、数が大きい事になります。
だから、「どっちにしろ数を表すビットが1が多いほど、数が大きい」と覚えた方がいいです。
よって正解は、76 - 128 = -48 です。

まず、符号無しで2ビット左にシフトの場合。
一見、2つずれると「00110000」となり、48に見えますが、実際の変数で考えると、
204 * 4 = 816 で余裕でオーバーフロー(使える範囲の限界をこえる事)しているのでダメです。

符号無しで2ビット右にシフトの場合。
2つずれると「00110011」となり、51です。これは問題ナシ。

符号有りで2ビット右にシフトの場合。
2つずれると「11110011」となり、-13となってしまいます。
実は、符号ありで右にシフトすると、左に新しく入るデータは、符号ビットの値と同じものになります。
2つ右にシフトする場合、符号ビットの値が1なら「11」が、0なら「00」が入ります。これは少し特殊ですね。

○ビット代入演算子

普通の計算に代入演算子があったと同様に、ビット演算にも代入演算子が用意されています。
演算子意味用法用法の意味
<<=ビットを左にシフト。a <<= 3aの値を3だけ左にシフトした値をaに代入する。
>>=ビットを右にシフト。a >>= baの値をbの値だけ右にシフトした値をaに代入する。



ビットごとにANDやOR演算をする事ができます。

&  ビットAND
|    ビットOR
^    ビットXOR
~ ビット反転

「ビットをマスク」して、ビット列の特定のビットが0か1かを調べたり、
特定のビットを1にしたりできます。
これはハード的に周辺機器コントロール用のフラグを調べたり、操作したりする場合に使われます。

具体的には、ビット演算のANDは、1ビットごとに両方1なら1、それ以外なら0にします。
ビット演算のORは、1ビットごとにどちらかが1なら1、それ以外なら0にします。

ひとつ例を挙げます。
「10010101」の下位4ビットを調べたい場合。
「00001111」とAND演算をして、上位4ビットをマスクして、「00000101」と
下位4ビットの値がそのまま出てきました。このようにして特定の場所のビットの値を取り出す事ができます。

また「10010101」の上位4ビットを1にしたい場合。
「11110000」とOR演算をして、上位4ビットが必ず1111になるようにします。
すると結果は「11110101」と見事に上位4ビットを1111にする事ができました。

AND、ORの他に、XORというものも存在します。「排他的論理和」ってやつです。
どうなるかというと、どちらか一方が1なら1になります。
両方1だったり、両方0だったりすると0になります。

○ビット代入演算子(その2)

ビットごとの論理演算にも代入演算子が有ります。
演算子意味用法用法の意味
&=ビットごとのANDa &= baとbのビットごとのANDをaに代入
|= ビットごとのORa |= baとbのビットごとのORをaに代入
^= ビットごとのXORa ^= baとbのビットごとのXORをaに代入



今までビット演算をやってきましたが、どう役に立つの?と思われた方もいると思います。
実は、掛け算・割り算よりもビットのシフトの方が速いです(動作が)。
あと、ONとOFFのフラグ8つ作る時、ビット演算を知っていればchar型変数1つで済みます。
つまりメモリの節約になるのです。

■sizeof演算子

いよいよラスト!sizeof演算子を紹介して今回は終わりにします。

sizeof演算子は、一言で言うと変数に割り当てるメモリの大きさを返す関数みたいなものです。
sizeof (long)  .... 4バイトが返されます。
sizeof (123 * 45) ....計算結果はintなので4バイトが返されます。

調べたいデータ型を括弧でくくって使います。関数みたいですね。
これは構造体のデータ量を調べるためにも使われます。


さて、様々な演算子を紹介してきました。得にビット演算子の所は重要です。
次回は演算子の優先順位、データ型の変換について解説します。


3.演算子の優先順位、型変換


■演算子の優先順位

前回演算子をずらずらと紹介してきましたが、それには優先順位があります。
まぁ、不安な場合は先に処理したい部分を括弧でくくってしまえばいいのですが。

式         () [] -> .
単項演算子  ! ~ ++ -- + - (型) * & sizeof    (*と&はポインタ)
二項演算子  乗除  * /
       加減   + -
       シフト << >>
       比較  < <= &rt; &rt;=
       等価  == !=
       ビット & | ^            (左から、AND、OR、EXORの順で低くなる)
       論理  && ||            (左から、AND、ORの順で低くなる)
条件演算子  ? :
代入演算子  = += -= *= /= %= >>= <<=  &= |= ^=
カンマ演算子 ,

上に行く程優先順位が高いです。
また普通同じ順位が並んだ場合左から右に実行しますが、
単項演算子、条件演算子、代入演算子は、右から左に実行するという法則があります。
例えば、
if (a == b && c){

この場合、等価はビットより先に行われて、a == b 、その値とcのANDという順になります。
a = b = c = d = 777;

右から左に実行されるので、d = 777、c = d、b = c ....の順で実行されて、
aには最終的に777が代入されます。もう一つ例を上げておきます。
a = b << 1 + 2 || c == 3 && d == 4;

複雑です。まず1 + 2が実行されます。その次に<<
それとc==3とd==4のANDが計算され、やっとORが実行され、そしてラストにaに代入。
a = (((b << (1 + 2)) == 72 ) || ((c == 3) && (d == 4)));
「bを1+2ビッとシフトした値が72」と「c が 3 且つ d が 4」のORをaに代入せよ。
という意味になります。


■型変換


C言語は、変数に「データ型」を持つ特殊な言語です。
では、異なるデータ型で計算させたらどうなるか?とか、
それをさらに別のデータ型に代入したらどうなるか?というのがこのステップのテーマです。

まず結論から話しちゃいますw
原則1、その式中の最も大きいデータ型に合わせて型変換を行う。
原則2、数値を代入する場合、左辺のデータ型にあわせられる。

原則1では、例えばchar型とlong型だとlong型に合わせられるので、別に数値は変わったりはしません。
ですが、原則2で小さなデータ型に数値を代入する場合、
char型にlong型の変数を代入する場合は無理矢理丸められてしまうので要注意です。
(その丸められ方はコンパイラによって違います。)

また、C言語では明示的に型変換を行う事ができます。こんな風に。
(データ型)変数・式など。


具体例を示すと、
void thefunction(long n){
   ・・・
}

int main(){

				int a;
				thefunction ((long)a);
  ・・・ 
  ・・・
}

これはint型のaがlong型に変換されています。
このように変数と違う型を持つ関数にその値の引き数を渡したい時に使ったりします。


4.ソートしてみる


■ソートとは?

今回は文法はお休みして、ソート処理のアルゴリズムを組んでみます。
ソートとは、一定のルールに従って並び変える事です。例えば、大きい順に並べるとか。
今回はソートするデータは配列。以下のものとします。
int Num[10] = { 50 , 24 , -9 , -7 , 4 , 24 , 8 , -17 , -7 , 0 };

では、これをfor文を使って大きい順に並び変える方法を考えてみます。


■解答例1・単純ソート


上を見て、「なんだ、簡単じゃないか」と思った方もおられるのでは?
そうです。これは二重のforループで簡単にできてしまうのです。


int main(){

				int Num[10] = { 50 , 24 , -9 , -7 , 4 , 24 , 8 , -17 , -7 , 0 };
				int Max,Maxn;
				int check[10];
				for (int n = 0 ; n <= 9 ; n ++){
								Max = Num [0];
								for (int m = 0 ; m <= 9 ; m ++){
												if (Max < Num [m] && check[m] == 0)	{
																Max = Num [m];
																Maxn = m;
								}
								check[Maxn] == 1;
								printf ("%d ",Max);
				}
				
			

}

原理は、
まずforループで配列の中の最も大きい数を探します。最大値の初期値は配列の最初とし、
最大値を越えたなら、その越えた数が格納されている配列番号、その数を記憶しておきます。
全部調べ終わったなら、配列番号と数の値を最大値とします。また、その配列番号に「調べ終わった」という印を付けます。
こんどは、印を付けた部分は無視して、forループで配列の中の最も大きい数を探します。(つまり二番目に大きな数になる)
...これを繰り返していく訳です。結構簡単?

■解答例2・バブルソート


上のソートの方法とはちょっと違ったやり方。
基本的には、
「隣り合った2数の数が逆順なら取り替える」
ということを繰り返していきます。
コードに行く前に、5つの数でバブルソートの原理を説明します。
5つの数があります。
1、3、4、2、5
順番がバラバラですね。大きい順にバブルソートしてみます。
まず、1番目と2番目の数をくらべます。1番目の方が小さいので、その2数を入れ替えます。
3、1、4、2、5
次に、2番目と3番目の数をくらべます。2番目の方が小さいので、その2数を入れ替えます。
3、4、1、2、5
...同様に、3番目と4番目、4番目と5番目 とやっていくと、
3、4、2、5、1
一番小さい数である1が、最も右側に寄ったではありませんか!これで第1段階完成。

次にもう一回同じ事をします。1番目と2番目で、1番目の方が小さいので、その2数を入れ替えます。
4、3、2、5、1
2番目と3番目では3番目の方が小さいので、そのままです。
3番目と4番目では3番目の方が小さいので、入れ替えます。
5番目には最小値の1が入っているので、ここでは操作しません。すると、
4、3、5、2、1
次に小さい数の2が、1のとなりに並びました。
同様に同じ事をあと2回繰り返すと、みごとに5、4、3、2、1と並びます。

これが原理です。
それだと...やっぱり2重for文を使えばできそうですね。


int main(){

				int Num[10] = { 50 , 24 , -9 , -7 , 4 , 24 , 8 , -17 , -7 , 0 };
				int s;
				int f;
				for (int n = 0 ; n <= 9 - f ; f ++){
								
								for (int m = 0 ; m <= 8 ; m ++){
												if (Num[m] < Num[m+1])	{
																s = Num[m+1];
																Num[m+1] = Num[m];
																Num[m] = s;
								}
				}
				//出力は省略
}

ちょっといいのは、check[10]なんてよけいな配列変数を使わなくていい事です。
少しバブルソートの方がプログラムとしてはレベルが高いです。まぁ誰かが考えたアイデアだけどね。
さて、次回は変数の有効範囲について話します。

5.変数の有効範囲


■結構重要


今回は、今まで何度もお世話になってきている「変数」の有効範囲について、確認します。
変数に有効範囲なんか決まってるんかい!...決まっています。決まってなかったらここでわざわざ解説しなくてもいいわけです(笑
では、例によって例のごとく最初に結論から...
・基本的に宣言したブロックの範囲内。
・関数の外に宣言した場合は、「グローバル変数」となり、どこでも使える。

ブロックというのは、{ と } で囲まれたあれです。
関数も大きな1ブロックですね。for 文、 while 文 もそうです。
ブロック内と書きましたが、関数で宣言した変数は、for文でも使えます。大きなブロックから小さなブロックはOKです。
つまり、
・ある関数で宣言した変数は他の関数では使えない。
・for文の中で宣言した変数は外では使えない。
・逆に、for文の外で宣言した変数はfor文の中でも使える。

グローバル変数は、その名の通り「グローバルな変数」でそのソース内ならどこでも使える変数です。
そのソースを通してしょっちゅう使う変数なら、グローバル変数にしたほうがいいです。

突然ですが、実験をしてみましょう(何。
変数を違うブロック内で宣言して、その値を調べるという実験です。
int main(){
				int i;
				for (i = 0 ; i < 2 ; i ++ ){
									
									int i;
									for ( i = 0 ; i < 3 ; i ++){
															int i = 0;
															i++;
															print ("i (3) .... %d\n", i);
										}
										printf ("i(2) .... %d\n", i);
				}
				printf ("i(1) ..... %d\n", i);

}

実際にタイプして実行してみて下さい。
どうなりましたか?
1
1
1
3
1
1
1
3
2

...のように表示されたと思います。
1行目から3行目、5行目から8行目に注目して下さい。
これが i(3) を表示する部分ですが、みんな1になってます。
つまり、ループに入る度に、変数iは初期化され0になり、1増えて表示されます。

また、4行目、7行目の値に御注目。外側のループのiの値に関係なく、値はいつも3です。
つまり同名の変数がぶつかった場合はそのブロックの中でのみ使える事になります。
ここでは内側ループのiのループを抜ける時の値、3が表示されています。要は違うブロックなら同名の変数でも区別されている訳ですね。
実はこの変数は、ブロックの外に出ると使えなくなってしまいます。

変数iはどのブロックでも区別されています。ブロックを抜けるとそのブロック内で使われていた変数は忘れられてしまいます。
なので入る度に初期化されるのです。

ちょっとややこしい話でした...(笑 
説明が雑で申し訳ありません。分からなかったら掲示板などで御報告お願いします。


6.静的変数


■前回の実験


今回は、新たに static(静的) 変数 についてお話します。

いままでずーーーーーーーっと使ってきた変数は「auto」変数です。
auto 変数は「スタック領域」に保存される変数です。
「スタック領域」というのは、メモリの領域の一種で、
記憶した順序と逆にデータを取り出す事が出来ます。
img
↑よく書かれる図ですが(笑

前回の実験を覚えているでしょうか?
「ブロックを抜けるとそのブロック内で使われていた変数は忘れられてしまいます」
これが実験の結果でした。これは、ブロックを抜けた瞬間に、そのブロックで使われていた変数が「pop」されてしまったということです。
外側のループは内側よりも前に変数が「push」されているので、内側のループを抜けた時に、
スタック領域の一番上にあるものは外側のループのものになります。
これがスタックの原理です。


■じゃあ、「静的変数」って何な訳?


「auto」変数と違い、静的変数(static 変数)は通常のメモリ領域に記憶されます。
スタック領域とは違い、コンパイラが一度記憶する場所を割り当てると、実行単位を抜けても、
常にその値が保持されます。多くの実行単位で共通に使われる変数等に用いられます。

では、早速前回やった実験を「static」変数を使ってやってみましょう。
int main(){
				int i;
				for (i = 0 ; i < 2 ; i ++ ){
									
									int i;
									for ( i = 0 ; i < 3 ; i ++){
															static int i = 0;
															i++;
															print ("i (3) .... %d\n", i);
										}
										printf ("i(2) .... %d\n", i);
				}
				printf ("i(1) ..... %d\n", i);

}

たぶん、こんな感じになったと思います。
1
2
3
3
4
5
6
3
2


1行目から3行目、5行目から8行目に注目して下さい。
i(3) を表示する部分ですが、前回はみんな1だったのに、今回は1ずつちゃんと増えてます。
実は、 静的変数は 「コンパイル時」に初期化され、実行時は保存されます。
普通の変数とは違う領域に保存されるので、ブロックを抜けてもその値は保持されるのです。