WisdomSoft - for your serial experiences.

7.8 型の別名

typedef 指定子を用いた型の別名を定義する方法を紹介します。これによって既存の型に対し単純な名前を与えるとこができ、複雑な構造体やポインタ型の存在を隠蔽できます。

7.8.1 独自の型

C 言語では、開発者が新しい独自の型を宣言する手段が存在します。正確には、既存の型に対して異なる別名を与えるというものです。すでに存在する型に異なる名前をつけることは無意味だと感じるかもしれません。しかし、使い方によってはコードを簡素にし、変更に強い柔軟で保守性の高いプログラムを実現します。

C 言語はあらゆるシステムで実装されている国際的なプログラミング言語です。特に標準化されている関数だけを使っているのであれば、理論的には移植を行うことなく他のシステムでもコンパイルできなければなりません。しかし、他のシステムは 32 ビットコンピュータかもしれないし、64 ビットコンピュータなのかもしれません。こうした異なるシステムへの移植作業を最小にする手段として、型の別名を使うことができます。

例えば、あるプログラムコードでは int 型が 32 ビットであると想定されているとして、これを 16 ビットコンピュータで実行しても、正しい動作は期待できません。そこで、32 ビットの整数型を DWORD、16 ビットの整数型を WORD 型としてソースを統一すれば、他のシステムへの移植も簡単になります。32 ビットコンピュータでは DWORD 型を int 型の別名、WORD 型を short int 型の別名と定義すれば、実体が抽象化されます。

これを他のシステムに移植する場合、そのシステムで 32 ビットと定義される型に DWORD を、16 ビットと定義される型に WORD という別名を指定します。そうすれば、残りのソースは一切変更する必要がなくなるのです。これは、移植性の向上に重要なテクニックとなるでしょう。

既存の型に別名をつけるには typedef 指定子を使います。typedef によって、型に対する新しい識別子を定義することができます。新しい型識別子を定義したあと、既存の型が使えなくなるわけではありません。

typedef 指定子
typedef 既存の型名 新しい型名;

既存の型名には int や char * などのすでに存在する型の名前を指定します。新しい型名にまだ存在していない型の識別子を指定します。例えば unsigned char の別名は次のように定義することができます。

typedef unsigned char BYTE;

この宣言は、unsigned char 型の別名として BYTE 型を定めています。typedef キーワードは型が宣言できる場所であれば指定することができますが、別名は typedef キーワードよりも後でなければ使うことができないので、通常は関数よりも前のソースの先頭に記述します。既存の型名はすでに存在する型であれば何でもかまいません。以前 typedef によって定義された型名を指定することも可能です。

コード1
#include <stdio.h>

typedef unsigned char BYTE;
typedef int DWORD;

struct Color {
	BYTE r , g , b;
};

DWORD main() {
	struct Color color = { 0xFF , 0xAA , 0xAA };
	printf("R = %d : G = %d : B = %d\n" , color.r , color.g , color.b);
	return 0;
}
実行結果
コード1 実行結果

このプログラムでは、unsigned char 型の別名 BYTE と、int 型の別名 DWORD を定義しています。Color 構造体や main() 関数では、これらの typedef 名を用いてプログラムされています。しかし、BYTE 型も DWORD 型もその実体は C 言語の単純型である unsigned char や int です。これらは単なる別名に過ぎないということを忘れないで下さい。

ポインタ型の別名は、既存の型名の後に * を指定するだけです。char 型へのポインタの別名 STR を作成するのであれば

typedef char * STR;

というように記述します。また、typedef の新しい型名にはカンマ "," で区切ることによって一度に複数の別名を与えることも可能です。例えば次のように記述することで、char 型の別名と、char 型へのポインタの別名を一度に定義することができます。

typedef char STR , *PSTR;

これは、char 型の別名 STR と、char 型へのポインタの別名 PSTR が新たに定義されています。通常、文字列へのポインタを記述する場合は char * というように書いたと思いますが、このようにポインタ型の別名を作成すれば PSTR を指定するだけでポインタ型の変数を宣言することができるようになります。

コード2
#include <stdio.h>
typedef char STR , *PSTR;

int main() {
	STR str[] = "Kitty on your lap";
	PSTR pstr = str;
	printf("%s\n" , pstr);
	return 0;
}
実行結果
コード2 実行結果

コード2では、char 型の別名 STR と、char * 型の別名 PSTR を定義し、これを main() 関数で実用しています。str[] 変数は STR 型なので実体は char 型の配列です。pstr 変数は PSTR 型なので char * 型の変数であると考えることができます。このようにポインタ型への別名を駆使することで、ポインタの存在をある程度隠蔽することが可能です。

とくに typedef が威力を発揮するのは、構造体や共用体の別名を作ったときです。単純型の別名には大きなメリットが感じられないかもしれませんが、構造体の別名を作成すれば、ソースの可読性の向上や生産性につなげることができます。

従来、構造体のタグ名から構造体のインスタンスを作成する作業は、面倒なことに struct キーワードを明示的に指定する必要がありました。これは冗長な構文で、多くのプログラマはこの無駄な struct や union キーワードの指定を嫌います。そこで typedef によって構造体や共用体に新しい型名を割り当ててしまいます。そうすることで struct や union キーワードを指定せずに、型名だけでインスタンスを作成することができるようになるのです。

struct tag_Point { int x , y; };
typedef struct tag_Point Point;

このように、構造体の別名を定義することができます。タグ名からインスタンスを作成するには struct tag_Point を記述しなければ、コンパイラは方を判断してくれませんでした。しかし、struct tag_Point 型に別名 Point を割り当てることによって、この後は Point を指定するだけで tag_Point 構造体型のインスタンスを作成することができるようになります。

しかし、tag_Point はこの場でしか使わないタグ名なので、あまり必要がありません。タグ名がその後に必要とされないのであれば、無名構造体に別名を与えた方が効率的です。構造体宣言時に次のような構文を用いることで同時に別名を定義することができるため、無名構造体に別名を与えることもできるのです。

typedef 指定子による構造体の別名
typedef struct タグ名 { メンバ宣言 } 新しい型名;

構造体の宣言でタグ名は省略できるため、必要でなければ無名構造体を宣言し、同時に新しい型名を与えることができます。多くの C 言語プログラマは、構造体に別名を与えることを好み、構造体の宣言時に typedef を指定して別名を与えます。

コード3
#include <stdio.h>
typedef struct { int x , y; } Point;

int main() {
	Point po = { 200 , 50 };
	printf("X = %d : Y = %d\n" , po.x , po.y);
	return 0;
}
実行結果
コード3 実行結果

コード3は、int 型のメンバ x と y を持つ無名構造体に Point という別名を与えています。多くのプログラマは、このようにタグ名ではなく typedef 名を構造体に指定します。このほうが struct キーワードを指定する必要がないので、構造体変数の宣言が簡単になります。共用体に別名をつける場合も同様です。

7.8.2 関数型と別名

実は、C 言語では関数もひとつの型であると解釈されています。関数の型は戻り値と引数リストで構成されています。しかし、関数そのものは識別子(すなわち関数名)で判断されるため、普段はこれを意識することはありません。しかし、関数型というものを意識した場合、関数とは関数型のインスタンスであると考えることができるのです。

例えば、次のような関数が宣言されている場合を考えましょう。

int Function1(char * , float);
int Function2(char * , float);

この場合、Function1() 関数と Function2() 関数は同じ関数型であると表現することができます。もちろん、これらの関数の定義に相互関係は存在していません。これらの関数は独立しており、物理的なつながりは何もないのです。唯一の共通点は、型が同じだということです。

このように、関数を型という視点から見つめることができるのです。そうすると typedef によって関数型の名前をつけるという面白い発想が浮かび上がってくるでしょう。実は、これは構文的に可能となっています。次のように記述することで、関数型に名前を付けることができるのです。

typedef 指定子による関数型の別名
typedef 戻り値 新しい型名 (引数リスト);

これは、非常に面白い試みです。関数型に名前をつけてしまえば、型名と関数名だけで関数を宣言できてしまいます。

コード4
#include <stdio.h>

typedef void TEXTOUT(char *str);
TEXTOUT println;

int main() {
	println("Kitty on your lap");
	return 0;
}

void println(char *str) {
	printf("%s\n" , str);
}
実行結果
コード4 実行結果

コード4の println() 関数に注目してください。この println() 関数は TEXTOUT 型の関数であると解釈することができるのです。TEXTOUT 型は、typedef 指定子で作成した、戻り値が void、パラメータが char * 型の関数型の別名です。TEXTOUT println という関数宣言子は void println(char *str) という宣言に等しいと考えることができます。