询问用户硬币落在头部的机会,每个实验的尝试次数以及实验的次数。例如,给定每个实验5个试验和20个实验,该程序将掷硬币5次并记录结果20次。然后,它显示结果以及每个事件发生的理论和观察到的概率。

我已经上交了,但是我非常喜欢做作业,我想知道如何进行它更好,更快等。我对计算机科学来说还很陌生,所以我想学习正确的实践和一切。马上,我知道我应该通过引用而不是通过值传递东西,并且我不应该使用全局变量。

//Name Lastname
//Class
//Coin Flip Simulation

//LIBRARIES
#include <stdio.h>
#include <iostream>
#include <fstream>
#include <time.h>
#include <cmath>

using namespace std;

//FUNCTION PROTOTYPES
bool generate();                    //Performs a coin flip with the given chance
void runExperiment();               //Performs and prints the results of the trials
void getInfo();                     //Gets the information to perform the experiment
int choose(int, int);               //Performs a combination (nCk)
double probability(int);            //Calculates the probability of a coin hitting heads n times (binomial thrm)

//GLOBAL VARIABLES
double chance = 0.5;                //Chance of landing on heads
int numTrials = 5;                  //Number of trials per experiment
int numExperiments = 1000;          //Number of experiments
bool printT = false;                //Whether to print the results of each trial

int main()
{
    srand(time(NULL));

    getInfo();
    runExperiment();

    getchar();
    getchar();
    return 0;
}

void getInfo()
{
    cout << "Please enter the chance of landing on heads (between 0.0 and 1.0, recommended 0.5)" << endl;
    cin >> chance;
    cout << "Please enter the number of trials per experiment (max 20, recommended 5)" << endl;
    cin >> numTrials;
    cout << "Please enter the number of experiments (recommended: 1000, max 1 billion)" << endl;
    cin >> numExperiments;
    cout << "Print the results of each experiment? Takes much longer (0 for no, 1 for yes)" << endl;
    cin >> printT;
}

void runExperiment()
{
    int m = 0, n = 0, a[22];

    for (int b = 0; b < 22; b++)
        a[b] = 0;

    for (int i = 0; i < numExperiments; i++)
    {
        int k = 0, l = 0;

        for (int j = 0; j < numTrials; j++)
        {
            if (generate())
                k++;
            else
                l++;
        }

        if (printT) //Prints experiment results
        {
            cout << "TRIAL: NUMBER OF HEADS: " << k;
            cout << " NUMBER OF TAILS: " << l << endl;
        }

        for (int p = 0; p < numTrials+1; p++)
        {
            if (k == p)
                a[p]++;
        }
        m = m + k;
        n = n + l;
    }
    //Prints final information to screen
    cout << "-----INFORMATION-----" << endl;
    cout << numTrials << " TRIALS PER EXPERIMENT" << endl;
    cout << numExperiments << " EXPERIMENTS" << endl;
    cout << "CHANCE OF LANDING ON HEADS: " << chance * 100 << "%" << endl;

    cout << endl << "-----RESULTS-----" << endl;

    cout << "TOTAL HEADS: " << m << endl;
    cout << "TOTAL TAILS: " << n << endl;

    cout << "PERCENT OF TIMES THE COIN LANDED ON HEADS: " << ((double)m / (m + n))*100 << "%" << endl;

    for (int q = 0; q < numTrials+1; q++)
        cout << "NUMBER OF EXPERIMENTS WITH " << q << " HEADS AND " << numTrials - q << " TAILS: " << a[q] << endl;

    cout << "-----STATS-----" << endl;

    double summation = 0;
    for (int z = 0; z < numTrials + 1; z++)
        summation = summation + a[z];

    for (int q = 0; q < numTrials + 1; q++)
        cout << "OBSERVED PROBABILITY OF " << q << " HEADS AND " << numTrials - q << " TAILS: " << (a[q]/summation)*100 << "%" << endl;

    cout << endl;

    for (int q = 0; q < numTrials + 1; q++)
        cout << "THEORETICAL PROBABILITY OF " << q << " HEADS AND " << numTrials - q << " TAILS: " << probability(q) << "%" << endl;

}

double probability(int k) //Binomial thrm
{
    double f = choose(numTrials, k)*(pow((chance), k))*(pow((1-chance), (numTrials - k)));
    f = f * 100;
    return f;
}

int choose(int n, int k) //nCk
{
    if (k > n)
        return 0;
    int r = 1;

    for (int d = 1; d <= k; ++d)
    {
        r *= n--;
        r /= d;
    }
    return r;
}

bool generate()
{
    double i = rand() % 100;
    i = i / 100;

    if (i < chance)
        return true;    //HEADS
    else
        return false;   //TAILS
}


评论

rand()以其非随机性而闻名。同样,自C ++ 14起,它已被更好的随机数库所取代。同样,随机范围也不能精确地除以100。因此[0-68]中的数字比[69-99]中的数字更有可能(假设随机最大值为32768)

@LokiAstari可以说这不是他最大的担忧...

#1 楼

这里有很多地方可以改进,所以我希望这些建议对您有用。

不要滥用using namespace std


using namespace std放在顶部每个程序的坏习惯都是您应该避免的坏习惯。

确保已具备所有必需的#include s

代码使用rand()而不使用#include <cstdlib>。重要的是要确保拥有所有必需的include文件,以确保程序能够可靠地编译。

仅使用必需的#include s

上述建议的反面是避免具有额外的#includes。在这种情况下,似乎未使用<fstream>。仅包含实际需要的文件。

避免使用全局变量

您已经注意到,应避免使用全局变量,而应使用局部变量。通常最好显式传递函数需要的变量,而不要使用模糊的全局变量隐式链接。例如,runExperiment函数依赖于numExperimentnumTrials,因此应该将它们传递给参数。

使用更好的命名方式

变量numTrialsnumExperiments是好名字,因为它们暗示这些变量对程序的意义。但是,runExperiment中的变量名为mnab等,这些变量根本不是描述性的。类似地,runExperiment是一个不错的函数名称,但generategetInfo不是(生成什么?;获取什么信息?)。

使用更好的随机数生成器

重新使用rand()可以大大改善。可以使用以下代码代替现有的generate代码:

bool generate() {
    return rand() < chance*RAND_MAX;
}


优点是它不会使注释中提到的结果倾斜。更好的方法是使用std::bernoulli_distribution,它使用了现代C ++随机数生成功能。

使用对象

当前的代码在很大程度上只是普通的过程C,而不是C ++。我建议将所有全局变量包装到一个对象中,例如CoinToss,然后使该对象的大多数函数成为成员函数。

当'\ n'可以使用时不要使用std::endl

使用std::endl会发出\n并刷新流。除非您真的需要冲洗流,否则您可以通过简单地发出'\n'而不是使用可能在计算上成本更高的std::endl来提高代码的性能。

更好地清理用户输入

如果我在程序中输入“ Edward”或负数之类的字符串,则会发生不良情况。用户可以做一些有趣的事情,而您希望程序更强大。一种方法是读取std::string,然后解析它,寻找错误。

忽略return 0


当C或C ++程序到达在main的末尾,编译器将自动生成返回0的代码,因此无需在return 0;的末尾显式放置main

注意:当我提出此建议时,几乎总是在以下两种评论中的一种:“我不知道。”或“这是个坏建议!”我的理由是,依靠该标准明确支持的编译器行为既安全又有用。对于C,自C99起;参见ISO / IEC 9899:1999第5.1.2.2.3节:从初始调用到main函数的返回等效于使用以下命令调用exit函数: main函数作为其参数返回的值;对于C ++,自1998年的第一个标准以来,到达终止}函数的main返回值0。请参见ISO / IEC 14882:1998第3.6.1节:


如果控制到达main的末尾而没有遇到return语句,则其作用是执行return 0;


此后,这两个标准的所有版本(C99和C ++ 98)都保持了相同的想法。我们依靠C ++中自动生成的成员函数,很少有人在return;函数的末尾编写显式的void语句。拒绝的原因似乎可以归结为“看起来很奇怪”。如果像我一样,您对更改C标准的理由感到好奇,请阅读此问题。还应注意,在1990年代初,这被视为“草率做法”,因为当时它是不确定的行为(尽管得到广泛支持)。

所以我主张省略它;其他人则不同意(通常是非常激烈!)无论如何,如果遇到忽略该代码的代码,您就会知道该标准已明确支持它,并且您会知道它的含义。

评论


\ $ \ begingroup \ $
他确实包括
\ $ \ endgroup \ $
– kyrill
17年4月17日在23:58

\ $ \ begingroup \ $
是的,您是对的。缺少。固定答案,谢谢!
\ $ \ endgroup \ $
–爱德华
17年4月18日在0:01

\ $ \ begingroup \ $
它可能包含在其他标头中,但是始终依靠它是不明智的。这是标准无法保证的实现细节。
\ $ \ endgroup \ $
–爱德华
17年4月18日在0:13

\ $ \ begingroup \ $
您在这里有很多要点。如何添加建议以使功能更短以使其更易于阅读和维护?
\ $ \ endgroup \ $
–大卫
17年4月18日在10:02

\ $ \ begingroup \ $
每次我看到有关C ++的帖子时,我都认为Edward将发布有关return 0的信息。您在计算机的某个位置是否保存了return 0文本墙?
\ $ \ endgroup \ $
–黛尔
17年4月19日在1:20



#2 楼

如您所述,代码的最大问题是使用全局变量。从外观上讲,该代码可读性强,但仍有改进的余地:


尝试将行长保持在100个字符以内。对于某些人来说,甚至80也太多了。

每行只有一个变量时,变量声明更易读:

int m = 0,
    n = 0,
    a[22];


甚至甚至

int m = 0;
int n = 0;
int a[22];



总是在forwhile等之后放置括号不太容易出错。

for (int b = 0; b < 22; b++) {
    a[b] = 0;
}


如果您随后要添加另一条语句,您不会忘记忘记添加它们并得到意外的行为,例如在此示例中:

if (k == p)
    a[p]++;
    printf("incremented a[%d]\n", p);



查看此内容:

if (i < chance)
    return true;    //HEADS
else
    return false;   //TAILS


要返回是否为i < chance。因此,您可以执行以下操作:

return (i < chance);


括号不是必需的,但是如果没有括号,可能会造成混淆–您是法官。

runExperiment函数可以分为两个逻辑单元-实验和结果打印输出。如果您有动力,可以设计一个数据结构以包含结果。然后将runExperiment函数拆分为两个函数。第一个将运行实验并返回结果,第二个将打印结果。

虽然有效,但通常不习惯将C样式的内容与C库的C ++样式的内容结合在一起:

#include <time.h>
#include <cmath>


最好只使用C ++样式的include:

#include <ctime>
#include <cmath>


在这个小型应用程序中,可能可行use整个命名空间,但是为了避免名称冲突,您应该麻烦键入std::cinstd::endl等,而不只是using namespace std。如果您不相信,这是一个stackoverflow问题,显示出您如何容易地遇到此问题。



#3 楼

来自其他人的所有注释都适用于这里:


C ++具有一个随机库,您应该使用该库来创建您的随机数生成器,因为它具有很多描述性。 >这确实令人困惑,生成会返回一个布尔值。为了提高可读性,您可能需要创建一个带有HEADS和TAIL值的枚举,并在代码中使用它。
命名:您有一个名为runExperiment的函数。然而,尽管它的名字如此,它不仅运行“一个”实验,而且运行全部。原则上,您应该尝试保持函数的简洁明了,这样一个函数可以生成单个实验,而另一个函数可以运行所有不同的实验。
类,类,类。每个实验运行都需要大量输入。因此,请将其封装在适当的类中,例如CoinTossExperiment或其他任何类。
更多功能也不会受到损害。特别是关于输出等。当您将所有内容封装到一个类中时,这将变得更加容易


#4 楼

课程建议。

为什么没有名为Coin的类。它可以有一个称为Flip的方法,该方法返回一个指示Heads或Tail的枚举。也许也有一些统计数据(翻转次数,头部被击中的次数……)

上一类叫做Flipper的类。构造函数接受一个参数,说您想要多少个硬币。它有一个称为FlipAll的方法,该方法返回一组硬币所返回的Head / Tail枚举向量。

额外的功劳-并行翻转硬币,学习如何使用线程:-)

评论


\ $ \ begingroup \ $
真的需要OO吗?
\ $ \ endgroup \ $
–innisfree
17年4月19日在10:22

\ $ \ begingroup \ $
我认为这是学习c ++的练习,而不是运输库。因此,添加对象似乎是一个自然的学习机会
\ $ \ endgroup \ $
–pm100
17年4月19日在23:15