代入 | Programming Place Plus 新C++編

トップページ新C++編

このページの概要

このページでは、代入を説明します。代入は、変数が変数と呼ばれる理由と強く関係する機能です。変数の値は、宣言されたあとでも何度も変更することができます。このような、変数の値の変更をおこなう操作が代入です。

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



std::cin による変数の値の変更

変数はいつも何らかの値を持っています。宣言のときに初期値を与えており、何も値を持っていないという状態はありえないのでした(「入力と変数」のページを参照)。

これまでのページでの変数の使い方は、std::cin と組み合わせて、入力されたデータを受け取るという用途に限られていました。たとえば、次のようなプログラムを作りました。

#include <iostream>

int main()
{
    int value {};
    std::cin >> value;
    std::cout << value << "\n";
}

実行結果:

10   <-- 入力した整数
10

変数 value を宣言するとき、初期値を {} と指定しています。このように指定した場合、型に応じたデフォルトの値が初期値になるのでした(「入力と変数」のページを参照)。int型なら 0、std::string型なら空文字列("")です。

ということは、変数 value は、0 という値を持った状態で始まっていますが、その後、std::cin のところで「10」のような入力を受けて、値が変更されていることになります。これは変数の重要な性質です。変数はいつも同じ値を持ち続ける必要はなく、自由に値を変更できます。変数(変わる数)という名前がこの特徴をよく表しています。

constexpr変数は、コンパイル時に値が確定して変更できないので、明らかにおかしな名前ですが。

代入

変数が持っている値を変更する操作を代入 (assignment) といいます。

任意のタイミングで、任意の値を代入するために、次の構文があります。

変数名 代入演算子 式

この構文は、代入式 (assignment expression) と呼ばれます。

代入演算子 (assignment operator) と呼ばれる演算子は、= という記号で表現します。

代入演算子には「=」、つまりイコールの記号を使いますが、「等しい」という意味はなく、「変数名 = 式」は「変数名と式が同じである」というようなことを意味しません。

プログラムの実行中、代入式の箇所に来ると、まず「式」の部分を処理します。は、処理を行うと必ずなんらかの値になります10 + 10 は 20 になりますし、a + b は変数a の値と変数b の値を足し合わせたものになります)。式から値を作り出すことを、式を評価 (evaluation) するといいます。「式」の部分を評価してつくられた値が、「変数名」に記述した変数に与えられます。

なお、代入演算子の左側の記述を左辺 (left-hand side)、右側の記述を右辺 (right-hand side) ということがあります。


代入は、すでに存在している変数の値を変更する行為ですから、「変数名」に書く変数は宣言されたものでなければなりません。そうでなければコンパイルエラーになります。constexpr変数やリテラルに対して代入することもできません(実行中に変更できるものではないので)。

実際に代入を試すプログラムを書いてみます。

#include <iostream>

int main()
{
    constexpr auto big_value = 10000;
    int value {};
    int value2 {20};

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

    // リテラルを代入
    value = 10;
    std::cout << value << "\n";

    // ほかの変数(の値)を代入
    value = value2;
    std::cout << value << "\n";

    // 計算式の結果を代入
    value = value2 + 10;
    std::cout << value << "\n";

    // constexpr変数(の値)を代入
    value = big_value;
    std::cout << value << "\n";
}

実行結果:

0
10
20
30
10000

5つある std::cout はすべて同じ変数 value の値を出力していますが、毎回異なる値が出力されていることが分かります。

代入と型

変数の宣言の際に、あつかいたいデータの種類に応じた型(int、std::string など)を指定しています。型が合わない値を代入することは、“通常は” できません

“通常は” というように、型が異なっていても代入が許されるケースもありますが、動作を理解していない限りは原則として避けたほうが無難です。できないことはコンパイラが教えてくれるので気付けますが、できてしまうことは気付きにくいので、代入は、型を気にしながら行うようにしてください。型に無頓着につくられたプログラムは、バグを作りこんでしまうことがあります。

たとえば、int型の変数に文字列を代入しようとするコードはコンパイルエラーになりますが、std::string型の変数に整数を代入しようとするコードはエラーになりません。

#include <iostream>
#include <string>

int main()
{
    int value {};
    std::string message {};

    // OK. int型の変数に整数を代入できる
    value = 100;

    // OK. std::string型の変数に文字列を代入できる
    message = "Hello";

    // エラー。int型の変数に文字列は代入できない
    value = "Hello";

    // 注意! std::string型の変数に整数は代入できてしまう
    message = 100;
}

message = 100; はコンパイルエラーになりません。かといって、結果が “100” のような文字列になるわけでもありません。

【上級】std::string型の変数に整数が代入できてしまうのは、char型を引数にとる operator=() が定義されているからです1。char型であつかえる範囲の整数は無条件で受け入れてしまいます。char型であつかえない大きさの整数の場合は、char型へ暗黙的に縮小変換されることを理由とした警告が出るかもしれません。

代入後の右辺の変数の値

代入式の右辺側に変数を指定した場合、その変数の値は代入後も変わりません。つまり代入は、値を移し替えているのではなく、コピーしています。

#include <iostream>

int main()
{
    int value {0};
    int value2 {20};

    std::cout << value << "," << value2 << "\n";

    value = value2;
    std::cout << value << "," << value2 << "\n";
}

実行結果:

0,20
20,20

20 で初期化された変数 value2 を、変数 value へ代入していますが、変数 value2 の値は 20 のまま変化していません。

{} を使った代入

変数を宣言するときに、int n {}; と書くと、型に応じたデフォルトの値で初期化されるのでした(「入力と変数」のページを参照)。

代入の場合も、右辺側に {} と書くことができます。その場合、左辺側の変数の型に応じたデフォルトの値を代入できます。int型なら 0、std::string型なら空文字列("")になります。

#include <iostream>
#include <string>

int main()
{
    int value {999};
    std::string message {"Hello"};

    value = {};    // 左辺が int型の変数なので、int型のデフォルト値 0 を代入
    message = {};  // 左辺が std::string型の変数なので、std::string型のデフォルト値 "" を代入

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

実行結果:

0

また、あまり使われませんが、n = {10} のように、{} の中に値を記述した書き方でも代入できます。

【上級】n の型が T であるとすると、n = {v}n = T{v} と同じ意味です。{} を使った初期化では、初期値として与えた値を T型に縮小変換しなければならない場合、コンパイルエラーとしてあつかいますから、n = {v} でも、縮小変換になっている場合にはコンパイルエラーになります。たとえば、int型の変数a に対して、a = 99.9; は許されますが(切り詰められて 99 になる)、a = {99.9}; はコンパイルエラーです。

代入式の連結

代入式は連結できます。

#include <iostream>

int main()
{
    int value {10};
    int value2 {20};
    int value3 {30};

    value = value2 = value3;

    std::cout << value << "," << value2 << "," << value3 << "\n";
}

実行結果:

30,30,30

value = value2 = value3 のように、代入式が連続している場合、右側から順に処理されます。つまり、value = (value2 = value3) であるかのように扱われます。2まず、value3 の値が value2 に代入され、その結果が value に代入されるということです。

この書き方は、複数の変数に同じ値を代入したいときに、1つの文にまとめて書けるのだと理解すればいいです。

自己代入

ある変数に、自分自身の値を代入する記述も可能です。

#include <iostream>

int main()
{
    int value {10};

    value = value;

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

実行結果:

10

value = value のように、自身の値を自身に代入するかたちを自己代入 (self assignment) と呼びます。

自己代入に特別なルールがあるわけではなく、いつもの代入のルールに従います。つまり、右辺の変数の値が、左辺の変数に代入され、右辺の変数に変化は起きません。結局のところ、何も変化は起こらないということになります。

わざわざ自己代入をすることに意味はありませんが、不正でないことを知っておくと便利な場面がいずれ登場します。

【上級】代入演算子の動作を書き換える場合(演算子のオーバーロード)、自己代入で値に変化が起こらないことを保証するのはプログラマーの役目です。


自己代入ではありませんが、似たようなかたちの代入はよく必要になります。たとえば、ある変数の値を「現在の3倍」にしたいとき、次のように書けます。

#include <iostream>

int main()
{
    int value {10};

    value = value * 3;

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

実行結果:

30

変数 value の値を 3倍した値を得るには、乗算演算子を使って value * 3 とすればいいです。これまでは、計算結果はすぐに std::cout に渡して出力するか、変数の初期値として使うかでした。今回は変数の値を変更したいので、代入します。

value * 3 という計算式の結果を value に代入すればよく、value = value * 3 という式になります。左辺と右辺に同じ変数があらわれる特徴的な代入式になりました。

代入にイコールの記号が使われる理由に頭を悩ませないでください。value = value * 3 は、= を「イコール」だと捉えてしまうとまったく意味不明にみえます。代入の「=」は単にそういう記号です。

複合代入演算子

value = value * 3 のように、ある変数に計算をおこなって、結果を同じ変数に入れたいという場面はよくあります。そのため、記述を少し簡単に済ませる方法が用意されています。

その方法は、複合代入演算子 (compound assignment operator) と呼ばれる演算子を使うことです。複合代入演算子はいくつかの演算子を総称した名称です。現時点では以下の5つを知っておくと良いです。

構文は以下のとおりです。

変数名 複合代入演算子 式

具体的には、たとえば value *= 3; のような記述になります。この意味は、value = value * 3; と同じです。つまり、「X op= E」は「X = X op E」と同じです。

【上級】E1 op= E2E1 = E1 op E2 と同じ意味ではありますが、複合代入演算子の場合は E1 が1回しか評価されない点でだけ異なっています3。通常これは好ましい動作です。

複合代入演算子を使ったプログラムは、次のようになります。

#include <iostream>

int main()
{
    int value {10};

    value += 20;    // value = value + 20; と同じ
    std::cout << value << "\n";

    value -= 10;    // value = value - 10; と同じ
    std::cout << value << "\n";

    value *= 3;    // value = value * 3; と同じ
    std::cout << value << "\n";

    value /= 2;    // value = value / 2; と同じ
    std::cout << value << "\n";

    value %= 4;    // value = value % 4; と同じ
    std::cout << value << "\n";
}

実行結果:

30
20
60
30
2

/= と %= については、除算と剰余の計算をしていることになるので、ゼロ除算の可能性があります。右辺を評価した結果が 0 にならないように注意してください(「乗算と除算」のページを参照)。

value *= value2 + 1; のような、右側が複雑な記述も可能です。この場合、value = value * (value2 + 1); と同じになります。() の存在に注意してください。この例の場合は、複合代入演算子を使うとかえって分かりづらく見えるかもしれませんが、その感覚も正しいと思います。短く書かれたプログラムが、必ずしも優れているわけではありません。

代入を使ったプログラム

3つの整数を入力させて、その合計を出力するプログラムを考えてみます。これまでのページでのやり方にならうと、3回分の入力をそれぞれ、変数value1、value2、value3 に受け取り、出力するときに合計を計算するでしょう。

#include <iostream>

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

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

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

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

実行結果:

Please enter the integer.
10    <-- 入力した整数
Please enter the integer.
20    <-- 入力した整数
Please enter the integer.
30    <-- 入力した整数
total: 60

これは目的をきちんと果たせており、別に問題があるわけではありませんが、代入を知った今では、ほかの書き方もできます。まず、合計値をあらわす変数を追加して、そこに結果を代入することができます。

#include <iostream>

int main()
{
    int total {0};

    std::cout << "Please enter the integer.\n";
    int value1 {};
    std::cin >> value1;
    total += value1;

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

    std::cout << "Please enter the integer.\n";
    int value3 {};
    std::cin >> value3;
    total += value3;

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

実行結果:

Please enter the integer.
10    <-- 入力した整数
Please enter the integer.
20    <-- 入力した整数
Please enter the integer.
30    <-- 入力した整数
total: 60

入力が行われるたびに、その値を変数total に加算していけば、変数total の中身はつねに現時点までの合計値になります。なお、合計を求める際には、初期値として 0 を入れておくことが重要です。最初が 0 でないところに足し合わせていったら、合計は当然おかしな値になります。

もちろん、int total {}; でも初期値は 0 になりますが、意図を明確にするためにあえて {0} と書くことは良い考えです。コメントでなくコードで示していることも良い習慣といえます。

入力のたびに合計値を計算してしまうのであれば、value1、value2、value3 を分けておく必要もなくなります。

#include <iostream>

int main()
{
    int total {0};
    int input_value {};

    std::cout << "Please enter the integer.\n";
    std::cin >> input_value;
    total += input_value;

    std::cout << "Please enter the integer.\n";
    std::cin >> input_value;
    total += input_value;

    std::cout << "Please enter the integer.\n";
    std::cin >> input_value;
    total += input_value;

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

実行結果:

Please enter the integer.
10    <-- 入力した整数
Please enter the integer.
20    <-- 入力した整数
Please enter the integer.
30    <-- 入力した整数
total: 60

input_value という変数に変更し、入力はすべてここに受け取るようにしました。変数の値が変更可能であるからこそできることです。


このように、変数と代入が使えるようになると、同じ結果になるプログラムを色々な方法で書けます。そうなると、どういう書き方が正解なのかが気になるかもしれませんが、明確にどれが良い・悪いとは言い切れないことが多いです。今回の例もそうです。

基本的には、代入は少ないほうがプログラムは分かりやすいといえます。どのタイミングでどのように値が変化するのかを理解・把握する労力がかかるからです。代入は少ないほうがよく、初期化だけで済めばなおよく、constexpr変数で済ませられればさらによいです。

【上級】変数への代入を禁止する const というキーワードがあります。const int n = 10; のように宣言すると、n = 20; のように後から代入するコードを書くとエラーにでき、初期化したときの値のまま変更されないことが明確になります。

ここまでの書き換えを行った結果、まったく同じ3つの文が3回登場するかたちのコードになったことは注目すべきです。ここまでの知識ではできませんが、まったく同じコードが複数あらわれる場合、それらを1つにまとめて書くことが可能です。つまり「ここからここまでのコードを、〇回だけ繰り返す」というふうに記述する方法があります。ソースコードを書き換えた結果、さらに小さく簡潔にまとめられるようになる余地がうまれたわけです。

入力する整数の個数を変えることも容易になっています。「〇回だけ繰り返す」の〇の部分を変えるだけで済むからです。

まとめ


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


参考リンク


練習問題

問題の難易度について。

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

問題1 (確認★)

次のプログラムの実行結果はどうなりますか?

#include <iostream>

int main()
{
    int value {10};
    int value2 {-10};

    value = value2;
    std::cout << value << "," << value2 << "\n";

    value2 = value + 5;
    std::cout << value << "," << value2 << "\n";

    value = value2 = 100;
    std::cout << value << "," << value2 << "\n";
}

解答・解説

問題2 (確認★)

次のプログラムにある3つの代入がコンパイルできない理由を説明してください。

#include <iostream>

int main()
{
    constexpr auto a = 100;

    a = 200;    // 代入1
    100 = 200;  // 代入2
    b = 200;    // 代入3

    int b {};

    std::cout << a << "\n";
    std::cout << b << "\n";
}

解答・解説

問題3 (確認★)

代入を使ったプログラム」のサンプルプログラムで、変数total を追加して、入力された整数の合計を求めるとき、

int value1 {};
std::cin >> value1;
total += value1;

というかたちのコードになっています。変数total を std::cin に直接指定しないのはなぜか説明してください。

解答・解説

問題4 (基本★★)

次のプログラムの問題点を指摘してください。

#include <iostream>

int main()
{
    int value {10};
    int value2 {};

    value2 *= 2;
    value /= value2;
    std::cout << value << "\n";
}

解答・解説

問題5 (基本★★)

整数を1つ入力させ、その数を2倍した数、さらに2倍した数、それをさらに2倍した数・・・を順々に出力するプログラムを作成してください(適当なところで止めてください)。

解答・解説

問題6 (応用★★★)

2つの int型の変数 a, b があるとして、互いに値を入れ替えるプログラムを書いてください。

解答・解説

問題7 (応用★★★)

問題6のプログラムは、変数の型が std::string であったとしたらどうなりますか?

解答・解説


解答・解説ページの先頭



更新履歴




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