Mã của bạn là hoàn toàn tốt
Bạn hoàn toàn chính xác và giáo viên của bạn sai. Hoàn toàn không có lý do nào để thêm sự phức tạp đó, vì nó hoàn toàn không ảnh hưởng đến kết quả. Nó thậm chí còn giới thiệu một lỗi. (Xem bên dưới)
Đầu tiên, việc kiểm tra riêng nếu n
bằng 0 rõ ràng là hoàn toàn không cần thiết và điều này rất dễ nhận ra. Thành thật mà nói, tôi thực sự đặt câu hỏi về năng lực giáo viên của bạn nếu anh ấy phản đối về điều này. Nhưng tôi đoán mọi người có thể có một bộ não xì hơi theo thời gian. Tuy nhiên, tôi nghĩ rằng while(n)
nên thay đổi thành while(n != 0)
vì nó thêm một chút rõ ràng mà không tốn thêm một dòng. Đó là một điều nhỏ mặc dù.
Điều thứ hai thì dễ hiểu hơn một chút, nhưng anh vẫn sai.
Đây là những gì tiêu chuẩn C11 6.5.5.p6 nói:
Nếu thương số a / b là đại diện, biểu thức (a / b) * b + a% b sẽ bằng a; mặt khác, hành vi của cả a / b và% b là không xác định.
Các chú thích nói điều này:
Điều này thường được gọi là "cắt ngắn về không".
Cắt ngắn về 0 có nghĩa là giá trị tuyệt đối a/b
bằng với giá trị tuyệt đối (-a)/b
cho tất cả a
và b
điều đó có nghĩa là mã của bạn hoàn toàn ổn.
Modulo là toán dễ, nhưng có thể phản trực giác
Tuy nhiên, giáo viên của bạn có một điểm mà bạn nên cẩn thận, bởi vì thực tế là bạn bình phương kết quả thực sự rất quan trọng ở đây. Tính toán a%b
theo định nghĩa trên là toán học dễ dàng, nhưng nó có thể đi ngược lại với trực giác của bạn. Đối với phép nhân và phép chia, kết quả là dương nếu các toán hạng có dấu bằng. Nhưng khi nói đến modulo, kết quả có cùng dấu với toán hạng đầu tiên . Toán hạng thứ hai hoàn toàn không ảnh hưởng đến dấu hiệu. Ví dụ, 7%3==1
nhưng (-7)%(-3)==(-1)
.
Đây là một đoạn chứng minh điều đó:
$ 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
Vì vậy, trớ trêu thay, giáo viên của bạn đã chứng minh quan điểm của mình bằng cách sai.
Mã giáo viên của bạn bị thiếu sót
Vâng, nó thực sự là. Nếu đầu vào là INT_MIN
AND thì kiến trúc là hai phần bù VÀ mẫu bit trong đó bit dấu là 1 và tất cả các bit giá trị là 0 KHÔNG phải là giá trị bẫy (sử dụng phần bù hai không có giá trị bẫy là rất phổ biến) thì mã của giáo viên của bạn sẽ mang lại hành vi không xác định trên dòng n = n * (-1)
. Mã của bạn là - nếu có một chút như vậy - tốt hơn của anh ấy. Và xem xét giới thiệu một lỗi nhỏ bằng cách làm cho mã trở nên phức tạp không cần thiết và đạt được giá trị hoàn toàn bằng không, tôi nói rằng mã của bạn RẤT NHIỀU.
Nói cách khác, trong các phần tổng hợp trong đó INT_MIN = -32768 (mặc dù hàm kết quả không thể nhận được đầu vào là <-32768 hoặc> 32767), đầu vào hợp lệ của -32768 gây ra hành vi không xác định, vì kết quả của - (- 32768i16) không thể được biểu diễn dưới dạng số nguyên 16 bit. (Trên thực tế, -32768 có thể sẽ không gây ra kết quả không chính xác, bởi vì - (- 32768i16) thường ước tính là -32768i16 và chương trình của bạn xử lý chính xác các số âm.) (SHRT_MIN có thể là -32768 hoặc -32767, tùy thuộc vào trình biên dịch.)
Nhưng giáo viên của bạn tuyên bố rõ ràng rằng n
có thể nằm trong phạm vi [-10 ^ 7; 10 ^ 7]. Số nguyên 16 bit quá nhỏ; bạn sẽ phải sử dụng [ít nhất] một số nguyên 32 bit. Việc sử dụng int
dường như làm cho mã của anh ấy an toàn, ngoại trừ đó int
không nhất thiết là số nguyên 32 bit. Nếu bạn biên dịch cho kiến trúc 16 bit, cả hai đoạn mã của bạn đều bị thiếu sót. Nhưng mã của bạn vẫn tốt hơn nhiều vì kịch bản này giới thiệu lại lỗi INT_MIN
đã được đề cập ở trên với phiên bản của anh ta. Để tránh điều này, bạn có thể viết long
thay vì int
, đó là số nguyên 32 bit trên một trong hai kiến trúc. A long
được đảm bảo có thể giữ bất kỳ giá trị nào trong phạm vi [-2147483647; 2147483647]. Tiêu chuẩn C11 5.2.4.2.1 LONG_MIN
thường-2147483648
nhưng giá trị tối đa (có, tối đa, đó là số âm) cho phép LONG_MIN
là 2147483647
.
Những thay đổi tôi sẽ làm cho mã của bạn?
Mã của bạn là tốt như vậy, vì vậy đây không phải là khiếu nại. Sẽ giống như vậy nếu tôi thực sự, thực sự cần nói bất cứ điều gì về mã của bạn, có một số điều nhỏ có thể làm cho nó rõ ràng hơn một chút.
- Tên của các biến có thể tốt hơn một chút, nhưng nó là một hàm ngắn dễ hiểu, vì vậy nó không phải là vấn đề lớn.
- Bạn có thể thay đổi điều kiện từ
n
sang n!=0
. Về mặt ngữ nghĩa, nó tương đương 100%, nhưng nó làm cho nó rõ ràng hơn một chút.
- Di chuyển khai báo
c
(mà tôi đã đổi tên thành digit
) vào bên trong vòng lặp while vì nó chỉ được sử dụng ở đó.
- Thay đổi loại đối số
long
để đảm bảo nó có thể xử lý toàn bộ bộ đầu vào.
int sum_of_digits_squared(long n)
{
long sum = 0;
while (n != 0) {
int digit = n % 10;
sum += (digit * digit);
n /= 10;
}
return sum;
}
Trên thực tế, điều này có thể gây ra một chút sai lầm bởi vì - như đã đề cập ở trên - biến digit
có thể nhận được giá trị âm, nhưng bản thân một chữ số không bao giờ dương hoặc âm. Có một vài cách xung quanh vấn đề này, nhưng đây là sự thật sự gây nghiện, và tôi sẽ không quan tâm đến những chi tiết nhỏ như vậy. Đặc biệt là chức năng riêng biệt cho chữ số cuối cùng đang đưa nó đi quá xa. Trớ trêu thay, đây là một trong những điều mà mã giáo viên của bạn thực sự giải quyết được.
- Thay đổi
sum += (digit * digit)
để sum += ((n%10)*(n%10))
và bỏ qua biến digit
hoàn toàn.
- Thay đổi dấu
digit
nếu âm. Nhưng tôi sẽ khuyên mạnh mẽ chống lại việc làm cho mã phức tạp hơn chỉ để làm cho một tên biến có ý nghĩa. Đó là một mùi mã RẤT mạnh mẽ.
- Tạo một hàm riêng biệt trích xuất chữ số cuối cùng.
int last_digit(long n) { int digit=n%10; if (digit>=0) return digit; else return -digit; }
Điều này rất hữu ích nếu bạn muốn sử dụng chức năng đó ở một nơi khác.
- Chỉ cần đặt tên
c
như bạn ban đầu làm. Tên biến đó không cung cấp bất kỳ thông tin hữu ích nào, nhưng mặt khác, nó cũng không gây hiểu lầm.
Nhưng thành thật mà nói, tại thời điểm này bạn nên chuyển sang công việc quan trọng hơn. :)
n = n * (-1)
là một cách vô lý để viếtn = -n
; Chỉ có một học giả thậm chí sẽ nghĩ về nó. Hãy để một mình thêm các dấu ngoặc đơn dư thừa.