格式化I/O函数分为输出函数和输入函数两大类,输入和输出格式是编程应该掌握的细节,同时也是编程时经常需要使用到的知识。
1.1 格式化输出函数
1.1.1 输出函数原型
格式化I/O输出函数原型如下:
#include <stdio.h>
int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);
#include <stdarg.h>
int vprintf(const char *format, va_list ap);
int vfprintf(FILE *stream, const char *format, va_list ap);
int vsprintf(char *str, const char *format, va_list ap);
int vsnprintf(char *str, size_t size, const char *format, va_list ap);
具体说明如下:
返回值:上述函数成功返回格式化输出的字节数(不包括字符串的结尾'\0'),出错返回一个负值,错误原因存在于error中。
printf函数会把格式化串打印到标准输出。
fprintf函数会把格式化串输出到指定的文件stream中。
sprintf函数会把格式化串输出到缓冲区str中,并在末尾加'\0',当str空间不够时,会造成缓冲区溢出。
snprintf函数会把格式化串输出到缓冲区str中,并在末尾加'\0',但格式化串长度超过size-1字节时,对格式化串按长度size-1进行截断,因此snprintf函数比sprintf函数使用起来更加安全。
上面列出的后四个函数在前四个函数名的前面多了个v,表示可变参数,不是以...的形式传进来,而是以va_list类型传进来。
1.1.2 输出函数格式说明
1. format格式说明
%(flags)(width)(.prec)type
以上圆括号括起来的参数为选择性参数,而%与type则是必要的。下面是format各参数的详细说明。
type选项说明 |
选项 | 说明 |
整数 | |
d | 整数的参数会被转成一有符号的十进制数字 |
u | 整数的参数会被转成一无符号的十进制数字 |
o | 整数的参数会被转成一无符号的八进制数字 |
x | 整数的参数会被转成一无符号的十六进制数字,并以小写abcdef表示 |
X | 整数的参数会被转成一无符号的十六进制数字,并以大写ABCDEF表示浮点型数 |
f | double 型的参数会被转成十进制数字,并取到小数点以下六位,四舍五入 |
e | double型的参数以指数形式打印,有一个数字会在小数点前,六位数字在小数点后,而在指数部分会以小写的e来表示 |
E | 与%e作用相同,唯一区别是指数部分将以大写的E 来表示 |
g | double 型的参数会自动选择以%f 或%e 的格式来打印,其标准是根据欲打印的数值及所设置的有效位数来决定 |
G | 与%g 作用相同,唯一区别在以指数形态打印时会选择%E 格式 |
字符串 | |
c | 整型数的参数会被转成unsigned char型打印出 |
s | 指向字符串的参数会被逐字输出,直到出现NULL字符为止 |
p | 如果是参数是“void *”型指针则使用十六进制格式显示 |
pre选项说明 |
① 正整数的最小位数 |
② 在浮点型数中代表小数位数 |
③ 在%g格式代表有效位数的最大值 |
④ 在%s格式代表字符串的最大长度 |
⑤ 若为×符号则代表下个参数值为最大长度 |
width选项说明 |
width为参数的最小长度,若此栏并非数值,而是*符号,则表示以下一个参数当做参数长度 |
flag选项说明 |
选项 | 说明 |
# | 此旗标会根据其后转换字符的不同而有不同含义。当在类型为o之前(如%#o),则会在打印八进制数值前多印一个o。而在类型为x 之前(%#x)则会在打印十六进制数前多印’0x’,在型态为e、E、f、g或G 之前则会强迫数值打印小数点。在类型为g 或G之前时则同时保留小数点及小数位数末尾的零 |
u | 一般在打印负数时,printf()会加印一个负号,整数则不加任何负号。此旗标会使得在打印正数前多一个正号(+) |
o | 整数的参数会被转成一无符号的八进制数字 |
0 | 当有指定参数时,无数字的参数将补上0。默认是关闭此标记,所以一般会打印出空白字符 |
- | 格式化后的内容居左,右边可以留空格 |
2. 输出字符串中字符类型说明
参数format字符串可包含下列3种字符类型:
① 一般文本,伴随直接输出。
② ASCII控制字符,如\t、\n等。
③ 格式转换字符。
格式转换为一个百分比符号(%)及其后的格式字符所组成。一般而言,每个%符号在其后都必需有一参数与之相呼应(只有当%%转换字符出现时会直接输出%字符)。
3. 常用格式化输出说明
格式化输出常用格式为:%(+|-|0)m.n。
对格式化输出常用格式解释如下:
① m:输出数据域宽,数据长度<m,左补空格,否则按实际输出。
② .n:对实数,指定小数点后位数(四舍五入);对字符串,指定字符串实际输出位数,超过指定长度则进行截断。
③ -:输出数据在域内左对齐(默认右对齐)。
④ +:指定在有符号数的正数前显示正号(+)。
⑤ 0:输出数值时指定左边不使用的空位置自动填0。
⑥ 输出百分号需要用两个%%。
4. 常用格式化输出使用举例
在实际应用编程中,常用的格式化输出有下面6种。
① 数字前补0:sprintf(acStr,"%06dSECCTL ",30 )。
② 左对齐:sprintf( acStr,"%-6.6s","05023")。
③ 右对齐:sprintf( acStr,"%6.6s","05023")。
④ 两位小数点输入:sprintf(acStr,"%.2f",19.79)。
⑤ 增加输出百分号:sprintf(acStr,"%%.2f",19.79)。
⑥ 指定最长输出位数:sprintf(acStr,"%6.6s","testTEST")。
5. 输出函数应用举例
printf.c代码如下:
#include <stdio.h>
int main()
{
int i = 150;
int j = -100;
double k = 3.14159;
printf("%d %f %x\n",j,k,i);
printf("%010d|%-6d|%4d|%*d\n",i,i,i,2,i); /*参数2 会代入格式*中,而与%2d同意义*/
return 0 ;
}
编译 gcc printf.c –o printf。
执行 ./printf,执行结果如下:
-100 3.141590 96
0000000150|150 | 150|150
变参使用范例,了解即可。
vprintf.c代码如下:
#include <stdio.h>
#include <stdarg.h>
int my_printf( const char *format,...)
{
va_list ap;
int retval;
va_start(ap,format);
printf("my_printf( ):");
retval = vprintf(format,ap);
va_end(ap);
return retval;
}
int main()
{
int i = 150,j = -100;
double k = 3.14159;
my_printf("%d %f %x\n",j,k,i);
my_printf("%2d %*d\n",i,2,i);
return 0 ;
}
编译 gcc vprintf.c –o vprintf。
执行 ./vprintf,执行结果如下:
my_printf( ):-100 3.141590 96
my_printf( ):150 150
1.2 格式化输入函数
1.2.1 输入函数原型
格式化I/O输入函数原型如下:
#include <stdio.h>
int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *str, const char *format, ...);
具体说明如下:
返回值:上述函数返回成功匹配和赋值的参数个数,成功匹配的参数可能少于所提供的赋值参数,返回0表示一个都不匹配,出错返回-1并设置errno。
scanf函数会将输入的数据根据参数format字符串来转换并格式化数据。
fscanf函数从指定的文件stream中读字符。
sscanf函数从指定的字符串str中读字符。
1.2.2 输入函数格式说明
1. format类型
%[*][size][l][h]type
以上圆括号括起来的参数为选择性参数,而%与type则是必要的。具体说明如下:
*代表该对应的参数数据忽略不保存。
size 为允许参数输入的数据长度。
l 输入的数据数值以long int 或double型保存。
h 输入的数据数值以short int 型保存。
type选项说明 |
选项 | 说明 |
d | 输入的数据会被转换成一有符号的十进制数字(int) |
i | 输入的数据会被转换成一有符号的十进制数字,若输入数据以“0x”或“0X”开头代表转换十六进制数字,若以“0”开头则转换八进制数字,其他情况代表十进制 |
o | 输入的数据会被转换成一无符号的八进制数字 |
u | 输入的数据会被转换成一无符号的正整数 |
x | 输入的数据为无符号的十六进制数字,转换后存于unsigned int型变量 |
X | 同%x |
f | 输入的数据为有符号的浮点型数,转换后存于float型变量 |
e | 同%f |
E | 同%f |
g | 同%f |
s | 输入数据为以空格字符为终止的字符串 |
c | 输入数据为单一字符 |
[] | 读取数据但只允许括号内的字符。如[a-z] |
[^] | 读取数据但不允许中括号的^符号后的字符出现,如[^0-9] |
2. 输入字符串中字符类型说明
格式化输入字符串中字符类型包括如下3种:
① 空格或Tab,在处理过程中被忽略。
② 普通字符(不包括%),和输入字符中的非空白字符相匹配,输入字符中的空白字符是指空格、Tab、\r、\n、\v、\f。
③ 格式转换是以%开头,以转换字符结尾,中间有若干个可选项。
3. scanf函数特别说明
scanf函数使用格式为scanf("格式控制串", 地址表),输入变量默认间隔是空格,如果用“,”号做间隔,输入时也要输入“,”号。
4. 输入函数应用举例
scanf.c源代码如下:
#include <stdio.h>
int main()
{
int i;
unsigned int j;
char s[5];
scanf("%d %x %5[a-z] %*s %f",&i,&j,s,s);
printf("%d %d %s\n",i,j,s);
return 0 ;
}
编译 gcc scanf.c -o scanf。
执行 ./scanf,执行结果如下:
输入: 10 0x1b aaaaaaaaaa bbbbbbbbbb
10 27 aaaaa
5. 变参实用项目实例
下面是一个打印源代码文件名称、报错位置和信息的实用日志函数,具体说明如下:
程序中syslog函数打印信息长度不限,报错参数个数和信息不限,并打印该进程进程号。
日志位置存放在自定义的目录下,日志文件名称可自定义。
日志文件按天生成,打印函数自动检测日志文件是否存在,如不存在建立相应的日志文件。
变参一般都需使用va_start和va_end函数,编程模式类似,读者可模仿掌握。
syslog.c源代码如下:
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdarg.h>
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
/*------------------------------------------------------------------------
* Function Name : syslog
* Description : 写日志
* Input : sysname -- 系统代号
* : modname -- 功能模块名
* : file_name -- 文件名
* : line_num -- 行号
* : format -- 消息
* Output :
* Return : success:0
* : fail :-1
*------------------------------------------------------------------------*/
int syslog( char *sysname, char *modname,char *file_name, int line_num, char *format, ... )
{
va_list ap ;
struct tm *ts;
time_t now;
char acTimeStr[16] ;
char filename[128] ;
char syscmd[256] ;
FILE *LogFileID ;
memset( filename, 0x00, sizeof( filename ) );
now = time(NULL);
ts = localtime( &now );
sprintf( filename, "%s/log/%s/%s.%02d%02d.log",
getenv("HOME"), sysname, modname, ts->tm_mon+1, ts->tm_mday );
LogFileID=fopen( filename, "a" );
if ( LogFileID == NULL )
{
sprintf( syscmd, "/bin/mkdir -p %s/log/%s/", getenv("HOME"), sysname );
if ( system( syscmd ) != 0 )
{
fprintf(stderr,"cread log folder fail [%s]", syscmd );
return -1;
}
LogFileID=fopen( filename, "a" );
if ( LogFileID == NULL )
{
fprintf(stderr,"open file fail [%s]", filename );
return -1 ;
}
}
sprintf( acTimeStr, "%02d-%02d %02d:%02d:%02d",
ts->tm_mon+1, ts->tm_mday, ts->tm_hour, ts->tm_min, ts->tm_sec);
fprintf( LogFileID, "[%s %d %s:%d] ", acTimeStr, getpid(), file_name, line_num );
va_start(ap, format );
vfprintf( LogFileID, format, ap );
va_end( ap );
fputc( 0x0a, LogFileID );
fflush( LogFileID );
fclose( LogFileID );
return 0 ;
}
int main()
{
syslog( "newSys", "SOCK", __FILE__, __LINE__, "hello world! %s %d", "what who why where when how", 999 );
syslog( "newSys", "SOCK", __FILE__, __LINE__, "every day is new day!");
return 0;
}
编译 gcc syslog.c –o syslog。
执行 ./syslog,在$HOME/log/newSys下产生了SOCK.0116.log文件,文件内容如下:
[01-16 19:51:04 11921 syslog.c:66] hello world! what who why where when how 999
[01-16 19:51:04 11921 syslog.c:67] every day is new day!
摘录自《深入浅出Linux工具与编程》