入力と変数 | Programming Place Plus 新C++編

トップページ新C++編

このページの概要

このページでは、データをソースコードに書き込むのではなく、プログラムを実行してから、人間が入力するかたちを実現します。いよいよ、実行結果が固定的でないプログラムを作れるようになります。

以下は目次です。要点だけをさっと確認したい方は、「まとめ」をご覧ください。



計算をコンパイル時から実行時へ

これまで、すべてのデータはリテラルで記述し、定数式で計算を行いました。この方法でもプログラムを作ることはできたわけですが、定数式の計算は、コンパイル時に終わっていますから、実行結果はいつも同じになります。これでは実用上は問題があります。

定数式と識別子」のページでも書いたように、「3 + 5」しかできない電卓には価値はありません。計算に使う数は自由に変えたいですし、計算式も好きに変えたいです。それができてはじめて、実用的なプログラムになったといえるでしょう。とはいえ、計算式自体を自由に変えるのは、まだまだハードルが高い目標なので、ここではまず、数の部分だけを変えられるようにします。「3 + 5」でも「10 + 20」でも「10 + 3」でもできるようになることを目指しましょう。

このような自由度を得ようと思ったら、定数式は使えません。計算がコンパイル時に終わってしまってはいけないからです。ソースコードを書き換えれば数を変えられますが、ソースコードは “あなた” が読み書きするものであって、プログラムを使ってくれる “ほかの誰か” が読み書きできるとは考えられません。

「“ほかの誰か” = “あなた”」である可能性はありますし、学習段階ですから多分そうなのでしょうけど、ソフトウェア開発では、“自分とはちがう誰か”(つまりお客様)を意識しなければなりません。

そこで、プログラムを実行してから、数を入力してもらうという方法を採ることになります。そうすることで、計算を行うタイミングが、コンパイル時から、実行した後(これを実行時 (runtime) といいます)になります。

数の入力には、キーボードを使います。

画面に「0~9」までの数字が書かれたボタンのようなものを表示して、マウスやタッチ操作で選んでもらう方法も考えられますし、「電卓」をイメージするのなら、それが自然でしょう。しかし、こういうグラフィカルな表現は、C++ の標準機能だけでは実現できません(C++ で実現できないのではなく、「標準機能にはない」ということです)。

std::cin

キーボードから入力を受け取るためには、C++ に標準で用意されている入力機能を使います。

これまで使ってきた std::cout は、C++ に標準で用意されている “出力” の機能でした。“入力” の機能のほうは、std::cin です。なお、std::cin を使うときにも、#include <iostream> の記述が必要です。

std::cin を使うときの記述は、次のようになります。

std::cin >> 入力されたデータを受け取る場所;

>> の部分ですが、std::cout のときとは逆を向いた記号であることに注意してください。出力と入力とでは方向が逆だということを示しているといえます。

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

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

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

なお、std::cin を使って、整数以外のデータ、たとえば Hello のような文字の並びを入力することも可能ですが、このページではいったん、整数の入力の話題に集中します。

変数

前の項で、入力データを受け取る「場所」が必要だと書きました。変数 (variable) はその「場所」を、ソースコード上で表現する手段です。

定数式と識別子」のページで、constexpr変数という機能が登場しました。constexpr変数と、単なる「変数」の違いは、コンパイル時にその内容が確定するかどうかです。

constexpr変数はコンパイル時にその内容が確定されています。そのため、実行してから入力されるデータを受け取る場所としては使えません。

#include <iostream>

int main()
{
    constexpr auto age = 25;      <-- constexpr変数

    std::cin >> age;              <-- エラー
    std::cout << age << "\n";     <-- OK
}

対して、変数のほうは、コンパイル時にはその内容は確定せず、実行時に決まります。そのため、実行してから入力されるデータを受け取る場所として使えます。

宣言

変数を使うにあたってはまず、宣言 (declaration) が必要です。宣言とは「こういう名前の変数を使いますよ」と、コンパイラに教えるための記述です。

変数を宣言する記法はいくつかありますが、そのうちの1つは次のようになります(後でもう1つ紹介します)。

型 識別子 = 初期値;

(type)」とは、データの種類のことです。たとえば、整数、小数点を含む数、文字といったものがあります。変数を宣言するときには、その変数があつかうデータの種類に応じて、型を指定しなければなりません。今回は整数が使いたいので、整数をあらわす int という型を選択します。

“int という型” をよく、int型 (int type) と呼びます。

int 識別子 = 初期値;

int 以外の型は大量にあるので、ここでは紹介しません。今後少しずつ新しい型が登場します。

「識別子」は、constexpr変数のときに説明したとおり、「複数のものがあるとき、それぞれを区別するための情報」のことで、要するに「名前」です。識別子のルールもすでに説明したとおりです

年齢をあらわす変数であるとして、age としてみます。

int age = 初期値;

変数は「場所」を表したものです。その場所にはいつも何かがあります。言い換えると、変数はいつも何かを保持しています。その “何か” は、int型の変数であれば “何らかの整数” ということになります。ここで、変数がもつ “何か” のことを、 (value) といいます。

「値」の読み方は「あたい」です。

「= 初期値」は、変数に具体的な値を与えるための記述です。変数の宣言と同時に与える値のことを初期値 (initial value) といいます。そして、初期値を与える行為を指して、初期化 (initialization) といいます。

「値」は「あたい」と読みますが、「初期値」は「しょきち」と読みます。

初期値として 25 を与えるなら、次のように書きます。

int age = 25;


ここでは初期値にリテラルを使いましたが、constexpr変数を使うこともできます。たとえば、成年となる年齢(20)に、adult_age という識別子を与える constexpr変数を導入して、初期値として使用してみます。

#include <iostream>

int main()
{
    constexpr auto adult_age = 20;
    int age = adult_age;

    std::cout << age << "\n";
}

実行結果:

20

また、すでに宣言済みの変数を使うことも可能ですし、初期値のところで計算を行うことも可能です。

#include <iostream>

int main()
{
    int age = 25;
    int age2 = age;
    int age3 = age + 10;

    std::cout << age << "\n";
    std::cout << age2 << "\n";
    std::cout << age3 << "\n";
}

実行結果:

25
25
35

プログラムの実行後、main関数内を上から下に進むというルールは、変数を宣言している行でも同様です。

ですから実行後、まずは int age = 25; によって、変数age が使えるようになります。次の行の int age2 = age; のように、変数age を使うことができ、その値は 25 ですから、変数age2 は 25 で初期化されます。int age3 = age + 10; も同様で、初期値は 35 ということになります。


ところで、入力データを受け取る場所として使う変数の場合、適切な値は入力されてから分かるものなので、宣言のときに初期値を指定しろといわれても困ってしまいます。このようなときには、次に紹介する記法を使うと良いです

一様初期化

変数を宣言する方法として、次の書き方もあります。

型 識別子 {初期値};

さきほどの宣言方法と比べると、初期値の記述が違っています。{} を使って初期値を囲むこの記法は、(現時点ではまだ解説していないような)さまざまな場面で使える共通性の高いものであり、一様初期化 (uniform initialization) と呼ばれています。

型は int、識別子は age、初期値は 25 とすると、次のように記述します。

int age {25};

= を省きましたが、int age = {25}; のように書くことはできます。ただし、注意しなければならない場面もあるので、今後も = は書かないようにします。事情は次の上級者向けコラムで説明しています。

【上級】auto age = {25};auto age {25}; のように、一様初期化と auto を併用した場合、{} を使っているためにリスト初期化であると判断されて、std::initializer_list<int> に推論されます。自然な動作ではあるものの、初期値が1つだけなのにリスト扱いされることは錯覚しやすくもあるので、注意が必要です。

【上級】【C++17】上記コラムの動作は C++17 からは、auto age = {25}; は std::initializer_list<int> に、auto age {25}; は int に推論されるように変更されました。使い分けが可能になったものの、C++14 までと結果が異なるため、注意が必要であることに変わりはありません。

一様初期化の場合、{} の中を空にして、次のように書くことができます。

int age {};

初期値を {} にした場合、空っぽであるということではなくて、デフォルト値(標準の値)が使われます。デフォルト値は型によって決まり、int の場合は 0 です。

前に「変数はいつも何かを保持しています」と書いたとおり、そもそも、変数の値が「空っぽ」であるということはあり得ません。

一様初期化には次のような利点があります。

  1. 色々な場面で使える構文である
  2. プログラムのミスを検出する機能がある

どちらも現時点ではきちんとした解説ができませんが、一様初期化は使う価値がある記法です。

【C++98/03 経験者】1について。たとえば、C++98/03 では、配列や構造体の初期化には {1, 2} のような記法を使うのに対し、単独の変数では {1} のような記法は使えませんでした。また、メンバ初期化子ではいつも mAge(25) のような記法でした。一様初期化を使うと、それぞれ {1, 2}{1}mAge{25} と書けます。また、C c(1); のような変数宣言が、関数宣言とみなされてしまう問題を解決する手段でもあります。

【上級】2について。int a = 3.5; のような宣言では、精度が落ちて、指定どおりの値にならないのにも関わらず、暗黙の型変換が行われてコンパイルが通ってしまう問題がありました。一様初期化では、コンパイルエラーになります。

一様初期化は新しい記法です。古い時代の C++ では int age = 25; の方法で宣言していました。そのため、この書き方は今でも非常によく見かけるので、自分では使わないとしても、知っておく必要があります。

constexpr変数の場合

一様初期化は constexpr変数でも使えます。

#include <iostream>

int main()
{
    constexpr int adult_age {20};
    int age = adult_age;

    std::cout << age << "\n";
}

実行結果:

20

ここまでのページでは、constexpr auto という形で記述してきましたが、実は auto も型の指定です。auto という型の指定については、機会をあらためて取り上げることにしますが、簡単にいえば「初期値の内容から型を自動的に (auto) 判断させる」というものです。初期値が 20 なら、それは整数ですから、型は int だと判断されます。

しかし、auto と一様初期化の相性には悪い面があって、constexpr auto adult_age {20}; と書くと想定した結果になりません。

C++17 で解消されました。また、Visual Studio 2015 では、想定どおり int型にしてくれます。なお具体的な話は、以前に上級者向けのコラムで書いています。

そのため、auto をあきらめて constexpr int adult_age {20}; のように明示的に型を指定するか、一様初期化をあきらめて constexpr auto adult_age = 20; のように書きます。

入力を受け取る

では、std::cin と変数を使ったプログラムを作ってみます。

「〇+△」のような足し算をするために、〇と△に入れる整数を入力してもらうことにします。 〇のために value1、△のために value2 という変数を宣言します。

#include <iostream>

int main()
{
    int value1 {};
    std::cin >> value1;

    int value2 {};
    std::cin >> value2;

    std::cout << value1 + value2 << "\n";
}

実行結果:

3   <-- 入力した整数。最後に Enterキーを押している
5   <-- 入力した整数。最後に Enterキーを押している
8

変数の宣言を記述する位置にはある程度自由度があります。たとえば、main関数の最初のところにまとめて書くのでもいいのですが、基本的には、実際にその変数を使う直前で宣言することを勧めます。これはソースコードを理解しやすくするためです。

【C言語プログラマー】古い時代のC言語に慣れたプログラマーは、変数宣言を関数の先頭にまとめがちですが、それは C++ では実行効率を落とす原因にもなるのでやめた方がいいです。ちなみにC言語でも、C99規格からは関数やブロックの先頭で宣言する必要はなくなっています。

実行すると、何も出力されないまま止まった状態になります。このとき、std::cin が入力を待っています。この状態で、キーボードから整数を入力して、Enterキーを押すと確定できます。入力する整数は負数でも構いません。

すると、入力された整数が変数に格納されます。1回目の入力データは変数 value1 に、2回目の入力データは変数 value2 に格納しています。

最後に、value1 と value2 の値を加算演算子で足し合わせて、その結果を std::cout を使って出力しています。

リテラルや constexpr変数のときと同様に、変数も計算に使えます。変数の場合は、その値を使って計算が行われます。value1 に格納された値が 3、value2 に格納された値が 5 であれば、value1 + value2 とは 3 + 5 のことです。

このプログラムを、そのままもう1度実行して、「10」と「20」を入力すれば、「30」という出力が得られます。ソースコードを書き換えたり、ビルドをし直したりすることなく、さまざまな計算ができるようになったわけです。本物の電卓の柔軟さに一歩近づきました。


ところで、このプログラムを実行すると、入力を待ち受ける状態で一時停止します。私たちはプログラムの内容を知っているので問題ないですが、プログラムの内容を知らない人が実行したら、何が起こっているのか理解できないでしょう。

そこで、入力を待ち受ける直前、つまり std::cin を使う直前で、ガイドとなるメッセージを出力するようにします。入力させるデータの意味(たとえば年齢)や、有効な範囲(たとえば、1~100 の整数)といったものも伝えるようにするといいでしょう。

#include <iostream>

int main()
{
    std::cout << "Please enter the first integer.\n";
    int value1 {};
    std::cin >> value1;

    std::cout << "Please enter the second integer.\n";
    int value2 {};
    std::cin >> value2;

    std::cout << value1 + value2 << "\n";
}

実行結果:

Please enter the first integer.
3   <-- 入力した整数。最後に Enterキーを押している
Please enter the second integer.
5   <-- 入力した整数。最後に Enterキーを押している
8

これで状況が分かりやすくなります。

入力を1度に受け取る

さきほどは、2つの整数の入力を受け取るために、std::cin を2回使いました。

std::cout << "age: " << age << \n"; のように書いて、1度に2つ以上の出力ができるのと同じで、std::cin も1度に2つ以上の入力を受け取れます。そのように書き換えるとこうなります。

#include <iostream>

int main()
{
    std::cout << "Please enter two integers.\n";
    int value1 {};
    int value2 {};
    std::cin >> value1 >> value2;

    std::cout << value1 + value2 << "\n";
}

実行結果:

Please enter two integers.
3 5  <-- 入力した整数。あいだにスペースキー、最後に Enterキーを押している
8

複数の入力を受け取る場合は、入力の際に、スペース(空白)を挟む必要があります。この例では、「3」「スペースキー」「5」「Enterキー」の順番で入力しました。

最初に入力した整数が value1 に格納され、次に入力した整数が value2 に格納されます。

入力処理の難しさ

入力は、単純に出力の反対、という程度に思われるかもしれません。しかし、割と単純で簡単にできる出力とちがって、入力はそう簡単ではありません。入力の処理には特有の難しさがあります。

まず、入力されるデータが想定した種類のものであるという保証がありません。このページで取り上げたプログラムでは、整数が入力されることを期待していますが、「abcde」のように整数ではないものが入力される可能性があります。

入力されるデータの個数も、想定どおりに与えられる保証がありません。2つの整数を入力して欲しかったのに、1つしか入力されなかったり、3つ以上入力されたりするかもしれません。

入力されるデータの範囲も、想定どおりである保証もありません。扱いきれないほど巨大な数であったり(100000000000000000000000000000000000000)、正の数であってほしいのに負数が入力されたりするかもしれません。

このように、入力の処理では、「想定外のデータが入力される」可能性を考えなければなりません。想定していなかったからといって、プログラムが異常な動作を取ってはならないのです。

しかし、こういった問題に完璧に対処することは、少なくともここまでの知識では無理です。今のところは、自分で作ったプログラムを自分で動かすだけなので、いつも想定どおりの入力が与えられるという前提で進めていくことにします。

まとめ


新C++編の【本編】の各ページには、末尾に練習問題があります。ページ内で学んだ知識を確認する簡単な問題から、これまでに学んだ知識を組み合わせなければならない問題、あるいは更なる自力での調査や模索が必要になるような高難易度な問題をいくつか掲載しています。


参考リンク


練習問題

問題の難易度について。

★は、すべての方が取り組める入門レベルの問題です。
★★は、自力でプログラミングができるようなるために、入門者の方であっても取り組んでほしい問題です。
★★★は、本格的にプログラマーを目指す人のための問題です。

問題1 (確認★)

次のプログラムの間違いを指摘してください。

#include <iostream>

int main()
{
    constexpr auto value = 100;

    std::cout << "Please enter an integer.\n";
    std::cin >> value;

    std::cout << value << "\n";
}

解答・解説

問題2 (確認★)

次のプログラムを実行すると、どのような結果になりますか?

#include <iostream>

int main()
{
    int value1 {};
    int value2 {value1 + 10};

    std::cout << value1 << "\n";
    std::cout << value2 << "\n";
}

解答・解説

問題3 (基本★★)

整数の入力を1つだけ受け取り、次の3つの数を出力するプログラムを作成してください。

  1. 入力された整数そのもの
  2. 入力された整数を 10倍した数
  3. 入力された整数の符号を反転させた数

解答・解説

問題4 (基本★★)

整数の入力を2つ受け取り、2つの数を、加算・減算・乗算・除算した数を出力するプログラムを作成してください(除算のときには注意すべき点がありますが、分かりますか?)

解答・解説

問題5 (調査★★★)

変数を宣言するとき、次のように、初期値の記述を省略することができます。

int age;

この場合、どのような意味になるのか調べてみて下さい(C言語や C++98/03 の経験者は分かっていると思いますので、この問題は取り組まなくて結構です)。

解答・解説


解答・解説ページの先頭



更新履歴




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