まずは"HelloWorld"と表示するプログラム。
#include <stdio.h> int main(void){ printf("HelloWorld\n"); return 0; }
メモ帳で保存してコンパイラに食わせるなり、なんなりして実行すると
HelloWorld
が表示される。「つまらんソフトだな」なんて思うことなかれ。全てはHelloWorldから始まるんだ。
ではひとつずつ見ていこう。
#include <stdio.h>
これは、コンパイラに対するもので、「stdio.hというファイルの中を見ておいてくれ」という命令。
stdio.hとは「○○関数( { ~ } までの命令のこと)はこういうものだ」という説明が書かれているファイルで、コンパイラに見せることで関数を使うことが出来る。
stdio.hには基本的な関数がたくさん入っているので初心者から上級者まで世話になることだろう。
int main(void){ ○○ △△ }
C言語のプログラムは、main関数に書かれた命令が真っ先に実行されることになっている。
上記のコードでは○○や△△がmain関数の中身であり、○○や△△が真っ先に実行される。
printf("HelloWorld\n");
これは「("")の中のものを表示する」という関数だ。
だが、「HelloWorld\n」とではなく、"HelloWorld"と表示される。
「\n」とは「エスケープシーケンス」と言う記号の一種で、「改行」の意味を持つ。
「エスケープシーケンス」とは文字では表せない特殊なものを記号を使って表したものだ。
それから、1つの命令の終わりには;(セミコロン)を必ずつける。
return 0;
この行は今のところプログラムの終わりに書くものと思っておいてほしい。
本来はちゃんとした意味があるのだが、それは今の段階では難しい。
関数の概念を理解すれば自然と分かるようになる。
C言語ではスペース、改行は殆ど意味を持たない。
しかし、書いた本人以外の人が見ても見やすい・修正しやすいコーディングを心がけよう。
前回の「Hello World」では
int main(void){ printf("HelloWorld\n"); return 0; }
というコードを書いたと思うが、main関数を下記のように書いても全く問題はない。
int main(void){printf("HelloWorld\n");return 0;}
しかし、これでは見づらい。
よって、C言語にはスペース・改行に関する明確な書式はないが、習慣としてこう書いた方が見やすいという書き方はある。
例として下記のコードを記述した。
int main(void) { printf("VIPWorld!\n"); ○○ { ××; □□ { ☆☆; } } }
ご存知のとおり、全く問題はない。しかしmain関数の中に中括弧( { と } )が入ってきたりするとmain関数の中括弧なのか○○の中括弧なのか分かりづらい。
そこでスペースを利用して下記のように書くと、
int main(void){ printf("VIPWorld!\n"); ○○{ ××; □□{ ☆☆; } } }
幾分かみやすくなった。どの中括弧に何が入っているのかが分かりやすくなった。
スペース入れ放題、といっても以下の場合エラーが出る
p r i n t f ( " hello world " );
関数名、後に出てくる変数名などにスペースを入れてはいけない。
printf ( " hello world " ) ;
↑これならOKである
他の言語では、改行、スペースに明確な決まりがあるものも多いが、スペースと改行を使って見やすくするということに関しては同様である。
そんな場合でも有効だ。
上述のとおり、明確な決まりというものはない。自分で見やすい・分かりやすいという書き方を見つけれてくれ。
毎回同じ文字列を出力するだけなら、プログラムどころか判子でも買え。コンピュータといえば計算だ。
今回は加減乗除( + - ÷ × の4つ)をするプログラムを作る。
今回扱う「変数」はとても重要なのでしっかり覚えよう。
#include <stdio.h> int main(void){ 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 0 2^31 - 1 までで、計算するとだいたい±20億程度。
小数型については難しいのでここでは省きますが、通常doubleにしておけばまず大きさが足りなくなることはありません。
(要は、初心者のうちは整数なら int、小数なら double を使っておけば間違いないということ。)
注意
今回は繰り返し同じ処理を実行したいときに使うループ制御。
コードを簡潔にすることが出来るが、下手に使うと無限ループに陥ったり、かえって複雑になってしまったりすることもある。
注意しよう。
最も使われていると思われるFor文。
下記のソースを見てほしい。
#include <stdio.h> int main(void){ 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と等しくない |
i>0 && i<10 | iが0より大きく、かつ10より小さい |
i>=20 || i<=-20 | iが20以上、またはマイナス20以下 |
特に5番目のものは i = 2 と書いてしまいがちなので注意が必要だ。(さらに悪いことに、こう書いても別の意味として正しいのでコンパイルエラーにならない)
これ以外にも、実は条件には「式であればなんでも入れられる」のだが、まだ難しいし必要ないのでここでは省略する。
whileを使うことでも繰り返し処理が出来る。
(例)
#include <stdio.h> int main(void) { int i = 0; while( i < 5 ) { printf( "ただいまループ%d回目です。\n", (i+1) ); 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(条件) の後の ; を忘れがちなので注意しよう。
ちなみにdo~while文にはこのような使い方もある。
#define swap(type, x, y) do { type t = x; x = y; y = t; } while(0)
このマクロを以下のように用いて、値を交換することができる。
if (a > b) swap(int, a, b); else swap(int, a, c);
これはプリプロセッサによって、次のように置換される。
if (a > b) do { int t = a; a = b; b = t; } while(0); else do { int t = a; a = c; c = t; } while(0);
仮にこのマクロをこのように定義すると
#define swap(type, x, y) { type t = x; x = y; y = t; }
このように置換されることとなる。
if (a > b) { int t = a; a = b; b = t; }; else { int t = a; a = c; c = t; };
これは以下のような構造を持つ。なぜならばセミコロンを空文として扱うからである。
if (expr) stmt stmt else stmt
したがってブロックのないif文ではコンパイルエラーとなる。
キーボードから文字、数字の入力を求め、処理します。
これにより、プログラムの幅が飛躍的に広がります。
以下のコードを見てください
#include <stdio.h> int main(void) { 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関数で表示するということになります。
また、入力には getchar();やgetch();などが存在する。
複数の文字入力ではなくて、文字を1つ入力することができる。
ただし、getch();は#include<stdio.h>とは別に、#include<conio.h>を宣言しなければならない。
具体的に説明すると
getchar();は、1文字入力して、Enterを押すまで処理が止まる。
getch();は、1文字入力すると、すぐに反応して処理を行う。
もしも○○ならば△△という処理を実行したいなんて時に使います。
下記のコードを見てください。
#include <stdio.h> int main(void){ 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(void){ 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; }
変数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(void) { 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(void) { hoge(); return 0; } void hoge(void) { printf("関数です\n"); }
上記のコードではmain関数内でmain関数より後に記述したhoge関数を使用している。
そのため、3行目で、プロトタイプ宣言をしている。
関数の呼び出しの際に値(引数)を渡し、その値を使って処理することが出来る。
また、関数の終了時に任意の値(返り値)を返すことが出来る。
(例)
#include <stdio.h> int addition(int x, int y); //プロトタイプ宣言 int main(void) { 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行目( int *ptr; )と7行目( printf(...) )を見るとそう見えます。が、そう考えると、
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; と書きたいんだけど、こうかくとキャストだと思われてエラーになってしまう。残念。)
さて、今、ある構造体と、それを指すポインタ(その構造体のアドレスを入れる変数)を用意したとします。
struct PC{ double CPU_Hz; int Monitor_size_tate; int Monitor_size_yoko; int HDD_GB; }; main(){ struct PC myPC = {1.66, 1024, 1240, 500}; struct PC* PC_ptr = &myPC; }
このとき、PC_ptr経由でmyPCのメンバにアクセスするにはどうすればいいでしょうか?
printf("%f\n", PC_ptr.CPU_Hz);
こうでしょうか?
ポインタの基礎でやった通り、PC_ptr の中には myPC のアドレスが入っているのであって、構造体の本体は入っていません。ですからこれではダメです。
では構造体の本体はどこにあるかというと、「ポインタに入っているアドレスの指定する先」です。
その中身を取得するには、演算子 * を使うのでした。なので、正しくは以下のようになります。
printf("%f\n", (*PC_ptr).CPU_Hz);
これで「PC_ptrに入っているアドレスにある構造体(myPC)の、CPU_Hz」が取得できました。
さて、(*PC_ptr).CPU_Hz のカッコがいるのかという問題ですが、残念ながらいります。
なぜなら、 10*1.5 を正しく 10*(1.5) と解釈( (10*1).5ではなく )するようにするために、「* よりも . の方が優先度が高い」ようにC言語がなっているからです。
なので、*PC_ptr.CPU_Hz とすると、*(PC_ptr.CPU_Hz) だと解釈されてしまいます。
これがどういう問題を引きこすかというと、例えば構造体がさらに他の構造体へのポインタをメンバとして持っている場合、
(*(*ptr1).ptr2).member
のようになり、非常に煩雑でわかりにくくなってしまいます。
そこで、救世主 -> の登場です。
この演算子を使うと、先ほどの例が次のようにかけます。
printf("%f\n", PC_ptr->CPU_Hz);
これで「PC_ptrに書いてあるアドレスにある構造体の、CPU_Hz」という意味になります。
この演算子が威力を発揮するのは、さきほどわかりにくくなる例で挙げたように、構造体がさらに別の構造体へのポインタを持っている場合です。次のようにかけます。
ptr1->ptr2->member
カッコがなくなってスッキリしました。ポインタが何個連なってもまったく怖くありません!
(*(*(*(*ptr1).ptr2).ptr3).ptr4).member /** -> を使わないとこうなる **/ ptr1->ptr2->ptr3->ptr4->member /** 分かりやすい!! **/
構造体とポインタを使ったプログラムでは、積極的に -> を使っていくようにしましょう。
(注意!)これは初学者向けではありません。最低でも関数とポインタの知識を得てからよむこと。
関数のアドレスを保持するポインタです。 実行する関数を動的に変えられたり、関数の配列を作ったり、関数の引数に関数ポインタ・・・・など、いろいろな事ができるようになります。
まずは関数の宣言から見ていきます。
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(void){ 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){ { 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先生に訊いて下さい。分かりやすい説明が出てくると思います。
追記:
gccで同様のコードをコンパイルしたところ、
error: ISO C++ forbids taking the address of a bound member function to form a pointer to member function. Say ‘&CFoo::func_v’
とのエラーが出ます。(訳:ISO C++では、インスタンスのメンバ関数のアドレスを、メンバ関数へのポインタを作るために取得することは禁じらています。&CFoo::func_vとしてください。)
要は仕様で禁止されている、ということです。
理由は知りません。ごめんネ
そもそもソケットて何?
皆さんはインターネットをしたことがありますよね?え、無い?そんな分けないでしょ。
今あなたはインターネットでこのページにたどり着いたんですから。
ちなみにそのインターネットと言うのは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 (消失)
にソースを置いておいたので
ちなみに流れても俺は知らん
文字列処理の関数などで、次のようなコードを見かけることがあります。
char* proccessed_text(const char* raw_text){ if(raw_text!=NULL && raw_text[0]=='~'){ ... } }
このような書き方に慣れていないと、「raw_textがNULLのときに raw_text[0] にアクセスするとまずい」から、
char* proccessed_text(const char* raw_text){ if(raw_text!=NULL){ if(raw_text[0]=='~'){ ... } } }
じゃないのか、と思ってしまいます。
ですが最初のコードで問題ありません。なぜならば、「&&演算子は、途中で偽が出た時点でそれ以降の評価をやめる」からです。(どこか一つ偽ならその論理積はそれ以上考えるまでも無く偽だから。)
例を見てましょう。
#include <stdio.h> int false(){ puts("false"); return 0; } int true(){ puts("true"); return 1;} main(){ if( false() && true() ){ puts("Hello"); } }
これを実行すると、次のようになります。
false
見ての通り、true は表示されません。つまり、&&で結ばれた第一項が偽だとわかったので、true() はもはや評価しても意味が無いと判断されて無視されています。
つまり、最初の例では、「raw_text!=NULL が偽となった時点で全体を偽として評価をやめる」から、raw_text が NULL のときには raw_text[0] にはアクセスされず、大丈夫ということになります。
&& と同様に、|| も途中で評価をやめることがあります。
|| の場合は「どこか一つでも真なら全体で真」ですから、左から順に評価していって真が出た時点でそれ以降の評価をやめます。
例
#include <stdio.h> int false(){ puts("false"); return 0; } int true(){ puts("true"); return 1;} main(){ if( true() || false() ){ puts("Hello"); } }
実行結果
true Hello
よくありそうな質問はこちら
適当に増やし過ぎだろこれ。
以下のWebサイトを読んだからといっていきなり中級者になれるわけではないですが、何も知らないと恐ろしいコードを書くことに……。
ポインタ http://kmaebashi.com/programmer/pointer.html
リファレンス http://www.cppreference.com/
Windows API http://wisdom.sakura.ne.jp/system/winapi/