演習課題「RPGの冒険者のステータスを出力しよう」
右のコードエリアには、冒険者の Adventurer 構造体が宣言されています。
引数として構造体を受け取って、ステータスを表示する print_status 関数を完成させてください。
プログラムを実行して、正しく表示されれば演習課題クリアです!
期待する出力値
Job: 冒険者, MP: 120
Job: ウィザード, MP: 549
Job: クルセイダー, MP: 50
Job: プリースト, MP: 480
#04:構造体を使った関数をつくろう
構造体を使った関数を作成します。
構造体から宣言した変数は、通常の変数と同じように、関数の引数で渡すことができます。#include <stdio.h>
#include <string.h>
typedef struct {
char name[20];
int hp;
int power;
} Player;
// 構造体を引数として受け取る関数
void walk(Player player)
{
printf("%sは荒野を歩いていた\n", player.name);
}
int main(void)
{
Player hero;
strcpy(hero.name, "勇者");
hero.hp = 100;
hero.power = 100;
// 関数の呼び出し
walk(hero);
}
※ 関数の引数で構造体を渡す場合、値のコピーが渡されます。
そのため、構造体のサイズが大きいと (構造体のメンバにある配列が大きいなど) コピーに時間がかかってしまいます。
○ そのようなときに、以下のように構造体へのポインタを渡す方法が推奨されることがあります。#include <stdio.h>
#include <string.h>
typedef struct {
char name[20];
int hp;
int power;
} Player;
// 構造体へのポインタを引数として受け取る関数
void walk(Player *player)
{
printf("%sは荒野を歩いていた\n", (*player).name);
}
int main(void)
{
Player hero;
strcpy(hero.name, "勇者");
hero.hp = 100;
hero.power = 100;
// 関数の呼び出し
walk(&hero);
}
構造体を作成して返す init 関数は次のように定義できます。// 構造体を作成して返す関数
Player init(char *name)
{
Player player;
strcpy(player.name, name);
player.hp = 100;
player.power = 100;
return player;
}
構造体は、直接代入ができるデータ型なので、関数の戻り値にすることができます。
一方で配列は、値を直接代入できず、関数の戻り値にすることができません。
このチャプターで作成したコードです。// 構造体を使った関数をつくろう
#include <stdio.h>
#include <string.h>
typedef struct {
char name[20];
int hp;
int power;
} Player;
Player init(char *name)
{
Player player;
strcpy(player.name, name);
player.hp = 100;
player.power = 100;
return player;
}
void walk(Player player)
{
printf("%sは荒野を歩いていた\n", player.name);
}
int main(void)
{
Player hero = init("勇者");
Player wizard = init("魔法使い");
walk(hero);
walk(wizard);
}
init 関数について、応用的な内容を補足します。
"勇者" や "魔法使い" のようなダブルクォーテーションで囲った文字列は「文字列リテラル」と呼ばれます。
文字列リテラルには、原則として「実行時に書き換えることができない」という特徴があります。
たとえば、以下のコードを実行すると、paiza.IO の環境ではエラーになります。#include <stdio.h>
int main(void)
{
// s は文字列リテラルの先頭の要素を指し示すポインタ
char *s = "hello";
// 文字列リテラルの先頭の要素を書き換えようとするとエラーになる
s[0] = 'H';
}
今回作成した init 関数でも同様の問題が考えられます。
次のように、
・init 関数を呼び出すときに、引数に文字列リテラルを指定する
・init 関数の中で、引数で受け取った文字列リテラルを書き換える
ということをしてしまうと、エラーになってしまいます。#include <stdio.h>
#include <string.h>
typedef struct {
char name[20];
int hp;
int power;
} Player;
Player init(char *name)
{
name[0] = 'H'; // 文字列リテラルの先頭を書き換える (エラーになる)
Player player;
strcpy(player.name, name);
player.hp = 100;
player.power = 100;
return player;
}
int main(void)
{
Player hero = init("hero");
}
これを防ぐには、init 関数の引数で、書き換えができないことを表す const キーワードをつける必要があります。Player init(const char *name)
{
name[0] = 'H'; // 文字列リテラルの先頭を書き換える (コンパイルエラーになる)
Player player;
strcpy(player.name, name);
player.hp = 100;
player.power = 100;
return player;
}
○ このようにすれば、引数で受け取った name の文字列は書き換えができなくなるので、書き換えようとすると、コンパイルエラーになります。
○ コンパイルエラーになると、実行可能なプログラムが生成されないので、実行時エラーを防止できます。
- strcpy - 初心者のためのポイント学習 C 言語
http://www9.plala.or.jp/sgwr-t/lib/strcpy.html
- typedef - 超初心者向けプログラミング入門
https://programming.pc-note.net/c/typedef.html