C++ on MSVC講習/変数基礎

あらすじと概要

前回は、Hello World!プログラムを通じて、基本的なコードの決まりを解説しました。
また、標準出力やリテラルを使い、コンソールへ単純な文字列を出力できるようになりました。
今回は、実行時に値を保持できるようになり、入力が出来るようになります。

重要語

変数

名前に値を関連付けることで値を保存する機能

コードでの振る舞いを規定し、実行前にミスを検出する機能

組み込み型

C++に元から組み込まれている型

ユーザー定義型

ライブラリ等で定義を導入できる型

宣言

コンパイラに新たな識別子を使うことを伝える構文

識別子

変数などの名前

キーワード

C++規格で特別な意味を持たせられたトークン

初期化

宣言と同時に値を設定する文法

代入

宣言後に値を変更する方法

標準入力

標準で用意されている入力元

std::cin

プログラム実行時に標準入力に紐づけられるもの

符号化

データをコンピューターで表わせる形式に変換すること

文字コード

文字に割り当てる番号と、それの符号化形式を定めるもの

必要語

コンソール

文字のみでPCとやり取りするCUI形式の画面

演算子とオペランド(被演算子)の並び

演算子

+-など、何らかの演算を表す記号

オペランド

何らかの「値」

順番に実行されるC++プログラムの断片

トークン

コード上で意味を持つ最小の単位

標準出力

標準で用意されている出力先

std::cout

プログラム実行時に標準出力に紐づけられるもの

リテラル

コード上に直接書かれているデータ

C++の変数

変数って、何なんでしょうか。ひとまず解説するので一読してみてください。
一読したら、進んでみて、また分からなくなったら読んでみてくださいね。

リテラルだと不便なところ

前回のコードHello World!では、リテラルを使っていました。
それでは、同じ値でも複数の文で使用するには、その度に書かなくてはいけません。
他にも、実行時に値を決定したり、変更したりすることが出来ません。
前回のプログラムだと、表示する内容を変えるごとにコンパイルしないといけません。

値を保存すればいいのよ

それらは、実行時に値を保存・変更できる仕組みがあれば解決することが出来ます。
その仕組みが変数で、名前に値を関連付けることで値を保存することが出来るようになります。

C++の変数は、必ず型を持っていて、型によってコードでの振る舞いが制限されています。
そして、型による振る舞いの制限が守られているかコンパイル時にチェックされます。
それによって、プログラマーのミスを実行前に発見し、修正することが出来るのです。
ですから多くの場合、型には、意味論としての使用法や性質が名前として付けられます。

型の種類

ライブラリ等が必要ない、C++に元から組み込まれている型を組み込み型と総称します。
一方、ライブラリ等で型の定義を導入できるものをユーザー定義型と総称します。
今回は組み込み型から、整数だけを扱えるintと、小数まで扱えるdoubleを、
ユーザー定義型からは、文字列を扱う為のstd::stringについて解説します。

整数を扱おう

まずは整数を扱ってみましょう。次のコードをコピペして実行しましょう。
整数
#include <iostream>

int main() {
    //符号あり(負数が使える)
    signed short int        a;
    signed int              b = 10;
    signed long int         c(20);
    signed long long int    d{ 40 };

    // 初期化されていない「a」は、値が不定なので使用してはいけない

    a = 1; // 「a」に1を代入

    // 代入された「a」は、代入された値になったので、使用してよい

    std::cout << "a : " << a << "\n"
        << "b : " << b << "\n"
        << "c : " << c << "\n"
        << "d : " << d << "\n\n";


    // 符号なし(負数が使えない)
    unsigned u_a = 1, u_b(10), u_c{ 20 }, u_d = { 40 };

    std::cout << "u_a : " << u_a << "\n"
        << "u_b : " << u_b << "\n"
        << "u_c : " << u_c << "\n"
        << "u_d : " << u_d << "\n";
}
実行結果
a : -1
b : -10
c : -20
d : -40
<
u_a : 1
u_b : 10
u_c : 20
u_d : 40

解説

さて、コードの解説に入っていきます。

宣言

変数は「名前に値を関連付けることで値を保存」出来ると説明しました。
そのためには、まずコンパイラに変数を使うことを教える必要があります。
それを宣言(文)といい、必要な所を単純化すると、以下のような構文になります。
宣言子では、識別子 初期化子(opt),区切りで複数書くことが出来ます。

追加される構文の記法

なにかしら(opt)

省略可能(例だと、「なにかしら」を省略可能)
変数宣言
型名 宣言子 ;
宣言子
識別子 初期化子(opt)
宣言子 , 識別子 初期化子(opt)

識別子

識別子は、変数の名前になるものです。以下に挙げるルールを守る必要があります。
ただし、MSVCは独自拡張として、あらゆる文字を使用することが出来ます。

識別子のルール

使える文字

基本ソース文字集合のアルファベット、数字、_アンダーバーなど

使えない単語

キーワードとなる単語

使ってはいけないアンダーバー1

識別子中に__ダブルアンダーバー(アンダーバーが2つ連続すること)

使ってはいけないアンダーバー2

識別子先頭に_と大文字のアルファベット

使ってはいけない場所があるアンダーバー

識別子先頭に_

キーワード

キーワードは、C++規格で特別な意味を持っているトークンのことです。
組み込み型の型名や修飾子等など(intdoubleなど)がこれにあたります。
なお、Visual Studioでは、キーワードは青く表示されます。
一覧はC++ のキーワード - cppreference.comを参照してください。

整数を扱う型

整数を扱う型は、組み込み型の1つであるintいんとです。
signedさいんど/unsignedあんさいんどで符号のあり/なし、即ち負の数が扱えない/るを、
shortしょーと/longろんぐ/long longろんぐろんぐで変数のサイズを指定することが出来ます。
サイズについては、次回に整数の内部表現を解説すると同時に解説します。

主な整数を扱う型と別名

short int

short/signed short/signed short int

int

signed/signed int

long int

long/signed long/signed long int

long long int

long long/signed long long/signed long long int

unsigned short int

unsigned short

unsigned int

unsigned

unsigned long int

unsigned long

unsigned long long int

unsigned long long

初期化

宣言すると同時に、値を設定しましょう。それが初期化です。
大雑把に説明すると、下3つは、値を省略すると0で初期化され、
特に、下2つは、値が変数で表せない値であるかのチェックがされます。
初期化子
= 値
( 値(opt) )
{ 値(opt) }
= { 値(opt) }

初期化してない組み込み型

初期化をしなかった組み込み型の変数は、その持つ値は不定になります。
この不定の値を持つ組み込み型の変数は、その値を使うことはしてはいけません。
その様な変数を使って良い状態になるのは、代入等で値を設定してからです。
コンパイルエラーは出ませんが、標準規格で未定義動作とされる状態になります。

未定義動作

未定義動作とは、C++の規格が処理系に対していかなる制約も課さない動作のことです。
未定義動作に陥ると、正真正銘何が起こるかわからず、ほとんどバグに繋がります。
特に、タイムトラベルしたり鼻から悪魔が出るので、起こしてはいけません。

代入

初期化したしないに関わらず、宣言後に値を変更するときは代入式を使用します。
左辺には変数、演算子は=、右辺には値(変数も大丈夫)を書きます。
すると、右辺の値を左辺に設定する働きをします。数学的なイコールとは違います。
代入は式なので、最後に;を付けて文にしないと存在できないことに注意しましょう。
構文は以下のようになります。2つ目の挙動は初期化のものとほぼ同じです。
代入
変数 = 値
変数 = { 値(opt) }

小数を扱おう

次は小数を扱ってみましょう。次のコードをコピペして実行しましょう。
小数
#include <iostream>
#include <iomanip>

int main() {
    float f;
    double d = .1;
    long double ld = 1234e-5;

    f = 2.345;

    std::cout << std::fixed << std::setprecision(10)
        << "f  : " << f << "\n"
        << "d  : " << d << "\n"
        << "ld : " << ld << "\n\n";
}
実行結果例
f  : 2.3450000286
d  : 0.1000000000
ld : 0.0123400000

実行結果例補足

代入値と異なる表示になる場合があります。
が、それは生じてしまう誤差なので気にしない方針にしてください。

解説

コードの解説をします。

概説

小数を扱える型は、floatふろーとdoubleだぶるlong doubleろんぐだぶるがあります。
これらは、左よりは右の方がより誤差無く表せる値の範囲が大きい、又は同様になります。
ただし、宣言と初期化、代入についての文法は、整数と同様なので飛ばします。
もちろん、整数と同様に、初期化をしないと不定な値を持ち、それを使用してはいけません。

小数リテラル

前回軽く取り上げた小数リテラルですが、指数表記も用いることが出来ます。
指数表記は、1.2e51.2×10**5=120000-1.2e-5-1.2×10**-5=-0.000012など、仮数×基数の指数乗で表現する表記法です。
普通1.2×10**51.2×10**5=120000などと基数を書きますが、C++ではEe10として用いて表記します。
また通常の表記法でも、整数部分や小数部分が0の場合、0を省略することも出来ます。
なお、**は冪乗のことを表すとします。例えば、10**5100000になります。

std::coutとマニピュレータ

std::cout、実は出力を少し変化させることが出来、その時に使うのがマニピュレータです。
マニピュレータを使う時には、main関数の前に#include <iomanip>と書きます。
小数を表示するときには、std::fixedstd::setprecisionが便利です。
先にstd::fixedを出力し、次にstd::setprecision(自然数)として出力します。
すると、小数点以下の出力桁数がstd::setprecisionの括弧の中に書いた値になります。

文字列を扱おう

ひとまず最後に文字列を扱いましょう。
文字列
#include <iostream>
#include <string>

int main() {
    std::string s1 = "namanegi",
        s2("namagoe"),
        s3{ "namahatsune" },
        s4 = { "mikudayo-" };

    std::cout << "s1 : " << s1 
        << "\ns2 : " << s2 
        << "\ns3 : " << s3 
        << "\ns4 : " << s4 << "\n";
}
実行結果
s1 : namanegi
s2 : namagoe
s3 : namahatsune
s4 : mikudayo-

解説

それでは解説です。

文字の表現

例によってコンピューターでは文字を直接表現することは出来ません。
なので、文字1つ1つに一意な番号をつけ、それを符号化することで表現しています。
また、文字とその番号、そしてそれの符号化形式を定めたものを文字コードと呼びます。
なお、符号化は、あるデータをコンピューターで表せるような形式にすることを指します。

文字列の表現

文字列は、当然文字の列ですから、変数で扱うときは1つの整数に収まる筈はありません。
沢山変数を並べた物を1つの変数として扱う必要があり、それを実現する機能が配列です。
文字列は配列を使って表現されますが、難しいので標準ライブラリを使いましょう。

<string>とstd::string

使う標準ライブラリはstringです。#include <string>をmain関数の前に書きましょう。
文字列を扱うために作られた、std::stringという型が使用できるようになります。
std::stirngは、ユーザー定義型ですから、型の定義をstringから導入しないといけません。

組み込み型と違う所

宣言と代入は組み込み型と同様ですが、初期化はユーザー定義型であるので、少し異なります。
丸括弧や波括弧を使った初期化の場合、,区切りの値の並びを取ることがあります。
ただし、これは型での定義次第なので、特になにも取らないときもあります。
詳しくは、ユーザー定義型の定義方法の1つ、クラスを扱うときに同時に扱います。

入力

さて、変数をやったので、実行時に値を決める手頃な手段の入力をしましょう。
標準出力の逆で、標準入力から値を取得しましょう。
入力
#include <iostream>
#include <string>

int main() {
    int i;
    double d;
    std::string s, t;

    std::cin >> i >> d >> s >> t;

    std::cout << "i : " << i
        << "\nd : " << d
        << "\nts: " << t << s;
}

入力例

今度は、コンソールに入力する必要があるので、入力も含めて実行結果例を書きます。
MSVCでは、標準では1つのコンソールに入出力をするので、混ざることがあります。
また、今後は入力にを先頭に追加して表記していきます。
実行結果例
$ 10 0.34 dayo- miku
i : 10
d : 0.34
ts: mikudayo-
実行結果例
$ 10
$ 0.34
$ dayo-
$ miku
i : 10
d : 0.34
ts: mikudayo-

解説

もう少し面白くなってきたと思います。

標準入力とstd::cinしーいん

標準出力の逆です。コンソールから数字や文字列を入力することが出来ます。
std::cinから入力する時に使う演算子も逆で、右シフト演算子である、>>を使用します。
変数stのように、文字列の場合は半角スペースや改行があると区切られて入力されます。

練習問題

今回はAPG4bという、AtCoder社のC++教材の問題も解いてみましょう。
AtCoder社は、プログラミングで問題を解くオンラインゲームを提供しています。
詳しくは、AtCoderとは - 競技プログラミング講習 - 駒場東邦物理部を参照してください。
AtCoderへの登録を済ませていた場合、AtCoderの問題ページ下から提出が出来ます。
GCCを選んで提出し、ACと出れば正解、CEならコンパイルエラー、WAなら不正解です。

その1-問題文

空白区切りの文字列S,Tの入力に対して、TSの順でくっつけて出力してください。
入力例は便宜的にまとめています。空行の上と下でそれぞれ入力、出力だと思って下さい。
入力
$ S T
入力例と出力例
$ oumu watashiha

watashihaoumu
回答例
回答例
#include <iostream>
#include <string>

int main() {
    std::string s;
    std::string t;

    std::cin >> s >> t;
    std::cout << t << s;
}

その2-問題文

2つの整数A,Bが以下のように与えられます。A+Bの結果と改行を出力してください。
出典:EX5 - A足すB問題
入力
$ A B
制約
0 <= A,B <= 100
入力例と出力例
$ 1 2

3
回答例
回答例
#include <iostream>

int main() {
    int a;
    int b;

    std::cin >> a >> b;

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

文字コード物語

文字を表すのは大変なんだ、という話をします。
この段落は私情が入った言い回しをしていますがお察しください。

ASCIIあすきー

まずほとんどの符号化形式の元となっているのが、ASCIIと呼ばれる文字コードです。
ASCIIでは、0-127の範囲に基本的な文字を割り当て、それを7bit以上の整数で符号化します。
実際の対応表は、ASCII - Wikipediaを確認してください。

悪夢の始まり

ASCIIの成立後、ASCIIが使用しない範囲に文字を割り当てた文字コードが乱立しました。
地域ごとに文字コードが存在し、文字コードを切り替えたりする必要があるのです。
これは、データの置き場たるメモリが、今のPC程潤沢でないことが理由していました。
日本語Windowsでは現在に至るまで、Shift-JISの亜種Windows-31Jが使われています。

悪夢の終わりかのように見えた

そうして乱立した文字コードの波に飲まれ、疲弊してしていたプログラマ達[要検証]。
そこに現れたのは、世界中の全ての文字を一つの文字コードにしようとしたUnicodeゆにこーど
Unicodeは元々、世界の文字を16bit65536文字に収めようとして作られ、様々模索されました。
しかし、世界の文字が16bitに収まる筈もなく、それらの模索は負債と化しました。

現在

さて、Unicodeが普及し、事無き事を得たように思えますが、そうでもありません。
現在のUnicodeでは、UTF-8、UTF-16、UTF-32という符号化形式が使われていますが、
日本語Windowsでは、未だにWindows-31Jが標準で、UTF-8のサポートはまだまだです。
C+においても、Unicode対応が杜撰であることは広く知られています。
互換性等様々な壁があることは確かなので、一概に言える話では無いのですが。

参照、出典

参照と出典です。

参照

オブジェクト - cppreference.com

https://ja.cppreference.com/w/cpp/language/object

型 - cppreference.com

https://ja.cppreference.com/w/cpp/language/type

組み込み型 (C++) | Microsoft Docs

https://docs.microsoft.com/ja-jp/cpp/cpp/fundamental-types-cpp?view=msvc-160

宣言 - cppreference.com

https://ja.cppreference.com/w/cpp/language/declarations

識別子 - cppreference.com

https://ja.cppreference.com/w/cpp/language/identifiers

C++ 識別子 | Microsoft Docs

https://docs.microsoft.com/ja-jp/cpp/cpp/identifiers-cpp?view=msvc-160

C++ のキーワード - cppreference.com

https://ja.cppreference.com/w/cpp/keyword

C++ キーワード | Microsoft Docs

https://docs.microsoft.com/ja-jp/cpp/cpp/keywords-cpp?view=msvc-160

初期化 - cppreference.com

https://ja.cppreference.com/w/cpp/language/initialization

代入演算子 - cppreference.com

https://ja.cppreference.com/w/cpp/language/operator_assignment

文字コード - Wikipedia

https://ja.wikipedia.org/wiki/%E6%96%87%E5%AD%97%E3%82%B3%E3%83%BC%E3%83%89

std::basic_string - cppreference.com

https://ja.cppreference.com/w/cpp/string/basic_string

std::cin, std::wcin - cppreference.com

https://ja.cppreference.com/w/cpp/io/cin

C++標準化委員会、ついに文字とは何かを理解する: char8_t - Qiita

https://qiita.com/yumetodo/items/54e1a8230dbf513ea85b

ASCII - Wikipedia

https://ja.wikipedia.org/wiki/ASCII

Unicode - Wikipedia

https://ja.wikipedia.org/wiki/Unicode

Shift_JIS - Wikipedia

https://ja.wikipedia.org/wiki/Shift_JIS

Microsoftコードページ932 - Wikipedia

https://ja.wikipedia.org/wiki/
Microsoft%E3%82%B3%E3%83%BC%E3%83%89%E3%83%9A%E3%83%BC%E3%82%B8932