Detailed explanation of c++---io flow

C++ standard IO stream

In the previous study, I know that cout<<data can be output to the screen by using, for example, the following code:

int main()
{
    
    
	int a = 10;
	char b = 'b';
	double c=10.1;
	string str("abcd");
	cout << a << endl;
	cout << b << endl;
	cout << c << endl;
	cout << str << endl;
	return 0;
}

The running results of the code are as follows:
Insert image description here
you can see that the same output form can be used to output different forms of data. This is because C++ has cout<<many forms of overloading internally, so a variety of data can be printed, such as Say the following picture: And there are many types of overloading
Insert image description here
for C++. For example, the following picture: Stream extraction is a buffering operation. By default, it ends with a space or newline character as the read, so we often use stream extraction. To achieve the following operations:cin>>
Insert image description here

int main()
{
    
    
	string tmp;
	while (cin >> tmp)
	{
    
    
		cout << tmp << endl;
	}
	return 0;
}

As long as we enter data, it will read the data and print it out. When the data is read and we have not entered any data, it will always be there waiting for us to enter data, such as the following picture: But this
Insert image description here
program How does it end? Some friends may say that pressing ctrl c to end the program, for example, as follows:
Insert image description here
But ctrl c is a very violent ending method. It ends the program by sending a signal, which is not a normal end of the program. So if you want to end this program normally, you have to enter ctrl z and line feed to end the above program. For example, the picture below: You
Insert image description here
can see that this exit method is reasonable, and the exit code is also 0. So here is a question, why Can ctlr z end the above loop and end the program? First of all, the return value we need to know cin >> tmpis an istream object. When judging whether the loop continues, if it is 0, the loop will end. If it is not 0, it will enter the loop. That is to say, if the result is true, the loop will continue to loop. If the result is false, the loop will stop. , but this is a stream object. It cannot be implicitly converted into an integer, so how can it determine whether it is true or false? Therefore, in order to allow istream objects to support true and false judgments, the library provides such an operation:
Insert image description here
with this special overload, an istream type object can be converted into a bool type object, then the process of the above code judging data It cin>>tmpreturns an istream object, and then calls a function when istream makes a judgment. operator boolInside this function, some information in the object will be judged. When we enter ctrl z, some information in the class will be changed. Once the function returns that this information has been modified, it will directly return false. This is the execution principle of the above code. With this principle, we can create a date class, implement operator bool in the class, and then create this class. The object is used as the judgment of the while loop, then the code here is as follows:

class Date
{
    
    
    friend ostream& operator << (ostream& out, const Date& d);
    friend istream& operator >> (istream& in, Date& d);
public:
    Date(int year = 1, int month = 1, int day = 1)
        :_year(year)
        , _month(month)
        , _day(day)
    {
    
    }
    operator bool()
    {
    
    
        // 这里是随意写的,假设输入_year为0,则结束
        if (_year == 0)
            return false;
        else
            return true;
    }
private:
    int _year;
    int _month;
    int _day;
};
istream& operator >> (istream& in, Date& d)
{
    
    
    in >> d._year >> d._month >> d._day;
    return in;
}
ostream& operator << (ostream& out, const Date& d)
{
    
    
    out << d._year << " " << d._month << " " << d._day;
    return out;
}
int main()
{
    
    
    // 自动识别类型的本质--函数重载
    // 内置类型可以直接使用--因为库里面ostream类型已经实现了
    int i = 1;
    double j = 2.2;
    cout << i << endl;
    cout << j << endl;
    // 自定义类型则需要我们自己重载<< 和 >>
    Date d(2022, 4, 10);
    cout << d;
    while (d)
    {
    
    
        cin >> d;
        cout << d;
    }
    return 0;
}

When the _year inside the object is 0, operator bool will directly return false, for example, the following operation results:
Insert image description here

Then this is the principle of operator bool, I hope everyone can understand.

C++IO流

Binary reading and writing

We first create a class to record the information to be written to the file, such as the following code:

struct ServerInfo
{
    
    
	 char _address[32];
	 int _port;
};

Then we create a class specifically to write the objects created by the above class into the file, or read the contents of the file. Then this class contains a member variable of string type to record the configuration file. Name, then the constructor needs a parameter to initialize the member variable, then the code here is as follows:

struct ConfigManager
{
    
    
public:
    ConfigManager(const char* filename)
        :_filename(filename)
    {
    
    }
private:
    string _filename; // 配置文件
};

There are two forms of reading and writing in C language, one is binary reading and writing and the other is text reading and writing. Binary reading and writing is to output the data in the memory directly to the file without processing, while text reading and writing is to process the data in the memory. Read, write and then output to a file, then there are two corresponding reading and writing methods in C++.
Insert image description here
Ifstream class is to read the data in the file into the program, and ofstream is to write the data in the program to the file. Let's take a look first. ofstream:
Insert image description here
There is a fopen function in C language to open a specified file, then there is also a corresponding open function in ofstream of C++ to open the file: the first parameter
Insert image description here
represents the name of the file to be opened, and the second parameter is Indicates the way to open this file, such as opening in reading mode, opening in writing mode, opening in binary form, opening with adding at the end, etc., so there is the following symbol: How do you want to open it
Insert image description here
? Open the file in the form of what type to add, and then use |these forms to link together. We can look at the explanation given by the document:

Insert image description here

For example, if I want to open a file named test.txt and add data to the end of the file, I can use out and app. Then the code here is as follows:

ofstream ofs;
ofs.open ("test.txt", ofstream::out | ofstream::app);

Here we create the ofstream object in the form of no parameters, and then call open to open the file in a form. Then there is another way to add the file to be opened and the form of file operation when creating the ofstream object. Let's take a look at the constructor of ofstream:
Insert image description here
then here we can create a function and write data to the file in binary form:

void WriteBin(const ServerInfo& info)
{
    
    
    ofstream ofs("test.txt", ios::out | ios::binary);
}

Because the ofstream class is an inherited ios class, there are these mark bits in the ios class,
Insert image description here
so we can directly use the mark bits in ios to open the mark bits. After the file is opened, data must be written to the file. The ofstream class provides A function named write is used to write data to a file. Let’s take a look at the introduction of this function:
Insert image description here
one parameter is a pointer indicating the address to which the data is to be written (note that the type of this address is char*), The second parameter indicates how many bytes of data to write into the file, so the code here is as follows:

void WriteBin(const ServerInfo& info)
{
    
    
    ofstream ofs("test.txt", ios::out | ios::binary);
    ofs.write((char*)&info, sizeof(info));
}

So there may be friends here who ask us if we need to add the closing function of the file? The answer is no, because here a class is created to open the file. When the function ends, the life cycle of the class ends, and then the destructor will be called to close the file. With the write function, we have to create another read function. Function, this function is almost the same as above. Just change ofstream to istream and out to in. Then there is a function called read in the ifstream class. The parameters of this function are as follows: the first parameter indicates the location where the read data exists, and the second
Insert image description here
parameter The data indicates how many bytes to read, so with these things we can write the following code:

void ReadBin(ServerInfo& info)
{
    
    
    ifstream ifs("test.txt", ios::in | ios::binary);
    ifs.read((char*)&info, sizeof(info));
}

Then we can use the following code to test:
i

nt main()
{
    
    
    ServerInfo winfo = {
    
     "192.0.0.1", 80 };
    ConfigManager cf_bin("test.txt");
    cf_bin.WriteBin(winfo);
    //ServerInfo rbinfo;
    //cf_bin.ReadBin(rbinfo);
    //cout << rbinfo._address << " " << rbinfo._port << endl;
    return 0;
}

Run the code and you can see that there is a file named test.txt in the path of the current program:
Insert image description here
Open this file and you can see that the content we entered exists in it:
Insert image description here
because it is binary, what is displayed here is garbled. Then we read this content and see that the output is in line with our expectations:

Insert image description here
So this is the process of binary reading and writing. One thing you should note here is that when using binary reading and writing, the character array here cannot be changed to string. For example, the following code:

struct ServerInfo
{
    
    
    string _address;
    int _port;
};

It becomes string here, and then we run it again and we can see that the content in the file is different:
Insert image description here
and there will also be problems when reading:
Insert image description here
although the printing is successful, there is a problem with the exit code. The reason is very simple. The string object does not store data, but stores the address of the data and the attributes of the data. When we write the data to the file, we actually write the address of the data and the attributes of the address to the file. When we read it again The address obtained is the address where the data is located. We are in the same process here, so the data is still there. If it is a different process, the data may still be there when writing, but the data may not be there when another process reads, so there is a problem here. . Therefore, C++ does not recommend using binary reading and writing but text reading and writing.

text literacy

When text reading and writing is used, default parameters are given by default, so we can just pass a file name directly. Text reading and writing are written in the form of strings. If a string is passed, Fortunately, if what is passed is an integer or other type, we have to convert it into other forms for transmission, so I recommend that you use the following method to implement it. C++ overloads many types. Although >> <<these
Insert image description here
Insert image description here
overloads Loading is not implemented in ifstream and ofstream, but these classes have an inheritance relationship, so
Insert image description here
Insert image description here
we can use operators >>和<<to write or read data from the file, for example, the following code:

    void WriteText(const ServerInfo & info)
    {
    
    
        ofstream ofs(_filename);
        ofs << info._address;
        ofs<< info._port ;
    }
    void ReadText(ServerInfo& info)
    {
    
    
        ifstream ifs(_filename);
        ifs >> info._address;
        ifs>> info._port ;
    }

Then we can use the following code to test:

int main()
{
    
    
    ServerInfo winfo = {
    
     "192.0.0.1", 80 };
    ConfigManager cf_bin("test.txt");
    cf_bin.WriteText(winfo);
    //ServerInfo rbinfo;
    //cf_bin.ReadText(rbinfo);
    //cout << rbinfo._address << " " << rbinfo._port << endl;
    return 0;
}

After the code is run, you can see that the content in the file becomes as follows:
Insert image description here
The read content becomes as follows:
Insert image description here
You can see that there is a problem here, and the reason is very simple when reading and printing cout and cin. Space or newline character is used as the delimiter, then the same is true when reading here. We have to add endl to add the delimiter, then the code here is as follows:

void WriteText(const ServerInfo & info)
{
    
    
    ofstream ofs(_filename);
    ofs << info._address<<endl;
    ofs<< info._port <<endl;
}
void ReadText(ServerInfo& info)
{
    
    
    ifstream ifs(_filename);
    ifs >> info._address;
    ifs>> info._port;
}

The running results are as follows:
Insert image description here
You can see that the running results here are normal, but there is a problem here. The built-in type compiler provides overloading, which we can easily implement here. For some overloading that is not implemented by the compiler What should I do with the class? For example, the data becomes as follows:

struct ServerInfo
{
    
    
    string _address;
    int _port;
    Date _date;
};

So here is the essence of C++ design. Didn’t we implement the overloading of the date class ourselves >>和<<? Then here we can directly use our own overloaded file to write or read content, for example, the following code :

void WriteText(const ServerInfo & info)
{
    
    
    ofstream ofs(_filename);
    ofs << info._address<<endl;
    ofs<< info._port <<endl;
    ofs << info._date << endl;
}
void ReadText(ServerInfo& info)
{
    
    
    ifstream ifs(_filename);
    ifs >> info._address;
    ifs>> info._port;
    ifs >> info._date;
}

The code for the test is as follows:

int main()
{
    
    
    ServerInfo winfo = {
    
     "192.0.0.1", 80 ,{
    
    1010,10,10} };
    ConfigManager cf_bin("test.txt");
    cf_bin.WriteText(winfo);
    ServerInfo rbinfo;
    cf_bin.ReadText(rbinfo);
    cout << rbinfo._address << " " << rbinfo._port <<" "<<rbinfo._date<< endl;
    return 0;
}

The results of the operation are as follows:
Insert image description here
The content of the file is as follows:
Insert image description here
in line with our expectations, so the reason why this can be achieved here is that we overloaded istream and ostream, and then these two finally inherited ifstream and ofsteam, although the date class The overload contains ostream type parameters, and when passing, we pass istream type parameters, but it can still be run here because the parent class type can receive the subclass type.

ostringstream and istringstream

Insert image description here
C language provides a function called to_string, which can directly convert some types of data into string types. Then C++ also provides
two classes, ostringstream and istringstream, to achieve similar functions. First, create an ostringstream object. , and then output the data to this object through the operator >>, for example, the following code:

#include<sstream>
int main()
{
    
    
    int i = 1234;
    double di = 11.11;
    Date d = {
    
     2023,5,16 };
    ostringstream oss;
    oss << i << " ";
    oss << di << " ";
    oss << d << " ";
}

Then this class provides a function named str to display the contents of the object:
Insert image description here
then we can write the following code:

#include<sstream>
int main()
{
    
    
    int i = 1234;
    double di = 11.11;
    Date d = {
    
     2023,5,16 };
    ostringstream oss;
    oss << i << " ";
    oss << di << " ";
    oss << d << " ";
    cout << oss.str() << endl;
}

The running result of the code is as follows:
Insert image description here
you can see that it has indeed become a string. Since multiple variables of different types can be combined into one string, in the same way we can also use istringstrem to decompose a string into multiple types. Variables are also implemented using the operator >>, so the code here is as follows:

#include<sstream>
int main()
{
    
    
    int i = 1234;
    double di = 11.11;
    Date d = {
    
     2023,5,16 };
    ostringstream oss;
    oss << i << " ";
    oss << di << " ";
    oss << d << " ";
    string tmp = oss.str();
    istringstream iss(tmp);
    int j;
    double ji;
    Date dd;
    iss >> j >> ji >> dd;
    cout << j << endl;
    cout << ji << endl;
    cout << dd << endl;
}

The result of the operation is as follows:
Insert image description here
Then this is the whole content of this article, I hope everyone can understand it.

Guess you like

Origin blog.csdn.net/qq_68695298/article/details/131833406