【C语言常用字符串解析】
总结一下在 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,结束循环。
-
-