C言語では構造体を使うことで、関連する複数のデータを1つにまとめて扱うことができるようになります。
プログラム内で様々な個人情報をユーザーごとに分けて処理する場合や、2次元・3次元空間の座標を表現したい場合など、データの集合を扱う際に構造体を使用することで、一つ一つのデータごとに変数を宣言する必要がなくなり、効率的かつ分かりやすいプログラムを作成することができます。
今回の記事では、この構造体について詳しく解説をしていきたいと思います!
C言語の構造体とは?
C言語における構造体(Structure)は、複数の値をまとめて格納することができるデータ型の一種です。
構造体の中に含まれる各データを「メンバ(member)」と呼び、それぞれに名前を付けて管理します。値を代入するための変数が複数用意されていると考えるとイメージしやすいかもしれません。
C言語で構造体を使用する際は、実体(インスタンス)と呼ばれるデータの集合を変数として宣言し、その変数をもとに各メンバを呼び出して値の代入や取得を行います。
似たような働きをするものに配列がありますが、配列は同じデータ型同士しか格納できないのに対し、構造体は int や char などの異なるデータ型同士で1つの構造体を定義することができるという点で大きく異なります。
構造体の使い方を解説
まずは、構造体の宣言方法や初期化の方法についてなど、基礎的な部分から順番に解説していきたいと思います。
structキーワードで構造体を宣言する
構造体を変数として扱うためには、あらかじめどのようなメンバで構成されているのかを定義しておく必要があります。
構成内容を定義する際は、以下のような形でコードを記述します。
【基本構文】
struct 構造体タグ名 {
データ型名1 メンバ名1;
データ型名2 メンバ名2;
・・・
};
「struct」キーワードの後に構造体のタグ名を指定し、「{};」ブロックの内部に保持したいメンバのデータ型名とメンバ名を順に定義します。
作成した構造体の実体を生成する場合は、以下のように記述して宣言を行います。
【基本構文】
struct 構造体タグ名 変数名;
構造体のデータ型名は「タグ名」ではなく「struct タグ名」のため、変数を宣言する際には必ずタグ名の前にstructを付けなければいけません。
コードをなるべく簡潔に書きたい!という場合には、「typedef」を使用してデータ型の名前を新しく付け直す方法があります。
【サンプルコード】
typedef struct character {
char *name;
int age;
int height;
int weight;
} CHARACTER;
CHARACTER chara; // 変数の宣言時
このように記述することで、新たに「CHARACTER」というデータ型が作成され、宣言時の型名として使用できるようになります。
typedefを使用する場合は、タグ名を省略して記述することもできます。
【サンプルコード】
typedef struct {
char *name;
int age;
int height;
int weight;
} CHARACTER;
構造体を初期化する
続いて、作成した実体を初期化する方法について説明していきます。
構造体の初期化の方法は、宣言時にまとめて値を代入する方法と、宣言後にそれぞれのメンバに代入する方法の二つがあります。
宣言と同時に一括で代入する場合は、次のように記述します。
【基本構文】
データ型(構造体)名 変数名 = { 値1, 値2, ... };
この方法を使う際の注意点ですが、一度に全ての値をまとめて代入できるのは、変数を宣言する時のみとなります。以下のような例はエラーが発生するため、行うことができません。
【エラーが出るコードの例】
CHARACTER chara;
chara = {"Taro", 26, 178, 64};
各メンバに個別で代入する場合は、以下のように記述します。
【基本構文】
データ型(構造体)名 変数名;
変数名.メンバ名1 = 値1;
変数名.メンバ名2 = 値2;
...
変数名の後に「. (ドット)」を付けてメンバ名を繋げることで、各メンバを呼び出すことができます。
以下のサンプルコードで、実際の使用例を見てみましょう。
【サンプルコード】
#include <stdio.h>
// struct+タグ名の場合
struct character {
char *name;
int age;
char *gender;
char *blood;
char *job;
};
// typedefを使う場合
typedef struct {
char *name;
int age;
char *gender;
char *blood;
char *job;
} CHARACTER;
// 代入用の関数
CHARACTER init(char *name, int age, char *gender, char *blood, char *job){
CHARACTER chara;
chara.name = name;
chara.age = age;
chara.gender = gender;
chara.blood = blood;
chara.job = job;
return chara;
}
int main() {
// 一括代入で初期化
struct character alex = {"Alex", 17, "男", "A", "剣士"};
printf("名前:%s 年齢:%d 性別:%s 血液型:%s 職業:%s\n", alex.name, alex.age, alex.gender, alex.blood, alex.job);
// 個別代入で初期化
CHARACTER bella;
bella.name = "Bella";
bella.age = 16;
bella.gender = "女";
bella.blood = "AB";
bella.job = "魔法使い";
printf("名前:%s 年齢:%d 性別:%s 血液型:%s 職業:%s\n", bella.name, bella.age, bella.gender, bella.blood, bella.job);
// 関数を使用して初期化
CHARACTER kate;
kate = init("Kate", 30, "不明", "O", "僧侶");
printf("名前:%s 年齢:%d 性別:%s 血液型:%s 職業:%s\n", kate.name, kate.age, kate.gender, kate.blood, kate.job);
return 0;
}
【実行結果】
名前:Alex 年齢:17 性別:男 血液型:A 職業:剣士
名前:Bella 年齢:16 性別:女 血液型:AB 職業:魔法使い
名前:Kate 年齢:30 性別:不明 血液型:O 職業:僧侶
今回のサンプルでは、一括代入する方法と個別代入する方法に加え、代入用の関数を自作して使用する例も合わせて紹介しています。
関数内で実体を生成し、引数の値を使って初期化した後に戻り値として返すことで、生成した実体を別の変数へと代入させることができます。宣言時の一括代入と同じような感覚で、任意のタイミングに初期化や再代入することができるようになる便利な方法なので、必要に応じて作成しておくといいでしょう。
Windowsで実行結果が文字化けしてしまう場合の対処法
日本語の文字列を扱っているCのソースコードをWindows環境でコンパイル・実行すると、正常に表示されず文字化けする場合があります。
その場合は、コードをコンパイルする前にコマンドプロンプトで chcp 65001 と入力してエンターキーを押してください。このコマンドを実行して “Active Code Page: 65001” と表示されてからもう一度コンパイルを行うと、正常な文字列が表示されるようになります。
これはCのソースコードを編集する際に使用するテキストエディタと、WindowsのOS内の基本設定とで文字コードが異なるため発生する現象です。
多くのテキストエディタでは UTF-8 という文字コードを使用しているのに対し、Windowsの言語設定を日本語にしている状態では基本的に CP932 という文字コードが使用されています。このように文字コードが異なると、その文字コード内で1文字ずつに割り振られている識別番号も異なるため、文字化けが発生してしまいます。
今回ご紹介した方法では、実行結果を表示するコマンドラインの文字コードをソースコードと同一の UTF-8 に設定することで文字化けを回避することができます。
構造体のポインタ、アロー演算子の使い方
次に、ポインタやアロー演算子の使い方について、詳しく解説していきたいと思います。
この記事を読んでいる方の中には、そもそもポインタとは何なのか?と疑問に感じている方もいるかもしれません。
ポインタとは、端的にまとめると「アドレスを代入するための変数」のことを指します。
プログラム実行時に使用するメモリには、各領域の場所を示すための番号が割り振られています。
変数は基本的に、宣言時に確保されたメモリ領域のアドレスを情報として保持しており、そのアドレス情報を元に領域にアクセスして、値の代入や変更を行なっているのです。
一方でポインタは、他の変数に与えられているアドレス情報そのものを格納して扱います。
ポインタを介してアドレス情報を共有することで、関数のブロック内で呼び出し元にある変数を直接扱うなど、本来の変数ではできないアクセス方法が可能になります。
ポインタを使用する際の宣言方法は、次のとおりです。
【基本構文】
データ型名 *ポインタ名;
通常の変数の時とは少々異なり、ポインタ名の前に「*(アスタリスク)」を付けて宣言します。
また、ポインタに値を代入する場合は、以下のように「&(アンパサンド)」を付ける必要があります。
【基本構文】
ポインタ名 = &変数名;
ポインタは、構造体に対しても使用することができます。
ポイントを介してメンバにアクセスする際に使うのが、「->(アロー演算子)」です。
以下のサンプルコードで、実際の使用例を見てみましょう。
【サンプルコード】
#include <stdio.h>
typedef struct {
char *name;
char *gender;
int age;
char *blood;
char *job;
} CHARACTER;
int main() {
CHARACTER jhone, *p;
// 実体のアドレスをポインタに代入
p = &jhone;
// ポインタを経由して初期化
p->name = "Jhone";
p->gender = "男";
p->age = 25;
p->blood = "B";
p->job = "格闘家";
printf("名前:%s 年齢:%d 性別:%s 血液型:%s 職業:%s\n", jhone.name, jhone.age, jhone.gender, jhone.blood, jhone.job);
return 0;
}
【実行結果】
名前:Jhone 年齢:25 性別:男 血液型:B 職業:格闘家
このように、アロー演算子を使用して構造体のメンバにアクセスすることができます。
ポインタには構造体のアドレス情報が格納されているため、ポインタを経由して代入した値が実体にも影響しているのが結果からも分かると思います。
構造体の実体をコピーするには?
構造体のデータを扱っているとき、「構造体の値を別の変数にコピーしたい」という場合もあり得ます。
実体を丸ごとコピーする場合は、同じ構成内容の構造体同士であれば、通常の変数と同じように代入式を使ってコピーすることができます。
【サンプルコード】
#include <stdio.h>
typedef struct {
char *name;
int age;
char *gender;
char *blood;
char *job;
} CHARACTER;
int main() {
CHARACTER chara1, chara2;
chara1.name = "Jhone";
printf("chara1の名前:%s\n", chara1.name);
// 実体をコピー
chara2 = chara1;
printf("chara2の名前:%s\n", chara2.name);
return 0;
}
【実行結果】
chara1の名前:Jhone
chara2の名前:Jhone
関数の引数に実体を渡して、実行中のブロック内で代入することもできます。
【サンプルコード】
#include <stdio.h>
typedef struct {
char *name;
int age;
char *gender;
char *blood;
char *job;
} CHARACTER;
void copyChara(CHARACTER chara) {
// 引数で渡された実体を代入
CHARACTER chara2 = chara;
printf("chara2の名前:%s\n", chara2.name);
}
int main() {
CHARACTER chara1;
chara1.name = "Jhone";
printf("chara1の名前:%s\n", chara1.name);
// 生成した実体を引数に渡す
copyChara(chara1);
return 0;
}
【実行結果】
chara1の名前:Jhone
chara2の名前:Jhone
実体を比較するには?
それぞれの実体が同じ内容であるかどうかを比較する時、主に2つの比較方法が存在します。
1つは、実体の各メンバごとに値を比べて判断する方法です。
メンバの数が多いほどコードの記述に手間がかかることと、構造体の定義内容が変わった場合に修正をしなければいけないデメリットがありますが、もう1つの方法は後述する理由により正確性にやや欠けるケースがあるため、より確実に動作する方法はこちらになります。
この方法で比較を行う場合は、関数を自作して構造体の近くに置いておくと管理しやすくなります。
【サンプルコード】
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
typedef struct
{
char *name;
int age;
char *gender;
} CHARACTER;
bool equalChara(CHARACTER *chara1, CHARACTER *chara2)
{
if (strcmp(chara1->name, chara2->name) != 0)
{
return false;
}
if (chara1->age != chara2->age)
{
return false;
}
if (strcmp(chara1->gender, chara2->gender) != 0)
{
return false;
}
return true;
}
int main()
{
CHARACTER chara1 = {"Emma", 20, "女"};
CHARACTER chara2 = chara1;
// 比較結果を表示
printf("%d\n", equalChara(&chara1, &chara2));
return 0;
}
【実行結果】
1
C言語では真偽値(true/false)を整数値として返す場合、 false = 0, true = 1 となります。そのため、ここでは1が返ってきていることで比較した結果が同値であることが分かります。
もう1つの比較方法として、memcmp関数を使用する比較方法がありますが、構造体においてはあまり推奨できません。
なぜなら、全く同じ内容の値が入れられている構造体でも、その他のコード内容や実行環境によってはサイズが異なるケースがあるからです。
なぜそのような現象が起こるのかというと、「アライメント」という機能が関係しています。
C言語では、各変数のメモリ領域を確保する場合に、CPUがデータにアクセスしやすいようにメモリ上の位置調整を自動で行います。この調整作業のことをアライメントと言います。
構造体を宣言すると、このアライメントによって余分な隙間が意図的に作られることがあるのです。この隙間のことを「パディング」と言います。
例えば、以下のような構造体があったとします。
struct sample {
char a;
int num;
};
環境によって変わることもありますが、この例ではcharが1バイト、intが4バイトだったとします。
なので、構造体のサイズは各メンバの合計である5バイト……ではなく、実際は8バイトになったりします。
これが、先ほど説明したアライメントの影響です。メモリへのアクセスは基本的に偶数単位(4バイトのケースが多い)で行われるため、領域ごとの区切りが4の倍数になるようパディングされているのです。
このパディングによって、同じ内容の構造体同士でも異なる実体であると誤認される可能性があるため、構造体はメンバごとの比較で判定する方が安全です。
構造体を配列で扱うには?
他のデータ型と同じように、構造体も配列の要素に加えることができます。
以下のサンプルコードで確認してみましょう。
【サンプルコード】
#include <stdio.h>
typedef struct {
char *name;
int age;
char *gender;
char *blood;
char *job;
} CHARACTER;
CHARACTER init(char *name, int age, char *gender, char *blood, char *job) {
CHARACTER chara;
chara.name = name;
chara.age = age;
chara.gender = gender;
chara.blood = blood;
chara.job = job;
return chara;
}
int main(void) {
// CHARACTER型を格納する配列を作成
CHARACTER charaArr[3];
// 各要素に実体を代入
charaArr[0] = init("Alex", 17, "男", "A", "剣士");
charaArr[1] = init("Bella", 16, "女", "AB", "魔法使い");
charaArr[2] = init("Kate", 30, "不明", "O", "僧侶");
for (int i = 0; i < 3; i++)
{
printf("名前:%s 年齢:%d 性別:%s 血液型:%s 職業:%s\n", charaArr[i].name, charaArr[i].age, charaArr[i].gender, charaArr[i].blood, charaArr[i].job);
}
return 0;
}
【実行結果】
名前:Alex 年齢:17 性別:男 血液型:A 職業:剣士
名前:Bella 年齢:16 性別:女 血液型:AB 職業:魔法使い
名前:Kate 年齢:30 性別:不明 血液型:O 職業:僧侶
関数の引数として構造体を使用するには?
実体のコピー方法の際にも少し触れましたが、関数の引数に構造体を指定することもできます。
先ほどのサンプルでは実体をそのまま渡していましたが、今度はポインタを引数に渡して処理してみましょう。
【サンプルコード】
#include <stdio.h>
typedef struct
{
char *name;
int age;
char *gender;
char *blood;
char *job;
} CHARACTER;
void changeJob(CHARACTER *chara, char *job)
{
chara->job = job;
}
int main(void)
{
CHARACTER alex = {"Alex", 17, "男", "A", "剣士"};
printf("名前:%s 年齢:%d 性別:%s 血液型:%s 職業:%s\n", alex.name, alex.age, alex.gender, alex.blood, alex.job);
changeJob(&alex, "騎士");
printf("名前:%s 年齢:%d 性別:%s 血液型:%s 職業:%s\n", alex.name, alex.age, alex.gender, alex.blood, alex.job);
return 0;
}
【実行結果】
名前:Alex 年齢:17 性別:男 血液型:A 職業:剣士
名前:Alex 年齢:17 性別:男 血液型:A 職業:騎士
ポインタを引数に渡した場合は、呼び出し元にある実体の値を直接変更することができます。
別の変数として扱いたいか否かで、引数に渡す時の型を使い分けると便利です。
まとめ
いかがでしたか?今回は、構造体について解説をしてきました。
中には少し複雑な内容もあったため、一度で全てを理解するのは難しい方もいたかもしれません。
ぜひ、何度も記事を読み返して、構造体についての知識を深めてみてください!
C言語の勉強方法は?
書籍やインターネットで学習する方法があります。昨今では、YouTubeなどの動画サイトやエンジニアのコミュニティサイトなども充実していて多くの情報が手に入ります。
そして、より効率的に知識・スキルを習得するには、知識をつけながら実際に手を動かしてみるなど、インプットとアウトプットを繰り返していくことが重要です。特に独学の場合は、有識者に質問ができたりフィードバックをもらえるような環境があると、理解度が深まるでしょう。
ただ、C言語に限らず、ITスキルを身につける際、どうしても課題にぶつかってしまうことはありますよね。特に独学だと、わからない部分をプロに質問できる機会を確保しにくく、モチベーションが続きにくいという側面があります。独学でモチベーションを維持する自信がない人にはプログラミングスクールという手もあります。費用は掛かりますが、その分スキルを身につけやすいです。しっかりと知識・スキルを習得して実践に活かしたいという人はプログラミングスクールがおすすめです。
プログラミングスクールならテックマニアがおすすめ!

ITスキル需要の高まりとともにプログラミングスクールも増えました。しかし、どのスクールに通うべきか迷ってしまう人もいるでしょう。そんな方にはテックマニアをおすすめします!これまで多くのITエンジニアを育成・輩出してきたテックマニアでもプログラミングスクールを開講しています。
<テックマニアの特徴>
・たしかな育成実績と親身な教育 ~セカンドキャリアを全力支援~
・講師が現役エンジニア ~“本当”の開発ノウハウを直に学べる~
・専属講師が学習を徹底サポート ~「わからない」を徹底解決~
・実務ベースでスキルを習得 ~実践的な凝縮カリキュラム~
このような特徴を持つテックマニアはITエンジニアのスタートラインとして最適です。
話を聞きたい・詳しく知りたいという方はこちらからお気軽にお問い合わせください。