$テン*シー*シー-Bootcamp2banner


(1)構造体を使う理由、malloc、freeを使う理由
(2)Objective-C言語を使う理由 その1
(3)Objective-C言語を使う理由 その2

派生から派生する
 サンプル:02-01-folderで、プログラム内容はよりオブジェクト指向らしくなったのですが、派生に関してはもう少し考えておく部分が残っています。
 この点を学ぶためフォルダを派生させたバンドルを考えてみましょう。


バンドル
 バンドルはFinder が特別な扱いをするフォルダのことです。
 Finder はバンドルのアイコンをダブルクリックされても、フォルダのように内包するファイルやフォルダを見せることはしません。
 1つのフォルダをあたかも1つのファイルのようにみせかけるもので、OS X や iOS のアプリケーションの多くが、このバンドルという形式で提供されます。

Control キーを押しながら Finder 上でアプリケーションのアイコンをクリックするとメニューが現れるの で、その中から「パッケージの内容を表示」を選べばアプリケーションバンドルが内包するファイルやフォ ルダを見る事ができます。
1


 これまでの考え方を応用するならバンドルはフォルダを派生させて定義するのが適切という事になります。
 ファイルやフォルダを内包するフォルダの特性を引き継ぎつつ、Item構造体の「コンソール上に名前を表示できる」特性では、ファイルのように自分の名前だけ表示し、内包するファイルやフォルダは表示しないようにします。
 そして、従来のように内包するファイルやフォルダを表示する特性は、あらたに「内容部を表示する:printContents」というバンドルの特性として追加する事にします。

2


 フォルダ用のFolderItem構造体の先頭にItem構造体を埋め込んだように、今度はFolderItem構造体を先頭に埋め込んだBundleItem構造体を用意しましょう。
 「内容部を表示する」特性用には構造体に関数ポインタメンバとしてprintContentsを追加します。

typedef struct BundleItem {
FolderItem super; // FolderItem構造体を先頭に埋め込む
void (*printContents)(struct Item*, int); // 「内容部を表示する」特性用関数
} BundleItem;

// バンドルオブジェクトの作成
Item* BundleItemAlloc();

 あとはバンドルを作成する関数を用意し、BundleItem構造体の関数ポインタメンバを設定すれば派生は完了です。

static void printBundleItem(Item* item, int indent)
{
・・・
printf("[%s]\n", item->name); // バンドルなのでファイル同様、名前を [] で囲んでから表示
}

Item* BundleItemAlloc() ←バンドル作成用関数
{
Item* item = malloc(sizeof(BundleItem));
item->init = initFolderItem;
item->print = printBundleItem; ←printにバンドル専用の表示関数を指定
item->dealloc = deallocFolderItem;
FolderItem* folderItem = (FolderItem*)item;
folderItem->add = addFolderItem;
BundleItem* bundleItem = (BundleItem*)item;
bundleItem->printContents = printFolderItem; ←printContentsにフォルダ用の表示関数を指定
return item;
}

 ただし解決しなければならない問題が1つあります。
 initやdealloc、add、printContentsに設定しているinitFolderItem、deallocFolderItem、addFolderItem、printFolderItemといった関数がstaticとなっているのでFolderItem.c内でしか見えない点です。
 先の記述はFolderItem.c外でおこなうとコンパイル時にXcodeから関数が無いと注意されます。

item->init = initFolderItem; ←FolderItem.cからしか見えない
・・・
item->dealloc = deallocFolderItem; ←〃
・・・
folderItem->add = addFolderItem; ←〃
・・・
bundleItem->printContents = printFolderItem; ←〃

 バンドル用のファイルを用意するのをあきらめて、FolderItem.c内で記述すれば問題は解決しますが、それでは、一番最初に話題にしたプログラムソースの保守管理という点でオブジェクト指向プログラミングを導入した意味が無くなります。
 あくまでBundleItem.cというようにファイルを分離した形でバンドルをプログラミングする方法を考えましょう。

 この場合、手っ取り早い方法はFolderItem.cのinitFolderItem、deallocFolderItem、addFolderItemからstaticを取りはぶくことです。
 注意)static指定のない関数はグローバルスコープになる事(ブートキャンプ(7))を思い出してください。
 関数名をもうすこし利用されにくい名前、例えば記述したプログラマの名前がPNameというなら
 initFolderItem → PName_Folder_initFolderItem

 といった他人が使いそうのない名前にしておき、フォルダから派生させたオブジェクトをプログラミングしようとしているプログラマだけに、その存在を教える(FolderItem.hでは公開しない)…

 それも1つの考えではありますが、ここでは、かなり乱暴ですが次のような方法を取ってみます。

static FolderItem* super = NULL; 関数利用のため、内々で利用するフォルダオブジェクト
・・・
Item* BundleItemAlloc()
{
if (super == NULL)
super = FileItemAlloc();
Item* item = malloc(sizeof(BundleItem));
*((FolderItem*)item) = *super; ←FolderItem構造体に設定された関数ポインタがコピーされる
BundleItem* bundleItem = (BundleItem*)item;
bundleItem->printContents = item->print; ←フォルダのprintはバンドルのprintContentsに移動
item->print = printBundleItem; ←バンドル専用の表示関数を指定
return item;
}

注意)ここでsuperに設定されたフォルダオブジェクトはアプリケーション終了まで存在し続け、アプリケーション終了時にOS側によって解放される事になります。

 これで、バンドルでもprint以外の関数ポインタメンバはフォルダの特性が引き継がれる事になります。
 具体的な作業はサンプル:02-02-folderを用意したので参照してください。

------------
サンプル:02-02-folder.zip

 main関数ではrootをバンドルとして作成しています。
 このようにすると、rootは今までどおりフォルダとしてファイルやフォルダを内包できますが、printでは内容部を表示しません。内容部を表示するにはBundleItem構造体に追加したprintContents関数を使います。

サンプル:02-02-folder main.c

int main(int argc, const char * argv[])
{
// rootバンドル作成
Item* root = BundleItemAlloc();
・・・
// 印刷
root->print(root, 0); ← バンドルなので内包するファイルやフォルダは表示されない

// 内容部印刷
printf("\nShow Contents\n");
((BundleItem*)root)->printContents(root, 0);
・・・

 出力結果は次のようになります。
 rootの表示が[root]となり内包したfolder A、folder B、file Aが表示されません。
 その後にあらためてprintContentsを使ってrootの内容を表示しています。

1.1


 使う側は作成時にバンドルを作る事で、従来部分を変更せずに、ファイル、フォルダ、バンドルに対応できるようになりました。そして必要ならバンドルの機能を使う事もできています。

 このような派生元の機能を派生側で利用する仕組みは、オブジェクト指向プログラミングで重要な意味を持ちます。
 バンドルでは派生元であるフォルダのinitやdeallocをそのまま使っているので、もし、フォルダ側のinitやdeallocに変更があった時も、その変更が正しく反映される事になるからです。
 その点を考えると、FileItem.cやFolderItem.cでおこなっているファイルやフォルダの初期化は適切とはいえません。
 Item構造体部分(ファイルの場合Item構造体をそのまま使っている)をそれぞれが個別に初期化しているからです。

サンプル:02-02-folder FileItem.c

static Item* initFileItem(Item* item, const char* name)
{
strcpy(item->name, name); // nameに指定される名前をコピーする
return item;
}

サンプル:02-02-folder FolderItem.c

static Item* initFolderItem(Item* item, const char* name)
{
strcpy(item->name, name); // nameに指定される名前をコピーする
FolderItem* folderItem = (FolderItem*)item;
・・・
}

 この部分をバンドル同様、派生元の機能を使うようにプログラミングしてみましょう。
 具体的な作業はサンプル:02-03-folderを用意したので参照してください。

------------
サンプル:02-03-folder.zip

 まずはItemAlloc関数を用意し、ファイルでもフォルダでもないItemというオブジェクトを用意できるようにしています。

サンプル:02-03-folder Item.c

static Item* initItem(Item* item, const char* name)
{
strcpy(item->name, name); // nameに指定される名前をコピーする
}
static void printItem(Item* item, int indent)
{
何もしない
}
static void deallocItem(Item* item)
{
free(item);
}
Item* ItemAlloc()
{
Item* item = malloc(sizeof(Item));
item->init = initItem;
item->print = printItem;
item->dealloc = deallocItem;
return item;
}

 そしてバンドルの例にならって、ファイルやフォルダでItemの初期化や破棄機能を利用するようにします。

サンプル:02-03-folder FileItem.c

static Item* file_super = NULL;← static なので BundleItem.c 側の super とぶつかる事はないが
後で説明するデバッガでうまく認識できないので別名にした

・・・
Item* FolderItemAlloc()
{
if (file_super == NULL)
file_super = ItemAlloc();
Item* item = malloc(sizeof(FileItem));
*item = *file_super;
item->printItem = printFileItem;
return item;
}



サンプル:02-03-folder FolderItem.c

static Item* folder_super = NULL;
・・・
static Item* initFolderItem(Item* item, const char* name)
{

folder_super->init(item, name);
FolderItem* folderItem = (FolderItem*)item;
・・・
}
static void deallocFolderItem(Item* item)
{
FolderItem* folderItem = (FolderItem*)item;
if (folderItem->subItems) {
・・・
}

folder_super->dealloc(item);
}

Item* FolderItemAlloc()
{

if (folder_super == NULL)
folder_super = ItemAlloc();
Item* item = malloc(sizeof(FolderItem));
・・・
}



 Runしてもサンプル:02-03-folderと変わりませんが、こちらは例えば派生元のItem構造体のメンバnameの文字列を9バイト固定とせず、可変長に変更した場合に効果が現れます。
 具体的な作業はサンプル:02-04-folderを用意したので参照してください。

------------
サンプル:02-04-folder.zip

 まずはItem構造体のnameをchar型のポインタとします。

サンプル:02-04-folder Item.h

typedef struct Item {
char* name; // 名前を可変長にする。mallocで確保
void (*print)(struct Item*, int);
・・・
} Item;


 この場合Item.cのinitItem関数やdeallocItem関数では、次のようにmallocによる必要な文字バイト分のメモリ確保が必要となります。

サンプル:02-04-folder Item.c

static Item* initItem(Item* item, const char* name)
{
item->name = malloc(strlen(name) + 1);
strcpy(item->name, name);
}
・・・
static void deallocItem(Item* item)
{
free(item->name);
free(item);
}


 しかしファイルやフォルダ、バンドル側に変更は必要ありません。派生によりsuper側の拡張はそのまま反映される事になります。

サンプル:02-04-folder FolderItem.c

static Item* initFolderItem(Item* item, const char* name)
{
super->initItem(item, name); ←変更せずとも可変長対応になっている
FolderItem* folderItem = (FolderItem*)item;
・・・
}

 実際にmain関数で文字列として9バイト以上の文字列を渡していますが、Runしてもどのオブジェクトも問題なく機能しています。

サンプル:02-04-folder main.c

int main(int argc, const char * argv[])
{
・・・
root->init(root, "over 9 bytes name root"); ← 9バイト以上の文字列を指定している
・・・
folderA->init(folderA, "over 9 bytes name folder A"); ← 〃
・・・
folderB->init(folderB, "over 9 bytes name folder B"); ← 〃
・・・
fileA->init(fileA, "over 9 bytes name file A"); ← 〃

 出力結果

[over 9 bytes name root]

Show Contents
over 9 bytes name root
over 9 bytes name folder A
over 9 bytes name folder B
[over 9 bytes name file A]
Program ended with exit code: 0


デバッガを使った確認
 関数群がどのように呼び出されているか興味がある人はデバッガを使って確認してみてください。  例えば、次のように Item.c を選んで、表示されたエディタ画面左の領域(ガターと言います)をクリックすると、マー クが付きます。

a1


 このマークはブレークポイントと言って、Run させるとアプリケーションがその場所で一時停止するようになりま す。マークを付ける作業をブレークポイントを設定すると言います。
 まずは Item.c にある initItem 関数の最初の処理にブレークポイントを設定してみてください。

a2a



 設定したブレークポイントは、ガター外にドラッグアンドドロップする事でいつでも解除できます。

a2




 設定したら実際に Run させてください。次のように initItem 関数の最初の行で一時停止したはずです。
 自動的に Debug Navigator 画面が表示された状態になり、Navigator 画面の下には関数の呼び出し状態が表示され ます。

a3


 表示された Debug Navigator 画面や Debug 画面では、ブレークポイントで一時停止した時点でのアプリケーショ ンの状況を確認できるようになっています。

a4


 そして、この関数の呼び出しリストの中から initFolderItem を選ぶと、initItem 関数が initFolderItem 関数内の、ど こから呼び出されているかもわかるようになっています。

a5


 同じようにして main 関数までたどる事ができます。

a6


 確認できたら Debug 画面の Continue program execution ボタンを押してください。
 これで一時停止が解除され Run が再開されます。

a7


 もっとも、またすぐに次の FolderItem オブジェクトの初期化により同じ場所で止まってしまいますが...

a8


きっちり調べたい人は、このまま folderB まで繰り返してもいいのですし、面倒ならブレークポイントをガター外 にドラッグアンドドロップする事でブレークポイントを解除してから Continue program execution ボタンを押して ください。

Objective C言語へ 
 ここまでの工夫により、オブジェクトは次のような特徴を持つ事になります。

1)派生元の拡張は自動的に派生先オブジェクトに引き継がれる。
2)必要なら派生先オブジェクトで派生元の関数の動きを変更できる。
3)オブジェクトを使う側が同じ指示を与えても、それぞれのオブジェクトに
    見合った結果を得る事ができる。

 そしてこれがオブジェクト指向プログラミングの特徴でもあります。

 このようにオブジェクト指向プログラミングはやろうと思えば C 言語でも実践可能です。
 ただし、本格的に C 言語でオブジェクト指向プログラミングをおこなう場合は、もう少し慎重な設計が必要でしょ う。
 ここで用意したサンプルソースは、C 言語でのオブジェクト指向プログラミングを実体験してもらうために用意さ れた玩具レベルと解釈してください。 玩具レベルでさえ、いちいち構造体の関数ポインタを用意したり設定したりと、さほどスマートに記述できている とは言えません。実用的なプログラミングにしようとするとさらに複雑さが増します。

 派生も今回のような単純なもので済まない場合もあります。
 例えば、最初に紹介した戦闘機、旅客機、装甲車、バスですが、大量輸送、戦闘という分類での派生も考慮すると、装甲車は大量輸送乗り物の派生であり戦闘乗り物の派生でもあるわけです。

3


 このような2重の派生の解決や次回以降に紹介するカプセル化など、C言語でオブジェクト指向プログラミングをおこなおうとすると、拡張や変更時の有効性以上に記述にコストがかかる事になってしまいます。
 そのため、これらをより洗練し容易に記述できるようにC++、Objective Cといった言語が考案されました。
 次回はObjective C言語によるオブジェクト指向プログラミングをみていきましょう。
AD

$テン*シー*シー-Bootcamp2banner


(1)構造体を使う理由、malloc、freeを使う理由
(2)Objective-C言語を使う理由 その1


オブジェクト指向プログラミングにおける派生とは
 例えば飛行機は空を飛び、車は地上を走ります。
 もし戦闘機、旅客機、装甲車、バスを、飛行機と車で分類せよといわれれば次のように分類する事になるでしょう。

1


 これは飛行機の特性を「主に空を飛ぶ」とし、車の特性を「主に地上を走る」と考えた分類です。
 さらに飛行機という分類を他の何らかの特性で分類したものが、戦闘機であり旅客機となります。この時の戦闘機、旅客機と飛行機の関係が派生です。
 車についても同じことが言えます。

2


 この派生という人間が日頃から慣れ親しんだ概念が、オブジェクト指向プログラミングにも取り入れられています。

 例えば、前回のファイルやフォルダは「コンソール上に名前を表示できる」特性を持つオブジェクトでした。
 ファイルの場合は名前の周りを[ ]で囲み表示オブジェクトです。
 フォルダの場合はフォルダやファイルを内包でき、それらの名前も一緒に表示するオブジェクトです。

3


 前回のサンプル:01-06-folderでの、Item構造体メンバであるprintやsubItemCountへの設定は、Item構造体のファイルやフォルダへの派生作業であったととらえる事もできます。

4


 ここで重要な点は「コンソール上に名前を表示できる」特性をもつオブジェクトから派生したオブジェクトは「コンソール上に名前を表示できる」特性を引き継ぐという概念です。

5


 前回は派生という概念の説明前だったので

 関数initFileItemを使って初期化するItem構造体だけが、構造体メンバprintに関数printFileが設定されるのでifによる分岐は必要なくなる。

といった表現をしましたが、これをオブジェクト指向プログラミングとして表現するなら

 Item構造体から派生させたオブジェクトはいずれも「コンソール上に名前を表示せよ」という希望を伝えることで、コンソール上で何らかの表示がおこなわれる。
 そのさい、malloc、initItemの組み合わせで作成されるオブジェクトはItem構造体から派生したフォルダとして振る舞い、malloc、initFileItemの組み合わせで作成されるオブジェクトはItem構造体から派生したファイルとして振る舞う。


といった表現になります。

サンプル:01-06-folder main.c

int main(int argc, const char * argv[])
{
↓Item構造体から派生したファイルオブジェクトの作成
Item* root = initItem(malloc(sizeof(Item)), "root"); 
・・・
// 印刷
root->print(root, 0); ←「コンソール上に名前を表示せよ」という希望を伝えている

 乱暴にいうなら

  • オブジェクトの振る舞いはオブジェクト自身が決める

  • 派生オブジェクトは派生元の特性を引き継ぐ


 このような概念(ルール)を守ってプログラミングするスタイルがオブジェクト指向プログラミングだと考えればいいでしょう。
 概念でしかないので、C言語でもオブジェクト指向プログラミングは十分に可能なわけです。
 では、オブジェクト指向プログラミングをおこなうためにC言語を拡張する必要性はどこにあるのでしょうか?
 それにはもう少し複雑な派生関係を考える必要があります。

複雑な派生関係をC言語で表現する
 オブジェクトの構造(C言語のなら構造体定義)や派生関係は、プログラムする者(プログラマ)が自由に設計してかまいません。
 ただし、効率的か、保守しやすいか、という点でプログラマの技量が問われる事になります。
 例えば、前回のItem構造体やその派生の設計はメモリ消費の点では適切とはいえません。
 Item構造体の設計が元々フォルダのみを考えたものだった事から、subItemsというファイルでは使わない構造体メンバを、ファイルオブジェクトを作成した時も抱えてしまっています。

6
オブジェクト指向を進めるとファイルにはsubItemCountも必要なくなるが、それはこの後で


 そのため、ファイルオブジェクトを大量に作成した場合、貴重なメモリ空間に利用されない領域が大量に発生します。

7


 この問題を解決するには、ファイルとフォルダの構造体を別々に定義する次のような設計が考えられます。

8


 具体的な作業はサンプル:02-01-folderを用意したので参照してください。

サンプル:02-01-folder.zip

 このサンプルは、ファイルや、フォルダオブジェクトを作って利用するmain.c、Item構造体定義にItem.h、ファイル用にFileItem.h/c、フォルダ用にFolderItem.h/cという構成になっています。

9


 main関数で作成するフォルダやファイルオブジェクトの構成はサンプル:01-06-folderと同じです。
 そのためRunした時のコンソールの出力もサンプル:01-06-folderと変わりません。
 異なるのはプログラムソースだけで、今回はItem構造体のメンバを「コンソール上に名前を表示する」特性に必要なものだけに絞りました。

typedef struct Item {
char name[10]; // 名前
void (*print)(struct Item*, int); // 出力用関数
} Item;

 ファイルオブジェクトの場合、この構造体で十分なので、そのままItem構造体を利用します。
 フォルダオブジェクトは、先頭にItem構造体を埋め込みsubItemCount、subItemsを追加した、新しい構造体FolderItemを定義し利用します。

typedef struct FolderItem {
Item super; // Item構造体を先頭に埋め込む
int subItemCount; // 自分が内包するフォルダやファイルの数
struct Item** subItems; // 自分が内包するフォルダやファイルの配列
} FolderItem;

 フォルダオブジェクトでは、このように定義したFolderItem構造体をmallocで確保し、戻されたメモリ先頭番地をItem構造体のポインタとして使用します。
 FolderItem構造体の先頭番地にはItem構造体を埋め込んでいるので、Item構造体の各メンバへのオフセットはまったく同じとなりItem構造体メンバのprintをそのまま利用できます。

10


 そしてFolderItem構造体として利用したいときは、mallocで戻されたメモリ番地をFolderItem構造体のポインタとして扱えばいいわけです。

11


 ファイルオブジェクトを作る時はItem構造体分のメモリ領域を確保し、フォルダオブジェクトを作る時だけFolderItem構造体分のメモリ領域を確保する事で、大量のファイルオブジェクトを作ってもメモリを無駄にする事なく、コンソールへの出力もできる事になります。

 ただし、この事により当初から利用しているItem構造体を破棄するための関数deallocItemはファイル用、フォルダ用に分ける必要がでてきます。

12


 そのためdeallocItem関数もprint同様、関数ポインタとして構造体メンバに追加しファイル、フォルダ別に用意します。また、初期化に関しても同じ事がいえるので関数ポインタとして構造体メンバに追加しています。
 最終的なItem構造体の定義は次のようしました。

サンプル:02-01-folder Item.h

typedef struct Item {
char name[10];
void (*print)(struct Item*, int);
struct Item* (*init)(struct Item*, const char*); // 初期化用関数
void (*dealloc)(struct Item*); // 破棄用関数
} Item;


 addItem関数もオブジェクト指向らしくFolderItem構造体に持たせる事にしています。

サンプル:02-01-folder FolderItem.h

typedef struct FolderItem {
Item super;
int subItemCount;
struct Item** subItems;
void (*add)(struct FolderItem*, Item*); // 内包するファイル、フォルダ追加用関数
} FolderItem;


 このように初期化、破棄もprint同様オブジェクト自身に持たせる事で、結果的にItem構造体の大きさが前回の大きさに戻っていますが、これは前回のオブジェクト指向プログラミングが限定的なものだったからです。
 今回のように初期化、破棄もオブジェクトに任せるのが

オブジェクトの振る舞いはオブジェクト自身が決める

というオブジェクト指向プログラミングの概念に則ったものといえます。あくまで

ファイルオブジェクトの大きさ < フォルダオブジェクトの大きさ

という点でメモリ効率を考えているとして読み進んでください。


 実際にはメモリ効率を考え、次のような関数ポインタだけを集めた構造体をファイル用、フォルダ用にそれぞれ1つ用意し、Item構造体やFolderItem構造体には、その構造体のポインタを用意するといった工夫をします。
 また、今回のように先頭に構造体を埋め込むようにすると、将来埋め込まれた構造体側の構成が変更すると埋め込んだ側の構造体を使うプログラムソースの再コンパイルが必要となるので、構造体の構成が変化しないよう、例えばItem構造体ならnameといった変数もメンバとして直接配置させず別の構造体として用意し、Item構造体malloc時に同時にmallocしてItem構造体に設定するよう工夫します。

struct Item;

typedef struct ItemFuncs {
void (*print)(struct Item*, int); // 出力用関数
struct Item* (*init)(struct Item*, const char*); // 初期化用関数
void (*dealloc)(struct Item*); // 破棄用関数
} ItemFuncs; ←この構造体をstatic変数として1つだけ用意する

typedef struct ItemAttributes {
char name[10]; // 名前
←ここに将来メンバを追加してもItem側の構成は変化しないで済む
} ItemAttributes; ←Item構造体ごとにmallocで確保して管理する

typedef struct Item {
ItemAttributes* attributes; ←Item構造体ごとにmallocで確保して管理したItemAttributesの先頭番地を設定する
ItemFuncs* funcs; ←static変数として用意しているItemFuncs構造体の先頭番地を設定する
} Item; ←item構造体のメンバは2つの構造体のポインタで固定され変化させない

 そこまで細かい管理をやってしまうと、どんどん説明が長くなってしまうのでここでは省略しています。

 ファイルや、フォルダのオブジェクト用にメモリ領域をmallocで確保し各関数を割り当てる作業は、それぞれの作成用関数としてまとめました。

サンプル:02-01-folder FileItem.c

Item* FileItemAlloc()
{
Item* item = malloc(sizeof(Item));
item->init = initFileItem;
item->print = printFileItem;
item->dealloc = deallocFileItem;
return item;
}

サンプル:02-01-folder FolderItem.c

Item* FolderItemAlloc()
{
Item* item = malloc(sizeof(FolderItem));
item->init = initFolderItem;
item->print = printFolderItem;
item->dealloc = deallocFolderItem;
FolderItem* folderItem = (FolderItem*)item;
folderItem->add = addFolderItem;
return item;
}

 main関数ではこれらFileItemAlloc、FolderItemAllocを使いファイルやフォルダオブジェクトを作成して利用します。
 FolderItemAllocは戻り値をItem構造体のポインタとしたので、そのままItem*型の変数rootに記憶させて利用しています。
 これでrootはFileItemAllocで作成したオブジェクトなのでinitやprint、deallocはフォルダとして振る舞います。

Item* root = FolderItemAlloc();
root->init(root, "root"); ←フォルダとしての初期化

 そしてFileItemAllocで作成したオブジェクトのinitやprint、deallocはファイルとして振る舞います。

Item* fileA = FileItemAlloc();
fileA->init(fileA, "file A"); ←ファイルとしての初期化

サンプル:02-01-folder main.c

・・・
#include "FileItem.h"
#include "FolderItem.h"

int main(int argc, const char * argv[])
{
// rootフォルダ作成、初期化
Item* root = FolderItemAlloc();
root->init(root, "root");
・・・
// fileAファイル作成、初期化
Item* fileA = FileItemAlloc();
fileA->init(fileA, "file A");
・・・
// rootフォルダにfileAを内包させる
((FolderItem*)root)->add((FolderItem*)root, fileA);
// 印刷
root->print(root, 0);
// 破棄
root->dealloc(root);
return 0;
}

 サンプル:02-01-folderをRunすると前回のサンプル:01-06-folderと同じ結果になるはずです。
 動作結果は同じですが、プログラム内容はよりオブジェクト指向らしいスタイルとなっています。

 ただ、派生に関してはもう少し考えておく部分が残っています。
 次回はその点について考えるために、フォルダを派生させたバンドルを用意してみましょう。
AD