【C言語】構造体の定義/宣言/初期化【詳細と豆知識】


目次

スポンサーリンク

基本的な定義方法

struct Number {
   int value;
};

変数宣言時にはstructキーワードが必須です。

struct Number obj;
printf("%d", obj.value);

structキーワードを省略する方法は後半の「# 一般的な定義方法」で紹介します。

構造体の初期化方法

構造体変数の宣言方法・初期化方法は複数あります。

よく知られている初期化方法

struct Number num;
num.value = 99;

初期化子による初期化

配列の初期化方法と同様の形式で初期化できます。

struct Number num = {99};
num.value; // 99

構造体のメンバが複数ある場合にも対応できます。

// struct Range { int location, length; };
struct Range range = {1, 2};
range.location; // 1
range.length;   // 2
初期化子という用語は少し曖昧な用語で、この場合正確には「『波括弧で囲まれた初期化子の並び』による初期化」というのが正しい表現になりそうです。なおC++の世界では初期化リスト/初期化子リストという用語が存在します。

複合リテラルによる初期化

構造体を一時オブジェクトとして扱う場合は初期化リストが使えません。代わりに複合リテラルを用います。

struct Range { int location, length; };

struct Range fn(struct Range) {
   return {3, 4};               // NO(初期化リスト)
   return (struct Range){3, 4}; // OK(複合リテラル)
}

fn({1, 2});               // NO(初期化リスト)
fn((struct Range){1, 2}); // OK(複合リテラル)

指示初期化子による初期化

指示初期化子によって配列のメンバ名を明示する形での初期化が可能になります。

struct Range r = {.location = 1, .length = 2};
struct Range r = {.length = 2, .location = 1};
fn((struct Range){.location = 1, .length = 2});
fn((struct Range){.length = 2, .location = 1});

初期化子の並びは自由です(# 注意点…)。

構造体ポインタの初期化

構造体用のメモリをmalloc関数で動的に確保する際には、sizeof(struct 構造体名)という形式で構造体のサイズを指定します。

struct Number *p = malloc(sizeof(struct Number));
p->value = 9; // 初期値

構造体のサイズをsizeof(*ポインタ変数名)と言う形で取得するテクニックもあります。

struct Number *p = malloc(sizeof(*p));

やってることは同じです。タイプ数が少なさやリファクタリングのしやすさから非常にオススメのテクニックです。

スポンサーリンク

指示付き初期化子(designated initializer)

# 指示初期化子による初期化で紹介した{.メンバ名 = 初期値}という特殊な式は指示付き初期化子/指示初期化子(designated initializer)と呼ばれるC言語(C99)の新機能です。

// struct Range { int location, length; };
struct Range r = {.location = 1, .length = 2};
struct Range r = {.length = 1, .location = 1};

現行のC++(C++14)には取り入れられていない機能ですので注意してください。ただClang++コンパイラではC言語互換の拡張機能として実装されています。なおC++では現在「Designated Initialization」という名前で正式な機能としての追加が提案されています。ただ注意したいのは、指定子の順序がメンバ変数の宣言順でなければならないという制限が検討されている点です。

struct { int a, b; } x = {.a = 2, .b = 1}; // OK
struct { int a, b; } y = {.b = 2, .a = 1}; // Error

Clang++で本拡張機能を使用する場合はその点の仕様を意識しておく必要があります。

一般的な定義方法

構造体はtypedefキーワードと組み合わせて利用することが一般的です。

typedef struct {
   int value;
} Number;

こうすることで、変数宣言時にstructキーワードが不要になります。

Number num;
printf("%d", num.value);

もちろん、ラベル名を明示することも可能です。

typedef struct Number_ { int value; } Number;

struct Number_ a = {1};
Number b = {2}; // どちらの記法も利用可能

解説

typedefによる構造体宣言方法は見慣れない記法かもしれませんが、仕組み自体は単純です。そもそもtypedef自体は型の別名を付ける機能なのですが、これがtypedef 型 別名という宣言方法になっています。

そして今回紹介した構造体の特殊な記法はこのの部分に構造体の宣言式を直接指定することで実現されています。

人によってはこのように書いたほうが最初は覚えやすいかもしれません。 やっていることはいっしょです。

struct Number_ { int value; };
typedef struct Number_ Number;

無名構造体

ラベル名を省略した構造体をその場で定義/利用することが可能です。

int main() {
   struct { int a; int b } pair;
   pair.a = 1;
   pair.b = 2;
}

ちょっとしたアルゴリズムを実現する際に重宝します。

typedefで型名を付けることも可能です。

int main() {
  typedef struct { char c; int i; } Pair;
  Pair pair = {'C', 99};
}
スポンサーリンク

自己参照構造体

自身の構造体型を自身のメンバ変数の型として利用する場合には、メンバ変数をポインタとして宣言する必要があります。

typedef struct Node_ {
   struct Node_ *next;
   
   // Node *next; // ERROR:  Unknown type name 'Node'
   // 上記の書き方はできない(typedefによる別名「Node」の宣言がまだ確定していないため)
} Node;
自身の型をメンバに持つ構造体を作る際にはラベル名が必要になります。メンバを宣言する段階ではtypedefによる別名の定義が完了していないためです。

上記のメンバ宣言時の記述struct Node_が煩わしいと感じる場合は、構造体の前方宣言を行うと良いです。こうすると構造体のメンバで自身の型をNode *と書くことができるようになります。

typedef struct Node_ Node;

typedef struct Node_ {
   Node *next; // OK
   // struct Node_の定義が確定していないためいずれも不可
   // Node_ next; // Field has incomplete type 'Node_'
   // Node  next; // Field has incomplete type 'Node_'
} Node;

typedefによる構造体名とラベル名は同じでも良い

今回の例ではtypedefによる構造体名と構造体ラベル名を区別するために、アンダースコア付きのNumber_という名称を用いていましたが、実際は両者で同じ名前を付けても問題ありません。変数宣言時のstructキーワードで両者を区別することができるためです。

typedef struct Number {} Number;

struct Number a; // 構造体タグ名で宣言
Number b; // typedefによる別名で宣言

ただ、実際の開発では名前を区別したり、ラベル名を省略した方が良いと思います。名前の重複や余計な型名が増えると、IDE側の機能が若干使いづらくなったりします。

XcodeのJump To Definition (^⌘J)機能やEclipseのOpen Declaration (F3)機能などがその良い例。構造体タグ名とtypedefの別名が同名だとジャンプ先の候補リストが表示されてしまう場合がある。名前重複が起こらなければ、定義箇所へのジャンプが一発で行えるようになる

ちなみに

ちなみにC++ではstruct Number {};と書くだけでstruct NumberNumberの両記法が利用できるようになります。気の利くヤツです。


C++「どや」
私「うむ、これはいいものだ」

広告

関連するオススメの記事