C言語ワークショップ 第4回  文

●はじめに

 今までは変数やポインタなど、C言語の細かい部分をはなしてきましたが、ここからが実際のプログラムの中身の構成にあたる「文」について学んでいきたいと思います。

●キーワード
式にセミコロン( ; )がついたものを「文」と呼ぶ
セミコロン( ; )は文のターミネータ(終端記号)である

●単文と複文

 単文とは

  a=0;

 のように式などにセミコロンが付いたものをいいます。
 これはCにおける文の最小単位です。

  a=1;b=2;

 という文があれはa=1;のまでが文、そしてそこから次のセミコロンまでが文ということになります。

 複文とは大カッコ{ }で囲まれた単文の集まりのこといいます。たとえば

  {
   a=1;
   b=2;
  }

 というのが複文です。
 そして複文はあたかも一つの文のように扱うことができるため。単文が使われてもよい場所であれば、どこで使ってもかまいません。
 大カッコ自体は文ではないため、大カッコの後にセミコロンは付きません。

 このように複文は「カッコので囲まれた部分は一つの文とみなされる」ということになり、これは条件文などで、特に重要になります。

●真と偽

 C言語で条件文を学ぶ前に必ず学ぶことが必要なものに「真」と「偽」という概念があります。
 これはある条件テストが行われた場合にその結果が返ってくるわけですが、その結果がCではなにを意味するか知る必要があります。
 そして、Cにおいて、その結果は次のようになり

  真 = TRUE = 0でない値
  偽 = FALSE = 0の値

 の2つになります。
 真とは正しいと考えられます。たとえば「ある値とある値が等しいかどうか」という条件テストが行われた場合、もしその結果が正しい(等しい)ならば真となるわけです。
 逆にそのテストの結果が誤っている(等しくない)ならば偽となるわけです。

 そして、それら結果が真であるなら0でない値が返ってきて、偽ならば0の値が返る訳です
 ただし、「ある値とある値が等しくないかどうか」という条件テストが行われた場合は当然答えは逆になります。正しい(等しくない)ならば真、誤っている(等しい)ならば偽となります。

 実際にはCにおいて、演算が真になった場合の非ゼロで返す値は1となります。

●比較演算子と等価演算子

 条件文を学ぶのにもう一つ理解する必要があるものがあります。それが比較演算子と等価演算子です。

 比較演算子とはその名の通り、比較を行う「演算子」です。
 演算子と言う言葉を強調したのには意味があります。ただ比較するのではなく、比較を演算するわけですから当然「値」が返ってくるということを知っておかないといけません。
 まず、等価演算子から見てみます。

   a == b   aとbは等しい
   a != b   aとbは等しくない

 等価演算子はこのような形で演算されます。そしてその結果の値は真(0でない値)であるか、偽(0の値)が返されます。
 このように演算が行われたわけですから当然値が返されるわけですので

  kekka = (a == b);

 のように(a == b)で等価演算された結果が変数kekkaに代入されます。
 このように、C言語では「等価演算」が行われるため、普通の演算式の中で値を比較しても、その結果が値として得られるということが重要です。
 要するに(a == b)という部分は比較して結果が0であれば0という数字に置き換わると見ることができるわけです。

 つぎに、比較演算子から見てみます。

   a < b   aはbより小さい
   a > b   aはbより大きい
   a <= b   aはbより小さいか、等しい
   a >= b   aはbより大きいか、等しい

 これらも結果は等価演算子と同様で、値が得られる演算子です。全く同じように
  kekka = (a <= b);
 のような演算が行えるわけです。

●制御文

 処理をある条件に従って変えるものを制御文と呼びます。
 制御文には条件文、繰り返し文など、いくつかの制御文があります。
 これからはそれぞれを、解説していきます。

●条件文

 条件文は演算の結果が真であるか偽であるかにより、処理の流れを変化できるものです。
 

1.if文

  書式:
    if(式)  文

  これはもし式の評価(演算が返す結果)が真(0でない)ならば、文が実行されます
  もし偽(0)ならば実行が行われません。

  たとえば最も簡単な例では

  if(0)  a=0;

  とあると式の評価が0(偽)であるためa=0;の文は実行されません。

  if(1)   a=0; や
  if(-1)   a=0;

  ですと、式の評価は0でない(真)ためa=0;の文が実行されます。

  ここでは真、偽の値を直接、式の部分へ入れていますので、その値の真、偽に従ってこのような動作になったわけです。ここに

  if(a == b)  a=0;

 となれば等価演算a == bが真であるか、偽であるかにより動作するわけです。

 ここで注意が必要です。よく誤る書き方で等価演算子の表記を

  if(a = b)  a=0;

 というように書いてしまうことがあります。しかしこう表記したとしてもコンパイルはエラーになりません。なぜなら、このような書き方でも文法的には間違ってはいないからです。

 上の場合a = bはaにbを代入するという代入「演算子」です。代入演算を行った「式」ですからif文の書式で()の中は式を記述することになっていますから、文法的に間違っているわけではないのです。
 ではもしこのようにしたらどう動作するでしょうか。
 a=bという演算では代入という動作をし、a=bの式自体はその演算結果(代入した値)を返しますので、実際には

 c=(a=b);

 という式を書けばcでa=bの結果を得ることが出来ます。そして結果はa=bで代入を行った値自体がその演算結果となります。

 これと似た例で

 c=a=b=0;

 という形で、さっきの式のカッコが無くなったものですが、こちらを見れば変数が同じ値になることからも、よくわかると思います。演算子については後ほど詳しく書いていきます。

 このように、制御文で扱われる「式」の部分は式であれば何でもいいわけですから、ちょっとしたミスですが、コンパイルは正常にできるので、思わぬ誤動作につながってしまうことになります。
 逆にこのように式であればいいということが、C言語における柔軟性といえると思います。

 ここで、次のような例をみましょう。

  if(a == b)  a=0;  b=10;

 この場合、このa == bが偽ならば文は実行されないことになるわけですが、上の場合だとb=10;は実行されることに注意が必要です。
 改めてif文の書式を見てみると
  if(式)  文
 のように、ifによって制御される部分は「文」の部分です。はじめにお話ししましたが「文」とはセミコロンまでの部分が文です。ですから当然a=0;がifによって制御される文でありb=10;はifに関係のない文となるわけです。

  if(a == b)  a=0;
  b=10;

 このように書けば、そのことがわかりやすいと思います。
 では、a=0;b=10;の両方をif文で処理したい場合どうしたらいいか
 それははじめの単文、複文のところでお話ししたとおり、{}で囲った文の集まりはこのカッコによって一つの文として扱うことができることを使えばいいわけです。
 ですから

  if(a == b){  a=0;  b=10;}

 とすることで大カッコに囲まれた部分は1つの文になり、a=0;b=10;の両方がif文で処理できるわけです。

2.else文

 else文は、常にif文と対にして使われます。これはまだ対になっていない最も近いif文に対してついを形成します。そして、elseはifより後になる決まりになっています。
 そしてelse文は対になっているifの条件が偽(0)であった場合にelse文が実行されます。 また真(0でない)で場合、else文は実行されません。

  if(式)  文1  else  文2

 また、
 書式:
    if(式)     文1
    else if(式)  文2
    else if(式)  文3
     ・
     ・
     ・
    else   文

 上の書式のようにelseはifその次にifをつなげることで、文の中から1つの文を選択して実行するような動作にすることもできます。

 上の書式だと
  はじめのifで真ならば文1実行し、それ以降最後までスキップされます。
  偽ならばelseを実行し、2行目のifが実行されます。
  同様に2行目のifが偽ならば3行目のelseが実行され、どの式も真でなかった
  場合には最後のelse文が実行されることになります。

3.switch文

 条件文にはもう一つif文に似た文があります。それがswitch文です。
 書式:
    switch(式){
      case const1:
            
      case const2:
        
       ・
       ・
       ・
      case const3:

      default:

    }
  文









 switch文は式の値を基にして、実行していく文を選択する時に利用します。
 式が評価されてその結果とconst(定数)が示すものと比較します。constの部分は必ず定数で記述しないといけません。たとえば

    switch(a){
      case 0:
            
      case 1:
        
      case 2:

      default:

    }
  文1
文2
文3
文4
文5
文6
文7
文8

 のように定数をcaseの後につけます。
 if文では一つの文を実行する決まりですが、switch文ではcaseの後には複数の文を並べることができます。文1、文2・・・の一つ一つが単文もしくは複文です。
 またswitch文は全体が{ }で囲まれていますがswitch文全体が一つの文であることに注意が必要で、この全体のカッコを省略することはできません。

 if文では一つの文の実行をするかしないかを条件式によって制御しました。しかしswitch文は条件によって、実行する文の「選択」を行います。選択とはcaseで示される定数を選択し、そこから実行することです。
 上の場合でもし変数aが0であった場合、case 0が選択されますが、実行される文は文1〜文8までになります。aが1であった場合、今度はcase 1が選択されますから、実行される文は文3〜文8までになります。そして、どのcaseにも当てはまらなければ、defaultの文7、文8が実行されます。
 なぜこのように、case0であってもすべてが実行されるのかというと

    switch(a){
      case 0:
      case 1:
      case 2:
      case 3:
      case 4:

      default:

    }




  文1
文2
文3
文4

 のように、aが0〜4までの値であった場合は文1〜文4を選択させ、0〜4に当てはまらなかった場合は文3、文4を選択させるといったような条件が作れるからです。

 さて今度はaが0〜4までの値であった場合に、文1〜文2のみを選択させるには次のようにします。
    switch(a){
      case 0:
      case 1:
      case 2:
      case 3:
      case 4:


      default:

    }




  文1
文2
break;
文3
文4

 default:の前にでてきたbreak;はbreak文と呼ばれる文です。この文がでてくるとswitch文の終わりまでの文実行せずにswitch文を終了します。

●繰り返し文

1.while文

  書式:
    while(式)  文

 while文は式が真(0でない)である間、文の実行を繰り返します。書式自体はif文と同じ構造です。式の結果が偽(0)になったとき、while文は終了します。
 if文と同様、「文」を繰り返しますから、繰り返すのは一つの単文もしくは複文です。

  while(1)  a=0;

 と表記した場合、この文(a=0;)は永久に繰り返される、永久ループになります。

2.do-while文

  書式:
    do  文  while(式);

 do-while文は条件式により文を実行し続けるかどうかを決定する場合に使われます。while文と同様で真(0でない)である間、文の実行を繰り返し、式の結果が偽(0)になったときに終了します。
 while文との違いは先に文が実行され、その後に式の評価が行われることです。文の実行後の値を式で評価して繰り返すかどうか決定する場合などに用いられます。

  do a=a+1; while(a!=100);

 do-while文の場合、whileのあとに ; のターミネータあることに注意してください。

3.for文

  書式:
    for(式1 ; 式2 ; 式3)  文

 for文は一般的な繰り返し文です。通常はある決まった回数を繰り返す制御を行う場合に使われます。条件式ですが、式1〜式3の順で評価していきます。
 まず、式1は最初の1度だけ評価されるようになっています。通常は繰り返し回数をカウントするカウンタ変数を初期化するために使われます。
 次に式2を評価します。これは条件式です。式2の結果が真(0でない)ならば文の実行を実行し、式の結果が偽(0)になったときに終了します。
 もし式2が真であれば、式3が実行されます。式3はカウンタの値を変化させる式で、この式に従った演算が行われます。

    for(c=0 ; c!=100 ; c++)  b=b+1;

 この場合c(カウンタ用の変数)が100になるまで式(b=b+1;)が繰り返されます。
 このfor文は3つの式のどれでも省略しても構いません。たとえば極端に

    for( ; ; )  b=b+1;

 といった文も正しいfor文です。この場合条件式(2式)が存在しないため、このfor文は永久ループになります。

●break文

 break文はswitch文でも登場しましたが、制御文がネストしているような構造({ }で囲まれている構造)の場合、そのネストされている制御文を終了させることができる文です。  たとえば

    while(1){
          文
          if(式) break;
    }

 の場合、文とif文が無限に実行されますが、もし、if文の式が真であった場合、break;文が実行されるため、break文自身が囲まれている制御文(この場合はwhile文)を抜け出すことができます。
 これは、switch文、while文だけでなくfor文やdo-while文でも同様です。

●continue文

 continue文は、for文、while文、do-while文でつかわれます。continue文は実行しているループをスキップして繰り返し文の先頭から再度実行する文です。ループを中断するという点でbreak文と似ていますが、異なるのはループ自体を終了しないことです。
 たとえば

    while(1){
          文1
          if(式1) continue;
          if(式2) break;
          文2
    }

 の場合、式1が真となれば文2を実行せず、実行をwhile文の先頭に戻します。式2が真となれば、このループは終了します。
 このcontinue文はある条件になった場合に、処理を中断して先頭に戻したい場合に利用します。

●gotoとラベル

  書式:
    goto ラベル;
       ・
       ・
       ・
    ラベル: 文

 goto文はラベル(識別子)で示す文へ無条件に制御を移すのに使われる文です。制御を移す文の先頭にはコロン(:)の付いたラベルを記述します。

   goto end;
   b=0;
   end: a=0;

 この例の場合goto文が示すendに対して制御が移るためb=0;の部分は実行されません。
 一般的にC言語において、goto文を使うことはありません。この文は規格上用意されていますが、他の制御文のみですべての制御は可能であるため、使うことはまず無いはずです。私自身もgoto文だけは一度も使ったことがありませんが・・・・。
 C言語の特性や制御構造からみても、このgoto文はプログラムを複雑かつ見にくいものにしてしまうため、使うことは逆にタブーと考えてもいいでしょう。

●null文

 書式:
     ;

 null文は空文ともいいます。たいしたものではありませんが、なにも無い文です。ただ;が付いたものです。これは実行すべき文が無い場合に使われます。

 たとえば

    while(式1) ;

 のように式1を評価するだけにループさせたい場合はこの空文を使用します。

●ブロック

 複文でも使われる { } で囲まれた部分をブロックとも呼びます。これは単文の集まりを複文と見なし、一つの文にするだけでなく、ブロックで囲むことでその中を一つの範囲として扱うことができます。
 たとえば

 {
  int a,b,c;
  a=b=c=0;
  文1
    {
      int d,a;
      文2
    }
  文3
 }

 といった場合、ネストされたブロック内ではそのブロックの内で有効になるという決まりがあります。
 この場合文1、文3は変数a,b,cを参照できますが、dを参照する事はできません。また文2は変数a,b,c,dのすべてを参照できますが、文2を含むブロック内で変数aを重複して宣言しているため、この場合ブロック外の変数aは参照できなくなります。