変数の宣言と定義について
C++の勉強(3)
Effective C++とは関係ない話題.ファイル分割と列挙型enum
のあたりでコンパイルエラーが頻出したので,そのことについて.
問題の詳細
一番の問題点は,変数の「宣言」と「定義」をよく理解できていなかった点.これを整理したら一発で修正できた.順を追って説明する.
変数の宣言と定義
マイクロソフトの宣言と定義 (C++)を見るのが一番間違いがない.自分でこの二つの言葉について説明しようとしたが,語彙力語りなさすぎて無理だった. 理解していることを簡単にまとめると,そのコードによってデータを伴う (メモリを食う)ような命令が「定義」で,データが伴わない(メモリは消費されない)ような命令を「宣言」と呼ぶと考えると良い.以下に,簡単な例をいくつか示す.
extern bool dec //宣言(externは宣言するためのやつ.) int a; //定義(明示的に初期化されていないが何らかの値がメモリに「保存」されている) int b = 1; //定義(初期化された値がメモリに「保存」されている) double c = 0.1; //定義(型の違いは消費されるメモリのサイズの違いでしかない.定義と宣言の違いで重要なのは,メモリが消費されるか否かなので,サイズの違いは本質的には関係ない.) enum d {d1, d2, d3}; //宣言(列挙型として,新たにdという「型」を作成したと捉える.その型のとる範囲が{d1, d2, d3}のみであることを意味するのがこの宣言の意味.) enum e {e1, e2, e3} e_def; //定義(列挙型としてeという「型」を作成し,e_defという変数を定義した.)
列挙型について
すでに上の例に出したが,c++
ではenum 型名 {シンボルの列}
で新たな型を「作る」ことができる.さらに,上の例のenum d {d1, d2, d3}
という型d
は{d1, d2, d3}
という値しか取れなくなる.
組み込み型のbool
がtrue, false
しかとれないのと同じで,これをenum
を用いてわざわざ書いてみると,enum bool {true, false}
となる.これと同じノリ.使う分にはこの知識だけあればいける.
より厳密な話は,読みにくいが列挙型 [C++]が確実でしょう.
例にあげたようにこの型の変数を定義するにはenum Type_Name {Symbol0, Symbol1, Symbol2, Symbol4} Variable_Name
のようにしてスペースを3つ挟んだ4つのまとまりが必要.
ファイル分割
クラスの定義が長い場合には,ヘッダファイルとソースファイルを分割することになる.基本的な思想としては,ヘッダファイルには諸々の宣言,ソースファイルには諸々の定義を書けというのがいろいろ調べてくうちに刷り込まれた.例えば,以下の仕様を持つクラスPerson
を作ってみる,
* public
なメンバ関数void Talk_To()
を持ち,`"Hi."'と返す.
これを満たすクラスのヘッダファイルは次の通り.
//Person.hpp #include <iostream> class Person{ public: Person(); ~Person(); void Talk_To(); };
ソースファイルは次の通り.
//Person.cpp #include "Person.hpp" Person::Person(){} void Person::Talk_To(){ std::cout << "Hi." << std::endl; }
これらから分かるように,ヘッダファイルに書いているのはクラスPerson
の宣言.そのメンバ関数も宣言である.一番初めの入門のときに全てのコードを一つのファイルに書いていて,可読性を高めるために関数はプロトタイプ宣言すると良いって書いてある情報がよくある.このプロトタイプ宣言と同様に,ヘッダファイルではmain
関数で使われる関数の紹介をしている.で,ソースファイルで行っているのは,Person
クラスのメンバ関数の定義である.Person
クラス自体はもちろん定義されていない.Person
クラスを使用する際には次のmain
ファイルのようなものからコールする.
//main.cpp #include "Person.hpp" int main(){ Person *P1 = new Person(); //これが定義. P1->Talk_To(); delete P1; return 0; }
結果はHi.
のみである.
結局何が言いたいかって言うと,変数の宣言・定義とファイル分割は同値だということ.
分割ファイルのコンパイル
main.cpp
,Person.hpp
,Person.cpp
に分割されたファイルをコンパイルするには,まずPerson.cpp
とmain.cpp
からオブジェクトファイルPerson.o
とmain.o
をビルドする.単なるビルドはgcc
ならg++ -c Person.o
とすればPerson.o
が作られる.が,いまはPerson.cpp
はPerson.hpp
をインクルードしているので,オプションとしてPerson.hpp
のパスをコンパイラに教える必要がある.これには,-I ./
とすれば良い.-I
がインクルードファイルのパスを示すためのフラグ.最後に,すべてのオブジェクトファイルをまとめて実行ファイルを作成する.これには,g++ -o Person main.o Person.o
とすれば良い.これにより実行ファイルPerson
が作成される.
g++ -c Person.cpp -I./
g++ -c main.cpp -I./
g++ -o Person Person.o main.p
休憩
疲れたし長くなったので次のに分けて書く.