總結一下在 C 語言中用于字符串解析(特別是從文件中讀取行并提取數據)的常用函數、
核心任務:?通常是從文件中讀取一行文本(一個字符串),然后從這個字符串中提取出需要的數據(比如數字、單詞等)。
常用函數總結:
-
fgets()?- 讀取行
-
頭文件:?<stdio.h>
-
原型:?char *fgets(char *str, int n, FILE *stream);
-
作用:?從指定的文件流?stream?中讀取一行,并將其存儲到?str?指向的字符數組中。
-
行為:
-
最多讀取?n-1?個字符(為末尾的空字符?\0?留出空間)。
-
在讀到換行符?\n、文件結束符 (EOF) 或達到?n-1?個字符時停止。
-
如果讀到了換行符?\n,它會被存儲在字符串?str?中。
-
總會在讀取的字符后面添加一個空字符?\0。
-
安全:?防止緩沖區溢出,因為讀取的字符數有上限。
-
-
返回值:?成功時返回?str?指針;如果遇到文件結尾且未讀取任何字符,或者發生錯誤,則返回?NULL。——不需要額外接受值等于,會返回在str里面
-
解析相關:?獲取需要解析的原始字符串行的主要方式。?注意:?通常需要手動去除末尾可能存在的換行符?\n。
char buffer[256]; if (fgets(buffer, sizeof(buffer), fp) != NULL) {// 移除可能的換行符buffer[strcspn(buffer, "\n")] = '\0'; // 查找第一個換行符并替換為\0// 現在 buffer 包含了一行沒有換行符的文本,可以進行解析 }
-
strcspn()?(來自?<string.h>)?在這里用來查找第一個換行符的位置。
-
-
strlen()?- 獲取字符串長度
-
頭文件:?<string.h>
-
原型:?size_t strlen(const char *str);?(size_t?通常是?unsigned int?或?unsigned long)
-
作用:?計算字符串?str?的長度,即第一個空字符?\0?之前的字符數。
-
行為:?不包括結尾的?\0。
-
返回值:?字符串的長度。
-
解析相關:?雖然不直接解析內容,但在處理字符串(如檢查是否為空、設置循環邊界等)時非常有用。
-
-
atoi()?- 字符串轉整數 (ASCII to Integer)
-
頭文件:?<stdlib.h>
-
原型:?int atoi(const char *str);
-
作用:?將表示整數的字符串?str?轉換為?int?類型。
-
行為:
-
跳過開頭的空白字符(如空格、制表符)。
-
讀取連續的數字字符,直到遇到非數字字符或字符串末尾?\0。
-
不進行錯誤檢查!?如果字符串不是有效的整數表示(例如是空的、"abc"、或者超出?int?范圍),行為是:
-
無法轉換或為空:通常返回?0。你無法區分輸入是?"0"?還是無效輸入。
-
超出范圍:行為未定義(可能溢出并得到錯誤結果)。
-
-
-
返回值:?轉換后的?int?值,或者在無法轉換時返回?0。
-
解析相關:?用于從字符串片段中提取整數。僅適用于簡單情況且不關心錯誤處理時。
-
-
atof()?- 字符串轉雙精度浮點數 (ASCII to Float)
-
頭文件:?<stdlib.h>
-
原型:?double atof(const char *str);
-
作用:?將表示浮點數的字符串?str?轉換為?double?類型。
-
行為:
-
類似?atoi,跳過空白,讀取數字、小數點、可選的?e?或?E?指數部分。
-
同樣不進行錯誤檢查!
-
無法轉換或為空:通常返回?0.0。你無法區分輸入是?"0.0"?還是無效輸入。
-
超出范圍:行為未定義。
-
-
-
返回值:?轉換后的?double?值,或者在無法轉換時返回?0.0。
-
解析相關:?用于從字符串片段中提取浮點數。僅適用于簡單情況且不關心錯誤處理時。
-
-
strtod()?- 字符串轉雙精度浮點數 (更健壯)
-
頭文件:?<stdlib.h>
-
原型:?double strtod(const char *nptr, char **endptr);
-
作用:?將字符串?nptr?的初始部分轉換為?double?值。
-
行為:
-
比?atof?更強大、更安全。
-
endptr?是一個二級指針。如果它不是?NULL,strtod?會將指向轉換結束后的第一個字符的指針存儲在?*endptr?中。這允許你:
-
檢查是否發生了轉換(如果?*endptr == nptr,則沒有字符被轉換)。
-
繼續從?*endptr?開始解析字符串的剩余部分。
-
-
進行錯誤檢查:
-
如果發生上溢,返回?HUGE_VAL(或?-HUGE_VAL),并將全局變量?errno?設置為?ERANGE(需要?#include <errno.h>?和?#include <math.h>)。
-
如果發生下溢,返回?0.0,并將?errno?設置為?ERANGE。
-
如果無法轉換,返回?0.0,并且?*endptr?會等于?nptr。
-
-
-
返回值:?轉換后的?double?值。
-
解析相關:?強烈推薦使用此函數代替?atof?來進行字符串到?double?的轉換,因為它提供了錯誤檢測和更精細的控制。類似地,還有?strtof()?(轉?float) 和?strtold()?(轉?long double),以及?strtol()?/?strtoul()?(轉?long/unsigned long,代替?atoi)。
char *line = "3.14 1.59 rest"; char *end; double val1, val2; errno = 0; // 清除 errnoval1 = strtod(line, &end); if (errno == ERANGE || line == end) { /* 處理錯誤 */ } printf("First value: %f, Remaining string: '%s'\n", val1, end);// 繼續解析 end 指向的剩余部分 line = end; // 更新 line 指針 errno = 0; val2 = strtod(line, &end);if (errno == ERANGE || line == end) { /* 處理錯誤 */ } printf("Second value: %f, Remaining string: '%s'\n", val2, end);
-
-
strtok()?- 分割字符串 (Tokenize)
-
頭文件:?<string.h>
-
原型:?char *strtok(char *str, const char *delim);
-
作用:?將字符串?str?分割成一系列的“令牌”(tokens),這些令牌由?delim?字符串中包含的任何一個字符分隔開。
-
行為:
-
會修改原始字符串?str!?它會在找到的分隔符位置寫入?\0。因此,你通常需要一個可寫的字符串副本。
-
非可重入,非線程安全:?它使用一個內部靜態指針來記住下次調用的位置。
-
第一次調用:?str?參數是要分割的字符串,函數返回第一個令牌的指針,如果沒有令牌則返回?NULL。
-
后續調用:?str?參數必須是?NULL,函數會從上次停止的位置繼續查找下一個令牌,返回令牌指針或?NULL。
-
-
返回值:?指向下一個令牌的指針,或者在沒有更多令牌時返回?NULL。
-
解析相關:?用于按分隔符(如空格、逗號)拆分字符串。例如,拆分一行坐標 "x1 y1 x2 y2 ..."。
char line[] = "10.0 20.5 30.0 15.0"; // 可寫副本 const char *delimiters = " "; // 按空格分割 char *token;token = strtok(line, delimiters); while (token != NULL) {printf("Token: %s\n", token);// 在這里可以用 strtod() 將 token 轉換為 doubledouble coord = strtod(token, NULL); // 簡單用法,忽略錯誤檢查printf("Coordinate: %f\n", coord);token = strtok(NULL, delimiters); // 獲取下一個 token }
-
注意:?因為它修改原字符串且非線程安全,有時會避免使用它,尤其是在復雜或多線程程序中。可以考慮使用?sscanf?或手動循環配合?strtod。
-
-
sscanf()?- 從字符串讀取格式化輸入
-
頭文件:?<stdio.h>
-
原型:?int sscanf(const char *str, const char *format, ...);
-
作用:?類似于?scanf,但是從字符串?str?而不是標準輸入?stdin?讀取數據。
-
行為:?根據?format?字符串中的格式說明符,嘗試從?str?中解析數據并存儲到后續的參數(必須是指針)中。
-
返回值:?成功賦值的參數個數。如果輸入在第一次賦值前就結束或失敗,返回?EOF。
-
解析相關:?非常強大,可以一次性解析多個不同類型的值,只要它們的格式是預期的。
char data[] = "Point 1 10.5 25.3"; char label[10]; int id; double x, y;int count = sscanf(data, "%s %d %lf %lf", label, &id, &x, &y);if (count == 4) {printf("Parsed: Label=%s, ID=%d, X=%.2f, Y=%.2f\n", label, id, x, y); } else {printf("Parsing failed or incomplete. Items matched: %d\n", count); }
-
在循環中解析坐標序列:?可以結合?%n?實現。%n?不消耗輸入,而是將在該點消耗的字符數寫入其對應的?int*?參數。這可以幫助你移動解析的“光標”。
char coords[] = "1.1 2.2 3.3 4.4 5.5 6.6"; char *ptr = coords; int offset; double val; int items_read;while (sscanf(ptr, "%lf%n", &val, &offset) == 1) {printf("Read coordinate: %f\n", val);ptr += offset; // 移動指針到已解析部分的后面// 跳過可能的空格while (*ptr == ' ') {ptr++;}if (*ptr == '\0') break; // 到達字符串末尾 }
-
如何選擇?
-
讀取文件行:?始終使用?fgets。
-
獲取字符串長度:?strlen。
-
字符串轉數字:
-
優先使用?strtod,?strtol,?strtoul?因為它們提供錯誤檢查和更好的控制。
-
atoi,?atof?僅用于非常簡單、不需錯誤處理的場景(或快速原型開發),生產代碼中應避免。
-
-
分割字符串:
-
strtok:?簡單,但修改原串且非線程安全。適用于簡單場景且擁有可寫副本時。
-
sscanf:?適合格式相對固定的輸入,可以一次性讀取多個值。不修改原串。
-
循環 +?strtod:?最靈活、最健壯的方法,尤其當分隔符復雜或需要精細錯誤處理時。通過?strtod?的?endptr?可以精確控制解析位置。
-
例子:
多邊形坐標解析任務(一行包含多個空格分隔的浮點數),以下兩種方法比較常用:
-
fgets?+?strtok?+?strtod:
-
fgets?讀取行。
-
創建行的可寫副本。
-
用?strtok?以空格為分隔符循環獲取每個坐標字符串 (token)。
-
在循環內用?strtod?將每個 token 轉換為?double?并進行錯誤檢查。
-
-
fgets?+ 循環 +?strtod:
-
fgets?讀取行。
-
使用一個指針?char *current_pos?指向當前解析位置(初始指向行首)。
-
在循環中:
-
調用?val = strtod(current_pos, &end_pos)。
-
檢查錯誤 (errno,?current_pos == end_pos)。
-
如果成功,存儲?val。
-
更新?current_pos = end_pos,讓下一次循環從這里開始。
-
如果?current_pos?指向?\0,結束循環。
-
-