コンテンツにスキップ

第 6 回 | 2024-11-08

  • 繰り返しの回数を指定する方法を学ぶ
  • 繰り返しを使って図形を描く方法を学ぶ
  • プログラムを一定時間スリープさせる方法を学ぶ

1. 繰り返しの回数を指定する

先週までの for (;;) は無限ループだったが、今回は繰り返しの回数を指定する方法を学ぶ。

1.1 決まった回数だけ繰り返す (1)

  • for での繰り返しの回数をコントロールするには、for に 3 つの設定を書く。
  • 3 つの設定は (最初の設定; 続ける条件; 毎回処理が終わったときにすること)

for における、繰り返しの流れ

  1. 「最初の設定」を行う。
  2. 「続ける条件」を確認する。条件を満たさなかったらそこで終了する。
  3. { } の中身を実行する。
  4. 「毎回処理が終わったときにすること」を行い、2.に戻る。
このコードでの for の動き
  • 「最初の設定」int i = 0; で int 型の変数 i を作る。値は 0。
  • 「続ける条件」を確認する。i < 3 について、i は 0 で条件を満たしているので続行する。
  • { } の中身を実行する。std::cout << i << "\n"; によって、現在の i の値 0 が画面に出力される。
  • 「毎回処理が終わったときにすること」の ++i を実行する。i は 0 から 1 になる。
  • 「続ける条件」を確認する。i < 3 について、i は 1 で条件を満たしているので続行する。
  • { } の中身を実行する。std::cout << i << "\n"; によって、現在の i の値 1 が画面に出力される。
  • 「毎回処理が終わったときにすること」の ++i を実行する。i は 1 から 2 になる。
  • 「続ける条件」を確認する。i < 3 について、i は 2 で条件を満たしているので続行する。
  • { } の中身を実行する。std::cout << i << "\n"; によって、現在の i の値 2 が画面に出力される。
  • 「毎回処理が終わったときにすること」の ++i を実行する。i は 2 から 3 になる。
  • 「続ける条件」を確認する。i < 3 について、現在の i は 3 なので条件を満たしていない。くりかえしは終了する。

📙 Unit 29
#include <iostream>

int main()
{
	for (int i = 0; i < 3; ++i)
	{
		std::cout << i << "\n";
	}
}
出力
0
1
2

  • ここでの変数 i のように、ループの制御用に使う変数を「ループ変数」という。

1.2 決まった回数だけ繰り返す (2)

  • 設定を工夫することで、カウントダウンするループも作れる。

#include <iostream>

int main()
{
	for (int i = 10; 0 <= i; --i)
	{
		std::cout << i << "\n";
	}
}
出力
10
9
8
7
6
5
4
3
2
1
0

1.3 「n 回繰り返す」の定石

  • ループを n 回実行したいときは、次のテンプレに沿って for を作成するのが一般的。
    • for (int i = 0; i < n; ++i) { ... }

3 回繰り返す
#include <iostream>

int main()
{
	for (int i = 0; i < 3; ++i)
	{
		std::cout << "Hello!\n";
	}
}
出力
Hello!
Hello!
Hello!


5 回繰り返す
#include <iostream>

int main()
{
	for (int i = 0; i < 5; ++i)
	{
		std::cout << "Hello!\n";
	}
}
出力
Hello!
Hello!
Hello!
Hello!
Hello!

1.4 n を入力する

  • 標準入力と組み合わせることで、実行時に n を指定できる。

#include <iostream>

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

	for (int i = 0; i < n; ++i)
	{
		std::cout << "Hello!\n";
	}
}
入力
5
出力
Hello!
Hello!
Hello!
Hello!
Hello!

1.5 ループの中身を実行しないこともある

  • 次のコードで 0 を入力すると、初回から for の「続ける条件」を満たさないため、{ } の中身を一度も実行せずに for を終了する。

#include <iostream>

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

	for (int i = 0; i < n; ++i)
	{
		std::cout << i << "\n";
	}
}
入力
0
出力

  • 次のコードの for では、i = start; が最初に設定されるが、start が最初から 3 以上であるときは、初回から「続ける条件」を満たさないため、{ } の中身を一度も実行せずに for を終了する。

#include <iostream>

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

	for (int i = start; i < 3; ++i)
	{
		std::cout << i << "\n";
	}
}
入力
5
出力

1.6 カウントダウン

  • 次のようなコードで、入力した数から 0 までをカウントダウンすることができる。
#include <iostream>

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

	for (int i = start; 0 <= i; --i)
	{
		std::cout << i << "\n";
	}
}

1.7 ループ変数を使う (1)

  • 通常、ループの制御に使っているループ変数(この場合 i)を、{ } 内で =+= などを使って別の値に変更してはいけない。ループの制御に影響し、ループが正しく動作しなくなるおそれがある。

ループが正しく動作しなくなる例

  • 次のコードは、ループ変数 i{ } 内で変更しているため、本来 5 回繰り返すはずのループが 3 回しか繰り返さない。

#include <iostream>

int main()
{
	for (int i = 0; i < 5; ++i)
	{
		std::cout << i << "\n";
		++i; // ループ変数を { } 内で変更するのは不適切
	}
}
出力
0
2
4

  • { } 内でループ変数を変更するのは不適切だが、計算の材料として使うだけなら問題ない。

#include <iostream>

int main()
{
	for (int i = 0; i < 5; ++i)
	{
		std::cout << (i * 100) << "\n";
	}
}
出力
0
100
200
300
400

1.8 ループ変数を使う (2)

  • 次のようなコードは、0 から 10 までの整数の合計を計算する。

#include <iostream>

int main()
{
	// 合計を計算するための変数
	int sum = 0;

	for (int i = 0; i <= 10; ++i)
	{
		sum += i;
	}

	std::cout << sum << "\n";
}
出力
55

  • 0 から 100 の合計を計算してみよう(答えは 5050)。
  • 98 から 100 の合計を計算してみよう(答えは 297)。

1.9 ループ変数を使う (3)

  • 次のコードは、AB を交互に、合わせて 10 個表示する。
  • ループ変数が偶数のときは A を、奇数のときは B を出力することで実現している。

#include <iostream>

int main()
{
	for (int i = 0; i < 10; ++i)
	{
		if ((i % 2) == 0)
		{
			std::cout << "A";
		}
		else
		{
			std::cout << "B";
		}
	}

	std::cout << "\n";
}
出力
ABABABABAB

2. 繰り返しを使って図形を描く

for の演習として、繰り返しを使って図形を描く方法を学ぶ。現時点までの知識では画像ファイルを作成することは難しいため、代わりに文字を縦横に並べることで図形を表現する。

横方向を X 軸、縦方向を Y 軸と考え、ループ変数の名前に xy を使うことにする。

2.1 縦の線を描く

  • 各行で # を出力することで、縦の線を描く。

#include <iostream>

int main()
{
	for (int y = 0; y < 5; ++y)
	{
		std::cout << "#\n";
	}
}
出力
#
#
#
#
#

2.2 横の線を描く

  • 繰り返し # を出力することで、横の線を描く。

#include <iostream>

int main()
{
	for (int x = 0; x < 8; ++x)
	{
		std::cout << "#";
	}

	std::cout << "\n";
}
出力
########

2.3 長方形を描く

  • ループは入れ子にすることができる。
  • 各行で横の線を描くことで、長方形を描く。

#include <iostream>

int main()
{
	for (int y = 0; y < 6; ++y)
	{
		for (int x = 0; x < 3; ++x)
		{
			std::cout << "#";
		}

		std::cout << "\n";
	}
}
出力
###
###
###
###
###
###

2.4 三角形を描く

  • 1 行ごとに横線の長さを増やしていくことで、三角形を描く。

#include <iostream>

int main()
{
	for (int y = 0; y < 6; ++y)
	{
		for (int x = 0; x <= y; ++x)
		{
			std::cout << "#";
		}

		std::cout << "\n";
	}
}
出力
#
##
###
####
#####
######

2.5 枠だけの長方形を描く

  • 最初と最後の行・列のみ # を出力し、それ以外は半角空白を出力することで、枠だけの長方形を描く。

#include <iostream>

int main()
{
	for (int y = 0; y < 5; ++y)
	{
		for (int x = 0; x < 8; ++x)
		{
			if ((x == 0) || (x == 7) || (y == 0) || (y == 4))
			{
				std::cout << "#";
			}
			else
			{
				std::cout << " ";
			}
		}

		std::cout << "\n";
	}
}
出力
########
#      #
#      #
#      #
########

2.6 市松模様を描く

  • 「行番号と列番号の和」が偶数か奇数かに応じて XO を塗り分けて長方形を描くと、市松模様になる。

#include <iostream>

int main()
{
	for (int y = 0; y < 5; ++y)
	{
		for (int x = 0; x < 8; ++x)
		{
			if ((x + y) % 2 == 0)
			{
				std::cout << "X";
			}
			else
			{
				std::cout << "O";
			}
		}

		std::cout << "\n";
	}
}
出力
XOXOXOXO
OXOXOXOX
XOXOXOXO
OXOXOXOX
XOXOXOXO

3. プログラムを一定時間スリープさせる

高性能オンラインコンパイラを使う

これ以降のコードは時間の経過を扱う。最終結果のみを表示する Simple C++ Editor や Wandbox では結果を時系列で確認できない。高性能オンラインコンパイラ「replit」を使って実行する。

3.1 スリープしない場合

  • cout を使った出力は短時間で終わるため、次のようなコードでは、始め終わり はほぼ同時に出力される。
#include <iostream>

int main()
{
	std::cout << "始め\n";

	std::cout << "終わり\n";
}

3.2 スリープする場合

  • std::this_thread::sleep_for(duration) でプログラムを、少なくとも duration だけスリープ(停止)させることができる。
  • duration は、具体的には std::chrono::milliseconds{ n } を使って指定する。
    • std::chrono::milliseconds(n) のように ( ) を使ってもよい。
  • 次のコードは少なくとも 3000 ミリ秒停止する(1 秒は 1000 ミリ秒)。
📙 Unit 32
#include <iostream>
#include <thread> // (1)!
#include <chrono> // (2)!

int main()
{
	std::cout << "始め\n";

	std::this_thread::sleep_for(std::chrono::milliseconds{ 3000 });

	std::cout << "終わり\n";
}
  1. std::this_thread::sleep_for() を使うためのインクルード
  2. std::chrono::milliseconds を使うためのインクルード

3.3 カウントダウン

  • カウントダウンする for とスリープを組み合わせることで、カウントダウンタイマーを実現できる。
  • 3.2 で述べたように、スリープする時間は「少なくとも」であるため、厳密には 1 秒よりもわずかに長くなるが、練習なのでこの講義では気にしないでよい。
#include <iostream>
#include <thread>
#include <chrono>

int main()
{
	for (int i = 10; 0 <= i; --i)
	{
		std::cout << i << "\n";

		// 1 秒スリープ
		std::this_thread::sleep_for(std::chrono::milliseconds{ 1000 });
	}

	std::cout << "終わり\n";
}

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

  • 繰り返しの回数を指定する方法を学んだ
  • 繰り返しを使って図形を描く方法を学んだ
  • プログラムを一定時間スリープさせる方法を学んだ

課題

🐣 練習問題

入力された秒数からカウントダウンするタイマーのプログラムを作成します。次の 2 つの機能を実装してください。

  • 残り時間(秒)と、進捗を整数 % で表示する。
  • カウントダウンごとに 1 秒スリープする。

カウントダウンする秒数は次の形式で標準入力から与えられます。

入力の形式
seconds
入力データ 意味
seconds カウントダウンする秒数(1 以上 99 以下の整数)
入力例 1

入力
5
出力
5 [0 %]
4 [20 %]
3 [40 %]
2 [60 %]
1 [80 %]
0 [100 %]

入力例 2

入力
30
出力
30 [0 %]
29 [3 %]
28 [6 %]
27 [10 %]
26 [13 %]
25 [16 %]
24 [20 %]
23 [23 %]
22 [26 %]
21 [30 %]
20 [33 %]
19 [36 %]
18 [40 %]
17 [43 %]
16 [46 %]
15 [50 %]
14 [53 %]
13 [56 %]
12 [60 %]
11 [63 %]
10 [66 %]
9 [70 %]
8 [73 %]
7 [76 %]
6 [80 %]
5 [83 %]
4 [86 %]
3 [90 %]
2 [93 %]
1 [96 %]
0 [100 %]

解法 Step 1

  • 入力した数から 0 までカウントダウンします。
#include <iostream>

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

	for (int i = time; 0 <= i; --i)
	{
		std::cout << i << "\n";
	}
}

解法 Step 2

  • 進捗が何 % であるかを計算して表示します。
#include <iostream>

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

	for (int i = time; 0 <= i; --i)
	{
		int progress = (100 * (time - i) / time);

		std::cout << i << " [" << progress << " %]\n";
	}
}

解法 Step 3

  • カウントごとに 1 秒スリープします。
#include <iostream>
#include <thread>
#include <chrono>

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

	for (int i = time; 0 <= i; --i)
	{
		int progress = (100 * (time - i) / time);

		std::cout << i << " [" << progress << " %]\n";

		std::this_thread::sleep_for(std::chrono::milliseconds{ 1000 });
	}
}

開発中はスリープをオフにする

開発中も 1 秒スリープするようにしていると、当然ながら実行結果の確認に時間がかかり、作業効率が悪い。スリープ部分をコメントにしたり、スリープ時間を短くして開発するとよい。提出の際にはその部分の修正を忘れないように。

📝 課題

問題文

指定した時間(分と秒)からカウントダウンするタイマーのプログラムを作成します。次のような機能を実装してください。

  • 残り時間が 1 分 30 秒の時は 1:30, 1 分 9 秒の時は 1:09 のように、秒はつねに 2 桁で表示する。
  • カウントダウンごとに進捗を 1 行のプログレスバー(形式自由)で表示する。
  • カウントダウンごとに 1 秒スリープする。

カウントダウンする時間の分と秒は、次の形式で標準入力から与えられます。

入力の形式
minutes seconds
入力データ 意味
minutes カウントダウン時間の分の部分(0 以上 9 以下の整数)
seconds カウントダウン時間の秒の部分(0 以上 59 以下の整数)

コードまたはコードの共有 URL(Wandbox)を提出してください。

ヒント 1

分と秒を分けて二重のループにするとプログラムが複雑になります。「1 分 30 秒」であれば「90 秒」のカウントダウンと考え、1 つのループを作れば、プログラムをシンプルにすることができます。分と秒は残りの秒から再計算できます。

ヒント 2

初心者は一度に全部を作ろうとして失敗します。練習問題の Step のように、まずは何を実装すべきか 1 つずつ整理しながら、段階を踏んでプログラムを作ることをお勧めします。ある程度のステップまで動くものが作れていれば、部分点を獲得できます。

入出力例

例 1

入力例 1
0 30
出力例 1
0:30 __________ [0 %]
0:29 __________ [3 %]
0:28 __________ [6 %]
0:27 #_________ [10 %]
0:26 #_________ [13 %]
0:25 #_________ [16 %]
0:24 ##________ [20 %]
0:23 ##________ [23 %]
0:22 ##________ [26 %]
0:21 ###_______ [30 %]
0:20 ###_______ [33 %]
0:19 ###_______ [36 %]
0:18 ####______ [40 %]
0:17 ####______ [43 %]
0:16 ####______ [46 %]
0:15 #####_____ [50 %]
0:14 #####_____ [53 %]
0:13 #####_____ [56 %]
0:12 ######____ [60 %]
0:11 ######____ [63 %]
0:10 ######____ [66 %]
0:09 #######___ [70 %]
0:08 #######___ [73 %]
0:07 #######___ [76 %]
0:06 ########__ [80 %]
0:05 ########__ [83 %]
0:04 ########__ [86 %]
0:03 #########_ [90 %]
0:02 #########_ [93 %]
0:01 #########_ [96 %]
0:00 ########## [100 %]

例 2

入力例 2
1 30
出力例 2
1:30 __________ [0 %]
1:29 __________ [1 %]
1:28 __________ [2 %]
1:27 __________ [3 %]
1:26 __________ [4 %]
1:25 __________ [5 %]
1:24 __________ [6 %]
1:23 __________ [7 %]
1:22 __________ [8 %]
1:21 #_________ [10 %]
1:20 #_________ [11 %]
1:19 #_________ [12 %]
1:18 #_________ [13 %]
1:17 #_________ [14 %]
1:16 #_________ [15 %]
1:15 #_________ [16 %]
1:14 #_________ [17 %]
1:13 #_________ [18 %]
1:12 ##________ [20 %]
1:11 ##________ [21 %]
1:10 ##________ [22 %]
1:09 ##________ [23 %]
1:08 ##________ [24 %]
1:07 ##________ [25 %]
1:06 ##________ [26 %]
1:05 ##________ [27 %]
1:04 ##________ [28 %]
1:03 ###_______ [30 %]
1:02 ###_______ [31 %]
1:01 ###_______ [32 %]
1:00 ###_______ [33 %]
0:59 ###_______ [34 %]
0:58 ###_______ [35 %]
0:57 ###_______ [36 %]
0:56 ###_______ [37 %]
0:55 ###_______ [38 %]
0:54 ####______ [40 %]
0:53 ####______ [41 %]
0:52 ####______ [42 %]
0:51 ####______ [43 %]
0:50 ####______ [44 %]
0:49 ####______ [45 %]
0:48 ####______ [46 %]
0:47 ####______ [47 %]
0:46 ####______ [48 %]
0:45 #####_____ [50 %]
0:44 #####_____ [51 %]
0:43 #####_____ [52 %]
0:42 #####_____ [53 %]
0:41 #####_____ [54 %]
0:40 #####_____ [55 %]
0:39 #####_____ [56 %]
0:38 #####_____ [57 %]
0:37 #####_____ [58 %]
0:36 ######____ [60 %]
0:35 ######____ [61 %]
0:34 ######____ [62 %]
0:33 ######____ [63 %]
0:32 ######____ [64 %]
0:31 ######____ [65 %]
0:30 ######____ [66 %]
0:29 ######____ [67 %]
0:28 ######____ [68 %]
0:27 #######___ [70 %]
0:26 #######___ [71 %]
0:25 #######___ [72 %]
0:24 #######___ [73 %]
0:23 #######___ [74 %]
0:22 #######___ [75 %]
0:21 #######___ [76 %]
0:20 #######___ [77 %]
0:19 #######___ [78 %]
0:18 ########__ [80 %]
0:17 ########__ [81 %]
0:16 ########__ [82 %]
0:15 ########__ [83 %]
0:14 ########__ [84 %]
0:13 ########__ [85 %]
0:12 ########__ [86 %]
0:11 ########__ [87 %]
0:10 ########__ [88 %]
0:09 #########_ [90 %]
0:08 #########_ [91 %]
0:07 #########_ [92 %]
0:06 #########_ [93 %]
0:05 #########_ [94 %]
0:04 #########_ [95 %]
0:03 #########_ [96 %]
0:02 #########_ [97 %]
0:01 #########_ [98 %]
0:00 ########## [100 %]