这是我们老师给我们的编程实践,如果有人可以看我的程序并告诉我我是否做得很好,我将不胜感激。

基本上,我的背景是我加入一家公司,我应该用C编写一个程序,该程序需要三个输入(钢的英尺数,滚珠轴承的数量和青铜的磅数),并返回第二天可以制作多少个铃铛。

吹口哨需要0.5英尺的钢和1个滚珠轴承。制作铃铛需要1.10磅的青铜(.75磅的青铜和1个拍板(.35磅的青铜))。

这是我编写的程序。如果出现问题,我深表歉意。

#include <stdio.h>
#include <stdbool.h>

void PrintLine();
int CalculateWhistles(int steel, int bearings);
int CalculateBells(int bronze);

int main() {
    bool runProgram = true;
    int runAgain = 0;

    while (runProgram == true) {
        int ftSteelIn = 0;
        int numBallBearingIn = 0;
        int ibsBronzeIn = 0;

        printf("\n\n"); // Formatting...
        PrintLine();

        printf("    How many feet of steel was received today:               "); // Steel received
        scanf("%d", &ftSteelIn);
        printf("    How many ball bearings were received today:              "); // Ball bearings received
        scanf("%d", &numBallBearingIn);
        printf("    How many pounds of bronze was received today:            "); // Bronze received
        scanf("%d", &ibsBronzeIn);

        PrintLine(); // Print line

        PrintLine(); // Print line

        // Print company logo and program title
        printf("                                Acme Corporation\n");
        printf("                                Product Report\n");

        PrintLine();// Print line

        // Print number of bells and whistles
        printf("    Number of Bells                                                      %d\n", CalculateBells(ibsBronzeIn));
        printf("    Number of Whistles                                                   %d\n", CalculateWhistles(ftSteelIn, numBallBearingIn));

        PrintLine(); // Print line
        PrintLine();

        printf("\n\n");
        PrintLine();

        // Ask the user if they would like to run the program again
        printf("    Would you like to run the program again? ('1' for yes, '2' for no): ");
        scanf("%d", &runAgain);
        PrintLine();

        // If user does not say 'yes', end the program and say goodbye
        if (runAgain != 1) {
            printf("\n    Exitting the program. Have a good day.\n\n");
            PrintLine();
            runProgram = false;
        }
    }

    return 0;
}

void PrintLine() {
    int i = 0;
    printf("    ");
    while (i <= 70) {
        printf("-");
        i++;
    }
    printf("\n");
}

int CalculateWhistles(int steel, int bearings) {
    steel = steel / 0.5;
    if (steel < bearings) {
        return steel;
    }
    else if (bearings < steel) {
        return bearings;
    }
}

int CalculateBells(int bronze) {
    int bells = bronze / 1.1;
    return bells;
}


它运行正常,没有错误。第二部分是我计算余数的部分,但是在继续该部分之前,我需要一些反馈。

任何建议都会受到赞赏,如何使其更有效或类似(我我只是在学习C)。

评论

忘了添加,讲师想要输出的PrintLine()函数。

既然您提到您是一个初学者,并且您似乎渴望学习,请查看本文。它充满了良好的编程习惯,如果您现在就开始使用它们,将会有更多的时间来学习有关编程的更多信息。

谢谢,我当然想变得更好,有时间我会阅读的! :)

“ PrintLine(); //打印行”这可能是我见过的最好的代码注释:o)

#1 楼

所有编程中大约有50%是关于处理错误的。您说您的代码有效,但是它无法处理错误,因此不起作用。

示例1:

How many feet of steel was received today:  four


程序不起作用不要说“错误,请重试”。而是显示所有提示,而无需等待任何用户输入和退出。

示例2:

How many feet of steel was received today:  3.7


程序没有说“错误,再试一次”。而是显示所有提示,而无需等待任何用户输入和退出。

示例3:

How many feet of steel was received today:  3 4


程序不会说“错误,再试一次”。相反,它假设钢为3英尺。显示“有多少个滚珠轴承”,并且不等待用户输入,并假设有4个滚珠轴承。

示例4:

Would you like to run the program again? ('1' for yes, '2' for no): yes


程序不会说“错误,请重试”。相反,它退出。

示例5:

How many feet of steel was received today:               -99
How many ball bearings were received today:              -99
How many pounds of bronze was received today:            -99


程序决定您可以制作-89铃铛和-198哨子。

示例6:

How many feet of steel was received today:               999999999999999999
How many ball bearings were received today:              999999999999999999
How many pounds of bronze was received today:            999999999999999999


程序决定可以制作-1351471477铃铛和-2147483648哨子。

示例7:

How many feet of steel was received today:               200
How many ball bearings were received today:              100


程序决定您可以使0发出哨声。注意:与前面的示例不同,这不是“无法检查无效输入”的问题。

评论


\ $ \ begingroup \ $
感谢您的建议,我将更改程序。如果有时间,我会添加其中的一些内容,但我不认为他们想要太复杂的程序(这实际上是初学者的课程)。我有点过分了;),所以我要补充一点,但我不希望评分员因必须检查我的代码是否包含所有多余的“垃圾”而感到沮丧。
\ $ \ endgroup \ $
– Qwurticus
2014-2-17在4:03

\ $ \ begingroup \ $
@Brendan另外50%是文档!
\ $ \ endgroup \ $
–杰森C
2014-2-17在6:20

\ $ \ begingroup \ $
如果您更改了那里过于夸张的50%,我会投票给您。如果您正确地计划了应用程序,则添加错误消息和警告所花费的时间不应超过开发总时间的10%。
\ $ \ endgroup \ $
–凯文
2014-2-17在11:52

\ $ \ begingroup \ $
@Kevin试图给出项目调试中任何常规百分比是不可能的。它取决于许多因素:技能,方法论,代码库,编程语言和个性等等。
\ $ \ endgroup \ $
– daramarak
2014-2-17在12:03

\ $ \ begingroup \ $
@daramarak这是关于添加错误处理,而不是调试。
\ $ \ endgroup \ $
–凯文
2014-02-17 12:04

#2 楼

我不确定您在课堂上走了多远,但以下是我在其他答案中没有看到的一些注意事项:




可能您的CalculateWhistles()方法可能无法通过if条件测试并到达非void方法的结尾。即使您什么也不返回,您的方法对于每种情况也应始终具有return语句。例如,如果steel等于bearings,您的代码将做什么?


int CalculateWhistles(int steel, int bearings) {
    steel = steel / 0.5;
    if (steel < bearings) {
        return steel;
    }
    else if (bearings < steel) {
        return bearings;
    }
}



,因为如果测试条件失败,它将无法在我的系统上编译。我的编译器比大多数编译器都更严格,因此这表明您没有在启用警告的情况下进行编译,而应该将其打开。我重写了该方法,以便可以在我的系统上正确编译。

int calculateWhistles(int steel, int bearings)
{
    steel /= 0.5;
    if (steel <= bearings) return steel;
    else return bearings;
}



for方法中使用PrintLine()循环。


void PrintLine() {
    int i = 0;
    printf("    ");
    while (i <= 70) {
        printf("-");
        i++;
    }
    printf("\n");
}



但是,当检查for循环时,它所做的只是打印出一些空间和一堆破折号。 printf()语句在系统上可能会很昂贵,因此要使效率最大化,您应尽可能少地使用它们。

void printLine()
{
     printf("    ------------------------------------------\n");
}


但这是我们真正可以提高效率的地方效率,因为您的老师要求使用此方法:在函数上使用inline关键字。制作函数inline的目的是向编译器暗示,值得花一些额外的精力来比其他方法更快地调用该函数-通常是通过将函数的代码替换为其调用程序。除了消除对调用和返回序列的需求外,它还可能允许编译器在两个函数的主体之间执行某些优化。

这并不意味着您应该对所有内容进行inline

使用inline


而不是#define

具有非常小的函数的inline的最佳选择:更快的代码和更小的可执行文件(更多机会保留在代码缓存中)
当函数很小且经常调用时,

不要使用inline:具有大型函数的


:导致较大的可执行文件,无论调用开销很少导致执行速度更快,无论何时很少使用该函数,都会显着降低性能。
/>
对于您的功能,也可以使用它们。

inline void printLine()
{
     puts("------------------------------------------");
}



方法bells中不需要CalculateBells()变量。


int CalculateBells(int bronze) {
    int bells = bronze / 1.1;
    return bells;
}



您可以仅对带括号的参数返回数学运算。

int calculateBells(int bronze)
{
    return (bronze / 1.1);
}



我的一些评论我认为没有必要。


   printLine(); // Print line




您可以在一行上初始化一种类型的所有变量。



int ftSteelIn = 0;
int numBallBearingIn = 0;
int ibsBronzeIn = 0;



这也会减少代码行。

int ftSteelIn = 0, numBallBearingIn = 0, ibsBronzeIn = 0;


如果将所有方法放在main()之前,则不必事先进行原型设计。但是,由于我们使用的是__inline__,我们将不得不使用prototypes。e
我将让用户输入一个“ y”或“ n”的char来表明他们是否要再次运行该程序,也许一个do-while循环。
您可以在某些地方使用puts()而不是printf()函数。

我个人觉得您的“报告”中的空间太大。


   printf("                                Acme Corporation\n");



但这是您自己决定的。



最终代码(更改了我的实现):

#include <stdio.h>
#include <stdbool.h>
#include <ctype.h>

void printLine();
int calculateWhistles(int steel, int bearings);
int calculateBells(int bronze);

inline void printLine()
{
    puts("----------------------------------------------------");
}

inline int calculateWhistles(int steel, int bearings)
{
    steel /= 0.5;
    if (steel <= bearings) return steel;
    else return bearings;
}

inline int calculateBells(int bronze)
{
    return (bronze / 1.1);
}

int main()
{
    bool isRunning = true;
    char runAgain = 'n'; // default to no

    do
    {
        int ftSteelIn = 0, numBallBearingIn = 0, ibsBronzeIn = 0;

        printf("How many feet of steel was received today? ");
        scanf("%d", &ftSteelIn);
        printf("How many ball bearings were received today? ");
        scanf("%d", &numBallBearingIn);
        printf("How many pounds of bronze was received today? ");
        scanf("%d", &ibsBronzeIn);
        for(; getchar() != '\n'; getchar())
        {
        } // eat superfluous input, including the newline

        // Print company logo and program title
        puts("\nAcme Corporation: Product Report");
        printLine();

        // Print number of bells and whistles
        printf("Number of Bells: %d\n", calculateBells(ibsBronzeIn));
        printf("Number of Whistles: %d\n", calculateWhistles(ftSteelIn, numBallBearingIn));

        // Ask the user if they would like to run the program again
        printf("Would you like to run the program again? (y/N) "); // capital 'N' to suggest it is default
        scanf("%c", &runAgain);

        if (tolower(runAgain) != 'y') isRunning = false;
    } while (isRunning);

    puts("Exitting the program. Have a good day.");
    return 0;
}


评论


\ $ \ begingroup \ $
谢谢!我不认为if / else语句过多。 printLine()语句是多余的,我将其删除(我应该已经编写了程序,然后添加了格式)。使用for循环代替printLine()的while循环之间有真正的区别吗?我们对C的了解还不够(我对Python和C ++相当了解,通常用于循环)。我之所以使用原型,是因为我们的讲师希望我们使用它们来表明我们知道“功能是如何工作的” Rolls的眼睛。
\ $ \ endgroup \ $
– Qwurticus
2014-02-17 5:43



\ $ \ begingroup \ $
@Qwurticus啊,我一直在寻找“原型”这个词! :)在使用for循环的情况下,除了看起来更干净之外,while循环和while循环之间没有太大区别。它应该以完全相同的方式执行(尽管对于for循环,当我查看Assembly输出时,我看到的指令更少,因此效率的提高可以忽略不计)。
\ $ \ endgroup \ $
–syb0rg
2014年2月17日下午5:56

\ $ \ begingroup \ $
好的。我喜欢效率(我同意for循环看起来更干净;))。
\ $ \ endgroup \ $
– Qwurticus
2014年2月17日下午6:05

\ $ \ begingroup \ $
如果打开警告,编译器应提醒您第一点(到达非void函数的末尾)。因此,请始终在打开警告的情况下进行编译。
\ $ \ endgroup \ $
– 200_success
2014年2月17日下午6:45

\ $ \ begingroup \ $
@ syb0rg我赞成,但我认为您应该考虑添加有关内联函数的警告。内联在这个示例中确实有意义,但是您要特别注意Noobies,因为它们可能决定内联所有内容,而不管项目的规模如何。
\ $ \ endgroup \ $
– David Schwartz
14年2月18日在20:27

#3 楼

这里的其他答案非常好,尤其是布伦丹接受的答案。我想补充一条评论。您写道:


任何建议,不胜感激,如何使其更有效...


我想谈谈您对只需简单的评论就可以使其“更加高效”:不用。编写代码,使其正常运行。干净地编写代码,并尽一切可能使您的设计简单明了。记录您的代码,考虑其他可能查看或使用它的人,并在将来考虑自己再回到代码中,而不记得您编写代码时的想法。

程序一旦完成写作,然后问自己:它不符合我设定的硬性能要求吗? UI太慢了吗?是这里或那里的某种算法花费的时间太长,并且实际上以明显和负面的方式影响了我程序的使用?我在某处内存不足吗?如果是这样,那么首先要集中精力在更高层次上改进任何算法或逻辑;举例来说,也许您正在排序大量数据,但它肯定太慢或占用大量资源-首先考虑使用其他排序算法。在对此感到满意之后,然后,如有必要,您可以继续进行进一步的微优化,但前提是您清楚地确定了实际瓶颈所在(例如,分析或测量功能时间,而不仅仅是盲目猜测)。

不要担心在这里或那里浪费一些CPU周期,如果这会导致干净,可维护,清晰的代码,尤其是在最初的开发过程中,您可能会意外地进行更改。您希望避免过早的优化,既过早地分散了您的实际目标,又将您锁定在某个特定的实现上,而这种实现在必要时无法轻易更改。

对于新程序员来说,开始立即进行不必要的微优化是非常普遍的。特别是在无关紧要的领域(例如,编写一个程序,会生成图像文件,但要尝试优化检查输出文件名字符串是否有效的代码。)请不要沿着那条路走!设计->实施->测试->配置文件->优化->测试,只有在不满足您的性能要求时才进行最后3次。

我知道这可能是一般建议,有点为时过早,但是如果您牢记这一点(以及此处其他最佳答案中的所有信息),您将为平滑,高效的体验做好准备。

评论


\ $ \ begingroup \ $
谢谢!我肯定会不耐烦,并且会更加专注于使代码正常工作。我听说过一些恐怖的故事,这些故事的作品效果很好,但无法阅读,对想要改进它的人有什么好处?
\ $ \ endgroup \ $
– Qwurticus
2014-2-17在6:28

\ $ \ begingroup \ $
@Qwurticus实际上,按小时付款对承包商来说很棒。承包商所钟爱的大型程序混乱无比,这会浪费可计费的时间。另外,他们不需要做任何清理,因为一旦合同确定,他们就可以进行下一场演出。
\ $ \ endgroup \ $
– corsiKa
2014-02-18 18:55



\ $ \ begingroup \ $
@corsiKa我希望您能开玩笑,因为这是可怕的建议,特别是对于新程序员而言。如果承包商想要“下次演出”,那不是最好的职业道德。但是,当我最终被雇用来清理您的烂摊子时,它对我有用。我从回头客(及其带来的推荐)中赚取的钱远比从维护中赚到的钱多-再加上良好的工作可以带来长期的领导/咨询工作(以及更快乐的最终用户,那里会有一些业障积分机会-下次获得对某个软件感到沮丧,也许它背后的开发人员也持同样的态度()。
\ $ \ endgroup \ $
–杰森C
2014-02-18 23:18



#4 楼


尽管以小写字母开头的函数是很常见的,但这取决于您编程环境中的首选。您可以选择任何一种情况,最好是提到的一种情况,除非您的讲师使用了某种情况。它只会使代码混乱,尤其是在PrintLine()中完成大部分工作时。由于教师需要这样做,因此您只需保留它们即可。但是,如果要保留或希望保留所分配程序的个人副本,则可以进行此更改。
不需要main()中的else if语句。由于该语句仅与第一个相反,因此可以使用简单的CalculateBells()。您仍然可以保持这种方式,以防万一需要添加某些内容。

此:

steel = steel / 0.5;


可以缩短为this:

steel /= 0.5;


这适用于所有数学运算符和类似情况。


对于这样的条件语句,您不需要不需要包含else

while (runProgram == true)


这也做同样的事情:

while (runProgram)


如果您曾经要与true一起使用,您将具有以下功能:

while (!someCondition)




评论


\ $ \ begingroup \ $
不幸的是,$ printLine()$函数是必需的,老师希望输出的行长。 :/我将if / else更改为else语句,这对我来说是愚蠢的,但是该函数应该返回一个整数,并返回我想要的数字,但是我将其更改为float或加倍,看看它是如何工作的。感谢您的建议!
\ $ \ endgroup \ $
– Qwurticus
2014-2-17的3:58



\ $ \ begingroup \ $
@Qwurticus:在这种情况下可以。只是想确保这不是您要执行的操作。 :-)请确保不要编辑原始代码,否则答案将无效。您仍然可以添加有关打印功能的注释,以使其他人知道。
\ $ \ endgroup \ $
– Jamal♦
2014-02-17 4:00



\ $ \ begingroup \ $
@Jamal只是为了挑剔,至少没有真正的“ C命名约定”,至少在某种意义上不是某种“官方” C方式。更重要的是,程序员应该使用与所使用的其他程序员一致的样式约定,或者如果他是一个人,则至少在其所有代码中都保持一致,以便以后不要混淆自己。用C编写的程序有无数流行的命名约定,当不同的冲突时,真正的问题就会发生。
\ $ \ endgroup \ $
–杰森C
2014年2月17日在6:19



\ $ \ begingroup \ $
实际上,CalculateBells()和CalculateWhistles()应该返回整数。什么样的工厂会发出哨声?半个哨声会发出什么样的声音?
\ $ \ endgroup \ $
– 200_success
2014-02-17 6:40

\ $ \ begingroup \ $
@ 200_success:好一半的声音:-)
\ $ \ endgroup \ $
– Juha Untinen
2014-2-17在11:41

#5 楼

您在代码中嵌入了一些“魔术数字”,这是一个非常糟糕的主意,因为它们难以维护或更改。您应该分离出固定比率,并给它们取有意义的名称。在C语言中使用#define作为数字常量并不是很糟糕,因为它没有const修饰符。因此,添加#define CLAPPER_BRONZE 0.35等。

最后一个微妙的地方是,您说一个哨子需要X和Y,并且已经添加了X和Y的权重(0.75 + 0.35)并实现了一个除以1.1的函数。

在代码中更直接,更透明地捕获您对领域的知识实际上要好得多。如果有人告诉您制作一个小部件需要X和Y,请对其进行编码。然后,担心编码X需要什么。这似乎有些过分,但是更改代码以适应不断变化的情况将更加容易。 (例如,我们改用X代替其他东西。)

评论


\ $ \ begingroup \ $
我不记得我的讲师是否经过了#define,所以我还不想把它扔在那里(这是C语言的新生课程),但是我会在下次编程时牢记这一点实践。您能否解释一下使“域更加透明”的最终观点?如果制作铃铛需要1.1磅青铜,不是简单地将其除以该数量即可获得最佳的铃铛数量吗? :(
\ $ \ endgroup \ $
– Qwurticus
14年2月17日在17:37

\ $ \ begingroup \ $
当X是“ 0.75磅青铜”和“ Y是0.35磅青铜”时,这似乎是巨大的杀伤力,除非您只建议用青铜/(BELL_BRONZE + CLAPPER_BRONZE)代替青铜/ 1.1。您还如何合理地编写要求:制作铃铛需要0.75磅青铜,然后再添加0.35磅青铜的要求?
\ $ \ endgroup \ $
–David Richerby
14年2月17日在18:05

\ $ \ begingroup \ $
当然,对于这个小例子来说,这太过分了。只是想刺激一个真实案例的脑细胞。
\ $ \ endgroup \ $
–数学家
14年2月18日在12:21

#6 楼

次要问题:不要写

steel = steel / 0.5;


将钢除以0.5的结果就不是钢的数量。这是您可以发出的哨声数量的上限。因此,写下

int whistles = steel / 0.5;


现在重要的是:您没有“钢”。你有“钢脚”。如果规格改变并且以米为单位输入钢的数量怎么办?还是以磅为单位,您必须计算长度?最好编写

steelInFeet


,因此,如果规范发生更改,则很明显,您必须在代码中进行更改。例如:

double steelInFeet = steelInMeters / 0.3048;
int whistles = steelInFeet / 0.5; 


评论


\ $ \ begingroup \ $
好的,我将更改名称,并在以后的程序中记住该名称。 :)他们还不希望我们考虑这些事情,所以尽管我怀疑不进行更改会给我带来伤害,但这样做也不会有任何伤害。
\ $ \ endgroup \ $
– Qwurticus
2014年2月17日在17:40

\ $ \ begingroup \ $
此外,x / 0.5与x * 2相同,只是更难阅读,更容易出现舍入/截断错误。 (在这种情况下没有错误,但是可能会发生。)如果有选择,我总是选择乘除法。
\ $ \ endgroup \ $
–达雷尔·霍夫曼(Darrel Hoffman)
14年2月18日在15:09

\ $ \ begingroup \ $
关于口味,我通常会省略In。所以我个人将使用steelFeet或steelMeters。我在训练应用程序中经常这样做。您将获得trainingMinutes和defaultCourseDurationHours。尽管100%的我每天都会用SteelInFeet代替钢!
\ $ \ endgroup \ $
– corsiKa
14年2月18日在18:57

#7 楼

人们对代码说了很多,所以我只说一分。

您的评论很糟糕。对于初学者来说,注释很困难,因为很难告诉您代码的哪些部分是显而易见的,并且不需要更多说明,添加注释会为您带来哪些好处。随着经验的增加,您会对此有所改善。

您的大多数评论都是完全多余的,因为它们没有添加任何解释。例如,您有几个

PrintLine(); // Print line


的实例。使用注释来记录PrintLine函数,而不是调用它的位置。很明显,它会打印一行,因此您无需继续说下去。

同样,您对读取用户输入的行的评论也无法帮助读者理解代码:很明显,这些行读入了钢,黄铜和球轴承的数量。

,您根本没有记录这两个Calculate函数。这是程序中真正需要解释的唯一部分,因为它是唯一使用要解决的问题的特定事实的部分(例如,您需要用什么材料制作铃声或口哨声)。像

int CalculateBells (int bronze)
// Calculate the number of bells that can be made from a given amount
// of bronze.  Each bell requires 1.1lb.
{
     [...]
}


评论


\ $ \ begingroup \ $
感谢您的建议,我的确意识到自己的评论多么愚蠢,自那以后又重新整理了一下。
\ $ \ endgroup \ $
– Qwurticus
14年2月17日在17:31

\ $ \ begingroup \ $
欢迎来到CR。很棒的第一答案。 +1
\ $ \ endgroup \ $
–rolfl
14年2月17日在17:34

#8 楼

您是否真的要使用整数进行这些计算?您不应该使用浮点变量吗?

让我印象深刻的是,其他答案都集中在美学上,却没有评论代码中最关键的问题:它甚至都不起作用使用正确的输入。

编辑:实际上,其他答案都涉及这个问题,是我未能注意到这并不是一个错误,因为使用int确实达到了理想的答案。 >

评论


\ $ \ begingroup \ $
如果它不起作用,我会发表评论。分配给int会自动将浮点值朝零舍入到下一个整数。如果您使用1.1磅的铜作为响铃,则“ int bells =铜/ 1.1;”确实可以提供正确的结果,因为您无法建立3 1/2铃。
\ $ \ endgroup \ $
– gnasher729
2014-02-17 12:46



\ $ \ begingroup \ $
确实可行,因为将结果四舍五入到最接近的int是合理的行为。此外,@ Brendan接受的答案指出该程序无法接受浮点输入。
\ $ \ endgroup \ $
– 200_success
2014-2-17在12:54

\ $ \ begingroup \ $
我纠正了。我在手机上阅读此内容,但没有意识到@Brendan的答案已经涉及浮点问题。整数截断也确实很理想,我很糟糕。
\ $ \ endgroup \ $
–蜘蛛
14年2月17日在14:18

\ $ \ begingroup \ $
@ 200_success:是的,这也是让我困惑的答案。足够的文档会有所帮助,但是OP并不能在这方面出类拔萃。
\ $ \ endgroup \ $
– Jamal♦
2014年2月17日在17:40

\ $ \ begingroup \ $
这很不好,以后我会发布原始文档/问题以使其更加清晰。此问题还有第二个“额外积分”部分,可以计算剩余部分并将其添加到第二天的物料中。那将使用浮点数/双精度数。
\ $ \ endgroup \ $
– Qwurticus
14年2月17日在17:45