未格式化的行I/O与格式化的行I/O

未格式化的行I/O 

char *fgets(char *buffer, int buffer_size, FILE *stream);
char *gets(char *buffer); 

fgets从指定的文件中读一行字符到调用者提供的缓冲区中, gets从标准输入读一行字符到调用者提供的缓冲区中。

gets函数的存在只是为了兼容以前的程序,我们写的代码都不应该调用这个函数。 gets函数的接口设计得很有问题,就像strcpy一样,用户提供一个缓冲区,却不能指定缓冲区的大小,很可能导致缓冲区溢出错误,这个函数比strcpy更加危险, strcpy的输入和输出都来自程序内部,只要程序员小心一点就可以避免出问题,而gets读取的输入直接来自程序外部,用户可能通过标准输入提供任意长的字符串,程序员无法避免gets函数导致的缓冲区溢出错误,所以唯一的办法就是不要用它。
fgets函数从stream所指的文件中读取以'\n'结尾的一行(包括'\n'在内)存到缓冲区s中,并且在该行末尾添加一个'\0'组成完整的字符串。

如果文件中的一行太长, fgets从文件中读了size-1个字符还没有读到'\n',就把已经读到的size-1个字符和一个'\0'字符存入缓冲区,文件中剩下的半行可以在下次调用fgets时继续读。

如果一次fgets调用在读入若干个字符后到达文件末尾,则将已读到的字符串加上'\0'存入缓冲区并返回,如果再次调用fgets则返回NULL,可以据此判断是否读到文件末尾。
对于fgets来说, '\n'是一个特别的字符,而'\0'并无任何特别之处,如果读到'\0'就当作普通字符读入。如果文件中存在'\0'字符(或者说0x00字节),调用fgets之后就无法判断缓冲区中的'\0'究竟是从文件读上来的字符还是由fgets自动添加的结束符,所以fgets只适合读文本文件
而不适合读二进制文件,并且文本文件中的所有字符都应该是可见字符,不能有'\0'

int fputs(char const *buffer, FILE *stream);
int puts(char const *buffer);

fputs向指定的文件写入一个字符串, puts向标准输出写入一个字符串。

缓冲区buffer中保存的是以'\0'结尾的字符串, fputs将该字符串写入文件stream,但并不写入结尾'\0'。与fgets不同的是, fputs并不关心的字符串中的'\n'字符,字符串中可以有'\n'也可以没'\n'

puts将字符串buffer写到标准输出(不包括结尾的'\0'),然后自动写一个'\n'到标准输出。 

格式化的行I/O

scanf和printf函数家族并不仅限于单行。也可以在行的一部分或多行上执行I/O操作。

scanf家族

#include <stdio.h>
int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *str, const char *format, ...);

#include <stdarg.h>
int vscanf(const char *format, va_list ap);
int vsscanf(const char *str, const char *format, va_list ap);
int vfscanf(FILE *stream, const char *format, va_list ap); 

scanf从标准输入读字符,按格式化字符串format中的转换说明解释这些字符,转换后赋给后面的参数,后面的参数都是传出参数,因此必须传地址而不能传值。

fscanf从指定的文件stream中读字符,而sscanf从指定的字符串str中读字符。

后面三个以v开头的函数的可变参数不是以...的形式传进来,而是以va_list类型传进来。

scanf用输入的字符去匹配格式化字符串中的字符和转换说明,如果成功匹配一个转换说明,就给一个参数赋值,如果读到文件或字符串末尾就停止,或者如果遇到和格式化字符串不匹配的地方(比如转换说明是%d却读到字符A)就停止。如果遇到不匹配的地方而停止,scanf的返回值可能小于赋值参数的个数,文件的读写位置指向输入中不匹配的地方,下次调用库函数读文件时可以从这个位置继续。

当格式化字符串到达末尾或读取的输入不再匹配格式字符串所指定的类型时,输入停止。被转换的输入值的数目作为函数的返回值。如果未转换任何输入值就到达尾部,函数返回常量EOF。

格式化字符串中包括: 

  • 空格或Tab,在处理过程中被忽略。
  • 普通字符(不包括%),和输入字符中的非空白字符相匹配。输入字符中的空白字符是指空格、 Tab\r\n\v\f
  • 转换说明,以%开头,以转换字符结尾,中间也有若干个可选项。 

转换说明中的可选项有: 

  • *号,表示这个转换说明只是用来匹配一段输入字符,但匹配结果并不赋给后面的参数。
  • 用一个整数指定的宽度N。表示这个转换说明最多匹配N个输入字符,或者匹配到输入字符中的下一个空白字符结束。
  • 对于整型参数可以指定字长,有hh、 h、 l、 ll(也可以写成一个L),含义和printf相同。但l和L还有一层含义,当转换字符是e、 f、 g时,表示赋值参数的类型是float *而非double*,这一点跟printf不同(结合以前讲的类型转换规则思考一下为什么不同),这时前面加上l或L分别表示double *或long double *型。

常用的转换字符有: 

转换字符

描述

d

匹配十进制整数(开头可以有负号),赋值参数的类型是int *。

i

匹配整数(开头可以有负号),赋值参数的类型是int *,如果输入字符以0x或0X开头则匹配十六进制整数,如果输入字符以0开头则匹配八进制整数。

o

u

x

匹配八进制、十进制、十六进制整数(开头可以有负号),赋值参数的类型是unsigned int *。

c

匹配一串字符,字符的个数由宽度指定,缺省宽度是1,赋值参数的类型是char*,末尾不会添加'\0'。如果输入字符的开头有空白字符,这些空白字符并不被忽略,而是保存到参数中,要想跳过开头的空白字符,可以在格式化字符串中用一个空格去匹配。

s

匹配一串非空白字符,从输入字符中的第一个非空白字符开始匹配到下一个空白
字符之前,或者匹配到指定的宽度,赋值参数的类型是char *,末尾自动添加'\0'。

e

f

g

匹配浮点数(开头可以有负号),赋值参数的类型是float *,也可以指定double*或long double *的字长。

%

转换说明%%匹配一个字符%,不做赋值。

printf家族 

#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_listap); 

printf格式化打印到标准输出,而fprintf打印到指定的文件stream中。

sprintf并不打印到文件,而是打印到用户提供的缓冲区str中并在末尾加'\0',由于格式化后的字符串长度很难预计,所以很可能造成缓冲区溢出,用snprintf更好一些,参数size指定了缓冲区长度,如果格式化后的字符串长度超过缓冲区长度,snprintf就把字符串截断到size-1字节,再加上一个'\0'写入缓冲区,也就是说snprintf保证字符串以'\0'结尾。snprintf的返回值是格式化后的字符串长度(不包括结尾的'\0'),如果字符串被截断,返回的是截断之前的长度,把它和实际缓冲区中的字符串长度相比较就可以知道是否发生了截断。

后四个函数在前四个函数名的前面多了个v,表示可变参数不是以...的形式传进来,而是以va_list类型传进来。

printf转换说明的可选项 :

选项

描述

#

八进制前面加0(转换字符为o),十六进制前面加0x(转换字符为x)或0X(转换字符为X)。

-

格式化后的内容居左,右边可以留空格。

宽度

用一个整数指定格式化后的最小长度,如果格式化后的内容没有这么长,可以在左边留空格,如果前面指定了-号就在右边留空格。宽度有一种特别的形式,不指定整数值而是写成一个*号,表示取一个int型参数作为宽度。

.

用于分隔上一条提到的最小长度和下一条要讲的精度。

精度

用一个整数表示精度,对于字符串来说指定了格式化后保留的最大长度,对于浮点数来说指定了格式化后小数点右边的位数,对于整数来说指定了格式化后的最小位数。精度也可以不指定整数值而是写成一个*号,表示取下一个int型参数作为精度。

字长

对于整型参数, hh、 h、 l、 ll分别表示是char、 short、 long、 long long型的字长,至于是有符号数还是无符号数则取决于转换字符;对于浮点型参数, L表示long double型的字长。

printf的转换字符:

转换字符

描述

d

i

取int型参数格式化成有符号十进制表示,如果格式化后的位数小于指定的精度,就在左边补0。

o

u

x

X

取unsigned int型参数格式化成无符号八进制(o)、十进制(u)、十六进制(x或X)表示, x表示十六进制数字用小写abcdef, X表示十六进制数字用大写ABCDEF,如果格式化后的位数小于指定的精度,就在左边补0。

c

取int型参数转换成unsigned char型,格式化成对应的ASCII码字符。

s

取const char *型参数所指向的字符串格式化输出,遇到'\0'结束,或者达到指定的最大长度(精度)结束。

p

取void *型参数格式化成十六进制表示。相当于%#x。

f

取double型参数格式化成[-]ddd.ddd这样的格式,小数点后的默认精度是6位。

e

E

取double型参数格式化成[-]d.ddde±dd(转换字符是e)或[-]d.dddE±dd(转换字符是E)这样的格式,小数点后的默认精度是6位,指数至少是两位。

g

G

取double型参数格式化,精度是指有效数字而非小数点后的数字,默认精度是6。如果指数小于-4或大于等于精度就按%e(转换字符是g)或%E(转换字符是G)格式化,否则按%f格式化。小数部分的末尾0去掉,如果没有小数部分,小数点也去掉。

%

格式化成一个%。

#include <stdio.h>
#include <string.h>
int main(void)
{
    printf("%.4d\n",100);    //十进制输出
    printf("%.3i\n",10);    //十进制输出

    printf("%#o\n", 255);    //无符号八进制表示,前边加0
    printf("%u\n", 255);    //无符号十进制表示 
    printf("%#x\n", 255);    //无符号十六进制小写表示,前边加0x
    printf("%#X\n", 255);    //无符号十六进制大写表示,前边加0X

    printf("%c\n", 256 + 'A');    //格式化成对应的ASCII码字符

    printf("%.3s\n", "hello");    //打印字符串

    printf("%p\n", main);    //打    印main函数的首地址

    printf("%f\n", 3.14);    
    printf("%e\n", 3.14);    

    printf("%g\n", 3.00);

    printf("%%\n");    //打印%
    
    return 0;
}

 

posted @ 2018-04-05 17:19  刘-皇叔  阅读(328)  评论(0编辑  收藏  举报