見出し画像

C言語教室 第36回 論理と列挙

さて、C言語の文法的な要素は、既に大部分の説明が済んだかなとは思っていますが、知っておく必要のありそうなものに「論理型」と「列挙型」が残っていました。これらはいずれも最初の「カーニハン&リッチー」な時代のC言語には無かったものではあるのですが、その後の仕様改訂でほかの言語に合わせて導入されたものです。

プログラミング言語C 第2版

※第2版は 1988年ANSI に準拠なので、少しだけ新しくなっています。


論理型

論理型はまたはのいずれかの値を持つ型です。元々のC言語で使われてきた論理値は整数で表現されていて、値が0であれば偽、そうでなければ真として扱われています。この扱いは NULL ポインタ(値が0のアドレス)と親和性が良く、NULLポインタが値として扱われれば偽になるわけです。

当時は論理型としての値を扱いたいときには、TRUEとFALSEをマクロとして定義して使うこともよくありました。またわざわざ typedef を使って int (稀に char) を bool と定義して使うこともありました。C99と呼ばれる1999年にC++の影響をC言語にフィードバックしたISO規格からは、論理型が導入されたのですが、この規格で規定されたのは _Bool型であって、これを typedef して bool 型が使えるようにするという手順になっています。このため bool という型を使うときには、<stdbool.h> をインクルードする必要があります。つまり追加されたのはアンダーライン付きの予約語だけで、言語自身にboolという名前の論理型が追加されたわけではありません。これは過去のソースに bool という型が既に溢れており、これを回避する手段を残す必要があった苦肉の策ではあったのでしょう。なお独自にBoolであったりBOOLという名前を使う処理系もあります(値もTrueとかね)。

bool

このヘッダを使うことで、bool 型は true または false の値を持つことができます。bool を int にキャストした時の値は、false が0であることだけを使ってよく、trueは「falseではない」ことしか仮定してはいけません。実際にはtrueは1になることが期待されるのですが、いろいろな理由で他の値が入ることもあります。ですからbool型の値を比較するときも、falseかfalseではないという比較が望ましく、trueと等号比較するのはお勧めできません(実際にコードの品質が劣化することすらある)。

ブーリアン型

なおC++では、このboolはちゃんと規定されていて値も必ずfalseが0でtrueが1であることが保証されています。


列挙型

列挙型というのはPASCALで初めて見たような気がするのですが、その型が持つ値をすべて書き並べた一覧で定義するものです。例えば信号機の色を定義するのであれば

<信号機の色> = { 赤、黄、青 }

という感じです。ですからこの型の変数に代入できるのは、本来であれば。ここで定義された「赤」「黄」「青」という値だけで、これらの値に対して出来るのは同じであるかの比較くらいで、足したり大小の判定はできません(C言語では出来てしまうのですが)。

この型はC89と呼ばれる1989年のISO規格で登場したのですが、先の例を使うと

enum signal_color {
  RED,
  AMBER,
  GREEN
};

と定義して、

enum signal_color status = GREEN;

のように変数を宣言して使います。

構造体の時と同じで毎回 enum を書きたくないので、typedef して使うこともよく行われます。

列挙型

enum の値を使って計算することはできないので、if や switch/case で使うくらいしかできないのですが、

switch(status) {
case RED:
  /* REDの処理 */
 break;
case AMBER:
  /* AMBERの処理 */
  break;
case GREEN:
  /* GREENの処理 */
  break;
}

といった使い方が多いです。

この enum、実際には値に整数の識別子が割り当てられ int にキャストすると値がバレます。実装が隠されていれば良いのですが、宣言の時に割り当てる識別子(という意味の整数の値)を定義することすらできます。

enum signal_color {
  RED = 1,
  AMBER,
  GREEN=0
};

ここで値を定義しておけば、その名前は整数にキャストしたときに、定義した値と等価になります。値の定義がない名前に対しては、他の名前の値と被らない値が割り当てられます。この時、複数の名前に同じ値を定義することもでき、その場合には同じ値を持つ名前を比較すれば同じという結果になります。

enum signal_color {
  RED = 1,
  AMBER = 2,
  YELLOW = 2,
  BLUE = 0,
  GREEN = 0
};

これで AMBER と YELLLOW、BLUE と GREEN は同じ扱いになるわけです。

C言語の enum で結構、困ることは、この名前を入出力に対して取り出す方法がなく、printfで表示できるのは、その値であって名前が使えないのです。そのため、enum を表示する関数を作ったりすることもあります。

#include <stdio.h>

enum signal_color { RED, AMBER, GREEN };

const char* signal_color_name(enum signal_color color) {
  switch(color) {
  case RED:
    return "red";
  case AMBER:
    return "amber";
  case GREEN:
    return "green";
  default:
    return "?";
  }
}

void main() {
  enum signal_color status = GREEN;
  printf("Signal Status:%s\n", signal_color_name(status));
}

これで

Signal Status:green

が出力できるようになります。

enum ができるまでは、値に名前を付けたいときはマクロを使っていたのですが、言語に取り込まれたので、デバッガでもその名前が使えるようなって、それだけは便利になったのですが、特別な構文がないので「次の」列挙型の値を取り出したり出来ませんし、式の中ではいつでも整数として扱われるので、実は大小比較もできてしまうなど、注意して使わないと謎の挙動を示すこともあります、整数なので負の値も取れるのですが、負の値を使うとちょっと不思議な動作になった記憶もあります。


課題

というところで、今回の課題を出します。この課題の後に前回の解答を載せますので、ここでまだ課題を解いていない方は、前回の課題に挑戦していただければと思います。

C言語教室 第35回 constの闇 と 第34回の解答

課題に挑戦したら、ぜひ見せていただければ幸いです。では今回は、

課題
列挙型を定義してから、列挙型の名前を文字列として表示する関数を書きなさい(複数の名前が同じ値を持つときは、どの名前を表示しても良いものとする)。

です。普通に考えれば今回の教室での説明で使ったようなswitch/caseを使うか、値と名前を持つ構造体を定義して、その配列を作って検索することになるとは思いますが、プリプロを使うという大技もあるかもしれません。


前回の課題の解答

前回の課題は以下の内容でした。

課題
関数の戻り値を const にした例を示し、その効果を考察しなさい。

https://note.com/kazushinakamura/n/n30de7c665504

実は関数の戻り値の扱いに関しては、引き数のときと似ていて、戻り値を変数に代入するときには「コピー」されるので、戻り値が const int であっても int に代入することは問題ありません。ですから const の力が発揮されるのは、const char * のように「指す先が」const であるときに限られます。

戻り値が const char * の場合は、その値を char * に代入することは、キャストしない限り出来ません(キャストはできてしまうのですよね)。ただ戻り値に const が付いているにはだいたいは理由があり、良くあるのは呼び出した関数の static 変数を指していることが多いです。ここで戻り値を書き換えてしまうと、その関数を呼び出しているすべての処理に影響を与える可能性が高いので、余程のことが無い限り、const で返しているポインタを書き換えてはいけません。

その17 constのあれこれ2

※この説明はC++に対するものなのですが、基本的にはC言語でも同じです(メソッドのconstとかは関係ありませんが)。


次回は名前と予約語について整理したいと思います。

ヘッダ画像は、いらすとや より
https://www.irasutoya.com/2013/06/blog-post_6.html

#C言語 #プログラミング講座 #論理型 #列挙型 #bool #enum #答え合わせ  

この記事が気に入ったらサポートをしてみませんか?