首先,欢迎来到 StackOverflow!我敢肯定,当您学习编程时,您会发现它是一种宝贵的资源。也就是说,为了充分利用该网站,遵守该网站的指南非常重要。一般来说,请记住,SO 不是一个论坛,而是一个问答网站,你会做得很好。
一般问题
您发布的代码存在一些问题:
A. 空白
使用空格非常有助于使您的代码对其他程序员和您自己都 清晰易懂。用 Tab 表示范围(写作
bool condition = ...
if (condition) {
action();
}
而不是
bool condition = ...
if (condition) {
action()
}
这并不表示操作范围)显着提高了可读性,尤其是当您发布的代码中存在多个嵌套 范围时。 间距也很重要。
B. 使用cout
1.
与其写作,不如cout << "" << endl
简单地写作cout << endl
。
2.
和之间 存在差异endl
"\n"
,从您的代码中可以看出您可能知道它们。虽然使用两者的性能特点并不完全相同,但考虑到现阶段肯定会过早优化。坚持endl
这种日志记录更具可读性。而不是
cout << "All cycle #1 approximate square are guessed\n" << "using 1 as the approximate." << endl;
最好写
cout << "All cycle #1 approximate square are guessed" << endl << "using 1 as the approximate." << endl;
在这种情况下。
3.
如果您在一行代码中插入的int超过了合适的范围(此代码,cout
cout << "This line of code is longer than feels comfortable." << endl << "It would be much more pleasant to break this into several lines so that I don't need to scroll to the right to view the whole thing." << endl;
例如,对于一行来说太长了),而不是像您的代码中那样,
cout << "Anmol's Square Root Calculator!" << endl;
cout << endl;
cout << "This program will compute the square root\n";
cout << "of a number using the Babylonian algorithm!\n";
cout << endl;
cout << "Only positive numbers work with this algorithm!" << endl;
cout << endl;
cout << "All cycle #1 approximate square are guessed\n" << "using 1 as the approximate." << endl;
cout << endl;
cout << endl;
cout << endl;
cout
多次使用插入运算符,你可以写
cout << "Anmol's Square Root Calculator!" << endl
<< endl
<< "This program will compute the square root" << endl
<< "of a number using the Babylonian algorithm!" << endl
<< endl
<< "Only positive numbers work with this algorithm!" << endl
<< endl
<< "All cycle #1 approximate square are guessed" << endl
<< "using 1 as the approximate." << endl
<< endl
<< endl
<< endl;
C. 模块化
你所有的代码都在main()
. 即使有这么短的程序,这仍然会妨碍可读性。更重要的是,这意味着您不能在其他地方重用您在此程序中创建的功能。
一个好的方法是将您的程序分解成单独的组件。在这个程序中,您有以下设置:
show a greeting
do {
get number from user
compute the squareroot of that number while logging
show the user the squareroot
ask the user if they want to do another computation
} while the user wants the program to keep running
show a farewell
此时,您可以为每个步骤编写函数。我在这里“编写”的函数摘自您的代码,稍作修改。
打印到屏幕
向用户显示问候和告别的功能是最简单的,因为它们(几乎)只需要使用cout
:
void showGreeting()
{
cout << "Anmol's Square Root Calculator!" << endl
<< endl
<< "This program will compute the square root" << endl
<< "of a number using the Babylonian algorithm!" << endl
<< endl
<< "Only positive numbers work with this algorithm!" << endl
<< endl
<< "All cycle #1 approximate square are guessed" << endl
<< "using 1 as the approximate." << endl
<< endl
<< endl
<< endl;
}
void showFarewell()
{
cout << "Thank you for using a program made by Anmol Sethi!" << endl
<< endl
<< endl
<< endl;
cin.get();
}
获取用户输入
接下来我们需要一个函数来从用户那里获取一个数字
double getInputNumber()
{
double num = 0.0f;
cout << "Please enter the number you would like to compute the square root of: ";
cin >> num;
cout << endl;
do {
if (num <= 0)
{
cout << endl
<< "Invalid input. Please re-enter a valid number: ";
cin >> num;
cout << endl;
}
} while (num <= 0);
cout << endl
<< endl
<< endl;
return num;
}
此外,我们需要确定用户是否希望程序再次运行。从程序的角度(的角度main
)来看,这在很大程度上是一个布尔问题:我们应该调用某个函数,它应该返回true
or false
。但是,在函数中,我们正在与用户进行交互,因此使用'y'
and'n'
是理想的(至少对于说英语的用户而言);
bool getRunAgain()
{
bool choice = false;
char playAgain = 'n';
do {
cout << endl
<< "Would you like to calculate again? (y/n): ";
cin >> playAgain;
cout << endl;
do {
cout << endl;
if ((playAgain !='y' && playAgain !='n'))
{
cout << "Invalid input. Only y/n: ";
cin >> playAgain;
cout << endl;
}
} while (playAgain !='y' && playAgain !='n');
} while (playAgain !='y' && playAgain !='n');
if (playAgain == 'y') {
choice = true;
} else /*if (playAgain == 'n')*/ {//At this, playAgain is either 'y' or 'n'. So if it's not 'y', it's 'n'.
choice = false;
}
return choice;
}
由于将此代码重写为函数,因此更容易看到这里正在完成的工作超出了必要的范围:考虑外部do...while
循环。它的重复条件是playAgain !='y' && playAgain !='n'
。乍一看,这似乎是有道理的:我们需要确保用户输入了“是”或“否”。do...while
但请注意,有一个条件完全相同的内部循环。playAgain
这意味着除非等于'y'
or ,否则内部循环不会退出'n'
。因此,当我们离开内部循环时,我们可以确定它playAgain
是'y'
or 'n'
。所以我们不需要再次检查它。这允许我们像这样重写函数:
bool getRunAgain()
{
bool choice = false;
char playAgain = 'n';
cout << endl
<< "Would you like to calculate again? (y/n): ";
cin >> playAgain;
cout << endl;
do {
cout << endl;
if ((playAgain !='y' && playAgain !='n'))
{
cout << "Invalid input. Only y/n: ";
cin >> playAgain;
cout << endl;
}
} while (playAgain !='y' && playAgain !='n');
if (playAgain == 'y') {
choice = true;
} else /*if (playAgain == 'n')*/ {
choice = false;
}
return choice;
}
但是我们在剩下的循环中有类似的情况:
do {
cout << endl;
if ((playAgain !='y' && playAgain !='n'))
{
cout << "Invalid input. Only y/n: ";
cin >> playAgain;
cout << endl;
}
} while (playAgain !='y' && playAgain !='n');
除了cout << endl
which 不太重要之外,看起来我们根本不想进入循环,除非(playAgain !='y' && playAgain !='n')
。这需要while
循环而不是do...while
循环:
while (playAgain !='y' && playAgain !='n')
{
cout << "Invalid input. Only y/n: ";
cin >> playAgain;
cout << endl;
}
把它放到我们的函数中,我们现在有
bool getRunAgain()
{
bool choice = false;
char playAgain = 'n';
cout << endl
<< "Would you like to calculate again? (y/n): ";
cin >> playAgain;
cout << endl;
while (playAgain !='y' && playAgain !='n')
{
cout << "Invalid input. Only y/n: ";
cin >> playAgain;
cout << endl;
}
if (playAgain == 'y') {
choice = true;
} else /*if (playAgain == 'n')*/ {
choice = false;
}
return choice;
}
计算平方根
最后,我们需要一个函数来使用巴比伦算法计算平方根。我们只需要获取您的代码
int count(25), cycle(1);
double guess, sqrt, num;
cout << "Please enter the number you would like to compute the square root of: ";
cin >> num;
cout << endl;
for (guess=1; count!=0; count--)
{
guess =(guess + (num/guess))/2;
cout<<""<<endl;
cout<<"Cycle "<<cycle<<" Aproximate: "<<guess<<endl;
sqrt = guess;
cycle++;
}
并将其放入一个接受 double 并返回一个的函数中:
double computeSquareRootBabylonian(double num)
{
int count(25), cycle(1);
double guess, sqrt;
for (guess = 1; count != 0; count--)
{
guess = (guess + (num/guess))/2;
cout << endl
<< "Cycle " << cycle << " Aproximate: " << guess << endl;
sqrt = guess;
cycle++;
}
return sqrt;
}
我们可以在这里停下来,但是有一些更改可以改进该功能。
1.
初始近似值(您正在使用的初始猜测,1
)实际上并不是该函数的一部分。调用此函数的代码必须指定该起点:
double computeSquareRootBabylonian
(double num,
double initialApproximation)
{
double approximation, sqrt;
unsigned int count(25), cycle(1);
for (approximation = initialApproximation; count != 0; count--)
{
approximation =(approximation + (num/approximation))/2;
cout << endl
<< "Cycle " << cycle << " Aproximate: " << approximation << endl;
sqrt = approximation;
cycle++;
}
return sqrt;
}
2.
迭代次数(或循环次数,正如您所说的那样)也不是函数的一部分。调用此函数的代码必须指定该数字:
double computeSquareRootBabylonian
(double num,
double initialApproximation,
unsigned int iterations)
{
double approximation, sqrt;
unsigned int iterationsRemaining = iterations;
unsigned int iteration = 1;
for (approximation = initialApproximation; iterationsRemaining != 0; iterationsRemaining--)
{
approximation =(approximation + (num/approximation))/2;
cout << endl
<< "Cycle " << iteration << " Aproximate: " << approximation << endl;
sqrt = approximation;
iteration++;
}
return sqrt;
}
3.
这个for
循环很奇怪。这很奇怪,因为您在初始化时初始化了变量guess
(我已将其重命名approximation
),尽管它不是 “循环变量”。在您的第二段代码中,您使用了
while (count > 0)
{
guess =(guess + (num/guess))/2;
cout << endl;
cout << "Cycle " << cycle << " Aproximate: " << guess << endl;
sqrt = guess;
count-=1;
cycle+=1;
}
反而。这段代码的意图要清楚得多,但是使用标准方式for
的循环(在初始化中初始化循环条件所依赖的相同变量,并且在每个循环结束时更改:),其意图会更清楚。在这里迭代的变量是(我称之为),只要它大于 ,它就会从用户指定的迭代次数(参数)中减少。这种情况需要以下 for 循环:for (int i = 0; i < 10; i++)
count
iterationsRemaining
iterations
1
0
for (unsigned int iterationsRemaining = iterations;
iterationsRemaining > 0;
iterationsRemaining--)
{
//...
}
将其代入我们的函数时,我们需要确保仍然初始化approximation
为initialApproximation
:
double computeSquareRootBabylonian
(double num,
double initialApproximation,
unsigned int iterations)
{
double approximation = initialApproximation;
unsigned int iteration = 1;
double sqrt;
for (unsigned int iterationsRemaining = iterations;
iterationsRemaining > 0;
iterationsRemaining--)
{
approximation =(approximation + (num/approximation))/2;
cout << endl
<< "Cycle " << iteration << " Aproximate: " << approximation << endl;
sqrt = approximation;
iteration++;
}
return sqrt;
}
旁白:更多过早的优化
你写了
在书中,我读到使用 for 循环进行算法更有效,所以我想使用 for 而不是 while 循环。
这不是使用for
循环的好理由。您不需要(在合理范围内)对代码进行更改以使其更快,除非您确定它运行得太慢。编写在阅读时有意义的代码更为重要。
4.
变量sqrt
有什么作用?它重复地approximation
赋值给它并从函数中返回,但它返回时的值与 的值相同approximation
。出于这个原因删除它相当于过早的优化。但是,调用该变量sqrt
表明它是真正的平方根,而不是近似值。出于这个原因,它应该被删除,留给我们
double computeSquareRootBabylonian
(double num,
double initialApproximation,
unsigned int iterations)
{
double approximation = initialApproximation;
unsigned int iteration = 1;
for (unsigned int iterationsRemaining = iterations;
iterationsRemaining > 0;
iterationsRemaining--)
{
approximation =(approximation + (num/approximation))/2;
cout << endl
<< "Cycle " << iteration << " Aproximate: " << approximation << endl;
iteration++;
}
return approximation;
}
5.
记录每次迭代的逐次近似值并不是您总是希望平方根函数做的事情。我们应该添加一个参数,允许调用代码指定是否记录近似值:
double computeSquareRootBabylonian
(double num,
double initialApproximation,
unsigned int iterations,
bool log)
{
double approximation = initialApproximation;
unsigned int iteration = 1;
for (unsigned int iterationsRemaining = iterations;
iterationsRemaining > 0;
iterationsRemaining--)
{
approximation =(approximation + (num/approximation))/2;
if (log)
{
cout << endl << "Cycle " << iteration << " Aproximate: " << approximation << endl;
}
iteration++;
}
return approximation;
}
编写主函数
我们现在已经准备好程序的所有组件。我们只需要改变我们的计划
show a greeting
do {
get number from user
compute the squareroot of that number while logging
show the user the squareroot
ask the user if they want to do another computation
} while the user wants the program to keep running
show a farewell
成一个完整的程序:
int main(int argc, char *argv[])
{
//Do this stuff.
}
首先,我们知道如何开始和结束程序。我们想使用我们的函数showGreeting()
和来表示问候和告别showFarewell()
:
int main(int argc, char *argv[])
{
showGreeting();
//do {
//get number from user
//compute the squareroot of that number while logging
//show the user the squareroot
//ask the user if they want to do another computation
//} while the user wants the program to keep running
showFarewell();
}
我们知道我们想要获取用户输入并至少计算一次平方根,并且我们知道我们可以得到bool
表示用户是否想要使用我们的函数计算另一个平方根的a getRunAgain()
。我们应该do
用户输入和计算while
getRunAgain()
!
int main(int argc, char *argv[])
{
showGreeting();
do {
//get number from user
//compute the squareroot of that number while logging
//show the user the squareroot
} while(getRunAgain());
showFarewell();
}
我们有一个函数getInputNumber()
,它返回一个double
代表用户想要计算平方根的数字。我们需要使用它double
两次,一次作为平方根函数的参数computeSquareRootBabylonian()
,一次将输入及其平方根输出给用户。因此,我们需要一个局部变量:
int main(int argc, char *argv[])
{
showGreeting();
do {
double number = getInputNumber();
//compute the squareroot of that number while logging
//show the user the squareroot
} while(getRunAgain());
showFarewell();
}
现在我们的平方根函数computeSquareRootBabylonian()
有四个参数:
- 计算平方根的数量
- 初始近似
- 迭代次数
- 是否记录每个逐次逼近
我们将number
作为第一个参数传递。现在,我们可以对剩余的参数进行硬编码1
,用于初始近似值、25
迭代次数以及true
是否记录。
由于我们只需要使用computeSquareRootBabylonian()
一次的结果(当注销结果时),我们可以不使用局部变量而逃脱。不过,为了清楚起见,让我们继续使用一个:
int main(int argc, char *argv[])
{
showGreeting();
do {
double number = getInputNumber();
double squareRoot = computeSquareRootBabylonian(number, 1.0f, 25, true);
//show the user the squareroot
} while(getRunAgain());
showFarewell();
}
我们只需要将结果显示给用户:
int main(int argc, char *argv[])
{
showGreeting();
do {
double number = getInputNumber();
double squareRoot = computeSquareRootBabylonian(number, 1.0f, 25, true);
cout << endl
<< "The square root of " << num << " is " << sqrt << "!" << endl
<< endl;
} while(getRunAgain());
showFarewell();
}
该程序遗漏了一件事:配置cout
.
int main(int argc, char *argv[])
{
cout.precision(50);
showGreeting();
do {
double number = getInputNumber();
double squareRoot = computeSquareRootBabylonian(number, 1.0f, 25, true);
cout << endl
<< "The square root of " << num << " is " << sqrt << "!" << endl
<< endl;
} while(getRunAgain());
showFarewell();
}
重述问题
你问了几个问题cout
:
在这段代码[第一个main
函数]中,我专门将小数的精度设置为 15。但是它输出的数字是小数点后 12 位。但在这段代码[第二个main
函数]中,它以小数点后 15 位输出。[...] 我很困惑为什么会这样。我试过 cout << fixed << showpoint; 但是当 num 无缘无故输出 15 个零时,它看起来很不愉快。谁能告诉我为什么会发生这种情况以及如何解决它?
为什么我不能超过小数点后 15 位?如果我将精度设置为 30/40,即使在我的计算机计算器中它达到 30/40,它对我的输出也没有任何影响。我应该使用另一种整数类型而不是双精度类型吗?
我稍后会解决这些问题。首先,我将描述我自己会如何询问他们,以便在 StackOverflow 上获得更好更快的响应。
要点是发布一个简短的、独立的、正确的(可编译的)示例。那么这里的问题到底是什么?它与使用插入(using operator <<
) into std::cout
(into std::ostream
in general) 有关。它与double
使用std::ostream::precision
. 在将问题减少到这一点之后,我将开始查看相关文档,如果没有提供足够的解释,则使用这些关键字在 StackOverflow 上进行搜索。如果我找不到问题的解决方案,我会生成一个示例来说明我遇到的困难。
提出第一个问题
第一个问题是,当我们将 cout 的精度设置为 15 时,我们只能得到 12 位小数。让我们编写一个程序,将 a 初始化double
为一个值,设置cout
to的精度15
并将我们的double
插入cout
:
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
double value = 1.1111111111111111111111111f;
cout.precision(15);
cout << value << endl;
return 0;
}
这个程序打印出来
1.11111111111111
1
这个数字在小数点后只有 14位。虽然这不是我们所希望的(小数点后有 14 位而不是 12 位),但看起来我们走在了正确的轨道上。看起来某种舍入行为正在发生。或许我们应该在小数点后的第 13 位和第 14 位加零:
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
double value = 1.1111111111110011111111111f;
cout.precision(15);
cout << value << endl;
return 0;
}
这个程序的输出
1.111111111111
正如我们所希望的那样,只有 12 位数字。所以我们现在有一个简短的、独立的、正确的(可编译的)示例来提出我们的问题。
陈述第一个问题
我std::cout
用来输出一个double
. 我想显示小数点后的前 15 位,或者至少与非零一样多。但是在这段代码中,例如
int main(int argc, char *argv[])
{
double value = 1.1111111111110011111111111f;
cout.precision(15);
cout << value << endl;
return 0;
}
输出1.111111111111
不是1.111111111111001
我预期的那样。我能够通过使用来解决这个问题std::fixed
int main(int argc, char *argv[])
{
double value = 1.1111111111110011111111111f;
cout.precision(15);
cout << std::fixed << value << endl;
return 0;
}
它确实1.111111111111001
按预期输出。不幸的是,即使所有这些数字都为零,使用std::fixed
结果总是在小数点后打印 15 位数字。例如来自的输出
int main(int argc, char *argv[])
{
double value = 1.0f;
cout.precision(15);
cout << std::fixed << value << endl;
return 0;
}
而1.000000000000000
不是1
我所希望的。
cout
当所有剩余数字为零时,如何使用最多输出 15 个十进制数字和更少?
提出第二个问题
第二个问题是,无论我们设置cout
的精度比 15 高多少,我们的输出中只能得到 15 个十进制数字。让我们编写一个程序,将 a 初始化double
为一个值,设置cout
to的精度30
并将我们的double
插入cout
:
int main(int argc, char *argv[])
{
double value = 1.55f;
cout.precision(30);
cout << value << endl;
return 0;
}
这将输出1.5499999523162841796875
,一个由小数点后 22 位组成的数字。这表明我们以某种方式不幸地得到了正好 15 位的输出:与我们在提出第一个问题时遇到的相同类型的舍入行为显然是与从平方根函数返回的值的输出进行交互,使得在这种舍入行为下,将打印小数点后十五位数字。
回答问题
您希望插入 a 以double
显示cout
尽可能多的非零十进制数字,最多 15 个。double
s in的字符串表示cout
应至少满足以下测试:
x.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxf ----> "x.xxxxxxxxxxxxxxx"
x.xxxxx0000000000000000000000000f ----> "x.xxxxx"
x.000000000000000000000000000000f ----> "x"
x.00000000000000x000000000000000f ----> "x.00000000000000x"
这不能通过使用任何明显的方式来实现std::ostream::precision
。正如您所指出的,我们不能使用std::fixed
,因为这肯定会通过我们上面的第三次测试。我们也不能轻易使用默认值,因为如cplusplus.com 所述,
在默认的浮点表示法上,精度字段指定要显示的有意义数字的最大数量,包括小数点之前和之后的数字。
这意味着我们需要考虑小数点前的位数。如果小数点前有 3 位,我们需要使用 15+3 的精度,以此类推。
double
实现此目的的一种方法是使用 a并返回 a的函数std::string
:
std::string stringFromDouble(double value, unsigned int decimalDigits)
{
int integerComponent = abs((value > 0) ? (int)(floor(value)) : (int)(ceil(value)));
std::ostringstream integerStream;
integerStream << integerComponent;
std::string integerString = integerStream.str();
unsigned int integerLength = integerString.length();
std::ostringstream stream;
stream.precision(decimalDigits + integerLength);
stream << value;
std::string str = stream.str();
return str;
}