【C言語】関数と構造体を組み合わせて使う
目次
C言語の関数で構造体を扱う方法
この記事ではC言語の関数で構造体を扱う方法を解説します。
関数と構造体は組み合わせて使われることが非常に多いです。
ですのでこれらの組み合わせの方法について理解しておくことはC言語を学ぶ上で重要と言えます。
関連記事
前提とする構造体
今回の解説で使用する構造体の定義は↓になります。
struct Monster { int age; // 年齢 double height; // 身長 char name[20]; // 名前 };
構造体Monster
はモンスターを表現する構造体です。
ゲームなどで使いそうな感じですね。
(^ _ ^) | モンスターをコードで表現 |
構造体変数を引数で渡す
構造体の変数を関数の引数に渡してその引数を関数内で使うというケースがあります。
構造体変数を関数に渡す場合、コピー渡しとポインタ渡しの2つの方法があります。
コピー渡し
ポインタ渡し
コピー渡しは構造体のメンバを丸ごと引数にコピーする方法です。
コピーが発生する分、ポインタ渡しより遅くなる場合があります。
いっぽうポインタ渡しは構造体変数のアドレスを引数のポインタ変数に渡す方法です。
ポインタ渡しでは余分なメンバのコピーが発生しないのでコピー渡しと比べて速度的に有利になる場合があります。
構造体変数をコピーするケースは必要になる時がありますがどちらかというとポインタ渡しの方がよく行われます。
C言語でプログラムを作るのは速度面で有利だからですが、構造体変数のコピー渡しを繰り返してプログラムが遅くなったら本末転倒です。
コピー渡し
まずコピー渡しです。
これは関数の引数に普通に構造体変数を渡すだけです。
#include <stdio.h> struct Monster { int age; // 年齢 double height; // 身長 char name[20]; // 名前 }; void func(struct Monster arg) { // argはコピー済みの引数 printf("%d\n", arg.age); // 20 printf("%f\n", arg.height); // 170.12 printf("%s\n", arg.name); // Tama } int main(void) { // monsterを定義する struct Monster monster = { 20, 170.12, "Tama" }; func(monster); // ここでmonsterがargにコピーされる return 0; }
↑のコードではfunc()
というのが関数です。
この関数にmain関数内で定義したmonster
変数を渡します。
この時にfunc()
の引数arg
にmonster
がコピーされます。
コピーされた構造体変数のメンバはfunc()
内でアクセスできます。
ポインタ渡し
次にポインタ渡しです。
これは関数の引数をポインタ変数にしておきます。
そして関数を呼び出すときに引数に構造体変数のアドレスを渡します。
#include <stdio.h> struct Monster { int age; // 年齢 double height; // 身長 char name[20]; // 名前 }; void func(struct Monster *arg) { // argはポインタ printf("%d\n", arg->age); // 20 printf("%f\n", arg->height); // 170.12 printf("%s\n", arg->name); // Tama } int main(void) { // monsterを定義する struct Monster monster = { 20, 170.12, "Tama" }; func(&monster); // ここでmonsterのアドレスをfunc()に渡す return 0; }
↑の場合func()
に構造体変数monster
のアドレスを渡しています。
monster
のアドレスはfunc()
の引数のarg
にコピーされます。
そうするとarg
からはアロー演算子(->
)を伸ばすとmonster
のメンバにアクセスできます。
↑のコードではfunc(struct Monster *arg)
となっています。
これはarg
のメンバに対して変更を加えられる状態です。
たとえば↓のようにfunc()
内でarg
のメンバを変更することができます。
void func1(struct Monster *arg) { arg->age = 30; }
このメンバの書き換えは関数の呼び出し元のもとになっている変数monster
にも影響します。
関数を呼び出した後はmonster.arg
の値が書き換えられて変更されています。
このようなメンバの書き換えを制限したい、つまり書き換えを行えないようにしたい場合はconst
を使います。
↓のように関数の引数にconst
を付けるとメンバが保護されます。
void func2(const struct Monster *arg) { // arg->age = 30; // error! }
しかしconst
修飾子も絶対ではありません。
const
外しという荒業があります。
これを使うとconst
を外すことができます。
void func3(const struct Monster *arg) { struct Monster *var = (struct Monster *) arg; var->age = 40; }
const
外しはプログラムの設計が失敗している時によく発生すると言われています。
頻繁にconst
外しが必要になっている場合は設計を見直したほうが良いでしょう。
構造体変数を返り値で返す
関数と言えば入力と処理と出力です。
関数への入力は引数で行います。
関数から出力は返り値で行います。
ここでは関数から構造体を返す方法について解説していきます。
返り値はコピーされる
関数内の構造体変数をそのままreturn
で返すと変数はコピーされます。
#include <stdio.h> struct Monster { int age; // 年齢 double height; // 身長 char name[20]; // 名前 }; struct Monster create_monster(void) { struct Monster ret = { 20, 170.12, "Tama" }; return ret; // コピーが発生 } int main(void) { struct Monster monster = create_monster(); // コピーが発生 printf("%d\n", monster.age); // 20 printf("%f\n", monster.height); // 170.12 printf("%s\n", monster.name); // Tama return 0; }
↑の場合、関数はcreate_monster()
です。
create_monster()
の返り値はstruct Monster
になっています。
この関数から関数内の構造体変数をそのまま返すと値はコピーされます。
↑のコードではcreate_monster()
内のret
変数が返り値になってますが、これは呼び出し元のmonster
に内容がコピーされています。
ですのでcreate_monster()
の呼び出しもとでmonster
のメンバを参照することができます。
動的なメモリ確保でポインタを返す
動的なメモリ確保で構造体変数のメモリを確保し、そのポインタを関数から返す設計も一般的です。
たとえば↓のようにです。
#include <stdio.h> #include <stdlib.h> #include <string.h> struct Monster { int age; // 年齢 double height; // 身長 char name[20]; // 名前 }; struct Monster *create_monster(void) { // malloc()で動的メモリ確保 struct Monster *ret = malloc(sizeof(*ret)); if (ret == NULL) { return NULL; } // メンバを初期化 ret->age = 20; ret->height = 170.12; strcpy(ret->name, "Tama"); return ret; } int main(void) { struct Monster *monster = create_monster(); if (monster == NULL) { perror("create_monster"); return 1; } printf("%d\n", monster->age); // 20 printf("%f\n", monster->height); // 170.12 printf("%s\n", monster->name); // Tama free(monster); // 忘れずにメモリを開放する return 0; }
malloc()
系の関数などで構造体変数のメモリを確保してメンバを初期化します。
そしてそのメモリのポインタを返り値で返します。
create_monster()
の呼び出し元には動的にメモリ確保されたポインタが返ってきます。
アロー演算子(->
)でメンバにアクセスして値を参照できます。
確保したメモリはfree()
関数などでちゃんと解放しておく必要があります。
メモリの開放を忘れるとメモリリークというバグになります。
静的な構造体変数のポインタを返す
静的(static
)な構造体変数のポインタを関数から返すという方法もあまり一般的ではありませんがあります。
#include <stdio.h> #include <stdlib.h> #include <string.h> struct Monster { int age; // 年齢 double height; // 身長 char name[20]; // 名前 }; struct Monster *get_static_monster(void) { static struct Monster ret = { 20, 170.12, "Tama" }; return &ret; } int main(void) { struct Monster *monster = get_static_monster(); printf("%d\n", monster->age); // 20 printf("%f\n", monster->height); // 170.12 printf("%s\n", monster->name); // Tama return 0; }
↑の場合get_static_monster()
内で定義されるret
は静的に1回だけ初期化されます。
この静的な変数のメモリの寿命はプログラムが終了するまでです。
その静的な変数のアドレスを返り値で返して呼び出し元で使うという感じです。
これは今までの方法と比べるとあまり一般的な設計ではありません
すこし特殊な設計です。
使いどころも特殊なわけですが使いようはありそうですね。
おわりに
今回はC言語で関数と構造体を使う方法を解説しました。
これらの組み合わせは非常によく使われます。
押さえておいていつでも使えるようにしておきましょう。
(^ _ ^) | C言語で関数と構造体を使う |
(・ v ・) | 色々なバリエーションがあるよ |