文字列

C では文字列は、文字の配列だ。最もよく使われる1次元配列は文字列だそうだ。文字列は1文字ずつ配列の要素に格納され、最後にヌル文字(0)がつく。
文字列に関する関数をいくつか見てみよう。

gets()

gets() はキーボードから文字列を読み取る。ユーザーが文字列に続いて Enter キーを押すと、gats() は改行文字をヌル文字に置き換えて返す。

#include

int main(void)
{
    char str[80];
    int i;

    printf("Input string (less than 80).: ");
    gets(str);
    for (i = 0; str[i]; i++) {
        printf("%c", str[i]);
    }
    printf("\n");

    return 0;
}
takatoh@nightschool $ gcc sample_5_2a.c -o sample_5_2a
sample_5_2a.c: In function ‘main’:
sample_5_2a.c:10:5: warning: ‘gets’ is deprecated (declared at /usr/include/stdio.h:638) [-Wdeprecated-declarations]
     gets(str);
     ^
/tmp/ccMGfyBh.o: 関数 `main' 内:
sample_5_2a.c:(.text+0x2e): 警告: the `gets' function is dangerous and should not be used.

おっと、何やら警告が出た。gets は危険だから使うべきじゃない、と。まあ、そのとおりらしい。本には gets は文字の長さをチェックしないので受け入れる配列におさまるかどうかプログラマが注意しなければいけない、と書いてある。
コンパイル自体はできてるようだし、今回はサンプルなのでこのまま使うことにしよう。

takatoh@nightschool $ ./sample_5_2a
Input string (less than 80).: Hello, this is Takatoh!
Hello, this is Takatoh!

このプログラムでは、文字列終端のヌル文字が 0 (偽)であることを利用している。

strcpy()

文字列ソースをターゲットにコピーする。string.h が必要。

strcpy(ターゲット, ソース);

使い方はこんな感じ。

char str[80];

strcpy(str, "Hello, world.");
printf("%s\n", str);

strcat()

文字列ターゲットにソースを連結する。string.h が必要。

strcar(ターゲット, ソース);

次のようにすると、「hello, threr」と表示する。

char str[80];

strcat(str, "hello,");
strcat(str, " there");

printf(str);

strcmp()

2つの文字列を比較する。string.h が必要。

strcmp(文字列1, 文字列2);

文字列はコード順に大きさを比較(コードが小さい方の文字列が小さいと判定される)され、文字列1と文字列2が同じ大きさなら 0 を返す。文字列1のほうが小さければ 0 より小さい値を、大きければ 0 より大きい値を返す。

strlen()

文字列の長さを返す。ヌル文字は含まれない。string.h が必要。

strlen(文字列);

練習問題

次のプログラムは、文字列を読み取り、それを逆の順序で表示する。

#include
#include

int main(void)
{
    char str[80];
    int i;

    printf("Input string (less than 80): ");
    gets(str);

    i = strlen(str);

    for ( ; i; i--) {
        printf("%c", str[i - 1]);
    }
    printf("\n");

    return 0;
}
takatoh@nightschool $ ./practice_5_2
Input string (less than 80): hello, world.
.dlrow ,olleh

配列

(1次元の)配列を宣言する一般的な形式は次の通り。

型 変数名[サイズ];
  • 配列の要素はすべて同じ型
  • 要素にアクセスするには変数名に大カッコ([])で囲んだ添字をつけてアクセスする
  • 添字は 0 から始まる
  • 配列は連続したメモリ領域に格納される
  • 配列をまるごと代入(コピー)することは出来ない。要素を1つずつ代入(コピー)する

要素を1つずつコピーしなきゃいけないのな面倒だな。

次のプログラムは、1 から 10 までの平方値を、配列 sqrs に格納してから表示する。

#include

int main(void)
{
    int sqrs[10];
    int i;

    for (i = 1; i < 11; i++) {
        sqrs[i - 1] = i * i;
    }
    for (i = 0; i < 10; i++) {
        printf("%d ", sqrs[i]);
    }
    printf("\n");

    return 0;
}
takatoh@nightschool $ ./sample_5_1
1 4 9 16 25 36 49 64 81 100

もうひとつ、大事なこと。
C では配列の添字の範囲をチェックしない。例えばサイズ 10(添字は 0〜9)の配列の11番目の要素(添字10)にもアクセスできてしまう。もちろん正しい動作にはならないので、場合によってはプログラムがクラッシュするかもしれない。
ちょっと試してみよう。

#include

int main(void)
{
    int ary[10];
    int i;

    for (i = 1; i < 11; i++) {
        ary[i - 1] = i;
    }

    printf("%d\n", ary[10]); /* out of range */

    return 0;
}
takatoh@nightschool $ gcc sample_5_1a.c -o sample_5_1a
takatoh@nightschool $ ./sample_5_1a
0

何事もないかのようにコンパイルされたけど、実行すると、代入したはずのない ary[10] の出力として、0 と表示された。
何度か試してみたけど、毎回 0 と表示される。これはたまたまかもしれないし、gcc がそうしているのかもしれない。どちらにせよ、配列の要素にアクセスするときには添字の範囲に注意が必要だ。

型キャスト

値の型を明示的に変換する方法を型キャスト(type cast)という。型キャストの一般亭な形式は次の通り。

(型) 値

次の例では、浮動小数点数を整数として表示するために int に型キャストしている。

#include

int main(void)
{
    float f;

    f = 100.5;

    printf("%d\n", (int) f);

    return 0;
}
takatoh@nightschool $ ./sample_4_7
100

代入文での型変換

代入分の左辺と右辺の型が異なる場合、右辺の型が左辺の型に変換される。もし、左辺が右辺よりも大きな型であれば問題はない。その逆の場合は問題ありで、例えば次のプログラムは、「1000」と表示せずに「-24」と表示する。

#include

int main(void)
{
    char ch;
    int i;

    i = 1000;
    ch = i;

    printf("%d\n", ch);

    return 0;
}
takatoh@nightschool $ ./sample_4_6a
-24

なぜなら、int の i を char の ch に代入する際、i の下位 8 ビットだけがコピーされるから。C ではこのような代入での型変換はエラーにならない。代入文で、大きな整数から小さな整数(char を含む)に変換されるとき、上位ビットが失われるのが基本的な規則。

また、大きな浮動小数点数から小さな浮動小数点数への変換では精度が落ちるし、浮動小数点数から整数への変換では小数部が失われる。
値が変換後の型におさまらない場合は、結果は意味のない値になってしまう。

次の2点は重要。

  • int から float、float から double への変換は、値の表現を変えるだけで精度を上げるものでなないこと。
  • コンパイラによって、char を符号付きとして扱うものと符号なしで扱うものがある。このため 127 よりも大きな値を代入した時の結果は処理系に依存する。これが問題になる場合は明示的に signed あるいは unsigned と宣言すべき。

次のプログラムでは、浮動小数点数を整数に代入することで小数部が失われることがわかる。

#include

int main(void)
{
    int i;
    float f;

    f = 1234.0098;
    i = f;
    printf("%f %d\n", f, i);

    return 0;
}
takatoh@nightschool $ ./sample_4_6b
1234.009766 1234

む、浮動小数点数の精度がおかしいな。とはいえ、整数に代入することで、小数部が失われることは分かった。