嵌入式之路,贵在日常点滴

---阿杰在线送代码

目录

一、系统调用

1.1 什么是系统调用

1.2 什么是库函数

1.3 将hello写入到文件1.txt流程

1.4 为什么要有缓冲区(补充)

1.5 内核缓冲区和C标准缓冲区的区别

二、文件编程常用API

1、文件打开 open()

1.1 函数原型

1.2 参数描述

1.3代码举例

附:关于mode

创建文件 creat()

2、文件写入  write()

2.1 函数原型

2.2 参数使用

2.3  代码举例

3、读取文件 read()

3.1 函数原型

3.2 参数使用

3.3 代码举例

4、文件光标移动 lseek()

4.1 函数原型

4.2 参数使用

4.3代码举例

4.4 三种使用举例

5、文件关闭 close()

5.1 函数原型

5.2 参数使用

5.3 代码举例

三、文件操作小应用

实现cp指令

修改配置文件

四、研究往文件里面写入整型数和结构体(增加认知)

整型数

结构体

五、Linux文件操作原理简述

文件描述符:

文件操作原理:

六、 open与fopen的区别

1. 来源

2. 移植性

3. 适用范围

4. 文件IO层次

5. 缓冲

七、用ANSIC标准中的C库函数进行文件编程

fopen

fread

fwrite

fseek

用fopen、fread、fwrite、fseek来给一个文件写入结构体

fputc

fgetc

feof


一、系统调用

1.1 什么是系统调用

系统调用函数属于操作系统的一部分,是为了提供给用户进行操作的接口(API函数),使得用户态运行的进程与硬件设备(如CPU、磁盘、打印机、显示器)等进行交互。

  • 例如常见的系统调用 等等write read open …

1.2 什么是库函数

  1. 库函数可分为两类,一类是C语言标准库函数,一类是编译器特定的库函数。
  2. 库函数可以理解为是对系统调用函数的一层封装。尽管系统函数执行效率是比较高效而精简的,但有时我们需要对获取的信息进行更复杂的处理,或更人性化的需要,我们把这些处理过程封装成一个函数,再将许多这类的函数放在一个文件(库)一般放在 .lib文件。最后再供程序员使用。
  • #include<stdio.h>使用的时候包含头文件就可以使用其中的库函数了
  • 例如常见的库函数printf fwrite fread fopen…等等

1.3 将hello写入到文件1.txt流程

  1. 首先fopen打开文件 fwrite参数附上要写入的内容
  2. 文本内容来到C标准缓冲区
  3. 如果满足条件就刷新C标准缓冲区,调用系统函数write进行写(补充:满了就会自动刷新)
  4. write却只是把要写入的内容写到内核缓冲区
  5. 如果内核缓冲区满足条件就刷新内核缓冲区,系统调用sys_write将缓冲区内容写入到磁盘(补充:有个进程会定时刷新内核缓冲区)
  6. 此时有进程读取1.txt文件内容,发现内核缓冲区就有这个文件内容,就直接从内核缓冲区

1.4 为什么要有缓冲区(补充)

定义:缓冲区就是内存里的一块区域,把数据先存内存里,然后一次性写入硬盘中的文件,类似于数据库的批量操作。
好处:减少对硬盘的直接操作,硬盘的执行速度为毫秒级别,内存为纳秒级别。在硬盘直接操作读写效率太低。

1.5 内核缓冲区和C标准缓冲区的区别

C语言标准库函数fopen()每打开一个文件时候,其都会对应一个单独一个缓冲区而内核缓冲区是公用的

二、文件编程常用API

1、文件打开 open()

1.1 函数原型

  • .int open(const char *pathname, int flags);
  • .int open(const char *pathname, int flags, mode_t mode);

1.2 参数描述

  • .pathname :文件路径+文件名(若不包含路径,则默认为当前路径)
  • .flats : (1) O_RDONLY 只读打开         O_WRONLY 只写打开         O_RDWR  可读可写打开

(2)当我们附带了权限后,打开的文件就只能按照这种权限来操作。

以上这三个常数中应当只指定一 个。下列常数是可选择的:

O_CREAT 若文件不存在则创建它。使用此选项时,需要同时说明第三个参数mode,用其说明该新文件的存取许可权限。

O_EXCL 如果同时指定了OCREAT,而文件已经存在,则出错。

O_APPEND 每次写时都加到文件的尾端。

O_TRUNC 属性去打开文件时,如果这个文件中本来是有内容的,而且为只读或只写成功打开,则将其长度截短为0。

当然也有组合使用(1.O_RDONLY |O_CREAT 只读如果不存在则创建)(O_WRONLY |O_CREAT 只写如果不存在则创建)(O_WRONLY | O_APPEND 文件存在则追加写入)

  • .mode:一定是在flags中使用了O_CREAT标志,mode记录待创建的文件的访问权限
  • . 返回值: 失败返回-1,成功返回 整形(fd:文件描述符)

fd>0,文件打开成功且fd为文件标识符 fd<0,文件打开失败*/

1.3代码举例

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "stdio.h"int main()
{int fd;//file descriptionfd = open("./file1",O_RDWR);printf("fd = %d\n",fd);return 0;
}

运行结果:

下面对flags(2)这类使用(|)进行附加使用的参数进行额外说明:

(1)O_CREAT:文件若不存在则创建

注意:需要额外说明文件操作权限参数mode

include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "stdio.h"int main()
{int fd;//file description//尝试打开当前路径下的文件file1.cfd = open("./file1",O_RDWR);//打开失败了if(fd == -1){printf("open file1 failed,fd = %d\n",fd);//尝试以可读可写的方式创建并打开文件fd = open("./file1",O_RDWR|O_CREAT,0600);if(fd > 0){printf("create file1,fd = %d\n",fd);}}return 0;
}

运行结果:可以看到已经成功创建了file1.c

(2)O_EXCL:如果同时指定了OCREAT,而文件已经存在,则出错(返回值为-1)

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "stdio.h"int main()
{int fd;//file descriptionfd = open("./file1",O_RDWR|O_CREAT|O_EXCL,0600);if(fd == -1){printf("file1 cunzai\n");}return 0;
}

运行结果:

  (3)O_APPEND:每次写时都加到文件的尾端(另起一行)

如果不使用这个参数,因为文件打开后光标是位于文件头的,写入数据会把原来的数据按长度覆盖。(本质上就是光标的问题)

原file1内容

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "stdio.h"
#include <unistd.h>
#include "string.h"int main()
{int fd;//file descriptionchar *buf = "a jie hen shuai";fd = open("./file1",O_RDWR|O_APPEND);printf("open suscess:fd = %d\n",fd);int n_write = write(fd,buf,strlen(buf));if(n_write != -1){printf("write %d byte to file\n",n_write);}close(fd);return 0;
}

运行后

若不加O_APPEND

(4)O_TRUNC清空原内容后写入

在每次打开文件写入之前,先把原有的内容清空后写入

原file1内容

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "stdio.h"
#include <unistd.h>
#include "string.h"int main()
{int fd;//file descriptionchar *buf = "a jie hen shuai";fd = open("./file1",O_RDWR|O_TRUNC);printf("open suscess:fd = %d\n",fd);int n_write = write(fd,buf,strlen(buf));if(n_write != -1){printf("write %d byte to file\n",n_write);}close(fd);return 0;
}

运行后:

附:关于mode

1、可读        r         4

2、可写        w        2

3、执行         x        1

创建文件 creat()

创建文件的另一种方法(不可用于打开)

int creat(const char *pathname, mode_t mode);/** mode **
S_IRUSR 可读            宏:4
S_IWUSR 可写            宏:2
S_IXUSR 可执行          宏:1
S_IRWXU 可读可写可执行   宏:7 */

简单使用示例:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main()
{//可读可写int fd = creat("./file1.c",S_IRUSR|S_IWUSR);if(fd > 0){printf("文件创建成功\n");}else{printf("同名文件已经存在\n");}close(fd);return 0;
}

2、文件写入  write()

2.1 函数原型

  • int write(int fd, void *buf, int count);

2.2 参数使用

  • fd   表示改文件的文件描述符 ,open的返回值
  • buf  写入的文本内容
  • count 写入的数据长度【使用srtlen非sizeof】
  • 返回值  成功返回写入的字节数 失败返回-1

2.3  代码举例

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "stdio.h"
#include <unistd.h>
#include "string.h"int main()
{int fd;//file descriptionchar *buf = "a jie hen shuai!";fd = open("./file1",O_RDWR);if(fd == -1){printf("open file1 failed,fd = %d\n",fd);fd = open("./file1",O_RDWR|O_CREAT,0600);if(fd > 0){printf("create file1,fd = %d\n",fd);}}printf("create file1,fd = %d\n",fd);write(fd,buf,strlen(buf));close(fd);//关闭文件return 0;
}

运行结果:

3、读取文件 read()

3.1 函数原型

  • int read(int fd, void *buf, size_t count);

3.2 参数使用

  • .fd 表示改文件的文件描述符,open的返回值
  • .buf 指缓冲区,读取的数据存放位置
  • .count 传入缓冲区的字节大小【使用sizeof非strlen】
  • .返回值 成功返回读出的字节数 失败返回-1
  • (值得注意的是也可能是以非阻塞的方式读一个设备文件和网络文件,后面网络编程常见)

3.3 代码举例

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "stdio.h"
#include <unistd.h>
#include "string.h"
#include <stdlib.h>int main()
{int fd;//file descriptionchar *buf = "a jie hen shuai!";fd = open("./file1",O_RDWR);if(fd == -1){printf("open file1 failed,fd = %d\n",fd);fd = open("./file1",O_RDWR|O_CREAT,0600);if(fd > 0){printf("create file1,fd = %d\n",fd);}}printf("create file1,fd = %d\n",fd);int n_write = write(fd,buf,strlen(buf));if(n_write != -1){printf("write %d byte to file\n",n_write);}close(fd);//关闭文件fd = open("./file1",O_RDWR);//重新打开char *readBuf;readBuf = (char *)malloc(sizeof(char)*n_write + 1);int n_read = read(fd,readBuf,n_write);printf("read %d,contest:%s\n",n_read,readBuf);close(fd);return 0;
}                       

运行结果:

read最需要注意的就是光标的位置,尤其是在write操作后,光标已经到达文件尾部,直接read,就会读个寂寞。

解决办法是重新打开文件(不建议)使光标回到文件头。或者使用后面所提到的lseek操作光标。

4、文件光标移动 lseek()

4.1 函数原型

  • .int lseek(int fd, off_t offset, int whence);

4.2 参数使用

  • .fd   表示改文件的文件描述符,open的返回值
  • .offset  表示偏移量
  • .whence 指出偏移的方式

   whence参数补充说明
  SEEK_SET:偏移到文件头+ 设置的偏移量
  SEEK_CUR:偏移到当前位置+设置的偏移量
  SEEK_END:偏移到文件尾置+设置的偏移量

4.3代码举例

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "stdio.h"
#include <unistd.h>
#include "string.h"
#include <stdlib.h>int main()
{int fd;//file descriptionchar *buf = "a jie hen shuai!";fd = open("./file1",O_RDWR);if(fd == -1){printf("open file1 failed,fd = %d\n",fd);fd = open("./file1",O_RDWR|O_CREAT,0600);if(fd > 0){printf("create file1,fd = %d\n",fd);}}printf("create file1,fd = %d\n",fd);int n_write = write(fd,buf,strlen(buf));if(n_write != -1){printf("write %d byte to file\n",n_write);}//      close(fd);
//      fd = open("./file1",O_RDWR);char *readBuf;readBuf = (char *)malloc(sizeof(char)*n_write + 1);//使得光标移动到文件的开始位置:lseek(fd,0,SEEK_SET);//将光标移动至首int n_read = read(fd,readBuf,n_write);printf("read %d,contest:%s\n",n_read,readBuf);close(fd);return 0;
}

运行结果:

4.4 三种使用举例

(1) 返回当前的偏移量

int fd,ret;
fd=open("hello1.txt",O_RDWR);
ret=lseek(fd,0,SEEK_CUR);
printf("%d\n",ret);

(2)返回文件大小

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "stdio.h"
#include <unistd.h>
#include "string.h"
#include <stdlib.h>int main()
{int fd;//file descriptionchar *buf = "a jie hen shuai!";fd = open("./file1",O_RDWR);int filesize = lseek(fd,0,SEEK_END);printf("file's size is :%d\n",filesize);close(fd);return 0;
}

运行结果:

(3)☆扩充文件大小   
特别注意扩充文件大小后 需要写入内容 否则扩充不生效

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd,ret;
char a[]="JMU WELCOME";
fd=open("hello1.txt",O_RDWR|O_CREAT,0777);
ret=lseek(fd,1000,SEEK_END);
write(fd,a,strlen(a));
printf("%d\n",ret);
return 0;
}

5、文件关闭 close()

5.1 函数原型

  • int close(int fd);

5.2 参数使用

5.3 代码举例

 int  fd;fd=open("tmp.txt",O_RDONLY);close(fd);

三、文件操作小应用

实现cp指令

cp src.c(源文件) des.c(目标文件)

1、 C语言参数  ./a.out   argc(3个参数)   argv(指针数组)

#include "stdio.h"int main(int argc,char **argv)
{printf("total params:%d\n",argc);printf("No.1 params :%s\n",argv[0]);printf("No.2 params :%s\n",argv[1]);printf("No.3 params :%s\n",argv[2]);return 0;
}/** 参数
argc:   参数个数
**argv:二级指针,数组的指针,即这个指针里的每一项都是一个数组(字符串)
**/


argc在C语言中表示运行程序时传递给main()函数的命令行参数个数

argv在C语言中表示运行程序时用来存放命令行字符串参数的指针数组

argc、argv用命令行编译程序时有用。主函数main中变量(int argc,char *argv[ ])的含义如下:

1、main(int argc, char *argv[ ], char **env)是UNIX和Linux中的标准写法。

2、argc: 整数,用来统计你运行程序时送给main函数的命令行参数的个数

3、* argv[ ]: 指针数组,用来存放指向你的字符串参数的指针,每一个元素指向一个参数。其中argv[0] 指向程序运行的全路径名,argv[1] 指向在DOS命令行中执行程序名后的第一个字符串,argv[2] 指向执行程序名后的第二个字符串,argv[argc]为NULL。

4、argc、argv是在main( )函数之前被赋值的,编译器生成的可执行文件,main( )不是真正的入口点,而是一个标准的函数,这个函数名与具体的操作系统有关。


2、编程思路:​​​​​​​

  • (1)打开src源文件(要被复制的文件)
  • (2)把源文件的内容读入buf
  • (3)创建目标文件
  • (4)把buf内容写入目标文件
  • (5)关闭源文件与目标文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "stdio.h"
#include <unistd.h>
#include "string.h"
#include <stdlib.h>int main(int argc,char **argv)
{int fdSrc;//file descriptionint fdDes;char *readBuf = NULL;if(argc != 3){    //判断是否为三个参数printf("pararm error\n");exit(-1);}//(1)打开src源文件(要被复制的文件)fdSrc = open(argv[1],O_RDWR); //打开src.cint size = lseek(fdSrc,0,SEEK_END);//计算存储fdSrc文件的字节大小lseek(fdSrc,0,SEEK_SET);//将光标移到开头readBuf = (char *)malloc(sizeof(char)*size + 8);//(2)把源文件的内容读入bufint n_read = read(fdSrc,readBuf,size);//读src到buf//(3)创建目标文件,如果已经存在则覆盖fdDes = open(argv[2],O_RDWR|O_CREAT|O_TRUNC,0600);//打开/创建des.c //(4)把buf内容写入目标文件int n_write = write(fdDes,readBuf,strlen(readBuf));//将buf写入到des.c//(5)关闭源文件与目标文件close(fdSrc);close(fdDes);return 0;
}

运行结果:

fdDes = open(argv[2],O_RDWR|O_CREAT|O_TRUNC,0600);//打开/创建des.c 
你是否有想过这里为什么要加上这两个额外说明吗

O_CREAT:文件若不存在则创建
O_TRUNC:在每次打开文件写入之前,先把原有的内容清空后写入(否则,如果new的文件已经存在且字节数比较多时,仅仅是覆盖数据而已,多出来的字节数还是原先new的内容)

修改配置文件

现有某配置文件如config,要求利用文件编程把LENG的值修改为5.

SPEED=3
LENG=3
SCORE=9
LEVEL=5

简单示例:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "stdio.h"
#include <unistd.h>
#include "string.h"
#include <stdlib.h>int main(int argc,char **argv)
{int fdSrc;//file descriptionchar *readBuf = NULL;if(argc != 2){printf("pararm error\n");exit(-1);}fdSrc = open(argv[1],O_RDWR);int size = lseek(fdSrc,0,SEEK_END);//ji suan chu fdSrcwenjian de zi jie da xiaolseek(fdSrc,0,SEEK_SET);readBuf = (char *)malloc(sizeof(char)*size + 8);int n_read = read(fdSrc,readBuf,size);char *p = strstr(readBuf,"LENG=");if(p==NULL){printf("not found\n");exit(-1);}p = p+strlen("LENG=");*p = '5';lseek(fdSrc,0,SEEK_SET);int n_write = write(fdSrc,readBuf,strlen(readBuf));close(fdSrc);return 0;
}

运行结果:

SPEED=3
LENG=5
SCORE=9
LEVEL=5

四、研究往文件里面写入整型数和结构体(增加认知)

为什么会有这一研究?

如果按照前面进行编写代码 写入文件API 和 读取文件API 操作的都是字符串,直接写入整型数编译会成功,但结果会起冲突。

非要写入整型数呢?让我们先来看看两个函数的原型

ssize_t write(int fd, const void *buf, size_t count);

ssize_t read(int fd, void *buf, size_t count); 
误区:const void *buf 一定代表着字符串,事实上,并非如此,它也可以代表一个指针 即地址

直接上代码

整型数

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "stdio.h"
#include <unistd.h>
#include "string.h"
#include <stdlib.h>int main(int argc,char **argv)
{int fd;//file descriptionint data = 100;int data2 = 0;fd = open("./file1",O_RDWR);int n_write = write(fd,&data,sizeof(int));lseek(fd,0,SEEK_SET);//将光标回到文件内容开头,方便后续读取等操作int n_read = read(fd,&data2,sizeof(int));printf("read %d\n",data2);close(fd);return 0;
}

运行结果:

看完结果,是不是认为这样这样操作也错误了呢

其实不然,结果并没有错误,并不影响程序的写入和读取操作,只不过人眼看起来有点不舒服,ASCII解锁出来并不是我们想象中的样子。(思想:关键是程序读取没错误)

结构体

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "stdio.h"
#include <unistd.h>
#include "string.h"
#include <stdlib.h>struct Test
{int a;char c;
};int main(int argc,char **argv)
{int fd;//file descriptionstruct Test data = {100,'a'};struct Test data2;fd = open("./file1",O_RDWR);int n_write = write(fd,&data,sizeof(struct Test));lseek(fd,0,SEEK_SET);int n_read = read(fd,&data2,sizeof(struct Test));printf("read %d,%c\n",data2.a,data2.c);close(fd);return 0;
}

甚至结构体数组

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "stdio.h"
#include <unistd.h>
#include "string.h"
#include <stdlib.h>struct Test
{int a;char c;
};int main(int argc,char **argv)
{int fd;//file descriptionstruct Test data[2] ={{100,'a'},{101,'b'}};struct Test data2[2];fd = open("./file1",O_RDWR);int n_write = write(fd,&data,sizeof(struct Test)*2);lseek(fd,0,SEEK_SET);int n_read = read(fd,&data2,sizeof(struct Test)*2);printf("read %d,%c\n",data2[0].a,data2[0].c);printf("read %d,%c\n",data2[1].a,data2[1].c);close(fd);return 0;
}

运行结果(结果并没有错误)

五、Linux文件操作原理简述

文件描述符:

1.对于内核而言,所有打开文件都由文件描述符引用。文件描述符是一个非负整数。当打开一个现存文件或者创建一个新文件时,内核向进程返回一个文件描述符。当读写一个文件时。用open和creat返回的文件描述符标识该文件,将其作为参数传递给read和write

按照惯例,UNIX shell使用文件描述符0与进程的标准输入相结合,文件描述符1与标准输出相结合。STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO这几个宏代替了0、1、2这几个数。

在Linux下,对文件的操作都是通过文件描述符来进行的。linux进程默认会打开三个文件描述符,分别是

stdin 标准输入 对应设备:键盘

1 stdout 标准输出 对应设备 :显示器

2 stderror 标准错误 对应设备:显示器

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "stdio.h"
#include <unistd.h>
#include "string.h"
#include <stdlib.h>int main(int argc,char **argv)
{int fd;//file descriptionchar readBuf[128];int n_read = read(0,readBuf,5);int n_write = write(1,readBuf,strlen(readBuf));printf("\ndone\n");return 0;
}

运行结果:

2.文件描述符,这个数字在一个进程中表示一个特定含义,当我们open一个文件时,操作系统在内存中构建了一些数据结构来表示这个动态文件,然后返回给应用程序一个数字作为文件描述符,这个数字就和我们内存中维护的这个动态文件的这些数据结构绑定上了,以后我们应用程序如果要操作这个动态文件,只需要用这个文件描述符区分。

3.文件描述符的作用域就是当前进程,出了这个进程文件描述符就没有意义了。

open函数打开文件,打开成功返回一个文件描述符,打开失败,返回-1.

文件操作原理:

1.在linux中要操作一个文件,一般是先open打开一个文件,得到文件描述符,然后对文件进行读写操作(或其他操作),最后是close关闭文件即可。

2.我们对文件进行操作时,一定要先打开文件,打开成功之后才能操作,如果打开失败,就不用进行后边的操作了,最后读写完成后,一定要关闭文件,否则会造成文件损坏

3.文件平时是存放在块设备中的文件系统文件中的,我们把这种文件叫静态文件,当我们去open打开一个文件时,linux内核做到操作包括:内核在进程中建立一个打开文件的数据结构,记录下我们打开的这个文件;内核在内存中申请一段内存,并且将静态文件的内容从块设备中读取到内核中特定地址管理存放(叫动态文件)。

静态文件:存放于磁盘,未被打开的文件

动态文件:当使用open后,在linux内核会产生一个结构体来记录文件的信息,例如fd,buf,信息节点.此时的read,write都是对动态文件进行操作,当close时,才把缓存区所有的数据写回磁盘中。

4.打开文件以后,以后对这个文件的读写操作,都是针对内存中的这一份动态文件的,而不是针对静态文件的。当然我们对动态文件进行读写以后,此时内存中动态文件和块设备文件中的静态文件就不同步了,当我们close关闭动态文件时,close内部内核将内存中的动态文件的内容去更新(同步)块设备中的静态文件。

5.为什么这么设计,不直接对块设备直接操作。

块设备本身读写非常不灵活,是按块读写的,而内存是按字节单位操作的,而且可以随机操作,很灵活。

六、 open与fopen的区别

对于这两个名字很类似的函数,对于很多初学者来说,不容易搞清楚它们有什么不同,只知道按照函数用法使用。如果能很好的区分两者,相信大家对于C语言和UNIX系统(包括LINUX)有更深入的了解。

在网上查找了一些资料,但是感觉不够全面,一些答案只是从某个角度阐述,所以让人觉得,这个也对,那个也对。但到底谁的表述更正确呢?其实都是对的,只是解释的视角不同罢了。下面结合个人的理解做一些梳理。

1. 来源

从来源的角度看,两者能很好的区分开,这也是两者最显而易见的区别:

  • open 是UNIX系统调用函数(包括LINUX等),返回的是文件描述符(File Descriptor),它是文件在文件描述符表里的索引。
  • fopen 是ANSIC标准中的C语言库函数,在不同的系统中应该调用不同的内核api。返回的是一个指向文件结构的指针。 

    PS:从来源来看,两者是有千丝万缕的联系的,毕竟C语言的库函数还是需要调用系统API实现的。

2. 移植性

这一点从上面的来源就可以推断出来,`fopen`是C标准函数,因此拥有良好的移植性;而`open`是UNIX系统调用,移植性有限。如windows下相似的功能使用API函数`CreateFile`。

3. 适用范围

  • open 返回文件描述符,而文件描述符是UNIX系统下的一个重要概念,UNIX下的一切设备都是以文件的形式操作。如网络套接字、硬件设备等。当然包括操作普通正规文件(Regular File)。
  • fopen   是用来操纵普通正规文件(Regular File)的。

4. 文件IO层次

如果从文件IO的角度来看,前者属于低级IO函数,后者属于高级IO函数。低级和高级的简单区分标准是:谁离系统内核更近。低级文件IO运行在内核态,高级文件IO运行在用户态。

5. 缓冲

  1. 缓冲文件系统 
    缓冲文件系统的特点是:在内存开辟一个“缓冲区”,为程序中的每一个文件使用;当执行读文件的操作时,从磁盘文件将数据先读入内存“缓冲区”,装满后再从内存“缓冲区”依此读出需要的数据。执行写文件的操作时,先将数据写入内存“缓冲区”,待内存“缓冲区”装满后再写入文件。由此可以看出,内存“缓冲区”的大小,影响着实际操作外存的次数,内存“缓冲区”越大,则操作外存的次数就少,执行速度就快、效率高。一般来说,文件“缓冲区”的大小随机器 而定。fopen, fclose, fread, fwrite, fgetc, fgets, fputc, fputs, freopen, fseek, ftell, rewind等。
  2. 非缓冲文件系统 
    缓冲文件系统是借助文件结构体指针来对文件进行管理,通过文件指针来对文件进行访问,既可以读写字符、字符串、格式化数据,也可以读写二进制数据。非缓冲文件系统依赖于操作系统,通过操作系统的功能对文件进行读写,是系统级的输入输出,它不设文件结构体指针,只能读写二进制文件,但效率高、速度快,由于ANSI标准不再包括非缓冲文件系统,因此建议大家最好不要选择它。open, close, read, write, getc, getchar, putc, putchar等。

一句话总结一下,就是open无缓冲,fopen有缓冲。前者与readwrite等配合使用, 后者与fread,fwrite等配合使用。

使用fopen函数,由于在用户态下就有了缓冲,因此进行文件读写操作的时候就减少了用户态和内核态的切换(切换到内核态调用还是需要调用系统调用API:readwrite);而使用open函数,在文件读写时则每次都需要进行内核态和用户态的切换;表现为,如果顺序访问文件,fopen系列的函数要比直接调用open系列的函数快;如果随机访问文件则相反。

这样一总结梳理,相信大家对于两个函数及系列函数有了一个更全面清晰的认识,也应该知道在什么场合下使用什么样的函数更合适,效率更高。

七、用ANSIC标准中的C库函数进行文件编程

fopen

使用给定的模式 mode 打开 filename 所指向的文件。

#include <stdio.h>

FILE *fopen(const char *path, const char *mode);//返回的是文件标识符

fopen函数用的是标准C语言库,第一个参数是文件路径,第二个参数是文件权限。

/** 返回值 **/
打开成功,指向该流的文件指针就会被返回。
打开失败,则返回 NULL,并把错误代码存在 error 中。

/** 参数 **/
mode:    "r"          只读  文件必须存在
              "w"         只写  文件创建,若存在则清空
              "a"         只读  打开或创建,在文件末尾追加
              带有"+"的   可读可写
              带有"b"的   二进制文件


r:以只读方式打开文件,该文件必须存在。
r+:以读/写方式打开文件,该文件必须存在。
rb+:以读/写方式打开一个二进制文件,只允许读/写数据。
rt+:以读/写方式打开一个文本文件,允许读和写。
w:打开只写文件,若文件存在则文件长度清为零,即该文件内容会消失;若文件不存在则创建该文件。
w+:打开可读/写文件,若文件存在则文件长度清为零,即该文件内容会消失;若文件不存在则创建该文件。
a:以附加的方式打开只写文件。若文件不存在,则会创建该文件;如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留(EOF 符保留)。
a+:以附加方式打开可读/写的文件。若文件不存在,则会创建该文件,如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留(EOF符不保留)。
wb:以只写方式打开或新建一个二进制文件,只允许写数据。
wb+:以读/写方式打开或新建一个二进制文件,允许读和写。
wt+:以读/写方式打开或新建一个文本文件,允许读和写。
at+:以读/写方式打开一个文本文件,允许读或在文本末追加数据。
ab+:以读/写方式打开一个二进制文件,允许读或在文件末追加数据。


fread

从给定流 stream 读取数据到 ptr 所指向的数组中。

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)

/** 参数 **/
ptr     指向带有最小尺寸  size*nmemb 字节的内存块的指针。(buf缓冲区)
size    要读取的每个元素的大小,以字节为单位。
nmemb   元素的个数,每个元素的大小为 size 字节。
stream  流

/** 返回值 **/
成功读取的元素总数会以 size_t 对象返回,size_t 对象是一个整型数据类型。
如果总数与 nmemb 参数不同,则可能发生了一个错误或者到达了文件末尾。

fwrite

把 ptr 所指向的数组中的数据写入到给定流 stream 中。

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)

/** 参数遇返回值和fread一样 **/

或者换种容易理解点的

参数一:要往文件写入的内容,是字符串格式
参数二:一次写入的字节数
参数三:写多少次
参数四:目标文件标识符

fseek

操作文件指针的位置

int fseek(FILE *stream, long int offset, int whence)

/* 参数 */
offset  相对 whence 的偏移量,以字节为单位。
whence  文件指针的位置
        SEEK_SET 文件的开头
        SEEK_CUR 文件指针的当前位置
        SEEK_END 文件的末尾

/* 参数 */
如果成功,则该函数返回零,否则返回非零值。

简单示例

#include "stdio.h"
#include "stdlib.h"
#include "string.h"int main()
{//以可读可写的方式创建文件,若存在则清空原文件FILE* fp = fopen("./fopenTest","w+");char* str = "chen li chen mei wo shuai";//向文件流中写入字符串fwrite(str,sizeof(char),strlen(str),fp);//文件指针回头才能读取fseek(fp,0,SEEK_SET);char* readBuf = (char *)malloc(strlen(str));memset(readBuf,'\0',strlen(str));fread(readBuf,sizeof(char),strlen(str),fp);//打印出读取到的readBufprintf("read:%s\n",readBuf);fclose(fp);return 0;
}

运行结果:

等效于

#include "stdio.h"
#include "stdlib.h"
#include "string.h"int main()
{FILE* fp = fopen("./fopenTest","w+");char* str = "chen li chen mei wo shuai";fwrite(str,sizeof(char)*strlen(str),1,fp);fseek(fp,0,SEEK_SET);char* readBuf = (char *)malloc(strlen(str));memset(readBuf,'\0',strlen(str));fread(readBuf,sizeof(char)*strlen(str),1,fp);printf("read:%s\n",readBuf);fclose(fp);return 0;
}

补充,研究一下fread和fwrite返回的值

#include "stdio.h"
#include "stdlib.h"
#include "string.h"int main()
{FILE* fp = fopen("./fopenTest","w+");char* str = "chen li chen mei wo shuai";int nwrite = fwrite(str,sizeof(char)*strlen(str),1,fp);fseek(fp,0,SEEK_SET);char* readBuf = (char *)malloc(strlen(str));memset(readBuf,'\0',strlen(str));int nread = fread(readBuf,sizeof(char)*strlen(str),1,fp);printf("read:%s\n",readBuf);printf("read=%d,write=%d\n",nread,nwrite);fclose(fp);return 0;
}

运行结果:

得出结论: 

写返回的值取决于第三个参数

读返回的值就不一定了

int nread = fread(readBuf,sizeof(char)*strlen(str),1,fp);

nread = 1

int nread = fread(readBuf,sizeof(char)*strlen(str),100,fp);

nread = 1

int nread = fread(readBuf,sizeof(char),strlen(str),fp);

nread = 25

用fopen、fread、fwrite、fseek来给一个文件写入结构体

#include <stdio.h>
#include <string.h>struct data
{int a;char b;
};int main()
{FILE* fp;struct data test2 = {1,'q'};struct data test1;fp = fopen("./file2","w+");//返回文件标识符fwrite(&test2,sizeof(struct data),1,fp);//每次写多少数据,写多少次fseek(fp,0,SEEK_SET);//光标移到文件头fread(&test1,sizeof(struct data),1,fp);fclose(fp);printf("test1.a = %d test1.b = %c\n",test1.a,test1.b);return 0;
}

fputc

描述

C 库函数 int fputc(int char, FILE *stream) 把参数 char 指定的字符(一个无符号字符)写入到指定的流 stream 中,并把位置标识符往前移动。

参数

  • char -- 这是要被写入的字符。该字符以其对应的 int 值进行传递。
  • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了要被写入字符的流。

返回值

如果没有发生错误,则返回被写入的字符。如果发生错误,则返回 EOF,并设置错误标识符。

#include "stdio.h"int main()
{FILE *fp;fp = fopen("./test.txt","w+");fputc('a',fp);fclose(fp);return 0;
}

运行结果:

写多个字符

#include "stdio.h"
#include "string.h"int main()
{FILE *fp;int i;char *str = "chenlichen mei wo shuai";int len = strlen(str);fp = fopen("./test.txt","w+");for(i=0;i<len;i++){fputc(*str,fp);str++;}fclose(fp);return 0;
}

for(i=0;i<strlen(str);i++){
                fputc(*str,fp);
                str++;
 }

这样可以吗

显然是不可以的,每次str都在变,for里面的条件就一直在变

fgetc

描述

C 库函数 int fgetc(FILE *stream) 从指定的流 stream 获取下一个字符(一个无符号字符),并把位置标识符往前移动。

参数

  • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了要在上面执行操作的流。

返回值

该函数以无符号 char 强制转换为 int 的形式返回读取的字符,如果到达文件末尾或发生读错误,则返回 EOF。

feof

描述

C 库函数 int feof(FILE *stream) 测试给定流 stream 的文件结束标识符。

参数

  • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。

返回值

当设置了与流关联的文件结束标识符时,该函数返回一个非零值,否则返回零。

#include "stdio.h"
#include "string.h"int main()
{FILE *fp;char c;fp = fopen("./test.txt","r");while(!feof(fp)){c = fgetc(fp);printf("%c",c);}fclose(fp);return 0;
}

[Linux系统编程]_文件编程(一)相关推荐

  1. 理解Unix/Linux系统中的文件描述符

    简介 文件描述符是针对Unix/Linux的每个进程而言的,每个进程都维护了一个文件指针表,指针指向操作系统的文件.这里的文件是指的Unix/Linux系统所说的文件,Unix/Linux下一切皆文件 ...

  2. linux系统中的文件传输

    Linux系统中的文件传输 1 实验环境 2 scp命令 3 rsync命令 3.1 rsync和scp命令对比 3.2 rsync命令用法 4 文件的归档压缩 4.1 文件归档 4.2 文件压缩 4 ...

  3. Linux系统上的文件类型

    Linux系统上的文件类型 -: 常规文件 d: directory,目录文件 b: block device,块设备文件,支持以"block"为单位进行随机访问 c: chara ...

  4. linux物理内存地址与iomem,一种Linux系统物理内存镜像文件分析方法_4

    模块信息,如图7所示,给出了本发明的实施例中 模块结构关系图,modules变量指向某一个已加载模块结构体module地址,所有已加载模 块其module形成一个双向链表,如图7所示,据此可以获取到所 ...

  5. linux mount命令衔接,Linux mount命令详解:挂载Linux系统外的文件

    Linux mount命令详解:挂载Linux系统外的文件 <Linux挂载>一节讲到,所有的硬件设备必须挂载之后才能使用,只不过,有些硬件设备(比如硬盘分区)在每次系统启动时会自动挂载, ...

  6. Android 系统(68)---使用Xshell在Windows系统和Linux系统之间进行文件传输

    使用Xshell在Windows系统和Linux系统之间进行文件传输 Windows系统在安装虚拟机centos系统之后,如何进行两者之间的文件传输和互操作,或者如何在Windows端使用Xshell ...

  7. linux系统 mysql日志文件太大。造成数据库无法正常启动怎么解决

    linux系统 mysql日志文件太大.造成数据库无法正常启动怎么解决 删除mysql日志: 执行:/usr/local/mysql/bin/mysql -u root -p 输入密码登录后再执行:r ...

  8. linux系统中清空文件内容的三种方法

    1.使用vi/vim命令打开文件后,输入"%d"清空,后保存即可.但当文件内容较大时,处理较慢,命令如下: vim file_name :%d :wq 2.使用cat命令情况,命令 ...

  9. 在linux系统中创建文件夹,Linux系统中创建文件夹命令详解

    Linux系统中创建一个新的文件夹我们可以使用命令来执行,下面由学习啦小编为大家整理了Linux系统中创建文件夹命令详解,希望对大家有帮助! Linux系统中创建文件夹命令详解 一.mkdir命令使用 ...

最新文章

  1. python type help copyright_Python关于import的实验(8)__init__.py文件内部代码的执行以及内部的导入和内部的变量...
  2. iOS H264,H265视频编码(Video encode)
  3. 北京科技计算机与通信工程学院,北京科技大学计算机与通信工程学院-任超
  4. 前端实现炫酷动效_20个网页动效设计的炫酷神器
  5. Ta Muid(Dreams 梦幻)
  6. Linux IPC之有名管道
  7. C++小游戏——迷宫
  8. Mac 安装和配置 Maven
  9. RocketMQ的底层通信模块remoting 源码解析
  10. postgreSQL中文拼音排序
  11. LTCC带通滤波器设计
  12. 过流媒体取流失败_海康硬盘录像机:监控点取流失败,开始重连.错误代码为iVMS-4200.EXE[302]求大神解决...
  13. 百度地图根据经纬度定位
  14. 首次登录Navicat连接数据库遇到的问题
  15. Docker 使用容器数据卷 实现宿主机与容器共享数据 容器数据持久化
  16. 关于图片转base64的加密解密
  17. 3D打印机的调平问题
  18. 嵌入式linux编程过成中模块从串口读数需要特定的字符段并且需要每两位字符数组元素转换成一个16进制数(提取特定字符串+字符串转16进制)
  19. 台式计算机戴尔3020,戴尔 Dell OptiPlex 3020M 台式机整机 评测
  20. Mysql权限系统工作原理(转)

热门文章

  1. 分享关于平板电视的一些使用、选购经验和想法。
  2. 一款炫酷Loading动画--加载失败
  3. 地图投影之UTM和高斯克里格投影
  4. 2007-01-22 日志——让泪化作相思雨
  5. 事务的提交commit、回滚rollback
  6. 百度小程序部署到服务器,三:百度小程序提交发布上线
  7. BW4HANA cockpit 权限设置
  8. 弘辽科技:淘宝商家该如何增加店铺竞争力?有哪些技巧策略?
  9. 杭电OJ(HDOJ)入门题目列表
  10. 定义银行账户类Account,有属性:卡号cid,余额balance,所属用户Customer 银行账户类Account有方法: (1)getInfo(),返回String类型,返回卡的详细信息