格式化I/O
格式化输出
#include <stdio.h>
int printf(const char* restrict format, ...);
int fprintf(FILE *restrict fp, const char *restrict format, ...);
int dprintf(int fd, const char *restrict format, ...);
这三个函数返回值: 若成功,返回输出字符数;若输出出错,返回负值。
int sprintf(char *restrict buf, const char *restrict format, ...);
返回值: 若成功,返回存入数组的字符数;若编码出错,返回负值
int snprintf(char *restrict buf, size_t n, const char *restrict format, ...);
返回值:若缓冲区足够大,返回将要存入数组的字符数;若编码出错,返回负值。
printf
函数将格式化数据写入标准输出;
fprintf
函数写至指定的流;
dprintf
写至指定的文件描述符;
sprintf
函数将格式化的字符送入数组buf
;sprintf
在该数组的尾端自动加一个null字节,但该字符不包括在返回值中。
注意: sprintf
函数可能会造成buf
所指向的缓冲区溢出。因此,此函数的调用,程序员必须保证足够大的缓冲区,否则会引起安全问题。为了避免带来安全问题,引入了snprintf
函数。在该函数中,缓冲区长度是一个显式参数,超过缓冲区大小的数据将会被丢弃。与sprintf
函数相同,该返回值不包括结尾的null字节。
格式说明控制其余参数如何编写,以后有如何显示。一个转换说明有四个可选部分,下面将它们都显示于方括号中。
%[flags][fldwidth][precision][lenmodifier]convtype
flags
标志总结如下表:
标志 | 说明 |
---|---|
‘ | (撇号)将整数按千位分组字符 |
- | 在字段内左对齐输出 |
+ | 总是显示带符号转换的正负号 |
空格 | 如果第一个字符不是正负号,则在其前面加上一个空格 |
# | 指定另一种转换形式(例如,对于十六进制格式,加0x前缀) |
0 | 添加前导0(而非空格)进行填充 |
fldwidth
说明最小字段宽度。转换后参数字符数若小于宽度,则多余字符位置用空格填充。字段宽度是一个非负十进制数,或是一个星号(*)
precision
说明整型转换后最少输出数字位数、浮点数转换后小数点后的最少位数、字符串转换后最大字节数。
lenmodifier
说明参数长度,其可能值如下表。
长度修饰符 | 说明 |
---|---|
hh | 将相应的参数按signed或unsigned char类型输出 |
h | 将相应的参数按signed或unsigned short类型输出 |
l | 将相应的参数按signed或unsigned long或宽字符类型输出 |
ll | 将相应的参数按signed或unsigned long long 类型输出 |
j | intmax_t 或 uintmax_t |
z | size_t |
t | ptrdiff_t |
L | long double |
convtype
不是可选的,它控制如何解释参数。各种转换类型字符如下表。
转换类型 | 说明 |
---|---|
d、i | 有符号十进制 |
o | 无符号八进制 |
u | 无符号十进制 |
x、X | 无符号十六进制 |
f、F | 双精度浮点数 |
e、E | 指数格式双精度浮点数 |
g、G | 根据转换后的值解释为f、F、e、E |
a、A | 十六进制指数格式双精度浮点数 |
c | 字符(若长度修饰符为1,为宽字符) |
s | 字符串(若长度修饰符为1,为宽字符) |
p | 指向void的指针 |
n | 到目前为止,此printf调用输出的字符的数目将被写入到指针所指向的带符号整型中 |
% | 一个%字符 |
C | 宽字符(等效于lc) |
S | 宽字符串(等效于ls) |
下列是printf
族的五种变体,但是可变参数表...
替换成了arg
。
#include <stdarg.h>
#include <stdio.h>
int vprintf(const char *restrict format, va_list arg);
int vfprintf(FILE *restrict fp, const char *restrict format, va_list arg);
int vdprintf(int fd, const char *restrict format, va_list arg);
上面三个函数返回值:若成功,返回输出的字符数;若输出出错,返回负值。
int vsprintf(char *restrict buf, const char *restrict format, va_list arg);
函数返回值: 若成功,返回存入数组的字符数;若编码出错,返回负值。
int vsnprintf(char *restrict buf, size_t n, const char *restrict format, va_list arg);
函数返回值:若缓冲区足够大,返回存入数组的字符数;若编码出错,返回负值。
由ISO C提供的可变长度参数表例程(
<stdarg.h>
) 与由较早版本UNIX提供的<varargs.h>
例程是不同的。
格式化输入
执行格式化输入处理有三个scanf
函数。
#include <stdio.h>
int scanf(const char *restrict format, ...);
int fscanf(FILE *restrict fp, const char *restrict format, ...);
int sscanf(const char *restrict buf, const char *restrict format, ...);
三个函数返回值:赋值的输入项数;若输入出错或在任一转换前已达到文件末尾,返回EOF。
格式说明控制如何转换参数,以便对它们赋值。
一个转换说明有3个可选部分,下面将它们都示于方括号中:
%[*][fldwidth][m][lenmodifier] convtype
*
是可选择性,用于抑制转换。
fldwidth
说明最大宽度。
convtype
字段类似于printf
族的转换类型字段。
scanf
函数族同样支持另外一种转换说明。其转换说明中的转换类型如下表:
转换类型 | 说明 |
---|---|
d | 有符号十进制,基数为10 |
i | 有符号十进制,基数由输入格式决定 |
O | 无符号八进制(输入可选的有符号) |
u | 无符号十进制,基数为10(输入可选的有符号) |
x、X | 无符号十六进制(输入可选的有符号) |
a、A、e、E、f、F、g、G | 浮点数 |
c | 字符(若带长度修饰符1,为宽字符) |
s | 字符串(若带长度修饰符1,为宽字符串) |
[ | 匹配列出的字符序列,以]终止 |
[^ | 匹配除列出字符外的所有字符,以]终止 |
p | 指向void的指针 |
n | 将到目前为止该函数调用读取的字符数写入到指针所指向的无符号整型中 |
% | 一个%符号 |
C | 宽字符(等效于lc) |
S | 宽字符串(等效于ls) |
与printf
族相同,scanf
族也使用由<stdarg.h>
说明的可变长度参数表。
#include <stdio.h>
#include <stdarg.h>
int vscanf(const char *restrict format, va_list arg);
int vfscanf(FILE *restrict fp, const char *restrict format, va_list arg);
int vsscanf(const char *restrict buf, const char *restrict format, va_list arg);
三个函数返回值:指定的输入项目数;若输入出错或在任一转换前文件结束,返回EOF
实现细节
在UNIX中,标准I/O中最终都要调用I/O例程,每个标准I/O流都有一个与之相关的文件描述符,可以调用fileno
函数来获得一个流的文件描述符。
#include <stdio.h>
int fileno(FILE *fp);
函数返回值:与该流相关的文件描述符。
当要调用dup
或是fcntl
等函数时,则需要此函数。
测试示例:
为三个标准流以及一个与普通文件相关联的流打印有关缓冲的状态信息。
#include "../../include/apue.h"
void pr_stdio(const char *, FILE *);
int is_unbuffered(FILE *);
int is_linebuffered(FILE *);
int buffer_size(FILE *);
int main(void)
{
FILE *fp;
fputs("enter any character\n", stdout);
if(getchar() == EOF)
err_sys("getchar error");
fputs("one line to standard error\n", stderr);
pr_stdio("stdin", stdin);
pr_stdio("stdout", stdout);
pr_stdio("stderr", stderr);
if((fp = fopen("/etc/passwd","r")) == NULL)
err_sys("fopen error");
if(getc(fp) == EOF)
err_sys("getc error");
pr_stdio("/etc/passwd", fp);
exit(0);
}
void pr_stdio(const char *name, FILE *fp)
{
printf("stream = %s, ", name);
if(is_unbuffered(fp))
printf("unbuffered");
else if(is_linebuffered(fp))
printf("line buffered");
else
printf("fully buffered");
printf(", buffer size = %d\n", buffer_size(fp));
}
/*
* The following nonportable.
* */
#if defined(_IO_UNBUFFERED)
int is_unbuffered(FILE *fp)
{
return (fp->_flags & _IO_UNBUFFERED);
}
int is_linebuffered(FILE *fp)
{
return (fp->_flags & _IO_LINE_BUF);
}
int buffer_size(FILE *fp)
{
return (fp->_IO_buf_end - fp->_IO_buf_base);
}
#elif defined(__SNBF)
int is_unbuffered(FILE *fp)
{
return (fp->_flags & __SNBF);
}
int is_linebuffered(FILE *fp)
{
return (fp->_flags & __SLBF);
}
int buffer_size(FILE *fp)
{
return (fp->_bf._size);
}
#elif defined(_IONBF)
#ifdef _LP64
#define _flag __pad[4]
#define _ptr __pad[1]
#define _base __pad[2]
#endif
int is_unbuffered(FILE *fp)
{
return (fp->_flag & _IONBF);
}
int is_linebuffered(FILE *fp)
{
return (fp->_flag & _IOLBF);
}
int buffer_size(FILE *fp)
{
#ifdef _LP64
return (fp->_base - fp->_ptr);
#else
return (BUFSIZE);
#endif
}
#else
#error unknown stdio implementation!
#endif
结果如下:
临时文件
ISO C标准I/O库提供了两个函数以帮助创建临时文件。
#include <stdio.h>
char *tmpnam(char *ptr);
返回值:指向唯一路径名的指针
FILE *tmpfile(void);
返回值:若成功,返回文件指针;若出错,返回NULL。
tmpnam
函数产生一个与现有文件名不同的一个有效路径名字符串。
tmpfile
创建一个临时二进制文件(类型wb+),在关闭该文件或程序结束时将自动删除这种文件。注意,UNIX对二进制文件不进行特殊区分。
测试示例:
#include "../../include/apue.h"
int main(void)
{
char name[L_tmpnam], line[MAXLINE];
FILE *fp;
printf("%s\n", tmpnam(NULL));
tmpnam(name);
printf("%s\n", name);
if((fp = tmpfile()) == NULL)
err_sys("tmpfile error");
fputs("one line of output\n", fp);
rewind(fp);
if(fgets(line, sizeof(line), fp) == NULL)
err_sys("fgets error");
fputs(line, stdout);
return 0;
}
结果如下:
为处理临时文件定义了另外两个函数,即mkdtemp
和mkstemp
。
#include <stdlib.h>
char *mkdtemp(char *template);
返回值:若成功,返回指向目录名的指针;若出错,返回NULL。
int mkstemp(char *template);
返回值:若成功,返回文件描述符;若出错,返回 -1。
mkdtemp
函数创建了一个目录,该目录有一个唯一的名字;
mkstemp
函数创建了一个文件,该文件有一个唯一的名字。
注意: tempnam
和tmpnam
函数创建临时文件时,在返回唯一的路径名和用该名字创建文件之间存在一个时间窗口,在这个时间窗口中,另一进程同样可以用相同的名字进行创建文件操作。因此应该使用tmpfile
和mkstemp
函数,因为它们不存在这个问题。
测试示例:
#include "../../include/apue.h"
#include <errno.h>
void make_temp(char *template);
int main(void)
{
char good_template[] = "/tmp/dirXXXXXX";
char *bad_template = "/tmp/dirXXXXXX";
printf("trying to create first temp file...\n");
make_temp(good_template);
printf("trying to create second temp file...\n");
make_temp(bad_template);
return 0;
}
void make_temp(char *template)
{
int fd;
struct stat sbuf;
if((fd = mkstemp(template)) < 0)
err_sys("can't create temp file");
printf("temp name = %s\n", template);
close(fd);
if(stat(template, &sbuf) < 0) /* get all information of file to judge whether file exists. */
{
if(errno == ENOENT)
printf("file doesn't exist\n");
else
err_sys("stat failed");
}else{
printf("file exists\n");
unlink(template);
}
}
结果如下:
两个创建文件的方式,对于第一种模板而言,因为使用了数组,名字是在栈上分配的。但第二种情况使用的是指针,在这种情况下,只有指针自身驻留在栈上。编译器将字符串放在可执行文件的只读段,当mkstemp
函数试图修改字符串时,出现了段错误(segment fault)。