C语言——文件操作

1.为什么使用文件

使用文件可以存放数据,使里面的内容永久保存下来,下次打开的时候不会丢失

2.什么是文件

磁盘上的文件就是文件

但程序设计中,主要分为程序文件和数据文件

2.1程序文件

源文件(后缀.c),目标文件(windows环境后缀位.obj),可执行程序(windows环境后缀为.exe)

2.2数据文件

文件内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件。或者输出内容的文件。

2.3 文件名

一个文件要有唯一的文件标识(文件名),以便用户识别和引用。

文件名包括三部分:文件路径+文件名主干+文件后缀

例:c:code\test.txt 其中c:code是文件路径,test.txt是文件名主干和文件后缀

3.文件的打开和关闭

3.1文件指针

关键的概念是“文件类型指针”,简称“文件指针”。

每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态以及文件当前位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名FILE。

 

fopen

可以搜一下MSDN来搜其功能

在操纵其他文件的路径的话要在文件名前加上绝对路径,否则只会在运行程序当前的路径新建。(ps:注意下方的w装的是双引号

int main()
{
  FILE* pf = fopen("test.txt","w");
  if(NULL==pf)
  {
      printf("打开文件失败\n");
      return 0;
  }
  return 0;
}

pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件。

当出现error时,会返回一个空指针

3.2文件的打开和关闭

fclose

int main()
{
  //打开文件
  FILE* pf = fopen("test.txt","w");
  if(NULL==pf)
  {
      printf("打开文件失败\n");
      return 0;
  }
  //写文件
  
  //关闭文件
  fclose(pf);
  pf=NULL;
  return 0;
}

4.文件的顺序读写

字符输入函数——fgetc

字符输出函数——fputc

文本行输入函数——fgets

文本行输出函数——fputs

写文件

int main()
{
    FILE* pf = fopen("data.txt","w");
    if(pf==NULL)
    {
        printf("%s\n",strerror(errno));
        return 0;
    }
    //写文件
    //fputc('a',pf);
    //fputc('b',pf);
    //fputc('c',pf);
    char ch=0;
    for(ch='a',ch<='z';ch++)
    {
        fputc(ch,pf);
    }
    
    fclose(pf);
    pf=NULL;
}

A:我们都知道,对文件进行操作的表现是需要用代码来进行输入以及输出的,但是为什么c语言程序输出到屏幕时不需要写什么代码?

Q:C语言程序,只有运行起来。就默认打开三个流:

FILE*类型:stdin-标准输入流    stdout-标准输出流     stderr-标准错误流

所以才能直接在屏幕上出现

所以,当我们在使用fputc中使用pf时,为文件流,在文件中输出,将这一行代码的pf改为strout标准输出流,就会在屏幕上打印出来。


读文件

fgetc()——字符输入函数—— int fgetc(FILE* stream)

(接上面)
int ch=fgetc(pf);
printf("%c\n",ch);
//继续往下读
int ch=fgetc(pf);
printf("%c\n",ch);
//继续往下读
int ch=fgetc(pf);
printf("%c\n",ch);

int ch=fgetc(pf);
printf("%c\n",ch);

这个可以一直读,直到读完了,就返回EOF,或者读取中途出现错误,也返回EOF

while((ch=fgetc(pf))!=EOF)
{
    printf("%c ",ch);
}

fputs——文本行输出操作 int fputs()     (写文件到文本里面去)——标准输出流

写个写一行读一行的操作:

int main()
{
    FILE* pf = fopen("data.txt","w");
    if(pf==NULL)
    {
        printf("%s\n",strerror(errno));
        return 0;
    }
    fputs("hello world",pf);
    
    fclose(pf);
    pf=NULL;
}

标准输出流:既可以写到文本也可以在屏幕

fputs("hello world",stdout);

读一行:

fgets——char* fgets(char* string,int n,FILE* stream)

n——空间容纳最大个数,例如放1000进去只会读取999

#define _CRT_SECURE_NO_WARNINGS 1
#include 

int main()
{
    FILE* pf = fopen("data.txt", "r");
    if (NULL == pf)
    {
        printf("打开失败\n");
        return 0;
    }
    char arr[1000] = { 0 };
    fgets(arr, 1000, pf);
    printf("%s\n", arr);
    fclose(pf);
    pf = NULL;
    return 0;
}

写一份文件拷贝

//拷贝文件
#define _CRT_SECURE_NO_WARNINGS 1
#include 
#include 
#include 

int main()
{
    FILE* pr = fopen("data.txt", "r");
    if (NULL == pr)
    {
        printf("openning is:%s\n", strerror(errno));
        return 0;
    }
    FILE* pw = fopen("data2.txt", "w");
    if (NULL == pw)
    {
        printf("openning is:%s\n", strerror(errno));
        return 0;
    }
    int ch = 0;
    while ((ch = fgetc(pr)) != EOF)
    {
        fputc(ch, pw);
    }
    fclose(pr);
    pr = NULL;
    fclose(pw);
    pw = NULL;
    return 0;
}

对于结构化数据,有具体格式化数据的,应该怎么输入输出?

fprintf——格式化输出数据——int fprintf( FILE* stream, const char* format [, argument ]...**);

相较于printf,会发现其实只是多一个FILE*罢了

写格式化数据

//格式化数据的输出
#define _CRT_SECURE_NO_WARNINGS 1
#include 
#include 
#include 
//假设给上一个结构体:
struct Stu
{
    char name[20];
    int age;
    float score;
};

int main()
{
    struct Stu s = { "Lisi",18,90.9 };
    FILE* pf = fopen("data.txt", "w");
    if (NULL == pf)
    {
        printf("openning is %s\n", strerror(errno));
        return 0;
    }
    fprintf(pf, "%s %d %.2f", s.name, s.age, s.score);
    fclose(pf);
    pf = NULL;
    return 0;
}

读格式化数据

fscanf——格式化输入数据 ——int fscanf( FILE *stream, const char *****format [, argument ]... );

我们使用这个与scanf比较,又可以发现只是多了一个FILE*

//读格式化数据
#define _CRT_SECURE_NO_WARNINGS 1
#include 
#include 
#include 

struct Stu
{
    char name[20];
    int age;
    float score;
};

int main()
{
    struct Stu s = { 0 };
    FILE* pf = fopen("data.txt", "r");
    if (NULL == pf)
    {
        printf("error is: %s\n", strerror(errno));
    }
    fscanf(pf, "%s %d %f", s.name, &s.age, &s.score);
    printf("%s %d %f\n", s.name, s.age, s.score);
    fclose(pf);
    pf = NULL;
    return 0;
}

二进制输出

fwrite—— size_t fwrite( const void buffer, size_t size,* size_t count FILE *****stream );

//以二进制的方式写文件
#define _CRT_SECURE_NO_WARNINGS 1
#include 
#include 
#include 

struct Stu
{
    char name[20];
    int age;
    float score;
};

int main()
{
    struct Stu s[2] = { {"Zhangsan",18,90.6},{"Lisi",20,95.5} };
    FILE* pf = fopen("data.txt", "wb");
    if (NULL == pf)
    {
        printf("openning erroe is %s\n", strerror(errno));
        return 0;
    }
    fwrite(s, sizeof(struct Stu), 2, pf);
    fclose(pf);
    pf = NULL;
    return 0;
}

以二进制的方式读

fread——读取文件 size_t fread( void buffer, size_t size, *size_t count,FILE *****stream );

ps:这里fread的返回值是你读到多少个元素就返回多少值,比如一开始设定读五个,如果有五个,就返回五,如果没有,例如只剩下三个了,就只能返回三。

与上面的二进制方式写入的格式是差不多的

//以二进制方式读
#define _CRT_SECURE_NO_WARNINGS 1
#include 
#include 
#include 

struct Stu
{
    char name[20];
    int age;
    float score;
};

int main()
{
    struct Stu s[2] = { 0 };
    FILE* pf = fopen("data.txt", "rb");
    if (NULL == pf)
    {
        printf("error is%s\n", strerror(errno));
        return 0;
    }
    fread(s, sizeof(struct Stu), 2, pf);
    printf("%s %d %f\n", s[0].name, s[0].age, s[0].score);
    printf("%s %d %f\n", s[1].name, s[1].age, s[1].score);
    return 0;
}

sprintf与sscanf

sprintf——把格式化数据转换成字符串 ——int sprintf( char* buffer, const char* format [, argument] ... );

相比fprintf,sprintf是直接把格式化的数据放到了一个字符串内。

sscanf——与sprintf相似。但是是与sprintf功能相反。将一个字符串按照某些我们写的格式放入结构体之中。 ——int sscanf( const char* buffer, const char* format [, argument ] ... );

ps:这里此时已经与文件相关没有关系了。

//sprintf与sscanf的使用
#define _CRT_SECURE_NO_WARNINGS 1
#include 
#include 
#include 

struct Stu
{
    char name[20];
    int age;
    float score;
};

int main()
{
    struct Stu s1 = { "Lisi",20,96.6 };
    char arr[100] = { 0 };
    sprintf(arr, "%s %d %.2f", s1.name, s1.age, s1.score);
    printf("%s\n", arr);
    struct Stu s2 = { 0 };
    sscanf(arr, "%s %d %f", s2.name, &s2.age, &s2.score);
    printf("%s %d %.2f\n", s2.name, s2.age, s2.score);
    return 0;
}

注意区别:

  • scanf——从标准输入流(stdin)上进行格式化输入的函数
  • printf——向标出输出流(stdout)上进行格式化的输出函数
  • fscnf——可以从标准输入流(stdin)/指定的文件流上读取格式化的数据
  • fprintf——把数据按照格式化的方式输出到标准输出流(stdout)/指定文件流
  • sscanf——可以从一个字符串中提取(转化)出格式化数据
  • sprintf——把一个格式化的数据转换成字符串

总结:

C语言——文件操作_第1张图片

 

5.文件的随机读写

5.1fseek

根据文件指针的位置和偏移量来定位文件指针

打开文件时用的是指向起始位置,我们可以通过这个函数来调整指向的位置

在正式使用前,要知道几个参数的含义:

fseek——int fseek( FILE stream, long offset,* int origin );

  • FILE——就是要输入的文件
  • offset——要移动的偏移量大小
  • origin——要怎么样移动

在这里origin又分为一下三种(选择从哪里开始读取):

  • SEEK_SET: 文件开头
  • SEEK_CUR: 当前位置
  • SEEK_END: 文件结尾

ps:当选择END的时候要记得偏移量是负数,因为要倒着数

fseek的实际应用:

输出数据到文件内:

#define _CRT_SECURE_NO_WARNINGS 1
#include 
#include 
#include 

//这时在已经创造了一个名为data的txt文件并且已经放入abcdef的情况下写的

int main()
{
    char ch = 0;
    FILE* pf = fopen("data.txt", "r");
    if (NULL == pf)
    {
        printf("error is%s\n", strerror(errno));
        return 0;
    }
    //单个读取字符
    ch = fgetc(pf);//这里读到字符a
    printf("%c\n", ch);

    ch = fgetc(pf);//这里读到字符b
    printf("%c\n", ch);

    //指定位置到f
    //fseek(pf, 3, SEEK_CUR);//从当前b开始读,往后三个偏移量
    //fseek(pf, 5, SEEK_SET);//从起始位置开始往后五个偏移量指向f
    fseek(pf, -1, SEEK_END);//从末尾开始,往前一个偏移量指向f
    ch = fgetc(pf);//经过移动后,直接跳过读到了字符f
    printf("%c\n", ch);
    //关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}

将文件内的数据修改:

//将文件中的某一位置数据给替换掉
#define _CRT_SECURE_NO_WARNINGS 1
#include 
#include 
#include 


int main()
{
    char ch = 0;
    FILE* pf = fopen("data.txt", "w");
    if (NULL == pf)
    {
        printf("error is%s\n", strerror(errno));
        return 0;
    }
    for (ch = 'a'; ch <= 'z'; ch++)//向data.txt文件先放入a~z
    {
        fputc(ch, pf);
    }
    fseek(pf, -2, SEEK_END);//先指向y的位置
    ch = '#';
    fputc(ch, pf);//把y位置替换成#
    //关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}

5.2 ftell

放回文件指针相对于起始位置的偏移量

ftell——long ftell( FILE *stream );

ftell的实际应用

#define _CRT_SECURE_NO_WARNINGS 1
#include 
#include 
#include 

//这时在已经创造了一个名为data的txt文件并且已经放入abcdef的情况下写的

int main()
{
    FILE* pf = fopen("data.txt", "r");
    if (NULL == pf)
    {
        printf("error is%s\n", strerror(errno));
        return 0;
    }
    char ch = 0;
    ch = fgetc(pf);//此时读入的是a
    printf("%c\n", ch);

    ch = fgetc(pf);//此时读入的是b
    printf("%c\n", ch);

    //假如此时我们不知道指针已经指向哪里了,那么便可以创造一个变量来存储调用ftell函数返回的指针指向的偏移量的值
    int ret = ftell(pf);
    printf("%d\n", ret);

    fclose(pf);
    pf = NULL;
    return 0;
}

5.3 rewind

将文件指针指向起始位置

rewind—— void rewind( FILE * );

//假设有一个FILE*变量为pf,此时这个pf代表的文件的指针不知道指向哪里了
rewind(pf);//这个时候就已经把指针提到起始位置了

6.文本文件和二进制文件

数据文件的分类

数据文件被分为文本文件二进制文件

二进制文件:数据在内存中以二进制的形式存储,如果不加转换的输出到外存。

文本文件:如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件。

数据在内存中的存储

字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。

例如整数10000:

ASII形式存储:

因为有五个字符,所以会用到五个字节,每个字节存储的是对应数字字符

C语言——文件操作_第2张图片

二进制形式存储:

会将10000转化为二进制来存储,用到四个字节。

 

7.文件读取结束的判定

  1. 文本文件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets ) 例如: fgetc 判断是否为 EOF . fgets 判断返回值是否为 NULL .
  2.  二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。 例如: fread判断返回值是否小于实际要读的个数。
#include 
#include 
int main(void)
{
    int c; // 注意:int,非char,要求处理EOF
    FILE* fp = fopen("test.txt", "r");
    if (!fp) {
        perror("File opening failed");
        return EXIT_FAILURE;
    }
    //fgetc 当读取失败的时候或者遇到文件结束的时候,都会返回EOF
    while ((c = fgetc(fp)) != EOF) // 标准C I/O读取文件循环
    {
        putchar(c);
    }
    //判断是什么原因结束的
    if (ferror(fp))//读取失败了,会返回非0值,否则返回0
        puts("I/O error when reading");
    else if (feof(fp))//当是读完全部正常结束的,会返回非0值,否则是因为其他原因结束的,则返回0
        puts("End of file reached successfully");
    fclose(fp);
}

8.文件缓冲区

不同编译器的缓冲方式不同

为了让办事效率高,很多代码都会集中在缓冲区,等到缓冲区放满,到一定程度就会一起运行。

当我们写文件的时候,会先放入缓冲区,不会立马写在文件里面,如果要立马再出去缓冲区,可以刷新缓冲区。

刷新缓冲区的函数——fflush

fflush(FILE*)    fclose也可以刷新缓冲区

你可能感兴趣的:(c语言)