相关文章:c语言:文件操作
目录
1.理解"文件"
1.1文件操作的归类认知
对于0KB的空文件是占用磁盘空间的。
文件是文件属性(元数据)+文件内容的集合(文件 = 属性(如fstat)(元数据) + 内容(如fread/fwrite))。
所有的文件操作本质是文件内容操作和文件属性操作。
思考
操作文件之前需要打开文件(fopen)
1.打开文件的本质是什么? 是把文件加载到内存(包含部分属性和内容)
2.为什么需要先打开文件? 由冯诺依曼体系结构规定
3.由谁打开? 代码示例:FILE *fp = fopen(); fwrite(fp......); 当我们输入这些代码时文件并没有被打开,而是通过编译,转换为exe文件,并且代码运行起来时由进程打开了文件!我们所说的malloc是动态内存分配,就是指在程序运行期间由内存分配大小。
所以学习对文件的操作本质是学习进程和文件的关系
1.2侠义理解
文件在磁盘中,磁盘是永久性存储介质,因此文件在磁盘上的存储是永久性的。
磁盘是外设(既是输出设备也是输入设备)
磁盘上的文件,本质是对文件的所有操作,都是对外设的输入和输出,简称IO(Input,Output),其中输入输出的含义是站在内存的视角定义的。
1.3广义理解
Linux下一切皆文件(键盘、显示器、网卡、磁盘......这些都是抽象化的过程)
1.4系统角度
对于文件的操作本质是进程对文件的操作
磁盘的管理者是操作系统
文件的读写本质不是通过C语言/C++的库函数来操作的(这些库函数只是为用户提供方便),而是通过文件相关的系统调用接口来实现的。
拓展
磁盘文件分为被进程打开的文件和没有被进程打开的文件。
其中内存中的打开文件有关于进程和文件的关系上,磁盘上没有被打开的文件在文件系统上。
磁盘是硬件,访问文件就是在访问磁盘。访问硬件需要操作系统和磁盘的驱动,操作系统必须提供系统调用来访问文件(本质就是访问硬盘),而我们在使用C语言时之所以没有使用系统调用的感受是因为C语言帮我们封装了系统调用!!!
2.C文件操作
2.1打开log.txt文件
myfile.c:
int main()
{
FILE *fp = fopen("log.txt", "w");
if(NULL == fp)
{
perror("fopen");
return 0;
}
fclose(fp);
return 0;
}

其中:
cwd:指向当前进程运行目录的一个符号链接。
exe:指向启动当前进程的可执行文件(完整路径)的符号链接。
打开文件,本质是进程打开,所以,进程知道自己在哪里,即便文件不带路径,进程也知道。由此,OS就能知道要创建的文件放在哪里了。
2.1.1fopen

其中:
FILE *:句柄
char *path:此处可以带路径或者不带路径。类似于上述示例写的myfile.c,当他运行起来时就已经有了当前路径:CWD。如果带路径:/home/xxx/xxx.txt(此为明确路径),如果不带路径:"log.txt"(默认路径),C语言会将此拼接为CWD/"log.txt"。不过打开一个文件,它必须带路径。因为它需要在系统中找到文件,而在Linux系统中,找文件必须带路径!

const char *mode:打开文件目的是访问文件,而常用方式见下:

2.1.2fclose
关闭时将文件全部flush到它的指针fp

2.2对文件做写入
它们功能上不重叠
2.2.1fwrite

注意:在传入具体大小时不考虑\0,因为它是以文件的形式传入的,文件不关心是否有无\0
int main()
{
FILE *fp = fopen("log.txt", "w");
if(NULL == fp)
{
perror("fopen");
return 0;
}
const char *message = "hello world\n";
int cnt = 0;
while(cnt < 10)
{
fwrite(message, 1, strlen(message), fp);
//fputs(message, fp);
//fprintf(fp, "hello world:%d\n", cnt);
cnt++;
}
fclose(fp);
return 0;
}
2.2.2fputs
像指定的文件流中写入字符串

int main()
{
FILE *fp = fopen("log.txt", "w");
if(NULL == fp)
{
perror("fopen");
return 0;
}
const char *message = "hello world\n";
int cnt = 0;
while(cnt < 10)
{
fputs(message, fp);
cnt++;
}
fclose(fp);
return 0;
}
2.2.3fprintf
以向显示器格式化输出一样,向指定文件流做写入

int main()
{
FILE *fp = fopen("log.txt", "w");
if(NULL == fp)
{
perror("fopen");
return 0;
}
int cnt = 0;
while(cnt < 10)
{
fprintf(fp, "hello world:%d\n", cnt);
cnt++;
}
fclose(fp);
return 0;
}
2.3fopen各个模式以及拓展
2.3.1w模式
现在,在log.txt中输入字符,再通过fputs向log.txt写入:


运行,结果原字符都被覆盖:

原因是因为,在C语言中,w模式在每次写入之前会将目标文件清空,并从头写入,如果目标文件不存在,它能新建:

2.3.2输出重定向
代码: >log.txt
符号 > ,表示将log.txt打开一次再关闭一次,而它打开文件时运行的就是w
2.3.3getcwd
获取当前进程的"当前工作目录"的绝对路径

char pwd[64];
getcwd(pwd,sizeof(pwd));
printf("cwd: %s\n", pwd);
2.3.4chdir
切换当前进程的工作目录。

与2.3.3相结合:
int main()
{
chdir("/home/xxx404/linux-learning/test2_2");
char pwd[64];
getcwd(pwd,sizeof(pwd));
printf("cwd: %s\n", pwd);
FILE *fp = fopen("log.txt", "w");
if(NULL == fp)
{
perror("fopen");
return 0;
}
fclose(fp);
return 0;
}
此时,在相关路径下就有了log.txt文件:


结论:Linux 中用相对路径打开文件时,文件的实际位置由进程的 "当前工作目录(CWD)" 决定,而非程序本身的存放目录
2.3.5a模式
全称:append,追加,每次写从结尾写。

FILE *fp = fopen("log.txt", "a");
if(NULL == fp)
{
perror("fopen");
return 0;
}
const char *message = "hello world\n";
fputs(message, fp);
现象:

2.3.6追加重定向
>>log.txt,其写入方式和a模式相同
2.4读
2.4.1fread

2.4.2fgets

2.4.3fscanf

2.4.4feof
用于判断文件指针是否已经超越过文件末尾。

代码:
FILE *fp = fopen("log.txt", "r");
if(NULL == fp)
{
perror("fopen");
return 0;
}
char inbuffer[1024];
while(1)
{
if(!fgets(inbuffer,sizeof(inbuffer), fp))
{
break;
}
printf("file: %s", inbuffer);
}
运行结果:

2.5对读和写的理解
当我们用cat获取文件内容时,如果是这样:
abcd
1234
那么在OS视角为:abcd\n1234
我们可以将文件的全部内容想象为一个一维字符数组char content[],而要想获取系统读写文件到哪一个位置,就需要ftell函数:

代码:
FILE *fp = fopen("log.txt", "r");
if(NULL == fp)
{
perror("fopen");
return 0;
}
char inbuffer[1024];
while(1)
{
long pos = ftell(fp);
printf("pos: %ld\n", pos);
int ch = fgetc(fp);
if(ch == EOF)
{
break;
}
printf("%c\n", ch);
printf("file: %s", inbuffer);
}
结果:

3.系统级别的文件操作
3.1学习系统调用
open

两个参数的open适用于文件存在,三个参数的适用于文件不存在,其中mode表示创建文件的权限。
path:同fopen,表示路径
flags:表示打开文件的模式
常见模式,这里表示形式是宏,本质是"位掩码",每个宏对应一个唯一的二进制位,

模式由int类型构成,也就是存在32个比特位,那么就可以通过比特位传递标志位完成高效的多标志传递设计。
位图传参
通过demo链接宏和比特位传递标志位
#define ONE (1<<0) // 1
#define TWO (1<<1) // 1
#define THREE (1<<2) // 4
#define FOUR (1<<3) // 8
#define FIVE (1<<4) // 16
void Print(int flags)
{
if(flags & ONE)
printf("ONE\n");
if(flags & TWO)
printf("TWO\n");
if(flags & THREE)
printf("THREE\n");
if(flags & FOUR)
printf("FOUR\n");
if(flags & FIVE)
printf("FIVE\n");
}
int main()
{
Print(ONE);
printf("\n");
Print(TWO);
printf("\n");
Print(ONE | TWO);
printf("\n");
Print(ONE | TWO | THREE);
printf("\n");
Print(ONE | TWO | THREE | FOUR);
printf("\n");
Print(TWO | THREE | FOUR | FIVE);
}
运行结果:

说明我们可以采用一个整数的32位比特位,用不同比特位的0,1表示是否,不同的比特位位置代表不同的选项,特定位置的0,1可以传递不同标志位,也就是说,可以通过一个整数去传递32个标志位。
所以上述模式的宏,只有一个比特位是1的整数值。要是想传递标志位时,可以将其组合传递给flags,从而得到不同效果,这就是位图传参。
文件描述符fd
如果打开成功,会有文件描述符file descriptor(fd),如果打开失败,会有错误码

fd是一个大于等于0的整数,所以,我们将open前的int返回值称为文件描述符,它可以用来标识一个被打开的文件,它也是一个句柄!!!(凡是标识资源唯一性的都可以被称为句柄)

3.2代码
3.2.1产生与fopen中w模式相同的效果
标志位(多个宏按位或):
O_CREAT:如果文件不存在,则创建该文件(必须配合第三个参数指定权限);
O_WRONLY:以只写模式打开文件(无法读取);
O_TRUNC:如果文件已存在且是普通文件,清空其所有内容(长度截断为 0)
此时,系统调用的方式与fopen产生的效果相同。

int main()
{
umask(0); // 只改变当前进程的umask
int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);
if(fd < 0)
{
perror("open");
return 1;
}
//const char *message = "1234567890abcdefg\n";
const char *message = "ccc";
write(fd, message, strlen(message));
close(fd);
return 0;
}
3.2.2产生与fopen中a模式相同的效果
代码:
int main()
{
umask(0);
int fd = open("log.txt", O_CREAT | O_WRONLY | O_APPEND, 0666);
if(fd < 0)
{
perror("open");
return 1;
}
const char *msg = "hello world\n";
int cnt = 10;
while(cnt--)
{
write(fd, msg, strlen(msg));
}
close(fd);
return 0;
}
本章完。