コンテンツにスキップ

C++ の基礎 Day 3

1. 変数宣言におけるプレースホルダー型

  • 変数宣言時、auto(オート)キーワードを使うと、変数の型の記述を省略できる。
  • auto は変数の初期化子から型を推論する。初期化子がない場合はコンパイルエラーになる。
  • プレースホルダー型は必ずコンパイル時に型を決定する。

#include <print>

double GetPi()
{
	return 3.14;
}

int main()
{
	auto a = 10;			// int 型
	auto b = GetPi();		// double 型
	auto c = "Hello, C++";	// const char* 型
	//auto d;				// コンパイルエラー:初期化子がない

	std::println("a = {}", a);
	std::println("b = {}", b);
	std::println("c = {}", c);
}
出力
a = 10
b = 3.14
c = Hello, C++

  • 基本型(intdouble など)を記述すればよい状況での auto の使用は、可読性を損なう可能性があるため、積極的には推奨されない。通常はイテレータやラムダ式など、型名が長くて複雑な場合に使う。

auto の使用が適している例
#include <print>
#include <vector>

int main()
{
	std::vector<int> v = { 1, 2, 3, 4 };

	// auto を使わない場合
	for (std::vector<int>::iterator it = v.begin(); it != v.end(); ++it)
	{
		std::println("{}", *it);
	}

	std::println("----------------");

	// auto を使った場合
	for (auto it = v.begin(); it != v.end(); ++it)
	{
		std::println("{}", *it);
	}
}
出力
1
2
3
4
----------------
1
2
3
4

  • auto には const(コンスト)やポインタ、参照修飾子を付けることができる。

#include <print>

int main()
{
	const auto a = 10;	// const int 型
	auto b = a;			// int 型
	auto* c = &a;		// const int* 型
	auto& d = a;		// const int& 型

	std::println("a = {}", a);
	std::println("b = {}", b);
	std::println("*c = {}", *c);
	std::println("d = {}", d);
}
出力
a = 10
b = 10
*c = 10
d = 10

2. 型の別名宣言

  • 型に別名を付けることができる。typedef(タイプデフ)を使う方法と、using(ユージング)を使う方法があるが、C++11 以降は using を使うことが推奨される。
  • 新しい型を導入するわけではなく、既存の型に別名を付けるだけである。

typedef

  • unsigned int 型に uint という別名を付ける例。

#include <print>

typedef unsigned int uint;

int main()
{
	uint a = 10;

	std::println("a = {}", a);
}
出力
a = 10

using

  • unsigned int 型に uint という別名を付ける例。

#include <print>

using uint = unsigned int;

int main()
{
	uint a = 10;

	std::println("a = {}", a);
}
出力
a = 10

  • using はエイリアステンプレートの作成にも使える点が typedef よりも優れている。
  • std::vector<T> 型に Array<T> というエイリアステンプレートを作成する例。
#include <vector>

template <typename T> using Array = std::vector<T>;

int main()
{
	Array<int> a = { 1, 2, 3, 4 };
	Array<double> b = { 1.1, 2.2, 3.3, 4.4 };
	Array<char> c = { 'a', 'b', 'c', 'd' };
}

3. if(イフ)文

if

  • if (条件式) true の場合の処理
  • 処理が複数行の場合は { } で囲んだ複合文を使う。
  • 1 行のときでも { } を使えば、行追加時のコード差分がシンプルになる。

#include <print>

int main()
{
	int n = 11;

	if (0 < n)
	{
		std::println("n は正の数です");
	}
}
出力
n は正の数です

  • 条件式が bool 型以外の場合、bool 型として評価される。
  • 具体的には、0false として扱われ、0 以外は true として扱われる。

#include <print>

int main()
{
	int n = 11;

	if (n % 2)
	{
		std::println("n は奇数です");
	}
}
出力
n は奇数です

else(エルス)節

  • 条件式が false の場合の処理も記述したい場合は、else 節を使う。

#include <print>

int main()
{
	int n = 10;

	if (n % 2)
	{
		std::println("n は奇数です");
	}
	else
	{
		std::println("n は偶数です");
	}
}
出力
n は偶数です

  • else ifelse 節に if 文を続けたもの。「else if」という 1 つの構文ではない。
#include <print>

int main()
{
	int n = 10;

	if (n < 0)
	{
		std::println("n は負の数です");
	}
	else if (n == 0)
	{
		std::println("n は 0 です");
	}
	else
	{
		std::println("n は正の数です");
	}
}
#include <print>

int main()
{
	int n = 10;

	if (n < 0)
	{
		std::println("n は負の数です");
	}
	else
		if (n == 0)
		{
			std::println("n は 0 です");
		}
		else
		{
			std::println("n は正の数です");
		}
}

条件式での初期化

  • 条件式には初期化も記述できる。

#include <print>

int Add(int a, int b)
{
	return a + b;
}

int main()
{
	int a = -2, b = 2;

	if (int sum = Add(a, b))
	{
		std::println("a と b の和は 0 以外");
		std::println("sum = {}", sum);
	}
	else
	{
		std::println("a と b の和は 0");
		std::println("sum = {}", sum);
	}

	// ここでは sum は使えない
}
出力
a と b の和は 0
sum = 0

初期化式と条件式を両方記述

  • 初期化式と条件式を両方記述することもできる。
  • if (初期化式; 条件式) true の場合の処理

#include <print>

int GetResult()
{
	return -3;
}

int main()
{
	if (int result = GetResult(); result < 0)
	{
		std::println("result は負の数です");
		std::println("result = {}", result);
	}
	else
	{
		std::println("result は 0 以上です");
		std::println("result = {}", result);
	}

	// ここでは result は使えない
}
出力
result は負の数です
result = -3

4. switch(スイッチ)文

case(ケース)ラベルと default(デフォルト)ラベル

  • ある条件式の値によって処理を分岐させる場合に switch 文を使う。条件式の結果は整数型である必要があり、浮動小数点数や文字列であることはできない。
  • case は定数式でなければならず、実行時に値が変わる変数や関数の戻り値は使えない。
  • case の処理の最後に break(ブレーク)がない場合、次の case の処理内容に進む。
  • default: ラベルとその場合の処理は省略可能。
switch (条件式)
{
case 定数式1:
	条件式が定数式1の場合の処理
	break;
case 定数式2:
	条件式が定数式2の場合の処理
	break;

...

default:
	条件式がどの case にも合致しない場合の処理
	break;
}

#include <iostream>
#include <print>

int main()
{
	std::println("フランスの首都は?");
	std::println("1. パリ 2. ロンドン 3. ローマ 4. マドリード");

	int n;
	std::cin >> n;

	switch (n)
	{
	case 1:
		std::println("正解!");
		break;
	case 2:
		std::println("ロンドンはイギリスの首都です");
		break;
	case 3:
		std::println("ローマはイタリアの首都です");
		break;
	case 4:
		std::println("マドリードはスペインの首都です");
		break;
	default:
		std::println("1 から 4 の数字を入力してください");
		break;
	}
}
入力例
1
出力例
フランスの首都は?
1. パリ 2. ロンドン 3. ローマ 4. マドリード
正解!

5. while(ホワイル)文

  • 条件式が true の間、処理を繰り返す。
  • while (条件式) 処理

#include <print>

int main()
{
	int enemyHP = 50;

	while (0 < enemyHP)
	{
		std::println("enemyHP = {}", enemyHP);
		enemyHP -= 10;
	}

	std::println("敵を倒した!");
}
出力
enemyHP = 50
enemyHP = 40
enemyHP = 30
enemyHP = 20
enemyHP = 10
敵を倒した!

6. do-while(ドゥー・ホワイル)文

  • do ブロック内の処理を 1 回実行し、その後条件式が true の間、処理を繰り返す。
  • do { 処理 } while (条件式)
  • C++ では、順列を列挙する標準ライブラリ機能 std::ranges::next_permutation()(ネクストパーミュテーション)(を使う際に役立つ。
    • next_permutation() で次の順列を作成する前に、最初の順列を出力しないといけないため。

#include <print>
#include <vector>
#include <algorithm>

int main()
{
	std::vector<int> v = { 1, 2, 3 };

	do
	{
		for (int i : v)
		{
			std::print("{} ", i);
		}

		std::println("");

	} while (std::ranges::next_permutation(v).found);
}
出力
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

7. for(フォー)文

  • 初期化式、条件式、更新式をもとループを作る。
  • for (初期化式; 条件式; 更新式) 処理
  • いずれも省略可能。

#include <print>

int main()
{
	for (int i = 0; i < 5; ++i)
	{
		std::println("i = {}", i);
	}

	std::println("----------------");

	for (int i = 5; 0 <= i; --i)
	{
		std::println("i = {}", i);
	}
}
出力
i = 0
i = 1
i = 2
i = 3
i = 4
----------------
i = 5
i = 4
i = 3
i = 2
i = 1
i = 0

  • for(;;) は無限ループを作る。

#include <print>

int main()
{
	for (;;)
	{
		std::println("無限ループ");
	}
}
出力
無限ループ
無限ループ
無限ループ
...

  • 範囲 for 文については次のページで説明する。

8. break 文

  • break を使うと、以下の文において処理を中断し、文の外に抜け出す。
    • switch
    • while
    • do-while
    • for
    • 範囲 for

#include <print>

int main()
{
	for (int i = 0; i < 10; ++i)
	{
		if (i == 5)
		{
			break;
		}

		std::println("i = {}", i);
	}
}
出力
i = 0
i = 1
i = 2
i = 3
i = 4

  • これらの文がネストしていたとしても、一番内側の文から抜けるだけ。

#include <print>

int main()
{
	for (int a = 0; a < 3; ++a)
	{
		for (int b = 0; b < 3; ++b)
		{
			if (b == 1)
			{
				break;
			}

			std::println("a = {}, b = {}", a, b);
		}
	}
}
出力
a = 0, b = 0
a = 1, b = 0
a = 2, b = 0

  • 深いネストから抜けたい場合、その処理を関数化して return(リターン)を使うのが一般的。

#include <print>

void Loop()
{
	for (int a = 0; a < 3; ++a)
	{
		for (int b = 0; b < 3; ++b)
		{
			if (b == 1)
			{
				return; // ここで関数から抜ける
			}

			std::println("a = {}, b = {}", a, b);
		}
	}
}

int main()
{
	Loop();
}
出力
a = 0, b = 0

9. continue(コンティニュー)文

  • continue を使うと、以下の文において処理を中断し、次の繰り返しに移る。
    • while
    • do-while
    • for
    • 範囲 for

#include <print>

int main()
{
	for (int i = 0; i < 5; ++i)
	{
		if (i == 2)
		{
			continue;
		}

		std::println("i = {}", i);
	}
}
出力
i = 0
i = 1
i = 3
i = 4

  • break と同様に、ネストしていても一番内側の文から次の繰り返しに移るだけ。

#include <print>

int main()
{
	for (int a = 0; a < 3; ++a)
	{
		for (int b = 0; b < 3; ++b)
		{
			if (b == 1)
			{
				continue;
			}

			std::println("a = {}, b = {}", a, b);
		}
	}
}
出力
a = 0, b = 0
a = 0, b = 2
a = 1, b = 0
a = 1, b = 2
a = 2, b = 0
a = 2, b = 2

力試し

Day 3 までの知識で解ける練習問題。最後に解答例あり。

ある回の全国自治宝くじ の購入者が持っているくじの番号が次の形式で標準入力から与えられる。当せん金額の合計を出力し、最後に改行を出力せよ。

組と番号がともに -1 のとき、入力の終わりとする。

入力の形式
unit number
unit number
unit number
...
-1 -1
入力データ 意味
unit 組(1 以上 100 以下)
number 番号(100000 以上 199999 以下)

終了を表す -1 -1 をのぞき、不正な組と番号は入力されない。あるくじと別のくじの組と番号がともに一致する重複は存在しないものとする。

当せん番号と当せん金額は次の通りである。

等級 当せん金額 番号
1 等 5 億円 15 組 118158 番
1 等の前後賞 1 億円 15 組 1 等の前後の番号
1 等の組違い賞 10 万円 1 等の組違い同番号
2 等 5 万円 各組共通 178805 番
3 等 1 万円 各組共通 下 3 ケタ 115 番
4 等 3000 円 各組共通 下 2 ケタ 87 番
5 等 300 円 各組共通 下 1 ケタ 3 番
  • 当選金額の合計は int 型で計算できるものとする。
  • 今回の課題では考慮する必要はないが、1 枚のくじが複数の等に重複当せんすることもある。1 等が 199999 の場合の前後賞は 199998100000 に、100000 の前後賞は 199999100001 になる。

入出力例

例 1

入力例 1
10 100080
10 100081
10 100082
10 100083
10 100084
10 100085
10 100086
10 100087
-1 -1
出力例 1
3300

4 等に 1 枚、5 等に 1 枚当せんしています。

例 2

入力例 2
28 105335
30 174785
50 115405
43 190963
6 124205
16 118159
62 156422
37 108039
42 166216
36 137208
34 105202
14 128345
97 181122
10 164570
24 197098
2 135785
45 120764
83 176129
54 127683
15 118156
96 191561
43 119596
-1 -1
出力例 2
600

5 等に 2 枚当せんしている。

例 3

入力例 3
15 118150
15 118151
15 118152
15 118153
15 118154
15 118155
15 118156
15 118157
15 118158
15 118159
-1 -1
出力例 3
700000300

1 等に 1 枚、1 等の前後賞に 2 枚、5 等に 1 枚当せんしている。

例 4

入力例 4
28 158992
83 139997
4 136750
95 134740
81 189115
76 172751
32 194607
80 167023
72 110224
2 198456
60 193678
59 199640
33 181566
20 183732
16 178832
31 116697
98 176288
93 129685
20 173981
10 177150
16 118158
80 178805
-1 -1
出力例 4
160300

1 等の組違い賞に 1 枚、2 等に 1 枚、3 等に 1 枚、5 等に 1 枚当せんしている。

例 5

入力例 5
76 173499
12 140588
78 116931
41 127299
59 113676
37 198932
53 170635
2 127206
15 161856
60 167138
67 152867
30 101820
89 155875
20 146517
1 171225
6 190909
15 117100
39 159785
-1 -1
出力例 5
0

当せんしていない。

解答例
#include <iostream>
#include <print>

int main()
{
	// 当せん金額の合計を記録する変数
	int sum = 0;

	for (;;)
	{
		// 組と番号を受け取る
		int unit, number;
		std::cin >> unit >> number;

		// -1 組 -1 番が与えられたら
		if ((unit == -1) && (number == -1))
		{
			// ループから抜ける
			break;
		}

		// 1 等(当せん金 5 億円)
		if ((unit == 15) && (number == 118158))
		{
			sum += 500000000;
		}	

		// 1 等の前後賞(当せん金 1 億円)
		if ((unit == 15) && ((number == 118157) || (number == 118159)))
		{
			sum += 100000000;
		}

		// 1 等の組違い賞(当せん金 10 万円)
		if ((unit != 15) && (number == 118158))
		{
			sum += 100000;
		}

		// 2 等(当せん金 5 万円)
		if (number == 178805)
		{
			sum += 50000;
		}

		// 3 等(当せん金 1 万円)
		if ((number % 1000) == 115)
		{
			sum += 10000;
		}

		// 4 等(当せん金 3000 円)
		if ((number % 100) == 87)
		{
			sum += 3000;
		}

		// 5 等(当せん金 300 円)
		if ((number % 10) == 3)
		{
			sum += 300;
		}
	}

	// 合計を出力する
	std::println("{}", sum);
}