入出力

コンピュータの基本構成には「入力装置」と「出力装置」がある。常に同じ計算をするだけのプログラムや自動的に計算をするようなプログラムを除けば、一般的にはプログラム外から何らかの入力を受け付けて実行内容を変化させたり、処理の途中経過や最終結果を出力して人間が確認したり装置を制御したり他のプログラムに働きかける。

C言語(および他の多くのプログラミング言語)には「標準入力」と「標準出力」という考え方がある。「標準」とは一番よく使うものということで、標準入力はキーボードからの入力、標準出力はターミナルへの文字表示、となっている(当時はグラフィクスは一般的ではなかった)。

もう一つ、エラーメッセージを出力するための「標準エラー出力」というものがあるが、ここでは説明は省略する。

既に学習したように、UNIXコマンドは標準入力と標準出力からデータを入出力することを基本としている。標準入出力の先をファイルにつなぎ替えたり(リダイレクト)、あるコマンドの標準出力を次のコマンドの標準入力とする(パイプライン)ことによってコマンドの汎用性を高めている。C言語の設計はUNIXコマンドの設計と合致している。つまり、標準入出力を扱うCプログラムは自作のコマンドとして、他のコマンドと組み合わせて使うことができるのだ。

printf関数: 標準出力に文字列を出力する(教科書2.2、2.3節)

printf関数は最初のプログラムでも登場した。

/*****
      g123456 Mitsuru Minakuchi
*****/

#include <stdio.h>

int main() {
    printf("Hello World!\n");
    return 0;
}    

このプログラム例ではターミナルにHello World!と表示させるためにprintf関数を使っている。このように、単純に文字列を引数に与えると、その文字列をそのまま表示させる(標準出力に出力する)。

printf(文字列);
文字列は"(ダブルクォーテーション)で囲む。

エスケープシーケンス escape sequence

\nは改行を指示する記法である。

この\のように、特殊な文字が表れた後は特別な解釈を行わせる取り決めをエスケープシーケンスと呼ぶ。元々はESC(0x1B)から始まるバイト列であったが、C言語の文字列中の\で始まる記法もエスケープシーケンスと呼ばれている。

\nは文字列中の途中に挟み込むこともできる。

練習:上のプログラムのprintf関数を次のように修正して実行結果を確認せよ。
printf("Hello\nWorld!\n");

逆に、\nを入れなければ改行されないので、複数のprintf関数での出力結果が続けて表示されることになる。

練習:上のプログラムのprintf関数を次のように修正して実行結果を確認せよ。
printf("Hello ");
printf("World!\n");

他の代表的なエスケープシーケンスを次にまとめる。

エスケープシーケンス 意味
\n 復帰改行
\t タブスペース
\b バックスペース
\a ベル
\\ 文字として\を出力する
\' 文字として'を出力する
\" 文字として"を出力する

 

\tはターミナルで設定された区切り位置(タブストップ、初期状態で8文字分)までの空白(TAB文字)を表示するものである。

練習:上のプログラムのprintf関数を次のように修正して実行結果を確認せよ。
printf("Hello\tWorld\n");

TAB文字というのは空白文字の一種で、スペースとは異なる点に注意。状況に応じて伸縮できる空白であるが長く見えても1文字分である(試しにターミナルの表示結果部分をマウスでドラッグしてみるとよい)。

\bは表示を1文字分戻して次の文字を書く場合に使用する。

練習:上のプログラムのprintf関数を次のように修正して実行結果を確認せよ。
printf("Hello World!\b?\n");

この例のように最初から\bを使って表示することはあまり意味が無い。実用的には例えば、後に扱う条件分岐を使って、ある条件が成立したときには!を?に置き換えて表示するような使い方ができる。

\aは音を鳴らすので、メッセージを表示するだけでなくユーザに注意を促したい時などに使うことができる。

練習:上のプログラムのprintf関数を次のように修正して実行結果を確認せよ。
printf("Hello World!\n\a");

\\、\'、\"は、\、'、"自体を表示させたい場合に使う。

値を表示する

あるゲームで次のようなメッセージを表示させることを考えてみよう(キャラクタ名トンヌラはここでは固定で変更できないとしておく):

トンヌラが次のレベルになるにはあと123の経験値が必要じゃ。

次のレベルへの経験値はプレイ状況によって変化するだろう。このように値が変化するメッセージを表示するために、

トンヌラが次のレベルになるにはあと___の経験値が必要じゃ。

のように値を入れ込む場所を用意しておいて、そこに実際の値を入れ込んで表示する。

このような表示方法をprintf関数で行うには次のように書く。

printf("トンヌラが次のレベルになるにはあと%dの経験値が必要じゃ。\n", 123);

文字列中の%dの部分が値を入れ込む場所になっている。このような%dを書式指定子と呼ぶ。書式指定子の場所には文字列の次の引数(上の例では123)が入れ込まれる。

複数の書式指定子がある場合は、それぞれの場所に対応する値が入れ込まれる。例えば次のprintf関数を実行すると、

printf("トンヌラはレベル%dになった! 体力が%d上がった!\n", 12, 34);

一つ目の%dには文字列の次の引数である12、二つ目の%dにはさらに次の引数である34が対応して入れ込まれるので次のような表示になる。

トンヌラはレベル12になった! 体力が34上がった!

printf関数の文法を一般化して書くと次のようになる:

printf(書式文字列, 値1, 値2, 値3, ...);
ただし、値1以降の引数は書式文字列中に出現する書式指定子に出現順に対応しており、型と個数は一致していなければならない。

printf関数のfは「書式付き」formattedのfである。すなわち、print formatted。

printf関数のような引数の個数が決まっていない関数は例外的と考えた方がよい。多くの関数では定義された通りの引数を与えるのが普通である。

練習:次の各メッセージを出力するprintf文を考えよ。ただし、数値の部分は書式指定子%dを使って、値を入れ込むこと。

財布の中身は5000円
私の誕生日は1997年1月23日です
2001年宇宙の旅
ゴルゴ13

値の部分には固定の数値以外にも、計算式や変数を書くことができる(むしろ固定の数値を書くことは意味が無いだろう)。例えば、

printf("トンヌラが次のレベルになるにはあと%dの経験値が必要じゃ。\n", 1000 - 877);

int nextExp // 次のレベルに必要な経験値
//途中省略
printf("トンヌラが次のレベルになるにはあと%dの経験値が必要じゃ。\n", nextExp);

なお、ここで出てきた%dはint型の数値を指定するための書式指定子である。short型の場合は%hd、long型の場合は%ldを使う必要がある。また、実数値を表示する場合は%fおよび%lfを使う。詳細は本講義では省略する。

確認課題:次のプログラムを完成させて「トンヌラはレベル12になった! 体力が34上がった!」と表示するようにせよ。メッセージの表示部分には、変数levelとhpを使うこと。

/*****
    levelup.c
    レベルアップ時のメッセージを表示する
    Mitsuru Minakuchi
*****/
#include <stdio.h>

int main() {
    int level;  // 現在のレベル
    int hp;  // 体力の増加値

    level = 12;  // レベル12になったとする
    hp = 34;  // 体力は34増える

        // メッセージを表示する(この部分を書け!!)

    return 0;
}

補足:変数の宣言時に初期値を代入してしまうこともできる。上のプログラムでは int level = 12; のように書ける。

scanf関数: 標準入力からデータを読み取る(教科書3.6節)

プログラムが標準入力からデータを読み取るためにscanf関数が用意されている。最も簡単な使い方の例を次に示す:

/*****
    scanf.c
    scanfで値を読み取る
    Mitsuru Minakuchi
*****/
#include <stdio.h>

int main() {
    int value;  // 入力値を格納するための変数
    scanf("%d", &value);  // 標準入力から整数値を読み取る
    printf("%d\n", value);  // 入力された値をそのまま表示してみる
    return 0;
}

scanf関数の中で、変数名valueに&が付いていること、\nを書かないことに注意!! 理由はちょっと難しいのでここでは省略する。scanf関数ではそう書くものだ、として覚えておいて欲しい。

気になる人向けの説明:C言語では関数の引数はその値を渡すことになっている。このため、valueと単に変数名を書いただけだとvalueの値が渡されてしまうので、scanf関数内ではただの整数値になってしまって値を格納できない。そこで、変数の存在する場所(ポインタ)を渡して、その場所に読み取った値を格納するようにしている。

このプログラムをコンパイルして実行させると次の表示で止まる:

% gcc -o scanf scanf.c
% ./scanf

これはscanf関数が読み取るべき値を待っている状態である。ここで、例えば123と数字を入力してreturnキーを押すと、次のように表示される:

% gcc -o scanf scanf.c
% ./scanf
123
123
%

なお、ここで数字以外の文字を間違って入力すると正しい値が表示されない。例えば次のようになる:

% gcc -o scanf scanf.c
% ./scanf
aaa
1706627166
%

この理由については後で説明する。

何も表示されずにプログラムが止まってしまうと使う人(ユーザ)が何をしてよいか分からないので、scanf関数の前で何か入力を促すメッセージを表示するとよい。例えば次のようにすると数字を入力すればよいことが分かる(メッセージの最後に\nを入れていない点に注意):

/*****
    scanf.c
    scanfで値を読み取る
    Mitsuru Minakuchi
*****/
#include <stdio.h>

int main() {
    int value;  // 入力値を格納するための変数
    printf("input number: ");  // 入力を促すメッセージ
    scanf("%d", &value);  // 標準入力から整数値を読み取る
    printf("%d\n", value);  // 入力された値をそのまま表示してみる
    return 0;
}

printf関数と同じようにして次のようにできそうな気がするが、これでは思ったようには動作しない:

/*****
    scanf.c
    scanfで値を読み取る
    Mitsuru Minakuchi
*****/
#include <stdio.h>

int main() {
    int value;  // 入力値を格納するための変数
    scanf("input number: %d", &value);  // 標準入力から整数値を読み取る
    printf("%d\n", value);  // 入力された値をそのまま表示してみる
    return 0;
}

練習:このプログラムを実行させて何か数値を入力してみよう。
(実は変数valueに正しく値を格納できる入力方法がある。ちょっと難しいが考えてみよう。)

scanf関数の文法はprintf関数と似ていて次のようになっている:

scanf(書式文字列, &変数名1, &変数名2, &変数名3, ...);
ただし、変数名1以降の引数は書式文字列中に出現する書式指定子に出現順に対応しており、型と個数は一致していなければならない。

上の例で示したようにscanf関数の書式文字列は、入力データが書式文字列で指定したパタンに該当する場合にのみ正しく値を読み取って変数に格納できることに注意。ユーザは意図したとおりに入力してくれるとは限らないので初心者のうちは次の形式でのみ使うのが間違いがなくて良いだろう:

scanf("%d", &変数名);
但し%dに対応する変数型はint型

printf関数と同様に、実数型であるfloatとdoubleにはそれぞれ%fと%lf、文字列型には%sを使うが、この演習では扱わない。

書式文字列を使うケースとしては、複数のデータを一度に入力してもらいたい場合や、リダイレクトやパイプラインでファイルの内容や他のプログラムからの出力内容を自動的に解釈してデータを受けとる場合などが考えられる。

%dの書式指定子で読み込まれたデータは10進数表現のint型と解釈されて、変数名で指定された変数に格納される。なので10進数でない値、たとえば「aaa」という文字列が入力されると解釈に失敗しておかしな値になってしまう。また、それ以降のscanf関数の動作がおかしくなることがある。

問題が起きないように、文字列で入力させプログラム内部で数値に変換する方法があるが、この演習のレベルを超えているので扱わないことにする。ユーザが変なデータを入力しないことを祈ろう。

応用練習

確認課題:入力された2つの整数の和を表示するプログラムadd.cを作成せよ。

いきなりどのようなプログラムになるか想像できない場合は、プログラムの手順を分解して考えてみよう。

練習:このプログラムの処理手順を日本語で書き出せ。

次に、日本語で書いた処理をC言語に翻訳していく。まず、骨格となるmain関数を書く。

/*****
    add.c
    入力された2つの整数の和を表示する
    2015.5.27 Mitsuru Minakuchi
*****/
#include <stdio.h>

int main() {

    return 0;
}

ポイントは変数の使い方である。このプログラムでは普通に考えれば、入力された2つの整数値を覚えておくための変数、これらの値の和を覚えておくための3つの変数を使う。和をいきなり表示させるなど変則的な変数の使い方も可能であるが、プログラムが読みにくくなることがあるので注意。

変数の使い方のポイント:
・変数をケチらない。1つの目的ごとに変数を用意する。
・変数名は、変数の意味を端的に表す名前を付ける。

練習:変数の宣言を追加せよ。

次に、2つの整数値を入力する部分を追加する。1番目の値と2番目の値を分けて順番に書けばよい。

練習:2つの整数値を入力する部分を追加せよ。

最後に和を計算させて表示する。

練習:残りの部分を追加して、コンパイル・実行できることを確認せよ。

プログラムを書き上げたら必ずコンパイルし、幾つかの値でテストして意図したとおりに動作していることを確認すること。

本日の提出課題

提出課題1:次の図を描くプログラムcalcCircles.cをHandyGraphicを使って作成せよ。ウィンドウの大きさは幅400x高さ400である。3つの円はお互いに接している。外側の一番大きな円の中心座標は(240, 210)、半径は120である。内側の円2つの直径は2:1となっており、中心のy座標はいずれも外側の円と同じである。図形描画に必要な座標値は変数を使ってプログラム中で計算させること(手計算はダメ)。

提出期限:次の土曜日の24:00まで

提出課題2:応用練習で作成したプログラムを拡張して、入力した2つの値の和、差、積、商を計算して表示するようにせよ。プログラム名はcalc.cとする。それぞれ、どの計算結果か分かるように、次の実行例のように表示されるようにせよ。実行例以外の値でも正しく計算できることを確認すること。

提出期限 次の授業開始時まで

実行例1:
      % gcc -o calc calc.c
      % ./calc
      input 1st number: 12
      input 2nd number: 3
      12 + 3 = 15
      12 - 3 = 9
      12 * 3 = 36
      12 / 3 = 4
      %
実行例2:
      % gcc -o calc calc.c
      % ./calc
      input 1st number: 10
      input 2nd number: 4
      10 + 4 = 14
      10 - 4 = 6
      10 * 4 = 40
      10 / 4 = 2
      %

実行例2のように、整数同士の除算の結果は余りを無視した整数値になることに注意(これで正しい結果である)。