【c基础】文件操作

1.fopen和fclose函数

函数原型

cpp 复制代码
 FILE *fopen(const char *path, const char *mode);

参数解释:

  • 返回值:fopen打开成功,则返回有效file的有效地址,失败返回NULL。
  • path是文件路径,可以相对路径,可以绝对路径
  • mode是模式

|---------|-------------------------------------------------------------------------------------------------------------------------|
| mode | 说明 |
| r或者rt | r以只读的方式打开文本文件,该文件必须存在,且是可读的。在一些系统上,r 模式也可以打开二进制文件,但这并不是标准行为。与 r 模式相比,rt 模式明确地指示打开文件为文本模式,但在大多数系统上,它与 r 模式的行为相同。 |
| w或者wt | 打开只写文件,若文件存在则文件长度清0,即该文件内容会消失,若文件不存在则建 立该文件只写打开或建立一个文本文件,只允许写数据。 |
| a或者at | 以追加的方式打开只写文件,若文件不存在则建立该文件,若文件存在,写入的内容将 被追加到文件末尾,即文件原先的内容会被保留。(EOF符保留) |
| | |
| rb | 只读打开一个二进制文件,只允许读数据 |
| wb | 只写打开或建立一个二进制文件,只允许写数据 |
| ab | 追加打开一个二进制文件,并在文件末尾写数据 |
| | |
| r+或者rt+ | r+以可读写的方式打开,+号表示该文件必须存在。与 r+模式相比,rt+模式明确地指示打开文件为文本模式。 |
| w+或者wt+ | 打开可读写文件,若文件存在则文件长度清0,即该文件内容会消失,若文件不存在 则建立该文件。 |
| a+或者at+ | 以追加的方式打开可读写文件,若文件不存在则建立该文件,若文件存在,写入的内 容将被追加到文件末尾,即文件原先的内容会被保留。(原来的EOF符不保留) |
| | |
| rb+ | 读写打开一个二进制文件,允许读写数据,文件必须存在 |
| wb+ | 读写打开或建立一个二进制文件,允许读和写 |
| ab+ | 读写打开一个二进制文件,允许读,或在文件末追加数据 |
| | |
| rw+ | 读写打开一个文本文件,允许读和写 |
| | |

读写文件出错的一大原因可能是没有权限。


fopen打开读取的文件内容占用的内存在堆里面,用fclose可以释放掉。

fclose关闭fopen打开的文件。只要成功用fopen打开的文件,使用完成后就一定要调用fclose关闭。fclose的参数就是fopen的返回值。

一个程序同时可以打开的文件数是有限的,如果超过系统限制,那么打开文件会失败。

一个fopen会占用一些内存,多个就会对内存消耗很大。

所以记得要fopen使用完文件后及时的fclose。


代码示例:

cpp 复制代码
#include <stdio.h>

int main()
{
    // 1.判断文件是否能打开
    // FILE *p = fopen("./test.txt", "r");
    // char *filepath = "./test.txt";
    char filepath[] = "./test.txt";
    FILE *p = fopen(filepath, "w");
    if (p)
    {
        printf("%p\n", p); // 00007FF98D85FA90
        printf("open success\n");
    }
    else
    {
        printf("open fail\n");
    }

    // 2.关闭文件
    if (p)
    {
        fclose(p);
        printf("close success\n");
    }

    return 0;
}

2.文本文件与二进制文件的区别

基于字符编码vs基于值编码

计算机的存储在物理上是二进制的,所以文本文件与二进制文件的区别并不是物理上的,而是逻辑上的。这两者只是在编码层次上有差异,简单来说,文本文件是基于字符编码的文 件,常见的编码有ASCII编码,UNICODE编码等等。二进制文件是基于值编码的文件,你可以根据具体应用,指定多少个比特代表一个值。

从上面可以看出文本文件基本上是定长编码的(也有非定长的编码如UTF-8)。而二进制文件可看成是变长编码的,因为是值编码嘛,多少个比特代表一个值,完全由你决定。

写入和读取时的区别

文本工具打开一个文件的过程是怎样的呢?拿记事本来说,它首先读取文件物理上所对应的二进 制比特流,然后按照你所选择的解码方式来解释这个流,然后将解释结果显示出来。

一般来说, 你选取的解码方式会是ASCII码形式(ASCII码的一个字符是8个比特),接下来,它8个比特8 个比特地来解释这个文件流。

例如对于这么一个文件流"01000000_01000001_01000010_01000011"(下划线''_'',为了增强可读性手动添加的), 第一个8比特''01000000''按ASCII码来解码的话,所对应的字符是字符''A'',同理其它3个8比特 可分别解码为''BCD'',即这个文件流可解释成"ABCD",然后记事本就将这个"ABCD"显示在屏 幕上。

再比如文件流''00000000_00000000_00000000_00000001''可能在二进制文件中对应的是一 个四字节的整数int 1,在记事本里解释就变成了"NULL_NULL_NULL_SOH"这四个控制符。

字符数据本身在内存中就经过了编码,所以无论是二进制还是文本形式都是一样的,而对于非字 符数据来说,例如char i=10;如果用二进制来进行存储的话为00001010,但是如果需要用文本形式来 进行存储的话就必须进行格式化编码(对1和0分别编码,即形式为'1'和'0'分别对应的码值,即00000001 和 00000000写入文件中)。

  • FILE *writep = fopen("a.dat", "w"); //"w"按文本文件的字符编码写入,比如写入数字 10 (char类型), 则会将字符'1'和'0'对应的ASCII编码,即00000001 和 00000000依次写入 a.dat中
  • FILE *writep = fopen("a.dat", "wb"); //"wb"按二进制文件的二进制编码写入,比如写入 数字 10(char类型), 会将10对应的二进制编码 比如 00001010 写入 a.dat中

文本文件的存储与其读取基本上是个逆过程。而二进制文件的存取显然与文本文件的存取差不 多,只是编/解码方式不同而已。

优缺点比较

  • 一般认为,文本文件编码基于字符定长,译码容易些;二进制文件编码是变长的,所以它灵活, 存储利用率要高些,译码难一些(不同的二进制文件格式,有不同的译码方式)。
  • 文本文件每条数据通常是固定长度的。以ASCII为例,每条数据(每个字符)都是1个字节。二进制 进制文件每条数据不固定。如short占两个字节,int占四个字节,float占8个字节。
  • 文本文件只能存储char型字符变量。二进制文件可以存储char/int/short/long/float/......各种 变量值。
  • 在windows下,文本文件不一定是一ASCII来存贮的,因为ASCII码只能表示128的标识,你打开一个 txt文档,然后另存为,有个选项是编码,可以选择存贮格式,一般来说UTF-8编码格式兼容性要好一 些,而二进制用的计算机原始语言,不存贮兼容性。

3.win和linux文本文件中换行符的区别

windows读写文件时

  • windows所有的文本文件都是\r\n结尾的,而不是\n结尾的,所以这里要占2个字节。
    • 写入时,如果是文本方式写入即用"w"参数时,每遇到一个''\n'',它将其换成''\r \n'', 然后再写入文件;
    • 读取时,当文本读取即用"r"参数时,它每遇到一个''\r\n''将其反变化为''\n'',然后送到 读缓冲区。
  • 对于二进制文件,正常读写,不存在任何转换。

linux读写文件时:

  • linux所有的文本文件,本来就是\n结尾的,前面没有\r,参数b在linux是无效的
  • 二进制文件,正常读写,不存在任何转换

windows和Linux间文本文件格式转换

  • linux转windows:unix2dos -n a.txt b.txt。将a.txt 中的\n变为\r\n,然后另存为b.txt。
  • windows转linux:dos2unix -n b.txt c.txt。将b.txt 中的\r\n变为\n,然后另存为c.txt。

示例代码理解windows下"r"的存在

cpp 复制代码
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>

int main()
{
    // 1.打印每个字符和对应的ASCII码
    FILE *filep = fopen("./add.txt", "r");
    unsigned char a = 0;
    while (1)
    {
        fread(&a, 1, sizeof(char), filep);
        if (feof(filep))
        {
            break;
        }
        printf("(%c,%d)\n", a, a);
    }
    fclose(filep);
    
    // 2.获取文件大小(注意'\r'也占用了一个字节)
    struct stat st = {0};
    stat("./add.txt", &st);
    printf("size=%d\n", st.st_size); // size=12
    // 'h' 'e' 'l' 'l' 'o' '\r' '\n' 'w' 'o' 'r' 'l' 'd'
    return (0);
}
/*
(h,104)
(e,101)
(l,108)
(l,108)
(o,111)
(
,10)
(w,119)
(o,111)
(r,114)
(l,108)
(d,100)
size=12
*/

4.getc和putc函数

getc

已字节为单位读写文件。

函数原型

cpp 复制代码
char getc(FILE *stream);

说明:

  • getc的参数是fopen成功打开文件后返回的指针,getc的返回值是一个char
  • getc的功能是以字节为单位读取文件内容。注意当文本文件中存在汉字内容时,读取会出现乱码,因为一个汉字占2个字节(gbk编码时)或者3个字节(utf-8编码时)。
  • 文本文件的最后结束标示是-1,是一个宏常量EOF
cpp 复制代码
#define EOF -1

示例代码

cpp 复制代码
#include <stdio.h>

int main()
{
    // 1.判断文件是否能打开
    char filepath[] = "./test.txt";
    FILE *p = fopen(filepath, "r");
    if (p)
    {
        printf("open %s success\n", filepath);
        // 2.读取内容
        char c = getc(p); // 读取一个字节内容
        printf("%c\n", c);
        c = getc(p); // 再读取一个字节内容
        printf("%c\n", c); 
    }
    else
    {
        printf("open %s fail\n", filepath);
    }

    // 3.关闭文件
    if (p)
    {
        fclose(p);
        printf("close success\n");
    }
    return 0;
}

用循环打印文件所有内容

#define EOF -1

EOF不是文件的一部分内容,只是文件结束的标识

cpp 复制代码
#include <stdio.h>

int print_file(char *filepath)
{
    char c;
    FILE *p = fopen(filepath, "r");
    if (p)
    {
        printf("open %s success\n", filepath);
        c = getc(p);
        while (c != EOF)
        {
            printf("%c", c);
            c = getc(p);
        }

        fclose(p);
    }
    else
    {
        printf("open %s fail\n", filepath);
        return -1;
    }

    return 0;
}

int main()
{
    // 打开文件
    //char filepath[] = "./test.txt";
    //char filepath[] = "./test.c";
    char filepath[] = "./test.txt";
    int ret = print_file(filepath);
    printf("ret=%d\n", ret);

    return 0;
}

自定义读取文件一行内容的函数

cpp 复制代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int read_line(char *filepath, char **file_data)
{
    FILE *filep = fopen(filepath, "r");
    if (!filep)
    {
        perror("fopen");
        return -1;
    }

    int line_size = 1;
    char *content = (char *)malloc(line_size);
    if (content == NULL)
    {
        perror("malloc");
        fclose(filep);
        return -2;
    }

    // 初始化content为空字符串 
    content[0] = '\0';

    int count = 0;
    int is_finish = 0;
    int i = 0;
    while (1)
    {
        char temp[1024] = {0};
        for (i = 0; i < 1023; i++)
        {
            char c = getc(filep);
            if (c == '\n' || c == EOF)
            {
                is_finish = 1;
                break;
            }
            temp[i] = c;
            count++;
        }

        
        if (count > 0) // 只有在读取到字符时才进行内存分配和字符串拼接
        {
            if (count >= line_size)
            {
                char *ret = (char *)realloc(content, count+1);
                if (ret == NULL)
                {
                    perror("realloc");
                    free(content); // 释放之前的内存
                    fclose(filep);
                    return -3;
                }
                content = ret;
                line_size = count + 1;
            }

            strcat(content, temp);

            // 添加空字符,确保字符串正确终止
            content[count] = '\0'; 
        }

        if (is_finish)
        {
            break;
        }
       
       
    }
    
    // printf("(%s)\n", content);   // 文件内容
    // printf("count=%d\n", count); // 字节数

    *file_data = content;

    fclose(filep);

    return 0;
}

int main()
{
    char filepath[100] = "./test.txt";
    char *file_content;

    int ret = read_line(filepath, &file_content);
    printf("ret = %d\n", ret);

    if (ret == 0)
    {
        printf("(%s)\n", file_content);
        printf("content len=%d\n", strlen(file_content));

        free(file_content);  
    }
    else
    {
        printf("Error reading line from file\n");  
    }


    return 0;
}

putc

函数原型

cpp 复制代码
int putc(int c, FILE *stream);

第一个参数是要写入的char, 第二个参数是fopen返回的文件指针

getc参数的文件指针必须是用r模式打开,putc必须是用w模式打开

cpp 复制代码
#include <stdio.h>

int main()
{
    // 打开文件
    char filepath[] = "./test.txt";
    FILE *filep = fopen(filepath, "w");
    if (filep)
    {
        // 写入内容
        putc('a', filep);
        putc('\n', filep);
        putc('b', filep);
        putc('c', filep);
        // 关闭文件
        fclose(filep);
    }
    else
    {
        printf("open fail\n");
    }
    
    return 0;
}

自定义将字符串写入文件的函数

cpp 复制代码
#include <stdio.h>
#include <string.h>

void write_str(char *filepath, const char *s, unsigned int len)
{
    FILE *filep = fopen(filepath, "w");
    if (filep)
    {
        for (int i = 0; i < len; i++)
        {
            printf("%c", s[i]);
            putc(s[i], filep);
        }
        fclose(filep);
    }
    else
    {
        printf("open fail\n");
    }
}

int main()
{
    char filepath[] = "./test.txt";
    char mystr[50] = "hello world";
    int len;
    // len = sizeof(mystr)/sizeof(mystr[0]);
    len = strlen(mystr); //用字符串长度
    write_str(filepath, mystr, len);

    return 0;
}

自定义文件拷贝函数实现命令行命令

使用c语言的main函数还可以自定义命令行的命令

比如可执行文件的名字叫 copyfile,通过设计源代码的main函数可以达到命令行的作用

  1. gcc test.c -o copyfile
  2. copyfile test.txt test_write.txt
  3. 可以实现将test.txt拷贝为test_write.txt

示例代码

cpp 复制代码
#include <stdio.h>

void copyfile(char *srcPath, char *tarPath)
{
    FILE *readp = fopen(srcPath, "r");
    FILE *writep = fopen(tarPath, "w");
    if (readp && writep)
    {
        while (1)
        {
            char c = getc(readp);
            if (c == EOF)
            {
                break;
            }
            putc(c, writep);
        }
    }

    fclose(readp);
    fclose(writep);
}

int test()
{
    char filepath[] = "./test.txt";
    char writePath[] = "./test_write.txt";
    copyfile(filepath, writePath);
    return 0;
}

int main(int argc, char **args)
{
    if (argc < 3)
    {
        printf("params less than 3");
        return -1;
    }

    copyfile(args[1], args[2]);

    return 0;
}

运行

bash 复制代码
[root@localhost test]# gcc main.c -o main
[root@localhost test]# ./main test.txt test_copy.txt

简单的文件加密和解密

加密:将一份文件拷贝时,给每个字符通过加减乘除或者指定函数等指定规则变为另一个字符再写入另一个文件。

解密:按加密的方式逆向获得原字符。

示例代码:

cpp 复制代码
#include <stdio.h>

void copyfile(char *srcPath, char *tarPath, char isEncode)
{
    FILE *readp = fopen(srcPath, "r");
    FILE *writep = fopen(tarPath, "w");
    if (readp && writep)
    {
        while (1)
        {
            char c = getc(readp);
            if (c == EOF)
            {
                break;
            }

            if (isEncode == '+')
                c++;
            else
                c--;
            
            putc(c, writep);
        }
    }

    fclose(readp);
    fclose(writep);
}

int test()
{
    char filepath[] = "./test.txt";
    char writePath[] = "./test_write.txt";
    copyfile(filepath, writePath, '+');
    return 0;
}

int main(int argc, char *argv[])
{
    if (argc < 4)
    {
        printf("params less than 3");
        return -1;
    }

    copyfile(argv[1], argv[2], argv[3][0]);

    return 0;
}

运行效果

bash 复制代码
[root@localhost test]# gcc main.c -o main
[root@localhost test]# ./main test.txt test_encode.txt +
[root@localhost test]# cat test.txt
hello world
[root@localhost test]# cat test_encode.txt 
ifmmp!xpsme
           [root@localhost test]# 
[root@localhost test]# ./main test_encode.txt test_decode.txt -
[root@localhost test]# cat test_decode.txt 
hello world
[root@localhost test]# 

5.EOF与feof函数

程序怎么才能知道已经到结尾了呢?文本文件内容的最后一个char是EOF。

EOF不属于文件的内容,只是文件的结尾标识,而且也不要直接用-1来替代EOF。

只有文本文件才可以通过EOF来判断文件的结尾标示,文本文件的结尾标示是EOF,但二进制文件不是,对于二进制文件EOF是无效的,二进制文件就需要用feof这个函数。feof也可以用在文本文件。

函数原型:

cpp 复制代码
int feof(FILE *stream);

参数就是fopen函数返回的指针。

文件指针所在位置:初始在第一行的开头,即第1个字符前面那。后续每次读取了内容,就在已读取内容的最后一个字符和下一个字符之间。读取内容的最后一个字符是当前行的换行符时,读取后,文件指针在下一行的开头。

feof的原理:EOF在最后一行末尾,在最后一行时,feof(filep)不会为True,读文件的函数不仅会把最后一行的文件内容读到,也会把EOF读到。示例代码理解:

cpp 复制代码
#include <stdio.h>

// 1.用feof
int main01()
{
    char filepath[] = "./test.txt";
    FILE *p = fopen(filepath, "r");

    int count = 0;

    if (p)
    {
        while (!feof(p))
        {
            char c = getc(p);
            // if (c == EOF){break;} // 不读取最后一个字符,即文件结尾符EOF

            // 打印字符和对应的ASCII码
            printf("(%c,%d)=>", c, c);
            count++;
        }
        fclose(p);
    }
    else
    {
        printf("open fail\n");
    }

    printf("count = %d\n", count); // count = 8

    return 0;
}



// 2.用EOF
int main02()
{
    char filepath[] = "./test.txt";
    FILE *p = fopen(filepath, "r");

    int count = 0;

    char c = getc(p);
    if (p)
    {
        while (c != EOF)
        {
            // 打印字符和对应的ASCII码
            printf("(%c,%d)=>", c, c);
            count++;
            c = getc(p);
        }
        fclose(p);
    }
    else
    {
        printf("open fail\n");
    }

    printf("count = %d\n", count); // count = 7
    return 0;
}


int main()
{
    main01();
    main02();

    return 0;
}

运行结果

bash 复制代码
[root@localhost test]# gcc main.c -o main
[root@localhost test]# cat test.txt
hello world
[root@localhost test]# ./main
(h,104)=>(e,101)=>(l,108)=>(l,108)=>(o,111)=>( ,32)=>(w,119)=>(o,111)=>(r,114)=>(l,108)=>(d,100)=>(
,10)=>(�,-1)=>count = 13
(h,104)=>(e,101)=>(l,108)=>(l,108)=>(o,111)=>( ,32)=>(w,119)=>(o,111)=>(r,114)=>(l,108)=>(d,100)=>(
,10)=>count = 12
[root@localhost test]# 

6.fputs和fgets 按照行读写文件

fputs 将字符串写入文件

函数原型

cpp 复制代码
int fputs(const char *str, FILE *stream);

该函数返回一个非负值,如果发生错误则返回 EOF(-1)。

示例代码:

cpp 复制代码
#include <stdio.h>

int main()
{
    FILE *filep = fopen("./fputs.txt", "w");
    if (filep)
    {
        fputs("hello world\n", filep);
        fputs("xxxx\n", filep);

        char info[10] = {0};
        sprintf(info, "work %d%d%d\n", 9, 9, 6);
        fputs(info, filep);

        fclose(filep);
    }
    else
    {
        perror("open file fail");
    }
        
    return 0;
}

fgets 从键盘输入或者文件输入

函数原型

cpp 复制代码
char *fgets(char *s, int size, FILE *stream);

参数:

  • s: 字符型指针,指向存储读入数据的缓冲区的地址
  • size: 从流中读入 size - 1 个字符,放入s的前size-1个位置,然后将第size个位置处放'\0' 字符。
  • stream: 指向读取的流。如果要从键盘输入,则传入stdin即可。如果要将文件作为输入来源,则传入对应的文件指针即可。

返回值:

  • 如果读入错误或遇到文件结尾(EOF),则返回NULL
  • fgets的返回值是 char *,代表函数读到的字符串的首地址,如果fgets到了文件末尾,继续调 用,返回NULL

fgets读取原理:

  • fgets 执行时会从文件流的当前位置读取 size - 1 个字符,然后在读取到的这些字符后面添加 一个 '\0' 字符并将这些字符放入 s 中,也就是第size位置处放'\0' 字符,作为字符串的结尾。
  • 如果 fgets 在执行时发现文件流的当前位置到换行符或文件末尾之间的这些字符不够 size - 1 个字符,则将不够数的字符统统用 '\0' 代替。
  • 换行符也是当前行的内容
cpp 复制代码
#include <stdio.h>
#include <string.h>

int main1()
{
    FILE *filep = fopen("./fputs.txt", "w");
    if (filep)
    {
        while (1)
        {
            // get data from keyboard
            char buf[1024] = {0};
            fgets(buf, sizeof(buf), stdin);
            if (strncmp(buf, "exit", 4) == 0)
                break;
            
            // write data into file
            fputs(buf, filep);
        }

        fclose(filep);
    }
    
    return 0;
}

int main2()
{
    FILE *filep = fopen("./fputs.txt", "r");
    char buff[10] = {0};
    if (filep)
    {
        // read data line by line from file
        while (fgets(buff, sizeof(buff), filep) != NULL)
        {
            printf("(%s)", buff);
        }
        fclose(filep);
    }

    return 0;
}

int main()
{
    main1();

    main2();

    return 0;
}

运行效果

bash 复制代码
[root@localhost test]# gcc main.c -o main
[root@localhost test]# ./main
hello world
work for life
exit
(hello wor)(ld
)(work for )(life
)[root@localhost test]# 

用fgets和fputs进行文件拷贝

cpp 复制代码
#include <stdio.h>

void copyfile(char *srcPath, char *tarPath)
{
    FILE *readp = fopen(srcPath, "r");
    if (!readp)
    {
        printf("fopen %s fail\n", srcPath);
        return;
    }

    FILE *writep = fopen(tarPath, "w");
    if (readp && writep)
    {
        while (!feof(readp))
        {
            char buff[1024] = {0};
            if (fgets(buff, sizeof(buff), readp) == NULL)
            {
                break;
            }
            // printf("(%s)\n", buff);
            fputs(buff, writep);
        }

        fclose(readp);
        fclose(writep);
    }

}

int main()
{
    char filepath[] = "./test.txt";
    char writePath[] = "./test_write.txt";
    copyfile(filepath, writePath);

    return 0;
}

7.fprintf和fscanf 按指定格式读写文件

fscanf从文件内容中读取指定的内容

fprintf输出指定格式内容到文件中

cpp 复制代码
int fscanf(FILE *stream, const char *format, [argument...])
int fprintf(FILE *stream, const char *format, [ argument ]...)

fscanf函数返回值:

  • 读文件成功,则返回成功读取的项数
  • 读文件失败,则返回EOF

fprintf()函数返回值

  • 写文件成功,则返回写入的总字符数
  • 写文件失败,则返回负值

解析文件内容并追加结果

cal.txt

bash 复制代码
12+3=
14*6=
5/7=
32-56=

示例代码

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int myfunc(int a, char b, int c)
{
    switch (b)
    {
    case '+':
        return a + c;
    case '-':
        return a - c;
    case '*':
        return a * c;
    case '/':
        if (c > 0)
            return a / c;
        else
            return 0;
    default:
        printf("error");
        return 0;
    }
}

int main()
{
    FILE *filep = fopen("./cal.txt", "r");
    FILE *writep = fopen("./cal_f.txt", "w");
    if (filep && writep)
    {
        while (1)
        {
            int a, c;
            char b;
            int ret;
            //读取文件一行
            ret = fscanf(filep, "%d%c%d=", &a, &b, &c);
            printf("\nret=%d\n", ret);
            /*
            当前文件最后一行具有内容时,此时EOF在最后一行末尾
            文件指针在最后一行时,feof(filep)会为真,fgets调用buff也会读取到内容,
            在fgets调用后feof(filep)会为假
            因此判断feof(filep)和处理内容谁前谁后很关键
            先处理内容,再判断feof(filep)是否退出
            */

            // 处理内容
            if (ret == 3)
            {
                printf("%d%c%d=%d\n", a, b, c, myfunc(a, b, c));
                fprintf(writep, "%d%c%d=%d\n", a, b, c, myfunc(a, b, c));
            }

            // 判断文件是否结束
            if (feof(filep))
            {
                break;
            }

        }

        fclose(filep);
        fclose(writep);
    }

    return 0;
}

cal_f.txt

bash 复制代码
12+3=15
14*6=84
5/7=0
32-56=-24

文件内容特定条件查找

文件:name_age.txt

bash 复制代码
name=liudehua,age=50
name=anbei,age=30
name=zhangxueyou,age=45
name=canglaoshi,age=70

要求:打印年龄第二大的人的姓名

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define NUM 100

int main()
{
    FILE *filep = fopen("./name_age.txt", "r");

    int maxage = 0;
    int max2age = -1;
    char maxname[100] = {0};
    char max2name[100] = {0};

    if (filep)
    {
        while (1)
        {
            printf("------------------\n");
            //读取文件一行
            char buff[1024] = {0};
            fgets(buff, sizeof(buff), filep);
            /*
            当前文件最后一行具有内容时,此时EOF在最后一行末尾
            文件指针在最后一行时,feof(filep)会为真,fgets调用buff也会读取到内容,
            在fgets调用后feof(filep)会为假
            因此判断feof(filep)和处理内容谁前谁后很关键
            先处理内容,再判断feof(filep)是否退出
            */
            printf("(%s)\n", buff);

            if (strncmp(buff, "name", 4) == 0)
            {
                //用,分割得到姓名
                char *s;
                s = strtok(buff, ",");
                printf("%s\n", s);
                char name[100] = {0};
                sscanf(s, "name=%s", name);
                printf("%s\n", name);

                //用,分割得到年龄
                s = strtok(NULL, ",");
                printf("%s", s);
                int age = 0;
                sscanf(s, "age=%d", &age);
                printf("%d\n", age);

                //更新最大值和姓名
                if (age > maxage)
                {
                    strcpy(max2name, maxname);
                    strcpy(maxname, name);
                    max2age = maxage;
                    maxage = age;
                }
                else if ((max2age < age) && (age < maxage))
                {
                    strcpy(max2name, name);
                    max2age = age;
                }
            }

            if (feof(filep))
            {
                break;
            }
            
        }
        fclose(filep);
    }

    printf("------------------\n");
    printf("%s,%d\n", maxname, maxage);
    printf("%s,%d\n", max2name, max2age);

    return 0;
}

8.stat函数

头文件

cpp 复制代码
#include <sys/stat.h>
#include <sys/types.h>

函数原型

cpp 复制代码
int stat(const char * Filename, struct stat *_Stat)

参数:

  • 第一个参数代表文件名
  • 第二个参数是struct stat结构,传出参数
  • 返回值:执行成功返回0,失败返回-1

函数作用:通过传出参数,得到文件的属性,包括文件建立时间,文件大小,最后访问时间,最后修改时间等信息,比如stat.st_size;得到文件大小,单位字节

结构体struct stat的参数说明

cpp 复制代码
struct stat
{
    dev_t st_dev; //device 文件的设备编号
    ino_t st_ino; //inode 文件的索引节点
    mode_t st_mode; //protection 文件的类型和存取的权限
    nlink_t st_nlink; //number of hard links 连到该文件的硬连接数目,
    //刚建立的文件值为1.
    uid_t st_uid; //user ID of owner 文件所有者的用户识别码
    gid_t st_gid; //group ID of owner 文件所有者的组识别码
    dev_t st_rdev; //device type 若此文件为装置设备文件, 则为其设备编号
    off_t st_size; //total size, in bytes 文件大小, 以字节计算
    unsigned long st_blksize;
    //blocksize for filesystem I/O 文件系统的I/O 缓冲区大小.
    unsigned long st_blocks;
    //number of blocks allocated 占用文件区块的个数,
    //每一区块大小为512 个字节.
    time_t st_atime; //time of lastaccess 文件最近一次被存取或被执行的时间,
    //一般只有在用mknod、utime、read、write 与tructate 时改变.
    time_t st_mtime; //time of last modification 文件最后一次被修改的时间,
    // 一般只有在用mknod、utime 和write 时才会改变
    time_t st_ctime; //time of last change i-node 最近一次被更改的时间,
    //此参数会在文件所有者、组、权限被更改时更新
};

示例代码

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
//#include <unistd.h>
#include <time.h>
#define NUM 100

int main()
{
    struct stat st = {0};
    int ret = stat("test.txt", &st);
    //调用完stat函数后,就得到了文件的各种属性

    printf("%d\n", ret); // 0
    if (ret == 0)
    {
        //文件大小
        int size = st.st_size;
        printf("%d\n", size); // 28

        struct tm *info;
        char buffer[80];

        //最近一次被执行的时间
        time_t atime = st.st_atime;
        printf("%d\n", atime); // 1668269249
        info = localtime(&atime);
        strftime(buffer, 80, "%Y-%m-%d %H:%M:%S", info);
        printf("%s\n", buffer); // 2022-11-13 00:07:29

        //最后一次被修改的时间
        time_t mtime = st.st_mtime;
        printf("%d\n", mtime); // 1668261399
        info = localtime(&mtime);
        strftime(buffer, 80, "%Y-%m-%d %H:%M:%S", info);
        printf("%s\n", buffer); // 2022-11-12 21:56:39
        
        //最近一次被更改的时间,此参数会在文件所有者、组、权限被更改时更新
        time_t ctime = st.st_ctime;
        printf("%d\n", ctime); // 1668252245
        info = localtime(&mtime);
        strftime(buffer, 80, "%Y-%m-%d %H:%M:%S", info);
        printf("%s\n", buffer); // 2022-11-12 21:56:39
    }
    else
    {
        printf("stat error\n");
    }

    return 0;
}

9.fread和fwrite 读写二进制数据

函数说明

fgets fputs fprintf, fscanf这些函数都是针对文本文件读写的,不能对一个二进制文件进行读写。

除了文本文件之外的文件都是二进制,比如图像,可执行程序,音乐,视频这些都是二进制 的(其实文本文件是特殊的二进制文件,只是编码方式不同而已,对于数值类型,文本文件 按字符编码,二进制文件按值编码)

fprintf等都是往文本文件里面写一个字符串,比如将123456写入文件,实际上把'1', '2', '3', '4', '5', '6'这6个字符写入文件。如果要把一个int整数写入文件,这个文件就不是文本文件了。

函数原型

cpp 复制代码
size_t fread(void *buffer, size_t size, size_t count, FILE *stream);
size_t fwrite(void *buffer, size_t size, size_t count, FILE *stream);

参数:

  • 第一个参数是buff的内存地址,表示要写什么
  • 第二个参数是每个单位的大小
  • 第三个参数是写 多少个单位(只需要第二个参数和第三个参数的乘积与 第一个参数指定的内存区域一样大即可)
  • 第四个参数是fopen返回的文件指针。

这个函数以二进制形式对文件进行操作,不局限于文本文件

返回值:返回实际写入的数据块数目

示例代码

cpp 复制代码
#include <stdio.h>

int main1()
{
    FILE *filep = fopen("./test.dat", "w");

    int i = 100;
    fwrite(&i, 1, sizeof(int), filep);
    
    short x = 50;
    fwrite(&x, 1, sizeof(short), filep);

    fclose(filep);

    return (0);
}

int main2()
{
    FILE *filep = fopen("./test.dat", "r");

    int a = 0;
    fread(&a, 1, sizeof(int), filep);
    printf("a=(%d)\n", a);

    short b = 0;
    fread(&b, 1, sizeof(short), filep);
    printf("b=(%d)\n", b);

    
    fclose(filep);
    return (0);
}


int main()
{
    // 1.fwrite
    main1();

    // 2.fread
    main2();

    return 0;
}

fread的返回值

  • fread第二个参数代表了一个单位多大,第三个参数一次要读多少单位
  • fread返回值是成功读取到的单位个数。fread的返回值是,实际读到的内容大小/单位大 小,比如函数读的单位是2个字节,实际读了8个字节的内容,则fread返回4。再比如单位是 4个字节,但只读了2个字节的内容,则返回0
  • 当fread的第二个参数是1时,可以认为fread返回的是读取到的字节数
cpp 复制代码
#include <stdio.h>
int main()
{
    FILE *filep = fopen("./test.dat", "r");
    int a = 0;
    while (1)
    {
        int res = fread(&a, 1, sizeof(int), filep);
        printf("%d\n", res); //4
        if (feof(filep))
        {
            break;
        }
        printf("%d\n", a);
    }
    fclose(filep);
    return (0);
}

实现二进制文件的拷贝

比如图像,可执行程序,音乐,视频这些都是二进制的,都可以用程序拷贝

一个字节一个字节的拷贝

下面代码速度较慢,因为是读一个字节,写一个字节

cpp 复制代码
#include <stdio.h>

int main()
{
    FILE *readp = fopen("./test.txt", "rb");
    if (readp)
    {
        printf("open read");
    }

    FILE *writep = fopen("./test2.txt", "wb");
    if (writep)
    {
        printf("open write");
    }

    while (1)
    {
        char a = 0;
        fread(&a, 1, 1, readp);
        if (feof(readp))
        {
            break;
        }
        fwrite(&a, 1, 1, writep);
    }
    
    fclose(readp);
    fclose(writep);

    return 0;
}

用栈每次拷贝多个字节

读1024个字节,写1024个字节,不够1024时,读多少写多少

cpp 复制代码
#include <stdio.h>

int main()
{
    FILE *readp = fopen("./test.txt", "rb");
    FILE *writep = fopen("./test2.txt", "wb");
    if (writep && readp)
    {
        while (!(feof(writep)))
        {
            char a[1024] = {0};
            int res = fread(&a, 1, sizeof(a), readp);
            fwrite(&a, 1, res, writep); //用res读多少,写多少
        }
    }
    fclose(readp);
    fclose(writep);
    return 0;
}

用堆动态分配内存拷贝

可以根据文件大小,动态分配一个堆内存出来

但是如果这个文件的内存很大一下把内存撑爆了

可以得到文件size后,判断size大小决定是否分成几次来写

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>

#define NUM 1024 * 64 //64 k

int main()
{
    FILE *readp = fopen("./test.txt", "rb");
    FILE *writep = fopen("./test3.txt", "wb");
    if (writep && readp)
    {
        struct stat st = {0};
        stat("test.txt", &st);
        int size = st.st_size;
        if (size >= NUM)
        {
            size = NUM;
        }

        char *buff = malloc(size);

        while (!(feof(readp)))
        {
            //char buff[1024] = {0};
            int res = fread(buff, 1, size, readp);
            fwrite(buff, 1, res, writep);
        }

        free(buff);
        fclose(readp);
        fclose(writep);
        printf("success");
    }
    
    return 0;
}

读写结构体数据

将结构体数据写入文件中,读取时直接就拿到这个结构体,方便。

把一个结构变量写入文件的时候,结构变量成员的对齐内存浪费部分同样也会写入文件。

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

struct man
{
    char name[20];
    int age;
};

int main()
{
    //将结构体等数据写入dat文件
    char filename[] = "xxx.dat";
    FILE *writep = fopen(filename, "wb");

    struct man m = {"tom", 12};
    fwrite(&m, 1, sizeof(struct man), writep);

    int x = 100;
    fwrite(&x, 1, sizeof(x), writep);

    fclose(writep);

    //从dat中读取数据
    FILE *readp = fopen(filename, "rb");

    struct man newm;
    fread(&newm, 1, sizeof(newm), readp);
    printf("%s, %d\n", newm.name, newm.age); // tom, 12

    int j = 0;
    fread(&j, 1, sizeof(int), readp); // j = 100
    printf("j = %d\n", j);

    fclose(readp);

    //读到堆中
    readp = fopen(filename, "rb");
    struct man *datap = malloc(sizeof(struct man));
    fread(datap, 1, sizeof(struct man), readp);
    printf("%s, %d\n", datap->name, datap->age); // tom, 12

    free(datap);

    int *intp = malloc(sizeof(int));
    fread(intp, 1, sizeof(intp), readp);
    printf("%d\n", *intp); // 100
    
    free(intp);
    fclose(readp);

    return 0;
}

10.fseek函数

FILE指针原理

  • FILE结构内部是有一个指针的,每次调用文件读写函数,这些函数就会自动移动这个指针 默认情况下指针只能从前往后移动。
  • c语言文件读写库函数都是自动维护FILE里面的相关成员,包括文件读写当前位置。 每次调用fgets,或者fread,下次再调用的时候,就会自动从已读取内容后面开始。
  • feof为什么能知道是否到了文件结尾了呢?fread或者fgets这些函数如果读完文件所有内容,他们会设置FILE里面相关变量的值。
  • feof只是判断FILE结构里面相关变量的值是否为文件已经结尾状态。
  • fopen成功打开一个文件后,文件指针默认都是在0 SEEK_SET这个位置。

fseek函数使用与技巧

事实上这个指针位置我们可以自己来调,需要用fseek这个函数。

cpp 复制代码
int feek(FILE *_File, long _Offset, int _Origin);
  • 第一个参数是fopen返回的文件指针
  • 第二个参数是偏移量。正数表示向后偏移,负数表示向前偏移。
  • 第三个参数是设置从哪里开始偏 移,可能取值为:SEEK_CUR,SEEK_END或者SEEK_SET。
bash 复制代码
SEEK_SET: 文件开头
SEEK_CUR:文件当前位置
SEEK_END:文件结尾

函数作用:设置文件的指针stream的位置。如果执行成功,stream将指向以fromwhere为基准,偏移offset个字节的位置,函数返回0。如果执行失败则不改变stream指向的位置,函数返 回一个非0值。

一些小技巧

  • fseek(readp, 0, SEEK_SET); 回到文件开始位置
  • fseek(readp, 0, SEEK_END); 回到文件结尾位置

注意事项

  • 实验得出,超出文件末尾位置,还是返回0,往回偏移超出首位置,还是返回0,请小心使 用。
  • 并且超出文件末尾或者首位置,读到的数据都是0

示例代码理解

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
    FILE *writep = fopen("a.dat", "w");
    char a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    fwrite(a, 1, sizeof(a), writep);
    fclose(writep);

    FILE *readp = fopen("a.dat", "r");
    fseek(readp, 2, SEEK_SET);
    // while (!feof(readp))
    for (int i = 0; i < 2; i++)
    {
        char a[2];
        fread(a, 1, sizeof(a), readp);
        printf("%d, %d\n", a[0], a[1]);
    }
    fclose(readp);

    /*
    3, 4
    5, 6
    */

    return 0;
}

用fseek快速生成超大文件

快速生成一个超大文件,因为需要经常测试代码的文件读写情况。

原始的慢方法

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
    FILE *writep = fopen("a.dat", "w");
    for (int i = 0; i < 10000000; i++)
    {
        char a = 0;
        fwrite(&a, 1, sizeof(a), writep);
    }
    fclose(writep);
    return 0;
}

用fseek技巧快速生成(超大空文件)

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
    FILE *writep = fopen("a.dat", "w");
    fseek(writep, 1000000, SEEK_SET);
    char a = 0;
    fwrite(&a, 1, sizeof(a), writep);
    fclose(writep);
    return 0;
}

11.ftell函数

函数ftell用于得到文件指针当前位置相对于文件首的偏移字节数。在随机方式存取文件时,由于文件位置频繁地前后移动,程序不容易确定文件文件的当前位置。

cpp 复制代码
long loc = ftell(fp);

以下代码通过ftell函数和feof函数,可以得出:

  • 文件的EOF在最后一行末尾,无论最后一行是"文件内容+EOF",还是只有"EOF", feof(readp)都为假
  • fread读最后一块内容时:
    • 如果是"文件内容+EOF",则会去掉EOF
    • 如果是只有"EOF",则读不到任何数据,此时用fread函数不会修改传入地址的值

示例代码

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
    FILE *writep = fopen("a.dat", "w");
    char a[3] = {1, 2, 3};
    fwrite(a, 1, sizeof(a), writep);
    fclose(writep);

    printf("--------------\n");
    FILE *readp = fopen("a.dat", "r");
    //fseek(readp, 0, SEEK_SET);
    long loc = ftell(readp);
    printf("loc = %lu\n", loc); //loc = 0

    printf("--------------\n");
    char b;
    while (!feof(readp))
    {
        fread(&b, 1, 1, readp);
        printf("%d\n",b);
        long loc = ftell(readp);
        printf("loc = %lu\n", loc);
    }

    printf("--------------\n");
    fseek(readp, 0, SEEK_END);
    loc = ftell(readp);
    printf("loc = %lu\n", loc);
    char x = 0;
    fread(&x, 1, 1, readp);
    printf("x = %d\n",x);

    printf("--------------\n");
    fseek(readp, -1, SEEK_END);
    loc = ftell(readp);
    printf("loc = %lu\n", loc);
    char y = 0;
    fread(&y, 1, 1, readp);
    printf("y = %d\n", y);

    fclose(readp);

    return 0;
}

运行结果

bash 复制代码
--------------
loc = 0
--------------
1
loc = 1
2
loc = 2
3
loc = 3
3
loc = 3
--------------
loc = 3
x = 0
--------------
loc = 2
y = 3

12.fflush函数和文件读写缓冲区

c语言所有的文件操作函数都是缓冲区函数

以下代码只有在fclose(writep);后文件a.txt中才会有内容,说明是缓冲区库函数

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    FILE *writep = fopen("a.txt", "w");
    while (1)
    {
        char a[100] = {0};
        scanf("%s", a);
        if (strcmp(a, "exit") == 0)
        {
            break;
        }
        fprintf(writep, "%s\n", a);
    }
    fclose(writep);
    return 0;
}

用ffush函数跳过内存直接写入磁盘

cpp 复制代码
int fflush(FILE *_File)

由于fflush是实时的将缓冲区的内容写入磁盘,所以不要大量去使用,因为fflush相当于是直接 用磁盘读写而不是通过内存再到磁盘,所以速度慢,而且频繁读写磁盘会降低磁盘寿命,但如果 是重要的数据,可以用fflush写入磁盘。

13.remove 删除文件

删除指定文件

cpp 复制代码
int remove(const char *Filename);

没有该文件时不会报错

cpp 复制代码
#include <stdio.h>

int main()
{
    char filename[] = "xxx.txt";
    FILE *writep = fopen(filename, "w");
    fclose(writep);
    remove(filename);
    return 0;
}

14.rename 文件改名或者剪切

cpp 复制代码
int rename(const char *OldFilename, const char *NewFilename);

示例代码

cpp 复制代码
#include <stdio.h>
int main()
{
    char filename[] = "xxx.txt";
    FILE *writep = fopen(filename, "w");
    fclose(writep);
    char newname[] = "../yyy.txt";
    rename(filename, newname);
    return 0;
}


相关推荐
逊嘘6 分钟前
【Java语言】抽象类与接口
java·开发语言·jvm
xinghuitunan6 分钟前
蓝桥杯顺子日期(填空题)
c语言·蓝桥杯
Half-up8 分钟前
C语言心型代码解析
c语言·开发语言
Source.Liu30 分钟前
【用Rust写CAD】第二章 第四节 函数
开发语言·rust
monkey_meng30 分钟前
【Rust中的迭代器】
开发语言·后端·rust
余衫马33 分钟前
Rust-Trait 特征编程
开发语言·后端·rust
monkey_meng36 分钟前
【Rust中多线程同步机制】
开发语言·redis·后端·rust
Jacob程序员38 分钟前
java导出word文件(手绘)
java·开发语言·word
懒大王就是我44 分钟前
C语言网络编程 -- TCP/iP协议
c语言·网络·tcp/ip
小白学大数据1 小时前
正则表达式在Kotlin中的应用:提取图片链接
开发语言·python·selenium·正则表达式·kotlin