読者です 読者をやめる 読者になる 読者になる

kentsu.log

何かその時の興味でいろいろする人。最近はScala使ってる。アルゴリズムと自然言語処理、深層学習が大好き。

C言語 制御の流れ

3章 制御の流れ

ここでは今まで普通に使ってきた制御に関することをもっと掘り下げていく

3.1 ブロックと文

これは凄く簡単なことで、C言語では式に;(セミコロン)を置くことで文になる
なので宣言や代入などそれら単体で文として独立する
また中カッコ{}は、それらで囲まれる部分を一つのブロックとして扱い、単一の文と同等の意味を持たせる

関数で関数がもつ処理を{}で囲んだりif~elseやfor,whileなどでブロックを使うことがその典型例である

3.2 if~else

判定を行うときはif~else文を用いる
else文は省略可能であり、正式にはこう書く

if (条件){
処理1
}else{
処理2
}

{}を省略することもできるが、それは時に曖昧さをもつ
以下の例をあげると

if(0 <= number)
    if(b < a)
        max = a;
    else
        max = b;

これは人間には解釈できるがコンパイラにとっては内側のifと同列にelseがあるように解釈される
特に以下の様な時に有害である

int array[10];
int i;
memset(array,-1,sizeof(array));
array[0] = 1;

if(0 <= array[0])
    for(i = 1; i < sizeof(array); i++){
        if(0 <= array[i]){
             printf("...\n");
        }
    }
else
    printf("error");

これらの予期せぬ動作を防ぐ為には{}を用いてブロックを明示的に区別するのが得策といえる

3.3 else if

3.1ではif elseの様に
条件一つに対して、真か偽かとその判定に対する処理の多くて二つしか実装できなかったが
else ifを使うことで更に多くの条件判定を行える

if(条件1){
}else if(条件2){
}else{
}

上記に示す様にifでは条件1,else ifでは条件2、elseではそれ以外のときとなる
これは多分岐の条件判定を実装する一般的な方法で、最後のelseは省略できる
またこれら同列に並ぶif,else if,elseは上から順番に判定されどれかに当てはまった段階でその後の判定を全てスキップする

補足ではあるがif文に於て、elseはそれまで続く全ての条件に当てはまらない場合に実行されるのでエラー検出に用いることもできる

3.4 switch

これはif,else if,elseを用いた多分岐条件判断でよく使われる
以下の様な構文で用いられる

switch(式){
case 定数式:

default:

}

if文と同様にdefaultは省略可能で、caseは幾つでも定義できる
だが少し注意が必要である

#include<stdio.h>

int main(){
	int i;
	for(i = 1; i <= 10; i++){
		switch(i){
			case 1:
			case 2:
			case 3:
			case 4:
			case 5:
				printf("i <= 5\n");
				break;
			default:
				printf("over 5\n");

		}
	}
	return 0;
}

上記のプログラムではcaseの後の文でbreakが宣言されていないため
1~5までの条件判断で処理を一つにまとめている
つまりbreak文が無いことで、次のcase文に移っていく
これはifで書くと

if(i <= 5)

もしくは

if(i == 1 || i == 2 || i == 3 || i == 4 || i == 5)

と解釈できる(switchと同じ意味合いであるなら後者)

3.5 ループ(forとwhile)

これまで説明こそしなかったものの、ループは問題なく使ってきた

まずはwhile文から
whileは以下の様な構造をとる

while(式){

}

while文ではまず式が評価され式が0とならないなら文に移り、式を評価して0になるまで繰り返される

次にfor文
forは以下のような構造をとる

for(式1;式2;式3){
文1
}


そして以下の様に書けば、whileとforは同じ意味合いをもつ

for(i = 0; i < 100; i++){}

これをwhileで書くと

i = 0;
while(i < 100){
    i++;
}

これで同じ意味合いになる

またforにおいて、三つの要素は文法的に式であり、一般的には式1と式3が代入又は関数呼出で式2が関係式となる
この3つの要素はどれを省略してもいいが、セミコロンを省略することはできない。
式1、式3は省略された場合単に展開されないだけだが、式2の関係式が省略されると、それは永久に真であることを示す
つまり、以下の様にfor文を書くと無限ループになる

for(;;){
}

無限ループはwhileにもありwhileでは以下の様に書く

while(1){
}

また無限ループから脱出する際にはbreakかreturnを使うことになるだろう

ここのループの話ではreverse関数という文字列sを逆順にする関数を考えながらコンマ演算子について触れておく

コンマ演算子といえば、変数宣言や初期化やその他諸々の様々な場合で使用してきたものとは違い
for文に適応できて以下の様に書ける

void reverse(char s[]){
    int c,i,j;
    for(i = 0, j = strlen(s)-1; i < j; i++,j--){
         c = s[i];
         s[i] = s[j];
         s[j] = c;
    }
}

ただこのコンマ演算子はあまり乱用するべきではない
使用してもいいのは、reverse関数のようにi,jが強く関連しているときなどである

3.6 ループ(do~while)

次に三番目のループ構文としてdo~while文を紹介する
これは基本的にはwhile文の様に式を評価してループするしくみなのだが、式に真か偽か関係なくdoのブロック内に含まれる処理を必ず一回は実行する
以下のような構文である

do{

}while(式);

もし、一回目の実行の後に式を評価し真であるならまた文を実行する
経験上for,while文ほど多用はしないが、たまに使いどころがくるので知っておくべき

3.7 breakとcontinue

breakとcontinueは主にループ内で多用し、breakにおいてはswitch文でも使用する
これらはループから早く抜ける為にあり、breakを使えばそれが含まれる最も内側のループから抜けることができるし
continueを使えば適切なタイミングでループをスキップし、次のループに移れる

3.8 gotoとラベル

C言語にはgotoという、ネストになったループから一発で抜けてしまうようなものがある
何度もifとbreakを書かなくていいという簡潔なメリットがあるが形式的にはgotoは不可欠なものでない
実際にそれらを使用しなくてもプログラムは書けるし、むしろgotoを使用するような構造のプログラムを書いてしまうことの方がまずいことである

gotoでは指定したラベルまで一気に飛べるため、以下のように使う

for(i = 0; i < n; i++){
    for(j = 0; j < m; j++){
        if(a[i] == b[j]){
             goto found;
        }
    }
}

found://ラベル

また一度に多くの処理を飛ばしてしまうことがありgotoを使ったプログラムはgotoを使わないプログラムに比べて保守しにくく
バグの原因となるので、あまり使うべきではない