コンテンツにスキップ

第 5 回 | 2024-10-25 🛜 オンデマンド

第 5 回は 🛜 オンデマンド講義

講義ページを自分で読み進め、いつもどおり課題を提出してください。講義のライブ配信や動画はありません。教室は開室しています。講義日に Moodle で質問をすると、翌日までにスピード対応します。

  • さらに分岐する方法を学ぶ
  • 変数のスコープについて学ぶ

1. さらに分岐する

1.1 条件をさらに分ける

  • if - eles から条件をさらに分けたいときには else if () を使う。
  • 次のコードは、age が 18 未満のときは こども, 18 以上 65 未満のときは 大人, 65 以上のときは お年寄り と出力する。
  • 一連の if - eles if - else は上から順にチェックしていき、最初に条件が成立したときの { } だけが実行される。

📙 Unit 21
#include <iostream>

int main()
{
	int age;
	std::cin >> age;

	if (age < 18)
	{
		std::cout << "こども\n";
	}
	else if (age < 65)
	{
		std::cout << "大人\n";
	}
	else
	{
		std::cout << "お年寄り\n";
	}
}
入力例
80
出力
お年寄り

  • else ifelse の前後には空白行を作らないほうが、一連の if - else が一つのまとまりであることがわかりやすい。

空白行を入れた場合(不適切)

#include <iostream>

int main()
{
	int age;
	std::cin >> age;

	if (age < 18)
	{
		std::cout << "こども\n";
	}

	else if (age < 65)
	{
		std::cout << "大人\n";
	}

	else		
	{
		std::cout << "お年寄り\n";
	}
}

1.2 条件をもっと分ける

  • else if () をさらに増やすことができる。
  • このプログラムは、age が 1 未満のときは 赤ちゃん, 1 以上 18 未満のときは こども, 18 以上 65 未満のときは 大人, 65 以上のときは お年寄り と出力する。

📙 Unit 21
#include <iostream>

int main()
{
	int age;
	std::cin >> age;

	if (age < 1)
	{
		std::cout << "赤ちゃん\n";
	}
	else if (age < 18)
	{
		std::cout << "子ども\n";
	}
	else if (age < 65)
	{
		std::cout << "大人\n";
	}
	else
	{
		std::cout << "お年寄り\n";
	}
}
入力例
0
出力
赤ちゃん

順番に気を付ける

一連の ifelse は上から順にチェックされる。そのため、次のような順番では、age が 0 のときに「赤ちゃん」ではなく「子ども」と出力されてしまう。

📙 条件の順番が不適切なコード
#include <iostream>

int main()
{
	int age;

	std::cin >> age;

	if (age < 18)
	{
		std::cout << "子ども\n";
	}
	else if (age < 1)
	{
		std::cout << "赤ちゃん\n";
	}
	if (age < 65)
	{
		std::cout << "大人\n";
	}
	else
	{
		std::cout << "お年寄り\n";
	}
}

2. 変数のスコープ

2.1 変数の生存期間

  • { } の内側で変数を作ると、その } が閉じたときに変数の有効期間(スコープ)が終わり、その変数は消える。
    • (第 4 回の 1.8 も参照)
  • 有効期間が終わった変数の名前は、別の新しい変数の名前として使うことができる。
  • スコープを意識することで、コード内の変数の名前が増えすぎることを防げる。
  • 次のコードでは n という名前の変数が 2 つ登場するが、それぞれ異なる変数である。

#include <iostream>

int main()
{
	{
		int n;
		std::cin >> n;
		std::cout << (n * 2) << "\n";
	}
	
	{
		int n;
		std::cin >> n;
		std::cout << (n * n) << "\n";
	}
}
入力例
5
3
出力
10
9

2.2 スコープを意識して同じ名前の変数を複数作る

  • スコープを意識することで、コードに登場する変数名の種類を抑制できる。その例を示す。
  • 次のコードは、n が 2 のときに 2 つの数の和を、3 のときに 3 つの数の和を出力する。
改善前
#include <iostream>

int main()
{
	int n;
	std::cin >> n;

	if (n == 2)
	{
		int a, b;
		std::cin >> a >> b;
		std::cout << (a + b) << "\n";
	}
	else if (n == 3)
	{
		int c, d, e;
		std::cin >> c >> d >> e;
		std::cout << (c + d + e) << "\n";
	}
}
  • 次のように変数名を書き換えることができる。
  • 使用するアルファベットの種類を減らし、やや見通しの良いコードになった。
改善後
#include <iostream>

int main()
{
	int n;
	std::cin >> n;

	if (n == 2)
	{
		int a, b;
		std::cin >> a >> b;
		std::cout << (a + b) << "\n";
	}
	else if (n == 3)
	{
		int a, b, c;
		std::cin >> a >> b >> c;
		std::cout << (a + b + c) << "\n";
	}
}

✅ 振り返りチェックリスト

  • さらに分岐する方法を学んだ
  • 変数のスコープについて学んだ

課題

📝 提出課題

6 人の学生が C++ でプログラムを作成しました。しかし、それぞれに何らかの問題があるようです。

  • ①~⑤ に対して問題点を指摘し、コードを修正してください(配点 100)
  • 可能であればプログラムに追加の機能や改善を加え、それを説明してください(加点 20)

(練習)A さん「最小の硬貨の枚数になるようにおつりを計算するプログラム」

#include <iostream>

int main()
{
	std::cout << "硬貨の枚数が最小になるようにおつりを計算します。\n";
	std::cout << "おつりの金額を入力してください:\n";

	int change;
	std::cin >> change;

	int coin500 = change / 500;
	int coin100 = change / 100;
	int coin50 = change / 50;
	int coin10 = change / 10;
	int coin5 = change / 5;
	int coin1 = change;

	std::cout << "500 円玉: " << coin500 << " 枚\n";
	std::cout << "100 円玉: " << coin100 << " 枚\n";
	std::cout << "50 円玉: " << coin50 << " 枚\n";
	std::cout << "10 円玉: " << coin10 << " 枚\n";
	std::cout << "5 円玉: " << coin5 << " 枚\n";
	std::cout << "1 円玉: " << coin1 << " 枚\n";
}
入力例
4
出力
500 円玉: 0 枚
100 円玉: 0 枚
50 円玉: 0 枚
10 円玉: 0 枚
5 円玉: 0 枚
1 円玉: 4 枚
解答例
【問題点】
各硬貨の枚数の計算に誤りがあり、その硬貨より大きい硬貨での支払いを無視している。
例えば 200 を入力したとき、100 円玉 2 枚のみになるべきだが、次のような不正な出力になる。

500 円玉: 0 枚
100 円玉: 2 枚
50 円玉: 4 枚
10 円玉: 20 枚
5 円玉: 40 枚
1 円玉: 200 枚

【修正方法】
修正するには、例えば 50 円玉の枚数は
(change % 100) / 50;
として、100 円までで払えなかった余りに対して計算するようにする。

【機能追加】
下記の 2 つの機能を追加した。
・0 枚の硬貨は出力しない。
・おつりがないときは「おつりはありません」と出力する。

【コード】
https://wandbox.org/permlink/0qgG4O2QyHYJx6tT

① B さん「クイズアプリだよ」

#include <iostream>

int main()
{
	std::cout << "アメリカの首都は?\n";
	std::cout << "1: ワシントン D.C.\n2: パリ\n3: ニューヨーク\n4: ロンドン\n";

	int answer;
	std::cin >> answer;

	if (answer = 1)
	{
		std::cout << "正解!\n";
	}
	else
	{
		std::cout << "不正解!\n";
	}
}
入力例
1
出力
正解!

② C さん「成績を出力するプログラムを作った」

#include <iostream>

int main()
{
	std::cout << "点数を入力してください:\n";

	int score;
	std::cin >> score;

	if (90 <= score <= 100)
	{
		std::cout << "A\n";
	}
	else if (80 <= score < 90)
	{
		std::cout << "B\n";
	}
	else if (70 <= score < 80)
	{
		std::cout << "C\n";
	}
	else if (60 <= score < 70)
	{
		std::cout << "D\n";
	}
	else
	{
		std::cout << "F\n";
	}
}
入力例
96
出力
A

③ D さん「時間を秒で入力すると、時間、分、秒に変換します」

#include <iostream>

int main()
{
	std::cout << "時間を秒で入力してください:\n";

	int time;
	std::cin >> time;

	int hour = (time / 3600);
	int minute = (time / 60);
	int second = (time % 60);

	std::cout << hour << "時間" << minute << "分" << second << "秒\n";
}
入力例
1234
出力
0時間20分34秒

④ E さん「元号を判定するプログラム」

#include <iostream>

int main()
{
	std::cout << "年月日を入力してください:\n";

	int year, month, day;
	std::cin >> year >> month >> day;

	if ((year < 1912) && (month < 7) && (day < 30))
	{
		std::cout << "大正以前\n";
	}
	else if ((year < 1926) && (month < 12) && (day < 25))
	{
		std::cout << "大正\n";
	}
	else if ((year < 1989) && (month < 1) && (day < 8))
	{
		std::cout << "昭和\n";
	}
	else if ((year < 2019) && (month < 4) && (day < 30))
	{
		std::cout << "平成\n";
	}
	else
	{
		std::cout << "令和\n";
	}
}
入力例
2024 10 25
出力
令和

元号の期間は下記の表に基づくこととします。

元号 開始年月日 終了年月日
大正 1912-07-30 1926-12-24
昭和 1926-12-25 1989-01-07
平成 1989-01-08 2019-04-30
令和 2019-05-01

⑤ F さん「ゲーム作ったけどバグってる」

乱数を生成する

このプログラムでは、今後の講義で扱う、乱数を生成する方法を先取りして使っています。次のコードを加えると、Random(a, b) で a 以上 b 以下の整数の乱数を生成できます。

#include <random> // 乱数用

// min 以上、max 以下の乱数を生成する関数
int Random(int min, int max)
{
	std::random_device rng;
	return std::uniform_int_distribution<int>{ min, max }(rng);
}
#include <iostream>
#include <random> // 乱数用

// min 以上、max 以下の乱数を生成する関数
int Random(int min, int max)
{
	std::random_device rng;
	return std::uniform_int_distribution<int>{ min, max }(rng);
}

int main()
{
	for (;;)
	{
		// 主人公の HP
		int playerHP = 200;

		// 敵の HP
		int enemyHP = 200;

		std::cout << "----------------------------\n";
		std::cout << "主人公\t HP: " << playerHP << "\n";
		std::cout << "敵\t HP: " << enemyHP << "\n";
		std::cout << "----------------------------\n";

		std::cout << "行動を選択してください\n";
		std::cout << "1: 攻撃 2: 回復 3: 逃げる\n";

		int action;
		std::cin >> action;

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

		if (action == 1) // 攻撃
		{
			std::cout << "主人公の攻撃!\n";
			
			// 敵に与えるダメージを 20 ~ 40 の範囲でランダムに決定
			int damage = Random(20, 40);

			std::cout << "敵に" << damage << "のダメージ!\n";
			
			enemyHP -= damage;
		}
		else if (action == 2) // 回復
		{
			std::cout << "主人公は 100 回復した!\n";

			playerHP += 100;
		}
		else if (action == 3) // 逃げる
		{
			std::cout << "うまく逃げ切れた!\n";
			break;
		}

		{
			std::cout << "敵の攻撃!\n";

			// 主人公に与えるダメージを 30 ~ 50 の範囲でランダムに決定
			int damage = Random(30, 50);
			
			std::cout << "主人公に" << damage << "のダメージ!\n";

			playerHP -= damage;
		}

		if (playerHP <= 0)
		{
			std::cout << "主人公は力尽きた...【敗北】\n";	
			break;
		}
		else if (enemyHP <= 0)
		{
			std::cout << "敵を倒した!【勝利】\n";			
			break;
		}
	}
}