入力と変数 | Programming Place Plus C言語編 第4章

トップページC言語編

このページの概要

以下は目次です。


標準入力

前の章までに、C言語で計算処理を記述できるようになりました。しかし今のところ、3 + 5 とか 8 / 2 といったように、計算式が固定されてしまっています。計算式を変更したいと思ったら、ソースコードを書き換えて、ビルドし直して、実行するといった手順が必要であり、まったくもって不便です。十分に実用的なプログラムにするためには、プログラムを実行したあとで、利用者が自由に計算式を入力できることが必要でしょう。

そこで、この章では、プログラムの実行中にデータの入力を受け取る方法を説明します。

データを入力する手段はいくつかあります。代表的なのはキーボードですが、マウスやタッチによる操作も入力ですし、どこかのファイルに書き込まれているデータをプログラムに読み取らせることも入力といえます。ほかのコンピュータからネットワークを通してデータを受け取ることも、音声入力、スキャナで画像を読み取るといったことも入力といえます。

ここでは、標準入力 (standard input) というものを取り上げることにします。つまり、プログラムを実行している環境におけるもっとも標準的な入力方法を用いるということですが、それはほとんどの場合、キーボードということになります。そのため、キーボードからの入力の話をしているのだと思っていただいて構わないですが、C言語(あるいはコンピュータの世界)ではよく、標準入力という用語を用いるので、今後も標準入力と呼んでいきます。

【上級】標準入力とは、いわば入力を受け取る経路のことであり、その経路を通ってデータが送られてくるというイメージです。その経路の入口の側に、通常はキーボードがあります。しかしそれは変更可能なものであるため、キーボードから入力されているのだと断定してはいけないのです。データの出どころが何であれ、プログラム側としては、標準入力という経路にだけ注目します。

ついでになりますが、標準出力というものもあります。考え方は同じで、もっとも標準的な出力方法ということになります。ほとんどの場合それは、画面上への表示を意味します(出力の方法としてはほかにも、ファイルへ書き出す、プリンタで紙に印刷する、音声で読み上げるなどがあります)。puts関数や printf関数は、標準出力を使ってデータを出力します。そのため、画面に文字が出力(表示)されました。

fgets関数

標準入力を使ってデータを受け取る方法はいくつかあります。まずは、fgets関数を使う方法を取り上げます。

fgets関数は、文字列の入力を受け取る関数です。名前の頭に付いている「f」は「file」を表しており、本来はファイルの内容を読み取るものですが、標準入力に対して使うこともできます。なお、fgets関数を使うためには、ソースコードの冒頭に #include <stdio.h> の記述が必要です。

fgets関数を使って、標準入力から入力を受け取るための記述は次のようになります。

fgets(入力された文字列を受け取る場所, 受け取れる最大文字数, stdin);

「入力された文字列を受け取る場所」が入力処理での1つのポイントになります。入力されたデータは、どこかに場所を用意してあげて、そこに受け取ります。

ここで「場所」というのは、コンピュータに内蔵されているメモリ (memory) という記憶装置の一部分のことです。こういうと難解な感じもしますが、ソースコードに書くことはそれほど難しいものではありません。必要なのは、このあと説明する「変数」という機能を理解することだけです。

単にメモリと表記することが多いですが、実際にはメインメモリ(主記憶)のことです。

「受け取れる最大文字数」はそのままの意味で、ここに指定した整数を超えた文字数の入力は受け取られません。入力はされたが受け取られなかった部分の文字がどうなるのかという問題があり、これは非常に対処の難しい問題です。この話題は第7章であらためて取り上げることにして、この章では、長すぎる文字列は入力されないという前提で話を進めることにします。

「stdin」は文字どおり、stdin と記述します。これは標準入力のことで、この記述によって、入力データを標準入力から受け取ることを指示します。


実際の使用例は次のようになります。詳細はこのあと順次解説していくので、まずはソースコードを眺めることと、実行するとどうなるのかを確認してください。

#include <stdio.h>

int main(void)
{
    puts("Please enter the string.");
    char input_string[80];
    fgets(input_string, sizeof(input_string), stdin);

    puts(input_string);
}

実行結果:

Please enter the string.
abcde  <-- 入力された文字列
abcde
 

【C89/95 経験者】C99 以降、ローカル変数の宣言を関数やブロックの先頭に記述しなければならないという制約はなくなっています。変数を使用する直前で宣言し、可能なら初期値も与えるようにしましょう。

実行すると、最初に「Please enter the string.」と表示され、その状態で停止します。ここで、キーボードから文字を入力できるので、何か適当な文字列を入力してください。Enterキーを押すと確定します。すると、入力した文字列がそのまま画面にもう一度表示されます。

string とは文字列のことです。

日本語を使うと、正しく動作しない可能性があります。詳しいことは、第46章第47章で説明しています。

改行

fgets関数は、最後に入力される改行(Enterキーを押したときに入る)まで含めて受け取るという特性があります。改行は、改行文字という特殊な文字で表現されるため、これも1文字分にカウントされます。そのため、fgets関数で受け取った文字列を、そのまま puts関数を使って出力すると、puts関数が付ける改行も加わって、1回余計に改行されます。さきほどの実行結果をよくみると、最後に1行分の空行があるのがわかります。

この余計な改行が嫌なら、fgets関数の呼び出しの直後に、末尾に付いた改行を取り除く必要があります。ただ、これは現時点の知識では難しいので、代わりの方法として、puts関数を printf関数に置き換える手を採ります。

fgets関数が受け取った文字列の末尾の改行を取り除く方法は、第40章で取り上げます。

#include <stdio.h>

int main(void)
{
    puts("Please enter the string.");
    char input_string[80];
    fgets(input_string, sizeof(input_string), stdin);

    printf("%s", input_string);
}

実行結果:

Please enter the string.
abcde  <-- 入力された文字列
abcde

printf関数の変換指定子を %s にすると、文字列を出力できます。そのうえで \n を使わなければ、printf関数は改行を行わないので、余計な改行が消えます。

つまり、fgets関数が取り込んでしまう改行は消せていませんが、代わりに puts関数が付けてしまう改行のほうを回避したというわけです。

【上級】printf("%s", input_string);printf(input_string); とは書かないように注意してください。printf関数の第1引数は %d などの特殊な文字に特殊な扱いをします。もし、input_string に %d が含まれていると、2つ目の実引数を参照しようとしますが、そのようなものを指定していないので、不正な値を読み取ってしまいます。特にこのサンプルのように、入力データとして受け取った文字列には、どのように細工されたデータが入力されるか分かりませんから、セキュリティの観点で非常に重大な問題を起こす恐れがあります。

変数

さきほどのサンプルプログラムの、main関数には以下の行があります。

char input_string[80];

これは、変数 (variable) を宣言する記述です。

入力されたデータを受け取る場所が必要だと前述しましたが、その「場所」として使えるものが変数です。つまり、この1行は、入力を受け取る場所を用意しています。

また、宣言とは、「こういう変数を使いたい」というプログラマーの願望をコンパイラに伝える方法のことをいいます。宣言を記述せずに変数を使うことはできません。

宣言は次のいずれかのかたちで記述します。

型名 変数名;
型名 変数名[要素数];

「型名」とは、変数の (type) の指定です。型とは、どのような種類のデータを扱えるかという指定です。今回は char を指定しましたが、これは「文字 (character)」のことです。char を指定して宣言された変数は、文字しか扱えません。

型にはさまざまな種類があります。以下に代表的なものを挙げます。

扱えるデータ
int 整数
char 文字
double 浮動小数点数

浮動小数点数は難しそうな名前ですが、ごく単純にいえば、小数点を含んでいるような数のことです。実際のところ、正しく取り扱うのは本当に難しいので、当面は取り上げません。今のところは intchar を知っておけば十分です。

浮動小数点数については、第20章で詳しく取り上げます。

型が int の変数を「int型の変数」、型が char の変数を「char型の変数」といったふうに表現することが多いです。

char型の変数は文字を取り扱えますが、それは1文字だけに限られます。今回、文字列の入力を受け取りたいので、これではまったく足りません。そこで、変数の宣言のとき、[80] のような記述を後ろに付けます。char input_string[80]; とはつまり、char型 80個分の変数を宣言しているという意味です。これで 80文字まで取り扱える変数を宣言したことになります(80 を選んだことに深い意味はありません。この程度で十分だろうということです)。

このように、変数が [80] のような記述を伴って宣言される場合、その変数を配列 (array) と呼びます(「char型の配列」のように呼ぶ)。また、配列である変数の型は、配列型 (array type) と呼ばれます。

配列ではない、単なる 1文字だけの文字の扱いについては、第8章で取り上げます。

【C++プログラマー】C言語には std::string のような文字列を表現する専用の型はありません。char型の配列を使って表現します。

識別子

「変数名」には、変数の名前を記述します。どんな名前にするかはプログラマーが決めます。変数名は正確には識別子 (identifier) と呼ばれ、複数のものがあるとき、それぞれを区別するための情報を意味します。区別を付ける意味があるので、同じ名前の変数を複数宣言することはできません。

【上級】例外として、互いの変数が無関係であることが明確に分かる場合は、同じ名前の変数が宣言できます。

変数名は、ソースコードを書いたあと、時間が経って読み返しても、意味をすぐに理解できるように付けましょう(これはなかなか難しいのですが)。

識別子として使える文字は、次のように決められています。1

  1. アルファベット (a~z、A~Z)
  2. 数字 (0~9)
  3. アンダースコア (_)
  4. ユニバーサル文字名(国際文字名)
  5. 処理系定義の文字

4の「ユニバーサル文字名」は難しいので、ここでは取り上げません(コラム参照)。

【上級】ユニバーサル文字名とは、ISO/IEC 10646 で定められた文字コードを使い、\u3042 のような形式で文字を記述する方法です(これは「あ」を意味しています)2

5の「処理系定義の文字」とは、各処理系がそれぞれの判断で使えるようにしている文字ということです。処理系というのは、ソースコードを解釈して実行するために必要な環境のことです。大抵の場面では、コンパイラのことだと思っていいです。

処理系定義とは、処理系が仕様を決めるという意味のC言語の用語です。つまり、C言語の規格を見てもどんな動作になるか書かれていないため、コンパイラのマニュアルなどから詳細を知る必要があるということです。

識別子に日本語を使いたいと思うかもしれません。それが可能かどうかは、5の処理系定義の文字に、日本語の文字が含まれているかどうか次第ということになります。つまり、コンパイラ次第だということです。C言語編としては当面、1~3に含まれる文字だけを使う方針で進めます。


一方で、使うことができない識別子のルールもあります。使うことができない識別子を使おうとすると、コンパイルエラーになります。

  1. 先頭に数字は使えない
  2. キーワードは使えない
  3. 予約済み識別子は使えない

キーワードをすべて覚える必要はありませんが、知らずに同じ名前を使おうとして、コンパイルエラーが起こることがあるかもしれません。C言語に存在するすべてのキーワードを、APPENDIX「キーワードの一覧表」にまとめてあります。

予約済み識別子 (reserved identifier) とは、C言語の現在や将来の実装で使うために確保されている名前のことです。以下の条件に当たるもの該当しますが、正確なルールは難しいため概略として示します。3

  1. 2文字以上の連続する _ が含まれた名前
  2. 先頭が _ で、2文字目が大文字のアルファベットになっている名前
  3. 先頭が _ の名前(使い方による)
  4. 標準ライブラリが定義している識別子など(使い方による)

4番のすべてを把握することは困難なので、あまり考えなくていいと思います。注意すべきは1~3です。この3つはすべて _ が絡んでいるので、_ を使うときに気を付けるようにしてください。_ は英単語の区切りの文字として使うように徹底していれば、問題になることはないはずですinput_datayour_name など)。

変数はデータを受け取る場所(それはメモリ上にある)として機能するわけですが、それは言い換えると、その場所にデータが記憶されるということです。ここで、メモリ上のある場所に記憶されているデータのことを、 (value) といいます(読み方は「あたい」です)。値は、何らかの型(int とか char とか)を持ちます。

変数はつねに何か1つだけの値を保持(記憶)しています。

さきほどのサンプルプログラムでは、次のように宣言をしました。

char input_string[80];

このように宣言した場合、どんな値を保持しているかはっきりしません。

この「値がはっきりしていない」状態を、「未初期化」「不定である」「不定値を持つ」といったふうに表現します。この状態にある変数は、取り扱いに慎重にならなければなりません。たとえば、次のコードは問題があります。

char input_string[80];
puts(input_string);

このように、ソースコード内のコードの一部分だけを抜き出して示すことがあります。実際には main関数の内側に記述しているものと考えてください。

変数input_string は未初期化状態なので、このまま puts関数を使って内容を出力しようとすると、何が起こるか分かりません。何も出力されないかもしれないですし、滅茶苦茶な出力になるかもしれません。そもそも、プログラムが正常に実行されるかどうかすら分かりません。未初期化状態の変数に対してできることは、はっきりした値を与えてやることだけです。

ある変数に最初に与えられる、はっきりした値を初期値 (initial value) と呼びます。初期値を与える方法はいくつかありますが、次のように fgets関数を使って入力された値を与えてやると、それが初期値になります。

char input_string[80];
fgets(input_string, sizeof(input_string), stdin);
puts(input_string);

このコードならば、puts関数は正常な結果を出力できます。

初期値を与えるほかの方法としては、変数の宣言のときに初期値を指定する方法や、あとから値を与える代入という操作があります。それぞれ、第7章で解説します。

sizeof

fgets関数を呼び出すコードに、sizeof(input_string) という記述がありました。

sizeof は、sizeof演算子 (sizeof operator) という演算子の一種です。演算子というと +* のような記号のイメージですが、こういうものもあります。演算子だという事実を気にする必要はないです。

ここでは詳細な解説はしませんが、fgets関数に渡す2つ目の情報(「受け取れる最大文字数」)を、sizeof(受け取る配列の名前) のように指定してやると、その配列に保存できる最大文字数を指定したことになります。

【上級】もう少し正確にいうと、指定した変数の型の大きさ(バイト数)になります。char型は 1バイトであり 1文字分の大きさなので、char型の配列を指定すると、取り扱える文字数を指定したのと同じことになります。

char input_string[80];

fgets(input_string, sizeof(input_string), stdin);
fgets(input_string, 80, stdin);

このコードで、2つの fgets関数の呼び出しはまったく同じ意味になりますが、sizeof演算子を使ったほうが優れたプログラムです。たとえば、input_string の宣言をあとから char input_string[40]; に修正することになったとします。fgets関数の呼び出しに 80 という整数定数を記述していたら、ここも忘れずに 40 に修正しなければなりません。1箇所の修正によって、ほかの箇所の修正が必要になるプログラムは管理が難しく、簡単にバグの原因になってしまいます。sizeof演算子を使っておけば、input_string の宣言に応じた指定に自動的に変更されるため、管理が容易になり、バグを防ぐ効果があります。


練習問題

問題① 次のうち、変数名として使えないものをすべて選んでください。

問題② 不定値を、printf関数で出力してみてください。

問題③ fgets関数で受け取った文字列を、標準出力へ2回出力するプログラムを作ってください。

問題④ fgets関数を2回使って適当な文字列を2つ受け取り、入力された順序の逆の順序で出力するプログラムを作成してください。


解答ページはこちら

参考リンク


更新履歴

’2018/5/18 「変数の名前」の項を修正。項の名前を「変数の名前(識別子)」に変更し、内容を見直した。

’2018/5/16 「変数の初期化」の項を修正。「初期値」と「初期化子」を明確にした。

’2018/5/11 章のタイトルを変更(「変数を宣言する」->「変数」)
「変数を使う」の項を細分化し、「定数」の説明を追加。変数から変数への代入について追記。
練習問題に問題④を追加。

≪さらに古い更新履歴を展開する≫



前の章へ (第3章 乗算・除算)

次の章へ (第5章 コメントの書き方)

C言語編のトップページへ

Programming Place Plus のトップページへ



はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る