C言語によるプログラミングに関する世界観3(データ構造)です。
ツール関係はC/C++ツールに移動しました。
システムに依存する部分はLinux(システムコール・API)やWindowsプログラミングも参照のこと。
グローバル変数とは、グローバルスコープに宣言された、どこからでもアクセスすることのできる変数のこと。
通常、関数の内部に宣言された変数のことをローカル変数、関数の外部に宣言された変数のことをグローバル変数と呼ぶ。
グローバル変数は、たくさんの関数の間でデータを共有するために使うことができるが、あまり使いすぎると、どのように動作しているのかわけの分からない「スパゲッティプログラム」ができてしまう。
また、C++のような名前空間のないC言語では、グローバルスコープに変数を宣言しすぎると、名前空間の汚染に繋がる。
なので、できるだけグローバル変数を使うべきではない。
グローバル変数の代わりとして使うことができるのが、関数の引数である。綺麗なコードを書くプログラマは、関数の引数を上手く使う。特に、関数の引数に構造体のポインタを与えることで、複数の関数の間でグローバル変数を使わなくてもデータを共有することができる。
staticは静的ローカル変数と静的グローバル変数を宣言するのに使う。
静的ローカル変数は、関数の中のローカル変数にstaticを付けたもので、関数の呼び出しが終わっても、プログラムが終了するまで、変数の値が保持されるようになる。ローカル変数でありながら、オブジェクト指向のクラスのインスタンス変数のように、「状態」を保持することができるようになる。たとえば、以下のように、関数の中で定義した静的ローカル変数をインクリメントすることで、関数を呼び出すたびに値を増やし続ける、といったことができる。
void func() { static int x = 0; x++; printf("%d\n", x); } int main() { func(); func(); func(); }
実行結果
1 2 3
この時、x = 0という初期化処理は最初の一回のみされ、xは関数の実行が終わってもプログラムの終了まで値を保持し続ける。
また、静的グローバル変数(あるいは関数)は、グローバル変数あるいは関数にstaticを付けたもので、そのファイル以外からアクセスできなくなる。グローバル変数でありながら、ほかのファイルの名前空間を汚染せず、秘匿されたプライベートなインターフェースとして仕様を隠蔽できる。関数外でstaticを使った場合、そのグローバル変数あるいは関数は静的グローバル変数(関数)となり、他のファイルからはアクセスできなくなる。
static変数は、簡易オブジェクト指向のようにも利用できる。
たとえば、オブジェクトのインスタンスを作る代わりに、静的ローカル変数を使用できる。また、静的グローバル変数を使うことで、複数の関数から共有されるデータをグローバル変数としながら、他のファイルからアクセスできなくし、隠蔽することができる。この場合、静的グローバル変数にアクセスするための特別のインターフェース関数を作って、そのインターフェースからのみグローバル変数にアクセスできるようにすることで、グローバル変数を隠蔽することができる。
ややこしいことにJavaにもstaticキーワードというものがあり、これも意味が違う。Javaのstaticメソッド(あるいは変数)は、クラスのインスタンスを作成しなくても実行できる、インスタンスの生成とは別個の関数的なメソッド(変数)である。
後日注記:staticは大人数でプログラムを記述する際には必ず必要となる機能。名前空間やオブジェクト指向のないC言語であっても、staticを使うことでいくらかそれらと同様のことをすることができる。
2022.12.05編集
自分の書いたブログ「未来のわたしの心より今のあなたへ」2021/03/27より。
リエントラントとは再入可能という意味で、あるプログラムの実行中に同じプログラムを起動できるかどうか。
ローカル変数を使う場合には、リエントラントでなくなることはないが、グローバル変数やstatic変数を使う場合、リエントラントが崩れることがある。
(Code Reading ~オープンソースから学ぶソフトウェア開発技法~ (プレミアムブックス版)を参考に執筆しました。)
「ANSI対応 C言語プログラミングレッスン 入門編」より編集して引用。
構造体は、複数の変数をひとつの変数としてまとめたり、具体的な構造体データをひとつの変数名で扱うことができるようにする。構造体にはint型やchar型などすべての基本型の変数を定義可能で、ポインタや配列も含むことができる。連結リストやツリーやグラフなどは、構造体の中に別の要素を指し示すポインタを入れ込むことで表現できる。また、構造体自体をポインタとして扱い、構造体のアドレスやポインタを関数の引数などに渡すことで、ヒープ領域に動的に格納したデータにさまざまな関数やスコープからアクセスできる。構造体の中に入れ子となった構造体を定義することもできる。
構造体を定義する際には、構造体名にsrtuctをつけ、{};の中に定義する。最後にセミコロン(;)が必要なことに気を付けよう。また、実際にこの構造体の型の変数を宣言する際には、「struct 構造体名 変数名」として変数を定義する:
struct art_scores { int id; char student_name[20]; int illust; int photo; int design; int piano; }; struct art_scores shinji;
また、構造体が最初からひとつしか必要ないような場合、以下のようにその場で宣言することもできる:
struct art_scores { int id; char student_name[20]; int illust; int photo; int design; int piano; } shinji;
この場合は、グローバル変数になってしまうが、関数の中に構造体を定義することもできる。
構造体の中のメンバにアクセスするためには、自分でつけた変数名と構造体のメンバ名を「.」でつないで、以下のようにアクセスする:
shinji.illust = 100; shinji.photo = 85; shinji.design = 60; shinji.piano = 40;
ポインタの先にある構造体のメンバは、「->」でアクセスできる。
以下は、関数の中から構造体のメンバを編集する例。
struct art_scores { int id; char student_name[20]; int illust; int photo; int design; int piano; }; void change_scores(struct art_scores *s, int a_illust, int a_photo, int a_design, int a_piano) { s->illust = a_illust; s->photo = a_photo; s->design = a_design; s->piano = a_piano; } int main() { struct art_scores shinji; change_scores(&shinji, 100, 85, 60, 40); }
オブジェクト指向のクラスとメソッドは、要はこの構造体の「.」を、構造体を作るのと同じように、同一インスタンスでのメンバ関数とメンバ変数の共有に応用したものである。
typedefを用いて型名を宣言することで、独自の型を宣言して、structをつけなくてもその型の変数を宣言できるようになる。C言語ではFILE型などがこれに当たる。
たとえば以下のようにすることができる。
typedef struct art_scores { int id; char student_name[20]; int illust; int photo; int design; int piano; } arts_t; arts_t shinji;
また、C++ではクラス名を宣言することでオブジェクトの型となるクラスを宣言できる。
Cの構造体の用途は、主に「データ構造の表現」と「ハードウェアなどの機能のマッピング」です。
たとえば、構造体は以下のような用途によく使われます。
・共通の特徴をもった変数のグループ化(たとえば関数への引数渡しなどに使う)
・決まりきったデータの表現(全ての月と日の網羅など)
・グラフィックスにおけるX軸とY軸の表現や、図形オブジェクトの位置やサイズなど、一時的な内部情報の保持
・連結リストやツリー、グラフなどの紐づけされたデータ構造
・メタ属性を持ったバッファの表現
・個人情報やひとつのグループとなったデータベース情報(掲示板の名前やコメントなど)
・準グローバル変数としての、各関数やメソッドから共有される、共有データプール
・ハードウェアデバイスのマッピング
・ネットワーク接続情報のマッピング
・プロセスやファイルを表すための低レベルシステムの内部的表現
また、ポインタとともに構造体を用いることで「動的に作成されるデータ」をC言語などで表現できます。ローカル変数ではないため、スコープから出ても消えることがなく、自分で作成・破棄のタイミングをコントロールします。
構造体の用途の例について、より正しい内容はCode Reading ~オープンソースから学ぶソフトウェア開発技法~ (プレミアムブックス版)が参考になります。上記は、この本を読んだうえで、自分なりに違う内容を書きました。
Cでオブジェクト指向は出来ないというのが通説だが、GTK+などでは、Cによるオブジェクト指向のライブラリであるGLibなどを使って、Cでもオブジェクト指向を行うことが出来る。
GTKを参照のこと。
Cの共用体(union)は、同じ記憶域を共有する複数の項目をひとつにまとめたもの。
enumは列挙型と言われるもので、値を列挙してそれぞれに数値を割り振ることで、複数の値を格納する「列挙スタイルの型を作る」ことができる。
enum animal { CAT, DOG, PIG, BIRD }; enum animal akind = PIG;
後日注記:0や1といったマジックナンバーでも同じことはできるが、あとあと理解するのがとても面倒になるため、マジックナンバーを避けることが大切。きちんとenum型を宣言して使うようにすることで、他人がそのコードを読んだ時の可読性や保守性が大幅に向上する。
何かと良く使われる、スタックとキューについて。
スタックでは、最後に入れたデータが最初に取り出される。上にお皿を積んでいき、上から取り出すようになる。
キューでは、最初に入れたデータが最初に取り出される。左から右に並んでいき、一番左の人から取り出されるようになる。
スタックとキューも参照のこと。
配列はメモリ内の連続した場所に連続した要素を格納する。リストはそれぞれの要素が前後のデータの位置関係を表すポインタによって紐づけされる。
配列とリストとハッシュも参照のこと。
以下の書籍が参考になります。
プログラミング作法(3B.データ操作)も参照のこと。
状態も参照のこと。
保守性も参照のこと。