hello,my friend。今天我们要学习的是基础IO部分,主要涉及内存和外设之间的数据交互。接下来,就让我们共同探讨这部分内容吧,那我们就开始吧!
本文章重点:
首先,我认为有必要明确一些共识:
总结:文件操作的本质:进程和被打开文件之间的关系。
C语言有文件操作接口,C++有文件操作接口,jave有文件操作接口,python有文件操作接口。但这些接口差别很大。
文件在哪里——>文件在磁盘——>磁盘属于硬件,由操作系统进行管理——>所有人想访问磁盘都绕不开操作系统——>使用操作系统的接口——>提供文件级别的系统调用接口——>吧冉语言的文件操作接口都可以在Linux下运行——>底层接口是一样的,这是变换的接口中不变的东西。
在使用文件之前应该打开文件,使用完之后应该关闭文件
ANSIC规定用fopen来打开文件,用fclose来关闭文件
FILE * fopen ( const char * filename, const char * mode ); //打开文件
int fclose ( FILE * stream ); //关闭文件
注1:当文件打开失败出错时,会返回一个空指针,因此我们一定要在打开文件之后,对文件指针进行有效性检查 注2:对于打开进行更新的文件(包含“+”号的文件),允许输入和输出操作,在写入操作之后的读取操作之前,应刷新(fflush)或重新定位流(fseek,fsetpos,rewind)。流应在读取操作之后的写入操作之前重新定位(fseek、fsetpos、rewind)(只要该操作未到达文件末尾)
实例:
#include<stdio.h>
#include<stdlib.h>
int main()
{
FILE* fp = fopen("hello.c", "w");
if (fp == NULL)
{
perror("fopen fail");
}
//进行文件相关的读写操作。
fclose(fp);
}
运行前:
运行后:
如下是C语言文件操作相关函数
我们知道:在C语言占位符中:%c表示字符,%s表示字符串。上面的以字符C结尾fgetc和fputc分别便是读取和输入一个字符。以字符Sfgets和fputs分别便是读取和输入字符串。
下面我挑重点讲解几个函数:
int fgetc ( FILE * stream );
实例:从data.txt文件中读取一个字符。
#include<stdio.h>
#include<stdlib.h>
int main()
{
FILE* fp=fopen("data.txt","r");
if (fp==nullptr)
{
perror("fopen fail");
exit(1);
}
char ch=fgetc(fp);
printf("%c",ch);
fclose(fp);
fp=nullptr;
return 0;
}
int fputc ( int character, FILE * stream );
实例:创建一个data.txt文件,并写入字符‘a’;
#include<stdio.h>
#include<stdlib.h>
int main()
{
FILE* fp=fopen("data.txt","w");
if (fp==nullptr)
{
perror("fopen fail");
exit(1);
}
fputc('a',fp);
fclose(fp);
fp=nullptr;
return 0;
}
运行结果:
int fputs ( const char * str, FILE * stream );
实例:
将字符串“abcdefg”写入data.txt文件。
#include<stdio.h>
#include<stdlib.h>
int main()
{
FILE* fp=fopen("data.txt","w");
if (fp==nullptr)
{
perror("fopen fail");
exit(1);
}
fputs((char*)"abcdefg",fp);
fclose(fp);
fp=nullptr;
return 0;
}
char * fgets ( char * str, int num, FILE * stream );
实例:从data.txt中读取所有字符。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
FILE* fp=fopen("data.txt","r");
if (fp==nullptr)
{
perror("fopen fail");
exit(1);
}
char ret[10];
memset(ret,0,10);//初始化空间为0;
fgets(ret,6,fp);
printf("%s",ret);
fclose(fp);
fp=nullptr;
return 0;
}
int fprintf ( FILE * stream, const char * format, ... );
例如:
将数字123和字符串“abcdef”写入文件
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
int num=123;
char *arr="abcdef";
FILE *fp=fopen("data.txt","w");
if(fp==nullptr)
{
perror("fopen fail");
}
fprintf(fp,"%d:%s",num,arr);
fclose(fp);
fp=nullptr;
return 0;
}
int fscanf ( FILE * stream, const char * format, ... );
实例:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
FILE *fp=fopen("data.txt","r");
if(fp==nullptr)
{
perror("fopen fail");
}
int num=0;
char arr[10]={0};
fscanf(fp,"%d:%s",&num,arr);
printf("%d:%s\n",num,arr);
fclose(fp);
fp=nullptr;
return 0;
}
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
以二进制的形式从流中读取count个元素,每个元素的大小为size字节,并将它们存储在 ptr 指定的内存块中。
返回成功读取的元素总数。
如果此数字与 count 参数不同,则表示读取时发生读取错误或到达文件末尾。在这两种情况下,都会设置正确的指标,可以分别用 ferror 和 feof 进行检查。
如果size或count为零,则该函数返回零,并且流状态和 ptr 指向的内容保持不变。
例如:读取文件中的字符串“1200”
int main()
{
FILE* fp = fopen("data.txt", "rb");
if (NULL == fp)
{
perror("fopen");
return 1;
}
int str[10] = {0};
int ret = fread(str, sizeof(int), 1, fp);
for (int i = 0; i < 10; i++)
printf("%d ", str[i]);
printf("\nret = %d\n", ret);
fclose(fp);
fp = NULL;
return;
}
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
以二进制的形式将ptr存储的数据写入流中,一共写入count个元素,每个元素的大小为size字节。
返回成功写入的元素总数。
如果此数字与 count 参数不同,则写入错误阻止函数完成。在这种情况下,将为流设置错误指示器(ferror)。
如果size或count为零,则该函数返回零,错误指示器保持不变。
例如:
向文件中写入字符串“abcdef”
#include<stdio.h>
int main()
{
FILE* fp = fopen("data.txt", "wb");
if (NULL == fp)
{
perror("fopen");
return 1;
}
char str[] = "abcdef";
int ret = fwrite(str, sizeof(char), strlen(str), fp);
printf("%d\n", ret);
fclose(fp);
fp = NULL;
return 0;
}
我们在用fopen以写的方式打开一个文件,如果文件不存在,系统会在当前路径下创建该文件,但为什么创建文件是在当前路径下创建呢?操作系统怎么找到当前路径的呢?
运行起该进程,然后查询到pid,在系统proc文件夹下查找。
其中,有两个非常显眼:
来看实例:
#include<stdio.h>
#include<unistd.h>
#include<stdio.h>
int main()
{
chdir("/home/user/exercise");//修改该进程的工作目录。
FILE* fp=fopen("log.txt","w");
while(1)
{
printf("hello world\n");
sleep(1);
}
}
这样,我们就把进程的工作目录更改到了/home/user/exercise下。
运行一下:
我们可以看到:文件就在我们修改后的工作目录下创建好了。
我们除了使用C语言函数或者其他语言函数对文件进行相关的操作,我们也可以调用系统文件操作接口来实现对文件的操作,且系统接口更加接近底层,语言层面的函数都是对系统接口的封装。
C语言的fopen函数底层就是依据open实现的,其为Linux的系统调用,函数原型为:
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
头文件:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
参数
对flag的进一步理解:
flag的中文名称是旗帜(标记位)的意思。这里的采用比特位的数组进行标记,并用位运算符进行运算,得到对文件的操作类型。为了更好的理解,我们来看这段代码:
#include <stdio.h>
#include <stdlib.h>
#define ONE (1 << 0)
#define TOW (1 << 1)
#define THREE (1 << 2)
#define FOUR (1 << 3)
void show(int flag)
{
if (flag & ONE)
printf("ONE\n");
if (flag & TOW)
printf("TOW\n");
if (flag & THREE)
printf("THREE\n");
if (flag & FOUR)
printf("FOUR\n");
}
int main()
{
show(ONE);
printf("--------------------------------------\n");
show(ONE | TOW);
printf("--------------------------------------\n");
show(ONE | TOW | THREE);
printf("--------------------------------------\n");
show(ONE | THREE);
}
我们可以 使用或运算 来做出 不同的行为,同样,open接口的flags参数也是如此使用方式,例如,我们以 使用open模拟fopen函数的 ‘w’ 行为:
include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#define MY_ENY "log.txt"
int main()
{
int n = open("MY_ENY", O_WRONLY | O_CREAT | O_TRUNC, 0666);
assert(n > -1);
// 进行相关的写操作。
close(n);
return 0;
}
我们真的使用open函数模仿除了fopen的‘w’行为,但是,仔细观察:我们发现创建的文件权限列表为0664,但是我们在open参数列表中传入的是:0666。这其中是权限掩码的原因(umask)。我们系统设定的权限掩码为0002,真正的权限列表为:umask&mode。当然,这个掩码也是可以修改的:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
int main()
{
umask(0);//修改权限掩码为0000;
int n = open("MY_ENY", O_WRONLY | O_CREAT | O_TRUNC, 0666);
assert(n > -1);
// 进行相关的写操作。
close(n);
return 0;
}
结果不出所料:
函数原型
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
参数:
实例:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include<string.h>
int main()
{
int cnt=5;
umask(0);//修改权限掩码为0000;
int n = open("MY_ENY", O_WRONLY | O_CREAT , 0666);
assert(n > -1);
char *arr="abcdefg";
char outBUffer[1024]={0};
while(cnt--)
{
sprintf(outBUffer,"%s:%d\n",arr,cnt);
ssize_t m=write(n,outBUffer,strlen(outBUffer));
// 进行相关的写操作。
}
close(n);
return 0;
}
运行一下:
结果不出意料。
接着,我们修改一下代码:修改一下要写入的数据。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include<string.h>
int main()
{
int cnt=5;
umask(0);//修改权限掩码为0000;
int n = open("MY_ENY", O_WRONLY | O_CREAT ,0666);
assert(n > -1);
char *arr="www";
char outBUffer[1024]={0};
while(cnt--)
{
sprintf(outBUffer,"%s:%d\n",arr,cnt);
ssize_t m=write(n,outBUffer,strlen(outBUffer));
// 进行相关的写操作。
}
close(n);
return 0;
}
运行一下:我们发现结果并不是我们预料的把之前的内容清空,然后再重新写入。
这是因为我们少传入了一个flag选项O_TRUNC,这个选项的作用就是在写入之前,清空文件里边的所有内容。
我们传入这个flag选项观察一下:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include<string.h>
int main()
{
int cnt=5;
umask(0);//修改权限掩码为0000;
int n = open("MY_ENY", O_WRONLY | O_CREAT|O_TRUNC ,0666);
assert(n > -1);
char *arr="www";
char outBUffer[1024]={0};
while(cnt--)
{
sprintf(outBUffer,"%s:%d\n",arr,cnt);
ssize_t m=write(n,outBUffer,strlen(outBUffer));
// 进行相关的写操作。
}
close(n);
return 0;
}
如此,一切都显得合理合规了。
read函数是Linux下的一个系统调用接口,C语言的fread函数的底层就是read。作用为从一个特定的文件流中读取内容。
函数原型:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
参数:
返回值
如果读取成功的话,返回读取到的元素的个数。
实例:
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
int main()
{
int cnt=5;
//umask(0);//修改权限掩码为0000;
//int n = open("MY_ENY", O_WRONLY | O_CREAT|O_TRUNC ,0666);
//assert(n > -1);
int n=open("MY_ENY",O_RDONLY);//读文件时,文件一定存在。
char arr[1024]={0};//先定义一个缓冲区,用于存放读取到的内容。
ssize_t m=read(n,arr,sizeof(arr));
assert(m>0);
arr[m]='\0';//使用C语言的方式对内容进行打印,字符串以'\0'结尾,\0=0=nullptr;:wq
printf("%s",arr);
close(n);
return 0;
}
运行一下:
结果就出现了。
close函数也是Linux下的一个系统调用接口,C语言的fclose底层就是close。
参数
fd:就是调用open时的返回值,本质是第一个文件描述符。
c语言库函数底层调用操作系统接口,然后系统调用接口把结果返回给C语言库函数。
写到最后,因作者水平有限,文中难免会有错误,请各位指正!!