有许多的库函数可以按我们所希望的方式产生输出,而如果我们有过一些C语言编程的经验,我们就会对于这些格式感到熟悉.这些函数包括prinf以及其他的一些向文件流中写入数据的函数以及scanf和其他的一些函数从文件流中读取数据的函数.
参数format字符串可包含下列三种字符类型:
1. 一般文本, 伴随直接输出.
2. ASCII控制字符, 如/t, /n等.
3. 格式转换字符.
格式转换为一个百分比符号(%)及其后的格式字符所组成. 一般而言, 每个%符号在其后都必需有一printf()的参数与之相呼应(只有当%%转换字符出现时会直接输出%字符), 而欲输出的数据类型必须与其相对应的转换字符类型相同.
printf()格式转换的一般形式如下:
%[flags][width][.prec]type
以中括号括起来的参数为选择性参数, 而%与type则是必要的. 底下先介绍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 *"型指针则使用十六进制格式显示.
prec有几种情况:
1. 正整数的最小位数.
2. 在浮点型数中代表小数位数
3. 在%g格式代表有效位数的最大值.
4. 在%s格式代表字符串的最大长度.
5. 若为×符号则代表下个参数值为最大长度.
width为参数的最小长度, 若此栏并非数值, 而是*符号, 则表示以下一个参数当做参数长度.
flags有下列几种情况:
- 此旗标会将一数值向左对齐.
+ 一般在打印负数时, printf()会加印一个负号, 整数则不加任何负号. 此旗标会使得在打印正数前多一个正号(+).
# 此旗标会根据其后转换字符的不同而有不同含义. 当在类型为o之前(如%#o), 则会在打印八进制数值前多印一个o.而在类型为x之前(%#o)则会在打印十六进制数前多印’0x’, 在型态为e、E、f、g或G之前则会强迫数值打印小数点. 在类型为g或G之前时则同时保留小数点及小数位数末尾的零.
0 当有指定参数时, 无数字的参数将补上0. 默认是关闭此旗标, 所以一般会打印出空白字符.
printf,fprintf,sprintf
printf函数家族可以格式化并输出不同类型的变量参数.在输出流中所代表的每一个函数的工作方式是由format参数来控制的,这个参数包含要打印的普通的字符串和代码,也就是称之为转义字符的部分,这些用来表明要如何和在哪里打印其余的参数.
其语法格式如下:
#include <stdio.h>
int printf(const char *format, ...);
int sprintf(char *s, const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
printf 函数在标准输出上产生他的输出.fprintf函数在一个指定的流上产生他的输出,而sprintf函数会将他的输出与一个结束空字符写入字符串s,而s 是作为参数来进行传递的.这个字符必须足够的大来包含所有的输出.另外还有一些printf函数家族中其他的函数可以用来以不同的方式来处理不同的参数. 我们可以通过查看printf手册页得到更为详细的内容.
普通的字符在传递到输出后并不会发生改变.转义字符会使得printf取回并格式化传递的其余参数.他们能通常是以%字符开头的.如下面的一些例子:
printf(“Some numbers: %d, %d, and %d/n”, 1, 2, 3);
他在标准输出的结果如下:
Some numbers: 1, 2, and 3
如果我们要打印一个%字符,我们必须使用%%,这样就不会与一个转义字符发生混淆了.
下面是一些最常的转义字符:
%d,%i:以十进制数打印一个整数
%o,%x:以八进制,十六进制数打印
%c:打印一个字符
%s:打印一个字符串
%f:打印一个浮点数(单精度数)
%e:以定点数的格式打印一个双精度数
%g:以普通格式打印一个双精度数
在format 字符串传递给printf函数与转义字符相匹配的参数类型和个数是非常重要的.一个可选的尺寸标识可以用来表明整数参数的类型.这个可以是h,例如,% hd用来表明short int,或是可以是l,例如,%ld用来表明long int.一些编译器可以检查printf的这些参数,但是他们并不是绝对可靠的.如果我们正在使用GNU编译器gcc,我们可以使用-Wformat来做 到这一点.
如下面的例子:
char initial = ‘A’;
char *surname = “Matthew”;
double age = 14.5;
printf(“Hello Miss %c %s, aged %g/n”, initial, surname, age);
这个例子的结果如下:
Hello Miss A Matthew, aged 14.5
如果我们使用域标识,我们就可以更多的控制打印的方式.这些扩展了转义字符从而可以控制输出中的空格.一个常用的用法是可以用来为浮点数的打印指定一个十进制数的空间或是为一个字符串指定一个打印的空间.
域标识是在转义字符的%字符后面以数字的方式来指定的.下面的这个表中包含了一些转义字符的例子以及他们的输出结果.
Format Argument | Output |
%10s “Hello” | Hello|
%-10s “Hello” |Hello |
%10d 1234 | 1234|
%-10d 1234 |1234 |
%010d 1234 |0000001234 |
%10.4f 12.34 | 12.3400|
%*s 10,”Hello” | Hello|
所 有的这些例子以10个字符的宽度进行打印.在这里我们要注意就是在域宽度中负数用来表明打印的内容要左对齐.一个变化的区域宽度可以用一个通配符*来指 定.在这样的情况下,下一个参数用来指定宽度.开头的0用来要打印的内容以0开头的.根据POSIX的说明,printf函数并不会截断一个要打印的域, 而是进行扩展来进行填充.所以,如果我们要打印一个比我们所指定的域长的内容,那么这个域会进行增长.
如下面的表格所示:
Format Argument | Output |
%10s “HelloTherePeeps” |HelloTherePeeps|
printf会返回一个整数,用来表明写入的字符个数.这在sprintf函数中并不包括结束字符null.如果出现错误,则会返回一个负数并且会设置errno.
scanf,fscanf,sscanf
scanf函数家族的工作方式也printf组的工作方式相类似,所不同的只是这些函数是从一个流中读取内容或者是在作为参数传递的指针地址处放置变量值.他们以同样的方式使用格式字符串来控制输入转换,而这些转义字符中的许多都是相同的.
其语法格式如下:
#include <stdio.h>
int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *s, const char *format, ...);
在这里很重要的一点就是用来存放由scanf函数所读入值的变量必须是正确的类型而且必须与格式字符串进行精确的匹配.如果不是这样,我们的内存就会泄漏而我们的程序就可能崩溃.这些并不会出现编译错误,如果我们幸运的话,有可能会得到警告信息.
scanf及其相关的函数的格式字符串包含有普通字符和转义字符,而这些与printf相类似.然而普通字符是用来指定必须在输入中出现的字符.
如下面的一个简单的例子:
int num;
scanf(“Hello %d”, &num);
这 个scanf调用只有当在标准输入中的下五个字符与Hello匹配才会成功.然后,如果下一个字符形成一个可以识别的十进制数,这个数就会被读入而他的值 将会赋给变量num.在格式字符串中的空格是用来忽略输入中转义字符之间的任何空格符(空格,Tab,或是新行).这就意味着如果我们指定下面的输入形式 中的任何一个都会成功并会将1234存入变量num中:
Hello 1234
Hello1234
在通常情况下,当转义开始时,空格符也会被忽略掉.这就意味着格式字符串%d将会一直从输入中读入,跳过任何空格以及新行直到发现一个数字充列.如果没有出现所希望的字符,转义就会失败,scanf函数返回.
如果我们不小心这样就会导致问题,如果在我们的程序中读入整数而输入中并没有数字字符,这样就会导致一个死循环.
scanf()格式转换的一般形式如下:
%[*][size][l][h]type
以中括号括起来的参数为选择性参数, 而%与type则是必要的.
* 代表该对应的参数数据忽略不保存.
size 为允许参数输入的数据长度.
l 输入的数据数值以long int 或double型保存.
h 输入的数据数值以short int 型保存.
底下介绍type的几种形式:
%d 输入的数据会被转成一有符号的十进制数字(int).
%i 输入的数据会被转成一有符号的十进制数字, 若输入数据以“0x”或“0X”开头代表转换十六进制数字, 若以“0”开头则转换八进制数字, 其他情况代表十进制.
%0 输入的数据会被转换成一无符号的八进制数字.
%u 输入的数据会被转换成一无符号的正整数.
%x 输入的数据为无符号的十六进制数字, 转换后存于unsigned int型变量.
%X 同%x
%f 输入的数据为有符号的浮点型数, 转换后存于float型变量.
%e 同%f
%E 同%f
%g 同%f
%s 输入数据为以空格字符为终止的字符串.
%c 输入数据为单一字符.
%[]:读入一个字符集
%%:读入一个%字符
[] 读取数据但只允许括号内的字符. 如[a-z].
[^] 读取数据但不允许中括号的^符号后的字符出现, 如[^0-9].
与printf 相类似,scanf转义字符也有一个宽度域来限制输入数量.一个尺寸标识(h代表shor,而l代表long)一个正接收的参数是否短于或是长于默认的情 况.这就意味着%hd表示short int,而%ld代表long int,%lg代表前面所说的双精度浮点数.
一个标识符如果以*开始则表明所有的内容都会被忽略掉.这就意味着所输入的信息并不会被保存,所以我们也就并不需要一个变量来进行接收.
我们使用%c来从输入中读取一个单一的字符,这并不会跳过初始的空格符.
我 们使用%s来读取一个字符串,但是我们必须小心.他会跳过开头的空格符,但是却会停在字符串中的第一个空格符处.所以我们最好使用他来读取一个单词而不是 通常的字符串.同时没有指定区域宽度标识符,所以他可能读取的字符串的长度并没有限制,所以这个接收字符串必足够的大来存放输入流中最长的字符串.我们最 好使用区域宽度标识,或者是组合使用fgets和sscanf来读入一行输入.这样就可以尽量防止怀有恶意的用户所造成的缓冲区溢出.
我们使用% []标识可以读入由一个字符集合所组成的字符串.格式串%[A-Z]可以读入有大写字母组成的字符串.如果在这个集合中的第一个字符为^,那么则会读入由 不在集合中的字符所组成的字符串.所以如果要读入含有空格但是却在第一个逗号处结束的字符串,我们可以格式串%[^,].
我们可以输入下面的输入行:
Hello, 1234, 5.678, X, string to the end of the line
这个scanf调用会正确的读入四个内容:
char s[256];
int n;
float f;
char c;
scanf(“Hello,%d,%g, %c, %[^/n]”, &n,&f,&c,s);
scanf函数会返回他所成功读取的内容数,如果第一个内容失败则会返回零值.如果与第一个内容匹配之前已经到达输入的结尾,则会返回EOF.如果在文件流上发生读错误,则会设置文件流错误标记,而错误变量将errno将会进行设置来表明错误类型.
在通常的情况下,scanf以及一些相关的函数并不会推荐使用,这是由于下面的三个原因:
传统的原因是因为这些函数的实现存在一些bug
他们的使用并不灵活
他们会使得正是分析的程序难于理解.
我们可以试着使用一些其他的函数,如fread或是fgets.
sscanf详解:
sscanf() - 从一个字符串中读进与指定格式相符的数据.
函数原型:
Int sscanf( string str, string fmt, mixed var1, mixed var2 ... );
int scanf( const char *format [,argument]... );
说明:
sscanf与scanf类似,都是用于输入的,只是后者以屏幕(stdin)为输入源,前者以固定字符串为输入源。
其中的format可以是一个或多个 {%[*] [width] [{h | l | I64 | L}]type | ' ' | '/t' | '/n' | 非%符号}
注:
1、 * 亦可用于格式中, (即 %*d 和 %*s) 加了星号 (*) 表示跳过此数据不读入. (也就是不把此数据读入参数中)
2、{a|b|c}表示a,b,c中选一,[d],表示可以有d也可以没有d。
3、width表示读取宽度。
4、{h | l | I64 | L}:参数的size,通常h表示单字节size,I表示2字节 size,L表示4字节size(double例外),l64表示8字节size。
5、type :这就很多了,就是%s,%d之类。
6、特别的:%*[width] [{h | l | I64 | L}]type 表示满足该条件的被过滤掉,不会向目标参数中写入值
支持集合操作:
%[a-z] 表示匹配a到z中任意字符,贪婪性(尽可能多的匹配)
%[aB'] 匹配a、B、'中一员,贪婪性
%[^a] 匹配非a的任意字符,贪婪性
例子:
1. 常见用法。
char buf[512] = ;
sscanf("123456 ", "%s", buf);
printf("%s/n", buf);
结果为:123456
2. 取指定长度的字符串。如在下例中,取最大长度为4字节的字符串。
sscanf("123456 ", "%4s", buf);
printf("%s/n", buf);
结果为:1234
3. 取到指定字符为止的字符串。如在下例中,取遇到空格为止字符串。
sscanf("123456 abcdedf", "%[^ ]", buf);
printf("%s/n", buf);
结果为:123456
4. 取仅包含指定字符集的字符串。如在下例中,取仅包含1到9和小写字母的字符串。
sscanf("123456abcdedfBCDEF", "%[1-9a-z]", buf);
printf("%s/n", buf);
结果为:123456abcdedf
5. 取到指定字符集为止的字符串。如在下例中,取遇到大写字母为止的字符串。
sscanf("123456abcdedfBCDEF", "%[^A-Z]", buf);
printf("%s/n", buf);
结果为:123456abcdedf
6、给定一个字符串iios/12DDWDFF@122,获取 / 和 @ 之间的字符串,先将 "iios/"过滤掉,再将非'@'的一串内容送到buf中
sscanf("iios/12DDWDFF@122", "%*[^/]/%[^@]", buf);
printf("%s/n", buf);
结果为:12DDWDFF
7、给定一个字符串““hello, world”,仅保留world。(注意:“,”之后有一空格)
sscanf(“hello, world”, "%*s%s", buf);
printf("%s/n", buf);
结果为:world
%*s表示第一个匹配到的%s被过滤掉,即hello被过滤了
如果没有空格则结果为NULL。
sscanf的功能很类似于正则表达式, 但却没有正则表达式强大,所以如果对于比较复杂的字符串处理,建议使用正则表达式.
sscanf,表示从字符串中格式化输入
上面表示从str中,输入数字给x,就是32700
久以前,我以为c没有自己的split string函数,后来我发现了sscanf;一直以来,我以为sscanf只能以空格来界定字符串,现在我发现我错了。
sscanf是一个运行时函数,原形很简单:
int sscanf(
const char *buffer,
const char *format [,
argument ] ...
);
它强大的功能体现在对format的支持上。
我以前用它来分隔类似这样的字符串2006:03:18:
int a, b, c;
sscanf("2006:03:18", "%d:%d:%d", a, b, c);
以及2006:03:18 - 2006:04:18:
char sztime1[16] = "", sztime2[16] = "";
sscanf("2006:03:18 - 2006:04:18", "%s - %s", sztime1, sztime2);
但是后来,我需要处理2006:03:18-2006:04:18
仅仅是取消了‘-’两边的空格,却打破了%s对字符串的界定。
我需要重新设计一个函数来处理这样的情况?这并不复杂,但是,为了使所有的代码都有统一的风格,我需要改动很多地方,把已有的sscanf替换成我自己的分割函数。我以为我肯定需要这样做,并伴随着对sscanf的强烈不满而入睡;一觉醒来,发现其实不必。
format-type中有%[]这样的type field。如果读取的字符串,不是以空格来分隔的话,就可以使用%[]。
%[]类似于一个正则表达式。[a-z]表示读取a-z的所有字符,[^a-z]表示读取除a-z以外的所有字符。
所以那个问题也就迎刃而解了:
sscanf("2006:03:18 - 2006:04:18", "%[0-9,:] - %[0-9,:]", sztime1, sztime2);
以v开始的函数功能同对应去掉v的函数, 就是参数不相同
vprintf: 格式化输出数据
函数定义: int vprintf(const char *format, va_list ap);
vfprintf: 格式化输入数据至文件
函数定义: int vfprintf(FILE *stream, const char *format, va_list ap);
vsprintf: 格式化字符串复制
函数定义: int vsprintf(char *str, const char *format, va_list ap);
vsnprintf: 格式化字符串复制
函数定义: int vsnprintf(char *str, size_t size, const char *format, va_list ap);
应用举例:
不定参数: va_list
C语言中有些函数会利用不定参数的方式来取得参数内容, 常见的如printf(), sprintf(), execl()等等, 其最后参数是以...定义, 即代表后面的参数为不定个数的参数内容. 不定参数相关的表头文件在/usr/lib/gcc/include/stdarg.h其定义如下:
typedef char *va_list;
#define va_start(ap, p) (ap = (char *)(&(p)+1))
#define va_arg(ap, type) ((type *)(ap+=sizeof(type)))(-1)
#define va_end(ap)
应用举例: