C言語のtypedefについて具体例を用いて分かりやすく解説

typedef解説ページのアイキャッチ

このページではC言語における typedef について解説します。

多くの具体例を踏まえて解説していますので、是非実際の使い方と一緒に typedef の使い方や効果を理解・実感していただければと思います!

typedef とは

typedef とは既存のデータ型に新たな名前をつけるキーワードです。

例えば既存のデータ型の unsigned int 型 に uint という新たな名前を付け、その新たな名前の uint 型を用いてプログラミングすることができます。

この時、uintunsigned int と全く同じ型として扱われます。

ポイントは、typedef では新たな名前を作ることはできるのですが、新たな型自体を作るわけではない点です。

typedef では元々ある型を違う名前で使用するためのものであって、必ず元となる型が存在します。

typedef の使い方

typedef は下記のように2つの型名を指定する形で使用します。

typedefの使い方
typedef 既存の型名 新たな型名;

1つ目は既存のデータ型名、2つ目は新たに名付けたい型名になります。

例えば int 型を新たに MyInt と新たに名付ける場合は下記のように記述します。

intにMyIntを名付ける例
typedef int MyInt;

既存のデータ型であれば、unsigned int のように間にスペースがあっても typedef はきちんと動作してくれます。

unsigned intにMyIntを名付ける例
typedef unsigned int MyInt;

ただし、下記のような場合はコンパイルエラーになるので注意してください。

  • ; を最後に付けていない(私は忘れがち)
  • 1つ目と2つ目の指定が逆
  • 1つ目の型名が存在しない
  • 2つ目の型名に既に存在している型名を指定している
  • 2つ目の型名に使用できない文字を使っている
    • おそらく変数名に使用できない文字は使用できない
    • スペースもダメ

typedef で作成した型は、既存の型と同じように使えます。

したがって、新たな型名を用いて下記のようなことを行うことが可能です。

  • 変数宣言
  • キャスト
  • 関数の引数
  • 関数の戻り値
  • sizeof によるサイズ取得

これらを利用したソースコードの例が下記のようになります。

typedef で作成した型 MyInt は、typedef の1つ目の型に指定した unsigned int と全く同じサイズ・符号ありなしの型として扱われます。

MyIntを利用したソースコード例
#include <stdio.h>
#include <stdlib.h>

/* MyInt型を作成 */
typedef unsigned int MyInt;

/* MyInt型を戻り値と引数の型に設定 */
MyInt calc(MyInt, MyInt);

MyInt calc(MyInt a, MyInt b) {
    return a + b;
}

int main(void) {
    /* MyInt型で変数宣言 */
    MyInt a, b, c;
    char d = 75;

    /* MyInt型でキャスト */
    a = (MyInt)d;
    
    b = 1085;

    c = calc(a, b);

    printf("%u\n", c);

    /* MyInt型のサイズを取得 */
    printf("%u\n", sizeof(MyInt));

    return 0;
}

スポンサーリンク

typedef の使い方の覚え方

typedef の使い方に迷った時は、変数宣言を思い出すと良いです。

変数宣言
既存の型名 名前;

これだけだと作られるのは変数ですが、先頭に typedef を付けてやれば、変数ではなく型が作成されます。

変数宣言
typedef 既存の型名 名前;

つまり、typedef の使い方は下記のように考えると覚えやすいです。

  • typedefの使い方の基本イメージは変数宣言
  • 変数宣言の前に typedef を付けてやれば、変数ではなく型が作られる

例えば下記のように変数宣言を行えば uint という “変数” が作られることになりますが、

uint変数の宣言
unsigned int uint;

先頭に typedef をつけることで uint という “型” が作られることになります。

uint型の宣言
typedef unsigned int uint;

また、下記のように変数宣言を行えば String という “変数” が作られることになりますが、

String変数の宣言
char String[100];

先頭に typedef をつけることで、String という “型” が作られることになります。

String型の宣言
typedef char String[100];

後に説明する配列の型や関数ポインタの型をtypedef するときの指定方法はちょっと特殊に見えますが、この覚え方だとすんなり覚えられるのではないかと思います。

typedef の使用例

ではこの typedef をどんな時に使用するのか、実際の使用例を踏まえて紹介していきたいと思います。

構造体の型を typedef する

構造体の型名には必ず struct が付きますが、typedef することで構造体の型を struct なしで利用することができるようになります。

構造体の説明と一緒に typedef が紹介されるケースも多く、おそらく一番利用されている使い方だと思います。

構造体の型を typedef するソースコード例は下記のようになります。

構造体の型をtypedefする例
#include <string.h>

struct person {
    char name[100];
    unsigned int age;
    double height;
    double weight;
};

/* 構造体の型をtypedef */
typedef struct person PERSON;

int main(void) {
    PERSON a;

    strcpy(a.name, "TARO");
    a.age = 15;
    a.height = 175.4;
    a.weight = 56.3;

    return 0;
}

構造体の型を typedef する例は下記ページでも紹介していますのでこちらも是非参考にしてください。

構造体解説ページのアイキャッチ【C言語】構造体について初心者向けに分かりやすく解説

スポンサーリンク

ポインタの型を typedef する

ポインタの型を typedef することも可能です。

typedef する際、* の位置は下記の4パターンのどれでも良く、全て同じ結果となります。

ポインタの型をtypedefする複数のパターン
typedef char *CharPtr; /* 2つ目の型名の前に*を付ける*/
typedef char* CharPtr; /* 1つ目の型名の後ろに*を付ける*/
typedef char * CharPtr; / 2つの型名の中間に*を付ける(スペースあり) */
typedef char*CharPtr; /* 2つの型名の中間に*を付ける(スペースなし) */ 

ポインタの変数宣言やキャスト時等も * なしに行うことができるようになります。

ただし、型名が変わるだけで変数自体の使い方は通常のポインタと同じであることに注意しましょう。

ポインタの型をtypedefする例
#include <stdio.h>
#include <string.h>

/* ポインタの型をtypedef*/
typedef char * CharPtr;

int main(void) {
    int i;
    char a = 'k';
    char b[10] = "aiueo\n";
    CharPtr x, y;

    x = &a;
    y = b;

    printf("%c\n", *x);
    for (i = 0; i < strlen(b); i++) {
        printf("%c", *y);
        y++;
    }
    return 0;
}

配列を typedef する

配列を typedef することも可能です。

この時、配列のサイズは1つ目の型ではなく、2つ目の型の方で指定します。

配列のtypedef
typedef char String[100];

直感的には逆な感じがしますがこれが正しいです。

例えば下記のように typedef することで、char 型のサイズ 100 の配列を String 型として扱うことができます。

String 型の変数は全て、char 型のサイズ 100 の配列となります。

配列をtypedefする例
#include <stdio.h>
#include <string.h>

/* 配列をtypedef */
typedef char String[100];

int main(void) {
    String str;

    strcpy(str, "aiueokakikukeko\n");

    printf("%s\n", str);

    return 0;
}

関数ポインタの型を typedef する

C言語では関数ポインタ変数を利用することが可能です。

関数ポインタ変数の宣言
戻り値の型名 (*変数名)(引数の型名);

戻り値の型名や引数の型名は、ポインタで指したい関数に合わせて記述します(引数の型名は複数指定可能です)。

関数ポインタについては下記ページで解説していますので、詳しく知りたい方はこちらも是非読んでみてください。

関数ポインタの解説ページアイキャッチ徹底図解!C言語の関数ポインタについて解説

関数ポインタ変数の宣言は上述のとおり非常に特殊です。私も書くたびにどう書けば良いか悩みます…。

ですが、typedef を用いることで通常の型同様に下記の形式で変数宣言できるようになります。

関数ポインタ型を用いた時の変数宣言
型名 関数ポインタ変数;

例えば下記のような関数への関数ポインタについて考えます。

関数ポインタで指したい関数
int func(long, char);

この関数を指す関数ポインタ変数を単純に宣言すると下記のようになります(より正確にはこの関数と戻り値と引数の型や個数が同じ関数を指すことができます)。

通常の関数ポインタ変数の宣言
int (*cb_func)(long, char);

この時 cb_funcが変数名となり、この変数に関数のアドレスを格納することができます。

一方、次のように typedef すれば、上記関数を指すことのできる FUNC_PTR 型が作成されます。

関数ポインタ型をtypedefする例
typedef int (*FUNC_PTR)(long, char);

この FUNC_PTR を用いて、下記のように関数ポインタ変数を宣言することができます。

関数ポインタ型をtypedefした時の変数宣言
FUNC_PTR cb_func1, cb_func_2;

こんな感じで一度 typedef さえしてやれば、後は簡単に関数ポインタ変数を宣言できるようになります。

下記は関数ポインタの型を typedef するソースコードの例になります。

関数ポインタの型をtypedefする例
#include <stdio.h>

/* 関数ポインタ型をtypedef */
typedef int (*CB_FUNC)(int, char*);

int printName(int, char*);

int printName(int id, char *name) {
    printf("%d:%s\n", id, name);
    return id + 1;
}

int main(void) {
    int ret;
    int (*cbFunc1)(int, char*);
    CB_FUNC cbFunc2;

    cbFunc1 = printName;

    ret = cbFunc1(5, "taro");
    printf("ret = %d\n", ret);

    cbFunc2 = printName;
    ret = cbFunc2(10, "hanako");
    printf("ret = %d\n", ret);


    return 0;
}

int (*cbFunc1)(int, char*);
 だと変数宣言している感じがあまりしないですが、CB_FUNC cbFunc2; だと直感的に変数宣言していることが分かりやすくなります。

スポンサーリンク

typedef で型の用途を明示する

C言語の基本的な型はデータのサイズと符号のありなしを示すだけのものばかりです。

なので、どの型も汎用性は高いというメリットがあります。が、どのような用途であるかが分かりにくいというデメリットもあります。

型の用途が分かりやすいように typedef を使って型名を付けてやれば、その型の変数がどのような用途の変数であるかが分かりやすくなります。

例えば色を表すカラーコードは6桁の16進数で表されますので int 型の変数で扱うことが可能です。

普通に int 型を使っても良いのですが、例えば intCOLOR 型で typedef してやればより型の用途が明確になります。

ソースコードを読む時でも、型名から「あ、多分色を扱う型だな」とすぐに用途が推測できるので、その分ソースコードが読みやすくなります。

例えば下記の例を見れば COLOR 型を戻り値の型にしている getRed の方が、より色の情報を返却する関数であることが分かりやすいと思います。

COLOR型で用途を明確にする例
typedef int COLOR;

COLOR getRed(void) {
    return 0xFF0000;
}

int getGreen(void) {
    return 0x00FF00;
}

他にも良くやる例が BOOL 型を typedef で作成することです。

BOOL型の作成
typedef int BOOL;

C言語にはもともと BOOL 型がないですが、このように typedef することで作成することができます。

といっても int に BOOL という名前を付けただけなので、TRUEFALSE 以外も格納できてしまいますが、関数の戻り値が BOOL であれば、即座に「この関数は何かを判断しする関数だな」ということが分かってソースコードが読みやすくなります。

例えば下記であれば、戻り値の型が BOOL 型の isEven の方が、何かを判断する関数であることがより分かりやすいと思います。

BOOL型で用途を明確にする例
#define TRUE 1
#define FALSE 0
typedef int BOOL;

BOOL isEven(int num) {
    if (num % 2 == 0) { 
        return TRUE;
    } else {
        return FALSE;
    }
}

int isOdd(int num) {
    if (num % 2 == 1) { 
        return 1;
    } else {
        return 0;
    }
}

typedef で型のサイズを明示する

また、C言語の各型にはその型のサイズがあります。ですが、型名だけからだとそのサイズが分かりにくいです。

サイズが分かりやすいように typedef を使って型名を付けてやれば、その型の変数がどのようなサイズであるかが分かりやすくなります。

サイズ名を明示してtypedefする例
/* サイズを明示するtypedef */
typedef char int8;
typedef short int16;
typedef int int32;
typedef long long int64;
typedef unsigned char uint8;
typedef unsigned short uint16;
typedef unsigned int uint32;
typedef unsigned long long uint64;

int main(void) {
    int32 x;

    x = 65536;

    printf("%d\n", x);

    return 0;
}

typedef の各型名についている数字は、その型のサイズをバイト数で表現したものになります。

はっきり言って、この typedef でサイズを明示する使い方はめちゃめちゃ使います。

typedef のメリット

最後に typedef のメリットとデメリットについて紹介しておきたいと思います。まずはメリットから!

スポンサーリンク

ソースコードが読みやすくなる

typedef で用途の分かりやすい型を作ることでソースコードを読みやすくすることができます。

typedef で型の用途を明示するで紹介したように、型名を工夫するだけでソースコードが読みやすくなります。

これは型名によって変数や関数の戻り値の用途が明確になるからです。

もちろん変数名や関数名を工夫すれば、それだけでソースコード は読みやすくなりますが、型名を工夫すればさらにソースコードが読みやすくなります。

型を楽に記述できるようになる

typedef で新たな型名を短いものにすれば、その分型を楽に記述できるようになります。

例えば構造体の型を typedef するで紹介したように構造体の型を struct を付けることなく記述することができるようになります。

unsigned int なんかも型名としては長いですが、これを uint なんかに typedef してやれば、短い記述で unsigned int と同じ型を使用できるようになります。

ソースコードの移植性が上がる

C言語の型のサイズは処理系(コンパイラ)によってサイズが異なることがあります。

なので、同じ int 型でもサイズが4バイトの場合もあれば、2バイトの場合もあります。

例えば、typedefで型のサイズを明示するで紹介したソースコードについて考えてみましょう。

int 型が4バイトであれば、実行結果は “65536” になりますが、int 型が2バイトであれば実行結果は “0” になってしまいます(符号あり2バイトの型の場合、 -32768 〜 32767 までしか表現できず、65536 を格納する際にオーバーフローが発生して上位ビットが切り捨てられる)。

したがって、処理系によってはプログラムがうまく動作してくれないことがあることになります。

なので、型のサイズが異なるような環境でプログラムを実行する場合は、ソースコードの修正が必要になります。

こんな時に便利なのが typedef です。

もし、typedef を使用せずに直接 int 型で変数宣言やキャストなどを行っている場合、その int 型の部分を全て変更する必要があります(例えばサイズ4バイトの long 型などに変更する)。

当然使用している箇所が多ければ多いほど変更が大変になります。

一方で typedef を使用している場合は、typedef している部分のみを変更してやれば良いです。

例えばtypedefで型のサイズを明示するで紹介したソースコードであれば、修正後は下記のようになります。

型のサイズのみを変更する
#include 

/* サイズを明示するtypedef */
typedef char int8;
typedef short int16;
typedef long int32;
typedef long long int64;
typedef unsigned char uint8;
typedef unsigned short uint16;
typedef unsigned long uint32;
typedef unsigned long long uint64;

int main(void) {
    int32 x;

    x = 65536;

    printf("%d\n", x);

    return 0;
}

こんな感じでも型のサイズが異なるような環境で動作させるように移植させる場合でも、typedef 部分のみの変更で済むので移植性の高いソースコードを作成することができます。

スポンサーリンク

typedef のデメリット

次にデメリットについて説明します。

元々の型が分かりにくい

typedef のデメリットは元々の型が分かりにくいことです。

例えば構造体の型を typedef するで紹介したように、typedef を利用することで struct を略すことについて考えてみましょう。

struct を記述せずに構造体の型が使えるようになるので、確かに楽になります。

ですが、型名に struct が無いので構造体の型であることが分かりにくくなります。

そのため、単純なデータ型と考えて使用してしまい、これがバグやコンパイルエラーを招くことになりかねません。

またポインタの型を typedef する場合でも、もともとの型がポインタ型であることを知らずに使用してしまうと、これも同様にバグやコンパイルエラーを招くことになります。

こんな感じで、元々の型がどのようなものであるかが分かりにくいため、かえって使いにくくなる場合があります。

ただしこのデメリットは typedef で名付ける型名で元々の型がどのような型であるかが分かりやすくすることで解消できると思います。

まとめ

このページではC言語の typedef について解説しました。

typedef により元々ある型に他の名前を名付けることができます。

これにより下記のようなメリットがあります。

  • ソースコードが読みやすくなる
  • 型を楽に記述できるようになる
  • ソースコードの移植性が上がる

typedef の使い方を忘れた時は変数宣言の仕方を思い出しましょう。変数宣言の前に typedef を付けることで、変数ではなく新たな型を作成することができます!

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です