C语言进阶 13. 文件
文章目录
- C语言进阶 13. 文件
- 13.1. 格式化输入输出
- 13.2. 文件输入输出
- 13.3. 二进制文件
- 13.4. 按位运算
- 13.5. 移位运算
- 13.6. 位运算例子
- 13.7. 位段
13.1. 格式化输入输出
-
格式化输入输出:
- printf
- %[flags][width][.prec][hlL]type
- scanf
- %[flags]type
- printf
-
%[flags][width][.prec][hlL]type:
flags 含义 - 左对齐 + 在前面放+或- (space) 正数留空 0 0填充 width或prec 含义 number 最小字符数 * 下一个参数是字符数 .number 小数点后的数字 .* 下一个参数是小数点后的位数 hlL 含义 hh 单个字节byte h short l long ll long long L long double type 用于 i或d int u unsigned int o 八进制 x 十六进制 X 字母大写的十六进制 f或F float e或E 指数 g float G float a或A 十六进制 c char s 字符串 p 指针 n 读入/写出的个数
-
scanf: %[flags]type:
flag 含义 * 跳过 数字 最大字符数 hh char h short l long double ll long long L long double type 用于 d int i int, 可以为十六进制或八进制 u unsigned int o 八进制 x 十六进制 a,e,f,g float c char s 字符串 p 指针 [...] 所允许的字符 [^,] 读到,为止
-
printf和scanf的返回值:
-
读入的项目数
-
输出的字符数
-
在要求严格的程序中, 应该判断每次调用scanf或printf的返回值, 从而了解程序运行中是否存在问题
-
#include <stdio.h>
int main(int argc, char const* argv[]) {
//printf("%9d", 123); // 123
//printf("h\n");
//printf("%-9d\n", 123);//123
//printf("%+d\n", 123); //+123
//printf("% d\n", 123); // 123
//printf("%09d\n", 123);//000000123
//printf("%9.2f\n", 123.0);// 123.00
//printf("%*d\n", 6, 123); // 123
//printf("%*.*f\n", 6, 2, 123.0);//123.00
//printf("%hhd\n", 12345);//57, 只取了1byte, 前面的位全部省去
//int num;
//scanf("%*d%d", &num);//1 2
//printf("%d\n", num);//2
//int num2;
//scanf("%i", &num2);//0x12
//printf("%d\n", num2);//18
//char s[10];
//char s2[10];
//char s3[10];
//scanf("%[^,], %[^,], %[^,]", s, s2, s3);//1,2,3,
//printf("%s %s %s\n", s, s2, s3);//1 2 3
int num;
int i1 = scanf("%d", &num);//123
int i2 = printf("%d\n", num);//123
printf("%d:%d\n", i1, i2);//1:4
return 0;
}
13.2. 文件输入输出
-
重定向没听懂
-
文件输入输出:
-
用>和<做重定向
-
使用Linux和Windows系统的命令行都可以使用这种方式
> D:\C语言\C_code\Two_Advanced\13.File\13.0
-
-
FILE:
- 打开文件的标准代码
FILE* fp = fopen("fileName", "r"); //r: 只读 fopen: 没有打开返回NULL if (fp) { int num; fscanf(fp, "%d", &num); printf("%d\n", num); fclose(fp); } else { printf("无法打开文件\n"); }
-
fopen:
fopen("参数1", "参数2"); 参数1: 文件名 参数2: r 只读 r+ 读写, 从文件头开始 w 只写, 如果不存在则新建, 如果存在则清空 w+ 读写, 如果不存在则新建, 如果存在则清空 a 追加, 如果不存在则新建, 如果存在则从文件尾开始 ..x 只新建, 如果文件已存在则不能打开
#include <stdio.h>
#include <string.h>
int main(int argc, char const* argv[]) {
FILE* fp = fopen("a.txt", "r");
if (fp) {
int num;
fscanf(fp, "%d", &num);
printf("%d\n", num);
fclose(fp);
}
else {
printf("无法打开文件\n");
}
return 0;
}
13.3. 二进制文件
-
后面的没听懂
-
二进制文件:
-
其实所有的文件最终都是二进制的
-
文本文件无非是用最简单的方式可以读写的文件
- more, tail
- cat
- vi
-
而二进制文件是需要专门的程序来读写的文件
-
文本文件的输入输出是格式化, 可能经过转码
-
-
文本 VS 二进制:
-
Unix喜欢用文本文件来做数据存储和程序配置
- 交互式终端的出现使得人们喜欢用文本和计算机"talk"
- Unix的shell提供了一些读写文本的小程序
-
Windows喜欢用二进制文件
- DOS是草根文化, 并不继承和熟悉Unix文化
- PC刚开始的时候能力有限, DOS的能力更有限, 二进制更接近底层
-
-
优缺点:
-
文本
- 优势是方便认类读写, 而且跨平台
- 缺点是程序输入输出要经过格式化, 开销大
-
二进制
- 缺点是认类读写困难, 而且不跨平台
- int的大小不一致, 大小端的问题…
- 优点是程序读写快
- 缺点是认类读写困难, 而且不跨平台
-
-
程序为什么要文件:
-
配置
- Unix用文本, Windows用注册表
-
数据
- 稍微有点量的数据都放数据库了
-
媒体
- 这个只能是二进制的
-
现实是, 程序通过第三方库来读写文件, 很少直接读写二进制文件了
-
-
二进制读写:
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为32位的机器上写成的数据文件无法直接在int为64位的机器上正确的读出
-
解决方案之一是放弃使用int, 而是typedef具有明确大小的类型
-
更好的方案是文本
-
13.4. 按位运算
-
按位运算:
- C有这些按位运算的运算符, 位指的是二进制位
& 按位与 | 按位或 ~ 按位取反 ^ 按位异或 << 左移 >> 右移
-
按位与&:
-
如果(x)i == 1 并且 (y)i == 1, 那么(x & y)i = 1
-
否则(x & y)i = 0
-
按位与常用于两种应用
- 让某一位或某些位为0: x & 0xFE
- 取一个数中的一段: x & 0xFF
-
-
按位或|:
-
如果(x)i == 1 或 (y)i == 1, 那么(x | y)i = 1
-
否则(x | y)i = 0
-
按位或常用于两种应用
- 使得一位或几个位为1: x | 0x01
- 把两个数拼起来: 0x00FF | 0xFF00
-
-
按位取反~:
- (~x)i = 1 - (x)i
-
把1位变0, 0位变1
-
想得到全部位为1的数: ~0
-
7的二进制是0111, x | 7 使得低3位为1
-
x & ~7, 就使得低3位为0
-
- (~x)i = 1 - (x)i
-
逻辑运算 VS 按位运算:
-
对于逻辑运算, 它只看到两个值: 0和1
-
可以认为逻辑运算相当于把所有非0值都变成1, 然后做按位运算
5 & 4 -> 4 而 5 && 4 -> 1 & 1 -> 1 5 | 4 -> 5 而 5 || 4 -> 1 | 1 -> 1 ~4 -> -5 而 !4 -> !1 -> 0
-
-
按位异或:
-
如果(x)i == (y)i, 那么(x ^ y)i = 0
-
否则(x ^ y)i = 1
-
如果两个位相等, 那么结果为0, 不相等, 结果为1
-
如果x和y相等, 那么x^y的结果为0
-
对一个变量用同一个值异或两次, 等于什么也没做
x^y^y -> x
-
#include <stdio.h>
int main(int argc, char const* argv[]) {
unsigned char c = 0xAA;
printf(" c = %hhx\n", c);// c = aa
printf("~c = %hhx\n", (char)~c);//~c = 55
printf("-c = %hhx\n", (char)-c);//-c = 56
printf("%d\n", ~5);
return 0;
}
13.5. 移位运算
-
左移<<:
-
i << j
-
i中所有的位向左移j个位置, 而右边填入0
-
所有小于int的类型, 移位以int的方式来做, 结果是int
-
x << 1 等价于 x *= 2
-
x << n 等价于 x *= 2^n
-
-
右移>>:
-
i << j
-
i中所有的位向右移j个位置
-
所有小于int的类型, 移位以int的方式来做, 结果是int
-
对于unsigned的类型, 左边填入0
-
对于signed的类型, 左边填入原来的最高位(保持符号位不变)
-
x << 1 等价于 x *= 2
-
x << n 等价于 x *= 2^n
-
-
no zuo no die:
- 移位的位数不要用负数, 这是没有定义的行为
- x << -2 //!!NO!!
- 移位的位数不要用负数, 这是没有定义的行为
#include <stdio.h>
int main(int argc, char const* argv[]) {
//unsigned char a = 0xA5;
//int n;
//scanf("%d", &n);
//printf("a = %hhx\n", a);//a = a5
//printf("a << 2 == %hhx\n", a << 2);//a << 2 == 94
//printf("a = %d\n", a);//a = 165
//printf("a << 2 == %d\n", a << 2);//a << 2 == 660
//printf("a << n == %d\n", a << n);
int a = 0x80000000;
//1000000000...000
//11000000000...000
unsigned int b = 0x80000000;
printf("a = %d\n", a);//a = -2147483648
printf("b = %u\n", b);//b = 2147483648
printf("a >> 1 = %d\n", a >> 1);//a >> 1 = -1073741824
printf("b >> 1 = %u\n", b >> 1);//b >> 1 = 1073741824
return 0;
}
13.6. 位运算例子
-
后面讲的单片机部分不会
-
输出一个数的二进制:
-
跟着视频写的
//10000000000000000000000000000000
//000000000000000000000000000000001
//00000000000000000000000000011110
#include <stdio.h>
int main(int argc, char const* argv[]) {
int num;
scanf("%x", &num);
//向左移31位, 这样最高位就变成了1
unsigned int mask = 1u << 31;
//每次循环让这个1右移1位, 直到mask为0
for (; mask; mask >>= 1) {
//num与mask, 循环遇到num出现1的位时, 输出1, 为0输出0
printf("%d", num & mask ? 1 : 0);
}
printf("\n");
return 0;
}
13.7. 位段
- 听不懂
- 位段:
-
把一个int的若干位组合成一个结构
struct { unsigned int leading : 3; unsigned int FLAG1 : 1; unsigned int FLAG2 : 2; int trailing : 11; };
-
可以直接用位段的成员名称来访问
- 比移位, 与, 或还方便
-
编译器会按排其中的位的排列, 不具有可移植性
-
当所需的位超过一个int时会采用多个int
-
#include <stdio.h>
void prtBin(unsigned int num);
struct U0 {
unsigned int leading : 3;//成员leading后面加上一个冒号和一个3, 意思是成员leading占了3个bit
unsigned int FLAG1 : 1;
unsigned int FLAG2 : 2;
int trailing : 32;
};
int main(int argc, char const* argv[]) {
struct U0 uu;
uu.leading = 2;
uu.FLAG1 = 0;
uu.FLAG2 = 1;
uu.trailing = 0;
printf("sizeof(uu) = %lu\n", sizeof(uu));
prtBin(*(int*)&uu);
return 0;
}
void prtBin(unsigned int num) {
unsigned mask = 1u << 31;
for (; mask; mask >>= 1) {
printf("%d", num & mask ? 1 : 0);
}
printf("\n");
}