50章 シフト演算子等

http://www.geocities.jp/ky_webid/c/050.html

また新しい演算子が出てきました。

これはビットを扱うための演算子といっても過言ではないでしょう。

int main(void){
    unsigned int num = 100;

    num = num << 2;
    printf( "%d\n", num );

    return 0;
}
$ main
400

100を2ビット左シフトすると400になるとのことです。

何故400になるのかちょっと順を追って確認してみます。

100は2進数で「00000000 01100100」となります

これを2ビット分左シフトすると「00000001 10010000」となり、これは10進数で400となります。

またシフトは2の累乗と凄く相性がいいです。

実はシフト演算は、乗算や除算の代わりになります。1ビット左にシフトすると、元の数の2倍の値になり、右にシフトすると元の数の2分の1になります。より一般化すると、nビット左シフトすると、2の n乗倍されます。だから上のプログラム例では、2ビットの左シフトで、2の2乗倍、つまり4倍されるという訳です。 シフト演算は、普通の乗算や除算よりも高速です。そのため、2の倍数の乗算及び除算は、シフト演算に置き換えた方が高速に動作します。実際には、コンパイラが自動的に最適化してくれることがほとんどなので、ソースが分かりにくくなるようなら、無理にシフト演算に置き換える必要はないと思います。

素晴らしいですね。

問題1

キーボードから入力された正数値に最も近い最大の、2のべき乗値を、画面に表示するプログラムを作って下さい。例えば、入力された正数が100であれば、64が表示されます(128は2のべき乗だが、100よりも大きいため、それより1つ小さい64となる)。なお、シフト演算を使って下さい。

シフト演算子を用いてやってみます。

int main (int argc,char *argv[]) {
    int num;
    int pow = 1;
    
    scanf("%d",&num);
    
    while(1){
        if ( num < pow ) {
            pow >>= 1;
            break;
        }
        pow <<= 1;
    }
    
    printf("%d",pow);
    return 0;
}
$ main
100
64

$ main
1000
512

$ main
2000
1024

$ main
0
0

うまく表示できてます。他にどんな実装があるのでしょうか。気になりますね。

答え合わせ

forでやってますね。

そうそうunsignedにするのを忘れてました。これをしないと符号ビットの問題が出るので今度から気をつけることにします。

問題2

次のマクロ(MACRO)は、何をしているのでしょう。

#define MACRO(dir) # dir "\\%s"

int main(void){
    char filename[256];
    
    puts( "ファイル名を入力して下さい" );
    fgets( filename, 255, stdin );
    
    printf( MACRO(test), filename );
    printf( "を開きます\n" );
    
    /* 以下、省略 */

    return 0;
}

何をしてるんでしょうか。

#演算子はマクロの引数を文字列に展開する演算子なので

MACROに渡されたtest変数を文字とみなして"\\%s"という文字列を結合していますね。

ということは

printf( "test\\%s", filename );

と展開されることになるんじゃないでしょうか。

答え合わせ

あってました。

問題3

入力された文字列の、先頭と末尾の文字を画面に表示し、その次は先頭から2文字目と末尾から2文字目の文字を、さらにその次は先頭から3文字目と末尾から3文字目というように表示していくプログラムを作って下さい。

これは今までに無く難しそうな問題ですね。やり応えがありますね。イッツチャレンジ!

int main (int argc,char *argv[]) {
    char str[81];
    size_t len;
    int i=0;
    
    fgets(str,sizeof(str),stdin);
    
    len = strlen(str);
    for(i=0;i<=len-1-i;++i) {
        if ( i == len-1-i ) {
            printf("%c",str[i]);
        }
        else {
            printf("%c%c",str[i],str[len-1-i]);
        }
    }
    
    return 0;
}
$ main
abcdef
a
bfced

$ main
abcdefg
a
bgcfde

うーん・・・・。一応出来たには出来たんだけど、今回の章で習ったマクロの演算子やシフト演算子を使ってないので多分間違ってるんだと思う。

答え合わせ

カンマ演算子を使ってやるわけですか。理解しました。

しかしひとつ気になる記述がありました。

最後の文字が入っている位置は、strlen関数で受け取った値-2の位置です。-1の位置には'\0'が入っています。今回のプログラムの目的からいって、'\0'を出力する必要はないので、ここを省く必要があります。

strlenは\0までの文字列のカウントを行うはずなので-1の位置は最後の文字が入ってるはずだと思うのですが・・・。

おそらくfgetsで受け取った値には改行コードが含まれるので、\0ではなく\nを出力する必要はないと言いたかったのかもしれませんね。