「幫忙看著這C語言程序錯哪了?求公倍數」——貼出的代碼存在函數定義位置錯誤、for循環缺少大括號、參數名衝突、算法效率低等多重問題。本文逐層剖析錯誤根因,提供標準修正版、優化版、工程化版三個演進方案。
// 原代碼 (標註錯誤)
#include
void main() // ❌ 1. main返回類型應為 int
{
int gbx(int x,int y,int z); // ❌ 2. 函數聲明不應在函數內部 (C99前非標準)
int a,b,c,n;
printf("x,y,z=?"); // ❌ 3. 缺少換行,提示不友好
scanf("%d,%d,%d",&a,&b,&c); // ❌ 4. 格式字符串逗號要求輸入必帶逗號,不友好
n=gbx(a,b,c);
printf("%d",n); // ❌ 5. 缺少換行,輸出擠在一起
} // ❌ 6. 這裡缺少右大括號 (原代碼漏寫)
int gbx(int x,int y,int z) // ❌ 7. 函數定義在 main 之後,需前置聲明
{
int k;
k=x>y?x:y; // ❌ 8. 只比較了 x,y,未包含 z
k=k>z?k:z; // 修正後 k 為三者最大值
for(;k<=x*y*z;k++) // ❌ 9. 缺少左大括號!循環體只包含下一條語句
(if (k%x==0&&k%y==0&&k%z==0) // ❌ 10. if 前多出括號,語法錯誤
{return(k); // ❌ 11. return 後 break 永遠不執行
break;} // ❌ 12. else if 邏輯多餘:k 遞增到 x*y*z 時必能整除
else if (k==x*y*z) // 但這樣寫掩蓋了邏輯意圖
{return(k);
break;}
} // ❌ 13. 缺少右大括號匹配函數體
}
#include
#include // for abs()
// 前置聲明
int lcm_three(int x, int y, int z);
int main() {
int a, b, c, n;
printf("請輸入三個正整數 (空格分隔): ");
if (scanf("%d %d %d", &a, &b, &c) != 3) {
fprintf(stderr, "輸入錯誤:需要三個整數\n");
return 1;
}
// 處理負數/零
if (a <= 0 || b <= 0 || c <= 0) {
fprintf(stderr, "輸入必須為正整數\n");
return 1;
}
n = lcm_three(a, b, c);
printf("最小公倍數: %d\n", n);
return 0;
}
// 最大公約數 (歐幾里得算法,O(log min(a,b)))
int gcd(int a, int b) {
while (b != 0) {
int r = a % b;
a = b;
b = r;
}
return a;
}
// 兩數最小公倍數:lcm(a,b) = a / gcd(a,b) * b (避免溢出)
int lcm_two(int a, int b) {
return a / gcd(a, b) * b;
}
// 三數最小公倍數:lcm(a,b,c) = lcm(lcm(a,b), c)
int lcm_three(int x, int y, int z) {
return lcm_two(lcm_two(x, y), z);
}
#include
#include
#include
// 使用 64 位中間結果防溢出
int64_t gcd64(int64_t a, int64_t b) {
while (b) {
int64_t r = a % b;
a = b;
b = r;
}
return a;
}
int64_t lcm64(int64_t a, int64_t b) {
if (a == 0 || b == 0) return 0;
int64_t g = gcd64(a, b);
// 溢出檢查:a/g * b > INT64_MAX ?
if (a / g > INT64_MAX / b) return -1; // 標記溢出
return a / g * b;
}
// 可變參數版本:計算任意個數的最小公倍數
#include
int64_t lcm_variadic(int count, ...) {
if (count <= 0) return 0;
va_list args;
va_start(args, count);
int64_t result = va_arg(args, int64_t);
if (result <= 0) { va_end(args); return 0; }
for (int i = 1; i < count; i++) {
int64_t val = va_arg(args, int64_t);
if (val <= 0) { va_end(args); return 0; }
result = lcm64(result, val);
if (result == -1) { va_end(args); return -1; } // 溢出
}
va_end(args);
return result;
}
int main() {
// 測試
printf("lcm(12, 18) = %lld\n", lcm64(12, 18)); // 36
printf("lcm(5, 7, 10) = %lld\n", lcm_variadic(3, 5, 7, 10)); // 70
printf("lcm(1000000, 2000000) = %lld\n", lcm64(1000000, 2000000)); // 2000000
// 溢出測試
int64_t r = lcm64(INT64_MAX/2, INT64_MAX/3);
if (r == -1) printf("溢出檢測生效\n");
return 0;
}
// lcm.h
#ifndef LCM_H
#define LCM_H
#include
typedef enum {
LCM_OK = 0,
LCM_ERR_INVALID_ARG = 1, // 參數 ≤ 0
LCM_ERR_OVERFLOW = 2, // 結果超出 int64_t
LCM_ERR_NO_ARGS = 3 // 無參數
} LcmError;
// 計算兩數最小公倍數
LcmError lcm_two(int64_t a, int64_t b, int64_t *result);
// 計算三數最小公倍數
LcmError lcm_three(int64_t a, int64_t b, int64_t c, int64_t *result);
// 計算數組最小公倍數
LcmError lcm_array(const int64_t *arr, size_t n, int64_t *result);
// 字符串化錯誤碼
const char* lcm_strerror(LcmError err);
#endif
// lcm.c
#include "lcm.h"
#include
static int64_t gcd64(int64_t a, int64_t b) {
while (b) { int64_t r = a % b; a = b; b = r; }
return a;
}
LcmError lcm_two(int64_t a, int64_t b, int64_t *result) {
if (!result) return LCM_ERR_INVALID_ARG;
if (a <= 0 || b <= 0) return LCM_ERR_INVALID_ARG;
int64_t g = gcd64(a, b);
if (a / g > INT64_MAX / b) return LCM_ERR_OVERFLOW;
*result = a / g * b;
return LCM_OK;
}
LcmError lcm_three(int64_t a, int64_t b, int64_t c, int64_t *result) {
int64_t tmp;
LcmError err = lcm_two(a, b, &tmp);
if (err != LCM_OK) return err;
return lcm_two(tmp, c, result);
}
LcmError lcm_array(const int64_t *arr, size_t n, int64_t *result) {
if (!arr || n == 0) return LCM_ERR_NO_ARGS;
if (!result) return LCM_ERR_INVALID_ARG;
int64_t res = arr[0];
if (res <= 0) return LCM_ERR_INVALID_ARG;
for (size_t i = 1; i < n; i++) {
if (arr[i] <= 0) return LCM_ERR_INVALID_ARG;
LcmError err = lcm_two(res, arr[i], &res);
if (err != LCM_OK) return err;
}
*result = res;
return LCM_OK;
}
const char* lcm_strerror(LcmError err) {
switch (err) {
case LCM_OK: return "成功";
case LCM_ERR_INVALID_ARG: return "無效參數 (必須為正整數)";
case LCM_ERR_OVERFLOW: return "結果溢出 int64_t 範圍";
case LCM_ERR_NO_ARGS: return "無輸入參數";
default: return "未知錯誤";
}
}
// test_lcm.c (單元測試)
#include "lcm.h"
#include
#include
void test_basic() {
int64_t r;
assert(lcm_two(12, 18, &r) == LCM_OK && r == 36);
assert(lcm_three(5, 7, 10, &r) == LCM_OK && r == 70);
assert(lcm_two(0, 5, &r) == LCM_ERR_INVALID_ARG);
assert(lcm_two(-3, 5, &r) == LCM_ERR_INVALID_ARG);
printf("基礎測試通過\n");
}
void test_overflow() {
int64_t r;
assert(lcm_two(INT64_MAX/2, INT64_MAX/3, &r) == LCM_ERR_OVERFLOW);
printf("溢出測試通過\n");
}
int main() {
test_basic();
test_overflow();
printf("所有測試通過!\n");
return 0;
}
| 算法 | 時間複雜度 | 空間複雜度 | 適用場景 |
|---|---|---|---|
| 暴力遞增 (原代碼) | O(x*y*z) 指數級 | O(1) | 極小數值教學演示 |
| 歐幾里得 GCD + LCM公式 | O(log min(a,b)) | O(1) | 通用高性能標準算法 |
| 質因子分解法 | O(√n) 分解 + 合併 | O(因子數) | 需要質因子分解信息時 |
從「能跑通」到「工業級」,關鍵在於:標準庫算法替代暴力、錯誤碼替代崩潰、模組化替代大雜烩、測試驅動替代手動驗證、文檔契約替代口頭傳承。這份演進路徑,適用於所有C語言數值計算類程序的重構。