サーチ…


前書き

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を持つ必要はありません。

Personstruct 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

C99

<stdint.h>ヘッダーと関連する<inttypes.h>ヘッダーは、さまざまなサイズの整数に対してtypedefを使用して標準の型名を定義しtypedefこれらの名前は、固定サイズの整数が必要な現代のコードでは、しばしば最良の選択です。たとえば、 uint8_tは符号なし8ビット整数型です。 int64_tは符号付き64ビット整数型です。 uintptr_t型は、オブジェクトへのポインタを保持するのに十分な大きさの符号なし整数型です。これらのタイプは理論的にはオプションですが、使用できないことはまれです。 uint_least16_t (少なくとも16ビットの最小符号なし整数型)とint_fast32_t (32ビット以上の最速符号付き整数型)のようなバリエーションがあります。また、 intmax_tuintmax_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つの引数、取りintSigCatcher 、そしてそれが返しSigCatcher - SigCatcher受け取る関数へのポインタであるint引数を何も返しませんが。

関数型へのポインタにtypedef名前を使用すると、人生が楽になりますが、後でコードを管理する他の人に混乱を招く可能性もありますので、注意して適切な文書を使用してください。 関数ポインタも参照してください。



Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow