平方数字を合計するときに、負の数またはゼロを明示的に処理する必要がありますか?


220

最近、クラスでテストを受けました。問題の1つは次のとおりです。

数値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がループに入るのに失敗しません。まったく同じように機能します。

次に、自宅のコンピューターで両方のバージョンの関数を試してみましたが、試したすべての例でまったく同じ答えが得られました。したがって、sum_of_digits_squared(-123)sum_of_digits_squared(123)(これもに等しい14)に等しくなります(追加したはずの詳細がない場合でも)。確かに、私が画面上で数字の数字を印刷しようとすると(重要度の最も低いものから最も重要なものまで)、123私が得3 2 1-123場合と私が得た場合-3 -2 -1(これは実際には一種の興味深い)です。しかし、この問題では、数字を二乗するので問題ではありません。

それで、誰が間違っているのですか?

編集:私の悪い、指定するのを忘れて、それが重要であることを知りませんでした。クラスとテストで使用されるCのバージョンは、C99 以降である必要があります。だから私は(コメントを読むことによって)私のバージョンが何らかの方法で正しい答えを得るだろうと思います。


120
n = n * (-1)とんでもない方法n = -nです。学者だけがそれを考えさえするでしょう。冗長な括弧を追加することは言うまでもありません。
user207421

32
一連の単体テストを記述して、特定の実装が仕様に適合しているかどうかを確認します。コードの一部に(機能的な)問題がある場合は、特定の入力が与えられたときに誤った結果を示すテストを作成することが可能です。
カール・

94
「二乗した数の数字の合計」が3つの完全に異なる方法で解釈できることは興味深いです。(数値が123の場合、可能な解釈は
18、14

23
@ilkkachu:「二乗した数値の数字の合計」。まあ、「二乗した数」は明らかに123 ^ 2 = 15129なので、「二乗した数の数字の合計」は「15129の数字の合計」であり、明らかに1 + 5 + 1 + 2 + 9です。 = 18。
Andreas Rejbrand

15
n = n * (-1)?えっ?教授が探しているのは、 `n = -n 'です。C言語には単項マイナス演算子があります。
Kaz

回答:


245

コメントに浸透しているディスカッションの要約:

  • を事前にテストする正当な理由はありませんn == 0while(n)テストは完全にそのケースを処理します。
  • %負のオペランドを使用した結果の定義が異なっていたとき、教師はまだ以前に慣れている可能性があります。一部の古いシステム(特に、デニスリッチーが最初にCを開発したPDP-11の初期のUnixを含む)では、の結果a % b常にの範囲[0 .. b-1]でした。つまり、-123%10は7でした。そのようなシステムでは、テスト事前にn < 0必要になります。

ただし、2番目の箇条書きは以前のバージョンにのみ適用されます。CとC ++の両方の標準の現在のバージョンでは、整数除算は0に向かって切り捨てられるように定義されているため、が負の場合でも(負の場合もあるn % 10)最後の桁が得られることが保証されています。nn

では、「どういう意味while(n)ですか?」である「と全く同じwhile(n != 0)と答え、「このコードの動作が正常ウィル負のためだけでなく、正のn?」「はい、最新の標準に準拠したコンパイラの下で 質問への答え「なぜインストラクターがそれをマークダウンしましたか?」おそらく、1999年にCに、2010年にC ++に起こった重要な言語の再定義を彼らが認識していないことです。


39
「n == 0 "について事前にテストする十分な理由はありません -技術的には正しいです。しかし、私たちが教育現場で教授について話していることを考えると、彼らは私たちよりも簡潔さよりも明快さを高く評価するかもしれません。n == 0少なくとも追加のテストを追加すると、その場合に何が起こるかが読者にすぐにわかりやすくなります。それがなければ、読者はループが実際にスキップされ、s戻り値のデフォルト値が正しいものであることを自分自身で満たさなければなりません。
ilkkachu

22
また、教授は、学生が気づいていて、関数がゼロの入力で動作する理由と方法について考えたことを知りたい場合があります(つまり、誤って正しい値を返さない)。彼らは、その場合に何が起こるか、ループが0回実行される可能性があることなどを理解していない学生に会った可能性があります。教育の設定を扱うときは、仮定とコーナーケースについてさらに明確にするのが最善です。 ..
ilkkachu

36
@ilkkachuその場合、教師は正しく機能するために、そのようなテストを必要とする課題を配るべきです。
klutt

38
@ilkkachuまあ、私は簡潔さよりも明快さを絶対に感謝しているので、一般的なケースであなたのポイントを取ります-そして、ほとんど常に、必ずしも教育的設定ではありません。しかし、そうは言っても、場合によっては簡潔さ明確になることもあり、メインコードが一般的なケースエッジケースの両方を処理できるように調整できれば、エッジケースの特別なケースでコード(およびカバレッジ分析)を混乱させることはありません、それは美しいことです!初心者でも大歓迎です。
スティーブサミット

49
@ilkkachuその引数により、n = 1などのテストも追加する必要があります。について特別なことは何もありませんn=0。不要な分岐や複雑化を導入してもコードは簡単にはなりませんが、一般的なアルゴリズムが正しいことを示す必要があるだけでなく、すべての特殊なケースについて個別に考える必要があるため、コードが難しくなります。
Voo

107

あなたのコードは完璧です

あなたは絶対に正しく、先生は間違っています。結果にまったく影響を与えないため、このような複雑さを追加する理由はまったくありません。それもバグを紹介します。(下記参照)

まず、nゼロかどうかの個別のチェックは明らかに完全に不要であり、これは簡単に実現できます。正直に言って、先生がこれに異議を唱えているかどうか、先生の能力に実際に質問します。しかし、誰もが時々脳をおならすことができると思います。ただし、追加の行を追加することなく、少し見やすくするためにwhile(n)変更する必要があると思いますwhile(n != 0)。それはささいなことですが。

2つ目はもう少しわかりやすいですが、彼はまだ間違っています。

これは、C11標準6.5.5.p6で述べられていることです。

商a / bが表現可能な場合、式(a / b)* b + a%bはaに等しくなります。それ以外の場合、a / bとa%bの両方の動作は未定義です。

脚注はこう言っています:

これは、しばしば「ゼロへの切り捨て」と呼ばれます。

ゼロへの切り捨ては、の絶対値a/b(-a)/bすべてのaおよびの絶対値と等しいことをb意味します。これは、コードが完全に問題ないことを意味します。

Moduloは簡単な数学ですが、直感に反する場合があります

ただし、結果を二乗しているという事実はここでは実際に重要であるため、教師は注意する必要があるという点を持っています。a%b上記の定義に従って計算することは簡単な数学ですが、直感に反する可能性があります。乗算と除算では、オペランドに等号がある場合、結果は正になります。しかし、それがモジュロになると、結果は最初のオペランドと同じ符号を持ちます。2番目のオペランドは符号にまったく影響しません。例えば、7%3==1しかし(-7)%(-3)==(-1)

以下は、それを示すスニペットです。

$ cat > main.c 
#include <stdio.h>

void f(int a, int b) 
{
    printf("a: %2d b: %2d a/b: %2d a\%b: %2d (a%b)^2: %2d (a/b)*b+a%b==a: %5s\n",
           a, b ,a/b, a%b, (a%b)*(a%b), (a/b)*b+a%b == a ? "true" : "false");
}

int main(void)
{
    int a=7, b=3;
    f(a,b);
    f(-a,b);
    f(a,-b);
    f(-a,-b);
}

$ gcc main.c -Wall -Wextra -pedantic -std=c99

$ ./a.out
a:  7 b:  3 a/b:  2 a%b:  1 (a%b)^2:  1 (a/b)*b+a%b==a:  true
a: -7 b:  3 a/b: -2 a%b: -1 (a%b)^2:  1 (a/b)*b+a%b==a:  true
a:  7 b: -3 a/b: -2 a%b:  1 (a%b)^2:  1 (a/b)*b+a%b==a:  true
a: -7 b: -3 a/b:  2 a%b: -1 (a%b)^2:  1 (a/b)*b+a%b==a:  true

皮肉なことに、あなたの教師は間違っていることで彼の主張を証明しました。

先生のコードに欠陥があります

はい、そうです。入力がINT_MINANDであり、アーキテクチャが2の補数であり、かつ符号ビットが1ですべての値ビットが0であるビットパターンがトラップ値ではない場合(トラップ値なしで2の補数を使用することは非常に一般的です)、教師のコードは未定義の動作を生成しますラインでn = n * (-1)。あなたのコードは-たとえ少しでも- 彼より優れています。そして、コードを不必要に複雑にし、絶対にゼロの値を取得することによって小さなバグを導入することを考えると、あなたのコードははるかに優れていると思います。

つまり、INT_MIN = -32768のコンパイルでは(結果の関数が<-32768または> 32767の入力を受信できない場合でも)、-(-32768i16)の結果が原因で、-32768 の有効な入力により未定義の動作が発生します。 16ビット整数として表現することはできません。(実際には、-(-32768i16)は通常-32768i16と評価され、プログラムは負の数を正しく処理するため、-32768はおそらく誤った結果を引き起こさないでしょう。

ただし、教師はn[-10 ^ 7;の範囲である可能性があることを明確に述べています。10 ^ 7]。16ビット整数は小さすぎます。[少なくとも] 32ビット整数を使用する必要があります。を使用intすると、int必ずしも32ビット整数であるとは限らないことを除いて、彼のコードは安全に思えるかもしれません。16ビットアーキテクチャ用にコンパイルすると、両方のコードスニペットに欠陥があります。しかし、このシナリオINT_MINでは彼のバージョンで上記のバグが再導入されるため、コードはさらに優れています。これを回避するには、どちらのアーキテクチャでも32ビット整数であるのlong代わりに記述できますint。A longは、[-2147483647;の範囲内の任意の値を保持できることが保証されています。2147483647]。C11標準5.2.4.2.1 LONG_MIN-2147483648しかし、許可される最大値(はい、最大値、負の数です)LONG_MIN2147483647です。

コードにどのような変更を加えますか?

あなたのコードはそのままで問題ないので、これらは実際には不満ではありません。私が本当にあなたのコードについて何かを言う必要があるなら、それをほんの少し明確にすることができるいくつかの小さなことがあるようなものです。

  • 変数の名前は少し良いかもしれませんが、理解しやすい短い関数なので、大したことではありません。
  • 状態をからnに変更できますn!=0。意味的には100%相当ですが、少し明確になります。
  • c(名前をに変更しましたdigit)の宣言をwhileループ内でのみ使用するため、whileループ内に移動します。
  • 引数の型をlongに変更して、入力セット全体を処理できるようにします。
int sum_of_digits_squared(long n) 
{
    long sum = 0;

    while (n != 0) {
        int digit = n % 10;
        sum += (digit * digit);
        n /= 10;
    }

    return sum;
}

実際、これは少し誤解を招く可能性があります。これは、前述のように、変数digitが負の値を取得する可能性があるためですが、数字自体が正または負になることはありません。これを回避する方法はいくつかありますが、これは本当につまらないものであり、そのような細かい点は気にしません。特に、最後の桁の個別の関数は、それを取りすぎています。皮肉なことに、これは教師のコードが実際に解決することの1つです。

  • 変数に変更sum += (digit * digit)してsum += ((n%10)*(n%10))digit完全にスキップします。
  • digit負の場合は符号を変更します。ただし、変数名を理解するためだけにコードを複雑にすることはお勧めしません。それは非常に強いコードのにおいです。
  • 最後の桁を抽出する別の関数を作成します。int last_digit(long n) { int digit=n%10; if (digit>=0) return digit; else return -digit; }これは、その関数を別の場所で使用する場合に役立ちます。
  • c最初と同じように名前を付けます。その変数名は有用な情報を提供しませんが、一方で、誤解を招くものでもありません。

しかし、正直なところ、この時点で、より重要な作業に移る必要があります。:)


19
入力がでINT_MINあり、アーキテクチャが2の補数を使用している場合(これは非常に一般的です)、教師のコードは未定義の動作を生成します。 痛い。それは印を残すでしょう。;-)
Andrew Henle

5
(a/b)*b + a%b ≡ aOPのコードは、に加えて、/ゼロに向かって丸められること、およびに依存することにも言及する必要があり(-c)*(-c) ≡ c*cます。それ十分に自明ではないので、標準がすべてを保証しているにもかかわらず、追加のチェックは正当化されると主張することができます。(もちろん、関連する標準セクションにリンクするコメントがあるべきであると同様に主張することもできますが、スタイルのガイドラインは異なります。)
leftaroundabout

7
@MartinRosenau「かもしれない」と言います。これは実際に起こっているのですか、それとも標準または何かによって許可されているのですか、それとも推測しているだけですか?
klutt

6
@MartinRosenau:わかりましたが、これらのスイッチを使用すると、Cではなくなります。GCC / clangには、私が知っているISAの整数除算を壊すスイッチはありません。符号ビットを無視すると、定数の除数に通常の乗法逆数を使用して速度が向上する可能性があります。(しかし、ハードウェア除算命令を持っている私がよく知っているすべてのISAは、C99の方法でそれを実装し、ゼロに向かって切り捨てます。そのため、C %/演算子はidiv、x86やsdivARMなどでコンパイルできます。それでも、それは多くのこととは無関係ですコンパイル時定数除数の高速なコード生成)
Peter Cordes

5
@TonyK AFIK、それは通常それが解決される方法ですが、標準によればそれはUBです。
klutt

20

私はあなたのバージョンも先生のバージョンも完全に好きではありません。先生のバージョンは、あなたが正しく指摘する必要のない余分なテストを実行します。Cのmod演算子は適切な数学的modではありません。負の数mod 10は負の結果を生成します(適切な数学的係数は常に非負です)。しかし、とにかくそれを二乗しているので、違いはありません。

しかし、これは明白とはほど遠いので、私はあなたのコードに先生のチェックではなく、なぜそれが機能するかを説明する大きなコメントを追加します。例えば:

/ *注:係数が2乗されるため、これは負の値に対して機能します* /


9
C %は、符号付きの型であっても、剰余と呼ばれるのが最適です。
Peter Cordes

15
二乗は重要ですが、それは明白な部分だと思います。指摘すべきことは、(たとえば)-7 % 10 実際に-7は3ではないということです
Jacob Raihle

5
「適切な数学係数」は何も意味しません。正しい用語は「ユークリッドモジュロ」(接尾辞に注意!)であり、Cの%演算子はそうではありません。
Jan Hudec

モジュロを解釈する複数の方法の問題を解決するので、私はこの答えが好きです。そのようなことをチャンス/解釈に任せないでください。これはコードゴルフではありません。
ハーパー-

1
「適切な数学係数は常に負ではありません -実際はそうではありません。モジュロ演算の結果は同値クラスですが、そのクラスに属する負ではない最小の数として結果を扱うのが一般的です。
klutt

10

注:この回答を書いているときに、あなたはCを使用していることを明確にしました。私の回答の大部分はC ++に関するものです。ただし、タイトルにはまだC ++が含まれており、質問にはまだC ++のタグが付けられているため、これが他の人に役立つ場合に備えて、とにかく回答することにしました。これまでに見たほとんどの回答は、ほとんど満足できないためです。

現代では C ++では(注:Cがこれに関してどこに立っているのか本当にわかりません)、教授は両方の点で間違っているようです。

最初はこの部分です:

if (n == 0) {
        return 0;
}

C ++では、これは基本的に次のものと同じです

if (!n) {
        return 0;
}

つまり、whileは次のようなものと同等です。

while(n != 0) {
    // some implementation
}

つまり、whileが実行されない場合にifで終了しているだけなので、ループの後に行っていることとifでの操作がいずれにしても同等であるため、ここにifを配置する理由はありません。何らかの理由でこれらは異なっていたと私は言うべきですが、もしそうならあなたはこれを持つ必要があります。

だから、本当に、このifステートメントは、私が間違っていない限り、特に役に立ちません。

2番目の部分は、物事が毛むくじゃらになる場所です。

if (n < 0) {
    n = n * (-1);
}  

問題の核心は、負数の係数の出力が何を出力するかです。

最近のC ++では、これはほとんど明確に定義されているようです。

バイナリ/演算子は商を生成し、バイナリ%演算子は最初の式を2番目の式で除算した余りを生成します。/または%の2番目のオペランドがゼロの場合、動作は未定義です。整数オペランドの場合、/演算子は、小数部分が破棄された代数商を生成します。商a / bが結果のタイプで表現できる場合、(a / b)* b + a%bはaと等しくなります。

以降:

両方のオペランドが負でない場合、残りは負ではありません。そうでない場合、残りの符号は実装定義です。

引用された回答の投稿者が正しく指摘しているように、この方程式の重要な部分はここにあります:

(a / b)* b + a%b

あなたのケースの例をとると、あなたはこのようなものを得るでしょう:

-13/ 10 = -1 (integer truncation)
-1 * 10 = -10
-13 - (-10) = -13 + 10 = -3 

唯一の問題は、その最後の行です。

両方のオペランドが負でない場合、残りは負ではありません。そうでない場合、残りの符号は実装定義です。

つまり、このような場合、記号のみが実装定義であるように見えます。とにかくこの値を二乗しているので、それはあなたのケースでは問題になりません。

とはいえ、これは必ずしも以前のバージョンのC ++またはC99に適用されるとは限らないことに注意してください。それが教授が使用しているものであれば、それが理由かもしれません。


編集:いいえ、私は間違っています。これはC99以降にも当てはまるようです

C99は、a / bが表現可能である場合に以下を要求します。

(a / b)* b + a%bはaに等しい

そして別の場所

整数が除算され、除算が不正確である場合、両方のオペランドが正の場合、/演算子の結果は代数商よりも小さい最大の整数であり、%演算子の結果は正です。いずれかのオペランドが負の場合、/演算子の結果が代数商よりも小さい最大の整数であるか、代数商よりも大きい最小の整数であるかは、%演算子の結果の符号と同様に、実装によって定義されます。商a / bが表現可能な場合、式(a / b)* b + a%bはaに等しくなります。

ANSI CまたはISO Cのどちらかで、-5%10をどのように指定する必要がありますか?

そう、そうです。C99でさえ、これはあなたに影響を与えていないようです。方程式は同じです。


1
あなたが引用した部分はこの答えをサポートしていません。「残りの符号が実装定義されている」を意味するものではない(-1)%10作り出すことができる-1、または1、これは、-1またはを生成できることを意味9し、後者の場合、OPのコードが(-1)/10生成されて-1終了することはありません。
stewbasic

この情報源を教えていただけませんか?私は(-1)/ 10が-1であると信じるのに非常に苦労しています。また、(-1)%10 = 9は支配方程式に違反しているようです。
チップスター

1
@Chipster、で始め(a/b)*b + a%b == a、次にa=-1; b=10、与え(-1/10)*10 + (-1)%10 == -1ます。ここで、-1/10実際に(-infに向かって)切り捨てられた場合(-1/10)*10 == -10、があり(-1)%10 == 9、最初の方程式が一致する必要があります。他の回答の状態と同様に、これは現在の標準内での動作方法ではありませんが、以前の動作方法です。それは、次のような剰余の符号が、どのように分割ラウンドと何残りは、その後については本当にありません持っている式を満足すること。
ilkkachu

1
@Chipsterソースは引用したスニペットです。なお(-1)*10+9=-1、選択肢ので、(-1)/10=-1(-1)%10=9支配方程式に違反していません。一方、選択(-1)%10=1は、どのよう(-1)/10に選択されても、支配方程式を満たすことができません。qそのような整数はありませんq*10+1=-1
stewbasic

8

他の人が指摘したように、すべての真面目なCプログラマにとって「while(n)」が仕事をすることは明らかであるため、n == 0の特別な扱いはナンセンスです。

n <0の動作はそれほど明白ではありません。そのため、次の2行のコードを確認したいと思います。

if (n < 0) 
    n = -n;

または少なくともコメント:

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

正直なところ、nが負の値になる可能性があると考え始めたのはいつですか。コードを書くとき、または先生の発言を読むとき?


1
Nが負かどうかに関係なく、Nの2乗は正になります。では、なぜ最初から看板を削除するのですか?-3 * -3 = 9; 3 * 3 = 9.または、私がこれを学んでから30年で数学が変更されましたか?
Merovex

2
@CB公平に言うと、テストを書いている間、nが負であることに気づきませんでしたが、戻ってきたとき、数値が負の場合でも、whileループがスキップされないという感覚がありました。私は自分のコンピューターでいくつかのテストを行い、それは私の懐疑論を確認しました。その後、この質問を投稿しました。だから、コードを書いている間、私はそれほど深く考えていませんでした。
user010517720

5

これは、失敗した割り当てを思い出させます

90年代にさかのぼります。講師はループについて発芽していましたが、要するに、私たちの割り当ては、0より大きい任意の整数の桁数を返す関数を書くことでした。

したがって、たとえば、の桁数はに321なります3

割り当ては単に桁数を返す関数を書くと言っていましたが、講義でカバーされているように、それが得られるまで10で割るループを使用することが期待されていました

しかし、ループの使用は明示的に述べられていなかったので、私took the log, stripped away the decimals, added 1は、クラス全体の前で非難されました。

ポイントは、この課題の目的は、講義中に学んだことに対する理解をテストすることでした。私が受けた講義から、コンピューターの先生はちょっとぎくしゃくしていることを学びました(しかし、おそらく計画を立てたぐいぐいですか?)


あなたの状況では:

C / C ++で、二乗した数値の数字の合計を返す関数を記述します

私は間違いなく2つの答えを提供したでしょう:

  • 正解(最初に数を二乗する)、および
  • 例に沿って、彼を幸せに保つための誤った答え;-)

5
そして、3番目の数字は数字の合計を二乗していますか?
クリス、

@kriss-うん、私はそれほど賢くない:-(
SlowLearner '17 / 10/17

1
また、学生時代には漠然とした課題も多かったです。先生は少し操作ライブラリを望んでいましたが、私のコードに驚いて、それが機能していないと言いました。私は彼が彼の割り当てでエンディアンを定義したことは決してなく、私が他の選択をする間、彼は下位ビットをビット0として選択したことを彼に指摘しなければなりませんでした。唯一の迷惑な部分は、彼が私に言わなくても違いがどこにあるのかを理解できたはずだったということです。
クリス、

1

一般に、割り当てでは、コードが機能するという理由だけですべてのマークが付けられるわけではありません。また、ソリューションを読みやすく、効率的でエレガントにするためのマークも得られます。これらは常に相互に排他的ではありません。

私が十分にストレス除去できないのは、「意味のある変数名を使用する」です。

あなたの例ではそれほど大きな違いはありませんが、何百万行ものコードを含むプロジェクトで作業している場合は、読みやすさが非常に重要になります。

Cコードでよく見られるもう1つのことは、人が賢く見えることです。むしろ使用するよりながら(N!= 0)私が書くことによってみんなどのように賢いI AMを紹介します(n)をしながら、それは同じことを意味するので。まあそれはあなたが持っているコンパイラで行いますが、あなたが示唆したように、先生の古いバージョンは同じようにそれを実装していません。

一般的な例は、配列内のインデックスを参照しながら同時にインクリメントすることです。Numbers [i ++] = iPrime;

さて、コードに取り組む次のプログラマーは、誰かが誇示できるように、割り当ての前または後にインクリメントされるかどうかを知る必要があります。

1メガバイトのディスク容量は、トイレットペーパーのロールよりも安く、容量を節約しようとするのではなく明確にするために、仲間のプログラマーは幸せになります。


2
Cでプログラミングしたのはほんの数回で、++i評価の前にi++増加し、その後に増加することを知っています。while(n)は共通言語機能でもあります。このようなロジックに基づいて、のようなコードをたくさん見ましたif (foo == TRUE)。ただし、変数名については同意します。
アランocallaghan

3
一般にそれは悪いアドバイスではありませんが、防御的プログラミングの目的で基本的な言語機能(とにかく必然的に出くわすことになる)を回避することは過剰です。とにかく、短くて明確なコードの方が読みやすいことがよくあります。ここでは、クレイジーなperlやbashのワンライナーについて話しているのではなく、本当に基本的な言語機能について話しているだけです。
アランocallaghan

1
確かではありませんが、この回答への反対票が与えられた理由は何ですか。私にとって、ここで述べられていることはすべて真実かつ重要であり、すべての開発者にとって良いアドバイスです。特にスマートアスプログラミングの部分は、while(n)その最悪の例ではありませんが(もっと「好き」ですif(strcmp(one, two))
Kai Huppmann '18

1
i++さらに++i、本番環境で使用する必要があるCコードの違いを理解していない人に変更を許可しないでください。
klutt

2
@PaulMcCarthyどちらもコードを読みやすくするためです。しかし、それが何を意味するかについては意見が分かれています。また、それは客観的ではありません。ある人にとって読みやすいものは、別の人にとっては難しい場合があります。性格と背景知識はこれに強く影響します。私の主なポイントは、盲目的にいくつかのルールに従うことによって最大の読みやすさを得られないということです。
klutt

0

「%」の元の定義と現代の定義のどちらが優れているかについては議論しませんが、このような短い関数に2つのreturnステートメントを書く人は、Cプログラミングをまったく教えるべきではありません。余分なリターンはgotoステートメントであり、Cではgotoを使用していません。さらに、ゼロチェックのないコードは同じ結果になるため、余分なリターンにより読みにくくなりました。


4
「追加の戻りはgotoステートメントであり、Cではgotoを使用しません。」-これは、非常に広範な一般化と非常に遠い範囲の組み合わせです。
SSアン

1
「私たち」は必ずCでgotoを使用します。これには何の問題もありません。
klutt

1
以下のような機能を持つ。また、そこの何も間違っているint findChar(char *str, char c) { if(!str) return -1; int i=0; while(str[i]) { if(str[i] == c) return i; i++; } return -1; }
klutt

1
@PeterKrassoi読みにくいコードを推奨していませんが、単純なgotoや余分なreturnステートメントを回避するためだけにコードが完全に混乱しているように見える例をたくさん見ました。gotoを適切に使用したコードを以下に示します。私は同時にコード読みやすいとmantainしながらのgoto文を削除するためにあなたに挑戦:pastebin.com/aNaGb66Qを
klutt

1
@PeterKrassoiまた、こののバージョンを表示してください:pastebin.com/qBmR78KA
klutt

0

問題の説明はわかりにくいですが、数値の例では、数値の二乗の合計の意味を明確にしています。ここに改良版があります:

整数を受け取り、CおよびC ++の共通のサブセットにおいて機能書くn範囲に[-10 7、10 7 ]あれば:とベース10に戻り、その表現の桁数の二乗の和が例nである123、自分の機能を14(1 2 + 2 2 + 3 2 = 14)が返されます。

あなたが書いた関数は2つの詳細を除いて素晴らしいです:

  • 引数は、型を有していなければならないlongタイプのように指定範囲内のすべての値に適応するためにlong、したがってすべての値を表現するのに十分な範囲で、少なくとも31個の値のビットを有するようにC規格によって保証されている[-10 7、10 7 ]。(int戻り値の型はtype で十分です。最大値は568。です。)
  • %負のオペランドの動作は直感的ではなく、その仕様はC99標準と以前のエディションの間で異なりました。否定的な入力に対してもアプローチが有効である理由を文書化する必要があります。

ここに変更されたバージョンがあります:

int sum_of_digits_squared(long n) {
    int s = 0;

    while (n != 0) {
        /* Since integer division is defined to truncate toward 0 in C99 and C++98 and later,
           the remainder of this division is positive for positive `n`
           and negative for negative `n`, and its absolute value is the last digit
           of the representation of `n` in base 10.
           Squaring this value yields the expected result for both positive and negative `c`.
           dividing `n` by 10 effectively drops the last digit in both cases.
           The loop will not be entered for `n == 0`, producing the correct result `s = 0`.
         */
        int c = n % 10;
        s += c * c;
        n /= 10;
    }
    return s;
}

教師の答えには複数の欠陥があります:

  • タイプ intの値の範囲が不十分である可能性があります。
  • 値を特別なケースにする必要はありません 0
  • 負の値を否定することは不要であり、に対して未定義の動作をする可能性がありますn = INT_MIN

問題の説明に追加の制約(C99との値の範囲n)がある場合、最初の問題のみが問題です。追加のコードでも正しい答えが得られます。

このテストでは良い点が得られますが、ネガティブの問題についての理解を示すために筆記テストでは説明が必要ですn。そうでない場合、教師はあなたが気づかずに運が良かったと思い込む場合があります。口頭試験では、あなたは質問を受け、あなたの答えはそれを釘付けにするでしょう。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.