このページでは、C言語の列挙型および enum
について解説していきます。
Contents
列挙型と enum
では早速、列挙型と enum
について解説していきます。
列挙型による列挙子の定義
列挙型とは複数の関連する列挙子(定数)を1つの集合として定義する型です。
この列挙型を定義する際に使用するキーワードが enum
となります。
列挙型は下記のように記述することで定義することができます。
enum {
列挙子1,
列挙子2,
列挙子3
};
{
}
内に ,
区切りで記述した各 列挙子
が定数として定義されます。
上記の例であれば、列挙子1
・列挙子2
・列挙子3
の3つが定数として定義されたということですね!さらに、各列挙子には自動的に数値が割り振られることになります。
上記では 列挙子
ごとに改行を入れていますが、,
で区切っておけば改行なしに横に並べる形で記述しても良いです。
例えば、下記のように定義を行えば、
enum {
DAY_MONDAY,
DAY_TUESDAY,
DAY_WEDNESDAY,
DAY_THURSDAY,
DAY_FRIDAY,
DAY_SATURDAY,
DAY_SUNDAY
};
下記の7つの列挙子が定数として定義されることになります。
DAY_MONDAY
DAY_TUESDAY
DAY_WEDNESDAY
DAY_THURSDAY
DAY_FRIDAY
DAY_SATURDAY
DAY_SUNDAY
スポンサーリンク
列挙型の型定義
さらに、enum
と typedef
とを組み合わせることで、定義した列挙子を扱う新たな型を定義することもできます(より正確に言えば、定義した列挙型を異なる名前の型として使用することができる)。
さまざまな書き方が可能ではありますが、下記のように enum
の前に typedef
を記述し、}
の後ろ側に 型名
を記述することで、{
}
内の定数のみを扱うことを意図する型を 型名
として定義することができます。
typedef enum {
列挙子1,
列挙子2,
列挙子3
} 型名;
新たに定義した型も、int
型や char
型などの組み込み型と同様にして変数宣言時に使用したり、関数の引数や戻り値の型として利用することができます。
例えば下記では、DAY
という型を定義し、変数 day
の型として、さらに関数 printDayOfWeek
の引数の型として利用している例になります。
#include <stdio.h>
typedef enum {
DAY_MONDAY,
DAY_TUESDAY,
DAY_WEDNESDAY,
DAY_THURSDAY,
DAY_FRIDAY,
DAY_SATURDAY,
DAY_SUNDAY
} DAY;
void printDayOfWeek(DAY day) {
switch(day) {
case DAY_MONDAY:
printf("月曜日です\n");
break;
case DAY_TUESDAY:
printf("火曜日です\n");
break;
case DAY_WEDNESDAY:
printf("水曜日です\n");
break;
case DAY_THURSDAY:
printf("木曜日です\n");
break;
case DAY_FRIDAY:
printf("金曜日です\n");
break;
case DAY_SATURDAY:
printf("土曜日です\n");
break;
case DAY_SUNDAY:
printf("日曜日です\n");
break;
default:
printf("day error\n");
break;
}
}
int main(void) {
DAY day = DAY_FRIDAY;
printDayOfWeek(day);
printf("%d\n", DAY_FRIDAY);
return 0;
}
列挙子として記述した DAY_MONDAY
〜 DAY_SUNDAY
を定数として扱うことができ、 変数 day
への代入や switch
文における case
のラベルとしての指定が可能であることも確認できると思います。
ちなみに、ここで使用した typedef
については下記ページで解説していますので、詳しく知りたい方は下記ページをご参照いただければと思います。
列挙子に割り当てられる値
また、上記のソースコードをコンパイルして実行すると次のような結果が表示されます。
金曜日です 4
注目していただきたいのは2行目で、これは printf("%d\n", DAY_FRIDAY);
によって出力された結果になります。この結果から、DAY_FRIDAY
には 4
という整数が割り当てていることが確認できます。
ただ、列挙型定義の際には各列挙子に値は割り当てていないので、この値は自動的に割り当てられたものであることが確認できます。こんな感じで、列挙子には自動的に値が割り当てられることになります。
具体的には、先頭の列挙子から順に 0
, 1
, 2
… と整数が連番で割り振られることになります。
ですので、上記の例では DAY_MONDAY
には 0
が DAY_TUESDAY
には 1
が、DAY_SUNDAY
には 6
が割り当てられることになります。
後述の 列挙型の詳細 で解説しますが、手動で好きな値を割り当てることも可能です
列挙型の注意点
また、上記の DAY
型の変数には DAY_MONDAY
から DAY_SUNDAY
の7つの列挙子しか代入できないようにも思えますが、C言語 の場合は他の値も代入できてしまうので注意してください(代入しても警告すら出ないはず)。
例えば main
関数の1行目を DAY day = -1;
に変更したとしてもコンパイルは正常に終了し、プログラムの実行も可能です。他の言語の場合、列挙子以外を代入するとコンパイル等でエラーになることもあります。
つまり、特に C言語 の場合、列挙型の型を定義することで、”この型の変数ではこれらの列挙子しか扱いません” という意思を示せたとしても、実際に他の値が代入されているかどうかまではチェックされないので注意が必要です(静的解析など利用すればチェックすることもできるかもしれないです)。
とはいえ、”この型の変数ではこれらの列挙子しか扱いません” という意思が示されることで、ソースコードも書きやすく・さらに読みやすくすることができます。これについては後述で実例を示しながら説明します。
スポンサーリンク
列挙型のメリット
続いて、この列挙型を利用するメリットについて解説していきます。
ソースコードの可読性が上がる
まず「ソースコードの可読性が上がる」というのがメリットの1つになります。
例えば、下記の findNearest_1
が何を行う関数であるか理解できるでしょうか?
void findNearest_1(int day) {
switch(day) {
case 0:
printf("今日です\n");
break;
case 1:
printf("1日前です\n");
break;
case 2:
printf("2日前です\n");
break;
case 3:
printf("3日前です\n");
break;
case 4:
printf("3日後です\n");
break;
case 5:
printf("2日後です\n");
break;
case 6:
printf("1日後です\n");
break;
default:
printf("day error\n");
break;
}
}
関数名をわざと分かりにくくしているというのもありますが、何を行う関数か分からないと思う人が多いと思います。
列挙型を使用した場合、findNearest_1
関数は下記の findNearest_2
関数のように書き換えることができます。おそらく何を行う関数であるかが分かりやすくなったのではないかと思います。
void findNearest_2(DAY day) {
switch(day) {
case DAY_MONDAY:
printf("今日です\n");
break;
case DAY_TUESDAY:
printf("1日前です\n");
break;
case DAY_WEDNESDAY:
printf("2日前です\n");
break;
case DAY_THURSDAY:
printf("3日前です\n");
break;
case DAY_FRIDAY:
printf("3日後です\n");
break;
case DAY_SATURDAY:
printf("2日後です\n");
break;
case DAY_SUNDAY:
printf("1日後です\n");
break;
default:
printf("day error\n");
break;
}
}
“引数で指定した曜日から一番近い月曜日が何日前 or 何日後であるかを表示する関数” であることが分かるのではないかと思います。
最初にお見せした findNearest_1
関数では、0
から 6
の数値が何を表しているかが分からないのでソースコードが読みにくいです。
その一方で、findNearest_2
関数では意味のある文字列として値が表現されているので、ソースコードが読みやすくなります。
基本的にソースコードでは、単に数値が記述されている場合、その数値の意味が分かりにくくなることが多いです。ですが、それを変数名や定数名、さらには今回紹介している列挙子のように、意味のある文字列として表すことで意味が分かりやすくなり、ソースコードの可読性を上げることができます。
分かりにくい “数値への意味付け” が避けられる
さて、ここで問題ですが、前述で示した findNearest_1
関数の引数に “日曜日を表す数値” を指定する場合、具体的に何の値を指定すれば良かったでしょうか?
ちなみに 6
が正解ですが、0
と考えた人も多いのではないでしょうか?週の初めを日曜日と考えた場合は、むしろ 0
の方が正しい気がしますよね。
こんな感じで、”数値自体には意味はないが、区別できるように無理やり数値に意味付けをする” ような場合、人によって「どの数値」に「どのような意味付けをするか」の考え方がばらつくこともあり得ます。
例えば日曜日が週の始まりと考えている人であれば、findNearest_1
関数に日曜日を表す値を引数に渡すために 0
を指定してしまい、それがバグとなる可能性もあります。
ただ、”数値自体には意味はないが、区別できるように無理やり数値に意味付けをする” 場面って結構多いと思うんですよね。
そんな時に列挙型を利用すれば、意味を持たせた文字列(列挙子)により区別することができ、無理やり数値に意味付けをすることが避けられます。
もちろん無理やり数値に意味付けをする事でもプログラム自体は上手く動作させられることはできるのですが、ソースコード作成者以外の人にとって、その数値がどういう意図で使用されているのかが分かりにくくなってしまいます。
意図が分からないと後から変更しにくいですし、変更するとどこまで影響範囲が広がるのかもソースコード作成者以外には把握しにくいです(ソースコード作成者が忘れる可能性もあります)。
列挙型を利用すれば、この分かりにくい数値への意味付けを避けることができます。そして、列挙子の名前で意味合いを明確に示すことで、他の人でも読みやすく、意図の分かりやすいソースコードにすることができます。
ちなみに、このようなソースコードに直に記述された出どころのよく分からない数値のことをマジックナンバーと呼びます。
スポンサーリンク
使いやすい関数が実現できる
また、列挙型の引数を利用することで、その引数に指定可能な値が明確になり、より使いやすい関数が実現できるというメリットもあります。
例えば下記の例では、引数の型が int
なので、int
型の最小値から最大値、環境にもよりますが -2147483648
〜 2147483647
が引数として指定可能になります。
関数仕様書をしっかり読まないと、この範囲の値の中で findNearest_1
を上手く動作させるためにはどの値を指定すれば良いのかが判断しにくいです。
void findNearest_1(int);
その一方で、下記のように型の定義とプロトタイプ宣言が記述されていれば、引数には DAY_MONDAY
から DAY_SUNDAY
のいずれかを指定すれば良いことが明確に分かります。
typedef enum {
DAY_MONDAY,
DAY_TUESDAY,
DAY_WEDNESDAY,
DAY_THURSDAY,
DAY_FRIDAY,
DAY_SATURDAY,
DAY_SUNDAY
} DAY;
void findNearest_2(DAY);
列挙型の注意点 で解説した通り、C言語の場合は DAY_MONDAY
〜 DAY_SUNDAY
の値も引数で指定することもできてしまいますが、それでも、関数利用者からすれば関数が使いやすくなることは理解していただけるのではないかと思います。
もちろん、プログラムをあなた一人で開発している場合、関数作成も関数利用もあなたが行うわけですから、列挙型を利用しなくても上手くプログラムを開発していくことができるかもしれません。
ただ、複数人でプログラム開発を行なっている場合、あなたが作成した関数を他の人が利用する可能性もあります。そのような場合は、やっぱり使いやすい関数の方が喜ばれますし、使いやすい関数の方がバグの可能性も減らせます。この辺りも考慮し、必要に応じて列挙型を積極的に利用するようにした方が良いです。
数値を割り当てる際のミスが防げる
また、C言語では列挙型をわざわざ利用しなくても定数を扱うことは可能です。例えば #define
で定義した定数マクロを利用するのでも、ソースコードの可読性の向上を実現することができます。
例えば下記のように #define
すれば、ソースコードの可読性が上がる で紹介した findNearest_2
関数とほぼ同じ可読性を持つ関数を列挙型を利用することなく実現することができます。
#define DAY_MONDAY 0
#define DAY_TUESDAY 1
#define DAY_WEDNESDAY 2
#define DAY_THURSDAY 3
#define DAY_FRIDAY 4
#define DAY_SATURDAY 5
#define DAY_SUNDAY 6
では、#define
による定数マクロではなく、わざわざ列挙型を利用するメリットはどのような点にあるでしょうか?
まず、列挙型はコンパイラによって解釈され、#define
はプリプロセッサによって解釈されるという違いがあります。
ただ、もっと単純に考えて、今回は数値自体に意味はなく、単に区別できれば良いだけですので、わざわざ上記のように各定数に数値を割り当てるのはちょっと面倒です。
また、人の手で数値を割り当てることになるのでミスも起こり得ます。例えば、全定数マクロに異なる値を割り当てようと考えていても、間違って同じ値を割り当てしまい、それがバグになってしまう可能性もあります。
列挙型の場合、自動的に列挙子に数値が割り当てられるので楽ができます。また自動的に割り当てられるので、ミスも起こらなくなります。
ただし、後述の 列挙型の詳細 で解説するように、実は列挙子には手動であなた自身で好きな値を割り当てることもできます。
手動で値を割り当てる場合、上記で解説したようなミスが発生する可能性もあるので注意してください。
関連性が分かりやすくなる
また、列挙子(定数)同士の関連性が分かりやすくなるというメリットもあります。
例えば下記のように列挙型が定義されていれば、どの列挙子同士に関連性があるのか、どの列挙子同士が同じグループのものであるかが明白だと思います。
enum {
JPG, PNG, BMP
};
enum {
NORMAL, ACTIVE, WAIT
};
enum {
RED, GREEN, BLUE
};
もちろん、#define
による定数マクロでも、マクロ名を工夫したり同じ位置に固めて定義を行うことで関連性を示すこともできますが、列挙型の場合は関連性があることを自然と示すことが可能です。
逆に関連性のない列挙子を1つの列挙型の中で定義すると読み手にとって分かりにくくなりますので、1つの列挙型の中で定義するのではなく、別の列挙型に分けて定義する方が良いと思います。
スポンサーリンク
列挙型の詳細
ここまで、大まかに列挙型の特に重要な点について解説をしてきました。
ここからは、もう少し細かい列挙型の使い方や特性等について解説していきたいと思います。
列挙子に自身で値を割り当てることが可能
ここまで列挙子には自動で値を割り当ててきましたが、自身で手動で値を割り当てることも可能です。
下記は列挙子に手動で値を割り当てる例となります。
enum {
DAY_MONDAY = 1,
DAY_TUESDAY,
DAY_WEDNESDAY = 5,
DAY_THURSDAY,
DAY_FRIDAY,
DAY_SATURDAY = 10,
DAY_SUNDAY
};
DAY_MONDAY
には 1
が、DAY_WEDNESDAY
には 5
が、DAY_SATURDAY
には 10
がそれぞれ割り当てられることになります。
手動で値を割り当てなかった列挙子には自動的に値が割り当てられることになります。
この時、上側で手動で値を割り当てた列挙子から順番に連番になるように値が割り当てられます。
DAY_TUESDAY
であれば、DAY_MONDAY
から連番になるように 2
が割り当てられますし、DAY_FRIDAY
であれば DAY_WEDNESDAY
から連番になるように 7
が割り当てられます(同様に DAY_THURSDAY
には 6
が割り当てられます)。
列挙子に割り当てることができるのは整数のみ
ただし、列挙子に割り当てることができるのは整数のみ、より正確には整数として扱われるもののみとなります。
例えば列挙子に浮動小数点数を割り当てようとするとコンパイルエラーになります。
逆に、整数として扱われるものであれば指定可能で、例えば他の列挙子であったり、整数の定数マクロ、16進数表記の整数、演算式なども指定可能です。
下記に列挙子への様々な値の割り当ての例を示します。コメントで /* NG */
と記述している行は、ダメな値の割り当て例となります。
enum {
DAY_MONDAY = -100 * 50, /* OK */
DAY_TUESDAY = 0x1234, /* OK */
DAY_WEDNESDAY = DAY_MONDAY, /* OK */
DAY_THURSDAY = 0.5, /* NG */
DAY_FRIDAY = (double)100, /* NG */
DAY_SATURDAY = "aiueo", /* NG */
DAY_SUNDAY = (int*)0x1234 /* NG */
};
スポンサーリンク
列挙子に割り当てる値を重複させることも可能
また、列挙子に意図的に重複した整数を割り当てることも可能です。
例えば下記のように列挙子に整数を割り当てたとしてもコンパイルエラーにはなりません(少なくともこの箇所では)。
enum {
DAY_MONDAY = 2,
DAY_TUESDAY,
DAY_WEDNESDAY,
DAY_THURSDAY,
DAY_FRIDAY,
DAY_SATURDAY = 3,
DAY_SUNDAY
};
上記の例においては、DAY_TUESDAY
と DAY_SATURDAY
とに同じ 3
が割り当てられ、DAY_WEDNESDAY
と DAY_SUNDAY
とに同じ 4
が割り当てられることになります。
重複した値を割り当てることで、複数の列挙子を同じ種類のものとして扱うことが可能で、異なる列挙子であっても同じ処理を行いたいような場合は便利だと思います。
ただ、意図せず同じ値が割り当てられる可能性があることにも注意が必要です。意図せず同じ値が割り当てられた場合、プログラムが思った通りに動かなかったり、switch
文の case
のラベル指定時にコンパイルエラーが発生する可能性があります(1つの swtich
文の case
の各ラベルには異なる値を設定しなければならない)。
また、他の人がソースコードを見て、意図して同じ値を割り当てるのかがちょっと分かりにくいです。例えば上記の例であれば、特に DAY_WEDNESDAY
と DAY_SUNDAY
に同じ値を割り当てているのが意図したものなのかバグなのかが見分けつきにくいです。
同じ値を割り当てる場合は、少なくとも「意図して同じ値を割り当てていること」が分かるようなコメントを書いた方が良いと思います。
ここまで列挙子への数値の手動での割り当てについて解説してきましたが、私自身は列挙子に数値を手動で割り当てた経験はあまり無いですね…。使用する目的の多くが単に “列挙子で場合分けを行いたいだけ” なので、列挙子に実際に割り当てられる数値は重複していなければなんでも良いです。
手動で割り当てなかった場合は自動的に重複しないように値が割り振られますし、割り当てる手間も省けるので手動での割り当てはあまり行いません。
ただ、列挙子に割り当てる値自体に意味を持たせたい場合は、ここまで解説してきたような方法で数値を割り当てる必要はあると思います。
列挙型にタグ名を指定することも可能
少し話は変わりますが、列挙型を定義する際にはタグ名を指定することが可能です。この場合、typedef
を利用しなくても変数宣言時等に enum タグ名
の形で型を指定することが可能になります。
enum タグ名 {
列挙子1,
列挙子2,
列挙子3
};
例えば、列挙型の型定義 で示したソースコードは下記のように書き換えることができます。
#include <stdio.h>
enum _day {
DAY_MONDAY,
DAY_TUESDAY,
DAY_WEDNESDAY,
DAY_THURSDAY,
DAY_FRIDAY,
DAY_SATURDAY,
DAY_SUNDAY
};
void printDayOfWeek(enum _day day) {
switch(day) {
case DAY_MONDAY:
printf("月曜日です\n");
break;
case DAY_TUESDAY:
printf("火曜日です\n");
break;
case DAY_WEDNESDAY:
printf("水曜日です\n");
break;
case DAY_THURSDAY:
printf("木曜日です\n");
break;
case DAY_FRIDAY:
printf("金曜日です\n");
break;
case DAY_SATURDAY:
printf("土曜日です\n");
break;
case DAY_SUNDAY:
printf("日曜日です\n");
break;
default:
printf("day error\n");
break;
}
}
int main(void) {
enum _day day = DAY_FRIDAY;
printDayOfWeek(day);
printf("%d\n", DAY_FRIDAY);
return 0;
}
enum _day
と、型名を指定する際に毎回 enum
を記述する必要がある点が煩わしいですが、enum
があることで型名から列挙型であることが明白に分かるという良さもあります。
列挙子も結局は整数
また、列挙子に割り当てられているのは結局は整数ですので、通常の整数の定数同様に扱うことができます。
配列の添字として使用する
例えば下記のように配列の添字に指定することも可能です。
C言語では辞書や連想配列は扱えませんが、ちょっと辞書っぽい使い方になりますし、水曜日の温度が 20
度であることを記憶する処理というのが直感的に理解しやすくなると思います。
int temperatures[7];
temperatures[DAY_WEDNESDAY] = 20;
ただし、列挙子に負の整数を割り当てているとプログラムが上手く動作しなくなるので注意してください。
配列のサイズ指定に使用する
また、列挙子を変数宣言時に配列のサイズとして指定することも可能です。
特に配列のサイズであったりループの継続条件に列挙子を利用するような場合、列挙子の最後に個数や番兵を示すものを追加しておくと、ソースコードが記述しやすくなります。
下記は配列のサイズに列挙子を指定する例になります。
typedef enum {
DAY_MONDAY,
DAY_TUESDAY,
DAY_WEDNESDAY,
DAY_THURSDAY,
DAY_FRIDAY,
DAY_SATURDAY,
DAY_SUNDAY,
DAY_NUM
} DAY;
int temperatures[DAY_NUM];
上記の場合、DAY_NUM
には 7
が割り当てられますので、配列 temperatures
の要素数は 7
になり、添字としては DAY_MONDAY
〜 DAY_SUNDAY
の列挙子、もしくは、0
〜 6
の整数を指定することが可能です。
わざわざ DAY_NUM
を追加しなくても、同様のことが int temperatures[DAY_SUNDAY + 1];
でも実現できますが、もし後から DAY_SUNDAY
の後に何かしらの列挙子が追加された場合に意図したようなサイズで配列が作成されるとは限りません。
そういった変更を考慮すると、列挙子の個数を取得したいような場合、上記のように DAY_NUM
を追加しておき、列挙子を追加する際には DAY_NUM
の前側に追加するようにした方が無難だと思います。
ただし、列挙子に負の値を割り当てたり、飛び飛びの値を割り当てたりする場合は上手くサイズが取得できなくなるので注意してください。
例えば DAY_MONDAY
のみに手動で -50
を割り当てている場合、DAY_NUM
には -43
が割り当てられることになり、配列のサイズとして指定することができませんし、列挙子の個数としても正しくありません。
列挙子の個数を取得したいような場合は、最初の列挙子には 0
を割り当てておくのが無難だと思います。
ちなみに、列挙子の個数は sizeof(列挙型の型名)
では取得できないので注意してください。この場合、その 列挙型
の型のサイズが取得されることになります。
おそらく処理型依存ですが、私の場合は sizeof(DAY)
の結果は 4
になりました。実際の列挙子の個数は 8
個なので、”列挙子の個数が取得できていない” ことが確認できると思います。
ループの継続条件に使用する
また、下記のようにループの継続条件等に列挙子を指定することも可能です。
for (DAY day = DAY_MONDAY; day < DAY_NUM; day++) {
printDayOfWeek(day);
}
スポンサーリンク
列挙型の変数には列挙子以外も代入可能
列挙型の注意点 で解説しましたが、列挙型の変数には列挙子以外の値も代入できてしまうので注意してください。
また、先ほど示したループ文からも分かるとおり、列挙型の変数に対して四則演算やインクリメント(day++
)等も行うことが可能です。
列挙子の値はプログラム実行中は変更不可
また、列挙子は定数として扱われますので、プログラム実行中に変更する事はできません。
例えば下記のように列挙子の1つである DAY_MONDAY
に値を代入しようとしたとしても、コンパイル時にエラーが発生します。
DAY_MONDAY = 100;
列挙子の値が変更できるのは列挙型の定義時のみになります。
まとめ
このページでは、C言語における列挙型 enum
について解説しました!
列挙型とは複数の関連する定数を1つの集合として定義する型です。この型を定義する際に enum
を用います。
この列挙型を用いることでソースコードの可読性がグッと上がります。
特に、場合分けをするために無理やり数値に意味づけするような場合に列挙型が便利であり、列挙型を用いることでソースコードが読みやすくなります。
特にソースコードに数値をそのまま記述してしまうと、後からソースコードを読んだ場合や他の人がソースコードを読んだ場合に、その数値の意味が分かりにくくなってしまいます。数値をそのまま記述するのではなく、列挙型が利用できないかどうかを検討してみましょう!
オススメの参考書(PR)
C言語学習中だけど分からないことが多くて挫折しそう...という方には、下記の「スッキリわかるC言語入門」がオススメです!
まず学習を進める上で、参考書は2冊持っておくことをオススメします。この理由は下記の2つです。
- 参考書によって、解説の仕方は異なる
- 読み手によって、理解しやすい解説の仕方は異なる
ある人の説明聞いても理解できなかったけど、他の人からちょっと違った観点での説明を聞いて「あー、そういうことね!」って簡単に理解できた経験をお持ちの方も多いのではないでしょうか?
それと同じで、1冊の参考書を読んで理解できない事も、他の参考書とは異なる内容の解説を読むことで理解できる可能性があります。
なので、参考書は2冊持っておいた方が学習時に挫折しにくいというのが私の考えです。
特に上記の「スッキリわかるC言語入門」は、他の参考書とは違った切り口での解説が豊富で、他の参考書で理解できなかった内容に対して違った観点での解説を読むことができ、オススメです。題名の通り「なぜそうなるのか?」がスッキリ理解できるような解説内容にもなっており、C言語入門書としてもかなり分かりやすい参考書だと思います。
もちろんネット等でも色んな観点からの解説を読むことが出来ますので、分からない点は別の人・別の参考書の解説を読んで解決していきましょう!もちろん私のサイトも参考にしていただけると嬉しいです!
入門用のオススメ参考書は下記ページでも紹介していますので、こちらも是非参考にしていただければと思います。
https://daeudaeu.com/c_reference_book/