24.C语言标准库函数(中)
本篇原文为:C语言标准库函数(中)。
更多C++进阶、rust、python、逆向等等教程,可点击此链接查看:酷程网
11.stdint
stdint.h
定义了一些固定宽度的整数类型别名,主要有下面三类:
- 宽度完全确定的整数
intN_t
,比如int32_t
。 - 宽度不小于某个大小的整数
int_leastN_t
,比如int_least8_t
。 - 宽度不小于某个大小、并且处理速度尽可能快的整数
int_fastN_t
,比如int_fast64_t
。
上面所有类型都是有符号的,类型名前面可以加一个前缀u
,表示无符号类型,比如uint16_t
。
C 语言标准要求定义以下类型:
- int8_t(可选) uint8_t(可选)
- int16_t(可选) uint16_t(可选)
- int32_t(可选) uint32_t(可选)
- int64_t(可选) uint64_t(可选)
- int_least8_t uint_least8_t
- int_least16_t uint_least16_t
- int_least32_t uint_least32_t
- int_least64_t uint_least64_t
- int_fast8_t uint_fast8_t
- int_fast16_t uint_fast16_t
- int_fast32_t uint_fast32_t
- int_fast64_t uint_fast64_t
以下两个类型表示当前系统可用的最大宽度整数。
- intmax_t
- uintmax_t
如果想要尽可能大的整数时,可以使用上面类型。
以下一些带参数的宏,可以生成固定宽度的整数常量。
- INT8_C(x) UINT8_C(x)
- INT16_C(x) UINT16_C(x)
- INT32_C(x) UINT32_C(x)
- INT64_C(x) UINT64_C(x)
- INTMAX_C(x) UINTMAX_C(x)
下面是用法示例:
uint16_t x = UINT16_C(12);
intmax_t y = INTMAX_C(3490);
下面一些宏代表了固定宽度的整数最大值和最小值。
- INT8_MAX INT8_MIN UINT8_MAX
- INT16_MAX INT16_MIN UINT16_MAX
- INT32_MAX INT32_MIN UINT32_MAX
- INT64_MAX INT64_MIN UINT64_MAX
- INT_LEAST8_MAX INT_LEAST8_MIN UINT_LEAST8_MAX
- INT_LEAST16_MAX INT_LEAST16_MIN UINT_LEAST16_MAX
- INT_LEAST32_MAX INT_LEAST32_MIN UINT_LEAST32_MAX
- INT_LEAST64_MAX INT_LEAST64_MIN UINT_LEAST64_MAX
- INT_FAST8_MAX INT_FAST8_MIN UINT_FAST8_MAX
- INT_FAST16_MAX INT_FAST16_MIN UINT_FAST16_MAX
- INT_FAST32_MAX INT_FAST32_MIN UINT_FAST32_MAX
- INT_FAST64_MAX INT_FAST64_MIN UINT_FAST64_MAX
- INTMAX_MAX INTMAX_MIN UINTMAX_MAX
注意,所有无符号整数类型的最小值都为0,所以没有对应的宏。
12.stdlib
stdlib.h
定义了下面的类型别名。
size_t
:sizeof
的返回类型。wchar_t
:宽字符类型。
stdlib.h
定义了下面的宏。
NULL
:空指针。EXIT_SUCCESS
:函数运行成功时的退出状态。EXIT_FAILURE
:函数运行错误时的退出状态。RAND_MAX
:rand() 函数可以返回的最大值。MB_CUR_MAX
:当前语言环境中,多字节字符占用的最大字节数。
abs
、labs
、llabs
这三个函数用于计算整数的绝对值。abs()
用于 int 类型,labs()
用于 long int 类型,llabs()
用于 long long int 类型:
int abs(int j);
long int labs(long int j);
long long int llabs(long long int j);
下面是用法示例:
// 输出 |-2| = 2
printf("|-2| = %d\n", abs(-2));
// 输出 |4| = 4
printf("|4| = %d\n", abs(4));
div(),ldiv(),lldiv() 这三个函数用来计算两个参数的商和余数。
div()
用于 int 类型的相除,ldiv()
用于 long int 类型的相除,lldiv()
用于 long long int 类型的相除:
div_t div(int numer, int denom);
ldiv_t ldiv(long int numer, long int denom);
lldiv_t lldiv(long long int numer, long long int denom);
这些函数把第2个参数(分母)除以第1个参数(分子),产生商和余数。
这两个值通过一个数据结构返回,div()
返回 div_t 结构,ldiv()
返回 ldiv_t 结构,lldiv()
返回 lldiv_t 结构。
这些结构都包含下面两个字段:
int quot; // 商
int rem; // 余数
它们完整的定义如下:
typedef struct {
int quot, rem;
} div_t;
typedef struct {
long int quot, rem;
} ldiv_t;
typedef struct {
long long int quot, rem;
} lldiv_t;
下面是一个例子:
div_t d = div(64, -7);
// 输出 64 / -7 = -9
printf("64 / -7 = %d\n", d.quot);
// 输出 64 % -7 = 1
printf("64 %% -7 = %d\n", d.rem);
stdlib.h
定义了一系列函数,可以将字符串转为数字。
- atoi():字符串转成 int 类型。
- atof():字符串转成 double 类型。
- atol():字符串转成 long int 类型。
- atoll():字符串转成 long long int 类型。
它们的原型如下:
int atoi(const char* nptr);
double atof(const char* nptr);
long int atol(const char* nptr);
long long int atoll(const char* nptr);
上面函数的参数都是一个字符串指针,字符串开头的空格会被忽略,转换到第一个无效字符处停止。函数名称里面的a
代表 ASCII,所以atoi()
的意思是“ASCII to int”。
它们返回转换后的数值,如果字符串无法转换,则返回0
。
下面是用法示例:
atoi("3490") // 3490
atof("3.141593") // 3.141593
如果参数是数字开头的字符串,atoi()
会只转换数字部分,比如atoi("42regular")
会返回整数42
。如果首字符不是数字,比如“hello world”,则会返回0
。
stdlib.h
还定义了一些更强功能的浮点数转换函数。
- strtof():字符串转成 float 类型。
- strtod():字符串转成 double 类型。
- strtold():字符串转成 long double 类型。
它们的原型如下:
float strtof(
const char* restrict nptr,
char** restrict endptr
);
double strtod(
const char* restrict nptr,
char** restrict endptr
);
long double strtold(
const char* restrict nptr,
char** restrict endptr
);
它们都接受两个参数,第一个参数是需要转换的字符串,第二个参数是一个指针,指向原始字符串里面无法转换的部分。
nptr
:待转换的字符串(起首的空白字符会被忽略)。endprt
:一个指针,指向不能转换部分的第一个字符。如果字符串可以完全转成数值,该指针指向字符串末尾的终止符\0
。这个参数如果设为 NULL,就表示不需要处理字符串剩余部分。
它们的返回值是已经转换后的数值。如果字符串无法转换,则返回0
。
如果转换结果发生溢出,errno 会被设置为 ERANGE。如果值太大(无论是正数还是负数),函数返回HUGE_VAL
;如果值太小,函数返回零:
char *inp = " 123.4567abdc";
char *badchar;
double val = strtod(inp, &badchar);
printf("%f\n", val); // 123.456700
printf("%s\n", badchar); // abdc
字符串可以完全转换的情况下,第二个参数指向\0
,因此可以用下面的写法判断是否完全转换:
if (*endptr == '\0') {
// 完全转换
} else {
// 存在无法转换的字符
}
如果不关心没有转换的部分,则可以将 endptr
设置为 NULL
。
这些函数还可以将字符串转换为特殊值 Infinity
和 NaN
。如果字符串包含 INF
或 INFINITY
(大写或小写皆可),则将转换为 Infinity
;如果字符串包含 NAN
,则将返回 NaN
。
str 系列函数也有整数转换的对应函数。
- strtol():字符串转成 long int 类型。
- strtoll():字符串转成 long long int 类型。
- strtoul():字符串转成 unsigned long int 类型。
- strtoull():字符串转成 unsigned long long int 类型。
它们的原型如下:
long int strtol(
const char* restrict nptr,
char** restrict endptr,
int base
);
long long int strtoll(
const char* restrict nptr,
char** restrict endptr,
int base
);
unsigned long int strtoul(
const char* restrict nptr,
char** restrict endptr,
int base
);
unsigned long long int strtoull(
const char* restrict nptr,
char** restrict endptr, int base
);
它们接受三个参数。
(1)nPtr
:待转换的字符串(起首的空白字符会被忽略)。
(2)endPrt
:一个指针,指向不能转换部分的第一个字符。如果字符串可以完全转成数值,该指针指向字符串末尾的终止符\0
。这个参数如果设为 NULL,就表示不需要处理字符串剩余部分。
(3)base
:待转换整数的进制。这个值应该是2
到36
之间的整数,代表相应的进制,如果是特殊值0
,表示让函数根据数值的前缀,自己确定进制,即如果数字有前缀0
,则为八进制,如果数字有前缀0x
或0X
,则为十六进制。
它们的返回值是转换后的数值,如果转换不成功,返回0
,下面是转换十进制整数的例子:
char* s = "3490";
unsigned long int x = strtoul(u, NULL, 10);
printf("%lu\n", x); // 3490
下面是转换十六进制整数的例子:
char* end;
long value = strtol("0xff", &end, 16);
printf("%ld\n", value); // 255
printf("%s\n", end); // 无内容
value = strtol("0xffxx", &end, 16);
printf("%ld\n", value); // 255
printf("%s\n", end); // xx
上面示例中,strtol()
可以指定字符串包含的是16进制整数,不能转换的部分,可以使用指针end
进行访问。
下面是转换二进制整数的例子:
char* s = "101010";
unsigned long int x = strtoul(s, NULL, 2);
printf("%lu\n", x); // 42
下面是让函数自行判断整数进制的例子:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
const char* string = "-1234567abc";
char* remainderPtr;
long x = strtol(string, &remainderPtr, 0);
printf("%s\"%s\"\n%s%ld\n%s\"%s\"\n",
"The original string is ",
string,
"The converted value is ",
x,
"The remainder of the original string is ",
remainderPtr
);
}
上面代码的输出结果如下:
The original string is "-1234567abc"
The converted value is -1234567
The remainder of the original string is "abc"
如果被转换的值太大,strtol()
函数在errno
中存储ERANGE
这个值,并返回LONG_MIN
(原值为负数)或LONG_MAX
(原值为正数),strtoul()
则返回ULONG_MAX
。
rand()
函数用来生成 0~RAND_MAX 之间的随机整数。RAND_MAX
是一个定义在stdlib.h
里面的宏,通常等于 INT_MAX
:
// 原型
int rand(void);
// 示例
int x = rand();
如果希望获得整数 N 到 M 之间的随机数(包括 N 和 M 两个端点值),可以使用下面的写法:
int x = rand() % (M - N + 1) + N;
比如,1 到 6 之间的随机数写法如下:
int x = rand() % 6 + 1;
获得浮点数的随机值,可以使用下面的写法:
// 0 到 0.999999 之间的随机数
printf("0 to 0.99999: %f\n", rand() / ((float)RAND_MAX + 1));
// n 到 m 之间的随机数:
// n + m * (rand() / (float)RAND_MAX)
printf("10.5 to 15.7: %f\n", 10.5 + 5.2 * rand() / (float)RAND_MAX);
上面示例中,由于rand()
和RAND_MAX
都是 int 类型,要用显示的类型转换转为浮点数。
rand()
是伪随机数函数,为了增加随机性,必须在调用它之前,使用srand()
函数重置一下种子值。
srand()
函数接受一个无符号整数(unsigned int)作为种子值,没有返回值:
void srand(unsigned int seed);
通常使用time(NULL)
函数返回当前距离时间纪元的秒数,作为srand()
的参数:
#include <time.h>
srand((unsigned int) time(NULL));
上面代码中,time()
的原型定义在头文件time.h
里面,返回值的类型是类型别名time_t
,具体的类型与系统有关,所以要强制转换一下类型。
time()
的参数是一个指针,指向一个具体的 time_t 类型的时间值,这里传入空指针NULL
作为参数,由于 NULL 一般是0
,所以也可以写成time(0)
。
abort()
用于不正常地终止一个正在执行的程序。使用这个函数的目的,主要是它会触发 SIGABRT 信号,开发者可以在程序中为这个信号设置一个处理函数:
void abort(void);
该函数没有参数。
exit
,quick_exit
,_Exit
这三个函数都用来退出当前正在执行的程序:
void exit(int status);
void quick_exit(int status);
void _Exit(int status);
它们都接受一个整数,表示程序的退出状态,0
是正常退出,非零值表示发生错误,可以使用宏EXIT_SUCCESS
和EXIT_FAILURE
当作参数,它们本身没有返回值。
它们的区别是,退出时所做的清理工作不同,exit()
是正常退出,系统会做完整的清理,比如更新所有文件流,并且删除临时文件。
quick_exit()
是快速退出,系统的清理工作稍微少一点,而_Exit()
是立即退出,不做任何清理工作
用法如下:
exit(EXIT_SUCCESS);
quick_exit(EXIT_FAILURE);
_Exit(2);
atexit()
用来登记当前程序退出时(调用exit()
或main()
正常退出),所要执行的其他函数。
at_quick_exit()
则是登记使用quick_exit()
方法退出当前程序时,所要执行的其他函数。
exit()
只能触发atexit()
登记的函数,quick_exit()
只能触发at_quick_exit()
登记的函数。
int atexit(void (*func)(void));
int at_quick_exit(void (*func)(void));
它们的参数是要执行的函数地址,即函数名。它们的返回值都是调用成功时返回0
,调用失败时返回非零值。
用法如下:
void sign_off(void);
void too_bad(void);
int main(void) {
int n;
atexit(sign_off); /* 注册 sign_off()函数 */
puts("Enter an integer:");
if (scanf("%d", &n) != 1) {
puts("That's no integer!");
atexit(too_bad); /* 注册 too_bad()函数 */
exit(EXIT_FAILURE);
}
printf("%d is %s.\n", n, (n % 2 == 0) ? "even" : "odd");
return 0;
}
void sign_off(void) {
puts("sign_off");
}
void too_bad(void) {
puts("too bad");
}
上面示例中,用户输入失败时,会调用sign_off()
和too_bad()
函数;但是输入成功时只会调用sign_off()
,因为只有输入失败时,才会进入if
语句登记too_bad()
。
另外,如果有多条atexit()
语句,函数退出时最先调用的,是最后一个登记的函数。
atexit()
登记的函数(如上例的sign_off
和too_bad
)应该不带任何参数且返回类型为void
。
通常,这些函数会执行一些清理任务,例如删除临时文件或重置环境变量。
at_quick_exit()
也是同样的规则,下面是一个例子:
void exit_handler_1(void) {
printf("1\n");
}
void exit_handler_2(void) {
printf("2\n");
}
int main(void) {
at_quick_exit(exit_handler_1);
at_quick_exit(exit_handler_2);
quick_exit(0);
}
执行上面的示例,命令行会先输出2,再输出1。
getenv()
用于获取环境变量的值。环境变量是操作系统提供的程序之外的一些环境参数:
char* getenv(const char* name);
它的参数是一个字符串,表示环境变量名。返回值也是一个字符串,表示环境变量的值。如果指定的环境变量不存在,则返回 NULL。
下面是输出环境变量$PATH
的值的例子:
printf("PATH is %s\n", getenv("PATH"));
system()
函数用于执行外部程序,它会把它的参数字符串传递给操作系统,让操作系统的命令处理器来执行:
void system( char const * command );
这个函数的返回值因编译器而异。但是标准规定,如果 NULL 作为参数,表示询问操作系统,是否有可用的命令处理器,如果有的话,返回一个非零值,否则返回零。
下面是执行ls
命令的例子:
system("ls -l");
stdlib.h 提供了一些内存操作函数,下面几个函数详见C语言内存管理一章,其余在本节介绍。
- malloc():分配内存区域
- calloc():分配内存区域。
- realloc():调节内存区域大小。
- free():释放内存区域。
很多系统有内存对齐的要求,即内存块的大小必须是某个值(比如64字节)的倍数,这样有利于提高处理速度,aligned_alloc()
就用于分配满足内存对齐要求的内存块,它的原型如下:
void* aligned_alloc(size_t alignment, size_t size);
它接受两个参数:
alignment
:整数,表示内存对齐的单位大小,一般是2的整数次幂(2、4、8、16……)。size
:整数,表示内存块的大小。
分配成功时,它返回一个无类型指针,指向新分配的内存块。分配失败时,返回 NULL
:
char* p = aligned_alloc(64, 256);
上面示例中,aligned_alloc()
分配的内存块,单位大小是64字节,要分配的字节数是256字节。
qsort()
用来快速排序一个数组,它对数组成员的类型没有要求,任何类型数组都可以用这个函数排序:
void qsort(
void *base,
size_t nmemb,
size_t size,
int (*compar)(const void *, const void *)
);
该函数接受四个参数:
base
:指向要排序的数组开始位置的指针。nmemb
:数组成员的数量。size
:数组每个成员占用的字节长度。compar
:一个函数指针,指向一个比较两个成员的函数。
比较函数compar
将指向数组两个成员的指针作为参数,并比较两个成员。
如果第一个参数小于第二个参数,该函数应该返回一个负值;如果两个函数相等,返回0
;如果第一个参数大于第二个参数,应该返回一个正数。
下面是一个用法示例:
#include <stdio.h>
#include <stdlib.h>
int compar(const void* elem0, const void* elem1) {
const int* x = elem0;
const int* y = elem1;
return *x - *y;
}
int main(void) {
int a[9] = {14, 2, 3, 17, 10, 8, 6, 1, 13};
qsort(a, 9, sizeof(int), compar);
for (int i = 0; i < 9; i++)
printf("%d ", a[i]);
putchar('\n');
}
执行上面示例,会输出排序好的数组“1 2 3 6 8 10 13 14 17”。
bsearch()
使用二分法搜索,在数组中搜索一个值。它对数组成员的类型没有要求,任何类型数组都可以用这个函数搜索值。
注意该方法只对已经排序好的数组有效:
void *bsearch(
const void* key,
const void* base,
size_t nmemb,
size_t size,
int (*compar)(const void *, const void *)
);
这个函数接受5个参数。
key
:指向要查找的值的指针。base
:指向数组开始位置的指针,数组必须已经排序。nmemb
:数组成员的数量。size
:数组每个成员占用的字节长度。compar
:指向一个将待查找值与其他值进行比较的函数的指针。
比较函数compar
将待查找的值作为第一个参数,将要比较的值作为第二个参数。
如果第一个参数小于第二个参数,该函数应该返回一个负值;如果两个参数相等,返回0
;如果第一个参数大于第二个参数,返回一个正值。
如果找到待查找的值,bsearch()
返回指向该值的指针,如果找不到,返回 NULL。
下面是一个用法示例:
#include <stdio.h>
#include <stdlib.h>
int compar(const void *key, const void *value) {
const int* k = key;
const int* v = value;
return *k - *v;
}
int main(void) {
int a[9] = {2, 6, 9, 12, 13, 18, 20, 32, 47};
int* r;
int key;
key = 12; // 包括在数组中
r = bsearch(&key, a, 9, sizeof(int), compar);
printf("Found %d\n", *r);
key = 30; // 不包括在数组中
r = bsearch(&key, a, 9, sizeof(int), compar);
if (r == NULL)
printf("Didn't find 30\n");
return 0;
}
执行上面的示例,会输出下面的结果:
Found 12
Didn't find 30
stdlib.h 提供了下面的函数,用来操作多字节字符:
- mblen():多字节字符的字节长度。
- mbtowc():将多字节字符转换为宽字符。
- wctomb():将宽字符转换为多字节字符。
- mbstowcs():将多字节字符串转换为宽字符串。
- wcstombs():将宽字符串转换为多字节字符串。
13.stdio
stdio.h
是 C 语言的标准 I/O 库,用于读取和写入文件,也用于控制台的输入和输出。
以下函数用于控制台的输入和输出。
- printf():输出到控制台,详见C语言基本语法。
- scanf():从控制台读取输入,详见C语言IO函数。
- getchar():从控制台读取一个字符,详见C语言IO函数。
- putchar():向控制台写入一个字符,详见C语言IO函数。
- gets():从控制台读取整行输入(已废除),C语言IO函数。
- puts():向控制台写入一个字符串,C语言IO函数。
以下函数用于文件操作,详见C语言文件操作一章。
- fopen():打开文件。
- fclose():关闭文件。
- freopen():打开一个新文件,关联一个已经打开的文件指针。
- fprintf():输出到文件。
- fscanf():从文件读取数据。
- getc():从文件读取一个字符。
- fgetc():从文件读取一个字符。
- putc():向文件写入一个字符。
- fputc():向文件写入一个字符。
- fgets():从文件读取整行。
- fputs():向文件写入字符串。
- fread():从文件读取二进制数据。
- fwrite():向文件写入二进制数据。
- fseek():将文件内部指针移到指定位置。
- ftell():获取文件内部指针的当前位置。
- rewind():将文件内部指针重置到文件开始处。
- fgetpos():获取文件内部指针的当前位置。
- fsetpos():设置文件内部指针的当前位置。
- feof():判断文件内部指针是否指向文件结尾。
- ferror():返回文件错误指示器的状态。
- clearerr():重置文件错误指示器。
- remove():删除文件。
- rename():文件改名,以及移动文件。
以下函数用于操作字符串,详见C语言字符串
- sscanf():从字符串读取数据,详见C语言IO函数。
- sprintf():输出到字符串。
- snprintf():输出到字符串的更安全版本,指定了输出字符串的数量。
tmpfile()
函数创建一个临时文件,该文件只在程序运行期间存在,除非手动关闭它。它的原型如下:
FILE* tmpfile(void);
tmpfile()
返回一个文件指针,可以用于访问该函数创建的临时文件。如果创建失败,返回一个空指针 NULL:
FILE* tempptr;
tempptr = tmpfile();
调用close()
方法关闭临时文件后,该文件将被自动删除。
tmpfile()
有两个缺点。一是无法知道临时文件的文件名,二是无法让该文件成为永久文件。
tmpname()
函数为临时文件生成一个名字,确保不会与其他文件重名,它的原型如下:
char* tmpname(char* s);
它的参数是一个字符串变量,tmpnam()
会把临时文件的文件名复制到这个变量里面,并返回指向该字符串变量的指针。如果生成文件名失败,tmpnam()
返回空指针 NULL。
char filename[L_tmpnam];
if (tmpnam(filename) != NULL)
// 输出诸如 /tmp/filew9PMuZ 的文件名
printf("%s\n", filename);
else
printf("Something wrong!\n");
上面示例中,L_tmpnam
是stdio.h
定义的一个宏,指定了临时文件的文件名长度。
tmpname()
的参数也可以是一个空指针 NULL,同样返回指向文件名字符串的指针:
char* filename;
filename = tmpnam(NULL);
上面示例中,变量filename
就是tmpnam()
生成的文件名。
该函数只是生成一个文件名,稍后可以使用fopen()
打开该文件并使用它。
fflush()
用于清空缓存区。它接受一个文件指针作为参数,将缓存区内容写入该文件。
fflush(fp);
如果不需要保存缓存区内容,则可以传入空指针 NULL。
fflush(NULL);
如果清空成功,fflush()
返回0,否则返回 EOF。
注意,fflush()
一般只用来清空输出缓存区(比如写文件)。如果使用它来清空输入缓存区(比如读文件),属于未定义行为。
fflush()
的一个用途是不等回车键,就强迫输出缓存区。大多数系统都是行缓存,这意味着只有遇到回车键(或者缓存区满了,或者文件读到结尾),缓存区的内容才会输出,fflush()
可以不等回车键,立即输出。
for (int i = 9; i >= 0; i--) {
printf("\r%d", i);
fflush(stdout);
sleep(1);
}
上面示例是一个倒计时效果,\r
是回车键,表示每轮循环都会回到当前行的行首,等于删除上一轮循环的输出。
fflush(stdout)
表示立即将缓存输出到显示器,这一行是必需的,否则由于上一行的输出没有回车键,不会触发缓存输出,屏幕上不会显示任何内容,只会等到程序运行结束再一次性输出。
setvbuf()
函数用于定义某个字节流应该如何缓存。它可以接受四个参数。
int setvbuf(FILE* stream, char* buffer, int mode, size_t size)
第一个参数stream
是文件流。
第二个参数buffer
是缓存区的地址。
第三个参数mode
指定缓存的行为模式,它是下面三个宏之一,这些宏都定义在stdio.h
。
_IOFBF
:满缓存。当缓存为空时,才从流读入数据;当缓存满了,才向流写入数据。一般情况下,这是默认设置。_IOLBF
:行缓存。每次从流读入一行数据,或向流写入一行数据,即以行为单位读写缓存。_IONBF
:无缓存。不使用缓存区,直接读写设备。
第四个参数size
指定缓存区的大小。较大的缓存区提供更好的性能,而较小的缓存区可以节省空间。stdio.h
提供了一个宏BUFSIZ
,表示系统默认的缓存区大小。
它的意义在于,使得用户可以在打开一个文件之前,定义自己的文件缓冲区,而不必使用fopen()
函数打开文件时设定的默认缓冲区。
char buffer[N];
setvbuf(stream, buffer, _IOFBF, N);
上面示例设置文件流stream
的缓存区从地址buffer
开始,大小为N
,模式为_IOFBF
。
setvbuf()
的第二个参数可以为空指针 NULL。这样的话,setvbuf()
会自己创建一个缓存区。
注意,setvbuf()
的调用必须在对文件流执行任何操作之前。
如果调用成功,setvbuf()
的返回值为0
,否则返回非零值。
下面的例子是将缓存区调整为行缓存:
FILE *fp;
char lineBuf[1024];
fp = fopen("somefile.txt", "r");
setvbuf(fp, lineBuf, _IOLBF, 1024);
setbuf()
是setvbuf()
的早期版本,可以视为后者的简化版本,也用来定义某个字节流的缓存区。
void setbuf(FILE* stream, char* buffer);
它的第一个参数stream
是文件流,第二个参数buffer
是缓存区的地址。
它总是可以改写成setvbuf()
:
char buffer[BUFSIZ];
setbuf(stream, buffer);
// 等同于
setvbuf(stream, buffer, _IOFBF, BUFSIZ);
上面示例中,BUFSIZ
是stdio.h
定义的宏,表示系统默认的缓存区大小。
setbuf()
函数没有返回值。
setbuf()
的第二个参数如果设置为 NULL,表示不进行缓存:
setbuf(stdout, NULL);
// 等同于
setvbuf(stdout, NULL, _IONBF, 0);
ungetc()
将从缓存里面读取的上一个字符,重新放回缓存,下一个读取缓存的操作会从这个字符开始。有些操作需要了解下一个字符是什么,再决定应该怎么处理,这时这个函数就很有用。
它的原型如下:
int ungetc(int c, FILE *stream);
它的第一个参数是一个字符变量,第二个参数是一个打开的文件流,它的返回值是放回缓存的那个字符,操作失败时,返回 EOF。
int ch = fgetc(fp);
if (isdigit(ch)) {
ch = fgetc(fp);
}
ungetc(ch, fp);
上面示例中,如果读取的字符不是数字,就将其放回缓存。
perror()
用于在 stderr 的错误信息之前,添加一个自定义字符串:
void perror(const char *s);
该函数的参数就是在报错信息前添加的字符串,没有返回值:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <errno.h>
int main(void) {
int x = -1;
errno = 0;
float y = sqrt(x);
if (errno != 0) {
perror("sqrt error");
exit(EXIT_FAILURE);
}
}
上面示例中,求-1
的平方根,导致报错。头文件errno.h
提供宏errno
,只要上一步操作出错,这个宏就会设置成非零值。perror()
用来在报错信息前,加上sqrt error
的自定义字符串。
执行上面的程序,就会得到下面的报错信息。
gcc test.c -lm
./a.out # sqrt error: Numerical argument out of domain
以下是一些可变参数的操作函数。
(1)输出函数
下面是printf()
的变体函数,用于按照给定格式,输出函数的可变参数列表(va_list)。
- vprintf():按照给定格式,输出到控制台,默认是显示器。
- vfprintf():按照给定格式,输出到文件。
- vsprintf():按照给定格式,输出到字符串。
- vsnprintf():按照给定格式,输出到字符串的安全版本。
它们的原型如下,基本与对应的printf()
系列函数一致,除了最后一个参数是可变参数对象。
#include <stdio.h>
#include <stdarg.h>
int vprintf(
const char * restrict format,
va_list arg
);
int vfprintf(
FILE * restrict stream,
const char * restrict format,
va_list arg
);
int vsprintf(
char * restrict s,
const char * restrict format,
va_list arg
);
int vsnprintf(
char * restrict s,
size_t n,
const char * restrict format,
va_list arg
);
它们的返回值都为输出的字符数,如果出错,返回负值。
vsprintf()
和vsnprintf()
的第一个参数可以为 NULL,用来查看多少个字符会被写入。
下面是一个例子。
int logger(char *format, ...) {
va_list va;
va_start(va, format);
int result = vprintf(format, va);
va_end(va);
printf("\n");
return result;
}
// 输出 x = 12 and y = 3.20
logger("x = %d and y = %.2f", x, y);
(2)输入函数
下面是scanf()
的变体函数,用于按照给定格式,输入可变参数列表 (va_list)。
- vscanf():按照给定格式,从控制台读取(默认为键盘)。
- vfscanf():按照给定格式,从文件读取。
- vsscanf():按照给定格式,从字符串读取。
它们的原型如下,跟对应的scanf()
函数基本一致,除了最后一个参数是可变参数对象。
#include <stdio.h>
#include <stdarg.h>
int vscanf(
const char * restrict format,
va_list arg
);
int vfscanf(
FILE * restrict stream,
const char * restrict format,
va_list arg
);
int vsscanf(
const char * restrict s,
const char * restrict format,
va_list arg
);
它们返回成功读取的项数,遇到文件结尾或错误,则返回 EOF。
下面是一个例子。
int error_check_scanf(int expected_count, char *format, ...) {
va_list va;
va_start(va, format);
int count = vscanf(format, va);
va_end(va);
assert(count == expected_count);
return count;
}
error_check_scanf(3, "%d, %d/%f", &a, &b, &c);
原文地址:https://blog.csdn.net/weixin_50964512/article/details/145227262
免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!