変数の宣言と定義について

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}という値しか取れなくなる.
組み込み型のbooltrue, 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.cppPerson.hppPerson.cppに分割されたファイルをコンパイルするには,まずPerson.cppmain.cppからオブジェクトファイルPerson.omain.oをビルドする.単なるビルドはgccならg++ -c Person.oとすればPerson.oが作られる.が,いまはPerson.cppPerson.hppをインクルードしているので,オプションとしてPerson.hppのパスをコンパイラに教える必要がある.これには,-I ./とすれば良い.-Iがインクルードファイルのパスを示すためのフラグ.最後に,すべてのオブジェクトファイルをまとめて実行ファイルを作成する.これには,g++ -o Person main.o Person.oとすれば良い.これにより実行ファイルPersonが作成される.

  1. g++ -c Person.cpp -I./ g++ -c main.cpp -I./
  2. g++ -o Person Person.o main.p

休憩

疲れたし長くなったので次のに分けて書く.