情報基礎演習II − 第6回


1.コンソール入出力


printf

既に printf については簡単に説明していますが、 このライブラリ関数は非常に機能が豊富で便利な関数なので、 ここで今一度整理をしたいと思います。

printf は引数に与えられたデータを、 書式文字列に従って文字列に「変換」し、 コンソール(画面)上に出力します。

整数や実数はコンピュータの内部では二進数であり、 これをそのまま画面に表示することはできません。 画面上に表示される 0123... などの数字は文字なので、 データを一旦文字に直してから出力する必要があります。 printf はこのような変換を行います。
printf(書式文字列, 引数, 引数, ...)
書式文字列の一般形は次のようになります。 [ .. ] は省略可能であることを示します。
%[フラグ][フィールド幅][.精度][h|l]型
フラグ+値が正のときも符号 (+) を書く
-左寄せで書く
#代替形式(このフラグの効果は型によって異なる)
フィールド幅整数欄の幅(文字数)、 0 で始まっていれば空白の部分を 0 で埋める
精度整数整数のときは最小限表示する桁数、 実数のときは小数点以下の桁数
h | lh短精度整数 (short) のとき
l長精度整数 (long) あるいは倍精度実数 (double) のとき
c文字
d符号付き十進整数
u符号なし十進整数
o八進整数
x十六進整数(a〜f 小文字)
X十六進整数(A〜F 小文字)
f実数
e実数(指数表現)
g実数(e か f のどちらかコンパクトに表示できる方)
s文字列
次の表は変換指定子の書式の例と、 それによる出力を示します。
なお printf の戻り値は、出力した文字列の文字数です。 文字列への変換に失敗した場合は、これが負になります。

■課題21■


scanf

コンソール(キーボード)から入力された文字列を、 プログラム中の変数に格納するには、 scanf を使うと便利です。
scanf は使うべきではないという議論もありますが、 標準的な関数の一つでもありますし、 fgets 等を使う方法はこの講義の説明に使うには少し煩雑なので、 ここでは scanf を用います。 scanf を使わない方法は scanf を使わないコンソール入力 を参照してください。なお、gets() は呪われているので触れてはなりません。
キーボードから入力されるのは文字なので、 例えば 10 という入力を数値の 10 として変数に格納するには、 文字を二進数に変換する必要があります。 scanf はこのような変換を行います。 また入力された文字列をどのようなデータ型に変換するのかは、 printf に似た書式文字列を使って指定します。

scanf(書式文字列, 引数, 引数, ...)
scanf は入力したデータを変換し、 引数に指定された「場所」に格納します。 つまり scanf に与える引数は変数の内容ではなく、 入力されたデータを格納すべき「場所」である必要があります。 この場所を示すデータのことを「ポインタ」と言い、 変数のポインタを得るために、& という演算子を用います。
int x;
scanf("%d", &x);
上の例では、キーボードをタイプした文字を十進数として、 int 型の変数 x に格納します。 %d は入力文字列を十進数として、 int 型に変換する変換指定子です。 この他の変換指定子も、 printf と大体同じです。
%[フラグ][フィールド幅][h|l]型
フラグ*データを変数に格納しない(読み飛ばし)
フィールド幅整数欄の幅(文字数)
h | lh短精度整数 (short) に変換
l長精度整数 (long) あるいは倍精度実数 (double) に変換
c文字に変換
d符号付き十進整数に変換
u符号なし十進整数に変換
o八進整数に変換
x十六進整数(a〜f 小文字)に変換
X十六進整数(A〜F 小文字)に変換
f実数 (float) に変換
s文字列として格納
書式指定子が連続しているか、 あるいは空白で区切られて複数指定されている場合は、 入力文字列中の個々のデータは 空白文字(空白、タブ、改行)で区切られている必要があります。
int x;
float y;
double z;
scanf("%d %f %lf", &x, &y, &z);  /* "%d%f%lf" でも同じ */
上の例に対しては、次のような文字列を入力します。
10 20.0 1e49
書式文字列に変換指定子以外の文字を含んでいるときは、 それが入力文字列に含まれている必要があります (その文字が現われるまで scanf が止まってしまいます)。 また書式文字列中の '\n' は入力の最後を意味します (入力の最後まで読み飛ばします)。
int x;
float y;
double z;
scanf("%d,%f,%lf\n", &x, &y, &z);
上の例はで変換指定子がコンマで区切られていますから、 入力文字列もコンマで区切られている必要があります。 また '\n' がありますから、 最後に入力の終了(Ctrl-D, ^D)をタイプする必要があります。
10,20.0,1e49
^D
書式文字列で想定していない文字を入力文字列に与えると、 scanf は止まってしまいます。
#include <stdio.h>

int main(void)
{
    int x = 1;

    while (x != 0) {
        scanf("%d", &x);
    }
    return 0;
}
上のプログラムは整数値の 0 が入力されたら終了しますが、 一度でもアルファベットなど数字(と空白文字)以外のものを入力すると、 scanf がその文字から先を読み込まなくなり、 プログラムが終了しなくなってしまいます。

scanf が入力文字列の変換に成功したかどうかを確かめるには、 scanf の戻り値が書式文字列に含まれる 変換指定子の数と一致しているかどうかを調べます。 上の例では、scanf の行を下のように変更することで、 とりあえず無限ループは避けることができます。

        if (scanf("%d", &x) != 1) break;
変換指定子にフィールド幅が指定されているとき、 入力文字列に含まれるデータはそれに従って切り出されます。
int x;
float y;
double z;
scanf("%2d%3f%4lf", &x, &y, &z);
これに次のような入力を加えます。
1234567890
x = 12, y = 345.0, z = 6789.0 というように格納されます。

■課題22■

入力された文字列を数値に変換せず、 そのまま文字列として格納したいときは、 文字列を格納する配列変数の変数名に & を付ける必要はありません。 これは配列変数の変数名自体が、ポインタになっているからです。

char str[21];
scanf("%20s", str);
変換指定子 %<欄の幅>s の欄の幅には、 必ず格納する配列変数のサイズより 1小さいフィールドの幅を指定してください。 これは、scanf が入力された文字列の最後に、 自動的に '\0'(文字列の終端)を追加するからです。

%s のようにフィールドの幅を省略することもできますが、 この場合文字列を格納する配列より長い入力文字があると、 プログラムの異常動作を引き起こします。

文字列は空白文字(空白、タブ、改行)で区切って変数に格納されます。 上の次のような入力を与えた場合、

Hello! How are you?
変数 str には Hello! だけが格納されます。

■課題23■


2.変数の初期化


変数の内容を、宣言した時点設定することができます(初期化)。
int sum = 0;            /* 合計を求める変数、初期値は0 */
char alpha = ' ';       /* アルファベット、初期値は空白 */
配列の場合は、個々の要素の初期値をコンマで区切って { } でくくります。
int data[3] = { 1, 2, 3 };
data[0] に1、data[1] に2、data[2] に3が入ります。 初期値の数が配列の要素数と同じときは、配列の要素数を省略できます。
char name[] = { 'K', 'e', 'n', '\0' };
文字型の配列を文字列で初期化するときは、次のように書くこともできます。
char name[] = "Ken";
通常の代入文ではこの記法を用いることはできませんが、 初期化の場合は可能です。 プログラム中で配列変数に文字列を格納するときは、 strcpy などのライブラリ関数を使用します。

初期値の数が配列の要素数より少ない場合は、 配列の先頭から初期値の数だけ要素に値が入ります。

char line[100] = "blank";
文字型の配列 line の最初の6個の要素 (line[0]〜line[5])が初期化されます (line[5] には '\0' が入っています)。

以上は ANSI-C の場合です。 古いバージョンのC言語 (UNIX-C あるいは Kernighan & Richie C と呼ばれるもの)では、 関数の内側で宣言されている static ではない (static に関しては後述します)配列変数を 初期化することはできません。

■課題24■


3.代入演算子


下の左のようなの代入は、右のようにも書くことができます。
i = i + 5;i += 5;
j = j - 5;j -= 5;
k = k * (i + j);k *= i + j;
l = l / (i * j);l /= i * j;
特に変数の内容を1だけ増す、 あるいは1だけ減じる場合は、 次のような演算子を使うことができます。
i = i + 1;i += 1;++i;
i = i - 1;i -= 1;--i;
++, -- は変数にだけ適用できる単項演算子で、 上記のように変数の左側に書く場合の他、 右側に書く場合もあります。 左側と右側では変数に対する効院疂1加減する)は同じですが、 その演算を「評価」したときの値が異なります。
i = 1;
j = ++i;
i も j も2になります。j には i の内容が1増やされた後の値が入ります。
i = 1;
j = i++;
i は2、j は1になります。 j には i の内容が1増やされる前の値が入ります。

++i のような式に限らず、普通の代入も値を持ちます。

j = (i = 1) + 1;
i = 1 という式は1という値を持ちますので、 j はそれに1を加えて2になります。
i = j = k = 3;
i, j, k とも3が代入されます。 = は右辺を評価して値を求め、 左辺の変数に代入しますので、 これは i = (j = (k = 3)); という具合に処理されます。

但し、次のような書き方は許されていません。

i = 1 + j = 3;
これは = より + の方が優先順位が高いため、 i = (1 + j) = 3; と処理されてしまうためです。 C言語では 1 + j = 3; のように、 = の左辺にこのような式を書くことはできません。 この場合は括弧を使います。
i = 1 + (j = 3);

■課題25■