博主不定期更新【保研/推免、C/C++、5G移动通信、Linux、生活随笔】系列文章,喜欢的朋友【点赞+关注】支持一下吧!
Lecture 10 文件
1. 文件
1.1 格式化输入输出
printf
%[flags][width][.prec][hlL]typeFlag
含义-
左对齐
+
在前面放+或-
(space)
整数留空
0
0填充width或prec
含义number
最小字符数(输出宽度)
*
下一个参数是字符数
.number
小数点后的位数
.*
下一个参数是小数点后的位数类型修饰
含义hh
单个字节(char)
h
short
l
long
ll
long long
L
long doubletype
用于
type
用于i或d
int
g
float
u
unsignde int
G
float
o
八进制
a或A
十六进制浮点
x
十六进制
c
char
X
字母大写的十六进制
s
字符串
f或F
float,6
p
指针
e或E
指数
n
到目前为止读入/写出的个数#include
int main()
{
int num;
printf("%d%n\n", 123456, &num);
printf("%d\n", num);
return 0;
}
运行结果:
123456
6
注:因为会造成格式化字符串漏洞的原因,目前Windows已弃用%n,因此在本地IDE上运行可能无法得到正确结果
scanf
%[flag]typeflag
含义
flag
含义*
跳过
l
long, double
数字
最大字符数
ll
long long
hh
char
L
long double
h
shorttype
含义
type
含义d
int
s
字符串
i
整数,可能十六进制或八进制
[…(多种可能)]
所允许的字符
u
unsigned int
p
指针
o
八进制
x
十六进制
a,e,f,g
float
c
charscanf和printf的返回值
读入的项目数
输出的字符数
在要求严格的程序中,应该判断每次调用printf或scanf的返回值,从而了解程序运行中是否存在问题
1.2 文件输入输出
用>和
用来指定一个文件,将输出写入到该文件
文本文件输入输出FILE
FILE* fopen(const char *restrict path, const char *restrict mode);
int fclose(FILE *stream);
fscanf(FILE*, ...);
fprintf(FILE*, ...);
打开文件的标准代码
FILE* fp = fopen("file", "r");
if (fp)
{
fscanf(fp, ...);
fclose(fp);
}
else
{
printf("无法打开文件");
...
}
fopen
rrr
打开只读r+
打开读写,从文件头开始
w
打开只写。如果不存在则新建,如果存在则清空
w+
打开读写。如果不存在则新建,如果存在则清空
a
打开追加。如果不存在则新建,如果存在则从文件尾开始
…x
(一般为wx/ax)只新建,如果文件已存在则不能打开
1.3 二进制文件
其实所有的文件最终都是二进制的
文本文件无非是用最简单的方式可以读写的文件(unix系统)
more、tail
cat
vi
而二进制文件是需要专门的程序来读写的文件
文本文件的输入输出是格式化,可能经过转码
文本 VS 二进制
Unix喜欢用文本文件来做数据存储和程序配置,Windows喜欢用二进制文件
文本的优势是方便人类读写,而且跨平台
文本的缺点是程序输入输出要经过格式化,开销大
二进制的优点是程序读写快
二进制的缺点是人类读写困难,而且不跨平台
int的大小不一致,大小端的问题…
程序为什么要文件
配置
Unix用文本,Window用注册表
数据
稍微有点量的数据都放数据库了
媒体
这个只能是二进制的
现实是,程序通过第三方库来读写文件,很少直接读写二进制文件了
二进制读写
size_t fread(void *restrict ptr, size_t size, size_t nitems, FILE *restrict stream);//第一个参数:指针,读或写的内存;第二个参数:这块内存的大小(一个结构的大小);第三个参数:有几个这样的内存;第四个参数:文件指针
size_t fwrite(const void *restrict ptr, size_t size, size_t nitems, FILE *restrict stream);
注意FILE指针是最后一个参数
返回的是成功读写的字节数
为什么有nitem?
因为二进制文件的读写一般都是通过对一个结构变量的操作来进行的
于是nitem就是用来说明这次读写几个结构变量
在文件中定位
long ftell(FILE *stream);
int fseek(FILE *stream, long offset, int whence);
SEEK_SET:从头开始
SEEK_CUR:从当前位置开始
SEEK_END:从尾开始(倒过来)
可移植性
这样的二进制文件不具有可移植性(int不同)
解决方案之一是放弃使用int,而是typedef具有明确大小的类型
更好的方案是用文本
2. 位运算
2.1 按位运算
按位运算的运算符:
& 按位的与
让某一位或某些位为0:x & 0xFE
0xFE->1111 1110,其作用为让x的最低位变为0
取一个数中的一段:x & 0xFF
0xFF->0000 0000 0000 0000 0000 0000 1111 1111
取x的最后一个字节,前面三个字节置0(这里假设x是32位int型)
| 按位的或
使得一位或几个位为1:x | 0x01
把两个数拼起来:0x00FF | 0xFF00
~ 按位取反
把1位变0,0位变1
想得到全部位为1的数:~0
7=0111;x | 7使得低3位为1,而x & ~7使得低3位为0
^ 按位的异或
两个位相等,结果为0;不相等,结果为1
对一个变量用同一个值异或两次,等于什么也没做
x ^ y ^ y -> x
逻辑运算 VS 按位运算
对于逻辑运算,它只看到两个值:0和1
可以认为逻辑运算相当于把所有非0值都变成1,然后做按位运算5 & 4 -> 4 而 5 && 4 -> 1 & 1 -> 1
5 | 4 -> 5 而 5 || 4 -> 1 | 1 -> 1
~4 -> 3 而 !4 -> !1 -> 0
2.2 移位运算
<< 左移
i << j 表示i中所有的位向左移动j个位置,而右边填入0
所有小于int的类型,移位以int的方式来做,结果是int
x <<= 1 ⇔\Leftrightarrow⇔ x *= 2
x <<= n ⇔\Leftrightarrow⇔ x *= 2n2^n2n
>> 右移
i >> j 表示i中所有的位向右移j位
所有小于int的类型,移位以int的方式来做,结果是int
对于unsigned类型,左边填入0;对于signed类型,左边填入原来的最高位(保持符号不变)
x >>= 1 ⇔\Leftrightarrow⇔ x /= 2
x >>= n ⇔\Leftrightarrow⇔ x /= 2n2^n2n
移位的位数不要用负数,这是没有定义的行为
附:有符号十六进制数转换为十进制数:
首先给出原码、反码、补码转换:
原码 ⟹\Longrightarrow⟹ 按位取反(除符号位) ⟹\Longrightarrow⟹ 反码 ⟹\Longrightarrow⟹ 加1 ⟹\Longrightarrow⟹ 补码
补码的补码是原码
实例:(signed) int 0x82000000
首先写成二进制:1000 0010 0000 0000 0000 0000 0000 0000
按位取反(除符号位):1111 1101 1111 1111 1111 1111 1111 1111
加1:1111 1110 0000 0000 0000 0000 0000 0000
计算:−(7×167+14×166)=−2113929216-(7 \times 16^7+14 \times 16^6)=-2113929216−(7×167+14×166)=−2113929216
而 unsigned int 0x82000000
转换为十进制为:8×167+2×166=21810380808 \times 16^7+2 \times 16^6=21810380808×167+2×166=2181038080
2.3 位运算例子
输出一个数的二进制
#include
int main()
{
int number;
scanf("%d", &number);
unsigned mask = 1u<<31;
for ( ; mask; mask >>=1 )
{
printf("%d", number & mask? 1:0);//逐一判断number的每个位上是0还是1
}
printf("\n");
return 0;
}
2.4 位段
把一个int的若干位组合成一个结构
struct {
unsigned int leading : 3; //数字表示所占比特数
unsigned int FLAG1: 1;
unsigned int FLAG2: 1;
int trailing: 11;
}
可以直接用位段的成员名称来访问
比移位、与、或还方便
编译器会安排其中的位的排列,不具有可移植性
当所需的位超过一个int时会采用多个int