Linux系统编程文件编程

文件编程

文章目录

  • 文件编程
    • 前言
    • 1.打开文件/创建文件
    • 2.文件写入
    • 3.读文件操作
      • 方法一:关闭再打开
      • 方法二:lseek光标移动
    • 拓展:
    • 4.文件描述符
    • 5.实战一:文件操作之实现cp指令
    • 6.实战二:配置文件的修改
    • 7.向文件写数据
      • 7.1写整数到文件中
      • 7.2写结构体到文件中
      • 7.3写结构体数组到文件
      • 7.4写链表到文件
    • 补充知识点:open与fopen的区别
    • 8.标准C库对文件操作
      • 8.1文件打开
      • 8.2文件读写/光标
      • 8.3写结构体到文件
    • 9.fgetc fputc feof

在这里插入图片描述

前言


为什么要学文件编程?

回想一下我们在使用windows系统下如何操作文件的?

比如写一个word文档:………

那么现在换成Linux系统了,你又该如何应对??
在这里插入图片描述


1.打开文件/创建文件

当我们渐学渐深,在后期调用封装好的API时,无非只需要知道两个,一个是他需要传入的参数(形参),另一个是他的返回值

open();

SYNOPSIS
//API源码说明
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname,int flags, mode _t mode);
int creat(const char *pathname, mode_t mode);
DESCRIPTION
Given a pathname for a file, open() returns a file descriptor, a small, nonnegative 
integer for use in subsequent systercalls (read(2), write(2), lseek(2), fcntl(2), etc.).
The file descriptor returned by a successful call will be the lowestnumbered file descriptor 
not currently open for the process.
//参数说明
Pathname:要打开的文件名(含路径,缺省为当前路径)
Flags:
    O_RDONLY 只读打开         O_WRONLY 只写打开         O_RDWR  可读可写打开
//一般 O_RDWR  可读可写打开  用的多
    当我们附带了权限后,打开的文件就只能按照这种权限来操作。
    以上这三个常数中应当只指定一 个。下列常数是可多选择的:     
        O_CREAT 若文件不存在则创建它。使用此选项时,需要同时说明第三个参数mode,用其说明该新文件的存取许可权限。
        O_EXCL 如果同时指定了OCREAT,而文件已经存在,则出错。        
        O_APPEND 每次写时都加到文件的尾端。
        O_TRUNC 属性去打开文件时,如果这个文件中本来是有内容的,而且为只读或只写成功打开,则将其长度截短为0。

Mode:必须在flags中使用了O_CREAT标志,mode记录待创建的文件的访问权限

1.open打开文件会有一个返回值,这个返回值叫文件描述符,类似于身份证,通过这个文件描述符我们可以对这个文件进行读或写。

创建文件命令:touch file1 创建一个名为file1的文件

思路:我们要用open函数,就需要知道他用到了那些头文件,这个时候我们可以通过man手册,man 2 open查看open函数的用法,其他函数可能在man 里面也可能在man 2 里面,多试几个总会知道在那个手册里面存着

open函数会返回一个整型值,这个值叫做文件描述符,我们需要定义一个整型变量来承接

注:字符串本身就是一个指针。

在这里插入图片描述

在这里插入图片描述

2.如果没有这个文件的时候,我们就需要创建一个,需要用到O_CREAT来创建文件,用到这个时,我们需要传入第三个参数一个权限0600(为什么是0600后续介绍)

在这里插入图片描述

在这里插入图片描述

3.拓展:为什么是0600

在这里插入图片描述

可读 r 4
可写 w 2
可执行 x 1

0600是给文件所有者的一个权限,如下:

在这里插入图片描述

2.文件写入


在这里插入图片描述

小知识:cat file2命令

cat命令的基本功能是在 Linux 中显示现有文件的内容

write

三个参数:第一个,文件描述符,第二个要传入的东西,第三个,大小(这里为什么不用sizeof呢?因为这个是一个指针,计算Linux指针的大小为8个字节,所有只会传入8个,所以这里要用strlen)

这里顺带着讲一下close,这个只需要传入一个参数,即文件描述符即可关闭。

//1.头文件
 #include <unistd.h>

//2.函数原型
ssize_t write(int fd, const void *buf, size_t count);
    int fd :文件描述符
    const void *buf :一个无类型的指针buf,是一个缓冲区
    size_t count:你要写入文件的大小
总结:写到那个文件?写什么?写多少?
        
//3.函数返回值
RETURN VALUE
On  success,  the  number of bytes written is returned (zero indicates nothing was written).  On error, -1 is returned, and errno is set appropriately.
如果成功,将返回写入的字节数 (0表示没有写入任何内容)。出现错误时,返回-1,并适当地设置errno

代码实现

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
 
int main()
{
        int fd;
        char *buf = "hello,world";
        
        fd = open("./file1",O_RDWR);   //打开一个file1的文件
 
        if(fd == -1){
                printf("open file1 fail 
");           
                fd = open("./file1",O_RDWR|O_CREAT,0600);  //如果file1不存在,就创建它 
                if(fd > 0){
                        printf("creat file1 success 
");
                }
        }
        printf("open file1 success: fd = %d 
",fd);
        write(fd,buf,strlen(buf));    //将buf里面的内容写入到fd里面去。计算字符串大小用strlen
        close(fd);      //用open打开一个文件,操作结束了就要关闭close                                                                                
        return 0;
}

在这里插入图片描述

3.读文件操作

我们在写完操作之后,光标默认会在文件的末尾,(这个时候如果我们再去读文件的话,什么也读不到)

有两种方法解决:1.关闭文件重新打开 2.把光标移到文章头

//1.头文件
#include <unistd.h>

//2.函数原型
ssize_t read(int fd, void *buf, size_t count);
    int fd :文件描述符
    const void *buf :一个无类型的指针buf,是一个缓冲区
    size_t count:你要读取文件的大小
总结起来就是:读那个文件?读存到哪里?读多少?
        
//3.函数返回值
函数返回值分为下面几种情况:

1、如果读取成功,则返回实际读到的字节数。这里又有两种情况:一是如果在读完count要求字节之前已经到达文件的末尾,
那么实际返回的字节数将 小于count值,但是仍然大于0;二是在读完count要求字节之前,仍然没有到达文件的末尾,这是
实际返回的字节数等于要求的count值。
2、如果读取时已经到达文件的末尾,则返回0。
3、如果出错,则返回-1。

这样也就是说分为>0 <0 =0三种情况进行讨论。在有的时候,<0 =0可以合为一种情况进行处理。这要根据程序的需要进行处理

在这里插入图片描述

方法一:关闭再打开

#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;
 char *buf="hello,world!";

 fd = open("./file2",O_RDWR);
//文件找不到,输出反馈信息,创建一个新的
 if(fd == -1)
 {
  printf("open file2 filed
");
  fd = open("./file2",O_RDWR|O_CREAT,0600);
        if(fd>0){
          printf("file2 creat success
");
        }
 }
 printf("fd is %d
",fd);
 //ssize_t write(int fd, const void *buf, size_t count);
 int n_write= write(fd,buf,strlen(buf));
 if(n_write != -1){
        printf("write %d byte to file
",n_write);
 }
//关闭重新打开
close(fd);
open("./file2",O_RDWR);

//ssize_t read(int fd, void *buf, size_t count);
 char *readbuf;
 readbuf = (char *)malloc(sizeof(char)*n_write+1);
 int n_read = read(fd,readbuf,n_write);

 printf("read %d,context :%s
",n_read,readbuf);
 close(fd);
 return 0;
}

在这里插入图片描述

方法二:lseek光标移动

lseek第二个参数偏移量(正数向后偏移,,负数向前偏移),第三个参数(SEEK_SET 文件头 SEEK_END 文件末尾 SEEK_CUR 当前位置),它的返回值是光标的位置相对于文件头偏移了多少个字节

//1.头文件
 #include <sys/types.h>
 #include <unistd.h>

//2.函数原型
off_t lseek(int fd, off_t offset, int whence);
	int fd :文件描述符
	off_t offset:偏移多少个字节
	int whence:光标偏移位置
        
给whence参数设定偏移位置:
SEEK_SET:光标偏移到头
SEEK_CUR:光标为当前位置
SEEK_END:光标偏移到末尾
        
//3.返回值
成功完成后,Iseek()返回从文件开始的偏移位置(以字节为单位)【就是返回偏移了多少个字节】。
发生错误时,返回值(off_t) -1,并设置errno来指示错误

实际代码如下:

#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;
 char *buf="hello,world!";

 fd = open("./file2",O_RDWR);

 if(fd == -1)
 {
  printf("open file2 filed
");
  fd = open("./file2",O_RDWR|O_CREAT,0600);
        if(fd>0){
          printf("file2 creat success
");
        }

 }
 printf("fd is %d
",fd);
 //ssize_t write(int fd, const void *buf, size_t count);
 int n_write= write(fd,buf,strlen(buf));
 if(n_write != -1){
        printf("write %d byte to file
",n_write);
 }
//close(fd);
//open("./file2",O_RDWR);

//ssize_t read(int fd, void *buf, size_t count);
 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,context :%s
",n_read,readbuf);
 close(fd);
 return 0;
}

运行结果和方法一一样,只是实现的方式不一样,不再展示。

在这里插入图片描述


Tips:(它的返回值是光标的位置相对于文件头偏移了多少个字节),我们可以根据 lseek返回值的特性来计算出文件的大小。

如下图:在这里插入图片描述

拓展:

lseek光标补充:

//将光标移动到头后,相对头偏移0个字节位置(常用一种就好啦)
lseek(fd,0,SEEK_SET); 
//常用于计算文件大小
lseek(fd,0,SEEK_END; 
//将光标移动到尾巴后,相对尾巴向头偏移,也就是往前偏移20个字节位置
lseek(fd,-20,SEEK_END); 
//将光标移动到尾巴后,相对尾巴向头偏移,也就是往前偏移写入操作write之后的(返回值,实际就是20)个字节位置
lseek(fd,-n_write,SEEK_END); 
//将光标移动到当前位置(上面代码也就是尾巴),相对当前位置(尾巴)向头偏移,也就是往前偏移20个字节位置
lseek(fd,-20,SEEK_CUR);  
//将光标移动到当前位置(上面代码也就是尾巴),也就是往前偏移写入操作write之后的(返回值,实际就是20)个字节
lseek(fd,-n_write,SEEK_CUR);   

open函数补充:

当我们附带了权限后,打开的文件就只能按照这种权限来操作。
    以上这三个常数中应当只指定一 个。下列常数是可选择的: 
//1. O_CREAT 若文件不存在则创建它。使用此选项时,需要同时说明第三个参数mode,用其说明该新文件的存取许可权限。
	fd = open("./file1",O_RDWR|O_CREAT,0600);  //如果file1不存在,就创建它 
//2. O_EXCL 如果同时指定了OCREAT,而文件已经存在,则出错。
	fd = open("./file1",O_RDWR|O_CREAT|O_EXCL,0600); 
	如何文件已经存在fd=-1,可以根据这个特性配合条件语句来做出一些反馈
//3. O_APPEND 每次写时都加到文件的尾端。
	fd = open("./file1",O_RDWR|O_APPEND); //如何不加这个的话,每次在对文件写操作时,会覆盖原本文件中的信息。
//4. O_TRUNC 属性去打开文件时,如果这个文件中本来是有内容的,而且为只读或只写成功打开,则将其长度截短为0。
	fd = open("./file1",O_RDWR|O_TRUNC);//啥个意思呢?不管你原本文件里存着什么东西,全部清空,再写上我要写的东西

creat函数

在这里插入图片描述

#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;
 char *buf="hello,world!";

 fd = creat("./file2",S_IRWXU);//参数可以换成数字表示
 return 0;
}

在这里插入图片描述

4.文件描述符

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

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

注释:0:标准输入, 1:标准输出,2:标准错误 (其实前面已经发现fd的值是3,012已经被Linux系统默认占了)

标准输入:从键盘输入

标准输出:输出从键盘输入的内容

标准错误:可以把这个放垃圾桶来用,将无用的信息丢进去

#include <unistd.h>
#include <string.h>
#include <stdlib.h>
 
int main()
{   
        char readBuf[128];
        read(0,readBuf,5); //读取从键盘输入的数据,不超5个字节,到readBuf这个缓冲区里面去
        write(1,readBuf,strlen(readBuf));  //从键盘输入的数据readBuf中,写入数据到输出界面去   
        return 0;
}

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

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

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


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

2、强调一点:我们对文件进行操作时,一定要先打开文件,只有操作成功后才能操作,如果打开失败,就不用进行后面的操作了,最后读写完成后,一定要关闭文件,否则会造成文件损坏。

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

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

5、为什么是这样设计,不能直接对块设备直接操作。

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

在这里插入图片描述

5.实战一:文件操作之实现cp指令

Linux指令:cp src.c des.c 其中,src.c为源文件 des.c为目标文件

main函数参数传参:

#include <stdio.h>
/***************************************
argc :代表的是 ./a.out argc argv 这三个参数的个数
argv[0] :代表第一个参数./a.out
argv[1] :代表第二个参数 argc
argv[2] :代表第二个参数 argv
****************************************/
int main(int argc,char **argv)
{
        printf("total params:%d
",argc);
        printf("No.1 params :%s
",argv[0]);
        printf("No.2 params :%s
",argv[1]);
        printf("No.3 params :%s
",argv[2]);
 
        return 0;
}

在这里插入图片描述

思路:

  1. 打开源文件src.c
  2. 读src到buf
  3. 打开/创建目标文件des.c
  4. 将buf写入des.c
  5. close两个文件

在这里插入图片描述

#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;
        int fdDes;
        char *readbuf =NULL; 
 
        if(argc != 3){
            printf("param error
");
            exit(-1);
        }
 		//1.打开要拷贝的源文件
        fdSrc = open(argv[1],O_RDWR);            
 
        int size = lseek(fdSrc,0,SEEK_END);      //利用lseek返回值计算文件的大小  
        lseek(fdSrc,0,SEEK_SET);                 //移动光标到头 
        readbuf = (char *)malloc(sizeof(char)*size + 8);
        //2.读源文件到readbuf缓冲区
        read(fdSrc,readbuf,size);                
  
 		//3.打开/创建你要拷贝到的目标文件,如果目标文件已存在有内容,O_TRUNC清除掉内容
        fdDes = open(argv[2],O_RDWR|O_CREAT|O_TRUNC,0600); 
 
        //4.将readbuf里面的内容写入目标文件
        write(fdDes,readbuf,strlen(readbuf));      

         //5.关闭打开的文件
        close(fdSrc);                                                   
        close(fdDes);
 
        return 0;
}

6.实战二:配置文件的修改

问题描述:有下列一个配置文件,里面存放着这些数据

如:

SPEED=5		
LENG=100		
SCORE=90           
LEVEL=95

如果我想把里面的LENG=100,改为LENG=5呢?怎么修改

在这里插入图片描述

补充:strstr函数声明:

char *strstr( const char *str1, const char *****str2 );

strstr函数是在字符串str1中查找是否含有字符串str2,如果存在,返回str2在str1中第一次出现的地址;否则返回NULL。

#include<stdio.h>
#include<string.h>
 
//strstr函数的应用
int main()
{
	char arr1[] = "abbbcdef";
	char arr2[] = "bbc";
 
	printf("%s
", strstr(arr1, arr2));
 
	return 0;
}
结果:
bbcdef

下面正式开始编写

#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<unistd.h>
#include<stdio.h>
#include<string.h>

void findFun(char *str1,char *str2)
{
     char *p=strstr(str1,str2);
     if(p==NULL)
     {
         printf("no found
");
         exit(-1);
     }
     p=p+strlen(str2);//指针偏移到需要改动的地方
     *p='5';//注意是字符5
}

int main(int argc,char **argv)
{
    int fdSrc;
    int size;

    char *readBuf=NULL;

    if(argc!=2)
    {
        printf("the params no ok
");
        exit(-1);
    } 

    fdSrc=open(argv[1],O_RDWR);
    
    size=lseek(fdSrc,0,SEEK_END);//计算出配置文件的大小
    lseek(fdSrc,0,SEEK_SET);//把光标移到头,为下面读操作做铺垫
    readBuf=(char*)malloc(sizeof(char)*size+1); 
    read(fdSrc,readBuf,size);
    findFun(readBuf,"LENG=");    
    
    lseek(fdSrc,0,SEEK_SET); //移到头,把原先信息覆盖掉 
/******************方法二:****************
    close(fdSrc);
    open("./file1",O_RDWR|O_TRUNC);
*****************************************/
    write(fdSrc,readBuf,strlen(readBuf));  
    close(fdSrc);
    return 0;
}

貌似不是很理想,仅仅是把1换成了5,数据变成了500。这里我们先保留。

在这里插入图片描述

7.向文件写数据

7.1写整数到文件中

#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;
        int data = 100;
        int data2 = 0;
 
        fd = open("./file",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 
",data2);
        close(fd);
 
        return 0;
}

在这里插入图片描述

7.2写结构体到文件中

#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;
 
        struct Test data = {100,'a'};
        struct Test data2;
 
        fd = open("./file",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 
",data2.a,data2.c);
        close(fd);
 
        return 0;
}                   

运行结果:在这里插入图片描述

7.3写结构体数组到文件

#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;
 
        struct Test data[2] = {{100,'a'},{101,'b'}};
        struct Test data2[2];
 
        fd = open("./file",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 
",data2[0].a,data2[0].c);
        printf("read %d,%c 
",data2[1].a,data2[1].c);
        close(fd);
 
        return 0;
}                                                                                  

运行结果:在这里插入图片描述

7.4写链表到文件

链表学习跳转

#include<stdio.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

struct Test
{
	int data;
	struct Test *next;
};

struct Test* insertFormHead(struct Test *head,struct Test *new)
{
	struct Test* point=head;

	if(point==NULL)
	{
		head=new;
		return new;
	}

	while(point->next!=NULL)
	{
		point=point->next;
	}

	point->next=new;
	return head;
}

struct Test* creatLink(struct Test *head)
{
	struct Test *new=NULL;
	int i;
	int num;    

	printf("please input link data
");
	scanf("%d",&num); 

	for(i=0;i<num;i++)
	{
		new=(struct Test*)malloc(sizeof(struct Test));
		new->next=NULL;
		printf("please input NO %d link data
",i+1);
		scanf("%d",&(new->data));
		head=insertFormHead(head,new);
	} 
	return head;
}

void printLink(struct Test *head)
{
	struct Test *point=head;

	while(point!=NULL)
	{
		printf("%d ",point->data);
		point=point->next;
	}
	putchar('
');
} 

void saveLink(struct Test *head)
{
	struct Test *point=head;
	int fd;

	fd=open("./file2",O_RDWR);
	if(fd==-1)
	{
		printf("open file failed
");
	}   

	while(point!=NULL)
	{
		if((write(fd,&point->data,sizeof(struct Test)))==-1)
		{
			printf("write failed
");
		} 
		point=point->next;
	}

	close(fd);
}

void readLink(struct Test *head)
{
	struct Test *point=head;
	int fd;
	int n_read;
	int n_write;
	int fd1;
	struct Test *buf=NULL;

	fd=open("./file2",O_RDWR);
	if(fd==-1)
	{
		printf("open file failed
");
	}

	int size=lseek(fd,0,SEEK_END);
	lseek(fd,0,SEEK_SET);
	buf=(struct Test*)malloc(sizeof(struct Test)*size+8);
	printf("open file2 success
");

	while(point!=NULL)
	{
		n_read=read(fd,buf,sizeof(struct Test));
		printf("buf:%d ",point->data);              
		
        if(n_read==-1)
		{
			printf("read failed
");
		} 
        
		fd1=open("./file3",O_RDWR);
		if(fd1==-1)
		{
			printf("open failed
");
		}

		n_write=write(fd1,buf,sizeof(struct Test));
		if(n_write==-1)
		{
			printf("write failed
");
		}

		point=point->next;
	}

	close(fd);
	close(fd1);
}

int main()
{
	struct Test *head=NULL;    

	head=creatLink(head);   
	printLink(head);
	saveLink(head);        
	readLink(head);

	return 0;
}

运行结果:在这里插入图片描述


补充知识点:open与fopen的区别

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

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

总结:open是Linux才有的,你用在Windows就没有,fopen是标准C库。

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有缓冲。前者与read, write等配合使用, 后者与fread,fwrite等配合使用

8.标准C库对文件操作

8.1文件打开

fopen函数

//1.头文件
#include <stdio.h>

//2.函数原型
FILE *fopen(const char *filename, const char *mode);
参数:
filename : 字符串,表示要打开的文件名称。
mode : 字符串,表示文件的访问模式,可以是以下表格中的值。
/****************************************
例子:文件指针名 = fopen(文件名,使用文件方式)
     FILE* fp = fopen ("file.txt", "w");
****************************************/
    
//3.返回值:
该函数返回一个 FILE 指针(文件指针)。如果打开失败则返回 NULL。

打开方式如下:

  • 注意:这里的输入意思是从文件获取信息;输出是向文件输出信息
  • 注意:如果使用任何一种 ”w" 模式打开一个现有文件,该文件的内容会被删除,以便程序在一个空白文件中开始操作
文件使用方式 含义 如果指定文件不存在
“r”(只读) 为了输入数据,打开一个已经存在的文本文件(从文件获取) 出错
“w”(只写) 为了输出数据,打开一个文本文件(向文件输出) 建立一个新的文件
“a”(追加) 向文本文件尾添加数据 出错
“rb”(只读) 为了输入数据,打开一个二进制文件 出错
“wb”(只写) 为了输出文件,打开一个二进制文件 建立一个新的文件
“ab”(追加) 向一个二进制文件尾添加数据 出错
“r+”(读写) 为了读和写,打开一个文本文件 出错
“w+”(读写) 为了读和写,创建一个新的文本文件 建立一个新的文件
“a+”(读写) 打开一个文件,在文件末尾读进行读写 建立一个新的文件
“rb+”(读写) 为了读和写打开一个二进制文件 出错
“wb+”(读写) 为了读和写,新建一个二进制文本文件 建立一个新的文件
“ab+”(读写) 打开一个二进制文件,在文件末尾进行读和写 建立一个新的文件
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
   FILE* fp = fopen ("file.txt", "w");
   fclose(fp);//关闭文件
   fp = NULL;
   return 0;
}
//此时我的项目路径下没有file.txt文件,因为打开方式是w,所以它会自动创建一个。

8.2文件读写/光标

fread函数,fwrite函数

二进制文件读写两个重要的函数 , fread 和 fwrite , fread 用于读取文件 , fwrite 用于写出文件 ;

1.fread / fwrite 函数 既可以操作 二进制文件 , 又可以操作 文本文件 ;

2.getc / putc 函数 , fscanf / fprintf 函数 , fgets / fgets 函数 , 只能用于操作 文本文件 ;

1.fread函数:从文件中读取若干字节数据到内存缓冲区中

//1.函数原型
size_t fread( void *buffer, size_t size, size_t count, FILE *stream );

void *buffer 参数 : 将文件中的二进制数据读取到该缓冲区中 ;
size_t size 参数 : 读取的 基本单元 字节大小 , 单位是字节 , 一般是 buffer 缓冲的单位大小 ;
    1.如果 buffer 缓冲区是 char 数组 , 则该参数的值是 sizeof(char) ;
    2.如果 buffer 缓冲区是 int 数组 , 则该参数的值是 sizeof(int) ;
size_t count 参数 : 读取的 基本单元 个数 ;
FILE *stream 参数 : 文件指针 ;
//第二个参数是读写的单位,第三个是读的次数(与返回值有关)
//2.返回值
size_t 返回值 : 实际从文件中读取的 基本单元 个数 ; 读取的字节数是基本单元数 * 基本单元字节大小 
//假如写了100个字节,读了10次,只返回10,读了200次,但只有100,返回100。

2.fwrite函数:用于写出文件

//1.函数原型
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
参数说明 :
const void *ptr : 指针指向要写出数据的内存首地址 ;
size_t size : 要写出数据的 基本单元 的字节大小 , 写出单位的大小 ;
size_t nmemb : 要写出数据的 基本单元 的个数 ;
FILE *stream : 打开的文件指针 

//2.返回值
size_t 返回值返回的是实际写出到文件的 基本单元 个数 ;
//与第三个参数有关,假如我写100次返回100,写200就是200,即使写的buf里面只有100个数据,write还是根据第三个参数值返回
//这是与fread不同的地方

3.lseek函数:移动光标

FILE 文件结构中 , 存在一个指针 , 每次调用文件的读写函数 , 该指针就会移动 ;

如 fgets / fputs , getc / putc , fscanf / fprintf , fread / fwrite 等函数 ;

默认情况下 , 指针是从前向后移动的 ;

该文件内部的指针指向的位置可以通过 fseek 函数进行改变 ;

//1.函数原型
#include <stdio.h>
int fseek(FILE *stream, long offset, int fromwhere);

设置的指针的位置是 起始位置 + 偏移量 ;

其中的 int fromwhere 参数就是 起始位置 , 有以下三种选择 :
1.文件头 SEEK_SET 0
2.当前位置 SEEK_CUR 1
3.文件尾 SEEK_END 2
    
long offset 偏移量参数 , 可以为正数 , 也可以为负数 ;

如果执行成功 , 则返回 0 , 失败返回非 0 , 并设置 error 错误代码 ;

示例代码:

#include <stdio.h>
#include <string.h>
 
int main()
{
        FILE *fp;
        char *str = "hello,world";
        char readbuf[128] = {0};
 
        fp = fopen("./file.text","w+");  
        fwrite(str,sizeof(char),strlen(str),fp);        
    //第二个参数:写一个字符的大小,第三个参数:写strlen计算的字符串的个数这么多次

        fseek(fp,0,SEEK_SET);
        fread(readbuf,sizeof(char),strlen(str),fp); 
// 第二种方法    fread(readbuf,sizeof(char)*strlen(str),1,fp); 
        printf("read data: %s 
",readbuf);
        fclose(fp);
 
        return 0;
}

运行结果:在这里插入图片描述

8.3写结构体到文件

#include <stdio.h>

struct Test
{
        int a;
        char c;
};
 
int main(int argc,char **argv)
{
        FILE *fp;
        struct Test data = {100,'a'};
        struct Test data2;
      
        fp = fopen("./file","w+");
 
        int n_write = fwrite(&data,sizeof(struct Test),1,fp);
        fseek(fp,0,SEEK_SET);
        int n_read = fread(&data2,sizeof(struct Test),1,fp);
        printf("read %d,%c 
",data2.a,data2.c);
 
        fclose(fp);
 
        return 0;
}

运行结果:在这里插入图片描述

9.fgetc fputc feof

1.fgetc函数

fgetc 是 file get char 的缩写,意思是从指定的文件中读取一个字符,并把位置标识符往前移动。

//1.函数原型
int fgetc (FILE *fp);

fp 为文件指针。fgetc() 读取成功时返回读取到的字符,读取到文件末尾或读取失败时返回EOF。
    
//2.返回值
EOF 是 end of file 的缩写,表示文件末尾,是在 stdio.h 中定义的宏,它的值是一个负数,
    往往是 -1。fgetc() 的返回值类型之所以为 int,就是为了容纳这个负数(char不能是负数)

**注意:**这个文件内部的位置指针与C语言中的指针不是一回事。位置指针仅仅是一个标志,表示文件读写到的位置,也就是读写到第几个字节,它不表示地址。文件每读写一次,位置指针就会移动一次,它不需要你在程序中定义和赋值,而是由系统自动设置,对用户是隐藏的。

2.对EOF说明

EOF 本来表示文件末尾,意味着读取结束,但是很多函数在读取出错时也返回 EOF,那么当返回 EOF 时,到底是文件读取完毕了还是读取出错了?我们可以借助 stdio.h 中的两个函数来判断,分别是 feof()ferror()

feof() 函数用来判断文件内部指针是否指向了文件末尾

//1.函数原型
int feof ( FILE * fp );

//2.返回值
当指向文件末尾时返回非零值,否则返回零值

ferror() 函数用来判断文件操作是否出错

//1.函数原型
int ferror ( FILE *fp );

//2.返回值
出错时返回非零值,否则返回零值

注:文件出错是非常少见的情况,一般都能保证将文件内的数据读取完毕。如果追求完美,也可以加上判断并提示。

#include<stdio.h>
int main(){
    FILE *fp;
    char ch;
  
    //如果文件不存在,给出提示并退出
    if( (fp=fopen("./text.txt","rt")) == NULL ){
        puts("Fail to open file!");
        exit(0);
    }
    //每次读取一个字节,直到读取完毕
    while( (ch=fgetc(fp)) != EOF ){
        putchar(ch);
    }
    putchar('
');  //输出换行符
    if(ferror(fp)){
        puts("读取出错");
    }else{
        puts("读取成功");
    }
    fclose(fp);
    return 0;
}

综合

示例代码:

#include <stdio.h>
 
int main()
{
        FILE *fp;
        char c;
 
        fp = fopen("./text.txt","r"); //以只读方式打开文件,该文件必须存在。
 
        c = fgetc(fp);
        while(!feof(fp)){
                printf("%c",c);
                c = fgetc(fp);
        }
 
        fclose(fp);
        printf("
");
        return 0 ;
}

运行结果:

在这里插入图片描述

3.fputc函数

fputc 是 file output char 的所以,意思是向指定的文件中写入一个字符

//1.函数原型
int fputc ( int ch, FILE *fp );
ch 为要写入的字符,fp 为文件指针。
    
//2.返回值
fputc() 写入成功时返回写入的字符,失败时返回 EOF,返回值类型为 int 也是为了容纳这个负数。

写一个字符到文件

//写一个字符a到文件中,比较简单,不再展示结果
#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;
        char *str = "hello,world";
        int i = 0;
        int len = strlen(str);
 
        fp = fopen("./test.txt","w+");//读写,创建一个新文件
 
        for(i=0;i<len;i++){
                fputc(*str,fp);
                str++;                //一个一个字符往文件里面写入
        }
        fclose(fp);
        return 0;                                                                                        
}
//程序结果
创建了一个test.txt文件,里面输入了hello,world

从键盘输入一行字符,写入文件

#include<stdio.h>
int main(){
    FILE *fp;
    char ch;
    //判断文件是否成功打开
    if( (fp=fopen("./test.txt","wt+")) == NULL ){
        puts("Fail to open file!");
        exit(0);
    }
    printf("Input a string:
");
    //每次从键盘读取一个字符并写入文件
    while ( (ch=getchar()) != '
' ){
        fputc(ch,fp);
    }
    fclose(fp);
    return 0;
}
//运行程序,输入一行字符并按回车键结束,打开D盘下的 demo.txt 文件,就可以看到刚才输入的内容。
//程序每次从键盘读取一个字符并写入文件,直到按下回车键,while 条件不成立,结束读取

总结:

  1. 被写入的文件可以用写、读写、追加方式打开,用写或读写方式打开一个已存在的文件时将清除原有的文件内容,并将写入的字符放在文件开头。如需保留原有文件内容,并把写入的字符放在文件末尾,就必须以追加方式打开文件。不管以何种方式打开,被写入的文件若不存在时则创建该文件。
  2. 每写入一个字符,文件内部位置指针向后移动一个字节。

最后的最后,文件编程就此告一段落啦!这世上唯一经得起摧残的是才华。加油,少年!!!