Java屋のためのC入門
現在Java屋(って理由だけで上記のタイトル)。
大昔にCやってたけど、なんか復習してみることにした。
環境構築
Windowで。Macとかもってない。Linuxは、、、環境作んのめんどい。
Cコンパイラは毎度お馴染みGCC。
Windows環境だとGDBっていうデバッガが含まれたMinGWってのがあるみたい。
IDEがほしいのでEclipseにする。Javaで慣れてるから。
Pleiades All in One 4.3のC/C++を入手。
Hello World
毎度お馴染みハローワールド。
- Eclipse起動
- パースペクティブはC/C++
- 「新規(C)」→「Cプロジェクト」→プロジェクト名を適当に入れる→「実行可能」の「Hello World ANSI Cプロジェクト」の「MinGW GCC」を選択→「完了」
- プロジェクト名は「HelloWorld」でやってみた
- この時点でHello Worldが出力されているコードが作成されている
- プロジェクトを右クリック→「構成のビルド」→「アクティブにする」→「2 Release(2)」
- プロジェクトを右クリック→「構成のビルド」→「すべてビルド」
- プロジェクトを右クリック→「実行」→「ローカル C/C++ アプリケーション」→再生マークの「[プロジェクト名].exe」
- Eclipseコンソールに「!!!Hello World!!!」と出る
コードは以下。
■HelloWorld.c
#include <stdio.h> #include <stdlib.h> int main(void) { puts("!!!Hello World!!!"); return EXIT_SUCCESS; }
コマンドラインでコンパイル・実行
パスは通しておいてね。
- Cドライブ直下にtempフォルダを作って、上記のHelloWorld.cを置く
- コマンドプロンプトを起動する
- 「>cd /d C:\temp」
- 「C:\temp>gcc -O0 -g3 -Wall -c HelloWorld.o HelloWorld.c」
- 「HelloWorld.o」ファイルができる
- 「C:\temp>gcc -oHelloWorld.exe HelloWorld.o」
- 「HelloWorld.exe」ファイルができる
- 「C:\temp>HelloWorld.exe」
- コンソールに「!!!Hello World!!!」と出る
ん?「.o」ってなんだ?昔コンパイルしたら「a.out」じゃなかったっけか?
- 疑問1「.oとは?」
- A. オブジェクトファイルっていうんだって。「.exe」とか「.out」とかはこれだけで実行可能なんだけど、「.o」はコンパイルしただけでこれだけで実行可能ってわけではないファイル。
- 疑問2「.outじゃなかったっけ?」
- 疑問3「昔はa.outじゃなかったっけ?」
- A. 「gcc HelloWorld.c」だけ打つと「a.exe」ができた。「.out」じゃないのは前述通り。
型
型の大きさ
型の大きさはコンパイラによりけり。
型 | Cでの規定 | GCC 3.4.5(MinGW) |
signed char | -127 〜 127 | -128 〜 127 |
unsigned char | 〜 255 | 〜 255 |
short int | -32767 〜 +32767 | -32768 〜 32767 |
unsigned short int | 〜 65535 | 〜 65535 |
int | -32767 〜 +32767 | -2147483648 〜 2147483647 |
unsigned int | 〜 65535 | 〜 4294967295 |
long int | -2147483647 〜 2147483647 | -2147483648 〜 2147483647 |
unsigned long int | 〜 4294967295 | 〜 4294967295 |
long long int | -9223372036854775807 〜 9223372036854775807 | -9223372036854775808 〜 9223372036854775807 |
unsigned long long int | 〜 18446744073709551615 | 〜 18446744073709551615 |
型の定義
自分で型を作れる。
typedef int T; T x;
「typedef <基本型> <作りたい型名>」でどの基本型に沿った独自の型を作れる。
上記の例だと、int型の代わりにT型を使えるようになる。扱いはint型に準ずる。
typedefがよく使われる例として、1バイトを表す型名がある。Cには1バイトを表す型はない。unsigned char型の大きさは1バイトだが、そのunsigned charという名前からバイト型であることは直接判断できない。そのため、1バイトの大きさを持つ型ということで、「typedef unsigned char BYTE;」のように宣言されることが多い。
配列
int array[3]; array[0] = 0; array[1] = 1; array[2] = 2; // 上記を一行で書くと「int array[] = {0, 1, 2};」 int i; for (i=0; i<3; i++) { printf("array[%d]の値は%dです。\n", i, array[i]); }
実行すると下記。
array[0]の値は0です。 array[1]の値は1です。 array[2]の値は2です。
構造体
Javaでいうと、「メソッド無しでフィールド変数だけのクラス」みたいなもの。
#include <stdio.h> #include <stdlib.h> struct kozo { int a; int b; }; // コロンがある void printKozo(struct kozo x) { printf("構造体kozoのaの値は%d、bの値は%dです。\n", x.a, x.b); } int main(void) { struct kozo y; y.a = 1; y.b = 10; printKozo(y); }
結果は下記。
構造体kozoのaの値は1、bの値は10です。
ポインタ
C言語といえばポインタ。
■例
#include <stdio.h> #include <stdlib.h> int main(void) { int a=1; int* pa; pa = &a; printf("aの値は%dです。\n",a); // ① printf("aのアドレスは%dです。\n",&a); // ② printf("paの値は%dです。\n",pa); // ③ printf("*paの値は%dです。\n",*pa); // ④ }
■結果
aの値は1です。 aのアドレスは2293536です。 paの値は2293536です。 *paの値は1です。
ポインタは
- 「ある値を格納している物理メモリの位置(番地、アドレス)」を保持する変数
という理解でとりあえずOKかなぁ。
ポインタは型にあわせて宣言する。
例えば「int型のポインタ」は下記。
int* pa;
値は物理メモリ上のある位置に格納される。
例えば、「int a=1;」と宣言した「1」を格納している場所(番地)は「&a」で取得することができ、これを「アドレス」という。
ポインタは「ある値のアドレスを保持する変数」なので
pa = &a;
のように代入する。(②と③は同じだね)
「pa」からアクセスするアドレスに格納されている値を取り出すには「*」をつけて「*pa」と書く。(①と④は同じだね)
ある値を物理メモリ上の一ヵ所に保存しておいて、それに対してソースコード上の各所からポインタでアクセスすればメモリを節約できる。同じ値をたくさんコピーする必要はない。
voidのポインタ
voidのポインタは他の型のポインタと相互に変換することができる。
例えばint型のポインタをdouble型のポインタへ渡すと強制型変換を行わない限りエラーが発生する。
int a; int* pa = &a; // 問題なし double* pd = &a; // エラー(int* を double* へ変換できない) void *pv = &a; // 問題なし
配列とポインタ
配列は実はポインタなんだ!(イミフ
int array[] = {1, 10, 100}; int i; for (i=0; i<3; i++) { printf("array[%d]の値は%dです。\n", i, array[i]); } printf("*arrayの値は%dです。\n", *array); printf("*(array+1)の値は%dです。\n", *(array+1)); printf("*(array+2)の値は%dです。\n", *(array+2));
実行結果は下記。
array[0]の値は1です。 array[1]の値は10です。 array[2]の値は100です。 *arrayの値は1です。 *(array+1)の値は10です。 *(array+2)の値は100です。
「int array[];」って宣言すると「array」は「array[0]へのポインタ」になってる。
だから、「*array」の結果は「array[0]」と同じなんだ。
じゃあ、「array[1]」のポインタは?→それは「array+1」でおk。
値を参照したければ「*(array+1)」。
じゃあ、「array[n]」のポインタは?→それは「array+n」でおk。
値を参照したければ「*(array+n)」。
なんでこうなってんのかは知らん。
ループで書く。
int array[] = {1, 10, 100}; int i; for (i=0; i<3; i++) { printf("array[%d]の値は%dです。\n", i, array[i]); } for (i=0; i<3; i++) { printf("*(array+%d)の値は%dです。\n", i, *(array+i)); }
結果は下記。
array[0]の値は1です。 array[1]の値は10です。 array[2]の値は100です。 *(array+0)の値は1です。 *(array+1)の値は10です。 *(array+2)の値は100です。
多次元配列とポインタ
ポインタのポインタだ!(イミフ
int array[2][2] = {{1, 10}, {100, 1000}}; int i, j; for (i=0; i<2; i++) { for (j=0; j<2; j++) { printf("array[%d][%d]の値は%dです。\n", i, j, array[i][j]); } } for (i=0; i<2; i++) { for (j=0; j<2; j++) { printf("*(*(array+%d)+%d)の値は%dです。\n", i, j, *(*(array+i)+j)); } }
結果は下記。
array[0][0]の値は1です。 array[0][1]の値は10です。 array[1][0]の値は100です。 array[1][1]の値は1000です。 *(*(array+0)+0)の値は1です。 *(*(array+0)+1)の値は10です。 *(*(array+1)+0)の値は100です。 *(*(array+1)+1)の値は1000です。
参考:http://www.atmarkit.co.jp/fcoding/articles/c/01/c01a.html