当前位置:首页 » 《资源分享》 » 正文

22 C 语言字符串到数值转换函数详解:atof、atoi、atol、strtod、strtol、strtoll、strtoul

9 人参与  2024年10月11日 12:40  分类 : 《资源分享》  评论

点击全文阅读


目录

1 atof()

1.1 函数原型

1.2 功能说明

1.3 案例演示

1.4 注意事项

2 atoi()

2.1 函数原型

2.2 功能说明

2.3 案例演示

2.4 注意事项

3 atol()

3.1 函数原型

3.2 功能说明

3.3 案例演示

3.4 注意事项

4 strtod()

4.1 函数原型

4.2 功能说明

4.3 案例演示

4.4 错误处理机制

4.5 注意事项

5 strtol()

5.1 函数原型

5.2 功能说明

5.3 案例演示

5.4 错误处理机制

5.5 注意事项

6 strtoll()

6.1 函数原型

6.2 功能说明

6.3 案例演示

6.4 错误处理机制

6.5 注意事项

7 strtoul()

7.1 函数原型

7.2 功能说明

7.3 案例演示

7.4 错误处理机制

7.5 注意事项

8 总结对比表


1 atof()

1.1 函数原型

#include <stdlib.h>  double atof(const char *str);

1.2 功能说明

        atof() 函数是 C 语言标准库中的一个函数,用于将字符串转换成浮点数(double 类型)。其原型定义在 <stdlib.h> 头文件中。以下是 atof() 函数的详细功能说明:

        atof() 函数从参数 str 指向的字符串开始解析,直到遇到第一个非法的字符(即不能转换为数字的字符)为止,然后将这部分字符串转换为 double 类型的浮点数并返回。如果字符串的第一个非空白字符(正负符号除外)不能被转换为数字,则函数直接返回 0.0。

        忽略前导空白字符:字符串开始处的空白字符(如空格、制表符、换行符等)会被 atof() 函数忽略。这意味着,如果字符串的前面有空白字符,atof() 会从第一个非空白字符开始解析。

        解析开始:从第一个非空白字符开始,atof() 会尝试将字符序列解析为一个浮点数。这包括可选的正负号(+ 或 -)、整数部分(一个或多个数字)、小数点(.)及其后的小数部分(一个或多个数字)、指数部分(一个字母 e 或 E,后面跟着可选的正负号和一个或多个数字,表示科学记数法)。

        解析停止:如果在这个过程中遇到了不能解释为数字的字符(例如字母、标点符号、特殊符号等),则解析会立刻停止,并忽略该字符及其后面的所有字符。

        特殊情况:空字符串或仅包含空白字符(如空格、制表符、换行符等)的字符串,atof() 会将这些情况视为无法转换为有效浮点数的情形,并返回 0.0。

        上溢和下溢:当数值超过 double 能表示的最大正值时,结果应为正无穷大(+Infinity),在使用 %f 或类似的格式说明符打印时,通常显示为 inf 或 INF(具体取决于系统和库的实现)。对于直接打印非常接近 0 但不为 0 的数值,如果它小于最小可打印的次正规数,则可能由于浮点数到字符串的转换精度限制而显示为 0.000000

1.3 案例演示

#include <stdio.h>  // 使用 printf 函数#include <stdlib.h> // 使用 atof 函数#include <float.h>  // 使用 double 类型的最值:DBL_MAX、DBL_MINint main(){    // 如果这里不明白,什么是字符指针(字符串的一种表现形式)    // 也可以使用字符数组(字符串的另一种表现形式)来表示字符串    // 如 char str[] = "123.456"    const char *str1 = "123.456";       // 正常转换    const char *str2 = "  -789.012  ";  // 忽略前导空格,正常转换    const char *str3 = "abc123";        // 无法转换    const char *str4 = "1.2.3";         // 部分转换    const char *str5 = "123e-2";        // 正常转换    const char *str6 = "1.797693e+309"; // 溢出,超出 double 类型的最大正值    const char *str7 = "2.225074e-309"; // 溢出,小于 double 类型的最小正值    const char *str8 = "";              // 空字符串    const char *str9 = "   ";           // 仅包含空白字符    // 查看 double 类型数据的数据范围,即最值    printf("double 类型的最大正值(float.h 中的 DBL_MAX): %e\n", DBL_MAX); // 1.797693e+308    printf("double 类型的最小正值(float.h 中的 DBL_MIN): %e\n", DBL_MIN); // 2.225074e-308    // 使用 %f 或 %lf 输出浮点类型数据    printf("原字符串:%s,转换后的浮点数:%f\n", str1, atof(str1));    printf("原字符串:%s,转换后的浮点数:%f\n", str2, atof(str2)); // 忽略前导空白字符    printf("原字符串:%s,转换后的浮点数:%f\n", str3, atof(str3)); // 转换在 'a' 处停止    printf("原字符串:%s,转换后的浮点数:%f\n", str4, atof(str4)); // 转换在第二个 '.' 前停止    printf("原字符串:%s,转换后的浮点数:%f\n", str5, atof(str5));    // 数值超过 double 能表示的最大正值时,结果应为正无穷大(+Infinity)    // 这在使用 %f 或类似的格式说明符打印时,通常显示为 inf 或 INF(具体取决于系统和库的实现)    printf("原字符串:%s,转换后的浮点数(上溢):%f\n", str6, atof(str6)); // 上溢:inf    // 对于直接打印非常接近 0 但不为 0 的数值,如果它小于最小可打印的次正规数,    // 则可能由于浮点数到字符串的转换精度限制而显示为 0.000000    printf("原字符串:%s,转换后的浮点数(下溢):%f\n", str7, atof(str7)); // 下溢:0.000000    printf("原字符串:%s,转换后的浮点数:%f\n", str8, atof(str8));    printf("原字符串:%s,转换后的浮点数:%f\n", str9, atof(str9));    return 0;}

        输出结果如下所示:

1.4 注意事项

        错误处理:atof() 函数不提供直接的错误处理机制。如果字符串不能转换为有效的浮点数,atof() 将返回 0.0,但这并不足以区分空字符串、仅包含空白字符的字符串和包含无法转换的数字的字符串。因此,如果需要更详细的错误处理,建议使用 strtod() 函数,它可以提供更丰富的错误信息和控制。

        性能与精度:atof() 函数的性能通常足够好,适用于大多数简单的浮点数字符串转换场景。然而,在对精度有极端要求的应用中,建议使用 strtod() 函数。

        溢出处理:atof() 函数不会检测溢出,即使字符串表示的数字超出了 double 类型的范围,它也会返回一个可能不正确的值(inf 或 0.0)。为了检测溢出,建议使用 strtod() 函数。

        使用场景:atof() 是处理简单浮点数字符串转换的便捷函数,适用于不需要复杂错误处理的场景。在需要处理复杂格式(如科学记数法、指数部分等)或需要更详细错误信息的场景中,strtod() 是更好的选择。


2 atoi()

2.1 函数原型

#include <stdlib.h>  int atoi(const char *str);

2.2 功能说明

        atoi() 函数是 C 语言标准库中的一个函数,用于将字符串转换成整数(int 类型)。其原型定义在 <stdlib.h> 头文件中。以下是 atoi() 函数的详细功能说明:

        atoi() 函数从参数 str 指向的字符串开始解析,直到遇到第一个非法的字符(即不能转换为数字的字符)为止,然后将这部分字符串转换为 int 类型的整数并返回。如果字符串的第一个非空白字符(正负符号除外)不能被转换为数字,则函数返回 0。

        忽略前导空白字符:字符串开始处的空白字符(如空格、制表符、换行符等)会被 atoi() 函数忽略。这意味着,如果字符串的前面有空白字符,atoi() 会从第一个非空白字符开始解析。

        解析开始:从第一个非空白字符开始,atoi() 会尝试将字符序列解析为一个整数。这包括:①可选的正负号:如果第一个非空白字符是 + 或 -,则它被识别为整数的符号,并继续解析后面的字符。②整数部分:atoi() 会读取一个或多个十进制数字作为整数部分。

        解析停止:如果在这个过程中遇到了不能解释为数字的字符(例如字母、小数点、标点符号、特殊符号等),则解析会停止,并忽略该字符及其后面的所有字符。

        特殊情况:空字符串或仅包含空白字符(如空格、制表符、换行符等)的字符串,atoi() 会将这些情况视为无法转换为有效整数的情形,并返回 0。

        上溢和下溢:当数值超出 int 类型所能表示的最大范围时,将发生数据溢出,编译器通常会采取回绕处理。这种情况下,可以通过该数据类型所占用的位数来表示相应长度的二进制数据,并据此执行计算。

2.3 案例演示

#include <stdio.h>  // 使用 printf 函数#include <stdlib.h> // 使用 atoi 函数#include <limits.h> // 使用 int 类型的最值:INT_MAX、INT_MINint main(){    const char *str1 = "12345";        // 正常转换    const char *str2 = "  -6789";      // 忽略前导空格,正常转换    const char *str3 = "abc123";       // 无法转换    const char *str4 = "12.345";       // 部分转换    const char *str5 = "21474836470";  // 溢出,超出 int 类型的最大值    const char *str6 = "-21474836480"; // 溢出,小于 int 类型的最小值    const char *str7 = "";             // 空字符串    const char *str8 = "   ";          // 仅包含空白字符    // 查看 int 类型数据的数据范围,即最值    printf("int 类型的最大值(limits.h 中的 INT_MAX): %d\n", INT_MAX); // 2147483647    printf("int 类型的最小值(limits.h 中的 INT_MIN): %d\n", INT_MIN); // -2147483648    // 使用 %d 输出十进制整数类型数据    printf("原字符串:%s,转换后的整数:%d\n", str1, atoi(str1));    printf("原字符串:%s,转换后的整数:%d\n", str2, atoi(str2));       // 忽略前导空白字符    printf("原字符串:%s,转换后的整数:%d\n", str3, atoi(str3));       // 转换在 'a' 处停止    printf("原字符串:%s,转换后的整数:%d\n", str4, atoi(str4));       // 转换在 '.' 处停止    printf("原字符串:%s,转换后的整数(溢出):%d\n", str5, atoi(str5)); // 溢出    printf("原字符串:%s,转换后的整数(溢出):%d\n", str6, atoi(str6)); // 溢出    printf("原字符串:%s,转换后的整数:%d\n", str7, atoi(str7));    printf("原字符串:%s,转换后的整数:%d\n", str8, atoi(str8));    // 字符串 str5 溢出解释:    // 21474836470 对应的二进制如下所示:    // 0100 1111 1111 1111 1111 1111 1111 1111 0110    // 低 32 位(因为 int 类型为 4 字节)二进制为:1111 1111 1111 1111 1111 1111 1111 0110    // 如果使用 %d 读取数据,表示有符号十进制数据,即为:-10    // 字符串 str6 溢出解释:    // -21474836480 对应的二进制如下所示:    // 1111 1111 1111 1111 1111 1111 1111 1011 0000 0000 0000 0000 0000 0000 0000 0000    // 低 32 位(因为 int 类型为 4 字节)二进制为:0000 0000 0000 0000 0000 0000 0000 0000    // 如果使用 %d 读取数据,表示有符号十进制数据,即为:0    return 0;}

        输出结果如下所示:

2.4 注意事项

        错误处理:atoi() 函数不提供直接的错误处理机制。如果字符串不能转换为有效的整数,它将返回 0,但这并不足以区分空字符串、仅包含空白字符的字符串和包含无法转换的数字的字符串。因此,如果需要更详细的错误处理,建议使用 strtol() 函数,它可以提供更丰富的错误信息和控制。

        性能与精度:atoi() 函数在将字符串转换为整数时通常足够快,适用于大多数简单的转换场景。然而,由于其返回类型为 int,因此存在整数溢出的风险。对于需要更大范围或更高精度的转换,建议使用 strtol() 函数,它提供了更广泛的整数范围和更详细的错误报告。

        溢出处理:atoi() 在处理超出 int 范围的值时会发生溢出,但具体行为取决于实现,可以通过该数据类型所占用的位数来表示相应长度的二进制数据,并据此执行计算。因此,在处理可能超出 int 范围的输入时,需要特别注意这一点。为了更安全地处理溢出,建议使用 strtol() 函数。

        使用场景:atoi() 是处理简单整数字符串转换的便捷函数,适用于不需要复杂错误处理的场景。在需要处理复杂格式(如非十进制数等)、需要更详细错误信息或需要处理大范围整数的场景中,strtol() 可能是更好的选择。


3 atol()

3.1 函数原型

#include <stdlib.h>  long atol(const char *str);

3.2 功能说明

        atol() 函数是 C 语言标准库中的一个函数,用于将字符串转换为长整型(long 类型)的整数。其原型定义在 <stdlib.h> 头文件中。以下是 atol() 函数的详细功能说明:

        atol() 函数从参数 str 指向的字符串开始解析,直到遇到第一个非法的字符(即不能转换为数字的字符)为止,然后将这部分字符串转换为 long 类型的整数并返回。如果字符串的第一个非空白字符(正负符号除外)不能被转换为数字,则函数返回 0。

        忽略前导空白字符:与 atoi() 类似,atol() 函数也会忽略字符串开始处的空白字符(如空格、制表符、换行符等)。这意味着,如果字符串的前面有空白字符,atol() 会从第一个非空白字符开始解析。

        解析开始:从第一个非空白字符开始,atol() 会尝试将字符序列解析为一个长整型数(long)。这包括:①可选的正负号:如果第一个非空白字符是 + 或 -,则它被识别为长整型数的符号,并继续解析后面的字符。②整数部分:atol() 会读取一个或多个十进制数字作为整数部分。与 atoi() 不同的是,由于返回类型是 long,它能够处理更大范围的整数。

        解析停止:如果在这个过程中遇到了不能解释为数字的字符(例如字母、小数点、标点符号、特殊符号等),则解析会停止,并忽略该字符及其后面的所有字符。

        特殊情况:空字符串或仅包含空白字符(如空格、制表符、换行符等)的字符串,atol() 会将这些情况视为无法转换为有效长整数的情形,并返回 0 。

        上溢和下溢:当数值超出 long 类型所能表示的最大范围时,将发生数据溢出,通常会采取回绕处理。这种情况下,同 int 类型一样,可以通过该数据类型所占用的位数来表示相应长度的二进制数据,并据此执行计算。

3.3 案例演示

#include <stdio.h>  // 使用 printf 函数#include <stdlib.h> // 使用 atol 函数#include <limits.h> // 使用 long 类型的最值:LONG_MAX、LONG_MINint main(){    const char *str1 = "12345";        // 正常转换    const char *str2 = "  -6789";      // 忽略前导空格,正常转换    const char *str3 = "abc123";       // 无法转换    const char *str4 = "12.345";       // 部分转换    const char *str5 = "21474836470";  // 溢出,超出 long 类型的最大值    const char *str6 = "-21474836480"; // 溢出,小于 long 类型的最小值    const char *str7 = "";             // 空字符串    const char *str8 = "   ";          // 仅包含空白字符    // 查看 long 类型能表示的数据范围(一般来说 long 和 int 相同)    printf("long 类型的最大值(limits.h 中的 LONG_MAX): %ld\n", LONG_MAX); // 2147483647    printf("long 类型的最小值(limits.h 中的 LONG_MIN): %ld\n", LONG_MIN); // -2147483648    printf("int 类型的最大值(limits.h 中的 INT_MAX): %d\n", INT_MAX);     // 2147483647    printf("int 类型的最小值(limits.h 中的 INT_MIN): %d\n", INT_MIN);     // -2147483648    // 查看字节数(一般来说 long 和 int 相同)    printf("long 类型的字节数:%zu\n", sizeof(long)); // 4    printf("int 类型的字节数:%zu\n", sizeof(int));   // 4    // 使用 %ld 输出 long 类型数据    printf("原字符串:%s,转换后的整数:%ld\n", str1, atol(str1));    printf("原字符串:%s,转换后的整数:%ld\n", str2, atol(str2));       // 忽略前导空白字符    printf("原字符串:%s,转换后的整数:%ld\n", str3, atol(str3));       // 转换在 'a' 处停止    printf("原字符串:%s,转换后的整数:%ld\n", str4, atol(str4));       // 转换在 '.' 处停止    printf("原字符串:%s,转换后的整数(溢出):%ld\n", str5, atol(str5)); // 溢出    printf("原字符串:%s,转换后的整数(溢出):%ld\n", str6, atol(str6)); // 溢出    printf("原字符串:%s,转换后的整数:%ld\n", str7, atol(str7));    printf("原字符串:%s,转换后的整数:%ld\n", str8, atol(str8));    // 字符串 str5 溢出解释:    // 21474836470 对应的二进制如下所示:    // 0100 1111 1111 1111 1111 1111 1111 1111 0110    // 低 32 位(因为 long 是 4 字节)二进制为:1111 1111 1111 1111 1111 1111 1111 0110    // 如果使用 %ld 读取数据,表示有符号十进制长整型数据,即为:-10    // 字符串 str6 溢出解释:    // -21474836480 对应的二进制如下所示:    // 1111 1111 1111 1111 1111 1111 1111 1011 0000 0000 0000 0000 0000 0000 0000 0000    // 低 32 位(因为 long 是 4 字节)二进制为:0000 0000 0000 0000 0000 0000 0000 0000    // 如果使用 %ld 读取数据,表示有符号十进制长整型数据,即为:0    return 0;}

        输出结果如下所示:

3.4 注意事项

        错误处理:与 atoi() 类似,atol() 函数不提供直接的错误处理机制。如果字符串不能转换为有效的整数,它将返回 0,但这并不足以区分空字符串、仅包含空白字符的字符串和包含无法转换的数字的字符串。因此,如果需要更详细的错误处理,建议使用 strtoll() 函数,它可以提供更丰富的错误信息和控制。

        性能与精度:atol() 函数在将字符串转换为长整型整数时通常足够快,适用于大多数简单的转换场景。然而,由于其返回类型为 long,因此存在整数溢出的风险。对于需要更大范围或更高精度的转换,建议使用 strtoll() 函数,它提供了更广泛的整数范围和更详细的错误报告。

        溢出处理:与 atoi() 类似,atol() 在处理超出 long 范围的值时会发生溢出,但具体行为取决于实现,可以通过该数据类型所占用的位数来表示相应长度的二进制数据,并据此执行计算。因此,在处理可能超出 long 范围的输入时,需要特别注意这一点。为了更安全地处理溢出,建议使用 strtoll() 函数。

        使用场景:atol() 是处理简单长整数字符串转换的便捷函数,适用于不需要复杂错误处理的场景。在需要处理复杂格式(如非十进制数等)、需要更详细错误信息或需要处理大范围整数的场景中,strtoll() 可能是更好的选择。


4 strtod()

4.1 函数原型

#include <stdlib.h>  double strtod(const char *restrict nptr, char **restrict endptr);

4.2 功能说明

        strtod() 函数是 C 语言标准库中的一个函数,用于将字符串转换为双精度浮点数(double 类型)。其原型定义在 <stdlib.h> 头文件中。以下是 strtod() 函数的详细功能说明:

        字符串转换:strtol 函数会扫描参数 nptr 指向的字符串,跳过前面的空白字符(如空格、制表符、换行符等),直到遇到数字或正负符号(+ 或 -)才开始进行转换。转换过程会持续到出现非数字字符或字符串结束(\0)为止。

        支持格式:参数 nptr 指向的字符串可以包含正负号(+ 或 -)、小数点(.)、指数部分(E 或 e)。例如,字符串 "123.456" 或 "123e-2" 都是有效的输入。strtod 函数支持科学记数法,可以在数字后面加上 E 或 e 来表示指数部分。

        错误处理:如果 nptr 指向的字符串无法被转换成有效的浮点数,strtod 函数将返回 0.0。然而,与 atof 函数不同,strtod 提供了通过 endptr 参数进行错误处理的能力。通过 endptr 可以更精确地判断转换是否成功。

        返回值:转换成功时,strtod 返回转换后的浮点数(double 类型)。如果转换失败(例如,字符串为空或仅包含无法转换为浮点数的字符),则返回 0.0。但是,请注意,0.0 也是一个有效的浮点数,因此仅通过返回值可能无法区分是转换成功但结果为 0.0 还是转换失败。此时,可以通过检查 endptr 的值来进一步判断。

        endptr 参数:如果 endptr 不为 NULL,strtod 函数会将指向遇到不合条件而终止的 nptr 中的字符的指针赋给 endptr。这样,就可以通过检查 endptr 的值来了解转换在何处停止,进而进行错误处理或进一步分析。若 endptr 指向的字符串与 nptr 相同,表明没有进行任何转换;若 endptr 指向的字符(即 *endptr)并非字符串的终止符('\0'),则意味着字符串中存在非数字字符,导致转换提前终止,仅转换了可转换的那部分;若上述两种情形均未出现,则表明字符串本身被转换成功,不过仍需检查是否发生了上溢或下溢的情况以确保转换结果的有效性。

        上溢和下溢:如果转换的结果超出 double 类型的表示范围,strtod 会返回 HUGE_VAL 或 -HUGE_VAL(直接使用 %f 打印的话,为 inf),并可能设置 errno 为 ERANGE。如果转换的结果太小,接近于 0,strtod 会返回 0.0(直接使用 %f 打印的话,为 0.000000),并可能设置 errno 为 ERANGE。但不同的编译器和 C 库实现可能会有不同的行为。有些实现可能更严格地遵循标准,而有些则可能更宽松或具有特定的优化。并非所有 C 库实现都会在 strtod 溢出时设置 errno 为 ERANGE,所以通过判断 errno 是否为 ERANGE 是不够准确的。

4.3 案例演示

#include <stdio.h>  // 使用 printf 函数#include <stdlib.h> // 使用 strtod 函数#include <float.h>  // 使用 double 类型的最值:DBL_MAX、DBL_MINint main(){    const char *nptr1 = "123.456";       // 正常转换    const char *nptr2 = "  -789.012  ";  // 忽略前导和尾随空格,正常转换    const char *nptr3 = "  +789.012  ";  // 忽略前导和尾随空格,正常转换    const char *nptr4 = "abc123";        // 无法转换    const char *nptr5 = "1.2.3";         // 部分转换    const char *nptr6 = "123e-2";        // 正常转换    const char *nptr7 = "1.797693e+309"; // 溢出,超出 double 类型的最大正值    const char *nptr8 = "2.225074e-309"; // 溢出,小于 double 类型的最小正值    const char *nptr9 = "";              // 空字符串    const char *nptr10 = "   ";          // 仅包含空白字符    // 查看 double 类型数据的数据范围,即最值    printf("double 类型的最大正值(float.h 中的 DBL_MAX): %e\n", DBL_MAX); // 1.797693e+308    printf("double 类型的最小正值(float.h 中的 DBL_MIN): %e\n", DBL_MIN); // 2.225074e-308    // 定义一个字符指针,用来保存转换后剩余字符串的指针    char *endptr;    double num;    // &endptr 的类型是一个指向指针的指针,也就是 char ** 类型,通常被称为二级指针或指针的指针    num = strtod(nptr1, &endptr);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 转换后的浮点数: %f, 剩余字符串: \"%s\"\n", nptr1, num, endptr);    // 原字符串:"123.456", 转换后的浮点数: 123.456000, 剩余字符串: ""    num = strtod(nptr2, &endptr);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 转换后的浮点数: %f, 剩余字符串: \"%s\"\n", nptr2, num, endptr);    // 原字符串:"  -789.012  ", 转换后的浮点数: -789.012000, 剩余字符串: "  "    num = strtod(nptr3, &endptr);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 转换后的浮点数: %f, 剩余字符串: \"%s\"\n", nptr3, num, endptr);    // 原字符串:"  +789.012  ", 转换后的浮点数: 789.012000, 剩余字符串: "  "    num = strtod(nptr4, &endptr);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 转换后的浮点数: %f, 剩余字符串: \"%s\"\n", nptr4, num, endptr);    // 原字符串:"abc123", 转换后的浮点数: 0.000000, 剩余字符串: "abc123"    num = strtod(nptr5, &endptr);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 转换后的浮点数: %f, 剩余字符串: \"%s\"\n", nptr5, num, endptr);    // 原字符串:"1.2.3", 转换后的浮点数: 1.200000, 剩余字符串: ".3"    num = strtod(nptr6, &endptr);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 转换后的浮点数: %f, 剩余字符串: \"%s\"\n", nptr6, num, endptr);    // 原字符串:"123e-2", 转换后的浮点数: 1.230000, 剩余字符串: ""    num = strtod(nptr7, &endptr);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 转换后的浮点数(上溢): %f, 剩余字符串: \"%s\"\n", nptr7, num, endptr);    // 原字符串:"1.797693e+309", 转换后的浮点数: inf, 剩余字符串: ""    num = strtod(nptr8, &endptr);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 转换后的浮点数(下溢): %f, 剩余字符串: \"%s\"\n", nptr8, num, endptr);    // 原字符串:"2.225074e-309", 转换后的浮点数: 0.000000, 剩余字符串: ""    num = strtod(nptr9, &endptr);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 转换后的浮点数: %f, 剩余字符串: \"%s\"\n", nptr9, num, endptr);    // 原字符串:"", 转换后的浮点数: 0.000000, 剩余字符串: ""    num = strtod(nptr10, &endptr);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 转换后的浮点数: %f, 剩余字符串: \"%s\"\n", nptr10, num, endptr);    // 原字符串:"   ", 转换后的浮点数: 0.000000, 剩余字符串: "   "    return 0;}

        输出结果如下所示:

4.4 错误处理机制

#include <stdio.h>  // 使用 printf 函数#include <stdlib.h> // 使用 strtod 函数#include <float.h>  // 使用 double 类型的最值:DBL_MAX、DBL_MIN#include <errno.h>  // 使用 errno、ERANGE 等#include <math.h>   // 引入数学库,使用 HUGE_VAL// 定义一个函数来测试 strtod 函数的行为void test_strtod(const char *nptr){    // 定义一个指针,用于 strtod 函数返回未转换部分的起始位置    char *endptr;    // 调用 strtod 函数尝试将字符串转换为双精度浮点数    double num = strtod(nptr, &endptr);    // 清除 errno    errno = 0;    // 检查转换是否成功    if (nptr == endptr)    {        // 没有进行任何转换        printf("原字符串\"%s\"转换浮点数失败,浮点数为:%f,剩余字符串(和原字符串一样):\"%s\"\n", nptr, num, endptr);    }    else if (*endptr != '\0')    {        // 转换在字符串的某个非数字字符处停止        printf("原字符串\"%s\"部分转换成功,转换后的浮点数为:%f,剩余字符串:\"%s\"\n", nptr, num, endptr);    }    // 并非所有 C 库实现都会在溢出时设置 errno 为 ERANGE    // 通过判断 errno 是否为 ERANGE 是不够准确的    else if (errno == ERANGE)    {        // 上溢        // errno.h 头文件中有宏定义:#define HUGE_VAL   ((double)INFINITY)        if (num == HUGE_VAL || num == -HUGE_VAL)        {            printf("原字符串\"%s\"转换成功,但发生了上溢!转换后的浮点数为:%f,剩余字符串(空字符):\"%s\"\n", nptr, num, endptr);        }        // 下溢        else if (num == 0.0)        {            printf("原字符串\"%s\"转换成功,但发生了下溢!转换后的浮点数为:%f,剩余字符串(空字符):\"%s\"\n", nptr, num, endptr);        }        // 如果不想处理所有其他情况,或者只是简单地让程序继续执行 if-else if 结构之后的代码,那么可以省略最后的 else 部分    }    else    {        // 完全转换成功        printf("原字符串\"%s\"完全转换成功,转换后的浮点数为:%f,剩余字符串(空字符):\"%s\"\n", nptr, num, endptr);    }}int main(){    // 查看 double 类型数据的数据范围,即最值    printf("double 类型的最大正值(float.h 中的 DBL_MAX): %e\n", DBL_MAX); // 1.797693e+308    printf("double 类型的最小正值(float.h 中的 DBL_MIN): %e\n", DBL_MIN); // 2.225074e-308    // 测试数据    const char *test_data[] = {        "123.45",        // 正常数据        "-123.45",       // 负号        "+123.45",       // 正号        "123.45e-2",     // 科学计数法指数部分        "123.45E2",      // 科学计数法指数部分        "abc123.45",     // 无法被转换成有效的浮点数        "123.45.67",     // 部分转换        "123.45 abc",    // 部分转换        "  123.45 ",     // 前导和尾随空格        "123.45\t",      // 尾随制表符        "123.45\n",      // 尾随换行符        "1.797693e+309", // 超出 double 类型的最大正值        "2.225074e-408", // 小于 double 类型的最小正值        "",              // 空字符串        "   "            // 仅包含空格    };    // 测试每个数据    for (int i = 0; i < sizeof(test_data) / sizeof(test_data[0]); i++)    {        test_strtod(test_data[i]); // 调用 test_strtod 函数测试每个字符串    }    return 0;}

        输出结果如下所示:

        不同的编译器和 C 库实现可能会有不同的行为。有些实现可能更严格地遵循标准,而有些则可能更宽松或具有特定的优化。并非所有 C 库实现都会在 strtod 溢出时设置 errno 为 ERANGE,所以通过判断 errno 是否为 ERANGE 来决定是否溢出是不够准确的。

        下面我们将  else if (errno == ERANGE) 这个分支判断取消掉,直接进行上溢和下溢的判断,如下所示:

#include <stdio.h>  // 使用 printf 函数#include <stdlib.h> // 使用 strtod 函数#include <float.h>  // 使用 double 类型的最值:DBL_MAX、DBL_MIN#include <math.h>   // 引入数学库,使用 HUGE_VAL// 定义一个函数来测试 strtod 函数的行为void test_strtod(const char *nptr){    // 定义一个指针,用于 strtod 函数返回未转换部分的起始位置    char *endptr;    // 调用 strtod 函数尝试将字符串转换为双精度浮点数    double num = strtod(nptr, &endptr);    // 检查转换是否成功    if (nptr == endptr)    {        // 没有进行任何转换        printf("原字符串\"%s\"转换浮点数失败,浮点数为:%f,剩余字符串(和原字符串一样):\"%s\"\n", nptr, num, endptr);    }    else if (*endptr != '\0')    {        // 转换在字符串的某个非数字字符处停止        printf("原字符串\"%s\"部分转换成功,转换后的浮点数为:%f,剩余字符串:\"%s\"\n", nptr, num, endptr);    }    // 并非所有 C 库实现都会在 strtod 溢出时设置 errno 为 ERANGE    // 不同的编译器和 C 库实现可能会有不同的行为    // 有些实现可能更严格地遵循标准,而有些则可能更宽松或具有特定的优化    // 所以通过判断 errno 是否为 ERANGE 来决定是否溢出是不够准确的    // 我们取消掉这里的 errno == ERANGE 判断,再进行测试验证    // 上溢    // errno.h 头文件中有宏定义:#define HUGE_VAL   ((double)INFINITY)    else if (num == HUGE_VAL || num == -HUGE_VAL)    {        printf("原字符串\"%s\"转换成功,但发生了上溢!转换后的浮点数为:%f,剩余字符串(空字符):\"%s\"\n", nptr, num, endptr);    }    // 下溢    else if (num == 0.0)    {        printf("原字符串\"%s\"转换成功,但发生了下溢!转换后的浮点数为:%f,剩余字符串(空字符):\"%s\"\n", nptr, num, endptr);    }    // 完全转换成功    else    {        // 完全转换成功        printf("原字符串\"%s\"完全转换成功,转换后的浮点数为:%f,剩余字符串(空字符):\"%s\"\n", nptr, num, endptr);    }}int main(){    // 查看 double 类型数据的数据范围,即最值    printf("double 类型的最大正值(float.h 中的 DBL_MAX): %e\n", DBL_MAX); // 1.797693e+308    printf("double 类型的最小正值(float.h 中的 DBL_MIN): %e\n", DBL_MIN); // 2.225074e-308    // 测试数据    const char *test_data[] = {        "123.45",        // 正常数据        "-123.45",       // 负号        "+123.45",       // 正号        "123.45e-2",     // 科学计数法指数部分        "123.45E2",      // 科学计数法指数部分        "abc123.45",     // 无法被转换成有效的浮点数        "123.45.67",     // 部分转换        "123.45 abc",    // 部分转换        "  123.45 ",     // 前导和尾随空格        "123.45\t",      // 尾随制表符        "123.45\n",      // 尾随换行符        "1.797693e+309", // 超出 double 类型的最大正值        "2.225074e-408", // 小于 double 类型的最小正值        "",              // 空字符串        "   "            // 仅包含空格    };    // 测试每个数据    for (int i = 0; i < sizeof(test_data) / sizeof(test_data[0]); i++)    {        test_strtod(test_data[i]); // 调用 test_strtod 函数测试每个字符串    }    return 0;}

        输出结果如下所示:

4.5 注意事项

        错误处理:如果字符串不能转换为有效的浮点数,strtod 会返回 0.0。但是,仅通过返回值无法确定转换是否失败,因为 0.0 也是一个有效的浮点数。此时应检查 endptr 指针来确认转换是否成功。

        尾随字符:strtod 会尝试转换字符串直到遇到第一个无法转换为数字的字符。这个字符及其后面的所有字符都会被留在字符串中,endptr 会指向这个字符。因此,即使转换成功(部分转换),也可能有尾随字符。

        科学计数法:strtod 支持科学计数法(如 "1.23e4"),但需要注意指数部分的有效范围。如果指数过大或过小,可能会导致溢出或下溢。

        并非所有 C 库实现都会在 strtod 溢出时设置 errno 为 ERANGE:不同的 C 库实现(如 glibc, MSVC 的 C 运行时库等)在处理溢出时的行为可能不同。有些实现可能会在溢出时设置 errno 为 ERANGE,而有些则不会。因此,不能依赖 errno 来检测所有情况下的溢出,有些时候可以直接进行上溢和下溢的判断。


5 strtol()

5.1 函数原型

#include <stdlib.h>  long int strtol(const char *restrict nptr, char **restrict endptr, int base);

5.2 功能说明

        strtol() 函数是 C 语言标准库中的一个函数,用于将字符串转换为长整型数(long int)。其原型定义在 <stdlib.h> 头文件中。与 strtod 函数类似,但专门用于处理长整数类型的转换。以下是 strtol 函数的详细功能说明:

        字符串转换:strtol 函数会扫描参数 nptr 指向的字符串,跳过前面的空白字符(如空格、制表符、换行符等),直到遇到数字或正负符号(+ 或 -)才开始进行转换。转换过程会持续到出现非数字字符或字符串结束(\0)为止。

        支持格式:参数 nptr 指向的字符串可以包含正负号(+ 或 -)、数字(0-9)。strtol 函数不支持小数点或指数部分,因为它专注于整数转换。例如,字符串 "12345" 或 "-6789" 都是有效的输入。

        基数(Radix):strtol 函数允许调用者指定基数,这是一个介于 2 和 36 之间的整数,用于指定转换时使用的数制。如果基数是 0,则 strtol 会自动根据字符串的格式(如前缀 "0x" 或 "0")来确定基数(十六进制或八进制)。如果基数在有效范围内,strtol 将使用该基数进行转换。

        错误处理:如果 nptr 指向的字符串无法被转换成有效的长整型数(比如空字符串、仅包含非数字字符的字符串,或者包含小数点、指数等不符合整数格式的字符),strtol 函数的行为取决于基数参数。如果基数是 0,strtol 会尝试自动检测基数(根据字符串的前缀判断,如 "0x" 或 "0" 开头的字符串分别表示十六进制和八进制)。如果无法识别为有效的整数格式,则转换失败。在转换失败的情况下,strtol 函数返回 0。

        返回值:转换成功时,strtol 返回转换后的长整型数(long int 类型)。如果转换失败(例如,字符串为空或仅包含无法转换为整数的字符),则返回 0。但是,请注意,0 也是一个有效的整数值,因此仅通过返回值可能无法区分是转换成功但结果为 0 还是转换失败。此时,可以通过检查 endptr 的值来进一步判断。

        endptr 参数:如果 endptr 不为 NULL,strtol 函数会将指向遇到不合条件而终止的 nptr 中的字符的指针赋给 endptr。这样,就可以通过检查 endptr 的值来了解转换在何处停止,进而进行错误处理或进一步分析。若 endptr 指向的字符串与 nptr 相同,表明没有进行任何转换;若 endptr 指向的字符(即 *endptr)并非字符串的终止符('\0'),则意味着字符串中存在非数字字符,导致转换提前终止,仅转换了可转换的那部分;若上述两种情形均未出现,则表明字符串本身被转换成功,不过仍需检查是否发生了上溢或下溢的情况以确保转换结果的有效性。

        上溢和下溢:如果转换的结果超出 long int 类型的表示范围,strtol 函数的行为是未定义的。但是,许多实现会返回 LONG_MAX 或 LONG_MIN(定义在 <limits.h> 中),并可能设置 errno 为 ERANGE(尽管这并非所有实现都会如此)。然而,由于未定义行为的存在,依赖于此进行错误处理可能不是最佳实践。正确的做法是检查转换的上下文和预期范围,并在必要时采取适当的预防措施。

5.3 案例演示

#include <stdio.h>  // 使用 printf 函数#include <stdlib.h> // 使用 strtol 函数#include <limits.h> // 使用 long 类型的最值:LONG_MAX、LONG_MINint main(){    const char *nptr1 = "12345";               // 表示十进制数    const char *nptr2 = "0x1A3F";              // 表示十六进制数    const char *nptr3 = "075";                 // 前导 0 可能被识别为八进制数    const char *nptr4 = "abc123";              // 包含非数字字符,转换失败    const char *nptr5 = "123.456";             // 包含非数字字符,部分转换    const char *nptr6 = "  -6789  ";           // 包含前导与尾随空格和负号    const char *nptr7 = "  +6789  ";           // 包含前导与尾随空格和正号    const char *nptr8 = "987654321012345678";  // 溢出,超出 long 类型的最大值    const char *nptr9 = "-987654321012345678"; // 溢出,小于 long 类型的最小值    const char *nptr10 = "";                   // 空字符串    const char *nptr11 = "   ";                // 仅包含空白字符    // 查看 long 类型能表示的数据范围(一般来说 long 和 int 相同)    printf("long 类型的最大值(limits.h 中的 LONG_MAX): %ld\n", LONG_MAX); // 2147483647    printf("long 类型的最小值(limits.h 中的 LONG_MIN): %ld\n", LONG_MIN); // -2147483648    printf("int 类型的最大值(limits.h 中的 INT_MAX): %d\n", INT_MAX);     // 2147483647    printf("int 类型的最小值(limits.h 中的 INT_MIN): %d\n", INT_MIN);     // -2147483648    // 查看字节数(一般来说 long 和 int 相同)    printf("long 类型的字节数:%zu\n", sizeof(long)); // 4    printf("int 类型的字节数:%zu\n", sizeof(int));   // 4    // 定义一个字符指针,用来保存转换后剩余字符串的指针    char *endptr;    long num;    // &endptr 的类型是一个指向指针的指针,也就是 char ** 类型,通常被称为二级指针或指针的指针    num = strtol(nptr1, &endptr, 10);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 指定基数(10)转换后的长整型数: %ld, 剩余字符串: \"%s\"\n", nptr1, num, endptr);    // 字符串:"12345", 指定基数(10)转换后的长整型数: 12345, 剩余字符串: ""    num = strtol(nptr1, &endptr, 0); // 如果基数是 0,strtol 会尝试自动检测基数    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 自动检测基数(0)转换后的长整型数: %ld, 剩余字符串: \"%s\"\n", nptr1, num, endptr);    // 原字符串:"12345", 自动检测基数(0)转换后的长整型数: 12345, 剩余字符串: ""    num = strtol(nptr2, &endptr, 16);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 指定基数(16)转换后的长整型数: %ld, 剩余字符串: \"%s\"\n", nptr2, num, endptr);    // 原字符串:"0x1A3F", 指定基数(16)转换后的长整型数: 6719, 剩余字符串: ""    num = strtol(nptr2, &endptr, 0); // 如果基数是 0,strtol 会尝试自动检测基数    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 自动检测基数(0)转换后的长整型数: %ld, 剩余字符串: \"%s\"\n", nptr2, num, endptr);    // 字符串:"0x1A3F", 自动检测基数(0)转换后的长整型数: 6719, 剩余字符串: ""    num = strtol(nptr3, &endptr, 8);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 指定基数(8)转换后的长整型数: %ld, 剩余字符串: \"%s\"\n", nptr3, num, endptr);    // 字符串:"075", 指定基数(8)转换后的长整型数: 61, 剩余字符串: ""    num = strtol(nptr3, &endptr, 0); // 如果基数是 0,strtol 会尝试自动检测基数    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 自动检测基数(0)转换后的长整型数: %ld, 剩余字符串: \"%s\"\n", nptr3, num, endptr);    // 原字符串:"075", 自动检测基数(0)转换后的长整型数: 61, 剩余字符串: ""    num = strtol(nptr4, &endptr, 10);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 指定基数(10)转换后的长整型数: %ld, 剩余字符串: \"%s\"\n", nptr4, num, endptr);    // 原字符串:"abc123", 指定基数(10)转换后的长整型数: 0, 剩余字符串: "abc123"    num = strtol(nptr5, &endptr, 10);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 指定基数(10)转换后的长整型数: %ld, 剩余字符串: \"%s\"\n", nptr5, num, endptr);    // 原字符串:"123.456", 指定基数(10)转换后的长整型数: 123, 剩余字符串: ".456"    num = strtol(nptr6, &endptr, 10);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 指定基数(10)转换后的长整型数: %ld, 剩余字符串: \"%s\"\n", nptr6, num, endptr);    // 原字符串:"  -6789  ", 指定基数(10)转换后的长整型数: -6789, 剩余字符串: "  "    num = strtol(nptr7, &endptr, 10);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 指定基数(10)转换后的长整型数: %ld, 剩余字符串: \"%s\"\n", nptr7, num, endptr);    // 原字符串:"  +6789  ", 指定基数(10)转换后的长整型数: 6789, 剩余字符串: "  "    num = strtol(nptr8, &endptr, 10);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 指定基数(10)转换后的长整型数(上溢<->LONG_MAX): %ld, 剩余字符串: \"%s\"\n", nptr8, num, endptr);    // 原字符串:"987654321012345678", 指定基数(10)转换后的长整型数(上溢<->LONG_MAX): 2147483647, 剩余字符串: ""    num = strtol(nptr9, &endptr, 10);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 指定基数(10)转换后的长整型数(下溢<->LONG_MIN): %ld, 剩余字符串: \"%s\"\n", nptr9, num, endptr);    // 原字符串:"-987654321012345678", 指定基数(10)转换后的长整型数(下溢<->LONG_MIN): -2147483648, 剩余字符串: ""    num = strtol(nptr10, &endptr, 10);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 指定基数(10)转换后的长整型数: %ld, 剩余字符串: \"%s\"\n", nptr10, num, endptr);    // 原字符串:"", 指定基数(10)转换后的长整型数: 0, 剩余字符串: ""    num = strtol(nptr11, &endptr, 10);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 指定基数(10)转换后的长整型数: %ld, 剩余字符串: \"%s\"\n", nptr11, num, endptr);    // 原字符串:"   ", 指定基数(10)转换后的长整型数: 0, 剩余字符串: "   "    return 0;}

        输出结果如下所示:

5.4 错误处理机制

#include <stdio.h>  // 使用 printf 函数#include <stdlib.h> // 使用 strtod 函数#include <limits.h> // 使用 long 类型的最值:LONG_MAX、LONG_MIN// 定义一个函数来测试 strtol 函数的行为void test_strtol(const char *nptr){    // 定义一个指针,用于 strtol 函数返回未转换部分的起始位置    char *endptr;    // 调用 strtol 函数,基数为 0,会自动根据字符串的格式确定基数    long num = strtol(nptr, &endptr, 0);    // 检查转换是否成功    if (nptr == endptr)    {        // 没有进行任何转换        printf("原字符串\"%s\"转换长整型数失败,长整型数为:%ld,剩余字符串(和原字符串一样):\"%s\"\n", nptr, num, endptr);    }    else if (*endptr != '\0')    {        // 转换在字符串的某个非数字字符处停止        printf("原字符串\"%s\"部分转换成功,转换后的长整型数为:%ld,剩余字符串:\"%s\"\n", nptr, num, endptr);    }    // 并非所有 C 库实现都会在溢出时设置 errno 为 ERANGE    // 通过判断 errno 是否为 ERANGE 是不准确的    // 所以这里不进行 errno==ERANGE 的判断    // 上溢    else if (num == LONG_MAX)    {        printf("原字符串\"%s\"转换成功,但发生了上溢!转换后的长整型数为(LONG_MAX):%ld,剩余字符串(空字符):\"%s\"\n", nptr, num, endptr);    }    // 下溢    else if (num == LONG_MIN)    {        printf("原字符串\"%s\"转换成功,但发生了下溢!转换后的长整型数为(LONG_MIN):%ld,剩余字符串(空字符):\"%s\"\n", nptr, num, endptr);    }    // 完全转换成功    else    {        printf("原字符串\"%s\"完全转换成功,转换后的长整型数为:%ld,剩余字符串(空字符):\"%s\"\n", nptr, num, endptr);    }}int main(){    // 查看 long 类型能表示的数据范围(一般来说 long 和 int 相同)    printf("long 类型的最大值(limits.h 中的 LONG_MAX): %ld\n", LONG_MAX); // 2147483647    printf("long 类型的最小值(limits.h 中的 LONG_MIN): %ld\n", LONG_MIN); // -2147483648    printf("int 类型的最大值(limits.h 中的 INT_MAX): %d\n", INT_MAX);     // 2147483647    printf("int 类型的最小值(limits.h 中的 INT_MIN): %d\n", INT_MIN);     // -2147483648    // 查看字节数(一般来说 long 和 int 相同)    printf("long 类型的字节数:%zu\n", sizeof(long)); // 4    printf("int 类型的字节数:%zu\n", sizeof(int));   // 4    // 测试数据    const char *test_data[] = {        "12345",               // 十进制数        "0x1A3F",              // 十六进制数        "075",                 // 前导 0 可能被识别为八进制数        "abc123",              // 包含非数字字符,转换失败        "123.456",             // 包含非数字字符,部分转换        "  -6789  ",           // 包含前导与尾随空格和负号        "  +6789  ",           // 包含前导与尾随空格和正号        "987654321012345678",  // 溢出,超出 long 类型的最大值        "-987654321012345678", // 溢出,小于 long 类型的最小值        "",                    // 空字符串        "   "                  // 仅包含空白字符    };    // 测试每个数据    for (int i = 0; i < sizeof(test_data) / sizeof(test_data[0]); i++)    {        test_strtol(test_data[i]); // 调用 test_strtol 函数测试每个字符串    }    return 0;}

        输出结果如下所示:

5.5 注意事项

        基数推断:当 base 为 0 时,strtol() 会根据字符串的格式自动推断基数。这提供了灵活性,但也需要了解字符串的可能格式。

        错误处理:虽然 strtol() 本身不提供直接的错误代码(如 errno),但可以通过检查 endptr 的值来推断转换是否成功。

        尾随字符:strtol 会尝试转换字符串直到遇到第一个无法转换为数字的字符。这个字符及其后面的所有字符都会被留在字符串中,endptr 会指向这个字符。因此,即使转换成功(部分转换),也可能有尾随字符。

        溢出处理:strtol() 在遇到超出 long 类型表示范围的数值时,可能会导致溢出(许多实现会返回 LONG_MAX 或 LONG_MIN)。因此,如果需要处理大数值并担心溢出,请考虑使用 strtoll()(对于 long long int)或进行额外的范围检查。

        使用场景:strtol() 是处理整数字符串转换的通用函数,适用于需要解析整数并可能指定不同基数的场景。然而,在处理大数值或需要高精度时,请考虑使用 strtoll() 或其他适当的函数。


6 strtoll()

6.1 函数原型

#include <stdlib.h>  long long int strtoll(const char *restrict nptr, char **restrict endptr, int base);

6.2 功能说明

        strtoll() 函数是 C 语言标准库中的一个函数,用于将字符串转换为长长整型数(long long int)。这个函数对于处理超出 long int 范围的大整数非常有用。其原型定义在 <stdlib.h> 头文件中。

        strtoll 函数与 strtol 函数在功能上是相似的,但主要区别在于它们返回的数据类型以及能够表示的数值范围。strtoll 专注于将字符串转换为 long long int 类型的整数,而 strtol 则转换为 long int 类型的整数。以下是 strtoll 函数的详细功能说明:

        字符串转换:strtoll 函数会扫描参数 nptr 指向的字符串,跳过前面的空白字符(如空格、制表符、换行符等),直到遇到数字或正负符号(+ 或 -)才开始进行转换。转换过程会持续进行,直到遇到非数字字符或字符串结束(\0)为止。

        支持格式:参数 nptr 指向的字符串可以包含正负号(+ 或 -)、数字(0-9)。strtoll 函数同样不支持小数点或指数部分,专注于整数转换。例如,字符串 "123456789012345" 或 "-543210987654321" 都是有效的输入。

        基数(Radix):strtoll 函数允许调用者指定基数,这是一个介于 2 和 36 之间的整数,用于指定转换时使用的数制。如果基数是 0,则 strtoll 会自动根据字符串的格式(如前缀 "0x" 或 "0")来确定基数(十六进制或八进制)。如果基数在有效范围内,strtoll 将使用该基数进行转换。

        错误处理:如果 nptr 指向的字符串无法被转换成有效的 long long int 类型的整数(比如空字符串、仅包含非数字字符的字符串,或者包含小数点、指数等不符合整数格式的字符),strtoll 函数的行为同样取决于基数参数。如果基数是 0,strtoll 会尝试自动检测基数。如果无法识别为有效的整数格式,则转换失败。在转换失败的情况下,strtoll 函数返回 0。

        返回值:转换成功时,strtoll 返回转换后的 long long int 类型的整数。如果转换失败(例如,字符串为空或仅包含无法转换为整数的字符),则返回 0。但需要注意,0 也是一个有效的整数值,因此仅通过返回值可能无法明确区分是转换成功但结果为 0 还是转换失败。为了区分这两种情况,可以使用 endptr 参数进行进一步的判断。

        endptr 参数:如果 endptr 不为 NULL,strtoll 函数会将指向遇到不合条件而终止的 nptr 中的字符的指针赋给 endptr。这样,就可以通过检查 endptr 的值来了解转换在何处停止,进而进行错误处理或进一步分析。若 endptr 指向的字符串与 nptr 相同,表明没有进行任何转换;若 endptr 指向的字符(即 *endptr)并非字符串的终止符('\0'),则意味着字符串中存在非数字字符,导致转换提前终止,仅转换了可转换的那部分;若上述两种情形均未出现,则表明字符串本身被转换成功,不过仍需检查是否发生了上溢或下溢的情况以确保转换结果的有效性。

        上溢和下溢:如果转换的结果超出 long long int 类型的表示范围,strtoll 函数的行为也是未定义的。然而,在许多实现中,如果发生上溢或下溢,strtoll 可能会返回 LLONG_MAX 或 LLONG_MIN(这两个宏定义在 <climits> 或 <limits.h> 中,具体取决于编译器和平台),并可能设置 errno 为 ERANGE(尽管这并非所有实现都会如此)。由于未定义行为的存在,依赖于此进行错误处理可能不是最佳实践。正确的做法是检查转换的上下文和预期范围,并在必要时采取适当的预防措施。

6.3 案例演示

#include <stdio.h>  // 使用 printf 函数#include <stdlib.h> // 使用 strtoll 函数#include <limits.h> // 使用 long long 类型的最值:LLONG_MAX、LLONG_MINint main(){    const char *nptr1 = "12345";                 // 表示十进制数    const char *nptr2 = "0x1A3F";                // 表示十六进制数    const char *nptr3 = "075";                   // 前导 0 可能被识别为八进制数    const char *nptr4 = "abc123";                // 包含非数字字符,转换失败    const char *nptr5 = "123.456";               // 包含非数字字符,部分转换    const char *nptr6 = "  -6789  ";             // 包含前导与尾随空格和负号    const char *nptr7 = "  +6789  ";             // 包含前导与尾随空格和正号    const char *nptr8 = "92233720368547758070";  // 溢出,超出 long long 类型的最大值    const char *nptr9 = "-92233720368547758080"; // 溢出,小于 long long 类型的最小值    const char *nptr10 = "";                     // 空字符串    const char *nptr11 = "   ";                  // 仅包含空白字符    // 查看 long long 类型数据的数据范围    printf("long long 类型的最大值(limits.h 中的 LLONG_MAX): %lld\n", LLONG_MAX); // 9223372036854775807    printf("long long 类型的最小值(limits.h 中的 LLONG_MAX): %lld\n", LLONG_MIN); // -9223372036854775808    // 查看字节数    printf("long long 类型的字节数:%zu\n", sizeof(long long)); // 8    // 定义一个字符指针,用来保存转换后剩余字符串的指针    char *endptr;    long long num;    // &endptr 的类型是一个指向指针的指针,也就是 char ** 类型,通常被称为二级指针或指针的指针    num = strtoll(nptr1, &endptr, 10);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 指定基数(10)转换后的长长整型数: %lld, 剩余字符串: \"%s\"\n", nptr1, num, endptr);    // 原字符串:"12345", 指定基数(10)转换后的长长整型数: 12345, 剩余字符串: ""    num = strtoll(nptr1, &endptr, 0); // 如果基数是 0,strtoll 会尝试自动检测基数    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 自动检测基数(0)转换后的长长整型数: %lld, 剩余字符串: \"%s\"\n", nptr1, num, endptr);    // 原字符串:"12345", 自动检测基数(0)转换后的长长整型数: 12345, 剩余字符串: ""    num = strtoll(nptr2, &endptr, 16);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 指定基数(16)转换后的长长整型数: %lld, 剩余字符串: \"%s\"\n", nptr2, num, endptr);    // 原字符串:"0x1A3F", 指定基数(16)转换后的长长整型数: 6719, 剩余字符串: ""    num = strtoll(nptr2, &endptr, 0); // 如果基数是 0,strtoll 会尝试自动检测基数    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 自动检测基数(0)转换后的长长整型数: %lld, 剩余字符串: \"%s\"\n", nptr2, num, endptr);    // 原字符串:"0x1A3F", 自动检测基数(0)转换后的长长整型数: 6719, 剩余字符串: ""    num = strtoll(nptr3, &endptr, 8);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 指定基数(8)转换后的长长整型数: %lld, 剩余字符串: \"%s\"\n", nptr3, num, endptr);    // 原字符串:"075", 指定基数(8)转换后的长长整型数: 61, 剩余字符串: ""    num = strtoll(nptr3, &endptr, 0); // 如果基数是 0,strtoll 会尝试自动检测基数    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 自动检测基数(0)转换后的长长整型数: %lld, 剩余字符串: \"%s\"\n", nptr3, num, endptr);    // 原字符串:"075", 自动检测基数(0)转换后的长长整型数: 61, 剩余字符串: ""    num = strtoll(nptr4, &endptr, 10);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 指定基数(10)转换后的长长整型数: %lld, 剩余字符串: \"%s\"\n", nptr4, num, endptr);    // 原字符串:"abc123", 指定基数(10)转换后的长长整型数: 0, 剩余字符串: "abc123"    num = strtoll(nptr5, &endptr, 10);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 指定基数(10)转换后的长长整型数: %lld, 剩余字符串: \"%s\"\n", nptr5, num, endptr);    // 原字符串:"123.456", 指定基数(10)转换后的长长整型数: 123, 剩余字符串: ".456"    num = strtoll(nptr6, &endptr, 10);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 指定基数(10)转换后的长长整型数: %lld, 剩余字符串: \"%s\"\n", nptr6, num, endptr);    // 原字符串:"  -6789  ", 指定基数(10)转换后的长长整型数: -6789, 剩余字符串: "  "    num = strtoll(nptr7, &endptr, 10);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 指定基数(10)转换后的长长整型数: %lld, 剩余字符串: \"%s\"\n", nptr7, num, endptr);    // 原字符串:"  +6789  ", 指定基数(10)转换后的长长整型数: 6789, 剩余字符串: "  "    num = strtoll(nptr8, &endptr, 10);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 指定基数(10)转换后的长长整型数(上溢<->LLONG_MAX): %lld, 剩余字符串: \"%s\"\n", nptr8, num, endptr);    // 原字符串:"92233720368547758070", 指定基数(10)转换后的长长整型数(上溢<->LLONG_MAX): 9223372036854775807, 剩余字符串: ""    num = strtoll(nptr9, &endptr, 10);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 指定基数(10)转换后的长长整型数(下溢<->LLONG_MIN): %lld, 剩余字符串: \"%s\"\n", nptr9, num, endptr);    // 字符串:"-92233720368547758080", 指定基数(10)转换后的长长整型数(下溢<->LLONG_MIN): -9223372036854775808, 剩余字符串: ""    num = strtoll(nptr10, &endptr, 10);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 指定基数(10)转换后的长长整型数: %lld, 剩余字符串: \"%s\"\n", nptr10, num, endptr);    // 原字符串:"", 指定基数(10)转换后的长长整型数: 0, 剩余字符串: ""    num = strtoll(nptr11, &endptr, 10);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 指定基数(10)转换后的长长整型数: %lld, 剩余字符串: \"%s\"\n", nptr11, num, endptr);    // 原字符串:"   ", 指定基数(10)转换后的长长整型数: 0, 剩余字符串: "   "    return 0;}

        输出结果如下所示:

6.4 错误处理机制

#include <stdio.h>  // 使用 printf 函数#include <stdlib.h> // 使用 strtoll 函数#include <limits.h> // 使用 long long 类型的最值:LLONG_MAX、LLONG_MIN// 定义一个函数来测试 strtoll 函数的行为void test_strtoll(const char *nptr){    // 定义一个指针,用于 strtoll 函数返回未转换部分的起始位置    char *endptr;    // 调用 strtoll 函数,基数为 0,会自动根据字符串的格式确定基数    long long num = strtoll(nptr, &endptr, 0);    // 检查转换是否成功    if (nptr == endptr)    {        // 没有进行任何转换        printf("原字符串\"%s\"转换长长整型数失败,长长整型数为:%lld,剩余字符串(和原字符串一样):\"%s\"\n", nptr, num, endptr);    }    else if (*endptr != '\0')    {        // 转换在字符串的某个非数字字符处停止        printf("原字符串\"%s\"部分转换成功,转换后的长长整型数为:%lld,剩余字符串:\"%s\"\n", nptr, num, endptr);    }    // 并非所有 C 库实现都会在溢出时设置 errno 为 ERANGE    // 通过判断 errno 是否为 ERANGE 是不够准确的    // 所以这里不进行 errno==ERANGE 的判断    // 上溢    else if (num == LLONG_MAX)    {        printf("原字符串\"%s\"转换成功,但发生了上溢!转换后的长长整型数为(LLONG_MAX):%lld,剩余字符串(空字符):\"%s\"\n", nptr, num, endptr);    }    // 下溢    else if (num == LLONG_MIN)    {        printf("原字符串\"%s\"转换成功,但发生了下溢!转换后的长长整型数为(LLONG_MIN):%lld,剩余字符串(空字符):\"%s\"\n", nptr, num, endptr);    }    // 完全转换成功    else    {        printf("原字符串\"%s\"完全转换成功,转换后的长长整型数为:%lld,剩余字符串(空字符):\"%s\"\n", nptr, num, endptr);    }}int main(){    // 查看 long long 类型能表示的数据范围    printf("long long 类型的最大值(limits.h 中的 LLONG_MAX): %lld\n", LLONG_MAX); // 9223372036854775807    printf("long long 类型的最小值(limits.h 中的 LLONG_MIN): %lld\n", LLONG_MIN); // -9223372036854775808    printf("int 类型的最大值(limits.h 中的 INT_MAX): %d\n", INT_MAX);             // 2147483647    printf("int 类型的最小值(limits.h 中的 INT_MIN): %d\n", INT_MIN);             // -2147483648    // 查看字节数    printf("long long 类型的字节数:%zu\n", sizeof(long long)); // 8    printf("int 类型的字节数:%zu\n", sizeof(int));             // 4    // 测试数据    const char *test_data[] = {        "12345",                 // 十进制数        "0x1A3F",                // 十六进制数        "075",                   // 前导 0 可能被识别为八进制数        "abc123",                // 包含非数字字符,转换失败        "123.456",               // 包含非数字字符,部分转换        "  -6789  ",             // 包含前导与尾随空格和负号        "  +6789  ",             // 包含前导与尾随空格和正号        "92233720368547758070",  // 溢出,超出 long long 类型的最大值        "-92233720368547758080", // 溢出,小于 long long 类型的最小值        "",                      // 空字符串        "   "                    // 仅包含空白字符    };    // 测试每个数据    for (int i = 0; i < sizeof(test_data) / sizeof(test_data[0]); i++)    {        test_strtoll(test_data[i]); // 调用 test_strtoll 函数测试每个字符串    }    return 0;}

        输出结果如下所示:

6.5 注意事项

        基数推断:当 base 为 0 时,strtoll() 会根据字符串的格式自动推断基数。这提供了灵活性,但也要求了解字符串的可能格式。

        错误处理:虽然 strtoll() 本身不提供直接的错误代码(如 errno),但可以通过检查 endptr 的值来推断转换是否成功。

        尾随字符:strtoll 会尝试转换字符串直到遇到第一个无法转换为数字的字符。这个字符及其后面的所有字符都会被留在字符串中,endptr 会指向这个字符。因此,即使转换成功(部分转换),也可能有尾随字符。

        溢出处理:strtoll() 在遇到超出 long long 类型表示范围的数值时,可能会导致溢出(许多实现会返回 LLONG_MAX 或 LLONG_MIN)。

        使用场景:strtoll() 是处理大整数字符串转换的理想选择,特别是当需要处理可能超出 long int 范围的数值时。然而,在处理极大数值时,请考虑数值的溢出问题,并可能需要采取额外的措施来确保结果的准确性。


7 strtoul()

7.1 函数原型

#include <stdlib.h>  unsigned long int strtoul(const char *restrict nptr, char **restrict endptr, int base);

7.2 功能说明

        strtoul() 函数是 C 语言标准库中的一个函数,用于将字符串转换为无符号长整型数(unsigned long int)。其原型定义在 <stdlib.h> 头文件中。与 strtol 函数类似,但有几个关键的区别,特别是在处理正负号和返回值范围上。以下是 strtoul 函数的功能详细说明:

        字符串转换:strtoul 函数会扫描参数 nptr 指向的字符串,跳过前面的空白字符(如空格、制表符、换行符等),直到遇到数字或正负符号(+ 或 -)才开始进行转换。转换过程会持续进行,直到遇到非数字字符或字符串结束(\0)为止。对于负数,底层处理的是其二进制数位表示的无符号正数。

        支持格式:参数 nptr 指向的字符串应包含要转换的无符号整数表示。由于 strtoul 是为无符号整数设计的,因此它不会输出负数。字符串可以包含正负号(+ 或 -,但对于负数,计算机底层处理的是二进制数位表示的无符号正数)、数字(0-9)以及可能的基数前缀(如 "0x" 或 "0" 表示十六进制或八进制)。

        基数(Radix):strtoul 函数同样允许调用者指定基数,这是一个介于 2 和 36 之间的整数,用于指定转换时使用的数制。如果基数是 0,则 strtoul 会自动根据字符串的格式(如前缀 "0x" 或 "0")来确定基数。具体来说,如果字符串以 "0x" 或 "0X" 开头,则基数为 16;如果以 "0" 开头但不以 "0x" 或 "0X" 开头,则基数为 8;否则,基数默认为 10。

        错误处理:如果 nptr 指向的字符串无法被转换成有效的无符号长整型数(比如空字符串、仅包含非数字字符的字符串,或者包含无法识别为有效整数的字符),strtoul 函数的行为取决于基数参数。在大多数情况下,如果无法转换整个字符串,strtoul 会返回已经转换的部分(即,在遇到第一个无法转换的字符之前已经转换的数字)。如果字符串不包含任何可转换的数字字符,则 strtoul 返回 0。但是,请注意,0 也是一个有效的无符号整数值,因此仅通过返回值可能无法明确区分是转换成功但结果为 0 还是转换失败。此时,可以通过检查 endptr 的值来进一步判断。

        返回值:转换成功时,strtoul 返回转换后的无符号长整型数(unsigned long int 类型)。如果转换失败(例如,字符串为空或仅包含无法转换为整数的字符),则返回 0。但是,如前所述,仅通过返回值可能无法准确判断转换是否成功。

        endptr 参数:如果 endptr 不为 NULL,strtoul 函数会将指向遇到不合条件而终止的 nptr 中的字符的指针赋给 endptr。这样,就可以通过检查 endptr 的值来了解转换在何处停止,进而进行错误处理或进一步分析。若 endptr 指向的字符串与 nptr 相同,表明没有进行任何转换;若 endptr 指向的字符(即 *endptr)并非字符串的终止符('\0'),则意味着字符串中存在非数字字符,导致转换提前终止,仅转换了可转换的那部分;若上述两种情形均未出现,则表明字符串本身被转换成功,不过仍需检查是否发生了上溢或下溢的情况以确保转换结果的有效性。

        溢出处理:如果转换的结果超出 unsigned long int 类型的表示范围,strtoul 函数的行为是未定义的。然而,在许多实现中,如果发生溢出,strtoul 会返回 ULONG_MAX(定义在 <limits.h> 中),但可能不会设置 errno 为 ERANGE(尽管这并非所有实现都会如此)。由于未定义行为的存在,依赖于此进行错误处理可能不是最佳实践。正确的做法是检查转换的上下文和预期范围,并在必要时采取适当的预防措施。

7.3 案例演示

#include <stdio.h>  // 使用 printf 函数#include <stdlib.h> // 使用 strtoul 函数#include <limits.h> // 使用 unsigned long 类型的最值:ULONG_MAXint main(){    const char *nptr1 = "12345";       // 表示十进制数    const char *nptr2 = "0x1A3F";      // 表示十六进制数    const char *nptr3 = "075";         // 前导 0 可能被识别为八进制数    const char *nptr4 = "abc123";      // 包含非数字字符,转换失败    const char *nptr5 = "123.456";     // 包含非数字字符,部分转换    const char *nptr6 = "  -6789  ";   // 包含前导与尾随空格和负号    const char *nptr7 = "  +6789  ";   // 包含前导与尾随空格和正号    const char *nptr8 = "42949672950"; // 溢出,超出 unsigned long 类型的最大值    const char *nptr9 = "";            // 空字符串    const char *nptr10 = "   ";        // 仅包含空白字符    // 查看 unsigned long 类型能表示的数据范围    printf("unsigned long 类型的最大值(limits.h 中的 ULONG_MAX): %lu\n", ULONG_MAX); // 4294967295    // 查看字节数    printf("unsigned long 类型的字节数:%zu\n", sizeof(unsigned long)); // 4    // 定义一个字符指针,用来保存转换后剩余字符串的指针    char *endptr;    unsigned long num;    // &endptr 的类型是一个指向指针的指针,也就是 char ** 类型,通常被称为二级指针或指针的指针    num = strtoul(nptr1, &endptr, 10);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 指定基数(10)转换后的无符号长整型数: %lu, 剩余字符串: \"%s\"\n", nptr1, num, endptr);    // 原字符串:"12345", 指定基数(10)转换后的无符号长整型数: 12345, 剩余字符串: ""    num = strtoul(nptr1, &endptr, 0); // 如果基数是 0,strtoul 会尝试自动检测基数    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 自动检测基数(0)转换后的无符号长整型数: %lu, 剩余字符串: \"%s\"\n", nptr1, num, endptr);    // 原字符串:"12345", 自动检测基数(0)转换后的无符号长整型数: 12345, 剩余字符串: ""    num = strtoul(nptr2, &endptr, 16);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 指定基数(16)转换后的无符号长整型数: %lu, 剩余字符串: \"%s\"\n", nptr2, num, endptr);    // 原字符串:"0x1A3F", 指定基数(16)转换后的无符号长整型数: 6719, 剩余字符串: ""    num = strtoul(nptr2, &endptr, 0); // 如果基数是 0,strtoul 会尝试自动检测基数    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 自动检测基数(0)转换后的无符号长整型数: %lu, 剩余字符串: \"%s\"\n", nptr2, num, endptr);    // 字符串:"0x1A3F", 自动检测基数(0)转换后的无符号长整型数: 6719, 剩余字符串: ""    num = strtoul(nptr3, &endptr, 8);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 指定基数(8)转换后的无符号长整型数: %lu, 剩余字符串: \"%s\"\n", nptr3, num, endptr);    // 字符串:"075", 指定基数(8)转换后的无符号长整型数: 61, 剩余字符串: ""    num = strtoul(nptr3, &endptr, 0); // 如果基数是 0,strtoul 会尝试自动检测基数    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 自动检测基数(0)转换后的无符号长整型数: %lu, 剩余字符串: \"%s\"\n", nptr3, num, endptr);    // 原字符串:"075", 自动检测基数(0)转换后的无符号长整型数: 61, 剩余字符串: ""    num = strtoul(nptr4, &endptr, 10);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 指定基数(10)转换后的无符号长整型数: %lu, 剩余字符串: \"%s\"\n", nptr4, num, endptr);    // 原字符串:"abc123", 指定基数(10)转换后的无符号长整型数: 0, 剩余字符串: "abc123"    num = strtoul(nptr5, &endptr, 10);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 指定基数(10)转换后的无符号长整型数: %lu, 剩余字符串: \"%s\"\n", nptr5, num, endptr);    // 原字符串:"123.456", 指定基数(10)转换后的无符号长整型数: 123, 剩余字符串: ".456"    num = strtoul(nptr6, &endptr, 10);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 指定基数(10)转换后的无符号长整型数: %lu, 剩余字符串: \"%s\"\n", nptr6, num, endptr);    // 原字符串:"  -6789  ", 指定基数(10)转换后的无符号长整型数: 4294960507, 剩余字符串: "  "    num = strtoul(nptr7, &endptr, 10);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 指定基数(10)转换后的无符号长整型数: %lu, 剩余字符串: \"%s\"\n", nptr7, num, endptr);    // 原字符串:"  +6789  ", 指定基数(10)转换后的无符号长整型数: 6789, 剩余字符串: "  "    num = strtoul(nptr8, &endptr, 10);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 指定基数(10)转换后的无符号长整型数(溢出<->ULONG_MAX): %lu, 剩余字符串: \"%s\"\n", nptr8, num, endptr);    // 原字符串:"42949672950", 指定基数(10)转换后的无符号长整型数(上溢<->ULONG_MAX): 4294967295, 剩余字符串: ""    num = strtoul(nptr9, &endptr, 10);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 指定基数(10)转换后的无符号长整型数: %lu, 剩余字符串: \"%s\"\n", nptr9, num, endptr);    // 原字符串:"", 指定基数(10)转换后的无符号长整型数: 0, 剩余字符串: ""    num = strtoul(nptr10, &endptr, 10);    // 使用转义字符输出双引号    printf("原字符串:\"%s\", 指定基数(10)转换后的无符号长整型数: %lu, 剩余字符串: \"%s\"\n", nptr10, num, endptr);    // 原字符串:"   ", 指定基数(10)转换后的无符号长整型数: 0, 剩余字符串: "   "    // 字符串 nptr6 溢出解释:    // -6789 对应的二进制如下所示:    // 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1110 0101 0111 1011    // 低 32 位(因为 unsigned long 类型为 4 字节)二进制为:1111 1111 1111 1111 1110 0101 0111 1011    // 如果使用 %lu 读取数据,表示无符号十进制数据,即为:4294960507    return 0;}

        输出结果如下所示:

7.4 错误处理机制

#include <stdio.h>  // 使用 printf 函数#include <stdlib.h> // 使用 strtoul 函数#include <limits.h> // 使用 unsigned long 类型的最值:ULONG_MAX#include <ctype.h>  // 使用 isspace 判断空白字符// 定义一个函数来测试 strtoul 函数的行为void test_strtoul(const char *nptr){    // 定义一个指针,用于保存传递过来的字符串    const char *temp = nptr;    // 定义一个指针,用于 strtoul 函数返回未转换部分的起始位置    char *endptr;    // 跳过字符串开头的空白字符,因为后面要判断负号    while (isspace((unsigned char)*nptr))    {        nptr++;    }    // 调用 strtoul 函数,基数为 0,会自动根据字符串的格式确定基数    unsigned long num = strtoul(nptr, &endptr, 0);    // 检查转换是否成功    if (temp == endptr)    {        // 没有进行任何转换        printf("原字符串\"%s\"转换无符号长整型数失败,无符号长整型数为:%lu,剩余字符串(和原字符串一样):\"%s\"\n", nptr, num, endptr);    }    else if (*nptr == '-')    {        // 负数处理        printf("原字符串\"%s\"含有负号,转换结果可能是经过回绕处理的,转换后的无符号长整型数为:%lu,剩余字符串:\"%s\"\n", nptr, num, endptr);    }    else if (*endptr != '\0')    {        // 转换在字符串的某个非数字字符处停止        printf("原字符串\"%s\"部分转换成功,转换后的无符号长整型数为:%lu,剩余字符串:\"%s\"\n", nptr, num, endptr);    }    // 并非所有 C 库实现都会在溢出时设置 errno 为 ERANGE    // 通过判断 errno 是否为 ERANGE 是不够准确的    // 所以这里不进行 errno==ERANGE 的判断    // 溢出    else if (num == ULONG_MAX)    {        printf("原字符串\"%s\"转换成功,但发生了溢出!转换后的无符号长整型数为(ULONG_MAX):%lu,剩余字符串(空字符):\"%s\"\n", nptr, num, endptr);    }    // 完全转换成功    else    {        printf("原字符串\"%s\"完全转换成功,转换后的无符号长整型数为:%lu,剩余字符串(空字符):\"%s\"\n", nptr, num, endptr);    }}int main(){    // 查看 unsigned long 类型能表示的数据范围    printf("unsigned long 类型的最大值(limits.h 中的 ULONG_MAX): %lu\n", ULONG_MAX); // 4294967295    // 查看字节数    printf("unsigned long 类型的字节数:%zu\n", sizeof(unsigned long)); // 4    // 测试数据    const char *test_data[] = {        "12345",       // 十进制数        "0x1A3F",      // 十六进制数        "075",         // 前导 0 可能被识别为八进制数        "abc123",      // 包含非数字字符,转换失败        "123.456",     // 包含非数字字符,部分转换        "  -6789",   // 包含前导与尾随空格和负号        "  +6789  ",   // 包含前导与尾随空格和正号        "42949672950", // 溢出,超出 unsigned long 类型的最大值        "",            // 空字符串        "   "          // 仅包含空白字符    };    // 测试每个数据    for (int i = 0; i < sizeof(test_data) / sizeof(test_data[0]); i++)    {        test_strtoul(test_data[i]); // 调用 test_strtoul 函数测试每个字符串    }    return 0;}

        输出结果如下所示:

7.5 注意事项

        基数推断:当 base 为 0 时,strtoul() 会根据字符串的格式自动推断基数。这提供了灵活性,但也要求了解字符串的可能格式。

         错误处理:与 strtol() 和 strtoll() 类似,strtoul() 本身不提供直接的错误代码(如 errno),但可以通过检查 endptr 的值来推断转换是否成功。

        尾随字符:strtoul 会尝试转换字符串直到遇到第一个无法转换为数字的字符。这个字符及其后面的所有字符都会被留在字符串中,endptr 会指向这个字符。因此,即使转换成功(部分转换),也可能有尾随字符。

        负号处理:strtoul() 在处理负数时,计算机底层处理的是其二进制数位表示的无符号正数,这涉及到计算机底层的补码存储原理(回绕处理),可以通过该数据类型所占用的位数来表示相应长度的二进制数据,并据此执行计算。

        溢出处理:当输入的数字超出 unsigned long int 的表示范围时,strtoul() 会导致溢出,因此,需要通过比较结果和 ULONG_MAX(在 <limits.h> 中定义)来手动检测溢出。

        使用场景:strtoul() 适用于处理期望为正数或零的数值字符串,并且当需要的结果类型是无符号长整型时。然而,在处理可能包含负号或极大数值时,请特别注意负号的问题。


8 总结对比表

函数原型功能说明错误处理
#include <stdlib.h>  
double atof(const char *str);
将字符串转换成浮点数(double 类型)无直接错误处理,返回转换的浮点数或 0.0(如果无法转换)

#include <stdlib.h>  
int atoi(const char *str);

将字符串转换成整数(int 类型)无直接错误处理,返回转换的整数或 0(如果无法转换)

#include <stdlib.h>

long atol(const char *str);

将字符串转换为长整型(long 类型)的整数无直接错误处理,返回转换的长整数或 0(如果无法转换)
#include <stdlib.h>  
double strtod(const char *restrict nptr, char **restrict endptr);
将字符串转换为双精度浮点数(double 类型),并设置结束指针。第二个参数指向第一个未转换字符的指针,返回转换的浮点数或 0.0(如果无法转换)
#include <stdlib.h>  
long int strtol(const char *restrict nptr, char **restrict endptr, int base);
将字符串转换为长整型数(long int),并设置结束指针。第二个参数指向第一个未转换字符的指针,返回转换的长整数或 0(如果无法转换)
#include <stdlib.h>  
long long int strtoll(const char *restrict nptr, char **restrict endptr, int base);
将字符串转换为长长整型数(long long int),并设置结束指针。第二个参数指向第一个未转换字符的指针,返回转换的长长整数或 0(如果无法转换)
#include <stdlib.h>  
unsigned long int strtoul(const char *restrict nptr, char **restrict endptr, int base);
将字符串转换为无符号长整型数(unsigned long int),并设置结束指针。第二个参数指向第一个未转换字符的指针,返回转换的无符号长整数或 0(如果无法转换)
atof(), atoi() 和 atol() 函数不提供直接的错误处理机制,它们只是简单地尝试将字符串转换为相应的数值类型,并在无法转换时返回特定的值(通常是 0 或 0.0)。strtod(), strtol(), strtoll() 和 strtoul() 函数提供了更灵活的错误处理机制,通过第二个参数(一个指向 char* 的指针)返回第一个未成功转换的字符的指针。这允许调用者检查转换是否成功以及在哪里停止。基数(radix)参数允许 strtod(), strtol(), strtoll() 和 strtoul() 函数根据给定的基数(2 到 36 之间)来解释字符串中的数字。如果基数为 0,则函数会自动根据字符串的格式(以 "0x" 或 "0X" 开头的为 16 进制,以 "0" 开头的为 8 进制,否则为 10 进制)来确定基数。strtod() 函数专门用于浮点数转换,而 strtol(), strtoll() 和 strtoul() 函数则用于整数转换,但支持不同的整数类型。

点击全文阅读


本文链接:http://zhangshiyu.com/post/170516.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1