C库函数-strtol()
概述
strtol()是C库函数,其功能是用于将字符串转换成整数。函数原型为
long int strtol(const char *str,char **endptr,int base)
该函数将str所指向的字符串根据给定的base转换为一个long int型的长整数,base必须介于2和36之间,或者是特殊值0。
参数
- str:要转换为长整数的字符串
- endptr:对类型为char* 的对象的引用,其值由函数设置为str中数值后的下一个字符。
- base:基数,必须介于2和36之间,或者是特殊值0。base代表的采用的进制方式。如base值为2就代表采用的二进制。当base值为0时则是采用10进制做转换。当遇到"0x"前置字符则会采用16进制做转换。
功能
- 开始时,strtol会扫描nptr所指向的字符串,这时它会跳过非法字符,如:空格。直到遇见数字或者"+、—“号才开始转换,再遇到非数字或者”\0"时结束转换。并将结果返回(返回长整型的整数)。
返回值
- 返回转换之后的长整型数,否则对异常返回并且设置errno。
几点说明
- strtol会从nptr所指向字符串的头不开始查找,当遇到数字或"+、-"时就开始转换,遇到其他的字符则停止转换并返回。如果字符串中穿插着两串数字,则只会对第一串数字进行转换。
- 如果endptr不是NULL,strtol()会将第一个无效的字符的地址方到*enptr。
- 如果字符串中没有数字,strtol()会将nptr的初始值,存储到endptr中,并且返回0。
例程
#include <stdlib.h>
#include <limits.h>
#include <stdio.h>
#include <errno.h>
int main(int argc, char *argv[])
{
int base;
char *endptr, *str;
long val;
if (argc < 2) {
fprintf(stderr, "Usage: %s str [base]/n", argv[0]);
/*目的是从命令行获取字符串,如果在命令行没有输入字符串则会退出。*/
exit(EXIT_FAILURE);
}
str = argv[1]; //获取命令行的第一个字符串
base = (argc > 2) ? atoi(argv[2]) : 10;
/*如果命令行没有输入base值,则默认为10,即按照十进制进行输出。atoi()函数是将字符串转换成相应的整数*/
errno = 0;
/*对调用结束之后的结果进行分析(避免'0'的影响),因为错误和字符传中只含有0时返回值都是0,但错误时会重置errno,此时就可以通过errno来区分二者*/
val = strtol(str, &endptr, base);
/* 检查不同的错误*/
if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN)) || (errno != 0 && val == 0)) {
perror("strtol");
exit(EXIT_FAILURE);
}
if (endptr == str) {
fprintf(stderr, "No digits were found/n");
exit(EXIT_FAILURE);
}
/* If we got here, strtol() successfully parsed
a number */
printf("strtol() returned %ld/n", val);
/* Not necessarily an error... */
if (*endptr != '/0')
printf("Further characters after number: %s/n", endptr);
exit(EXIT_SUCCESS);
}
<注:上面这段程序是Linux中strtol()函数帮助文档中一个例子程序,我在相应的部分添加了注释>
- 该函数功能和使用其实还是挺简单的,多敲几个例子基本就可以摸清楚怎么玩儿。
源码
下面附上该函数实现的源码,有兴趣的同学可以琢磨一下。
long strtol(const char * restrict nptr, char ** restrict endptr, int base)
{
const char *s;
unsigned long acc;
char c;
unsigned long cutoff;
int neg, any, cutlim;
s = nptr;
do {
c = *s++;
} while (isspace((unsigned char)c));
if (c == '-') {
neg = 1;
c = *s++; //去掉前导空格和+ - 符号
} else {
neg = 0;
if (c == '+')
c = *s++;
}
//判断进制,并去除前导0x或者0
if ((base == 0 || base == 16) &&
c == '0' && (*s == 'x' || *s == 'X') &&
((s[1] >= '0' && s[1] <= '9') ||
(s[1] >= 'A' && s[1] <= 'F') ||
(s[1] >= 'a' && s[1] <= 'f'))) {
c = s[1];
s += 2;
base = 16;
}
if (base == 0)
base = c == '0' ? 8 : 10;
acc = any = 0;
if (base < 2 || base > 36)
goto noconv;
/*判断溢出的方法:
cutoff为系统能够表示的最大数除以base的结果,也就是当前进制能够表示的最大有效的数。
例如32为系统下长整形的范围是[-2147483648..2147483647],如果base是10的话,则cutoff就是
214748364,而cutlim就是7(正整数)或者8(负整数)。如果当前算得的值大于cutoff就溢出了,或者等于cutoff但是下一位大于cutlim也就溢出了
*/
cutoff = neg ? (unsigned long)-(LONG_MIN + LONG_MAX) + LONG_MAX
: LONG_MAX;
cutlim = cutoff % base;
cutoff /= base;
for ( ; ; c = *s++) {
if (c >= '0' && c <= '9')
c -= '0';
else if (c >= 'A' && c <= 'Z')
c -= 'A' - 10;
else if (c >= 'a' && c <= 'z')
c -= 'a' - 10;
else
break;
if (c >= base)
break;
//如果溢出则设置any为负数
if (any < 0 || acc > cutoff || (acc == cutoff && c > cutlim))
any = -1;
else {
any = 1;
acc *= base;
acc += c;
}
}
if (any < 0) { //如果溢出就返回最大能表示的值
acc = neg ? LONG_MIN : LONG_MAX;
errno = ERANGE;
} else if (!any) {
noconv:
errno = EINVAL;
} else if (neg)
acc = -acc;
if (endptr != NULL)
*endptr = (char *)(any ? s - 1 : nptr);
return (acc);
}
<注:其中关键字restrict是C99引入的,它只用于限定和约束指针并标明指针是访问一个数据对象的唯一且初始的方式,**即它告诉编译器,所有修改该指针所指向内存中内容的操作都必须通过该指针来修改,而不能通过其他途径(其他变量或指针)来修改;**这样做的好处是:**能帮助编译器进行更好的有优化代码,生成更有效率的汇编代码。**如上面char * restrict nptr表明ptr指向的内存单元只能由访问到,其他任何同样指向这个内存单元的其他指针都是未定义的,即无效指针(野指针)>