stdio.h

流的定向

  • 宽定向,则代表从流中读,写的字符是多字节的.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),则默认是行缓冲的.不然默认是全缓冲的.

fgets()

char *fgets(char *s, int size, FILE *stream);
  • 该函数会从流 stream 中读取数据,而后将数据复制到 buf 指向的缓冲区中,并追加'\0'字符,函数会一直读取直至遇到如下三种状况,才会中止读取:

    • 读取了 size-1 个字符.

    • 遇到 EOF.

    • 遇到了换行符,此时会将换行符也存放到缓冲区 buf 中.

  • 返回值;若返回0,表示未读取任何内容,此时多是由于遇到了错误,或者EOF;不然返回 s.

printf()

int printf(const char *format, ...);

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&plusmn;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 *

sscanf()

int sscanf(const char *str, const char *format, ...);

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
d,i,o,u,x,X,n
e,f,g
c,s
unsigned|signed long int *
double *
wide-character(宽字符|宽字符串)
L
e,f,g
d,i,o,u,x
long double *
long long*
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"

  • 若是字符串以"0x"开头,则匹配[0-9a-f],16进制

  • 若是字符串以'0'开头,则匹配[0-7],8进制

  • 其它状况下匹配[0-9],10进制

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
相关文章
相关标签/搜索