L12_文件处理___2023-02-04

目录


格式化输入输出

输出的格式字符串

格式字符串有4个可选的参数
完整的格式字符串是这样的:
%[flags][width][.prec][h|L]type

flags

flags是一个标志, 控制数字输出的样式
有这样一些可选项

flags 含义
- 左对齐
+ 显示正负号
空格 正数留空
0 用零填充多余的位置

注意, 一个格式字符串可以有很多flags
这些flags的先后顺序无关紧要
例如printf("%+-9d",123);就会输出左对齐的+123
但是, 左对齐, 正数留空用零填充 这三个需求是冲突的, 只能出现一个

width和.prec

width是一个正整数, 表示输出的数字总共所占据的字符的个数
.prec是加在width前面的小数点, 表示输出的数字保留小数点后的个数

注意:

  • %9.2d表示总共是9个字符位, 小数点后保留两位

不要把%9.2d误当做是小数点前有9位的意思!

  • width还可以是一个星号*, 让一个参数传入来作为width的值

举例:

int len1 = 10;
int len2 = 2;
printf("下面这个数字共%d位, 保留了%d位小数点\n->|%+0*.*f|-<", len1, len2, len1, len2, 123.4);

其输出为:

下面这个数字共10位, 保留了2位小数点
->|+000123.40|-<

h|L

h|L是对类型的修饰

类型修饰 含义
hh 单个字节
h short
l(小写L) long
ll(小写L) long long
L long double

事实上并未生效, 能生效的只有hl两个
hh要实现的效果似乎可以直接用c替代, 就是输出最短的数据类型
这里有个有趣的现象: printf("%c", 12345);输出的结果是9

这是因为12345在内存中储存为16进制的形式0x3039
只取最后一位, 以字符形式输出就是9

type

汇总一下所有的类型

type 用于
i或者d int
u unsigned int
o 八进制
x 字母小写的十六进制
X 字母大写的十六进制
f或者F 浮点数(到小数点后6位)
e或者E 用科学计数法/指数形式表示, 大小写对应输出的"e"的大小写
g或者G 保留6位有效数字的浮点
a或者A 十六进制浮点
c char
s 字符串
p 指针
n 读入/写出的个数
int num=0;
printf("->|%d|<-%n\n", 12345, &num);
printf("%d", num);

输出结果是:

->|12345|<-
11

笔者在VSC没有成功生效(虽然但是也没warning)

输入的格式字符串

相较于输出, 输入可选的参数比较简单
其格式字符串是这样的: %[flag]type

flag

flag 含义
* 跳过一项
数字 最大读入的字符数
hh 作为字符读入char
h short
l(小写L) long或者double
ll(小写L) long long
L long double

举例:

int num=0;
scanf("%*d%d", &num);
printf("%d", num);

输入输出:

输入: 12345 67890  
输出: 67890

type

type 用于
d int
i 整数, 可以是十/八/十六进制的(更灵活)
u unsigned int
o 八进制
x 十六进制
a/e/f/g float
c char
s 字符串
[...] 所允许的字符(类似于正则表达式)
p 指针

举例1:

int n1 = 0;
int n2 = 0;
scanf("%i%i", &n1, &n2);
printf("%i %i", n1, n2);

输入输出:

输入: 0xab 012
输出: 171 10

举例2:

char n1[20] ;
char n2[20];
scanf("%*[^:]:%[^,],%[^.]", n1, n2);
printf("%s %s", n1, n2);

输入输出:

输入: 输入了两个数字:123,456.
输出: 123 456

这里的[^:]是指: 在第一个:前(不包括:)所有的内容(视为字符串)
其他两个[^,] [^.]同理
分析一下这里的输入格式字符串:

  • 首先, 跳过封号:前所有内容
  • 读入封号:
  • 读入逗号,前所有内容, 作为字符串交给地址n1
  • 读入逗号,
  • 读入句号.前所有内容, 作为字符串交给地址n2

[...]的作用不止这一个, 但网上一下子搜不到相关知识点orz
有一篇blog讲的较为详细, 链接-> c语言学习笔记第四章——字符串和格式化输入、输出

printf和scanf的返回值

printf会返回成功输出的 字符数
scanf会返回成功读入的 项目数

在较为严格的项目中, 应当判断每次输入输出的返回值
以确保程序运行中不会产生问题

举例:

int num = 0;
int s = 0, p = 0;
s=scanf("%i", &num);
p=printf("%i\n",num);
printf("scanf返回值:%d\n",s);
printf("printf返回值:%d\n",p);

输入: 0xabc
输出:

0xabc
2748
scanf返回值:1
printf返回值:5

这里返回值为5, 因为回车符也算在其中

文件的输入输出

FILE

FILE是标准库中定义的结构
常用的是文件指针: FILE *

该结构的其他成员用来反映文件的相关信息

操作文件的函数

  • fopen函数打开指定文件, 并返回一个FILE指针
    FILE * fopen (const char * restrict path, const char * restrict mode);
  • fclose函数关闭文件
    int fclose (FILE * stream);
  • fscanf函数负责从文件中读入
    int fscanf(FILE *__stream, const char *__format, ...)
  • fscanf函数负责向文件中写入
    int fprintf (FILE *__stream, const char *__format, ...)

打开文件的标准代码

FILE *fp = fopen("file", "r");
if (fp)
{
    fscanf(fp, ...);
    /* handler */
    fclose(fp);
}
else
{
    /* fp == null */
    printf("无法打开文件\n");
}

如果找不到文件, fopen函数会返回null

函数

fopen

fopen的第一个参数是文件的路径
第二个变量是打开文件的方式, 下面这个表格是所有可选的方式

方式代号 效果
r 只读
r+ 读写, 从文件头开始
w 只写, 不存在就新建, 存在就清空
w+ 读写, 不存在就新建, 存在就清空
a 追加, 不存在就新建, 存在就从文件尾部开始
..x(在其他代号的后面加x) 只新建, 如果文件已经存在了就不能打开

二进制文件

所有文件最终都是二进制
文本文件可以通过简单的方式实现读写
但是二进制文件需要专门的程序来实现读写

选择文本文件还是二进制文件的历史

Unix 喜欢使用文本文件来做数据存储和程序配置

由于交互式终端的出现(在Unix之前), 人们更倾向于使用文本与计算机"talk"(交互)

Unix的shell提供了利于读写文本的小程序, 因此使用文本很方便

Windows喜欢使用二进制文件

DOS操作系统的发明来源于草根文化, 是设计者革新的发明, 完全出自对于计算机硬件的理解
因此, DOS并不继承Unix的特点

用二进制的方式做输入输出更接近底层

文本文件和二进制文件的比较

文本文件:

  • 优点
    • 方便人类读写
    • 跨平台(到哪儿都能用)
  • 缺点
    • 输入输出需要格式化
    • 运算和处理的开销较大

二进制文件:

  • 缺点
    • 输入输出是直接的
    • 程序读写无需计算, 速度很快
  • 缺点
    • 人类读写困难
    • 不支持跨平台(例如int这样的类型在不同操作系统中的长度不一样, 还有可能涉及到大小端的问题)

程序运行为什么需要文件

  1. 配置信息
    • Unix采用文本
    • Windows用注册表记录

注册表是一个很大的二进制文件, 所有文件的配置信息全部记录在其中

  1. 数据存储
    • 量大的数据可以使用数据库很轻松的存储
  2. 媒体文件
    • 媒体文件只能是二进制文件

事实上, 程序基本都通过第三方的库来实现对文件的读写
很少直接读写二进制文件

操作二进制文件

读写

实在要直接处理二进制文件, 则采用以下这对函数:

size_t fread (void * pointer, size_t size, size_t nitems, FILE * stream);
size_t fwrite (const void * pointer, size_t size, size_t nitems, FILE * stream);

参数表:

  1. 指针, 指向要读写的内存
  2. 这块内存的大小
  3. 有几个这样的内存
  4. 文件指针

返回值 的是成功的读写的字节数

第三个参数nitems( number of items )的作用是什么?
一般对二进制文件的读写都是通过对一个结构变量的操作进行的
nitems就是用来说明这次读写操作几个结构变量

定位

  • 得到现在文件中的位置(从文件开头到现在位置的字节数)
    long ftell (FILE * stream);
  • 定位到指定的位置 int fseek (FILE * stream, long offset, int whence);

fseek这个函数有三个参数:

  1. stream是FILE指针
  2. offset是偏移量, 也就是数到第几个字节
  3. whence是寻找的方式, 有三个选项(都是预定义的宏, 所以表现出来的是int)
  • SEEK_SET: 从头开始找
  • SEEK_CUR: 从当前位置开始找
  • SEEK_END: 从尾巴开始从后向前找

一般从二进制文件中得到结构个数的方式如下:
fseek(fp, 0L, SEEK_END);可以移动到末尾(这里的0L是将数字0作为长整型Long的表达方式)
然后再用long size = ftell(fp);就可以得到从头到尾的字节数
用这个size除以每一个结构的大小就可以得到这个文件中的结构个数

可移植性问题

二进制文件不具有可以执行性

int在32位机的字节数与64位机的不一致
读写就是错误的

解决方案之一是放弃使用int
转而使用typedef人为定义出具有明确字节数的自定义类型
当然, 更好的方案还是使用文本