C Language
Typedef
サーチ…
前書き
typedef
メカニズムは、他の型のエイリアスの作成を可能にします。新しいタイプは作成されません。多くの場合、 typedef
を使用してコードの移植性を向上させたり、型の構造体または共用体にエイリアスを付けたり、関数(または関数ポインタ)型の別名を作成したりします。
C標準では、 typedef
は便宜上「ストレージクラス」として分類されています。 static
またはextern
ようなストレージクラスが現れることがある場合、構文的に発生します。
構文
- typedef existing_nameエイリアス名;
備考
typedefの短所
typedef
は大規模なCプログラムの名前空間の汚染につながる可能性があります。
typedef構造体の短所
また、タグ名のないtypedef
構造体は、ヘッダファイル間の順序関係を不必要に課す大きな原因です。
検討してください:
#ifndef FOO_H
#define FOO_H 1
#define FOO_DEF (0xDEADBABE)
struct bar; /* forward declaration, defined in bar.h*/
struct foo {
struct bar *bar;
};
#endif
このような定義では、 typedefs
を使用せずに、コンパイル単位にfoo.h
をインクルードしてFOO_DEF
定義を取得することがFOO_DEF
ます。 foo
構造体のbar
メンバーを参照解除しようとしない場合、 bar.h
ファイルを含める必要はありません。
Typedefと#define
#define
は、 typedef
似ていtypedef
が、以下の違いがあるさまざまなデータ型のエイリアスを定義するためにも使用されるCのプリプロセッサディレクティブです。
typedef
は#define
を使って値のエイリアスを定義するためだけに型に記号名を与えることに限られています。typedef
解釈はコンパイラによって実行されtypedef
が、#define
文はプリプロセッサによって処理されます。#define cptr char *
後ろにcptr a, b;
続くことに注意してくださいcptr a, b;
typedef char *cptr;
と同じことをしませんtypedef char *cptr;
cptr a, b;
続きcptr a, b;
。#define
では、b
は普通のchar
変数ですが、typedef
持つポインタでもありtypedef
。
構造体と共用体のtypedef
struct
エイリアス名を付けることができます:
typedef struct Person {
char name[32];
int age;
} Person;
Person person;
structを宣言する伝統的な方法と比べて、プログラマーはstruct
のインスタンスを宣言するたびにstruct
を持つ必要はありません。
Person
( struct Person
とは対照的に)という名前は、最後のセミコロンまで定義されていないことに注意してください。したがって、同じ構造体型へのポインタを含む必要があるリンクされたリストとツリー構造では、次のいずれかを使用する必要があります。
typedef struct Person {
char name[32];
int age;
struct Person *next;
} Person;
または:
typedef struct Person Person;
struct Person {
char name[32];
int age;
Person *next;
};
union
体型のtypedef
の使用は非常に似ています。
typedef union Float Float;
union Float
{
float f;
char b[sizeof(float)];
};
float
値を構成するバイトを分析するために、これに似た構造体を使用することができます。
typedefの簡単な使用法
データ型に短い名前を付けるため
の代わりに:
long long int foo;
struct mystructure object;
1つは使用できます
/* write once */
typedef long long ll;
typedef struct mystructure mystruct;
/* use whenever needed */
ll foo;
mystruct object;
これにより、タイプがプログラムで何度も使用される場合に必要な型付けの量が減ります。
移植性の向上
データタイプの属性は、アーキテクチャによって異なります。たとえば、 int
はある実装では2バイト型で、別の実装では4バイト型です。プログラムが正しく動作するために4バイトタイプを使用する必要があるとします。
1つの実装では、 int
のサイズを2バイトとし、 long
のサイズを4バイトとします。別の例では、 int
のサイズを4バイト、 long
さを8バイトとします。プログラムが2番目の実装を使用して記述されている場合、
/* program expecting a 4 byte integer */
int foo; /* need to hold 4 bytes to work */
/* some code involving many more ints */
プログラムを最初の実装で実行するには、すべてのint
宣言をlong
に変更する必要があります。
/* program now needs long */
long foo; /*need to hold 4 bytes to work */
/* some code involving many more longs - lot to be changed */
これを避けるために、 typedef
/* program expecting a 4 byte integer */
typedef int myint; /* need to declare once - only one line to modify if needed */
myint foo; /* need to hold 4 bytes to work */
/* some code involving many more myints */
次に、プログラム全体を調べるのではなく、 typedef
ステートメントだけを変更する必要がありtypedef
。
<stdint.h>
ヘッダーと関連する<inttypes.h>
ヘッダーは、さまざまなサイズの整数に対してtypedef
を使用して標準の型名を定義しtypedef
これらの名前は、固定サイズの整数が必要な現代のコードでは、しばしば最良の選択です。たとえば、 uint8_t
は符号なし8ビット整数型です。 int64_t
は符号付き64ビット整数型です。 uintptr_t
型は、オブジェクトへのポインタを保持するのに十分な大きさの符号なし整数型です。これらのタイプは理論的にはオプションですが、使用できないことはまれです。 uint_least16_t
(少なくとも16ビットの最小符号なし整数型)とint_fast32_t
(32ビット以上の最速符号付き整数型)のようなバリエーションがあります。また、 intmax_t
とuintmax_t
は、実装でサポートされている最大の整数型です。これらのタイプは必須です。
使用方法を指定するか、または読みやすさを向上させる
データのセットに特定の目的がある場合、 typedef
を使用して意味のある名前を付けることができます。さらに、基本型が変更されるようにデータのプロパティが変更された場合、プログラム全体を調べる代わりにtypedef
文だけを変更する必要がありtypedef
。
関数ポインタのtypedef
typedef
を使用すると、関数ポインタの使用を簡単にすることができます。私たちがいくつかの関数を持っているとしましょう。それらの関数はすべて同じシグネチャを持ち、引数を使ってさまざまな方法で何かを出力します:
#include<stdio.h>
void print_to_n(int n)
{
for (int i = 1; i <= n; ++i)
printf("%d\n", i);
}
void print_n(int n)
{
printf("%d\n, n);
}
これで、 typedef
を使ってprinterという名前の関数ポインタ型を作成できます。
typedef void (*printer_t)(int);
これは、単一のint
引数をとり、何も返さない関数へのポインタのprinter_t
という名前の型を作成します。この関数は、上記の関数のシグネチャに一致します。それを使用するために、作成された型の変数を作成し、問題の関数の1つを指すポインタを割り当てます。
printer_t p = &print_to_n;
void (*p)(int) = &print_to_n; // This would be required without the type
次に、関数ポインタ変数が指す関数を呼び出す:
p(5); // Prints 1 2 3 4 5 on separate lines
(*p)(5); // So does this
したがって、 typedef
は、関数ポインタを扱う際に、より単純な構文を可能にします。これは、関数への引数など、より複雑な状況で関数ポインタが使用されている場合に、より明らかになります。
定義された関数ポインタ型なしで関数ポインタをパラメータとして取る関数を使用している場合、関数定義は、
void foo (void (*printer)(int), int y){
//code
printer(y);
//code
}
ただし、 typedef
では次のようになりtypedef
。
void foo (printer_t printer, int y){
//code
printer(y);
//code
}
同様に、関数は関数ポインタを返すことができます。また、 typedef
使用すると、構文をより簡単にすることができます。
典型的な例は、 <signal.h>
signal
関数です。その宣言は(C標準から):
void (*signal(int sig, void (*func)(int)))(int);
これは2つの引数をとる関数ですint
と引数としてint
をとり、何も返さない関数へのポインタで、2番目の引数のように関数へのポインタを返します。
関数型へのポインタのエイリアスとして型SigCatcher
を定義した場合:
typedef void (*SigCatcher)(int);
signal()
を宣言することができます:
SigCatcher signal(int sig, SigCatcher func);
全体として、これは理解しやすい(C標準が仕事を行う型を定義することを選択しなかったとしても)。 signal
関数は2つの引数、取りint
とSigCatcher
、そしてそれが返しSigCatcher
- SigCatcher
受け取る関数へのポインタであるint
引数を何も返しませんが。
関数型へのポインタにtypedef
名前を使用すると、人生が楽になりますが、後でコードを管理する他の人に混乱を招く可能性もありますので、注意して適切な文書を使用してください。 関数ポインタも参照してください。