宽定向,则代表从流中读,写的字符是多字节的.shell
字节定向,则代表从流中读,写的字符是单字节的.数组
当流被建立时,流的定向是未定义的,此时ide
在该流上使用一个多字节 IO 函数会将该流设置为宽定向.函数
在该流上使用一个单字节 IO 函数会将该流设置为字节定向.ui
当流用于输入时,只存在2种缓冲类型:spa
无缓冲.则此时一次读取一个字符.3d
有缓冲,则此时一次读取缓冲区长度个字符,指针
int main(int argc,char *argv[]){ // setvbuf(stdin,0,_IONBF,0); char buf[128]; fgets(buf,sizeof(buf),stdin); } /* 注释 setvbuf() 所在行,此时输入流使用缓冲. */ $ strace ./Debug/Test 2 > sys_nobuf.log HelloWorld # 这个是输入. $ cat sys_nobuf.log read(0, "HelloWorld\n", 1024) = 11 /* 此时一次读取 1024 个字符 */ /* 取消对 setvbuf() 所在行的注释 */ $ strace ./Debug/Test 2 > sys_nobuf.log HelloWorld # 这个是输入. $ cat sys_nobuf.log # 由下能够看出当输入流不使用缓冲区时,一次读取一个字符. read(0, "H", 1) = 1 read(0, "e", 1) = 1 read(0, "l", 1) = 1 read(0, "l", 1) = 1 read(0, "o", 1) = 1 read(0, "W", 1) = 1 read(0, "o", 1) = 1 read(0, "r", 1) = 1 read(0, "l", 1) = 1 read(0, "d", 1) = 1 read(0, "\n", 1) = 1
当流用于输出时,有三种类型的缓冲:code
全缓冲,此时直至缓冲区满时,才会调用 write() 将整个缓冲区的内容送往内核.orm
行缓冲,此时直至缓冲区满,或者输出'\n'字符,或者须要调用 read() 从终端设备获取数据时会经过 write() 调用将数据送往内核.
无缓冲,此时直接将输出经过 write() 调用送往内核.
/* 当输入流须要从终端获取数据时,会刷新全部的行缓冲输出流 */ int main(int argc,char *argv[]){ setvbuf(stdout,0,_IOLBF,0);/* 显式将 stdout 设置为行缓冲,由于若在 shell 中使用了重定向,则 stdout 可能会是全缓冲类型 */ char buf[4]; fputs("Hello",stdout); fgets(buf,sizeof(buf),stdin); fputs("World",stdout); fgets(buf,sizeof(buf),stdin); puts("Exit"); } $ strace ./Debug/Test 2>sys.log 1>/dev/null HelloWorld # 这个是输入. $ cat sys.log write(1, "Hello", 5) = 5 read(0, "HelloWorld\n", 1024) = 11 write(1, "WorldExit\n", 10) = 10 # 由上输出能够看出在第一次调用 fgets() 时,须要从终端获取数据,因此会刷新全部的行缓冲输出流.即 write(1,"Hello",5) 调用在 read() 以前. # 由上输出也能够看出第2次 fgets() 调用并不须要从终端获取数据,因此不会刷新行缓冲输出流. $ cat t HelloWorld # 文件 t 的内容 $ strace ./Debug/Test 2>sys.log 1>/dev/null <t $ cat sys.log read(0, "HelloWorld\n", 4096) = 11 write(1, "HelloWorldExit\n", 15) = 15 # 此时并非从终端获取数据,而是从文件中获取输入数据,因此不会刷新行缓冲输出流,从上面 sys.log 的内容也能够看出来.
int main(int argc,char *argv[]){ char buf[4]; fputs("Hello",stdout); fgets(buf,sizeof(buf),stdin); fputs("World",stdout); fgets(buf,sizeof(buf),stdin); puts("Exit"); } $ strace ./Debug/Test 2>sys.log 1>/dev/null # stdout 被重定向至文件,因此此时缓冲类型为全缓冲. HelloWorld $ cat sys.log read(0, "HelloWorld\n", 1024) = 11 write(1, "HelloWorldExit\n", 15) = 15 # 以上输出演示了从终端输入数据并不会刷新全缓冲输出流.
标准出错输出流 stderr,始终是无缓冲的.
若流与终端设备关联(如:stdout,stdin),则默认是行缓冲的.不然默认是全缓冲的.
char *fgets(char *s, int size, FILE *stream);
该函数会从流 stream 中读取数据,而后将数据复制到 buf 指向的缓冲区中,并追加'\0'字符,函数会一直读取直至遇到如下三种状况,才会中止读取:
读取了 size-1 个字符.
遇到 EOF.
遇到了换行符,此时会将换行符也存放到缓冲区 buf 中.
返回值;若返回0,表示未读取任何内容,此时多是由于遇到了错误,或者EOF;不然返回 s.
int printf(const char *format, ...);
format,格式字符串;在其中有2类字符: 普通字符,转换指定串.
普通字符,会被直接复制到输出流上.
转换指定串,消耗0个或多个参数,将其转换为一个字符串,而后将该字符串复制到输出流上,由'%'开始,由转换指定符结尾,中间可能有(按照顺序):
0个或多个标志
一个可选的最小字段宽度,又称'域宽',至关于一个文本框,好比若域宽为7,能够想象一个长度为7个字符的文本框.
一个可选的精度,
一个可选的长度修饰符
#,在输出中额外添加一些信息用来表示输出的类型,如:
使用'#'状况 | 示例 | |
o | 添加前缀'0',代表输出是8进制 | printf("%o",12);// 输出 '14' printf("%#o",12);// 输出 '014' |
x,X | 添加前缀: '0x'(x),'0X'(X) | |
e,E,f,F,g,G,a,A | 总会加上小数点,不管小数点后有没有数字 | |
g,G | 不会移除尾随0 | printf("%g\n",3.33300000); /* 3.333 */ printf("%#g\n",3.33300000); /* 3.33300 */ |
0,指定了输出应该对齐在域宽的右侧,即右对齐;同时在左侧补0以占满整个域宽.如:
printf("|%7d|\n",33); printf("|%07d|\n",33); /* --- 执行输出 --- */ | 33| |0000033|
-,指定了输出应该在域宽的左侧对齐,默认右对齐,此时忽略'0'标志,如:
printf("|%-7d|\n",33); printf("|%7d|\n",33); /* --- 执行输出 --- */ |33 | | 33|
' '(空格),在正数(包括整数与浮点数)以前应该添加一个' '(空格),如:
printf("|%d|\n",33); printf("|% d|\n",33); /* 输出 */ |33| | 33|
+,在正数(包括整数与浮点数)以前应该添加一个'+',如:
printf("|%d|\n",33); printf("|%+d|\n",33); /* --- 输出 --- */ |33| |+33|
域宽的行为:
当转换后的字符串所含字符个数大于域宽时,域宽会自动扩充.
当转换后的字符串所含字符个数小于域宽时,默认行为: 字符串位于域宽的右侧,而后以' '来填充左侧.
经过'*'来指定域宽
若以'*'来指定域宽,代表域宽是由参数指定
若参数是一个负数,则至关于同时指定'-'标志,而后用负数的绝对值来指定域宽
printf("|%7d|\n",3); printf("|%*d|\n",7,3); /* 经过'*'指定域宽 */ printf("|%-7d|\n",3); printf("|%*d|\n",-7,3); /* 参数为负 */ /* 执行输出 */ | 3| | 3| |3 | |3 |
在不一样的转换指定符下,精度具备不一样的语义:
d,i,o,u,x,X 数字的最小位数,不足则填充0;如: printf("|%.7d|\n",3);/* |0000003| */
a,A,e,E,f,F 小数点后的数字位数
g,G 最大的有效数字,如: printf("%.5g",123.234334); // 123.23
s,S 容许输出的最大字符个数,如: printf("%.3s","HelloWorld"); // Hel
精度的指定
以'.'开头,紧随若干个数字如:"%.33d";代表转换的整数字符串最少要具备33个字符.
与最小字段宽度同样,也能够经过'*'来指定域宽;此时当参数为负时,至关于未指定过精度,如:
printf("|%.7d|\n",3); /* |0000003| */ printf("|%.*d|\n",7,3); /* |0000003| */ printf("|%.*d|\n",-7,3); /* |3| */
进一步指定了参数的类型.如:
hh
对于: 'd,i,o,u,x,X'来讲,代表参数类型为unsigned char,或者char类型,而再也不是默认的int类型.
对于: n 来讲,代表后续参数类型为 singed char*;而再也不是默认的 int* 类型.
h,同 hh,只不过将 char 替换为 short;
l
同 hh,只不过将 char 换成 long int;
对于 %c 来讲表示后续参数的类型为 wint_t;
对于 %s 来讲表示后续参数的类型为 wchar_t*;
ll,同 hh,只不过将 char 替换为 long long int;
L,对于 'a,A,e,E,f,F,g,G' 来讲,代表了参数类型为 long double;
j,对于: 'd,i,o,u,x,X'来讲,代表了参数类型为 intmax_t, uintmax_t;
z,对于: 'd,i,o,u,x,X'来讲,代表了参数类型为 size_t,或者 ssize_t;
t,对于: 'd,i,o,u,x,X'来讲,代表了参数类型为 ptrdiff_t;
指定了参数的类型与参数应该被转换为何格式的字符串.
d,i 指定了参数类型为int;应该被转换为十进制整数.
o,u,x,X 指定了参数类型为 unsigned int;
o 指定了参数应该被转换为八进制
u 指定了参数应该被转换为10进制
x,X 指定了参数应该被转换为16进制,只不过'x'使用'abcdef',而'X'使用'ABCDEF'.
e,E 指定了参数类型为 float/double,应该按照科学计数法转换为字符串,格式是这样的:[-]d.ddde±dd;其中指数部分至少2个数字,如:
printf("%e\n",1.0); /* 1.000000e+00 */ printf("%E\n",1.0); /* 1.000000E+00 */
f,F 指定了参数类型为 float/double,应该按照十进制计数法转换为字符串,格式: [-]ddd.ddd.
g,G 根据参数值来判断是使用'f,F';或者'g,G'.
s 指定了参数类型为 char*;若是指定了精度(而且小于字符数组的长度),则不须要'\0'做为字符串的结尾,如:
char str[]={'I','L','Y'}; printf("%.3s",str); /* 此时不要求以'\0'结尾 */
c 指定了参数类型为 unsigned char,而后写入参数指定的字符(即该字符的 ASCII 码值由参数指定).
p 指定了参数类型为 void*,至关于'#lx'.
n 将目前已经写至流的字符个数存放在一个 int* 参数.如: printf("HelloWorld%d%n",13,&i);// 则 i==12
m 将 strerror(errno) 写道输出流中,如: printf("%m");如: printf("%m"); /* Success *
int sscanf(const char *str, const char *format, ...);
空白字符,若一个字符使 isspace() 返回为真,则该字符为空白字符,如:'\n','\t',' '
format 是一个字符序列,其中字符能够分为三类:
空白字符,匹配输入串 str 中0个或多个空白字符.
普通字符,必须匹配输入串 str 中下一个字符.
转换指定串,指定了匹配那些字符,以及将匹配得来的子串转换为什么种数据类型.
char buf[]="He\n\t \t lloWorld"; char ch; sscanf(buf,"He l%cs",&ch); /* * 则匹配过程以下图: * H,e 精确匹配. * 空白字符匹配0个或多个空白字符. * l 精确匹配. * %c 匹配任意字符. * s,o 匹配失败,因此 sscanf() 返回. */
转换指定串,指定了匹配那些字符,以及将匹配得来的子串转换为什么种数据类型.其语法为: %[*][域宽][m][类型修饰符]转换指定符.其中'[]'表示可选.注意顺序不要搞错.
* 丢弃匹配得来的子串,此时不消耗参数.
char buf[]="123abcn" int i,j; sscanf(buf,"%*d%x",&i,&j); // %d 匹配得来的子串: 123;而后丢弃该子串,即并不进行转换而后将整数 123 存入 i 中; // %x 匹配得来的子串: abc,将其转换整数而后将结果 2748 存入 i 中.由于 * 不消耗参数,因此存入 i 中
域宽 指定了本次匹配的最大字符个数.
int i=33; char buf[]="1234567"; sscanf(buf,"%4d",&i);// 此时 %4d 匹配的子串为 1234,并非 1234567.
m 仅当转换指定符为'c','s','[]'才有效果,此时 sscanf() 内部会分配适当长度的内存块,将匹配子串存入该内存块中,而且将内存块首地址赋值给指定的参数.如:
char buf[]="HelloWorld"; char *str=0; sscanf(buf,"%7ms",&str); // 7 指定了域宽,域宽与'm'的顺序不可搞反. // 传递的是 &str,指向字符指针的指针. printf("%p: %s\n",str,str);
类型修饰符,用于进一步描述转换指定符.
转换指定符,用于指定匹配那些字符,以及将匹配子串转换成何种数据类型.
类型修饰符 | 对应的转化指定符 | 对应的指针类型 | ||||||
h | d,i,o,u,x,X,n | signed|unsigned short int * | ||||||
hh | 同 h | signed|unsigned char* | ||||||
j | 同 h | intmax_t* | uintmax_t *(8个字节的 int) | ||||||
l |
|
|
||||||
L |
|
|
||||||
t | 同 h | ptrdiff_t* | ||||||
z | 同 h | size_t* |
"%%"就是普通字符'%'
int a; scanf("%%%d",&a); /* 在数字以前必须有一个'%',如输入"43",则因为"%%"没有找到匹配项'%'因此a不会被赋值 * 输入"%43",则a成功输入 */ printf("%d",a);
转换指定字符 |
匹配的字符 |
对应的类型 |
d |
十进制有符号数字'[-0-9]' |
int |
i |
有符号整数,匹配的字符串相似"[-][0x|0]...",如"-0x33","-033"
|
int |
o |
[0-7];无符号八进制整数 |
unsigned int |
u |
[0-9];无符号十进制整数 |
unsigned int |
x|X |
[0-9a-z];无符号16进制数 |
unsigned int |
f |
有符号的浮点数 |
float |
s |
匹配不是空白字符的字符,默认跳过空白字符开始读取,终止条件:at white space or at the maximum field width, whichever occurs first |
字符串 |
c |
匹配全部字符,终止条件:at the maximum field width(默认为1,因此经常使用来读取字符); |
字符数组(不会自动添加'\0') |
[] |
匹配[]指定的集合,终止条件:at the maximum field width 或者遇到不符合[]指定的字符集合; |
字符串 |
p |
匹配指针类型 |
void* |
n |
不是用来指示如何转换字符串的,而是统计:the number of characters consumed thus far from the input(目前从输入串中使用的字符数量,包括自动略过/主动略过的空白字符) |
int |
在使用'[]'时,因为'-'与']'都是元字符,因此要想匹配'-'或者']';要把']'放在最开始的地方,'-'放在最后一个地方;如:'[]0-9-]用来匹配']','-','0-9';
%n 包括自动略过/主动略过的空白字符,%n 转换并不计入 sscanf() 的返回值中!
int main(int argc,char *argv[]){ int num; int used_chars; int ret; ret=sscanf(argv[1],"%i%n",&num,&used_chars); PrintVar(num); PrintVar(used_chars); PrintVar(ret); } /* 执行输出 */ $ ./Debug/Test ' 123 ' num: 123 used_chars: 6 /* 自动忽略的空格也被计入使用过的字符数中 */ ret: 1 /* %n 并不计入返回值. */ $ ./Debug/Test '123 ' num: 123 used_chars: 3 ret: 1