Are you still troubled by the input every time you open the program, this article will make you not confused

In the programs we wrote before, we always need to enter some data for the program to use for calculation, but when we exit the program, the entered data will be destroyed, because the data is stored in the memory at this time. When the program is run next time, the data has to be re-entered, which is very uncomfortable.

We have to think of a way to make the entered data persistent. At this time, I can use file operations to store the entered data in the file, so that the entered data can be persisted until the next time. You can continue to use the data in the file after opening the program, very easy to use! ! !

Let's take a deeper look at the files.

Table of contents

what is a file

 program files 

 data file

file name

file opening and closing 

file pointer

 file opening and closing

Sequential reading and writing of files

Introduction to Sequential Read and Write Functions

 Random reading and writing of files

fseek function

ftell function

​Edit rewind function

 text files and binary files

 Judgment of the end of file reading

 misused feof function

file buffer


what is a file

Files on disk are files. But in program design, we generally talk about two types of files: program files and data files (classified from the perspective of file functions).

 program files 

Including source program file (suffix .c), object file (windows environment suffix .obj), executable program (windows environment suffix .exe).

 data file

The content of the file is not necessarily the program, but the data read and written when the program is running, such as the file from which the program needs to read data, or the file that outputs the content.

In this blog we are talking about data files 

In the previous content, all the output and input of our processing data are based on the terminal, that is, the keyboard of the terminal inputs data, and the operation result is output to the display screen. In fact, sometimes we will output the information to the disk, and then read the data from the disk into the memory for use when needed. Here, the files on the disk are processed.

file name

A file must have a unique file identifier for user identification and reference.

The file name consists of 3 parts: file path + file name trunk + file suffix

For example: c:\code\test.txt 

Under normal circumstances, the file name under the same path is unique and cannot be repeated!

For convenience, the file ID is often referred to as the file name .

file opening and closing 


file pointer

In the cache file system, the key concept is " file type pointer ", referred to as " file pointer ".

Each used file has opened up a corresponding file information area in the memory, which is used to store the relevant information of the file (such as the name of the file, the status of the file and the current location of the file, etc.). This information is stored in a structure variable. The structure type is declared by the system, named FILE.

For example, the stdio.h header file provided by the VS2013 compilation environment has the following file type declaration:

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

 The contents of the FILE type of different C compilers are not exactly the same, but they are similar. Whenever a file is opened, the system will automatically create a variable of the FILE structure according to the situation of the file, and fill in the information, and the user does not need to care about the details. Generally, the variables of this FILE structure are maintained through a FILE pointer, which is more convenient to use. Below we can create a FILE* pointer variable: 

 FILE* pf;//file pointer variable

Define pf as a pointer variable pointing to FILE type data. You can make pf point to the file information area of ​​a certain file (it is a structure variable). The file can be accessed through the information in the file information area. That is to say, the file associated with it can be found through the file pointer variable. 

 file opening and closing

Files should be opened before reading and writing , and should be closed after use .

When writing a program, when opening a file, a FILE* pointer variable will be returned to point to the file, which is equivalent to establishing the relationship between the pointer and the file.

 ANSIC stipulates that the fopen function is used to open the file, and the fclose is used to close the file.

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

The first parameter in the fopen function is very obvious, which is to create a file name, generally represented by a string. But what does the second parameter mean? This corresponds to how the file is opened. Open as follows:

file usage meaning If the specified file does not exist
"r" (read-only) To enter data, open an existing text file go wrong
"w" (write only) To output data, open a text file create a new file
“a” (append) Add data to the end of the text file create a new file
"rb" (read-only) To enter data, open a binary file go wrong
"wb" (write only) To output data, open a binary file create a new file
“ab” (append) append data to the end of a binary file create a new file
"r+" (read and write) Open a text file for reading and writing go wrong
"w+" (read and write) For reading and writing, suggest a new file create a new file
"a+" (read and write) Open a file for reading and writing at the end of the file create a new file
"rb+" (read and write) Open a binary file for reading and writing go wrong
"wb+" (read and write) Create a new binary file for reading and writing create a new file
"ab+" (read and write) Open a binary file for reading and writing at the end of the file create a new file

Below we will use:

#include<stdio.h>

int main()
{
	FILE* fp = fopen("data.txt", "r");
	if (fp == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	//关闭文件
	return 0;
}

In the current path, we open the data.txt file in read-only form "r". But we did not create a data.txt file in this path, so FILE*fp will receive a null pointer, we use if to judge, and use the perror function to print the error, we can see: but if we create in this  path What happens when a file is opened read-only again?  The code does not report an error and runs normally. 

#include<stdio.h>

int main()
{
	FILE* fp = fopen("data.txt", "w");
	if (fp == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	//关闭文件
	return 0;
}

We will keep the code unchanged, but change the read-only mode to write-only mode. If there is no such file in the path, a new file will be created.

And closing the file is very simple, just use the fclose function to put the file pointer into it. Finally, don't forget to set the pointer after closing the file to a null pointer.

#include<stdio.h>

int main()
{
	FILE* fp = fopen("data.txt", "w");
	if (fp == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	//关闭文件
    fclose(fp);
    fp = NULL;
	return 0;
}

 So how should we read and write files? There are two ways here: 1. Sequential reading and writing 2. Random reading and writing.

Sequential reading and writing of files

Introduction to Sequential Read and Write Functions

Why doesn't the scanf function printf function we used before open the standard input stream and standard output stream, just input and output the desired content directly in the function. Because as long as the C language program is running, 3 streams are opened by default. The standard output stream stdin the standard output stream stdout and the standard error stream strerr. So we don't need to open it again, but the data stream must be opened for file input and output.

Let's explain the above functions one by one:

The fgetc and fputc functions are for all input and output streams. We can use both standard input and output streams and files.

 fputc takes the desired character as a parameter, and the following parameter is a file pointer, pointing to the file to be written. If we want to output to the screen, we can use stdout.

#include<stdio.h>

int main()
{
	FILE* fp = fopen("data.txt", "w");
	if (fp == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	fputc('w', fp);
	//关闭文件
	fclose(fp);
	fp = NULL;
	return 0;
}

We input the w character into the file data.txt, we can find this file in the same path to see the content:

In this way, we can enter the content into the file. Now we want to read the contents of the file and only need to use the fgetc function to give the file pointer.

int main()
{
	FILE* fp = fopen("data.txt", "w");
	if (fp == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	for (int i = 0; i < 3; i++)
	{
		fputc('a' + i, fp);
	}
	//关闭文件
	fclose(fp);
	fp = NULL;
	return 0;
}

We can use the for loop to continuously input abc.

This is sequential read and write.

The fgetc function is the function that reads the content of the file. The return value is the number of content read each time.

int main()
{
	FILE* fp = fopen("data.txt", "r");
	if (fp == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	for (int i = 0; i < 3; i++)
	{
		int ch = fgetc(fp);
		printf("%c ", ch);
	}
	//关闭文件
	fclose(fp);
	fp = NULL;
	return 0;
}

 The content stored in the data.txt file just now is abc, and the reading of fgetc is also sequential reading. We can read the content from the file by using the for loop to read three times.

Next is the text line input and output functions fgets, fputs. They are also for all input and output streams.

 The fputs function is similar to the fputc function, except that the first parameter is a string. The second file is the pointer to the location you want to input, which can be either the screen stdout or a file.

int main()
{
	FILE* fp = fopen("data.txt", "w");
	if (fp == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写文件
	fputs("hello world", fp);
	fputs("hehe", fp);
	//关闭文件
	fclose(fp);
	fp = NULL;
	return 0;
}

 We input the two strings hello world and hehe into the data.txt file. So are these two strings placed on one line or two lines in the file?

It is placed on one line, so we have to add a newline character \n when we want to change the line.

int main()
{
	FILE* fp = fopen("data.txt", "w");
	if (fp == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写文件
	fputs("hello world", stdout);
	fputs("hehe", stdout);
	//关闭文件
	fclose(fp);
	fp = NULL;
	return 0;
}

If we write the second parameter of fputs as stdout, the function becomes the standard input, which will output on the screen.

Parameters of the fgets function:  the general function reads the second parameter num-1 characters, or it will stop early when encountering \n.

int main()
{
	FILE* fp = fopen("data.txt", "r");
	if (fp == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	char arr[10] = { 0 };
	fgets(arr, 10, pf);
	printf("%s", arr);
    fgets(arr, 10, fp);
	printf("%s", arr);
	//关闭文件
	fclose(fp);
	fp = NULL;
	return 0;
}

 The data.txt file contains the string hello worldhehexxxxxxxxxx. Now we read 10-1 characters from the file and put them into the created character array, and then print them on the screen:

Only read 9 characters, and end the string with \0 at the end.

If we are reading once:

It will continue to read 9 characters along the back of what has just been read, and then add \0 at the end to end.

The two functions just mentioned are very limited and can only write content in one format. However, the fscanf and fprintf functions can input and output all the data in the format.

Let's understand these two functions:

 In fact, there is only one difference between these two functions and printf and scanf, that is, there is a file pointer parameter. Explain that these two functions also apply to all streams.

struct S
{
	int a;
	float s;
};

int main()
{
	FILE* fp = fopen("data.txt", "w");
	if (fp == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写文件
	struct S s = { 100,3.14f };
	fprintf(fp, "%d, %f", s.a, s.s);

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

 We create a structure with members of an int type and a float type. We want to write the contents of the structure to the file, we use fprintf to operate (similar to printf, just add a file pointer in front)

We can then read the text:

We can also read the contents of the file:

int main()
{
	FILE* fp = fopen("data.txt", "r");
	if (fp == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	struct S s = { 0 };
	fscanf(fp, "%d, %f", &(s.a), &(s.s));
	printf("%d %f\n", s.a, s.s);
	//关闭文件
	fclose(fp);
	fp = NULL;
	return 0;
}

Still the data entered just now, we use the fscanf function to read from the file into our structure, and then print the structure members to get the answer.

  

scanf: Read formatted data from the standard input stream.

printf: Writes formatted data to the standard output stream.

fscanf: formatted input function for all input streams

fprintf: formatted output function for all output streams

There are also two functions that are very similar to scanf and printf: sscanf and sprintf. What are these two functions for?

These two functions put the formatted data into the string and extract the formatted data from the string respectively.

 sscanf: read formatted data from a string

sprintf: converts formatted data into strings

We can convert the file pointer into an array pointer similar to the fprintf and fscanf functions. This is to let everyone distinguish and not to confuse them.

 Binary input and output

Starting from the position of ptr, find count pieces of content with a size of size bytes and put them in the file stream.

struct S
{
	int a;
	float s;
	char str[10];
};

int main()
{
	FILE* fp = fopen("data.txt", "wb");
	if (fp == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	struct S s = { 100,3.14f,"world"};
	fwrite(&s, sizeof(struct S),1,fp);

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

 When using binary output, the file should be opened with "wb" in binary. Use fwrite to write the contents of structure members to text in binary form.

The content written in binary is generally incomprehensible. 

 The fread function has the same parameters as the fwrite function, and what it does is just the opposite.

Find the content of count size in the file and put it into str.

struct S
{
	int a;
	float s;
	char str[10];
};

int main()
{
	FILE* fp = fopen("data.txt", "rb");
	if (fp == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	struct S s = { 0 };
	fread(&s, sizeof(struct S), 1, fp);
	printf("%d %f %s\n", s.a, s.s, s.str);
	//关闭文件
	fclose(fp);
	fp = NULL;
	return 0;
}

Use the binary reading mode to read the contents of the text into the structure, and then print it.

 Random reading and writing of files

fseek function

Positions the file pointer based on the file pointer's position and offset.

 Assuming that abcdefghi is put in the file, and we want to read the f character, we use the fseek function to give the file pointer, the offset is 5, and the last one is read from the beginning of the file: SEEK_SET.

int main()
{
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror(fopen);
		return 1;
	}
	//读文件
	//定位文件指针到f
	fseek(pf, 5, SEEK_SET);
	int ch = fgetc(pf);
	printf("%c\n", ch);
	fclose(pf);
	pf = NULL;
	return 0;
}

 We can read the f character in the file.

If we want to read the f character from the end of the file, we only need to replace the offset parameter in the fseek function with -4, and read from the end of the file: SEEK_END.

fseek(pf,-4,SEEK_END);

When we print three characters abc first, and then want to print a, we can use fseek(pf,-3,SEEK_CUR);

ftell function

Returns the offset of the file pointer relative to the starting position.

int main()
{
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror(fopen);
		return 1;
	}
	//读文件
	int ch = fgetc(pf);
	printf("%c\n", ch);
	ch = fgetc(pf);
	printf("%c\n", ch);
	ch = fgetc(pf);
	printf("%c\n", ch);
	int pos = ftell(pf);
	printf("%d\n", pos);
	fclose(pf);
	pf = NULL;
	return 0;
}

When the fgetc function is used to print the contents of the file three times, then the ftell function can be used to know that the offset between the current position of the file and the position of the first element is 3. 

 rewind function

Return the position of the file pointer to the beginning of the file.

When we use the file pointer and the file pointer has an offset relative to the first element, then use the rewind function to make the file pointer return to the beginning.

int main()
{
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror(fopen);
		return 1;
	}
	//读文件
	int ch = fgetc(pf);
	printf("%c\n", ch);
	ch = fgetc(pf);
	printf("%c\n", ch);
	ch = fgetc(pf);
	printf("%c\n", ch);
	rewind(pf);
	ch = fgetc(pf);
	printf("%c\n", ch);
	fclose(pf);
	pf = NULL;
	return 0;
}

 text files and binary files

Depending on how the data is organized, data files are called text files or binary files .

Data is stored in binary form in memory, and if it is output to external storage without conversion, it is a binary file .

If it is required to store in the form of ASCII code on the external storage, it needs to be converted before storage. A file stored in the form of ASCII characters is a text file .

How is a piece of data stored in memory?

Characters are all stored in ASCII form, and numeric data can be stored in either ASCII or binary form. For example, if there is an integer 10000 , if it is output to the disk in the form of ASCII code, it will occupy 5 bytes (one byte for each character) on the disk, and if it is output in binary form, it will only occupy 4 bytes on the disk.

 Judgment of the end of file reading

 misused feof function

Keep in mind: During the file reading process, the return value of the feof function cannot be used to directly determine whether the file is over.

The function of feof is: when the file reading ends, judge whether the reason for the end of reading is: the end of the file is encountered.

 1. Whether the reading of the text file is over, judge whether the return value is EOF ( fgetc ), or NULL ( fgets )

For example: fgetc judges whether it is EOF. fgets judges whether the return value is NULL.

2. Judging the end of reading the binary file, and judging whether the return value is less than the actual number to be read.

For example: fread judges whether the return value is less than the actual number to be read. Correct usage:

Example of a text file:

#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);
}

Example of a binary file:

#include <stdio.h>
enum { SIZE = 5 };
int main(void)
{
    double a[SIZE] = {1.,2.,3.,4.,5.};
    FILE *fp = fopen("test.bin", "wb"); // 必须用二进制模式
    fwrite(a, sizeof *a, SIZE, fp); // 写 double 的数组
    fclose(fp);
    double b[SIZE];
    fp = fopen("test.bin","rb");
    size_t ret_code = fread(b, sizeof *b, SIZE, fp); // 读 double 的数组
    if(ret_code == SIZE) {
        puts("Array read successfully, contents: ");
        for(int n = 0; n < SIZE; ++n) printf("%f ", b[n]);
        putchar('\n');
   } else { // error handling
       if (feof(fp))
          printf("Error reading test.bin: unexpected end of file\n");
       else if (ferror(fp)) {
           perror("Error reading test.bin");
       }
   }
    fclose(fp);
}

file buffer

The ANSIC standard uses the " buffer file system " to process data files. The so-called buffer file system means that the system automatically creates a "file buffer " in the memory for each file being used in the program. Data output from memory to disk will be sent to the buffer in memory first, and then sent to disk together after the buffer is filled. If data is read from the disk to the computer, the data read from the disk file is input to the memory buffer (full of the buffer), and then the data is sent from the buffer to the program data area (program variables, etc.) one by one. The size of the buffer is determined by the C compilation system.

#include <stdio.h>
#include <windows.h>
int main()
{
 FILE*pf = fopen("test.txt", "w");
 fputs("abcdef", pf);//先将代码放在输出缓冲区
 printf("睡眠10秒-已经写数据了,打开test.txt文件,发现文件没有内容\n");
 Sleep(10000);
 printf("刷新缓冲区\n");
 fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到文件(磁盘)
 //注:fflush 在高版本的VS上不能使用了
 printf("再睡眠10秒-此时,再次打开test.txt文件,文件有内容了\n");
 Sleep(10000);
 fclose(pf);
 //注:fclose在关闭文件的时候,也会刷新缓冲区
 pf = NULL;
 return 0;
}

 A conclusion can be drawn here: Because of the existence of the buffer, when the C language operates the file, it needs to refresh the buffer or close the file at the end of the file operation.

Failure to do so can cause problems reading and writing files.

Guess you like

Origin blog.csdn.net/m0_74755811/article/details/131892565