以下では例として3種類のコンパイラを挙げていますがどれか一つでおkです
また、すでに他のコンパイラが入っている場合は無視してもおk
Windowsなら特に理由がないかぎり最初のもの(BCC)をお勧めします(ただし、性能とか最近の標準に対応しているかとかで言うと話は別、結構古いコンパイラなので)
導入方法。
まず、コンパイラを導入しよう。Borland C++Compilerをダウンロード。 FreeCommandLineTool2.exeなんて名前で落ちてくるはず。 自己解凍形式のソレを実行し、インストールする。
デフォルトの位置はC:\borland\bcc55のはずだ。
これでコマンドプロンプトからコンパイルできるようになった。
ただ、これだけでは一々コマンドプロンプトを開いたり、新しいプログラムを作るたびにバッチファイルを書いたり、なんてことをしなければならないので非常に面倒だ。
そこで、CPadというツールも導入しよう。(現在のCPadの在り処は公式の掲示板を参照)
具体的な場所は以下のようだよ!
http://6005.teacup.com/kitobbs/bbs?BD=16&CH=5&M=ORM&CID=1054
CPad自体は解凍しただけで使えるC言語用のエディタだ。
CPadアプリケーションを統合開発環境と呼ぶ。詳しくはプログラミング用語を参照。
コマンドプロンプトを開かずともエディタから直接コンパイルや実行ができる。
ただ、上述のとおり、プログラミングをする上でコマンドプロンプトの使い方を覚えておくことはとても重要だ。
面倒でも必ず統合開発環境を使ってコンパイルする前にコマンドプロンプトでのコンパイル方法をマスターしてくれ。かなり重要だから。
ただし、別にコマンドプロンプトについてはスルーしてもC言語の勉強はできるんで、とりあえずスルーしてCPadでやってくのも可。
そして、エディタの設定の中にコンパイラのパスを指定する箇所がある。 デフォルトだとC:\borland\bcc55\bin\bcc32.exeなはずだ。その設定を済ますと、CPadはコンパイラのための設定ファイルを勝手に書いてくれる。そうすれば後はコンパイル可能だ。
コマンドプロンプトからbcc32 hoge.cのようにコンパイルするには、また別に環境変数PATHをbcc32.exeのあるフォルダに通す作業が必要になる。
-----その他のツール----- setbcc(BCCセットアップお助けツール) http://www.cmagazine.jp/setbcc.html BCC Developer(無料BCCをIDEにしてしまう) http://www.hi-ho.ne.jp/jun_miura/bccdev.htm ------------------------
CD(.iso)イメージをダウンロード
http://www.microsoft.com/japan/msdn/vstudio/express/maninstall/
Webからインストール
http://www.microsoft.com/japan/msdn/vstudio/express/visualc/
CDイメージはdeamontoolを使用する、または、CDに一旦焼いてからインストールしなければならない等、 Webからインストールより厄介なんだけど、 Webからインストールする方法だと、"ちょっとした個人情報"を求められる。
http://www.microsoft.com/japan/msdn/vstudio/express/2005/visualc/usingpsdk/
これをやっておくと、Windowアプリケーションが作りやすくなる。 というか、やんないと悩むこと請け合いだから、やっとけ。 俺を信じろ。
※初心者非推奨!!
詳しい導入法
コレ入れればココで扱うコンソールベースのテストプログラム程度のものならワザワザ別途環境変数とか切る必要も無いヨ。
デスクトップ上に生成された「Cygwin(又は'Cygwin BASH Shell')」をダブルクリックし、Cygwinのコンソールが立ち上がったら、おもむろに以下の内容をコンソールにコピーしよう。
(コンソールへのコピペはコンソールウインドウ上で右クリック。)
echo -e "#include <stdio.h>\nint main(int argc ,char *argv[]){\n\tprintf(\"Hello world.\\\n\");\n}\n" >test.c echo -e "CC=gcc\n\nALL : test.exe\n\ntest.exe : test.c\n\t\$(CC) -o test.exe test.c\n" >Makefile make ./test.exe
これだけでソースの生成から実行結果の確認まで可能。環境によってはコピペ開始からプログラムの実行まで10秒以内で終わるだろうw
まずは"HelloWorld"と表示するプログラム。
#include <stdio.h> int main(){ printf("HelloWorld\n"); return 0; }
メモ帳で保存してコンパイラに食わせるなり、なんなりして実行すると
HelloWorld
が表示される。「つまらんソフトだな」なんて思うことなかれ。全てはHelloWorldから始まるんだ。
ではひとつずつ見ていこう。
#include <stdio.h>
これは、コンパイラに対するもので、「stdio.hというファイルの中を見ておいてくれ」という命令。
stdio.hには「○○関数( { 〜 } までの命令のこと)はこういうものだ」という説明が書かれているファイルで、コンパイラに見せることで関数を使うことが出来る。
stdio.hには基本的な関数がたくさん入っているので初心者から上級者まで世話になることだろう。
int main(){ ○○ △△ }
C言語のプログラムは、main関数に書かれた命令が真っ先に実行されることになっている。
上記のコードでは○○や△△がmain関数の中身であり、○○や△△が真っ先に実行される。
printf("HelloWorld\n");
これは「("")の中のものを表示する」という関数だ。
だが、「HelloWorld\n」とではなく、"HelloWorld"と表示される。
でも「\n」にはちゃんとした意味がある。
これは「エスケープシーケンス」って言う記号の一種で「改行」の意味がある。
「エスケープシーケンス」とは文字では表せない特殊なものをあらわすもの。
それから、1つの命令の終わりには;(セミコロン)を必ずつける。
return 0;
この行は今のところプログラムの終わりに書くものと思っておいてくれ。
本来はちゃんとした意味があるのだが、それはちょっと難しい。
関数の概念を理解すれば自然と分かるようになる。
C言語ではスペース、改行は殆ど意味を持たない。
しかし、書いた人、それ以外の人が見ても見やすい・修正しやすいコーディングを心がけよう。
前回の「Hello World」では
int main(){ printf("HelloWorld\n"); return 0; }
というコードを書いたと思うが、main関数を下記のように書いても全く問題はない。
int main(){printf("HelloWorld\n");return 0;}
しかし、これでは見づらい。
よって、C言語にはスペース・改行に関する明確な書式はないが、習慣としてこう書いた方が見やすいという書き方はある。
例として下記のコードを記述した。
int main() { printf("VIPWorld!\n"); ○○ { ××; □□ { ☆☆; } } }
ご存知のとおり、全く問題はない。しかしmain関数の中に中括弧( { と } )が入ってきたりするとmain関数の中括弧なのか○○の中括弧なのか分かりづらい。
そこでスペースを利用して下記のように書くと、
int main(){ printf("VIPWorld!\n"); ○○{ ××; □□{ ☆☆; } } }
幾分かみやすくなった。どの中括弧に何が入っているのかが分かりやすくなった。
スペース入れ放題、といっても以下の場合エラーが出る
p r i n t f ( " hello world " );
関数などにスペースを入れてはいけない。
printf ( " hello world " ) ;
↑これならOKである
他の言語では、改行、スペースに明確な決まりがあるものも多いが、スペースと改行を使って見やすくするということに関しては同様である。
そんな場合でも有効だ。
上述のとおり、明確な決まりというものはない。自分で見やすい・分かりやすいという書き方を見つけれてくれ。
毎回同じ文字列を出力するだけなら、プログラムどころか判子でも買え。コンピュータといえば計算だ。
今回は加減乗除( + − ÷ × の4つ)をするプログラムを作る。
今回扱う「変数」はとても重要なのでしっかり覚えよう。
#include <stdio.h> int main(){ int wa; int sa; int seki; int shou; wa = 10 + 5; sa = 10 - 5; seki = 10 * 5; shou = 10 / 5; printf("和=%d ", wa); printf("差=%d ", sa); printf("積=%d ", seki); printf("商=%d\n", shou); return 0; }
CPUは物覚えが悪いので、単純な足し算でも答えを書き留めて置く入れ物を作る。
この入れ物のことを変数と呼ぶ。
ここでは、和、差、積、商それぞれの答えを置く4つの変数を作る。
変数にはいろんな種類があるが、今回作るのは整数用の入れ物を4つ。
例として、下記のソースを見てほしい。
int x;
intは整数用(正式には整数型)を表し、上記のソースはxという変数を作るという意味になる。
従って、4行目は「整数型の変数waを作る」という意味。
5〜7行目も同様に整数型の変数sa、seki、shouを作っている。これで、答えを書き留めて置く変数4つができた。
ついでに、4〜7行目までは以下のように省略することも出来る。
int wa, sa, seki, shou;
ちなみに今は「変数を作る」と書いたが、正しくは「変数を宣言する」である。
9〜12行目では実際に計算を行う部分。C言語で計算する場合、代入(入れる)していくのが基本だ。
1行目は「変数waに10+5の答えを入れる」と言う意味になる。
これも実は変数の宣言と同時に行うことが出来る。
int wa = 10 + 5;
そして14〜17行目で計算した結果を表示する。
例として下記のコードを見てほしい。
printf("%d",x);
上記のコードは「%dにxの中身を表示する」という意味になる
つまり、14〜17行目では変数(wa, sa, seki, shouそれぞれ)を"%d"の中に放り込み、文字と一緒に表示している。
従って、waの中身である("10 + 5"の計算結果)15が"和=%d"の"%d"の中に放り込まれ、"和=15"という形で画面に表示される(sa, seki, shouについても同じ)。
ついでに14〜17行目は下記のように省略することが出来る。
printf("和=%d 差=%d 積=%d 商=%d\n", wa, sa, seki, shou);
よって実行結果は
和=15 差=5 積=50 商=2
となるはずだ。前述のとおり、変数は重要だからしっかりマスターしよう。
今やったプログラムで、 int というのが出てきました。
これは簡単に言えば「変数の種類」を表し、これを変数の型といいます。
変数はその型によって代入できるものが決まっています。(これは正確には嘘ですが、初心者のうちはこう思っておいて実用上まったく問題ありません。)
では、変数の型にはどんなものがあるのか一覧にまとめておきます。(背景をピンクで示したものは特によく使うので、チェックしておきましょう。それ以外はしばらくはスルーでいいです。)
型名 | 入れられるもの | 使用例 | サイズ |
int | 整数 | int a = 10; | 4バイト |
char | 文字または整数 | char ch = 'a'; | 1バイト |
float | 小数 | float f = 3.14; | 4バイト |
double | 小数 | double df = 3.14; | 8バイト |
short | 整数 | short sh = 10; | 2バイト |
long | 整数 | long l = 10; | 4バイト |
long long | 整数 | long long ll = 10; | 8バイト |
long double | 小数 | long double ld = 3.14; | 12バイト |
ところで、表のなかに「サイズ」という項目がでてきました。これは変数の大きさのことで、詳しく言えば「数値などを入れておくために用意された場所の広さ」のことです。(つまり、その広さに収まらないような大きな数字は代入することができない。)
サイズがnバイトの整数型には、-2^(n*8 -1) 〜 2^(n*8 - 1) - 1 までの数(マイナス{2の(n×8 - 1)乗} から 2の(n×8 - 1)乗ひく1 まで)の数を代入することができます。
例えば、int型なら、n = 4 なので、-2^31 〜 2^31 - 1 までで、計算するとだいたい±20億程度。
小数型については難しいのでここでは省きますが、通常doubleにしておけばまず大きさが足りなくなることはありません。
(要は、初心者のうちは整数なら int、小数なら double を使っておけば間違いないということ。)
注意
今回は繰り返し同じ処理を実行したいときに使うループ制御。
コードを簡潔にすることが出来るが、下手に使うと無限ループに陥ったり、かえって複雑になってしまったりすることもある。
注意しよう。
最も使われていると思われるFor文。
下記のソースを見てほしい。
#include <stdio.h> int main(){ int i; for ( i = 0 ; i < 5 ; i++ ) printf( "ただいまループ%d回目です。\n" , i +1 ); printf( "ループから抜けました。\n" ); return 0; }
新しく出てきた「For」だが、これで同じ動作を繰り返すことが出来る。
for ( × = 0 ; × < ○ ; ×++ ; ) ☆☆;
上記のコードで繰り返すことが出来る。
「 × = 0 ; 」というのは変数の初期化だ。変数に0を代入している。
「 ○ < × ; 」というのは処理を続けるための条件である。○が×未満の場合、処理(この場合では☆☆;)を行うということである。
そして「 ×++ 」だが、処理が終わったあとに行う文である。
i++ は「iを1増やす」という意味なので、総合すると7行目は「iに0を代入して、printf( "ただいまループ%d回目です。\n" , i +1 );をiが5未満の間、繰り返し、その後、iに1を足す」
ということになる。
ここで注目してもらいたいのは、printf関数の引数にiを使っているということである。
iは繰り返しの度に動的・規則的に変化していくから、iを使うことで繰り返しの回数を処理に組み込むことが出来る。
従ってこのコードの実行結果は、
ただいまループ1回目です。 ただいまループ2回目です。 ただいまループ3回目です。 ただいまループ4回目です。 ただいまループ5回目です。 ループから抜けました。
となる。
注
for文の条件には、次のようなものを入れられる。これは後に出てくる while や if などでもまったく同じである。
条件の例 | 意味 |
i < 10 | iが10未満 |
i <= 10 | iが10以下 |
i > 0 | iが0より大きい |
i >= 0 | iが0以上 |
i == 2 | iが2と等しい |
i != 2 | iが2と等しくない |
特に5番目のものは i = 2 と書いてしまいがちなので注意が必要だ。(さらに悪いことに、こう書いても別の意味として正しいのでコンパイルエラーにならない)
これ以外にも、実は条件には「式であればなんでも入れられる」のだが、まだ難しいし必要ないのでここでは省略する。
whileを使うことでも繰り返し処理が出来る。
(例)
#include <stdio.h> int main() { int i = 0; while( i < 5 ) { printf( "ただいまループ%d回目です。", i ); i++; } printf( "ループから抜けました。" ); return 0; }
whileの基本的な文法は以下のとおり。
while (条件) { 処理 }
条件というのはループを続けるかどうか判定するためのものである。
例ではint型の変数iの値が5未満の場合、処理を行うということになる。
#iが5未満かどうか調べる(5未満なら処理を行い、5以上ならループから抜ける)
#printf( "ただいまループ%d回目です。", i );を実行
#i++を実行
#1に戻る 従って実行結果は
ただいまループ1回目です。 ただいまループ2回目です。 ただいまループ3回目です。 ただいまループ4回目です。 ただいまループ5回目です。 ループから抜けました。
となる。
While文もForと同様にbreakを使うことが出来る。
For文とWhile文は文法も違うのだが、For文は主に、回数が決まった処理を行う場合、While文は何回とは決まってない場合に使うことが多い。
While文の項目の例では5回と回数が決まっていたが、i++;を忘れていたりすると、iの値は変化せず、永遠にループすることになる。(For文はそういったミスが少ない)
しかし、ゲーム制作などで半永久的に処理を続けたいという場合なら、
while( 1 ) { 処理; if ( 終わるための条件を書く ) break; }
理由は難しいのでまだ書かないが1を条件にすると無限ループとなる。
While文ならこのような場合に便利である。
forとwhile以外に、もう一つループ構文がある。
これはマイナーだし while で簡単に置き換えらえるので特に覚える必要はないが、一応こんなのもあるということだけは頭の片隅に置いておこう。
do{ 処理; } while(条件);
この構文は、まずとりあえず 処理; を一回行う。次に条件をチェックして、成り立てばもう一度 処理; を行い、また条件をチェックして・・・ ということを条件が成り立たなくなるまで繰り返す。
通常の while は初めから条件が成り立たなければ処理を一回も行わないが、こちらは必ず一回は行うというのが特徴だ。
while(条件) の後の ; を忘れがちなので注意しよう。
キーボードから文字、数字の入力を求め、処理します。
これにより、プログラムの幅が飛躍的に広がります。
以下のコードを見てください
#include <stdio.h> int main() { int x,y; printf("Xを入力してください\n"); scanf("%d",&x); printf("Yを入力してください\n"); scanf("%d",&y); printf("XとYの和は%dです",x+y); return 0; }
今回新しく出てきた「scanf」ですが、scanf関数を使うことにより、キーボードからの入力を受け取ることが出来ます。
scanf("%d",&x);
上記の式は、「xという変数に整数型として入力された値を代入する」ということになります。
scanf関数を使う時に最も気をつけるべきことは、代入される変数に「&」をつけるということです。
理由は難しいのでここでは説明しませんが、必ず付けてください。
従ってこのコードは7行目と10行目でキーボードから入力された値を整数型の変数x,yに入れ、
その和をprintf関数で表示するということになります。
もしも○○ならば△△という処理を実行したいなんて時に使います。
下記のコードを見てください。
#include <stdio.h> int main(){ int val; printf("数を入力してください>"); scanf("%d", &val); if( val > 0 ) printf("正の数です\n"); else if( val < 0 ) printf("負の数です\n"); else printf("0です\n"); return 0; }
「if」というものが出てきました。ifは
if (○○) △△;
上記のコードは、「もし、○○ならば、△△を実行」という意味になります。
従って9行目は、「もしvalの値が0より大きければ、"正の数です\n"を表示します」という意味になります。
次に「else」ですが、「if」の条件が満たされなかった場合にelseが実行されます。
じゃあ、「○○が満たされた場合、△△と××の2つの処理を実行したい」という場合は下記のようにします。
if ○○ { △△; ××; }
また、8行目には「else if」という文がありますが、ただ「else」と「if」を組み合わせただけです。
イメージとしては、
else { if (○○) △△; }
という文が、
else if (○○) △△;
という文に省略された、と考えてください。
さて、ifを覚えました。早速使ってみましょう。1~3の数字を入力してもらい、その数字によって処理を変えるプログラムです。
#include <stdio.h> int main(){ int val; do{ printf("数を入力してください(1~3)\n"); scanf("%d", &val); }while(val > 3 && val < 1); /* valの値が4以上か、0以下のときループする */ if(val == 1){ (処理1) }else if(val == 2){ (処理2) }else{ (処理3) } return 0; }
ここまで書いて「やってしまった」と思いました。なぜかというと
do{ printf("数を入力してください(1~3)\n"); scanf("%d", &val); }while(val > 3 && val < 1); /* valの値が4以上か、0以下のときループする */
この部分の説明がまだないからです。ですがここは条件分岐、無視してやっていきましょう。
話を戻すと、変数valの値によってifやelseを使って別の処理をしています。
別にこのままでもいいのですが、値の範囲が広くなるにつれてifやらelseやらをたくさん書いていくのは面倒くさいだろうと思います。
そこで今回のテーマ"switch"を使いましょう。switchの文法は
switch(変数){ case 値: (処理) (default: (処理)) }
です。case 値: の部分はいくらでも増やせます。defaultの部分はなくても構いません。
これと先ほどのifを変えてみると
switch(val){ case 1: (処理1) break; case 2: (処理2) break; default: (処理3) break; }
となります。今回defaultを使ったのは、このプログラムの場合valの値が1でも2でもないそれ以外の場合、つまり3のとき
という意味になるからです。
breakについて説明します。
breakはこのプログラムの場合"switch文を抜ける"という処理をします。
case 1:内のbreak; に到達すると、一番下の } に飛ぶといった具合です。
もし、このbreakが無ければ次にbreakかswitch文の}が来るまで続けて
処理を実行してしまいます。つまり1を入力しても2や3の時の処理もしてしまいます。
なので、場合にもよりますが普通はbreakを書くようにしましょう。
そもそも構造体って何?
たとえば君がプログラミングをしてるときに
int man_age, man_height, man_weight;
int woman_age, woman_height, woman_weight;
って感じの変数を使って、男性や女性の情報を得るプログラムを作りたいとしよう。
まあ男性と女性2人だけならあまり見にくいとは感じないけどもしこれが太郎、次郎、三郎の成績、身長、体重をまとめたりするのだったら、
int taroh_grade, taroh_height, taroh_weight;
int jiroh_grade, jiroh_height, jiroh_weight;
int saburoh_grade, saburoh_height, saburoh_height;
見たいな感じにしなきゃそしたら面倒でしょ。
でもこの中の成績、身長、体重って三人とも共通するデータだよね。だったらまとめたら便利そうだよね。
だからこれをまとめちゃうんだよ。それが構造体。
具体的には
struct stat { int grade; int height; int weight; };
と言う感じで成績、身長、体重をstatという名前でまとめられるんだ。
ちなみにこれを変数として使いたい時は
struct stat taroh, jiroh, saburoh;
見たいな感じでできる。
そしてそれぞれの成績、身長、体重(これをメンバと呼ぶ)にアクセスするには
taroh.gradeという形でアクセスできる。
taroh.grade = 120;と言う形で代入もできる。
え?これじゃただ宣言が短くなっただけで実際ほとんど変わらないじゃんって?
いえいえそんなことはありません。たとえばあなたが100人分の生徒の情報を扱うとき、
1_grade, 1_height, 1_weight;
2_grade, 2_height, 2_weight;
3_grade, 3_height, 3_weight;
以下省略
なんてことしてたら確かに無理ではないけど面倒すぎて死んでしまいます。
でもこれが構造体を使うと
struct stat students[100];
これだけになります。なんとこれは99行の省略になります。
99行書いてる間にはいろいろなことができます。だからこれは非常に有用です。
他にも利点はあります。
たとえば関数を呼び出すとき
hyper_ultra_super_function(baka, aho, doji, manuke, usero, kiero, shine, kimoi, kuso, doutei, DQN, DVD!DVD!DVD);
と言う長い引数を渡したい場合があります
これって見にくいですよね。
でもこのbaka, aho, doji, manuke, usero, kiero, shine, kimoi, kuso, doutei, DQN, DVD!DVD!DVDを構造体にまとめれば、
hyper_ultra_super_function_ver2(batou);
と言う感じで済みます(構造体のメンバにいちいち代入しなきゃいけませんけども・・・)。
どうですか?すごいと思いませんか?
ちなみに構造体のポインタと言うものも作ることはできます
struct stat taroh; struct stat *students; taroh.grade = 4; taroh.height = 156; taroh.weight = 50; students = &taroh;
と言う感じでいつも通りの代入ができます。
しかし、studentsからメンバにアクセスする場合、students.gradeのような形ではアクセスできません。
構造体のポインタからアクセスするにはstudents->gradeという形でアクセスできます。
これが構造体と言うものです。
今まではmain関数の中にいろんな処理を詰め込んできたが、関数を作ることによってより分かりやすく、高度なコーディングが可能になる。
C言語の構造化プログラミングでは関数がサブルーチンとなる。
例として以下のコードを参照。
void hoge(void) { printf("関数です\n"); } int main() { hoge(); return 0; }
1行目から4行目までがhoge関数である。
まず、main関数が実行される。その中の「hoge();」でhoge関数が実行され、hoge関数の中身であるprintf("関数です\n");が実行される。
void hoge()のvoidとは返り値が無いことをあらわす。
返り値については後ほど説明する。
コンパイラはソースを上から順に処理していくので、作成した関数よりmain関数が先に記述してあった場合、main関数を実行している時点では、作成した関数をコンパイラは認識してないのでエラーが出てしまう。
そのため、main関数より後に記述してた関数をmain関数内で使用するには、コンパイラに関数の情報を渡してやることが必要になる。
これをプロトタイプ宣言と言う。
(例)
include <stdio.h> void hoge(void); // プロトタイプ宣言
int main() { hoge(); return 0; } void hoge(void) { printf("関数です\n"); }
上記のコードではmain関数内でmain関数より後に記述したhoge関数を使用している。
そのため、3行目で、プロトタイプ宣言をしている。
関数の呼び出しの際に値(引数)を渡し、その値を使って処理することが出来る。
また、関数の終了時に任意の値(返り値)を返すことが出来る。
(例)
include <stdio.h> int addition(int x, int y) //プロトタイプ宣言 int main() { int a; a = addition( 2, 9 ); //addition関数に2と9を渡して返り値をaに代入 printf( "%d", a ); return 0; } int addition(int x, int y) { return x + y; //xとyの和を返す }
まず3行目でaddition関数のプロトタイプ宣言。
addition関数の返り値がint型で、引数にint型の変数x,yを取るということをあらわしている。
8行目で、int型の変数aにaddition関数の返り値を代入。
addition関数の引数には、2と9を渡している。
ここから重要であるが、addition関数では渡された2と9をそれぞれx,yとして受けている。
プロトタイプ宣言などで宣言した引数はそのまま関数内で使用することができ、呼び出しの際に引数が代入されている。
分かりにくいかもしれないが、8行目で渡された2と9がxとyに入る。~15〜16行目でxとyを足した数値をzに代入し、return文でzを返している。
ここで必ず気をつけるべきことは、
・関数の宣言の際に宣言した引数の型と、関数呼び出しの際に渡す引数の型を一緒にする
・関数の宣言の際に宣言した返り値の型と関数内でreturn文を使って返す値の型を一緒にする
ということである。
従って上記のコードの実行結果は、
11
と表示される。
ついにC最大の鬼門と言われるポインタの解説です。
でも大丈夫。たいして難しくありません。
注意)この項目は初心者にポインタを感覚的に理解して貰うために、「正確には少々語弊がある」といった表現も含まれています。非初心者の方が目を通される場合はそこをご理解のほどよろしくお願いします。
ポインタを説明する上でアドレスというものは避けて通れません。
ではアドレスとはなんでしょうか。
アドレス。日本語にすると住所です。つまり、
アドレスとは、変数の住所のことである
3回復唱してみましょう。
アドレスとは、変数の住所のことである
アドレスとは、変数の住所のことである
アドレスとは、変数の住所のことである
もう覚えましたね。変数が分からない人は上のほうで解説してあるのでそっちを先に見てください。
ではその住所とはどんなものか見てみましょう。変数のアドレス(=住所)を取得するには、
&変数名
とします。例えば、int型の変数aのアドレス(=住所)なら、
&a
が、変数のアドレスを表す数字になります。
ためしに下のようなプログラムを実行してみましょう。
#include <stdio.h> main(){ int a = 5; printf("%d\n", &a); /** aのアドレスを、数字として表示。aの中身(=5)ではない。 **/ }
実行結果
-1081884848
えっ?実行結果が例と違う?
それはなぜかというと、変数のアドレスを何番にするかは、PCがプログラムを実行するときに適当に決めるからです。
実行したときのPCの気分(状態)によってころころ変わります。
でも、一回の実行中でアドレスが変わってもらっては困りますね。そこは大丈夫です。例えば、
#include <stdio.h> main(){ int a = 5; int b, c; printf("%d\n", &a); b = 10; // なんか適当な処理 c = b * a + 2; // なんか適当な処理 printf("いろいろ処理してるうちにaのアドレスが変わったりして・・・\n"); // なんか適当な処理 printf("%d\n", &a); }
を実行すると、
-1079031288 いろいろ処理してるうちにaのアドレスが変わったりして・・・ -1079031288
となります。2回 &a を表示していますが、もちろん値は変わりません。(最初の例の値とは違う。ここはあまり気にしなくていい。)
なぜマイナスなのか気になるって?
これには意外と深いようでものすごく浅い事情があるのですが、「マイナスでもなんでも、他の変数と区別できれば住所としての役割は果たすな」くらいに思って、スルーしてください。
ではいよいよポインタの説明です。ポインタとはなんでしょうか?
ズバリ言うと、
ポインタとは、アドレスを入れるための変数である
これ以上のなにものでもありません。
intなら整数、charなら文字、と入れるものによって変数の型を使い分けてましたよね?あの延長です。
では復唱しましょう。
ポインタとは、アドレスを入れるための変数である
ポインタとは、アドレスを入れるための変数である
ポインタとは、アドレスを入れるための変数である
はい、覚えましたね。
では具体的にポインタはどうやって使うのでしょうか。
一例として、int型変数のアドレスを入れるためのポインタを作ってみます。
int型変数のアドレスを入れるためのポインタを作るには、
int* ポインタの名前;
とします。例えば、
int* ptr;
のように。
それでは実際のプログラムにしてみましょう。
#include <stdio.h> main(){ int a = 5; int* ptr; /** int型変数のアドレスを入れるためのポインタを用意 **/ ptr = &a; /** aのアドレスを入れた **/ }
このように使います。まだ何の役にも立ちませんね。
ここでちょっと注意があります。
世の中の9割以上の本やソースコードには、 int* ptr; ではなくて、
int *ptr;
と書いてあります(スペースの位置が違う)。
これは int* ptr; と同じ意味なので慣れればどっちを書いてもいいのですが、初心者のうちは
int* ptr; と書くことを強くすすめます
これには三つほど理由があるのですが、現時点で言える理由は、
ということです。
intと同様に、char型変数のアドレスを入れるためのポインタは char* ptr; と宣言します。
またその他の型用のポインタについても、
型の名前* ポインタの名前;
で宣言できます。
さて、ポインタの概念と作り方はわかりましたが、このままでは使い道がサッパリ分かりません。
そこで使い道を説明していきます。
まず、変数の頭に & を付けるとその変数のアドレスを表したような感じで、
ポインタの先頭に * を付けると「そのポインタに書いてあるアドレスにある変数の、中身」を表します。
アドレスとは変数の住所のことであるでしたから、アドレスの場所にはなにか変数があるはずです。その中身ですね。
「え? * ってポインタを作るときに使うやつでしょ?」はい、その通りです。これがポインタを学ぼうとして諦める人が多い原因ではないかと思っています。
ー は普通「引く」を表すけど、「数字の前についたらマイナスを表す」ようなものです。ひとつの記号なのに意味が2つあってややこしい。
では早く具体例をみて把握してしまいましょう。
#include <stdio.h> main(){ int a = 5; int* ptr; ptr = &a; /** aのアドレスを、ptrに代入した **/ printf("%d\n", *ptr); /** 「ptrに書いてあるアドレス」にある変数(つまりa)の中身を表示 **/ }
実行結果
5
ここで int *ptr; と書いてほしくないもうひとつの理由が出てきます。
上のプログラムを int *ptr; と書くと、
#include <stdio.h> main(){ int a = 5; int *ptr; ptr = &a; printf("%d\n", *ptr); }
となります。
「そうか! *ptr が int型の変数だ と思えばいいんだ!」
確かに5行目と7行目を見るとそう見えます。が、そう考えると、
ptr = &a;
が意味不明になります。つい
*ptr = a;
としてしまいそうです。これがなぜ間違いか考えてみましょう。
5行目(int *ptr;)までの状態では、ptrには「何が入っているか分からない状態」になっています。(変数は宣言しただけでは何が入っているか分からない。)
なので、この状態で *ptr = a; とすると、「ptrに書いてあるアドレス(どっか適当なアドレス)に変数があると思って、そこにaの値5を代入する」という動作を行います。
これはいけませんよね。
さて、だいぶ * の説明が長くなってしまいましたが、いよいよポインタの使い道です。
次のコードがどんな結果を生じるか考えてみてください。
#include <stdio.h> void inc(int x){ x = x + 1; } main(){ int a = 5; inc(a); printf("%d\n", a); }
さてどうでしょうか?一見6が表示されそうですが、残念ながらそうはなってくれません。
inc を呼び出すと、「 x に a の値がコピー」されますが、この「コピー」がくせものです。
例で理解しましょう。
いま、あなたがmain関数だと思いましょう。あなたは a という名前の紙を持っていて、5と書いてあります。
あなたにはincという友達がいます。あなたは彼の持っている紙(名前はx)に「 a をコピーして」渡します。
そして彼が x にかいてある 5 を 6 に書き換えて、その後あなたは a の中身を表示します。
さて、どうなるでしょうか?
そうです。この方法ではaの値変わらないのです。
ここでポインタの登場です。上のプログラムをポインタを使って次のように書き換えました。どうなるでしょうか?
#include <stdio.h> void inc(int* x){ *x = *x + 1; } main(){ int a = 5; inc(&a); printf("%d\n", a); }
友達のinc君が持っている int* 型の変数x(int型変数のアドレスを入れるためのポインタx)には、aのアドレス(&a)がコピーされて入っています。
この状態でincは *x に、 *x + 1 を代入します。
*x は「xに書いてあるアドレスにある変数の、中身」でした。さらに x には a のアドレスが入っています。つまりこれは
(aの中身) = (aの中身) + 1;
となり、めでたくmainの持つ a が 6 に書き換わります!
このように、ポインタは呼び出された関数から呼出し元の持つ変数の内容を変えたいときなどに利用されます。
重要ポイントのまとめです。
アドレスとは、変数の住所のことである
ポインタとは、アドレスを入れるための変数である
*ptr とは、ptrに書いてあるアドレスにある変数の、中身である
分からなくなったときはこれを思い出してください。
int* ptr; と書け と強くすすめましたが、ちょっとだけ問題があります。
それは次のようにポインタをいくつかまとめて宣言する場合です。
int* a, b, c;
これは
int *a, b, c;
と同じだとみなされて、b と c はただのint型になってしまいます。なのでこういうときは諦めて
int *a, *b, *c;
と書いてください。
(本当は (int*) a, b, c; と書きたいんだけど、こうかくとキャストだと思われてエラーになってしまう。残念。)
(注意!)これは初学者向けではありません。最低でも関数とポインタの知識を得てからよむこと。
関数のアドレスを保持するポインタです。 実行する関数を動的に変えられたり、関数の配列を作ったり、関数の引数に関数ポインタ・・・・など、いろいろな事ができるようになります。
まずは関数の宣言から見ていきます。
int MyFunc(float);
これはint型の値を返し、引数にfloat型をとる関数の宣言です。余談ですが、関数の宣言もしくは定義には、仮引数に引数名を与えなくても型だけで十分です。 定義時に引数名を省略しても、関数内部で使えないだけで問題はありません。
関数ポインタは、入る関数の引数と戻り値が分かっている必要があります。というのも、関数ポインタの宣言時にそれらの情報が必要だからです。
宣言の形は、
関数の戻り値 (*関数ポインタ名)(関数のとる引数)
です。
上記のMyFunc関数が入る関数ポインタpFuncを宣言するには、
int (*pFunc)(float);
とします。 これは、
今まで秘密にしてきましたが(?)、関数ポインタは通常のポインタと同じく、単なる変数です。もちろん値を入れられます。 入れる値とは、関数のアドレスです。 以下は関数、関数ポインタ、それへの代入の一通りの例です。
int MyFunc_1(flaot a){ return (int)a + 1; } int MyFunc_2(flaot a){ return (int)a + 99; } int (*pFunc)(float); /* .... */ pFunc=MyFunc_1; printf("MyFunc_1 %d\n",pFunc(2.0f)); pFunc=MyFunc_2; printf("MyFunc_2 %d\n",pFunc(1.0f));
実行結果は
MyFunc_1 3 MyFunc_2 100
となります。
関数ポインタですが、変数というからには型があります。型があるからには、もちろんtypedefをする事ができます。typedefとは既存の型に好きな別名を与えるということでしたね。 では、関数ポインタの型とは何なのでしょうか。
下のpFuncの場合、
int (*pFunc)(float);
型は
int (*)(float);
です。どう見てもアナルです。爆発はしません。
typedefの使い方は、
typedef int INTEGER;
これでintの別名"INTEGER"の完成でしたね。
よく見てください。
int INTEGER;
後ろ部分は、『int型の変数の宣言』と形が同じです。 という事は、関数ポインタのtypedefも、 『int (*)(float)型の変数の宣言』に"typedef"をくっつけたものと考えることができますね。 つまり、下です。
typedef int (*pFunc_t)(float);
これで『int (*)(float)の別名pFunc_t』のできあがりです。 次からは、
pFunc_t pFunc;
として関数ポインタを宣言することができます。 すっきりしましたね。
通常のポインタには、多重ポインタがあります。『ポインタのポインタ』とか、『ポインタのポインタのポインタ』といったものです。
int v=0; int* p = &v; //int型へのポインタ int** pp = &p; //int*型へのポインタ int*** ppp = &pp; //int**型へのポインタ ***ppp = 99; //v = 99
関数ポインタも例外でなく、『関数ポインタへのポインタ』などといった形にすることができます。
typedef int (*pfunc_t)(void); pfunc_t* ppfunc; //int (**ppfunc)(void)と同等 pfunc_t** pppfunc; //int (***pppfunc)(void)と同等 pfunc_t*** ppppfunc; //int (****ppppfunc)(void)と同等 int (*****pppppfunc)(void); //pfunc_t**** pppppfuncと同等
こんなに多重ポインタを作ることはまずありえませんが、『関数ポインタのポインタ』は別のスコープで関数ポインタの内容を動的に変えるのに使えそうです。
関数の引数に関数ポインタのポインタを渡して見る例です。
#include <stdio.h> int add(int x,int y){ return x+y; } //足し算をする関数 int mlt(int x,int y){ return x*y; } //掛け算をする関数 typedef int (*pfunc_t)(int,int); void set_1(pfunc_t*f){ *f=add; return; } void set_2(pfunc_t*f){ *f=mlt; return; } int main(){ pfunc_t pf; set_1(&pf); printf("add %d\n",pf(99,1)); set_2(&pf); printf("mlt %d\n",pf(33,3)); return 0; }
pfunc_t pfの宣言外部で、pfの内容を操作しています。 実行結果は次のようになります。
add 100 mlt 99
(注意)ここはCの範囲ではありません、Cではできないことをやってます。
いやむしろなぜCの項に用意したんだよww(関数ポインタの有用性をアピールしたいのでしょうが)
関数Functionをオーバーロードしています。
#include <stdio.h> void Function(long int){ printf_s("long intはintよりも大きいから豊乳じゃと!? \n"); } void Function(short){ printf_s("shortはintよりも小さいからは貧乳じゃと!? \n"); } void Function(void){ printf_s("ツルペタ始まったな\n"); } int main(){ { void (*pfunc)(short); pfunc=Function; //コンパイル時に型が自動推論される pfunc(short()); } { void (*pfunc)(void); pfunc=Function; //コンパイル時に型が自動推論される pfunc(); } return 0; }
実行結果は
shortはintよりも小さいからは貧乳じゃと!? ツルペタ始まったな
となります。
メンバ関数のポインタをとってみましょう。
class CFoo{ public: void func_v(){ printf_s("仮想関数だと!?\n"); } static void func_s(){ printf_s("ぼくは静的関数ちゃん!\n"); } }; /* .... */ void (*pfunc)(); pfunc=CFoo::func_v; //ここでコンパイルエラー
CFoo::func_vはCFooのインスタンスがないと実行できない関数です。 staticがないので当たり前ですね。
では以下はどうでしょうか。 インスタンスを生成してメンバ関数をとってみます。
void (*pfunc)(); CFoo a; pfunc=a.func_v; //error C3867 , error C2440
これもエラーです。エラー内容を見てみましょう。
:error C3867:"CFoo::func_v"のポインタを取得したかったら"&CFoo::func_v"ってやってください><
どうやら、ポインタを取得するには"a.func_v"ではなく、"&CFoo::func_v"としないと駄目なようです。
でもこれではせっかくインスタンスを作ったのに意味がありませんね。
:error C2440:"a.func_v"は"void(__thiscall CFoo::*)()"型だから"void (*)()"型には代入できません><
という事は"pfunc"を"void(__thiscall CFoo::*pfunc)()"と宣言し、"&CFoo::func_v"を代入すれば解決でしょうか。
結論から言えば、理論的にはこれで解決です。コンパイルできます。実行もできます。
ただし、重要な問題が発生します。
それは、この場合のpfuncは『関数ポインタ』ではなく『メンバ関数ポインタ』だという事です。なので関数ポインタのような使い方はできません。
メンバポインタについては『関数ポインタのC++に対するアプローチ』という趣旨からほぼ外れるので、ここでは説明を割愛します。各自Google先生に訊いて下さい。分かりやすい説明が出てくると思います。
そもそもソケットて何?
皆さんはインターネットをしたことがありますよね?え、無い?そんな分けないでしょ。
今あなたはインターネットでこのページにたどり着いたんですから。
ちなみにそのインターネットと言うのは1969年にアメリカ国防総省がARPANETというものを作ったのがその起源とされていて・・・
(以下省略)
と言うものです。
さてここで本題のソケットですが、ソケットと言うのは簡単に言えばアプリケーション同士で通信するときに使うライブラリです。
ここでは皆さんが使っているであろうWindowsを例に説明したいと思います。ちなみに「私はLinuxを使ってるわ」とか、「自分はBSDユーザです」、「おいどんはSolarisユーザーでござんす」と言う人も居るかもしれない。
しかし基本的にWindowsのソケット通信もUNIX系のソケット通信もほとんど同じです。
移植するのは極めて簡単です。だからこの説明も一応有用だと思います。
まあと言うかこの話は逆でUNIX系のOSであるBSDのソケットをWindowsがパクったんです。だからそっくりなんです。
というわけで本題です。
まずWindowsでソケットを使いたい場合は
winsock2.hとWSock32.libをリンクする必要があります。
そしてプログラムの先頭でWSAStartupと言う関数を実行する必要があります。
これはWinsockをスタートアップする関数です。
int WSAStartup( WORD wVersionRequested, LPWSADATA lpWSAData );
という引数を取ります。第一引数のwVersionRequestedはWinsockのバージョンです。
もしwinsock2.hの2に疑問持った人が居られたらあなたは実にセンスが良いかもしれません。
この2はバージョンをあらわしているんです。
では具体的にこの引数には何を渡せば良いのか。
winsock2の2がバージョンのことだってあんたさっき言ったじゃん。だから2を指定するんじゃないの?
と思うかもしれません。でも実際には下位互換性というものがあり、winsock2でもwinsock1は使えます。
もちろんその逆はできません。
ではとりあえず第一引数には何を指定すれば良いかと言うと、MAKEWORD(1, 1)と言うものを指定すれば良いと思います。
ん?と思った方が居るかもしれません。どうしてバージョン指定なのに変なものが出てくるのか。
答えはwVersionRequestedにはWORD型の変数で上位バイトにマイナーバージョン、下位バイトにメジャーバージョンを指定しなければならないからです。
そしてこのMAKEWORDでは上位バイトと下位バイトをそれぞれ設定してくれるのです。ちなみにこれはマクロで
#define MAKEWORD(a, b) ((WORD)(((BYTE)((DWORD_PTR)(a) & 0xff)) | ((WORD)((BYTE)((DWORD_PTR)(b) & 0xff))) << 8))
という形で宣言されてます。というわけでこのように指定できるわけですね。
それでは次の引数のlpWSADataですがこれはソケットに関する情報を格納するためのWSADATA型変数へのポインタです。
つまりWSAStartupを使うには
WSADATA wsadata; WSAStartup(MAKEWORD(1, 1), &wsadata);
と言う感じです。
ちなみに0なら失敗です。
これでWinsockが使えるようになりました。
続きはまた今度
やあやあ というわけでパパ、調子に乗ってHow to talk 2chなんて項目作っちゃったぞ これ理解するのにはソケットプログラミングができないといけないから。
とりあえず2chに書き込むには皆さんご存知bbs.cgiというのに頼まなきゃいけません。 まあ簡単に言うとここに書き込みたいデータをポストするだけなんですけどね。 具体的に言うとこんな感じ
POST /test/bbs.cgi HTTP/1.1 //まあこれは言わずもがなで、ポストしたいcgiだ Host: //これはwwwww.2ch.netみたいなホスト指定すればおk Content-Length: //これは書き込みデータのサイズだ Cookie: //これにはクッキーを入れなきゃいけない Referer: //これは、まあよくわからんがhttp://wwwww.2ch.net/news4vip/見たいな感じで指定すればおkだ User-Agent: //ここにはユーザーエージェントを入れるんだ。ちなみにここを適当に書いて運営で表示してみるってのも面白いかもしれない Connection: close //まあこれもよくわからんがとりあえずこうしとけばおkだ [改行] //そしてここに書き込む内容を書けばおk
書き込む内容は
hana=mogera&bbs=%s&key=%s&FROM=%s&mail=%s&submit=書き込む&time=1&MESSAGE=%s
みたいな感じだまあ大体見ればわかると思うbbsにはnews4vipとか入れてkeyには書き込むスレッドの番号とか入れればいい ちなみにこれら全部URLエンコーディングしなきゃならない まあ別にしなくてもちゃんと書き込めたりするけど推奨しない
まあ基本これらの内容をポストすれば良いわけだ
だが実際一度ポストしてみればわかるようにクッキーを要求される だからもう一度クッキー込みでポストしなきゃいけないわけで 一度上のような内容でポストしてみればSet-Cookie:とか言うのでこのクッキーが足りないと要求してくるとこで Set-Cookie:以降の部分をそのままコピーしてしまえば良いんだ
とりあえずサンプルまでに俺の作った書き込みプログラムがある まあ出来の方は期待するな あと悪用するな ライセンスは知らん 自己責任だ。俺は一切責任を負わない これでアク禁になろうがブタ箱に入ろうが知ったこっちゃ無い http://www8.uploader.jp/dl/vipprog/vipprog_uljp00252.zip.html にソースを置いておいたので ちなみに流れても俺は知らん
入門 http://homepage3.nifty.com/mmgames/c_guide/
ポインタ http://kmaebashi.com/programmer/pointer.html
診断室 http://www.pro.or.jp/~fuji/mybooks/cdiag/index.html