8-x 第八章总结与测验

章节回顾

CPU在程序中执行的特定语句序列称为程序的执行路径execution path直线型程序straight-line program每次运行都遵循相同路径。

控制流语句control flow statements(也称流程控制语句flow control statements)允许程序员改变常规执行路径。当控制流语句导致程序开始执行非顺序指令序列时,这种情况称为分支branch

条件语句conditional statement用于指定关联语句是否应被执行。

if语句if statements根据条件真伪决定关联语句的执行;else语句else statements则在条件为假时执行。多个if和else语句可串联使用。

悬空elsedangling else指else语句与if语句的关联关系存在歧义的情况。悬空else语句将与同一代码块中最后一个未匹配的if语句配对。因此,只需确保if语句主体置于代码块内,即可轻松避免悬空else问题。

空语句null statement仅由分号构成,不执行任何操作。当语言要求存在语句但程序员无需其执行任何操作时使用。

switch语句switch statements为多项匹配选择提供了更简洁高效的方案。该语法仅适用于整数类型,通过case标签case labels标识待匹配的条件值。当未找到匹配的case标签时,系统将执行default标签default label下的语句。

当执行流程从某标签下的语句流向后续标签下的语句时,称为贯穿(穿透)fallthrough。可通过break语句(或return语句)阻止贯穿。可使用[[fallthrough]]属性标注有意设计的穿透行为。

goto语句goto statements允许程序向前或向后跳转至代码其他位置。通常应避免使用此类语句,因其易导致程序执行路径如意大利面般错综复杂,形成“意大利面代码spaghetti code”。

while循环while loops使程序在给定条件为真时持续执行。条件判断在循环执行前进行。

无限循环infinite loop是指条件始终为真的循环,除非使用其他控制流语句终止,否则将持续执行。

循环变量loop variable(又称计数器counter)是用于统计循环执行次数的整型变量,每次循环执行称为一次迭代iteration

do while循环do while loops与while循环类似,但条件在循环执行后而非之前进行评估。

for循环for loops是最常用的循环结构,适用于需要固定次数迭代的场景。当循环迭代次数多一次或少一次时,会引发差一错误off-by-one error

break语句break statements可使程序跳出switch、while、do while或for循环(包括尚未讲解的基于范围的for循环)。continue语句continue statements则能使程序立即进入下一次循环迭代。

终止halts操作可强制程序结束。正常终止normal termination指程序以预期方式退出(状态码将指示成功与否)。std::exit()会在main函数末尾自动调用,也可显式调用终止程序。它会执行部分清理工作,但不会清理局部变量或展开调用栈。

异常终止abnormal termination发生于程序遭遇意外错误而被迫关闭时。std::abort可用于实现异常终止。

算法algorithm是为解决特定问题或产生有用结果而设计的有限指令序列。若算法在调用间保留信息,则称为有状态的stateful。反之,无状态stateless算法不存储任何信息(调用时必须提供其运行所需的所有信息)。应用于算法时,术语“状态state”指有状态变量中持有的当前值。

若给定输入(即初始值)总能产生相同输出序列,则被视为算法的确定性deterministic

伪随机数生成器(PRNG) pseudo-random number generator (PRNG)是一种生成序列的算法,其特性可模拟随机数序列。PRNG实例化时,可提供称为随机种子random seed(或简称种子seed)的初始值(或值集)来初始化其状态。当PRNG通过种子完成初始化时,我们称其已被种子化seeded。种子值的大小可能小于PRNG状态的大小。此时我们称PRNG处于种子不足underseeded状态。PRNG开始重复输出前的序列长度称为周期period

随机数分布random number distribution将伪随机数生成器的输出转换为另一种数字分布。均匀分布uniform distribution是一种随机数分布,其输出在两个数值X和Y(含边界值)之间以等概率产生。


测验时间

警告:从此刻起测验难度将逐步提升,但你一定能行。让我们一起征服这些测验吧!

问题 #1

第4.x课——第4章总结与测验中,我们编写了一个模拟球体从塔顶坠落的程序。由于当时尚未学习循环结构,该程序只能模拟5秒的下落过程。

请修改下方程序,使球体下落时间可自由设定直至触地。同时更新程序以应用所有已学最佳实践(命名空间、constexpr等)。

#include <iostream>

// Gets tower height from user and returns it
double getTowerHeight()
{
	std::cout << "Enter the height of the tower in meters: ";
	double towerHeight{};
	std::cin >> towerHeight;
	return towerHeight;
}

// Returns the current ball height after "seconds" seconds
double calculateBallHeight(double towerHeight, int seconds)
{
	const double gravity { 9.8 };

	// Using formula: s = (u * t) + (a * t^2) / 2
	// here u (initial velocity) = 0, so (u * t) = 0
	const double fallDistance { gravity * (seconds * seconds) / 2.0 };
	const double ballHeight { towerHeight - fallDistance };

	// If the ball would be under the ground, place it on the ground
	if (ballHeight < 0.0)
		return 0.0;

	return ballHeight;
}

// Prints ball height above ground
void printBallHeight(double ballHeight, int seconds)
{
	if (ballHeight > 0.0)
		std::cout << "At " << seconds << " seconds, the ball is at height: " << ballHeight << " meters\n";
	else
		std::cout << "At " << seconds << " seconds, the ball is on the ground.\n";
}

// Calculates the current ball height and then prints it
// This is a helper function to make it easier to do this
void calculateAndPrintBallHeight(double towerHeight, int seconds)
{
	const double ballHeight{ calculateBallHeight(towerHeight, seconds) };
	printBallHeight(ballHeight, seconds);
}

int main()
{
	const double towerHeight{ getTowerHeight() };

	calculateAndPrintBallHeight(towerHeight, 0);
	calculateAndPrintBallHeight(towerHeight, 1);
	calculateAndPrintBallHeight(towerHeight, 2);
	calculateAndPrintBallHeight(towerHeight, 3);
	calculateAndPrintBallHeight(towerHeight, 4);
	calculateAndPrintBallHeight(towerHeight, 5);

	return 0;
}

显示方案

#include <iostream>

namespace Constants
{
	constexpr double gravity { 9.8 };
}

// Gets tower height from user and returns it
double getTowerHeight()
{
	std::cout << "Enter the height of the tower in meters: ";
	double towerHeight{};
	std::cin >> towerHeight;
	return towerHeight;
}

// Returns the current ball height after "seconds" seconds
// note: this function could be constexpr, but we haven't covered constexpr functions yet
double calculateBallHeight(double towerHeight, int seconds)
{
	// Using formula: s = (u * t) + (a * t^2) / 2
	// here u (initial velocity) = 0, so (u * t) = 0
	const double fallDistance { Constants::gravity * (seconds * seconds) / 2.0 };
	const double ballHeight { towerHeight - fallDistance };

	if (ballHeight < 0.0)
		return 0.0;

	return ballHeight;
}

// Prints ball height above ground
void printBallHeight(double ballHeight, int seconds)
{
	if (ballHeight > 0.0)
		std::cout << "At " << seconds << " seconds, the ball is at height: " << ballHeight << " meters\n";
	else
		std::cout << "At " << seconds << " seconds, the ball is on the ground.\n";
}

// Calculates the current ball height and then prints it
// This is a helper function to make it easier to do this
double calculateAndPrintBallHeight(double towerHeight, int seconds)
{
	const double ballHeight{ calculateBallHeight(towerHeight, seconds) };
	printBallHeight(ballHeight, seconds);

	return ballHeight;
}

int main()
{
	const double towerHeight{ getTowerHeight() };

	int seconds { 0 };
	while (calculateAndPrintBallHeight(towerHeight, seconds) > 0.0)
	{
		++seconds;
	}

	return 0;
}

问题 #2

质数是指大于1的自然数,它只能被1和它自身整除(无余数)。
请编写isPrime()函数并使用for循环完成以下程序。成功时,程序将输出“Success!”。

// Make sure that assert triggers even if we compile in release mode
#undef NDEBUG

#include <cassert> // for assert
#include <iostream>

bool isPrime(int x)
{
    return false;
    // write this function using a for loop
}

int main()
{
    assert(!isPrime(0)); // terminate program if isPrime(0) is true
    assert(!isPrime(1));
    assert(isPrime(2));  // terminate program if isPrime(2) is false
    assert(isPrime(3));
    assert(!isPrime(4));
    assert(isPrime(5));
    assert(isPrime(7));
    assert(!isPrime(9));
    assert(isPrime(11));
    assert(isPrime(13));
    assert(!isPrime(15));
    assert(!isPrime(16));
    assert(isPrime(17));
    assert(isPrime(19));
    assert(isPrime(97));
    assert(!isPrime(99));
    assert(isPrime(13417));

    std::cout << "Success!\n";

    return 0;
}

相关内容
assert 是一个预处理器宏,当关联的参数评估为假时会终止程序。因此当我们编写 assert(!isPrime(0)) 时,其含义是“如果 isPrime(0) 为真,则终止程序”。我们在第 9.6 课——assert 和 static_assert 中将更详细地讲解 assert。

显示方案

// Make sure that assert triggers even if we compile in release mode
#undef NDEBUG

#include <cassert> // for assert
#include <iostream>

// Non-optimized version
bool isPrime(int x)
{
    if (x <= 1) // if x is negative, 0, or 1 then the number is not prime
        return false;

    for (int test{ 2 }; test < x; ++test)
    {
        if (x % test == 0) // if x is evenly divisible
            return false;  // then this number isn't prime
    }

    return true; // if we didn't find any divisors, then x must be prime
}

int main()
{
    assert(!isPrime(0));
    assert(!isPrime(1));
    assert(isPrime(2));
    assert(isPrime(3));
    assert(!isPrime(4));
    assert(isPrime(5));
    assert(isPrime(7));
    assert(!isPrime(9));
    assert(isPrime(11));
    assert(isPrime(13));
    assert(!isPrime(15));
    assert(!isPrime(16));
    assert(isPrime(17));
    assert(isPrime(19));
    assert(isPrime(97));
    assert(!isPrime(99));
    assert(isPrime(13417));

    std::cout << "Success!\n";

    return 0;
}

额外加分题:

上述解决方案中的 for 循环存在两个不足之处:

  • 它会检查偶数因数。我们无需测试这些因数(2 除外)。
  • 它会检查所有小于等于 x 的数是否为因数。非质数(合数)至少有一个小于或等于其平方根的因数,因此无需检查大于x平方根的因数。std::sqrt(x)(位于头文件中)可返回x的平方根。

针对后者,有两种优化方案:在循环前预先计算std::sqrt(x),再将循环变量与该值进行比较。或者,通过将比较式两边平方来完全优化掉 std::sqrt(x) 的计算(感谢读者 JJag 提出此建议)(如需更多帮助请参阅提示)。我们在测验解决方案中将采用后者。

显示提示

提示:对于两个变量 a ≥ 0 和 b ≥ 0,当比较式 a ≤ √b 的两边都平方后,该比较式仍然成立。这得到 a² ≤ √b²,等价于 a² ≤ b。

更新上述解决方案以实现这两种优化。

显示方案

#include <cassert>
#include <cmath> // for std::sqrt
#include <iostream>

// optimized version
bool isPrime(int x)
{
    if (x <= 1)     // if x is negative, 0, or 1 then the number is not prime
        return false;
    if (x == 2)     // the number 2 is the only even prime
        return true;
    if (x % 2 == 0) // any other even number is not prime
        return false;

    // For any number 3 or greater, test odd values (this is why we add 2)
    // between 3 and sqrt(x) to see if they are a divisor
    // Also see https://stackoverflow.com/questions/5811151/why-do-we-check-up-to-the-square-root-of-a-number-to-determine-if-the-number-is
    // Since test >= 0 and x >=0, we can optimize test < std::sqrt(x) to test * text < x
    for (int test{ 3 }; test * test <= x; test += 2)
    {
        if (x % test == 0) // if x is evenly divisible
            return false;  // then this number isn't prime
    }

    return true; // if we didn't find any divisors, then x must be prime
}

int main()
{
    assert(!isPrime(0));
    assert(!isPrime(1));
    assert(isPrime(2));
    assert(isPrime(3));
    assert(!isPrime(4));
    assert(isPrime(5));
    assert(isPrime(7));
    assert(!isPrime(9));
    assert(isPrime(11));
    assert(isPrime(13));
    assert(!isPrime(15));
    assert(!isPrime(16));
    assert(isPrime(17));
    assert(isPrime(19));
    assert(isPrime(97));
    assert(!isPrime(99));
    assert(isPrime(13417));

    std::cout << "Success!\n";

    return 0;
}

问题 #3

实现一个猜数字游戏。首先,程序应随机生成一个1到100之间的整数。用户有7次机会猜数字。

若用户未猜中正确数字,程序应告知其猜得过高还是过低。若用户猜中数字,程序应告知其获胜。若用尽尝试次数,程序应告知其失败并显示正确数字。游戏结束时应询问用户是否继续,若未输入'y'或'n'则再次询问。

本测验假设用户输入有效数字。

使用8.15节——全局随机数生成(Random.h)中的Random.h头文件。

输出效果应如下所示:

Let's play a game. I'm thinking of a number between 1 and 100. You have 7 tries to guess what it is.
Guess #1: 64
Your guess is too high.
Guess #2: 32
Your guess is too low.
Guess #3: 54
Your guess is too high.
Guess #4: 51
Correct! You win!
Would you like to play again (y/n)? y
Let's play a game. I'm thinking of a number between 1 and 100. You have 7 tries to guess what it is.
Guess #1: 64
Your guess is too high.
Guess #2: 32
Your guess is too low.
Guess #3: 54
Your guess is too high.
Guess #4: 51
Your guess is too high.
Guess #5: 36
Your guess is too low.
Guess #6: 45
Your guess is too low.
Guess #7: 48
Your guess is too low.
Sorry, you lose. The correct number was 49.
Would you like to play again (y/n)? q
Would you like to play again (y/n)? n
Thank you for playing.

额外加分题:将最小值、最大值和猜测次数设为可配置参数。

显示方案

#include <iostream>
#include "Random.h" // https://www.learncpp.com/cpp-tutorial/global-random-numbers-random-h/

// Returns true if the user won, false if they lost
// We don't use the return value in this program, but it doesn't add complexity to have it, and may be useful in a future update
// (e.g. if we wanted to move the won/lost messages out of the function, or conditionalize other behavior based on won/lost)
bool playHiLo(int guesses, int min, int max)
{
	std::cout << "Let's play a game. I'm thinking of a number between " << min << " and " << max << ". You have " << guesses << " tries to guess what it is.\n";
	const int number { Random::get(min, max) }; // this is the number the user needs to guess

	// Loop through all of the guesses
	for (int count{ 1 }; count <= guesses; ++count)
	{
		std::cout << "Guess #" << count << ": ";

		int guess{};
		std::cin >> guess;

		if (guess > number)
			std::cout << "Your guess is too high.\n";
		else if (guess < number)
			std::cout << "Your guess is too low.\n";
		else // guess == number, so the user won
		{
			std::cout << "Correct! You win!\n";
			return true;
		}
	}

	// The user lost
	std::cout << "Sorry, you lose. The correct number was " << number << '\n';
	return false;
}

bool playAgain()
{
	// Keep asking the user if they want to play again until they pick y or n.
	while (true)
	{
		char ch{};
		std::cout << "Would you like to play again (y/n)? ";
		std::cin >> ch;

		switch (ch)
		{
		case 'y': return true;
		case 'n': return false;
		}
	}
}

int main()
{
	constexpr int guesses { 7 }; // the user has this many guesses
	constexpr int min     { 1 };
	constexpr int max     { 100 };

	do
	{
		playHiLo(guesses, min, max);
	} while (playAgain());

	std::cout << "Thank you for playing.\n";

	return 0;
}

我们将在第9.x课——第9章总结与测验中为该解决方案添加错误处理功能。

posted @ 2026-02-28 15:51  游翔  阅读(0)  评论(0)    收藏  举报