void gets_line(MYSQL *conn_ptr) {
FILE *fp = fopen("test.csv", "r");
char line[60];
while (fgets(line, 60, fp) != NULL) {
line[strlen(line)-1] = '\0'; // 危險!假設必有 \n
printf("%s\n", line);
}
fclose(fp);
}
**隱患**:`fgets` 讀取行時,**保留換行符** (若緩衝區足夠)。但:
char *newline = strchr(line, '\n');
if (newline) *newline = '\0'; // 僅當存在 \n 時移除
// 兼容 \r\n: 進一步檢查前一字符
if (newline && newline > line && *(newline-1) == '\r')
*(newline-1) = '\0';
// 返回: 0=成功/EOF, -1=錯誤/行過長
int read_line(FILE *fp, char *buf, size_t bufsz) {
if (!fgets(buf, bufsz, fp)) return 0; // EOF 或錯誤
size_t len = strlen(buf);
if (len == 0) return 0; // 空行 (極少見)
// 情況 1: 正常讀取完整行 (含 \n)
if (buf[len-1] == '\n') {
buf[--len] = '\0';
// 處理 \r\n
if (len > 0 && buf[len-1] == '\r') buf[--len] = '\0';
return 0;
}
// 情況 2: 行過長被截斷 (無 \n) — 需讀取並丟棄剩餘字符
int ch;
while ((ch = fgetc(fp)) != EOF && ch != '\n'); // 丟棄到換行或 EOF
return -1; // 標記行被截斷
}
// POSIX getline (Linux/Unix, 需 _GNU_SOURCE)
char *line = NULL;
size_t len = 0;
ssize_t nread;
while ((nread = getline(&line, &len, fp)) != -1) {
// line 包含 \n, nread 含 \n 長度
if (nread > 0 && line[nread-1] == '\n') line[--nread] = '\0';
if (nread > 0 && line[nread-1] == '\r') line[--nread] = '\0';
// 使用 line...
}
free(line);
// C++ 標準庫 (推薦現代 C++ 專案)
#include
#include
std::ifstream file("test.csv");
std::string line;
while (std::getline(file, line)) {
// 自動處理 \n, \r\n, \r,不包含分隔符
// 直接使用 line
}
| 場景 | 推薦方案 | 關鍵點 |
|---|---|---|
| 現代 C++ 專案 | `std::ifstream` + `std::getline` | 自動處理所有換行符、無緩衝區限制、異常安全 |
| C 語言維護項目 | 封裝 `read_line()` 工具函數 | 統一處理 \n/\r\n/截斷/EOF、返回狀態碼 |
| 高性能解析大文件 | 內存映射 `mmap` + 手寫解析器 | 零拷貝、隨機訪問、需自行處理換行符 |
| CSV 專用解析 | 成熟庫 (csv-parser, fast-cpp-csv-parser) | 處理引號、轉義、多行字段、編碼 |
核心洞見: `fgets` 保留換行符是為了讓調用者**區分「完整行」與「截斷行/EOF」**。正確處理換行符,本質上是對**輸入數據格式不確定性**的防禦性編程。現代 C++ `std::getline` 已將此複雜性封裝,**新項目無理由繼承 C 風險**。