13.1.1 格式化输入输出
从第一天学c语言就接触到了printf和scanf.下面彻底研究下占位符%d有多少细节。
printf: %[flags][width][.prec][hIL]type
flag:标志,有以下几种
flag | 含义 |
---|---|
- | 左对齐(和width一起用) |
+ | 输出前面带正负号 |
(space) | 正数留空 |
0 | 空格用0填充(不能和-同时使用) |
width(宽度)或prec
width或prec | 含义 |
---|---|
number | 最小字符数(包括小数点) |
* | 下一个读入的参数是字符数 |
.number | 小数点后面的位数 |
.* | 下一个参数是小数点后的位数 |
hIL:类型修饰符
类型修饰 | 含义 |
---|---|
hh | 单个字节 |
h | short |
l | long |
ll | long long |
L | long double |
type:类型
type | 用于 |
---|---|
i或d | int |
u | unsigned int |
o | 八进制 |
x | 十六进制 |
X | 大写字母的十六进制 |
f或F | float, 6 |
e或E | 指数 |
g | float |
G | float |
a或A | 十六进制浮点 |
c | char |
s | 字符串 |
p | 指针 |
n | 读入/写出的个数 |
%n是当操作做到这里时,已经输出了多少个字符,并且填到指针所指的变量里。
scanf: %[flag]type
flag | 含义 |
---|---|
* | 跳过 |
数字 | 最大字符数 |
hh | char |
h | short |
l | long, double |
ll | long long |
L | long double |
type | 用于 |
d | int |
i | 整数,可能是十六进制或八进制 |
u | unsigned int |
o | 八进制 |
x | 十六进制 |
a, e, f, g | float |
c | char |
s | 字符串(单词) |
[...] | 所允许的字符 |
p | 指针 |
%i会根据输入(如0x12、012)来判断是十六进制还是八进制还是十进制。
[...]举例
GPS中会读到一串字符,用逗号分隔
占位符%[^,]
表示读入逗号前的所有字符
printf和scanf是有返回值的。scanf是返回几个item,即这次读入了几个变量;而printf则是这次输出了多少个字符。因此当我们面对要求严格的程序,比如长期运行的大程序,就需要判断每次调用scanf和printf的返回值来了解程序运行中是否会存在问题。
13.1.2 文件输入输出
我们之前运行程序的时候在命令行里输入./test(test是文件名)即可。
对文件做输入输出:./test > 12.out,然后再输入scanf标准输入的内容,但是程序没有printf标准输出了。但是打开12.out文件(输入more 12.out),会输出标准输入和标准输出。
同样,当我们输入 cat >12.in ,输入标准输入,然后再Ctrl D结束程序,再查看12.in文件(输入more 12.in)会输出标准输入。
然后运行test文件的时候这样写:./test < 12.in,12.in的内容会输入给./test,然后我们会得到其输出。
(当然还可以./test < 12.in > 12.out,输入是文件,输出也是文件。12.out里也有我们所期望的结果。)
以上是程序运行中的重定向。我们用<来指定一个文件的输入,用>来指定输出到一个文件中。
然而这并不是一般的文件输入输出方式,一般的方式需要做一个FILE,在stdlib.h中已经声明好过。
用fopen打开文件打开后用fscanf和fprintf函数来读写。
FILE* fp=fopen("file","r");//file是文件名,r表示读
if(fp){//如果没打开,会返回NULL
fscanf(fp,...);//读文件。省略号的东西和正常的scanf一样了。后面还可以printf
fclose(fp);
}else{}//无法打开的反馈。如:输出 无法打开文件
直接./test运行即可,会打开12.in然后正常输出。
如果删掉12.in(rm 12.in)就无法打开文件。
fopen第一个参数是文件名的字符串,第二个参数字符串用途如下:
r | 打开只读 |
r+ | 打开读写,从文件头开始读/写 |
w | 打开只写,如果不存在则新建,如果存在则清空 |
w+ | 打开读写,如果不存在则新建,如果存在则清空 |
a | 打开追加,如果不存在则新建,如果存在不清空,从文件尾开始 |
在结尾加x | 只新建,如果文件已存在则不能打开 |
13.1.3 二进制文件
所有文件最终都是二进制的。文本文件,只是用一种简单的方式可以进行读写的文件。如more, tail 可以打开文件,cat 打开文件或定向到其他文件,vi 做完整的编辑,etc。但是二进制没这么简单。
选择文本还是二进制?
UNIX(和windows一样都是操作系统。)喜欢用文本文件储存数据、配置程序。交互式终端的出现(类似windows的cmd窗口)使得人们喜欢用文本和计算机“交流”。因此,UNIX的shell就提供了一些读写文本的小程序。
windows(更准确要说DOS),个人计算机的制作者并不继承、熟悉UNIX文化(宛如在围墙之外,不熟悉围墙内)全凭自己的理解做出草根文化的DOS,更喜欢用二进制文件。
PC和DOS刚开始的时候能力有限,二进制更加接近底层。
文本、二进制各有优劣。
文本方便人类读写,而且跨平台;缺点是程序的输入输出要经过格式化,开销大。
二进制的缺点是人类读写困难而且不跨平台;(如不同计算机int大小不一致)优点是读写快。
在这里,我们要回顾下为什么程序要使用文件。
- 有配置(比如窗口大小、字体颜色)UNIX用文本文件就能编辑,Windows是用一个大文件:注册表编辑。
- 数据:保存数据,如学生成绩。稍微大一些的数据都放数据库那了。
- 媒体:如图片音频,这些不能是文本文件,只能是二进制文件。
其实现在程序通过第三方库来读写文件,很少直接读写二进制文件了。
对二进制的读写
第一个参数是指针,要读写的内存;第二个参数是那块内存(一个结构)的大小;第三个参数是有几个这样的内存,第四个参数是文件指针。返回成功读写的字节数。
因为二进制文件的读写一半是通过对一个结构变量的操作来进行的,所以nitem就是用来说明这次读写了几个结构变量。
这里老师做了一个非常有意思的东西,建议去看看(我懒了)
fwrite可以把数据以二进制形式写到文件中,fread类似。
定位:找出现在处在文件中的位置,或跳到指定位置。
long ftell(FILE *stream);
int fseek(FILE *stream, long offset, int whence);
/*whence:SEEK_SET 从头开始
SEEK_CUR 从当前位置开始
SEEK_END 从末尾开始*/
如:fseek(fp, 0L, SEEK_END);
当前位置就在结尾了。这时如果我们令long size=ftell(fp);
得到的就是文件大小。
我们可以先这样获得文件大小,然后除以sizeof来得知内部储存了多少个数据;然后用户说我想看第几个数据,我们再利用fseek函数跳到那个位置,输出每一项。
这样的二进制文件不具备可移植性;即在int为32位的机器上写出的数据文件不能在int为64位的机器上正确读出。解决方案1是放弃int,使用typedef具有明确大小的类型;2是用文本。