long long 型 (C++11)
C++11 では、 新た な 整数型 として long long 型 が 追加 さ れ て い ます。 C 言語 では 1999 年 の C 99 規格 の 時点 で 追加 さ れ て いる 型 なので、 古い コンパイラ でも 対応 し て いる 可能性 が あり ます。
int main() {
long long n1 = 0LL;
long long int n2 = 0LL; // 「int」 は あっても 無くても 同じ
signed long long n3 = 0LL; // 「signed」 は あっても 無くても 同じ
unsigned long long n4 = 0ULL;
}long long 型 の 整数 リテラル を 表す サフィックス は「 ll」 または「 LL」 です。 unsigned long long 型 の 場合 は「 ull」 または「 ULL」 になり ます。 long long 型 の サイズ は 64 ビット 以上 で ある こと が 保証 さ れ て い ます。
#include < iostream >
#include < limits >
int main() {
std::cout << std::numeric_ limits < long long >::min() << std::endl;
std::cout << std::numeric_ limits < long long >::max() << std::endl;
std::cout << std::numeric_ limits < unsigned long long >::min() << std::endl;
std::cout << std::numeric_ limits < unsigned long long >::max() << std::endl;
}実行結果:
-9223372036854775808
9223372036854775807
0
18446744073709551615
using による型の別名定義 (C++11)
従来 から、 型 に 別名 を 与える 際 には、 typedef を 用い て き まし た。 C++11 からは、 同様 の こと を using キーワード を 用い て 行う こと が でき ます。 using を 使う 場合 の 構文 は、 以下 の よう になり ます。
before (C ++ 98/ 03)
#include < list >
typedef unsigned int u32_t;
typedef std::list<int>::iterator lst_it;
typedef const char* (*GetTextFunc_t)( int);
int main() {}after (C++11/ 14/ 17)
#include < list >
using u32_t = unsigned int;
using lst_it = std::list<int>::iterator;
using GetTextFunc_t = const char*(*)(int);typedef とは 逆 に、 まず 新しい 型 の 名前 を 書き、「=」 で 区切っ て、 既存 の 型名 を 書き ます。 区切り が 明確 です し、 変数 を 宣言 し て 初期 値 を 与える 際 の 形 と 同じ 形 を し て い て、 意味 も 分かり やすく なり ます。 例えば、 1 個 目 の 型 定義 は、「 u32_t という 名前 の 型 を 宣言 し、 それ は unsigned int の 別名 で ある」 という よう に 簡単 に 読み 下せ ます。 関数 ポインタ 型 の 別名 定義 も、 他 と 同じ 形 で 書ける よう に なっ て おり、 読み やすく なっ て い ます。
範囲for文 (C++11、 C++17)
C++11 では、 従来 の for 文 に 加え て、 範囲 for 文 という 記法 が 使える よう になり まし た。 範囲 for 文 は、 配列 や コンテナ の よう な 要素 の 集まり を、 先頭 から 末尾 に 向かっ て 列挙 し て いく 際 に 使え ます。 構文 は 次 の よう になり ます。
for (型 名前 : 要素の集まり) 処理
繰り返し の たび に 要素 が 1つ 取り出さ れ て、「 型 名前」 の 部分 に 記述 し た( 宣言 し た) 変数 に 代入 さ れ ます。 これ は 要素 の コピー なので、 ループ の 中 で、 元 の 要素 を 書き換え たい 場合 は、 参照 で 受け取る よう に し ます。
before (C++98/ 03)
#include < iostream >
#include < list >
int main() {
int array[5] = {0, 1, 2, 3, 4};
std:: list < int > lst( array, array + 5);
for (int i = 0; i < 5; ++ i) {
array[i] += 10;
}
for (std:: list < int >:: iterator it = lst.begin(); it != lst.end(); ++ it) {
*it += 10;
}
for (int i = 0; i < 5; ++ i) {
std:: cout << array[i] << "\n";
}
for (std:: list < int >:: const_iterator it = lst.begin(); it != lst.end(); ++ it) {
std:: cout << *it << "\n";
}
}実行結果:10 11 12 13 14 10 11 12 13 14
after (C++11/ 14/ 17) 範囲 for 文を使う場合、参照を用いて書きます。
#include < iostream >
#include < list >
int main() {
int array[5] = {0, 1, 2, 3, 4};
std:: list < int > lst( array, array + 5);
for (auto& n : array) {
n += 10;
}
for (auto& n : lst) {
n += 10;
}
for (auto n : array) {
std:: cout << n << "\n";
}
for (auto n : lst) {
std:: cout << n << "\n";
}
}実行結果:10 11 12 13 14 10 11 12 13 14
コード量が大幅に削減されることが分かります。 簡潔に書けることで楽ができますし、間違いが入り込む余地も減ります。また、添字やイテレータがなくなり、配列とコンテナの違いも消え去りました。配列の要素数を書かなくなったのも、コードの重複が減り、間違いも減らせるので良いポイントです。
名前空間の入れ子構文 (C++17)
C++17 から、名前空間の中にある名前空間を定義するとき、これまでよりも簡潔な記述が可能になりました。例えば、Aという名前空間の中に、Bという名前空間があるとすると、次のように記述できます。
namespace A::B { }これは以下と同等です。
namespace A {
namespace B {
}
}if、switchの条件式と初期化の分離 (C++17)
C++17 から、if文とswitch文の ( ) の中に初期化文を置くことができるようになりまし た。
int getResult() {
return 1;
}
int main() {
if (auto result = getResult(); result > 0) {}
switch (auto result = getResult(); result) {
case -1: break;
case 0: break;
default: break;
}
}before (C++98/ 03/ 11/ 14)
変数 の スコープ を 小さく 保つ ため、 無駄 な { } を 補う 方法 を 使う こと が あり まし た。
enum Status { // C ++ 11 以降 なら Scoped Enum にすべし
STATUS_ OK,
STATUS_ ERROR,
};
Status getStatus() {
return STATUS_ OK;
}
int main() {
{
const Status status = getStatus();
if (status == STATUS_ ERROR) {}
}
{
const Status status = getStatus();
switch (status) {
case STATUS_ OK: break;
case STATUS_ ERROR: break;
}
}
}余分な {} を補うのは、面倒であるばかりか、忘れやすくもあります。余計なネストが発生する点でも、あまり気持ちの良いものではありませんでし た。
after (C++17)
enum class Status { // Scoped Enum
OK,
ERROR,
};
Status getStatus() {
return Status::OK;
}
int main() {
if (const auto status = getStatus(); status == Status::ERROR) {}
switch (const auto status = getStatus(); status) {
case Status::OK: break;
case Status:: ERROR: break;
}
}構造化束縛 (C++17)
C++17 で 追加 さ れ た 構造 化 束縛 は、 配列 や 構造 体 の よう に、 複数 の 値 を 持っ た 値( 多値) を、 1つ 1つ に 分解 し て、 別個 の 変数 で 受け取る 機能 です。 変数 宣言 と、 多値 の 分解 を 同時に同時に 行える 機能 で ある と 考え られ ます。 構造 化 束縛 は auto [変数 名, …] = 多値 という 形 で 記述 し ます。 これ は、 変数 宣言 の 文法 の 一種 です。 変数 を 宣言 し つつ、 初期 値 を 与え て いる の だ と 理解 し て 下さい。 多値 を 分解 し た 値 が、 変数 の 初期 値 として 使用 さ れ ます。 変数 の 型 は 常に「 auto」 を 指定 し ます。 多 値 側 の 値 の 型 が、 宣言 さ れ た 各 変数 の 型 となり ます。 この とき の 型 の 決定 方法 は、 auto と いう よりも、 むしろ decltype の ルール と 合致 し ます。 つまり、 多 値 側 の 型 が 参照 型 で あれ ば、 宣言 する 変数 の 型 は きちんと 参照 型 になり ます。 多値 に 含ま れる 値 の 個数 と、 宣言 側 の 変数 の 個数 は 一致 し て い なけれ ば なり ませ ん。
配列を分解する
次のプログラムは、配列 array の各要素を分解して、別個の変数を初期化しています。
#include < iostream >
#include < string >
int main() {
const int array[] = {1, 2, 3};
auto [a, b, c] = array; // a、 b、 c はいずれも int
std::cout << a << "\n" << b << "\n" << c << std::endl;
}実行結果: 1 2 3
auto [a, b, c] = array; は、変数 a, b, c をそれぞれ宣言し、初期値として array[0]、array[1]、 array[2] の値を与えています。
構造体を分解する
次のプログラムは、構造体 Data の各メンバを分解して別個の変数を初期化してい ます。
#include < iostream >
#include < string >
int main() {
struct Data {
int x;
double y;
std::string z;
};
const Data data = {10, 3.5, "???"};
auto [x, y, z] = data; // x は int、 y は double、 z は std::string
std::cout << x << "\n" << y << "\n" << z << std::endl;
}実行結果:10 3. 5 ???
変数の型の修飾
構造化束縛の変数宣言型の型「 auto」 には、参照を表す「&」や、 const を付加することができます。これらの効果は、宣言するすべての変数に適用されます。
#include < iostream >
#include < string >
int main() {
struct Data {
int x;
double y;
const std:: string& z;
};
Data data = {10, 3. 5, "???"};
auto [x, y, z] = data; // x は int、 y は double、 z は const std::string&
auto& [rx, ry, rz] = data; // rx は int&、 ry は double&、 rz は const std::string&
data. x = 999;
std::cout << x << "\n" << y << "\n" << z << std::endl;
std::cout << rx << "\n" << ry << "\n" << rz << std::endl;
const auto [cx, cy, cz] = data;
// cx は const int、 cy は const double、 cz は const std::string&
// cy = 0. 0;
// cy は const double なので 書き換え られ ない
}実行結果: 10 3. 5 ??? 999 3. 5 ???
std::pair に対する構造化束縛
構造化束縛 は、std::pair の 要素 を 分解 する こと にも 使用 でき ます。
#include < iostream >
#include < utility >
int main() {
const auto x = std::make_ pair( 10, false); // x は std::pair < int, bool >
auto [value, flag] = x; // value は int、 flag は bool
std::cout << value << "\n" << flag << std::endl;
}実行結果: 10 0
std::pair は、std::map がキー と値を表現する方法としても使われていますから、std::map と構造 化束縛を組み合わせて使うこともあります。例えば、範囲 for文を使って、std::map の内容を走査する処理を、次のように記述できます。
#include < iostream >
#include < map >
#include < string >
int main() {
std::map < std::string, int > m = {
{"aaa", 10},
{"bbb", 20},
{"ccc", 30},
};
for (const auto& [key, value] : m) { // key は const std::string&、 value は const int&
std::cout << key << "," << value << std::endl;
}
}実行結果: aaa, 10 bbb, 20 ccc, 30
std::map の mを範囲 for 文 で使用すると、std::map の begin() と end() の 範囲 で 要素 が 走査 さ れ ます。 ループ の たび に 各 要素 を 変数 に 受け取り ます が、 std::map の 要素 は std::pair に なっ て いる ので、 構造 化 束縛 で 分解 する こと が でき ます。 構造 化 束縛 の 記法 を、 範囲 for 文 の 変数 宣言 の ところ に 書け ば 良い です( 前 に 述べ た 通り、 構造 化 束縛 は 変数 宣言 の 文法 の 一種 です)。


コメント