看似簡單的「判斷字串是否為數字」,實則需處理正負號、前導零、十六進制前綴、小數點、科學計數法等細節。標準庫 strtod/strtol 雖可用,但行為寬鬆(如允許尾部垃圾字元),往往不符合嚴格驗證需求。
採用狀態機思路,單次掃描,O(N) 時間,O(1) 空間。定義巨集簡化字元分類:
#define IS_BLANK(c) ((c)==' ' || (c)=='\t') #define IS_DIGIT(c) ((c)>='0' && (c)<='9') #define IS_ALPHA(c) (((c)>='a'&&(c)<='z') || ((c)>='A'&&(c)<='Z')) #define IS_HEX_DIGIT(c) (((c)>='A'&&(c)<='F') || ((c)>='a'&&(c)<='f'))
is_number()/*** 返回值:0=非數字, 1=十進制整數, 2=十六進制整數, 3=浮點數 ***/
int is_number(char *s) {
if (!s) return 0;
char *p = s;
int base = 10, type = 0;
// 1. 跳過前導空白
while (IS_BLANK(*p)) p++;
// 2. 處理正負號
if (*p == '+' || *p == '-') p++;
// 3. 首字元必須是數字或小數點
if (IS_DIGIT(*p) || *p == '.') {
if (*p != '.') { // 非小數點開頭 → 可能為整數或十六進制
// 3.1 檢測 0x/0X 前綴
if (*p == '0' && p[1] && (p[1]=='x' || p[1]=='X')) {
type = 2; base = 16; p += 2;
}
// 3.2 跳過前導零
while (*p == '0') p++;
// 3.3 讀取數字(十六進制包含 A-F/a-f)
while (IS_DIGIT(*p) || (base==16 && IS_HEX_DIGIT(*p))) p++;
}
// 4. 處理小數點(僅十進制支援)
if (base == 10 && *p && *p == '.') {
type = 3; p++;
while (IS_DIGIT(*p)) p++; // 讀取小數部分
}
// 5. 結束檢查:必須正好到達字串結尾
if (*p == '\0') return type ? type : 1;
}
return 0;
}
#include <stdio.h>
#include <assert.h>
int main() {
assert(is_number(NULL) == 0);
assert(is_number("") == 0);
assert(is_number("9a") == 0);
assert(is_number("908") == 1);
assert(is_number("-908") == 1);
assert(is_number("+09") == 1);
assert(is_number("-+9") == 0);
assert(is_number(" 007") == 1);
assert(is_number("0x9a8F") == 2);
assert(is_number("-0xAB") == 2);
assert(is_number("-9.380") == 3);
assert(is_number("-0xFF.3") == 0); // 十六進制不支援小數
printf("All tests passed!\n");
return 0;
}
e/E):在浮點數分支後增加指數部分解析1_000_000)long long/double)sscanf?sscanf(s, "%lf", &d) 會忽略尾部非數字字元(如 "123abc" 視為合法),且難以區分整數/十六進制/浮點數類型。自定義解析器提供嚴格語法檢查與類型區分。