【C语言】0基础教程——文件操作

目录

🍁为什么要使用文件

🍁什么是文件

😶‍🌫️文件

1.程序文件

2.数据文件

我们写代码的时候与文件是什么关系呢?

😶‍🌫️文件名

😶‍🌫️文件指针

文件信息区那这块内存在什么位置呢?

由什么来维护呢?

🍁相关函数

😶‍🌫️文件的打开和关闭(fopen,fclose)

文件有那些打开方式呢?

        例一:只写

        例二:只读

😶‍🌫️文件的顺序读写

1.fputc

        例一

2. fgetc

         例二

3.fguts

        例三

4.fgets

        例四

5.fprintf

        例五

6.fscanf

        例6

7.fwrite

        例7

8.fread

        例子8

9.fseek

        例9

10.rewind

        例10

11.feof

        例11

🍁文本文件和二进制文件

数据在文本文件中和在二进制文件中分别是怎么存储的?

🍁面试题



🍁为什么要使用文件

        在上一篇文章里展示了如何构造一个通讯录,可是一旦程序退出后,空间便还给操作系统,不存在了,但是当我我们需要保留下这些文件时又无可奈何,所以这章通过学习文件操作,便可以实现保存信息功能啦。


🍁什么是文件

😶‍🌫️文件

磁盘上的文件即是文件,但我们从文件功能的角度看又往往分为两类

1.程序文件

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

2.数据文件

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

我们写代码的时候与文件是什么关系呢?

看下图

        可能有人又不明白啦,为什么写是输出到文件,读是输入到内存里呢?

首先,可以先问问自己,真的知道什么内存吗?(博主经常听到有人说自己手机内存256G...一提运行内存就说8G)

        比如,你买了个手机,是8G/256G的,这两个数据分别是什么呢?实际上前面那个数值小一点的叫内存,也就是咱平时口中的运行内存,,而后面那个较大的数值呢,叫外存,也就是咱平时说的什么C盘D盘...这些磁盘。

       理解了这些,再结合上图,就不难理解,从文件中读入数据会存入内存的意思了(实际上就是输入缓冲区)。

😶‍🌫️文件名

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

文件名包含3部分:文件路径+文件名主干+文件后缀

例如:c:\code\test.txt

有些人可能会问,为什么我没有看见文件后缀名呢?

那不妨看看下图这个选项打开了吗?

为了方便起见,文件标识常被称为文件名。

😶‍🌫️文件指针

缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”

每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。

这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名FILE.

听着是不是有点懵?什么意思呢?

        通俗的讲啊,这个文件指针是用来维护一块空间的,怎样的一块空间呢?是一个结构体,在stdio.h定义中被声明为FILE

看以下图解:

假设我的磁盘中有一个文件叫test.txt这样一个文件,如下图:

 一旦当我打开这个文件时,他就会在内存中开辟一块文件信息区(一个结构体,类型是FILE),而FILE ptr(假设创建的变量名为ptr)这个结构体就是这个文件信息区的一块空间(如下图)

 想看这个结构体的成员,可以通过VS2013,(2019已经不显示了)如下:

struct _iobuf 
{       char *_ptr;
        int   _cnt;
        char *_base; 
        int   _flag;
        int   _file;
        int   _charbuf;
        int   _bufsiz;
        char *_tmpfname;
};typedef struct _iobuf FILE;

刚刚提到一旦打开test.c文件就会在内存中创建文件信息区

文件信息区那这块内存在什么位置呢?

由什么来维护呢?

        在C语言的程序里通过fopen这个函数可以打开文件,打开成功会后就会返回这块空间起始位置的地址,这块空间类型为FILE,所以返回的地址就是FILE*类型,程序员就可以通过创建一个FILE*的变量来维护这块空间,这就是文件指针。

下面演示一下如何创建一个指针变量

FILE* pf;//文件指针变量

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

如下图:


🍁相关函数

😶‍🌫️文件的打开和关闭(fopen,fclose)

咱先给出语法格式

//打开文件
FILE * fopen ( const char * filename, const char * mode );
//关闭文件
int fclose ( FILE * stream );

需要注意以下几点:

        文件读写前要打开文件,读写后要关闭文件;

        ANSIC 规定使用fopen函数来打开文件,fclose来关闭文件;

        在编写文件,打开文件的同时fopen会返回一个指针,若打开成功,则返回该文件在文件信息区的起始空间地址,若打开失败则返回空指针,所以我们为了防止程序出bug,因该这样写(如下代码):

#include<stdio.h>
#include<errno.h>
#include<string.h>

int main()
{
	FILE* ptr = fopen("test.txt", "w");
	if (ptr == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//处理
	fclose(ptr);
	return 0;
}

亦或是这样写,如下(报出错误信息即可)

#include<stdio.h>
#include<errno.h>
#include<string.h>

int main()
{
	FILE* ptr = fopen("test.txt", "w");
	if (ptr == NULL)
	{
		perror("fopen");//如果没能打开文件,perror会直接打印错误信息
		return 1;       //括号里是可以任意写的一个常量字符串
	}
	//处理
	fclose(ptr);
	return 0;
}

这样写真的就够了吗?

        想象一下,fclose可以释放带哦ptr所指向的那块空间,而一旦ptr所指向的空间被释放掉,ptr便成了野指针,所以将ptr置为空指针,如下:

#include<stdio.h>
#include<errno.h>
#include<string.h>

int main()
{
	FILE* ptr = fopen("test.txt", "w");
	if (ptr == NULL)
	{
		perror("fopen");//如果没能打开文件,perror会直接打印错误信息
		return 1;       //括号里是可以任意写的一个常量字符串
	}
	//处理
	fclose(ptr);
    ptr = NULL;
	return 0;
}

文件有那些打开方式呢?

 最常用的便是只读、只写,以下为代码演示:

例一:只写

#include<stdio.h>
#include<errno.h>
#include<string.h>

int main()
{

	//打开文件
	FILE* ptr = fopen("test.txt", "w");
	if (ptr == NULL)
	{
		perror("fopen");//如果没能打开文件,perror会直接打印错误信息
		return 1;       //括号里是可以任意写的一个常量字符串
	}

	//处理
	fprintf(ptr, "hello world");

	//关闭文件
	fclose(ptr);
	return 0;
}

例二:只读

#include<stdio.h>
#include<errno.h>
#include<string.h>

int main()
{
	char str[15] = { 0 };
	//打开文件
	FILE* ptr = fopen("test.txt", "w");
	if (ptr == NULL)
	{
		perror("ptr");//如果没能打开文件,perror会直接打印错误信息
		return 1;       //括号里是可以任意写的一个常量字符串
	}

	//处理
	fprintf(ptr, "hello world");

	//关闭文件
	fclose(ptr);

	//打开文件
	FILE* ptr_ = fopen("test.txt", "r");
	if (ptr_ == NULL)
	{
		perror("ptr_");
		return 1;
	}

	//处理
	fgets(str, sizeof(str), ptr_);
	printf("%s\n", str);

	//关闭文件
	fclose(ptr_);
	ptr_ = NULL;

	return 0;
}

😶‍🌫️文件的顺序读写

1.fputc

先看看要求

怎么用呢?

格式:

int fputc( int c, FILE *stream );

        c表示要输入的字符,stream表示要被输出的位置

例一

#include<stdio.h>
#include<errno.h>
#include<string.h>

int main()
{
	//打开文件
	FILE* ptr = fopen("test.txt", "w");
	if (ptr == NULL)
	{
		perror("ptr");//如果没能打开文件,perror会直接打印错误信息
		return 1;       //括号里是可以任意写的一个常量字符串
	}

	//处理
	char i = 0;
	for (i = 'a'; i <= 'z'; i++)
	{
		fputc(i, ptr);
	}

	//关闭文件
	fclose(ptr);
	ptr = NULL;
	
	return 0;
}

2. fgetc

先看看要求

 

 怎么用呢?

格式:

        

int fgetc( FILE *stream );

        FILE* stream 表示一个文件指针变量,stream表示流,也就是介于内存和文件信息区的输入缓存区(这里不细讲,不是本篇重点,以后会出相关博客),简而言之就是需要你刚刚打开的文件的指针,返回类型为int。

 例二

#include<stdio.h>
#include<errno.h>
#include<string.h>

int main()
{
	//打开文件
	FILE* ptr = fopen("test.txt", "w");
	if (ptr == NULL)
	{
		perror("ptr");//如果没能打开文件,perror会直接打印错误信息
		return 1;       //括号里是可以任意写的一个常量字符串
	}

	//处理
	char i = 0;
	for (i = 'a'; i <= 'z'; i++)
	{
		fputc(i, ptr);
	}

	//关闭文件
	fclose(ptr);
	ptr = NULL;
	
	//打开文件
	FILE* ptr_ = fopen("test.txt", "r");
	if (ptr_ == NULL)
	{
		perror("ptr_");
		return 1;
	}

	//处理
	int ch = 0;//要注意,fgetc返回值是int类型,所以用int来接收
	while ((ch = fgetc(ptr_)) != EOF)//读取完返回EOF  这里注意优先级,用括号括起来
	{
		printf("%c ", ch);
	}


	//关闭文件
	fclose(ptr_);
	ptr_ = NULL;

	return 0;
}

3.fguts

先来看看要求

 怎么用呢?

格式

int fputs( const char *string, FILE *stream );

         string是要被输出的常量字符串,stream是被输出的文件指针;

例三

#include<stdio.h>
#include<errno.h>
#include<string.h>

int main()
{
	//打开文件
	FILE* ptr = fopen("test.txt", "w");
	if (ptr == NULL)
	{
		perror("ptr");
		return 1;
	}

	//操作
	fputs("hehe\n", ptr);

	//关闭文件
	fclose(ptr);
	ptr = NULL;
	return 0;
}

4.fgets

先来看看要求

怎么用呢?

格式

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

        从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。

例四

#include<stdio.h>
#include<errno.h>
#include<string.h>

int main()
{
	//打开文件
	FILE* ptr = fopen("test.txt", "w");
	if (ptr == NULL)
	{
		perror("ptr");
		return 1;
	}

	//操作
	fputs("hehe\n", ptr);

	//关闭文件
	fclose(ptr);
	ptr = NULL;

	//打开文件
	FILE* p = fopen("test.txt", "r");
	if (p == NULL)
	{
		perror("p");
		return 1;
	}

	//操作
	char str[5] = { 0 };
	fgets(str, 5, p);
	printf("%s\n", str);

	//关闭文件
	fclose(p);
	p = NULL;
	return 0;
}

5.fprintf

先看看要求

 怎么用呢?

格式

int fprintf( FILE *stream, const char *format [, argument ]...);

 这里都可以对比printf的格式

int printf(const char *format [, argument ]...);

实际不难看懂,只是多了一个被输出的文件流

例五

#include<stdio.h>
#include<errno.h>
#include<string.h>

int main()
{
	//打开文件
	FILE* ptr = fopen("test.txt", "w");
	if (ptr == NULL)
	{
		perror("ptr");
		return 1;
	}

	//操作文件
	fprintf(ptr, "hello world\n");

	//关闭文件
	fclose(ptr);
	ptr = NULL;

	return 0;
}

6.fscanf

先来看看要求

 怎么用呢?

格式

int fscanf( FILE *stream, const char *format [, argument ]... );

        同理和scanf函数对比格式

int scanf(const char *format [, argument ]... );

不难理解,也是少了文件指针流

例6

#include<stdio.h>
#include<errno.h>
#include<string.h>

int main()
{
	//打开文件
	FILE* ptr = fopen("test.txt", "w");
	if (ptr == NULL)
	{
		perror("ptr");
		return 1;
	}

	//操作文件
	fprintf(ptr, "hello world\n");

	//关闭文件
	fclose(ptr);
	ptr = NULL;

	//打开文件
	FILE* p = fopen("test.txt", "r");
	if (p == NULL)
	{
		perror("p");
		return 1;
	}

	//操作文件
	char str[20] = { 0 };
	fscanf(p, "%s", str);//fscanf遇到空格停止读取
	printf("%s ", str);
	fscanf(p, "%s", str);
	printf("%s\n", str);

	//关闭文件
	fclose(p);
	p = NULL;

	return 0;
}

7.fwrite

先看看要求

 怎么用呢?

 格式

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

        简而言之:将buffer这个任意类型,放入count个以size个字节的大小到stream

例7

int main()
{
	//打开文件
	FILE* ptr = fopen("test.txt", "w");
	if (ptr == NULL)
	{
		perror("ptr");
		return 1;
	}

	//操作文件
	student s = { "chen",19 };
	fwrite(&s, sizeof(s), 1, ptr);

	//关闭文件
	fclose(ptr);
	ptr = NULL;

	return 0;
}

8.fread

先来看看要求

怎么用呢?

格式

size_t fread( void *buffer, size_t size, size_t count, FILE *stream );

简而言之:将stream这个文件流的count个size大小的数据写入buffer中

例子8

#include<stdio.h>
#include<errno.h>
#include<string.h>

typedef struct student
{
	char name[15];
	int age;
}student;

int main()
{
	//打开文件
	FILE* ptr = fopen("test.txt", "w");
	if (ptr == NULL)
	{
		perror("ptr");
		return 1;
	}

	//操作文件
	student s = { "chen",19 };
	fwrite(&s, sizeof(s), 1, ptr);

	//关闭文件
	fclose(ptr);
	ptr = NULL;

		//打开文件
	FILE* p = fopen("test.txt", "r");
	if (p == NULL)
	{
		perror("p");
		return 1;
	}

	//操作文件
	student s1 = { 0 };
	fread(&s1, sizeof(s), 1, p);
	printf("%s %d\n", s1.name, s1.age);

	//关闭文件
	fclose(p);
	p = NULL;
	return 0;
}

9.fseek

先看看要求

 怎么用呢?

格式

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

        简而言之:在文件stream里,以为起点origin(偏移起始位置:文件头0(SEEK_SET),当前位置1(SEEK_CUR),文件尾2(SEEK_END),将光标偏移offset个字符;

例9

#include<stdio.h>
#include<errno.h>
#include<string.h>

int main()
{
	//打开文件
	FILE* ptr = fopen("test.txt", "w");
	if (ptr == NULL)
	{
		perror(ptr);
		return 0;
	}

	//处理文件
	fputs("hellowcdef", ptr);
	fseek(ptr, 6, SEEK_SET);
	fputs(" hello world\n", ptr);
	
	//关闭文件
	fclose(ptr);
	ptr = NULL;
	return 0;
}

10.rewind

先看看要求

 怎么用呢?

格式 

//未保存数据丢失//

例10

#include<stdio.h>
#include<errno.h>
#include<string.h>

int main()
{
	//打开文件
	FILE* ptr = fopen("test.txt", "w");
	if (ptr == NULL)
	{
		perror(ptr);
		return 0;
	}

	//处理文件
	fputs("world", ptr);
	rewind(ptr);
	fputs("hello\n", ptr);
	
	//关闭文件
	fclose(ptr);
	ptr = NULL;
	return 0;
}

11.feof

        被许多人误用的函数:feof用来判断文件是否结束

        实际上:feof是在文件结束的时候判断文件结束的原因,判断文件是因为遇到文件位结束还是读取失败结束

  • 1. 文本文件读取是否结束,判断返回值是否为EOF(fgetc),或者NULL(fgets)例如:
  • fgetc判断是否为EOF.
  • fgets判断返回值是否为NULL.
  • 2. 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。例如:
  • fread判断返回值是否小于实际要读的个数

例11

#include <stdio.h>#include <stdlib.h>
 
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))
        puts("I/O error when reading");
    else if (feof(fp))
        puts("End of file reached successfully");
     fclose(fp);
     fp = NULL;
     return 0;
}

🍁文本文件和二进制文件

  • 文本文件
  • { 以ASCII字符的形式存储的文件就是文本文件 };
  • 二进制文件
  • 数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件。 }

数据在文本文件中和在二进制文件中分别是怎么存储的?

        如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节),而二进制形式输出,则在磁盘上只占4个字节。


🍁面试题


对比一组函数
scanf/fscanf/sscanf
printf/fprintf/sprintf

scanf/prinf 是针对标准输入流/标准输出流的 格式化输入/输出
fscanf/fprintf 是针对所有输入流/所有输出流的 格式化输入/输出
sscanf/sprintf sscanf是从字符串中读取格式化的数据 sprintf是把格式化的数据输出成(存储到)字符串

码字不易~

  • 10
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
“新概念C语言”突破了以往任何一种语言教材的旧的模式,将教学内容分为入门篇和提高篇两个篇章。在入门篇中只引进程序设计必要的语法现象,达到快速入门。激发兴趣的目的。在入门篇和提高篇之间插一个强化上机实验周,巩固学习内容。在提高篇中完成完整的语法、算法、程序设计思想等教学目的的学习任务。由于学生对语言已具有了初步的了解并掌握了最基本的语法和程序设计思想,能设计较简单的程序,所以在提高篇的学习中,不论对灵活语法的学习和掌握,还是对程序设计思想的掌握都更加容易,从而可以较容易达到教学目标。 第一部分 入门篇 1 第1章 C语言的产生及其工作流程 3 1.1 C语言的产生 3 1.2 程序和程序设计 3 1.2.1 C程序 3 1.2.2 程序设计 4 习题 4 第2章 C程序设计的初步知识 5 2.1 简单C程序的构成和格式 5 2.2 常量、变量和标识符 6 2.2.1 标识符 6 2.2.2 常量 7 2.2.3 用定义一个符号名的方法来代表一个常量 7 2.2.4 变量 8 2.3 整型数据 8 2.3.1 整型常量 8 2.3.2 整型变量 8 2.4 实型数据 9 2.4.1 实型常量 9 2.4.2 实型变量 9 2.5 字符型数据 10 2.5.1 字符常量 10 2.5.2 字符串常量 11 2.5.3 字符变量 11 2.6 算术表达式 11 2.6.1 基本的算术运算符 11 2.6.2 运算符的优先级、结合性和算术表达式 12 2.6.3 强制类型转换表达式 13 2.7 赋值表达式 13 2.7.1 赋值运算符和赋值表达式 13 2.7.2 赋值运算中的类型转换 14 习题 15 第3章 顺序结构程序 16 3.1 复合语句和空语句 16 3.1.1 复合语句 16 3.1.2 空语句 16 3.2 程序举例 16 习题 17 第4章 选择结构 19 4.1 关系运算和逻辑运算 19 4.1.1 C语言中的逻辑值 19 4.1.2 关系运算符和关系表达式 19 4.1.3 逻辑运算符和逻辑表达式 20 4.2 if语句和用if语句构成的选择结构 21 习题 22 第5章 循环结构 25 5.1 for语句和用for语句构成的循环结构 25 5.1.1 for语句构成的循环结构 25 5.1.2 for循环的执行过程 25 5.1.3 有关for语句的说明 25 5.2 循环结构的嵌套 27 习题 28 第6章 函数 30 6.1 函数的定义和返回值 30 6.1.1 函数定义的语法 30 6.1.2 函数的返回值 30 6.2 函数的调用 32 6.2.1 函数的调用方式 32 6.2.2 函数调用时的语法要求 32 6.3 函数的说明 33 6.3.1 函数说明的形式 33 6.3.2 函数说明的位置 34 6.4 调用函数和被调用函数之间的数据传递 34 6.5 库函数 36 6.6 数据输入输出及常用库函数 37 6.6.1 printf函数(格式输出函数) 37 6.6.2 scanf函数(格式输入函数) 39 6.6.3 调用putchar和getchar函数输出和输入字符 40 6.7 程序举例 41 习题 43 第7章 数组 45 7.1 一维数组的定义和一维数组元素的引用 45 7.1.1 一维数组的定义 45 7.1.2 一维数组元素的引用 46 7.1.3 一维数组的初始化 46 7.1.4 一维数组的定义和数组元素引用举例 47 7.2 函数之间对一维数组和数组元素的引用 47 7.2.1 数组元素做实参 47 7.2.2 数组名做实参 49 7.3 一维数组应用举例 50 7.4 字符数组 53 7.4.1 字符数组的定义 53 7.4.2 字符数组的初始化 54 7.4.3 字符数组的引用 54 7.4.4 字符串与字符串结束标志 55 7.4.5 字符数组的输入输出 56 7.4.6 字符串处理函数 58 7.4.7 字符数组应用举例 60 习题 61 第8章 文件 63 8.1 C语言文件的概念 63 8.2 打开文件 64 8.3 关闭文件 65 8.4 调用getc(fgetc)和putc(fputc)等常用函数进行输入和输出 65 8.5 判断文件结束函数feof 68 习题 69 第二部分 提高篇 71 第9章 算法 73 9.1 算法 73 9.2 结构化程序设计和模块化结构 74 9.2.1 结

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈亦康

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值