Я попытался реализовать fizzbuzz на C ++, и меня смущают различные результаты, которые выдают следующие примеры кода:
int main()
{
int val1 = 1;
while (val1 < 101) {
if (val1 % 15 == 0)
cout << "FizzBuzz\n";
if (val1 % 3 == 0)
cout << "Fizz\n";
if (val1 % 5 == 0)
cout << "Buzz\n";
else cout << val1 << "\n";
++val1;
}
keep_window_open();
}
Для всех кратных 15 этот код выводит
FizzBuzz
Fizz
Buzz
вместо просто FizzBuzz
, Все кратные (только) 5 правильно заменены на Buzz
но все кратные 3 распечатать Fizz
а потом сам номер.
Следующий код, однако, работает отлично. Я понимаю, что иначе, если заставляет это работать правильно, но я просто не вижу, каков путь к коду, когда val1
= 3, или 5, или 15.
int main()
{
int val1 = 1;
while (val1 < 101) {
if (val1 % 15 == 0)
cout << "FizzBuzz\n";
else if (val1 % 3 == 0)
cout << "Fizz\n";
else if (val1 % 5 == 0)
cout << "Buzz\n";
else cout << val1 << "\n";
++val1;
}
keep_window_open();
}
Ваш else
принадлежит только последнему if
:
if (val1 % 15 == 0)
cout << "FizzBuzz\n";
if (val1 % 3 == 0)
cout << "Fizz\n";
if (val1 % 5 == 0)
cout << "Buzz\n";
else // any val1 which doesn't divide by 5
cout << val1 << "\n";
Вот почему, если val1
не делится на 5, он выводит это значение.
Кроме того, 15 делится либо на 15, 5 и 3. Это запускает все три if
и выводит все три строки.
Ты можешь использовать else if
для того, чтобы получить правильные результаты, но лучший способ — заменить его завершением на завершенном состоянии.
Например, если бы у вас была функция, вы могли бы сделать это следующим образом:
string GetOutput(int val)
{
if (val % 15 == 0) return "FizzBuzz";
if (val % 3 == 0) return "Fizz"; // 2. val % 15 != 0 implied
if (val % 5 == 0) return "Buzz"; // 3. val % 15 != 0 && val % 3 != 0 implied
return to_string(val1); // 4. val % 15 != 0 && val % 5 != 0 && val % 3 != 0 implied
}
int main() {
cout << GetOutput(val) << endl;
}
Это будет работать, потому что выполнение заканчивается true
условие, и любая проверка условия подразумевает, что все предыдущие являются ложными. Эти два правила гарантированно верны в этом примере:
1. If execution is at lines 2 or 3 - val is not divisible by 15
2. If execution is at line 4 - val is not divisible by 3, 5 and 15
При таком подходе вам не нужно описывать эти условия вручную.
Более того, если у вас будет все больше и больше условий, поддерживать и читать такую функцию будет намного проще, чем писать очень длинные логические условия.
Например,
string GetOutput(int val)
{
if (val % 64 == 0) return "FizzBuzz"; // such wow
if (val % 32 == 0) return "Fizz"; // much readable
if (val % 16 == 0) return "Buzz";
if (val % 8 == 0) return "Muzz";
if (val % 4 == 0) return "Guzz";
if (val % 2 == 0) return "Duzz";
return "Hizz";
}
вместо
if (val % 64 == 0) cout << "FizzBuzz";
if (val % 64 != 0 && val % 32 == 0) cout << "Fizz";
if (val % 64 != 0 && val % 32 != 0 && val % 16 == 0) cout << "Buzz";
if (val % 64 != 0 && val % 32 != 0 && val % 16 != 0 && val % 8 == 0) cout << "Muzz";
if (val % 64 != 0 && val % 32 != 0 && val % 16 != 0 && val % 8 != 0 && val % 4 == 0) cout << "Guzz";
if (val % 64 != 0 && val % 32 != 0 && val % 16 != 0 && val % 8 != 0 && val % 4 != 0 && val % 2 == 0) cout << "Duzz";
if (val % 64 != 0 && val % 32 != 0 && val % 16 != 0 && val % 8 != 0 && val % 4 != 0 && val % 2 != 0) cout << "Hizz";
или же
if (val % 64 == 0)
{
cout << "FizzBuzz";
} else {
if (val % 32 == 0)
{
cout << "Fizz";
} else {
if (val % 16 == 0)
{
cout << "Buzz";
} else {
if (val % 8 == 0)
{
cout << "Muzz";
} else {
if (val % 4 == 0)
{
cout << "Guzz";
} else {
if (val % 2 == 0)
{
cout << "Duzz";
} else {
cout << "Hizz";
}
}
}
}
}
}
В первом примере:
if (val1 % 15 == 0)
cout << "FizzBuzz\n";
if (val1 % 3 == 0)
cout << "Fizz\n";
if (val1 % 5 == 0)
cout << "Buzz\n";
else cout << val1 << "\n";
каждый случай будет проверяться на каждой итерации. Поскольку все кратные 15
также кратны 3
а также 5
все три будут напечатаны на экране.
В другом случае:
if (val1 % 15 == 0)
cout << "FizzBuzz\n";
else if (val1 % 3 == 0)
cout << "Fizz\n";
else if (val1 % 5 == 0)
cout << "Buzz\n";
else cout << val1 << "\n";
первый случай (val1 % 15 == 0
) будет проверен, и если он ложный, будет проверен второй, и если он ложный, будет проверен третий. Это означает, что только первый сверху вниз из четырех случаев когда-либо будет проверяться на истинность за итерацию.
Например, если вы должны были изменить порядок 5
а также 15
:
if (val1 % 5 == 0)
cout << "FizzBuzz\n";
else if (val1 % 3 == 0)
cout << "Fizz\n";
else if (val1 % 15 == 0)
cout << "Buzz\n";
else cout << val1 << "\n";
ты бы получил это Buzz
никогда не будет напечатан, потому что, если значение делится на 15
ты бы остановил самый первый val1 % 5 == 0
тест, поэтому печать FizzBuzz
,
Сначала код ловит числа, кратные 15, что означает, что они кратны двум а также 5. В этом случае печатается «FizzBuzz». Любое число, которое не проходит этот тест, может быть кратным 3 или же 5 но не и то и другое. Следующие два если операторы проверяют на кратность 3 или 5 соответственно
while (val1 < 101) {
if (val1 % 15 == 0) // multiple of 3 AND 5
cout << "FizzBuzz\n";
else if (val1 % 3 == 0) // multiple of ONLY 3
cout << "Fizz\n";
else if (val1 % 5 == 0) // multiple of ONLY 5
cout << "Buzz\n";
else cout << val1 << "\n";
++val1;
}
Кратный 15 также кратное 5 и кратное 3. else
говорит «только когда предыдущий если не было true затем do .. «; таким образом, удаление его из серии не взаимоисключающих условных выражений меняет логику.
Однако учтите, что условия мог быть сделаны взаимоисключающими:
if (val1 % 15 == 0)
cout << "FizzBuzz\n";
if (val1 % 3 == 0 && !(val1 % 15 == 0))
cout << "Fizz\n";
if (val1 % 5 == 0 !(val1 % 15 == 0))
cout << "Buzz\n";
if (!(val1 % 3 == 0) && !(val1 % 5 == 0)) // implies !(val1 % 15 == 0)
cout << val1 << "\n";
В этом случае для любого целочисленного значения только один из if
условия будут верными.
Я также изменил финал else
чтобы показать взаимное исключение распространяется на все пути. Как говорится, я бы не рекомендую писать такой код
С другой точки зрения, каждый else if
последовательность может быть записана как «вложенное дерево». Семантика точно идентична, но структура может облегчить визуализацию пути. В этом случае должно быть ясно, что каждый случай терминала («cout») является взаимоисключающим от любого другого.
if (val1 % 15 == 0) {
cout << "FizzBuzz\n";
} else {
if (val1 % 3 == 0) {
cout << "Fizz\n";
} else {
if (val1 % 5 == 0) {
cout << "Buzz\n";
} else {
cout << val1 << "\n";
}
}
}