C言語の構造体を関数で扱う
目次
C言語の構造体を関数で扱う
C言語の構造体を関数で扱う場合、値渡しとアドレス渡しの違いについて理解しておく必要があります。
関数の引数や返り値(戻り値)で構造体を値渡し、つまりコピーする場合は速度に影響が出る場合があります。
プログラムの処理速度を上げたい場合は関数の引数や返り値にはアドレス渡しを使うのが一般的です。
まずは関数の引数と構造体について見ていきたいと思います。
関数の引数に構造体を渡す
関数の引数に構造体を渡す場合は↓の4つの方法があります。
値渡し(コピー)
constを付けた値渡し
アドレス渡し(参照渡し)
constを付けたアドレス渡し
最初に構造体を値渡しする方法について見てみます。
値渡しと構造体
↓のコードが構造体の値渡しのサンプルです。
#include <stdio.h> // 動物を表す構造体 struct Animal { int age; // 年齢 char name[40]; // 名前 }; // 引数animalは値渡しされる void func(struct Animal animal) { printf("age[%d] name[%s]\n", animal.age, animal.name); // age[20] name[Tama] // メンバの値が全てコピーされている } int main(void) { // 構造体変数animalを定義する struct Animal animal = { 20, "Tama" }; // funcに値渡しでanimalを渡す func(animal); return 0; }
Animal
という構造体はメンバにage
とname
を持ちます。
main
関数内でこの構造体の変数animal
を定義しています。
その変数animal
を関数func
に渡しています。
この時、引数に&
などの演算子を付けない場合、それは値渡しになります。
値渡しでは構造体のメンバはすべて関数の引数にコピーされます。
Animal
のage
とname
がコピーされるということです。
値渡しによるコピーは、変数の独立性を保つという意味でよく使われることがあります。
しかし構造体のメンバをすべてコピーするので、速度的にはあまりよろしくありません。
プログラムの速度を上げたい場合は、後述するアドレス渡しを使います。
func()
の引数にconst
を付けると、その引数は読み取り専用の変数になります。
void func(const struct Animal animal) { animal.age = 30; // error }
構造体のメンバを保護したい場合はconst
を付けておくといいでしょう。
ただしconst
は全体的な設計にも影響を与えます。
ゲッターにはconst
を付けてセッターにはconst
を付けない、といったような統一的な設計が必要になります。
アドレス渡しと構造体
↓は関数に構造体をアドレス渡しするサンプルです。
#include <stdio.h> // 動物を表す構造体 struct Animal { int age; // 年齢 char name[40]; // 名前 }; // 引数animalはアドレス渡しされる void func(struct Animal *animal) { printf("age[%d] name[%s]\n", animal->age, animal->name); // age[20] name[Tama] // メンバの値が全てコピーされている } int main(void) { // 構造体変数animalを定義する struct Animal animal = { 20, "Tama" }; // funcにアドレス渡しでanimalを渡す // '&'がついているのが特徴 func(&animal); return 0; }
構造体変数animal
の先頭に&
というアドレス演算子を付けると、その変数のアドレス(ポインタ)を取り出せます。
このアドレスをfunc
に渡すことでアドレス渡しが成立します。
func()
側の引数では「struct Animal *animal
」というように、引数をポインタにしておきます。
func()
の内部では引数animal
はポインタです。
そのためanimal
のメンバにアクセスするにはアロー演算子(->
)を使います。
値渡しとアドレス渡しの違いは、まずメンバのコピーの有無です。
値渡しでは構造体のメンバはすべてコピーされますが、アドレス渡しではアドレスがポインタにコピーされるだけです。
つまりメンバのage
やname
はコピーされず、ポインタを通じて間接的に参照されるようになります。
間接的な参照なので、値渡しと違ってメンバの値を書き換えると、関数呼び出し側の変数の内容も変わります。
そのためこのようなアドレス渡しは、関数に渡した構造体を関数内部で変更したい時によく使われます。
参照だけで書き換えを不可にしたい場合はfunc()
側にconst
を付けます。
void func(const struct Animal *animal) { animal->age = 30; // error }
関数の返り値と構造体
関数の返り値で構造体の変数を返すことができます。
#include <stdio.h> // 動物を表す構造体 struct Animal { int age; // 年齢 char name[40]; // 名前 }; // 返り値の型がstruct Animalな関数func struct Animal func(void) { struct Animal animal = { 20, "Tama" }; return animal; // 値渡しで返す } int main(void) { // funcの返り値を構造体変数で受け取る struct Animal animal = func(); printf("age[%d] name[%s]\n", animal.age, animal.name); // age[20] name[Tama] return 0; }
関数の返り値の型(func
の返り値の型)がstruct Animal
になっています。
この場合、func()
内でreturn
される値は値渡しになります。
値渡しというか値返し?
そんな用語は聞かんぞ
この返り値の場合も構造体のメンバは全てコピーされて返されます。
ですので速度が欲しい場合にはあまり向いてない設計です。
しかし、頻繁に呼び出される関数でない場合はこの設計も有りかと思います。
たとえばcreate_animal()
という関数を作って、構造体をコピーで返す設計などが考えられます。
しかしcreate_animal()
が頻繁に呼び出される関数の場合は↓のようにinit_animal()
などに変更したほうがいいでしょう。
void init_animal(struct Animal *animal) { animal->age = 20; // 初期化処理 } int main(void) { struct Animal animal; init_animal(&animal); return 0; }
このような設計はcreate_animal()
と比べて一般的です。
おわりに
今回はC言語の構造体と関数について解説しました。
C言語の構造体はメンバをコピーしているとプログラムが遅くなることがあります。
特に頻繁に呼び出される関数でそういう設計になると、ボトルネックになる可能性が高まります。
C言語の構造体を関数で使う場合は値渡しとアドレス渡しを用途に合わせて使うようにすると吉です。
コピーは安全
ポインタは速い
関連動画
- C言語のアロー演算子の使い方【構造体のポインタから伸ばす演算子でメンバにアクセスする方法】 - Youtube
- C言語で構造体の継承を行う【オブジェクト指向プログラミング】 - Youtube
- C言語で構造体を初期化する【0クリア、初期化子リスト、指示初期化子】 - Youtube
- C言語の配列とは?その美しさに迫る - Youtube
- C言語のif文、パーフェクトガイド【初心者向け】 - Youtube
- C言語の難しい型、size_tの解説【初心者向け】 - Youtube
- C言語のデバッグ方法を解説しました【初心者向け】 - Youtube
- C言語でポインタと配列を入れ替える方法について解説します【初心者向け】 - Youtube