以下では例として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/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の答えを入れる」と言う意味になる。
そして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
となるはずだ。前述のとおり、変数は重要だからしっかりマスターしよう。
今回は繰り返し同じ処理を実行したいときに使うループ制御。
コードを簡潔にすることが出来るが、下手に使うと無限ループに陥ったり、かえって複雑になってしまったりすることもある。
注意しよう。
最も使われていると思われる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を代入している。
「 ○ < × ; 」というのは処理を続けるための条件である。○が×未満の場合、処理(この場合では☆☆;)を行うということである。
そして「 ×++ 」だが、処理が終わったあとに行う文である。
よって、7行目は「iに0を代入して、printf( "ただいまループ%d回目です。\n" , i +1 );をiが5未満の間、繰り返し、その後、iに1を足す」
ということになる。
ここで注目してもらいたいのは、printf関数の引数にiを使っているということである。
iは繰り返しの度に動的・規則的に変化していくから、iを使うことで繰り返しの回数を処理に組み込むことが出来る。
従ってこのコードの実行結果は、
ただいまループ1回目です。 ただいまループ2回目です。 ただいまループ3回目です。 ただいまループ4回目です。 ただいまループ5回目です。 ループから抜けました。
となる。
処理の途中で抜け出したい場合はbreak;と記述すればループから抜けることが出来る。
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文ならこのような場合に便利である。
キーボードから文字、数字の入力を求め、処理します。
これにより、プログラムの幅が飛躍的に広がります。
以下のコードを見てください
#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 *students, taroh; 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を返している。{{br}}
ここで必ず気をつけるべきことは、
・関数の宣言の際に宣言した引数の型と、関数呼び出しの際に渡す引数の型を一緒にする
・関数の宣言の際に宣言した返り値の型と関数内でreturn文を使って返す値の型を一緒にする
・引数を取らない関数を作るときは必ず ( ) の中にvoidを記述して、○○(void)という形にすること
ということである。
従って上記のコードの実行結果は、
11
と表示される。
(注意!)これは初学者向けではありません。最低でも関数とポインタの知識を得てからよむこと。
関数のアドレスを保持するポインタです。 実行する関数を動的に変えられたり、関数の配列を作ったり、関数の引数に関数ポインタ・・・・など、いろいろな事ができるようになります。
まずは関数の宣言から見ていきます。
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
関数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ユーザです」、「おいどんはSlarisユーザーでござんす」と言う人も居るかもしれない。
しかし基本的に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)*1 | ((WORD)*2
という形で宣言されてます。というわけでこのように指定できるわけですね。
それでは次の引数のlpWSADataですがこれはソケットに関する情報を格納するための変数です。
つまりWSAStartupを使うには
WSDATA wsdata;
WSAStartup(MAKEWORD(1, 1), wsdata);
と言う感じです。
ちなみに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:以降の部分をそのままコピーしてしまえば良いんだ 俺は
char *strnstr (char *cs, char *ct) {//文字列検索して発見された文字列の後のポインタを返す
int i, j;
for (i = 0; cs[i] != '\0'; i++) {//ヌルまでfor if (cs[i] == ct[0]) { for (j = 0; cs[i] == ct[j]; j++, i++) {//確かめる if (ct[j + 1] == '\0') { if (cs[i] != '\0') return &cs[i + 1]; else return NULL; } if (cs[i] == '\0') return NULL; } } } return NULL;
}
void GetCookie (char *ptr, char *cookie) {//Set-Cookieの部分を検索
char *str; int i, j;
str = strnstr(ptr, "Set-Cookie:"); if (str == NULL) { *cookie = '\0'; return; }
for (i = 0, j= 0; str[i] != '\n'; i++, j++) cookie[j] = str[i]; cookie[j++] = ';';
GetCookie(&str[i], &cookie[j]);
return;
}
こんな関数を作ってみたわけだ まあこんな感じでCookieとかの設定すれば書き込めるわけだ
てかこの文章読んで理解できる奴っているのだろうか 今読み返してもまったくもって意味不明だ
入門 http://homepage3.nifty.com/mmgames/c_guide/
ポインタ http://kmaebashi.com/programmer/pointer.html
診断室 http://www.pro.or.jp/~fuji/mybooks/cdiag/index.html