【问题标题】:Choose 4 randoms printf选择 4 个随机数 printf
【发布时间】:2020-06-29 01:44:48
【问题描述】:

所以,我的代码有问题。程序需要从 4 个 printfs 中随机选择一个并在终端打印。我是新手,对此我深表歉意。

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>

int main () {
    setlocale (LC_ALL, "Portuguese");
    int opcao;
    opcao = rand() % 3 + 1;

    if (opcao == 0) {
        printf ("\nA opção sorteada foi a de que o 1º classificado atual será o campeão (FC Porto)");
    }

    if (opcao == 1) {
        printf ("\nA opção sorteada foi a de que o 1º classificado na 1ª volta será o campeão (SL Benfica)");
    }

    if (opcao == 2) {
        printf ("\nA opção sorteada foi a de que Porto e Benfica farão um jogo em campo neutro para determinar o campeão!");
    }

    if (opcao == 4) {
        printf ("\nFoi sorteada a opção de que não haverá campeão esta época");
    } 


    return 0;
}

这是我的代码,但只是永远选择相同的 printf。

【问题讨论】:

  • 在使用 rand 之前先使用 srand。
  • 注意这个表达式 opcao = rand() % 3 + 1;永远不等于 0。你应该使用 opcao = rand() % 4;
  • 虽然这仍然无法处理以下情况:if (opcao == 4)
  • rand() % 5 将返回值 0-4modulo 运算符(例如%)与v = rand() % n; 的结果始终为0 &lt;= v &lt; n
  • 一些elses 可能也有帮助

标签: c++ printf cout


【解决方案1】:

使用&lt;random&gt; 库,而不是过时且容易出错的std::rand(使用模运算符获取范围内的随机整数为a common mistake)。请参阅Why is the new random library better than std::rand()? 了解更多信息。

#include <iostream>
#include <random>

int main()
{
    std::mt19937 engine{std::random_device{}()};
    std::uniform_int_distribution<int> dist{0, 3};

    switch (dist(eng)) {
    case 0:
        std::cout << "...\n";
        break;
    case 1:
        std::cout << "...\n";
        break;
    case 2:
        std::cout << "...\n";
        break;
    case 3:
        std::cout << "...\n";
        break;
    }
}

在这里,我们首先创建一个std::mt19937 引擎,它在半开范围 [0, 232) 内生成均匀分布的整数,并使用 std::random_device 为其播种,即应该生成一个不确定的数字(例如,它可以使用系统时间来实现)。然后,我们创建一个std::uniform_int_distribution 以将引擎生成的随机数映射到包含区间 [0, 3] 中的整数,方法是使用引擎作为参数调用它。


这可以通过打印一系列字符串来概括:

template <typename RanIt, typename F>
decltype(auto) select(RanIt begin, RanIt end, F&& f)
{
    if (begin == end) {
        throw std::invalid_argument{"..."};
    }

    thread_local std::mt19937 engine{std::random_device{}()};

    using index_t = long long; // for portability
    std::uniforn_int_distribution<index_t> dist{0, index_t{end - begin - 1}};

    return std::invoke(std::forward<F>(f), begin[dist(engine)]);
}

int main()
{
    const std::array<std::string, 4> messages {
        // ...
    };
    select(messages.begin(), messages.end(),
           [](const auto& string) {
               std::cout << string << '\n';
           });
}

在这里,我们采用a pair of random access iteratorsa Callable object 来支持从任意随机可访问范围中选择一个元素并对其执行任意操作。

  • 首先,我们检查范围是否为空,在这种情况下无法选择,throwing 和exception 会报告错误。

  • 然后,我们创建一个std::mt19937引擎,即thread_local(即每个线程都有自己的引擎)来防止data races。引擎的状态在调用之间保持不变,因此我们只为每个线程播种一次。

  • 之后,我们创建一个std::uniform_int_distribution来生成一个随机索引。请注意,我们使用long long 而不是typename std::iterator_traits&lt;RanIt&gt;::difference_typestd::uniform_int_distribution 仅保证与shortintlonglong longunsigned shortunsigned int、@9876546 一起使用和unsigned long long,因此如果difference_typesigned char 或扩展的有符号整数类型,则会导致未定义的行为。 long long 是最大支持的有符号整数类型,我们使用braced initialization 来防止narrowing conversions

  • 最后,我们std::forward Callable 对象和std::invoke 它与选定的元素。 decltype(auto) 说明符确保调用的类型和value category 被保留。

我们使用std::arraylambda expression 调用该函数,以打印所选元素。

从C++20开始,我们可以使用概念来约束函数模板:

template <std::random_access_iterator RanIt,
          std::indirectly_­unary_­invocable<RanIt> F>
decltype(auto) select(RanIt begin, RanIt end, F&& f)
{
    // ...
}

在C++20之前,我们也可以使用SFINAE:

template <typename RanIt, typename F>
std::enable_if_t<
    std::is_base_of_v<
        std::random_access_iterator_tag,
        typename std::iterator_traits<RanIt>::iterator_category
    >,
    std::invoke_result_t<F, typename std::iterator_traits<RanIt>::value_type>
> select(RanIt begin, RanIt end, F&& f)
{
    // ...
}

【讨论】:

  • 在这里很好地使用了现代 C++。虽然它没有指出原始问题中的直接错误,但这是一个关于如何使用标准库的非常好的示例。
  • 确实使用新的随机库更好!我会为第二个示例添加更多解释,因为它不是很容易理解,尤其是对于提出问题的用户而言。
  • @Kerek 我添加了一些解释和一堆链接。现在好点了吗?
  • 完美! :) 感谢您的澄清!
【解决方案2】:

我想在这里添加一些东西,添加到以前的答案。

首先,您并不是在为自己编写代码。作为一个非英语母语的人,我理解为什么用你的母语写代码似乎更容易,但不要这样做!

其次,我对代码进行了更改,以使其更易于阅读:

#include <time.h>
#include <cstdint>
#include <iostream>
#include <string>

constexpr uint32_t NUM_OF_POSSIBLE_PROMPTS = 4;

int main () {
    srand(time(NULL)); // seed according to current time

    for (uint32_t i = 0; i < 10; ++i)
    {
        int option = rand() % (NUM_OF_POSSIBLE_PROMPTS);

        std::string prompt = "";

        switch (option)
        {
            case 0:
                prompt = "option 0";
                break;
            case 1:
                prompt = "option 1";
                break;
            case 2:
                prompt = "option 2";
                break;
            case 3:
                prompt = "option 3";
                break;
            default:
                // some error handling!
                break;
        }

        std::cout << prompt << std::endl;
    }

    return 0;
}
  1. 我使用的是switch-case 而不是if-else-if-else,后者更具可读性和高效

  2. 我使用constexpr 来存储我的硬编码号码 - 在代码中硬编码号码是一个坏习惯(在实际程序中,我会constexpr 值 10 以及循环边界)。

  3. 在 c++ 中(与 c 不同),我们使用 std::cout 及其运算符

由于此代码更有条理,更容易了解可能发生错误的位置,并且从一开始就不太可能发生错误。

例如,使用 gcc 的 -Wswitch-enum 标志将确保如果您使用枚举,则所有值都必须在 switch-case 部分中处理(这当然使您的程序不易出错)。

P.S,我添加循环只是为了向您展示这段代码每轮都会得到不同的结果,您可以通过多次运行代码来测试它。

【讨论】:

    【解决方案3】:

    你的程序有几个问题:

    • 您没有种子,这就是数字重复的原因。使用
    srand (time(NULL)); // #include <time.h>
    

    在你使用rand()之前

    • 你的随机数没有排序,你有0-2,然后是4,当你得到3时,没有可用的选项。如果是故意的,请忽略此评论。

    • 使用rand() % 3 + 1;,您的随机数范围将从13,因此opcao == 0opcao == 4 将永远不会出现。对于0-4 间隔,您将需要类似:

     opcao = rand() % 5;
    

    【讨论】:

      【解决方案4】:

      您没有为随机数生成器提供种子。从man 页面,

      如果没有提供种子值,函数会自动播种 值为 1。

      如果每次运行都有相同的种子,那么您将始终获得相同的随机序列。

      【讨论】:

        猜你喜欢
        • 2014-06-12
        • 2012-03-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-09-17
        • 1970-01-01
        相关资源
        最近更新 更多