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);
}
- 基本型(
int
やdouble
など)を記述すればよい状況での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);
}
}
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);
}
2. 型の別名宣言¶
- 型に別名を付けることができる。
typedef
(タイプデフ)を使う方法と、using
(ユージング)を使う方法があるが、C++11 以降はusing
を使うことが推奨される。 - 新しい型を導入するわけではなく、既存の型に別名を付けるだけである。
typedef¶
unsigned int
型にuint
という別名を付ける例。
using¶
unsigned int
型にuint
という別名を付ける例。
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 行のときでも
{ }
を使えば、行追加時のコード差分がシンプルになる。
- 条件式が
bool
型以外の場合、bool
型として評価される。 - 具体的には、
0
はfalse
として扱われ、0
以外はtrue
として扱われる。
else(エルス)節¶
- 条件式が
false
の場合の処理も記述したい場合は、else
節を使う。
#include <print>
int main()
{
int n = 10;
if (n % 2)
{
std::println("n は奇数です");
}
else
{
std::println("n は偶数です");
}
}
else if
はelse
節にif
文を続けたもの。「else if
」という 1 つの構文ではない。
条件式での初期化¶
- 条件式には初期化も記述できる。
#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 は使えない
}
初期化式と条件式を両方記述¶
- 初期化式と条件式を両方記述することもできる。
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 は使えない
}
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;
}
}
5. while(ホワイル)文¶
- 条件式が
true
の間、処理を繰り返す。 while (条件式) 処理
#include <print>
int main()
{
int enemyHP = 50;
while (0 < enemyHP)
{
std::println("enemyHP = {}", enemyHP);
enemyHP -= 10;
}
std::println("敵を倒した!");
}
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);
}
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);
}
}
for(;;)
は無限ループを作る。
- 範囲 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);
}
}
- これらの文がネストしていたとしても、一番内側の文から抜けるだけ。
#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);
}
}
}
- 深いネストから抜けたい場合、その処理を関数化して
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();
}
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);
}
}
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);
}
}
}
力試し¶
Day 3 までの知識で解ける練習問題。最後に解答例あり。
ある回の全国自治宝くじ の購入者が持っているくじの番号が次の形式で標準入力から与えられる。当せん金額の合計を出力し、最後に改行を出力せよ。
組と番号がともに -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
の場合の前後賞は199998
と100000
に、100000
の前後賞は199999
と100001
になる。
入出力例¶
例 1
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
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
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
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
当せんしていない。
解答例
#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);
}