我最近在班上进行了一次测试。其中一个问题如下:

给定一个数字n,用C/ c++编写一个函数,返回该数字的数字和的平方。(以下是重要的)。n的取值范围是[-(10^7),10^7]。示例:如果n = 123,函数应该返回14(1^2 + 2^2 + 3^2 = 14)。

这是我写的函数:

int sum_of_digits_squared(int n) 
{
    int s = 0, c;

    while (n) {
        c = n % 10;
        s += (c * c);
        n /= 10;
    }

    return s;
}

在我看来是这样的。所以现在测试回来了,我发现老师没有给我所有的分数,原因是我不明白。根据他的说法,为了使我的功能完整,我应该添加以下细节:

int sum_of_digits_squared(int n) 
 {
    int s = 0, c;

    if (n == 0) {      //
        return 0;      //
    }                  //
                       // THIS APPARENTLY SHOULD'VE 
    if (n < 0) {       // BEEN IN THE FUNCTION FOR IT
        n = n * (-1);  // TO BE CORRECT
    }                  //

    while (n) {
        c = n % 10;
        s += (c * c);
        n /= 10;
    }

    return s;
}

它的论点是数字n在[-(10^7),10^7]的范围内,所以它可以是一个负数。但是我不知道我自己版本的函数哪里失败了。如果我理解正确,while(n)的含义是while(n != 0),而不是while(n > 0),所以在我的函数版本中,数字n不会失败进入循环。这还是一样的。

Then, I tried both versions of the function on my computer at home and I got exactly the same answers for all the examples that I tried. So, sum_of_digits_squared(-123) is equal to sum_of_digits_squared(123) (which again, is equal to 14) (even without the detail that I apparently should've added). Indeed, if I try to print on the screen the digits of the number (from least to greatest in importance), in the 123 case I get 3 2 1 and in the -123 case I get -3 -2 -1 (which is actually kind of interesting). But in this problem it wouldn't matter since we square the digits.

那么,谁错了呢?

编辑:我的错,我忘了说明,也不知道这很重要。我们的类和测试中使用的C版本必须是C99或更新版本。所以我猜(通过阅读评论)我的版本无论如何都能得到正确答案。


当前回答

我不会争论'%'的原始定义还是现代定义更好,但任何在这么短的函数中编写两个return语句的人都不应该教C编程。Extra return是一个goto语句,我们在C中不用goto。 此外,没有零检查的代码也会有相同的结果,额外的返回使其更难阅读。

其他回答

你和你老师的说法我都不太喜欢。你的老师的版本会做额外的测试,而你正确地指出这些测试是不必要的。C的模运算符不是一个正确的数学模:一个负数模10将产生一个负的结果(正确的数学模总是非负的)。但既然你把它平方了,没什么区别。

但这还远远不够明显,所以我不会在代码中添加老师的检查,而是添加一个大注释,解释为什么它可以工作。例如:

注意:这适用于负值,因为模量得到平方*/

一般在作业中,不是所有的分数都是因为代码可以运行。你也会因为你的解决方案易于阅读、高效和优雅而得分。这些事情并不总是相互排斥的。

我强调的一点是“使用有意义的变量名”。

在你的例子中,这没有太大的区别,但是如果你正在处理一个有一百万行代码的项目,可读性就变得非常重要了。

我在C代码中看到的另一件事是人们试图看起来聪明。 我不使用while(n != 0),而是用while(n)来告诉大家我有多聪明,因为它的意思是一样的。 在你的编译器中是这样的,但正如你所建议的,老师的旧版本并没有以同样的方式实现它。

一个常见的例子是引用数组中的一个索引,同时增加它;数字[i++] = iPrime;

现在,下一个处理代码的程序员必须知道i是在赋值之前还是之后递增的,这样就有人可以炫耀了。

1兆字节的磁盘空间比一卷卫生纸便宜,追求清晰而不是试图节省空间,你的程序员同事会更开心。

我不会争论'%'的原始定义还是现代定义更好,但任何在这么短的函数中编写两个return语句的人都不应该教C编程。Extra return是一个goto语句,我们在C中不用goto。 此外,没有零检查的代码也会有相同的结果,额外的返回使其更难阅读。

总结一下评论中流传的讨论:

There is no good reason to test in advance for n == 0. The while(n) test will handle that case perfectly. It's likely your teacher is still used to earlier times, when the result of % with negative operands was differently defined. On some old systems (including, notably, early Unix on a PDP-11, where Dennis Ritchie originally developed C), the result of a % b was always in the range [0 .. b-1], meaning that -123 % 10 was 7. On such a system, the test in advance for n < 0 would be necessary.

但第二颗子弹只适用于更早的时代。在C和c++标准的当前版本中,整数除法被定义为向0截断,因此即使n为负数,n % 10也保证会给出n的最后一位(可能是负数)。

因此,对于“while(n)的含义是什么?”这个问题的答案是“与while(n != 0)完全相同”,而对于“这段代码对于负n和正n是否都能正常工作?”的答案是“在任何现代的、符合标准的编译器下都能正常工作”。“那为什么老师要把它记下来?”这个问题的答案很可能是他们没有意识到1999年C语言和2010年左右c++发生了重大的语言重新定义。

正如其他人指出的那样,对n==0的特殊处理是毫无意义的,因为对于每个认真的C程序员来说,显然“while(n)”可以完成这项工作。

n<0的行为不是那么明显,这就是为什么我更喜欢看到这两行代码:

if (n < 0) 
    n = -n;

或者至少是一个评论:

// don't worry, works for n < 0 as well

说实话,你什么时候开始考虑n可能是负的?写代码的时候,还是读老师的评语的时候?