[C language] interpretation of ten thousand words -- about the reading and writing function of files

Keywords: C R Language

preface:
In c language, we usually destroy it after running, so the running results cannot be retained, but we can also make an organic association between c language and files, that is, various functions for files.

Tip: the following function definitions and parameters can be found on the cpulspouls website. The links are as follows:

Click → cplusplus.com

1, Opening of files

First, there are two kinds of reading and writing: sequential reading and writing and random reading and writing.

We can look down at the difference between the two, but in the association between c language and files, we must first open and close the file, and the way to open the file is also a function, so is closing.

Here, I don't want to elaborate on the opening and closing functions, just a simple description. If you want to have a deeper understanding, you can see cpulspuls You can also refer to another blogger's article to learn the file operation in detail, which is more conducive to eating this blog: [advanced C language] from introduction to burial (file operation)

For file opening, the function we use is fopen:

FILE * fopen ( const char * filename, const char * mode );

For example, if I want to open my data text document for operation, it is:

FILE* pf = fopen("E:\\Code base\\test 10.10\\data.txt", "r");
//The returned pointer is stored in '', where is the path '' to open the file. Here is the specified operation on the file

Appendix: open file operation table

File usagemeaningIf the specified file does not exist
"r" (read only)To enter data, open an existing text fileerror
"w" (write only)To output data, open a text fileCreate a new file
"a" (added)Add data to the end of a text fileerror
"rb" (read only)To enter data, open a binary fileerror
"wb" (write only)To output data, open a binary fileCreate a new file
"ab" (additional)Add data to the end of a binary fileerror
"r +" (read / write)To read and write, open a text fileerror
"w +" (read / write)A new file is recommended for reading and writingCreate a new file
"a +" (read / write)Open a file and read and write at the end of the fileCreate a new file
"rb +" (read / write)Open a binary file for reading and writingerror
"wb +" (read / write)Create a new binary file for reading and writingCreate a new file
"ab +" (read / write)Open a binary file and read and write at the end of the fileCreate a new file

Then the closing function is fclose:

int fclose ( FILE * stream );

After opening the file, be sure to close it and set the pointer storing the file address as a null pointer, otherwise it will be very dangerous:

    //Close the file with the fclose function
	fclose(pf);//Close the pointer to the file just opened
	
	pf = NULL;//At the same time, pf should also be set as a null pointer

Here is a brief introduction to the code for opening and closing files. If you are more detailed, you can go to another blog article. I believe you can also get the functions of these two functions by looking at the following code.

2, Sequential reading and writing

1.fputc and fgetc

In sequential reading and writing, we must first understand that the data in memory can be output, that is, written to a file. The function used is fputc. The data read from the file is the input, and the function of fgetc is used.

Then let's take a look at the parameters and definitions of fputc:

Interpretation: writes characters to the stream and advances the position indicator. That is, the character is written at the position indicated by the internal position indicator of the stream, and then it is automatically advanced by a position indicator.

In fact, it means that this function writes characters, one character at a time, and then the pointer to it will move one position. The next time it is written, it can be written at the end.

Parameters:

int fputc ( int character, FILE * stream );

According to his definition, the preceding int c is the character write stream, and the following FILE * stream is the address of the familiar write location. So we open the code as before and write:

int main()
{
	//Open file
	FILE* pf = fopen("E:\\Code base\\data.txt", "w");
	//Note that my operation here has become 'w' (write), that is, write
	if (pf == NULL)
	{
		perror("fopen");//Prevent file open errors and continue running
		//When the file is in error, the perror function will read the returned error value and report the error
		return -1;
	}
	//Write file
	fputc("h", pf);
	fputc('e', pf);
	fputc('l', pf);
	fputc('l', pf);
	fputc('o', pf);

	//Close file
	fclose(pf);
	pf = NULL;

	return 0;

}

When we run it, there is nothing in the console, which means there is no error. Then when we open our data.txt file, we can see that the written hello is already in it:

The sequential read / write functions are as follows:

functionFunction nameApply to
Character input functionfgetcAll input streams
Character output functionfputcAll output streams
Text line input functionfgetsAll input streams
Text line output functionfputsAll output streams
Format input functionfscanfAll input streams
Format output functionfprintfAll output streams
Binary inputfreadfile
Binary outputfwritefile

Here, what is flow?

In fact, flow is a highly abstract concept. You can understand it as water flow. For example, our files, screens, networks, CDs, floppy disks and other external devices can be read and written by the stream. printf in c language is output from the screen, scanf is input into the keyboard, and the flow is between these programs. For c language, as long as it runs, three streams will be opened: standard output stream (stdout), standard input stream (stdin) and standard error stream (stderr). These three streams are also FILE *.

Therefore, for the stream of FILE *, we can directly use it like the above function. For example, if you want to output, you can directly input stdout on the screen.

int main()
{

	fputc('h', stdout);
	fputc('h', stdout);
	fputc('h', stdout);
    //Operation result: hhh
	return 0;
}

Then, when reading things in the file, we use the fgetc function:

int fgetc ( FILE * stream );

Fgetc reads data from the stream, and the return value is int, that is, after fgetc finds characters in the file, the return value is int, which we can receive with int and print out. For example, the hello we just entered will print back:

int main()
{
	FILE* pf = fopen("data.txt", "r");
	if (NULL == pf)
	{
		perror("fopen");
		return -1;
	}

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

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

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

	ch = fgetc(pf);
	printf("%c", ch);
    //Run result: hello
	return 0;
}

As shown in the previous table, fgetc is applicable to the used input stream, so the standard input stream (stdin) can be used. We can try:

int main()
{
	int ch = fgetc(stdin);
	printf("%c", ch);

	ch = fgetc(stdin);
	printf("%c", ch);

	ch = fgetc(stdin);
	printf("%c", ch);

	ch = fgetc(stdin);
	printf("%c", ch);

	return 0;
}

When the code here runs, first we will see the cursor flickering and the input stream, so we need to input, and then print the characters just entered under fgetc(stdin) and printf(). Only four reads are written here, so no matter how much you input, you will read the first four.

2.fputs and fgets

But is it too slow to read and write one by one, so the following functions fgets and fputs are text line input and output functions. In fact, the method used is the same as the previous character input and output, and the function parameters may be slightly different.

fputs function:

Interpretation: write the C string pointed to by str to the stream. The function copies from the specified address (Str) until the terminating null character ('\ 0') is reached. This terminating null character is not copied to the stream

From the definition, we know that the stream written here is a string, so we can save the shortage that we can only write one character when writing.

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

As can be seen from the parameters here, the written data, that is, the constant string, is followed by the pointer that appears after the FILE is opened, so that is to say, the content of the FILE you want to input is written in front, and the pointer to the FILE * of the open FILE is written in the back.

So we can try to write a code to run:

//fputs
int main()
{
	FILE* pf = fopen("data.txt", "w");
	if (NULL == pf)
	{
		perror("fopen");
		return -1;
	}
	//Write file
	//Write a line of data
	fputs("hello world\n", pf);
	//Write another line of data
	fputs("hello china\n", pf);

	//Close file
	fclose(pf);
	pf = NULL;
}

The results show that:

Then let's take a look at fgets:

Interpretation: get string from stream. Read characters from the stream and store them in STR as a C string until (num-1) characters are read, or until a newline or the end of the file, whichever occurs first. The newline character causes fgetc to stop reading, but the function considers it a valid character and contains it in the string copied to str.

For fgets, it is actually getting strings from the stream, which is equivalent to reading the data in it, such as the data in the file.

Parameters:

char * fgets ( char * str, int num, FILE * stream );

What does str mean for parameters? In fact, it is also in the definition. It refers to a pointer to the character array read by the copied string, that is, a pointer to the read data. Let's use code directly to illustrate:

The code here continues the above fputs, that is, there are already two lines of data in data.txt.

//fgets
 int main()
{
	FILE* pf = fopen("data.txt", "r");
	if (NULL == pf)
	{
		perror("fopen");
		return -1;
	}
	
	//read file
	//Read the first row of data
	char arr[20] = { 0 };
	fgets(arr, 20, pf);//Store the pointer contents of the first 20 characters of the read pf in the arr array
	printf("%s\n", arr);//Print arr content

    //Read the second line
	fgets(arr, 20, pf);
	printf("%s\n", arr);
    //ps: if 5 characters are read, 4 characters are actually read,
    //    Because the fifth request reads a '\ 0'.
 
	//Close file
	fclose(pf);
	pf = NULL;
}

So what we see will be:

3.fprintf and fscanf

The above fgets and fputs are only for strings. If they are other types or different types, you need to use fprintf and fscanf functions.

We can check it out fscanf-cpulspuls Explanations and parameters in:

Interpretation: read data from the stream and store them in the location pointed to by the additional parameters according to the parameter format. Additional parameters should point to an assigned object of the type specified by the corresponding format specifier in the format string.

For fscanf and fprintf, that is, format input functions, even if they are written in a certain format, they are read in a certain format.

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

If we don't understand the parameters here, we can compare them. We can open the description of scanf to compare them:

In fact, fscanf has a FILE * parameter, that is, the address parameter of the open FILE. When we use it, we can first write it like scanf, and then add a FILE * parameter in front of it.

For example:

int main()
{
    int c = 0;
    scanf("%d",&c);//scanf

    return 0;
}
int main()
{
    int c = 0;
    
    FILE* pf = fopen("data.txt", "r");//Document content: 10
	if (NULL == pf)
	{
		perror("fopen");
		return -1;
	}
	
	fscanf(pf, "%d", &c);//fscanf
	//Read 10 this data from the file
    return 0;
}

The fprintf function is:

Output formatted data to all output streams (screens / files)

Similarly, we can check it first fprintf - cplusplus:

Interpretation: writes the C string pointed to by format to the stream. If the format includes format specifiers (subsequences starting from%), the formatted additional parameters will be formatted and inserted into the result string to replace the respective specifiers.

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

Similarly, after comparison, we found that fprintf and printf are just one FILE * parameter short, so we can also write in the format of printf first, and then add the parameters to use fprintf well.

The following are examples of fprintf and fscanf:

//Fprintf (write in a format)
struct S
{
	int n;
	double d;
};

int main()
{
	struct S s = { 100, 3.14 };

	FILE* pf = fopen("data.txt", "w");
	if (NULL == pf)
	{
		perror("fopen");
		return -1;
	}
	//Write file
	fprintf(pf, "%d %lf", s.n, s.d);

	//Close file
	fclose(pf);
	pf = NULL;
}
//fscanf
struct S
{
	int n;
	double d;
};
int main()
{
	struct S s = {0};
	FILE* pf = fopen("data.txt", "r");
	if (NULL == pf)
	{
		perror("fopen");
		return -1;
	}
	//read file
	fscanf(pf, "%d %lf", &(s.n), &(s.d));
   
	printf("%d %lf\n", s.n, s.d);

	//Close file
	fclose(pf);
	pf = NULL;
}

4.fwrite and fread

fwrite function:

Definition: writes a data block to a stream. Write the count element array from the memory block pointed to by PTR, and the size of each element is bytes to the current position in the stream.

In fact, it is written to the file in binary form.

Parameters:

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

If you can't understand the parameters, the following explanations are given:

The general meaning is: the first ptr points to the data to be written. The second is size, that is, the size of an element written in bytes. The third count is the number of elements, that is, how many data are written. The fourth is the familiar FILE pointer of FILE *.

So let's write a code directly:

//fwrite
struct S
{
	int n;
	double d;
	char name[10];
};

int main()
{
	struct S s = {100, 3.14, "zhangsan"};

	FILE* pf = fopen("data.txt", "wb");
	if (NULL == pf)
	{
		perror("fopen");
		return -1;
	}
	//Write file - binary write
	fwrite(&s, sizeof(s), 1, pf);
	//Write the contents of structure s, with the size of sizeof(s). Write one structure at a time and write pf
	
	//Close file
	fclose(pf);
	pf = NULL;
}

After running, we open notepad and see:

Note: because it is written in binary, the writing method of binary is different from that of text, so we can't understand some data in the file, but we can understand it by printing it in binary form. For example, use the following freead function:

Interpretation: reads data blocks from a stream. Reads an array of count elements from the stream, each of which is bytes in size, and stores them in the memory block specified by PTR.

That is, how the above is written and how to read it back now.

Parameters:

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

The parameters are the same as those written above, so let's go directly to the code:

//fread
struct S
{
	int n;
	double d;
	char name[10];
};

int main()
{
	struct S s = {0};

	FILE* pf = fopen("data.txt", "rb");
	if (NULL == pf)
	{
		perror("fopen");
		return -1;
	}
	//Read file - binary read
	fread(&s, sizeof(struct S), 1, pf);

	//Print
	printf("%d %lf %s\n", s.n, s.d, s.name);
	
	//Close file
	fclose(pf);
	pf = NULL;
}

For the data that you just can't understand in the text file, you can see here:

3, Random reading and writing

Then, for the above functions and sequential reading and writing, we need to read from the beginning and continue to read. If I need to read a certain piece of information, what should I do? Then it's time for us to read and write at random.

We can imagine that when we open a FILE, a pointer to FILE * often points to the first element. If it can point to the section we want to read, can we read a certain section of information? So we have the following function.

Random read / write contains several functions. Let's take a look at them together:

1.fseek function

First, we can take out the definition of fseek:

Reposition the flow position indicator. For streams opened in binary mode, the new position is defined by adding an offset to the reference position specified by the origin.

In other words, for this function, you can select its reading position through the offset to achieve the reading and writing of a certain part.

Parameters:

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

Among the parameters, there are familiar file pointers and two new parameters. The previous offset is the offset, that is, where we want to start reading from this position, which enables us to select the starting reading position. Then, the latter one is the source, that is, the position indicated by the pointer. Here, the function also gives us three selected positions:

The first is SEEK_SET, that is, the starting position of the file, which is the same starting position as sequential reading and writing.

The second is SEEK_CUR means the current position of the file pointer. When we use the fseek function to read a certain place of the file, the next reading of the file pointer will not return to the starting position, but will be there. Therefore, we have this choice to offset from which position.

The third is SEEK_END means the end of the file.

If we don't understand it, we can understand it in the code:

int main()
{
	FILE* pf = fopen( "data.txt","r");

	if (pf == NULL)
	{
		perror(fopen);
		return - 1;
	}

	//File data content
	//abcdef

    //read file
    fseek(pf, 2, SEEK_SET);//Read from the starting position to the next two positions, and what you read is c
	int ret = fgetc(pf);
	printf("%c\n", ret);

	fseek(pf, -2, SEEK_CUR);
	//After reading, you will jump directly to the next position, that is, d, and then move forward two bits to read b
	ret = fgetc(pf);
	printf("%c\n", ret);
	
    //Operation results: c b
	return 0;
}

Note: in the fgetc function, the character currently pointed to by the internal file location indicator of the specified stream is returned. The internal file location indicator is then advanced to the next character.

So when the above offset is - 2, you read b instead of a.

2.ftell function

The fseek function above can be used to read the position at a certain place after the offset. Sometimes we don't know what to do. At this time, we can use the ftell function:

Definition: returns the current value of the position indicator of the stream. For binary streams, this is the number of bytes at the beginning of the file.

In fact, the meaning here is to get the position of the current position indicator, and then return a value, which is the distance from the starting position to the current position. That is, the offset from the starting position.

Returns the offset of the file pointer from the starting position

Parameters:

long int ftell ( FILE * stream );

The parameters here only need to put the pointer of FILE * into the FILE, and the return value is long int.

Let's write the following code:

int main()
{
	FILE* pf = fopen("data.txt", "r");

	if (pf == NULL)
	{
		perror(fopen);
		return -1;
	}

	//File data content
	//abcdef

	//read file
	fseek(pf, 2, SEEK_SET);
	int ret = fgetc(pf);
	printf("%c\n", ret);

	fseek(pf, -2, SEEK_CUR);
	ret = fgetc(pf);
	printf("%c\n", ret); //c b

	int b = ftell(pf);
	printf("%d\n", b);//2

	return 0;
}

What is printed here is 2. Are we wrong? In fact, it is not. After we last read b, the position indicator has jumped to the next, that is, c, so the offset is 2 relative to the starting position a.

3.rewind function

When we use the fseek function to read, we may go far away, and then when we need him to come back, we can use the rewind function:

Interpretation: set the position of the stream to the beginning. Set the location indicator associated with the stream to the beginning of the file.

Actually, it means to return to the starting position:

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

Parameters:

void rewind ( FILE * stream );

The parameter is also passed to the FILE * pointer of the FILE, and there is no return value. In other words, if it is run directly, it is equal to the function of returning the position of the FILE pointer to the starting position of the FILE first.

Or the code immediately above:

int main()
{
	FILE* pf = fopen("data.txt", "r");

	if (pf == NULL)
	{
		perror(fopen);
		return -1;
	}

	//File data content
	//abcdef

	//read file
	fseek(pf, 2, SEEK_SET);
	int ret = fgetc(pf);
	printf("%c\n", ret);//c

	fseek(pf, -2, SEEK_CUR);
	ret = fgetc(pf);
	printf("%c\n", ret);//b

	rewind(pf);
	ret = fgetc(pf);
	printf("%c\n", ret);//a

	return 0;
}

So it's clear here that it's back to the starting position, so the print result is a.

4, Read function end flag

If we keep reading with the fgetc function, when will it stop? At this time, when we look at the definition, we will find that when the read is empty, the returned value is EOF. For example, the following code:

int main()
{
	//1. Open the file
	FILE* pf = fopen("data.txt", "r");
	if (NULL == pf)
	{
		perror("fopen");
		return -1;
	}

    //Suppose abcde is in data.txt
	int ch = 0;
	while ((ch = fgetc(pf)) != EOF)
	{
		printf("%c ", ch);
	}
	//Print result: abcde
	return 0;
}

For other functions above, you can get the same result. You can receive the return value of the function call and judge whether it reads the whole file. For example, after reading the fgets function, the character pointer is returned. There is also fscanf, which returns EOF when an error is encountered or at the end of the file. For fread, the return value is a size_t. If you want to read 5 but return a number smaller than you, you have finished reading.

5, Determination of end of document

But! In the process of file reading, the return value of feof function cannot be directly used to judge whether the file is finished. And the ferror function!

int feof ( FILE * stream );

For the feof function, it is used to judge whether the reading fails or the end of the file is encountered when the file reading ends.

int ferror ( FILE * stream );

For the ferror function, it is applied to the end of file reading to judge whether the reading ends after an error is encountered.

Like the reading end flag mentioned above, we summarize it as follows:

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

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

  1. Judge whether the return value is less than the actual number to be read after reading the binary file.

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

In summary:

The purpose of feof is to judge whether the end of the file is encountered after the file reading is completed
Purpose of ferror: after reading the file, judge whether the reading ends after encountering an error

That's all for this article. If you feel it's helpful to you, you might as well like it.

Posted by liro on Thu, 14 Oct 2021 13:26:56 -0700