はじめに
C言語の講義やサイトでは、初歩的な段階で終わるものばかりすぎて、基本的な文法だけ学んで、構造体や列挙体なんて一度習っただけで使ったことない人が多い気がする。
ましてや関数ポインタとか、constとかexternとか、習わないのではなかろうか(自分の受けた講義では習わずに終わりました)
基本的にここで解説するのではなく、概説のみ書いて、リンクを辿ってもらう方針で書きます。
自分は普段はSwiftなどを書いているのですが、大学の授業でCをすることになって、どうせならと、鋭意学習中です。やはりC言語は奥深すぎて、とても「C言語できる」なんて言えません・・・
ファイル分割
include
#include <>
#include ""
includeの違いがわからない場合はこの辺を参考に→Oracle:2.14 インクルードファイルを指定する方法
.hファイルと.cファイル
内容はhoge.cに実装して、ヘッダファイルhoge.hには、関数の一覧とか構造体の定義とかを書きます。(ファイル名は同じ)
main.c, hoge.c, hoge.h
という3つのファイルがあった時、基本的な実装では、
main.cとfile.cでfile1.hをインクルードします。.cをインクルードすることはありません。
参考サイト:MMGames:分割の定石
このサイトの第3項で紹介されている#ifndefとかの方法が一般的だと思います。
externとかも思えておいてください
複数ファイルあるときのコンパイル
gcc main.c file.c
のように、依存ファイルをすべて指定します。(.cのみ)
makeコマンド
gcc -o a.out main.c
みたなのを毎度やっていると面倒なのと、ファイル数が増えていくと全てをコンパイルするのは冗長(更新したファイルだけでいい)なため、makeというコマンドがあります。
書き方
# Makefile
main: main.c Vector3.c
gcc -o main.out main.c Vector3.c
このようなファイルをプロジェクトと同じフォルダに入れておけば、makeとコマンド入力するだけで、自動的にMakefileが読み込まれ、コンパイルコマンドが実行されます。(別のファイルの指定とかも可能です)
どのファイルが更新されたかによって実行するコマンドを変えることで、一部分のみコンパイルさせて、コンパイル時間を短縮することも出来ます。個人レベルでそこが問題になることはあまりないと思うので、ここでは割愛します。
C言語のバージョン
C言語はあまり変わらないとはいえ、時代とともに規格変わります。1999年にC99が、2011年にC11が発表され、2015年現在はC11が最新です。おそらく大抵のコンパイラはC11に対応しているかと思います。
バージョンや、どの規格に準拠しているかによっては、たとえば
for(int i=0; i<10; i++){
みたいに()内で変数を定義するのが許されなかったり、変数定義は各スコープ({}のこと)の一番上で定義しなければいけなかったりします。mainの後ろにreturn 0;を書かないとエラーになったり・・・
教科書などに載っているCはかなり古いです。
規格はISOから購入できますが、高い(CHF 198,00)ので、まとめてくださっている方々に感謝しながら、参考にしましょう。
参考:C言語の最新事情を知る > C99の仕様
このリンク先のサイトに、C11の仕様なども書かれています。とても参考になります。
その他授業ではあまり使わなさそうなもの
仕様
メモリ管理
外部変数(グローバル変数)は初期化しなくても、0が代入されます。配列も全て0に初期化されます。ただし、自動変数(ローカル変数)は初期化されません。また、関数の実行が終わると破棄されるので、自動変数へのポインタなどは返したりしても意味をなしません。
比較の順番
ifなどによる比較は左から行われる。よって、stをある構造体のポインタ、nextはそのメンバの1つとして、
if ((st->next != NULL) && (st != NULL)) {
このようなNULLチェックは意味を成さない(NULLについては後述)。or条件なら1つでも成立すれば右側の条件は判定されないので、これを使ってうまく書く必要がある
各演算子の優先度はここでは言及しません
const
const int vect[3] = {0};
配列初期化の時、全部書かなくても{0};とすると全て0になります。C99の仕様、のリンクにその他便利な初期化方法も記載されています。
constをつけると、定数になります。後から書き換えることが出来ません。書き換えない変数にはこちらを使うべきです。
static
#include <stdio.h>
int generateId() {
static int id = 0;
return id++;
}
int main() {
for(int i=0; i<10; i++) {
printf("%d,", generateId());
}
return 0;
}
0,1,2,3,4,5,6,7,8,9,
関数内で定義、関数内でのみ使用可能だが、関数を抜けても値は保持される
明示しなくても、自動的に0に初期化される
キャスト
#include <stdio.h>
int main() {
double doubleValue=1.0;
printf("%d", (int)doubleValue);
return 0;
}
(型)を前につけると、型を変換することが出来る(キャストと呼ぶ)。
NULL
int* val = NULL;
ポインタ型変数にNULLを代入することが出来ます。
汎用ポインタ
void* name;
で定義される
ただアドレスとして受け取りたいときに使える
自己参照構造体
struct ListCellStruct {
int id;
struct ListCellStruct* next;
};
typedef struct ListCellStruct ListCell;
構造体へのポインタも作ることが出来ます。アクセスには、
next->id
のように書きます。
ポインタであれば、メンバとして自分の型へのポインタを持つことが出来ます。線形リストを作る時などに力を発揮します。
関数ポインタ
#include <stdio.h>
int generateId() {
static int id = 0;
return id++;
}
int main() {
int (*func)() = generateId;
for(int i=0; i<10; i++) {
printf("%d,", func());
}
return 0;
}
関数へのポインタも作ることが出来ます。
これを構造体に持たせれば、APIオブジェクトみたいなのは作れそう。でも、インスタンスメソッドみたいなのを作りたくても、関数へは外部に置くか、引数として渡すことしか出来なくて、インスタンスメソッドの実装はむずかしい。
引数・返り値が同じなら、ラムダ式っぽいのは作れる。
細かいこと
以下のサイトが非常に参考になる。
ビリーの講義
17, 18章の動的なメモリ管理についての細かいことは読むべきかもしれない。
おまけ
練習がてら、Listオブジェクト(線形リスト)とか、Vector3クラスとか、Matrixクラスとか、なんちゃって実装しました
List.c
List.h
ListTest.c
clang List.c ListTest.c
./a.out
Vector3.c
Vector3.h
Vector3Test.c
clang Vector3.c Vector3Test.c
./a.out
Matrix.c
Matrix.h
MatrixTest.c
clang Matrix.c MatrixTest.c
./a.out
おなじgitリポジトリにいろいろありますが、関係ないファイルなので無視してください。
おわりに
もっと多くを書こうかと思ったのですが、リンク先を順に読んでいくと多分これでもかなりの分量になると思います。
何を書こうか、は書きながら考えていたので、そこ書いてここかかないのかよ、というのはあると思います。是非コメントで、おしえてください。