!!!Chapter 8 The IO Library

本文深入探讨了C++标准库中的输入输出(IO)机制,包括面向对象的库设计、条件状态管理、输出缓冲区管理、文件输入输出操作以及字符串流的使用。文章详细解释了如何通过继承来支持不同类型的设备和字符流,并介绍了各种IO类型的特点和用法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

8.1 An Object-Oriented Library

To support or using different kinds of devices and different sized character streams, the library usesinheritance to define a set of object-oriented classes.

When one class inherits from another, we can use the same operations on both classes.

In C++ we speak of the parent as the base class and the inheriting class as a derived class.

The IO types are defined in three separate headers:

1. iostream defines the types used to read and write to a console window

2. fstream defines the types used to read and write named files

3. sstream defines the types used to read and write in-memory strings.

When we have a function that takes a reference to a base-class type, we can pass an object of a derived type to that function.

IO Library Types and Headers
HeaderTypeInternational Char
iostreamistream, ostream, iostreamwistream, wostream, wiostream
fstreamifstream, ofstream, fstreamwifstream...
sstreamistringstream, ostringstream, stringstream...

International Character Support

The library defines a corresponding set of types supporting the wchar_t type. Each class is distinguished from its char counterpart by a "w" prefix. So wostream, wistream, and wiostream read and write wchar_t data to or from a console window. 

The library also defines objects to read and write wide characters from the standard input and standard output. These objects are distinguished from the char counterparts by a "w" prefix:The wchar_t standard input object is named wcin; standard ouput is wcout; and standard error is wcerr.

Each of the IO headers defines both the char and wchar_t classes and standard input/output objects. The stream-based wchar_t classes and objects are defined iniostream, the wide character file stream types infstream, and the wide character stringstreams in sstream.

No Copy or Assign for IO Objects

The library types do not allow copy or assignment:

ofstream out1, out2;
out1 = out2;               //error: cannot assign stream objects
// print function: parameter is copied
ofstream print(ofstream);
out2 = print(out2);        //error: cannot copy stream objects

This requirement has two implications:

1. we cannot have a vector(or other container) that holds stream objects, as only element types that support copy can be stored in vectors or other container types.

2. we cannot have a parameter or return type that is one of the stream types. If we need to pass or return IO object, it must be reference or pointer.

ofstream &print(ofstream &);     //ok: takes a reference, no copy
while (print(out2)) {...}        //ok: pass reference to out2

Typically, we pass a stream as a nonconst reference because we need to read or write to the object. And reading or writing an IO object changes its state.

8.2 Condition States

The IO library manages a set of condition state members that indicate whether a given IO object is in a usable state or has encountered a particular kind of error.

The easiest way to test whether a stream is okay is to test its truth value:

if (cin)               //ok to use cin, it is in a valid state
while (cin >> word)    //ok: read operation successful

IO Library Condition State P 288

Condition States

1. strm::iostate

Each stream object contains a condition state member that is managed through the setstate and clear operations.

2. strm::badbit, strm::failbit, strm::eofbit

The badbit indicates a system level failure, such as an unrecoverable read or write error. The failbit is set after a recoverable error, such as reading a char when numeric data was expected. The eofbit is set when an end-of-file is encountered. Hitting eof also sets the failbit.

3. s.eof(), s.fail(), s.bad(), s.good()

The state of the stream is revealed by the bad, fail, eof, and good operations. If any of the first three are true, then testing the stream itself will indicate that the stream is in an error state.

4. s.clear(), s.clear(flag), s.setstate(flag)

The clear and setstate operations change the state of the condition member. The clear put the condition back in its valid state.

5. s.rdstate()

rdstate member function returns an iostate value that corresponds to the entire current condition state of the stream:

//remember current state of cin
istream::iostate old_state = cin.rdstate();
cin.clear();
process_input();            //use cin
cin.setstate(old_state);    //now reset cin to old state

Interrogating and Controlling the State of a Stream

int val;
// read cin and test only for EOF; loop is executed even if there are other IO failures
while (cin >> ival, !cin.eof())
{
    if (cin.bad())                      //input stream is corrupted; bail out
        throw runtime_error("IO stream corrupted");
    if (cin.fail())                     //bad input              
    {
        cerr << "bad data, try again";  //warn user
        cin.clear(istream::failbit);    //reset the stream
        continue;                       //get next input
    }
}

Dealing with Multiple States
When we need to set or clear multiple state bits. We can do so by making multiple calls to the setstateand clear functions. We can also use bitwise OR to do this(because different error type use different bit):

//sets both the badbit and the failbit
is.setstate (ifstream::badbit | ifstream::failbit);

It will turn on badbit and failbit, all other bits are zero.

8.3 Managing the Output Buffer

Each IO object manages a buffer, which is used to hold the data that the program reads and writes.

There are several conditions that cause the buffer to be flushed - that is, written - to the actual device or file:

1. The program completes normally. All output buffers are emptied as part of the return from main.

2. At some indeterminate time,the buffer can become full, in which case it will be flushed before writing the next value.

3. We can flush the buffer explicitly using a manipulator such as endl.

4. We can use the unitbuf manipulator to set the stream's internal state to empty the buffer after each output operation.

5. We can tie the output stream to an input stream, in which case the output buffer is flushed whenever the associated input stream is read.

Flushing the Output Buffer:

There are three normally used manipulations that can flush buffer: flush, ends, endl

cout<< "hi" << flush;   //flushes the buffer; adds no data

cout<< "hi" << ends;    //insert a null, then flushes the buffer

cout<< "hi" << endl;    //insert a newline, then flushes the buffer

The unitbuf Manipulator

If we want to flush every output, it is better to use the unitbuf manipulator:

cout<< unitbuf << "first" << "second"<< nounitbuf;

//equalsto

cout<< "first" << flush << "second" <<flush;

The nounitbuf manipulator restores the stream to use normal, system-managed buffer flushing.

Tying Input and OutputSteams Together

When an input stream is tied to an output stream, any attempt to read the input stream will first flush the buffer associated output stream.

Interface systems usually should be sure that their input and output streams are tied, so that any output, which might include prompts to the user, has been written before attempting to read.

The tie function takes a pointer to an ostream and ties the argument stream to the object on which tie was called:

//illustrationonly: the library ties cin and cout for us
cin.tie(&cout);
cin.tie(0);      //break tie between cin and cout
cin.tie(&cerr);

An ostream object can be tied to only one istream object at a time. To break an existing tie, we pass in an argument of 0.

8.4 File Input and Output

The fstream header defines three types to support file I/O:

1. ifstream, derived fromistream, reads from a file

2. ofstream, derived fromostream, writes to a file

3. fstream, derived from iostream, reads and writes the same file

In addition to the behavior that fstream types inherit from iostream, they also define two new operations:open and close. These operations can be called on objects of fstream, ifstream or ofstream but not on other IO types.

8.4.1 Using File Stream Objects

When we want to read or write a file, we must define our own objects and bind them to the desired files. Supplying a file name as an initializer to an ifstream or ofstream object has the effect of opening the specified file:

//construct an ifstream and bind it to the file named ifile
ifstream infile(ifile.c_str());
//ofstream output file object to write file named ofile
ofstream outfile(ofile.c_str());

PS: the IO library uses C-style character strings. Assuming the name of the file we wish to use is in a string, we can use c_str member to obtain C-style string. P 139

We can also define an IO object and bind it to a file later. We bind an existing fstream object to the specified file by calling the open member.

ifstream infile;      //unbound input file stream
ofstream outfile;     //unbound output file stream
infile.open("in");    //open file named "in" in the current directory
outfile.open("out");  //open file named "out" in the current directory

Checking Whether an Open Succeeded

if (!infile)
{
  cerr << "error: unable to open input file: "
       << ifile << endl;
  return -1;
} 

When we test a stream, the effect is to test whether the object is "okay: for input or output:

if (outfile)  //ok to use outfile?
if (!outfile)  //not ok to use outfile?

Rebinding a File Stream to a New File
Once a fstream has been opened, it remains associated with the specified file. To associate the fstream with a different file, we must first closethe existing file and then open a different file:
ifstream infile("in");    //open file named "in" for reading
infile.close();           //close "in"
infile.open("next");      //opens file named next for reading

The open function checks whether the stream is already open. If so, it sets its internal state to indicate that a failure has happened. Subsequent attempts to use the file stream will fail.

Clearing the State of a File Stream

Closing a stream does not change the internal state of the stream object. If the last read or write operation failed, the state of the object remains in a failure mode until we execute clear to reset the condition of the stream. After the clear, it is as if we had created the object afresh. (We should add clear after each close operation)

Two ways to read multiple files: 1. recreate the stream object for each file; 2. reuse the stream object and close/clear after each file.P 295

8.4.2 File Mode

Whenever we open a file, a file mode is specified. Different modes are represented by a set of values that can be controlled by bitwise operators:

File Modes
inopen for inputifstream/fstream
outopen outputofstream/fstream
appseek to the end before every writeofstream/fstream
ateseek to the end immediately after the openany file
trunctruncate an existing stream when opening itofstream/fstream
binarydo IO operations in binary modeany file
ate:has an effect only at the open: Opening a file in ate mode puts the file at the eof immediately after the open.

binary: process the file as a sequence of bytes; it does no interpretation of the characters in the stream.

in: by default, files associated with anifstream are opened inin mode. It permits the file to be read.

out: by default, files associated with an ofstream are opened in out mode. It permits the file to be written.A file opened in out mode is truncated: All data stored in the file is discarded. (in effect, specifying out mode for an ofstream is equivalent to specifying both out and trunc)

app: The only way to preserve the existing data in a file opened by an ofstream is to specify app mode explicitly

//output mode by default: truncates file named "file1"
ofstream outfile("file1");
// equivalent effect: "file1" is explicitly truncated
ofstream outfile2("file2", ofstream::out | ofstream::trunc);
// append mode: adds new data at the end of existing file named "file2"
ofstream outfile3("file3", ofstream::app);

Using the Same File for Input and Output   A.3.8(p.837)

An fstream object can both read and write its associated file. How an fstream uses its file depends on the mode specified when we open the file.

Mode Is an Attribute of a File, Not a Stream

So we need to set mode each time we open a file.

8.4.3 A Program to Open and Check Input Files

// opens in binding it to the given file
ifstream open_file(ifstream &in, const string &file)
{
  in.close();    //close in case it was already open
  in.clear();    //clear any existing errors
  // if the open fails, the stream will be in an invalid state
  in.open(file.c_str());  //open the file we were given
  return in;     //condition state is good if open succeeded
}

8.5 String Streams

The iostream library supports in-memory input/output, in which a stream is attached to a string within the program's memory. We should include sstream header to use these classes:

1. istringstream, derived from istream, reads from a string

2. ostringstream, derived from ostream, writes to a string

3. stringstream, derived from iostream, reads and writes the same string

sstream types have a constructor that takes a string. The constructor will copy the string argument into the stringstream object. These classes also define a member named str to fetch or set the string value that the stringstream manipulates.

stringstream-Specific Operations
stringstream strm;creates an unbound stringstream
stringstream strm(s);create a stringstream that holds a copy of the string s
strm.str()return a copy of the string that strm holds
strm.str(s)copies the string s into strm. return void

Using a stringstream

Some programs need to do some processing on a per-line basis and other works on each word within each line:

string line, word;            //will hold a line and word from input, respectively
while (getline(cin, line))    //read a line from the input into line
{
    //do per-line processing
    istringstream stream(line);  //bind stream to the line we read
    while (stream >> word)
    {
        //do per-word processing
    }
} 

stringstreams Provide Conversions and/or Formatting P 300

One common use of stringstream is when we want to obtain automatic formatting across multiple data types.

The sstream input and output operations automatically convert an arithmetic type into its corresponding string representation or back again

<think>好的,我现在需要帮助用户解决他们在使用ARMCLANG V6.23编译器时遇到的编译问题。用户提到的错误包括宏重定义、未声明的标识符'humidity'和'D11_OK',以及库函数'sprintf'未声明的问题。我需要仔细分析每个问题的可能原因,并提供有效的解决方案。 首先,关于'__ARMCLIB_VERSION'宏重定义的警告。用户可能同时使用了不同版本的编译器或头文件,导致该宏被重复定义。我需要想到如何处理不同编译器版本之间的兼容性问题,比如通过条件编译来避免重复定义。可能需要检查项目中的头文件,特别是用户自定义的bg.h文件,是否在包含系统头文件之前定义了该宏,从而引发冲突。 接下来是未声明的标识符'humidity'和'D11_OK'。这两个错误通常是因为变量或宏未正确声明或包含相应的头文件。用户可能在代码中使用了这些标识符,但没有在相关文件中声明它们,或者对应的头文件没有被正确包含。需要检查是否包含了DHT11传感器的驱动头文件,或者是否有条件编译指令导致这些声明未被包含。另外,D11_OK可能是一个宏,需要确认其定义的位置,并确保在调用D11_ReadData函数之前已经定义。 第三个问题是未声明的库函数'sprintf'。这可能是因为在ARMCLANG V6.23中,某些标准库函数默认被禁用或需要特定的宏定义来启用。用户可能没有包含<stdio.h>头文件,或者编译器配置中禁用了标准库函数,比如没有启用MicroLib。需要检查项目设置,确认是否启用了MicroLib,或者在代码中添加必要的宏定义来允许使用这些函数。 我需要参考用户之前的问题和回答,确保解决方案的一致性。用户之前提到在解决宏重定义时使用了条件编译,因此在这次回答中也需要推荐类似的方法。同时,用户可能在使用不同的编译器版本(如AC5和AC6),所以需要检查项目中的编译器配置是否正确,是否选择了正确的编译器版本,并确保所有路径和包含目录设置正确。 另外,用户提供的代码片段显示在main.c中调用了D11_ReadData函数,但出现了'humidity'和'D11_OK'未声明的错误,这可能意味着相关的传感器驱动头文件未被正确包含,或者这些标识符的声明被条件编译指令所包围,而对应的宏未定义。需要检查代码中的#include指令,并确保所有必要的头文件都被正确包含。 关于'sprintf'未声明的问题,可能需要检查是否在代码中包含了<stdio.h>,或者在项目设置中启用了C99模式,因为某些编译器版本可能默认使用不同的标准。另外,使用ARM Compiler 6时,可能需要添加特定的编译选项或宏来启用标准库函数。 总结下来,解决方案需要分步骤处理每个错误: 1. 处理'__ARMCLIB_VERSION'宏重定义:通过条件编译或调整头文件包含顺序来避免冲突。 2. 解决'humidity'和'D11_OK'未声明:确保包含正确的头文件,检查变量和宏的定义位置,确认驱动文件正确添加到项目中。 3. 解决'sprintf'未声明:包含<stdio.h>,启用MicroLib,或添加必要的编译器选项和宏定义。 同时,需要提供具体的代码示例和项目配置步骤,帮助用户逐步实施解决方案。确保回答结构清晰,步骤明确,并验证每个解决方案的有效性。</think>### 分步解决方案(基于ARMCLANG V6.23) #### 一、宏重定义问题 `__ARMCLIB_VERSION` **根本原因**:编译器版本冲突导致标准库版本宏重复定义(ARM Compiler 5和6同时存在)[^1][^2] **修复步骤**: 1. **强制指定编译器版本** 在项目配置中明确使用AC6编译器: ``` Options for Target > Target > ARM Compiler: V6.23 ``` <img src="https://example.com/keil_compiler_select.png" width="400" alt="编译器选择界面示意"> 2. **条件编译隔离定义** 在自定义头文件`bg.h`中添加版本判断: ```c #if (__ARMCC_VERSION >= 6000000) // AC6环境下禁用旧版本宏 #undef __ARMCLIB_VERSION #define __ARMCLIB_VERSION 6230001 // 与stddef.h保持一致 #else #define __ARMCLIB_VERSION 5060016 // AC5保留原定义 #endif ``` #### 二、未声明标识符 `humidity`和`D11_OK` **根本原因**:传感器驱动头文件未正确包含或硬件接口配置错误[^3] **修复步骤**: 1. **验证传感器驱动包含** 在`main.c`头部添加驱动声明: ```c #include "DHT11/dht11.h" // 确保路径正确 #include "bsp/bsp_io.h" // 硬件接口定义 extern DHT11_HandleTypeDef hdht11; // 外部声明传感器对象 ``` 2. **检查硬件初始化代码** 确认GPIO配置与传感器匹配: ```c // 在MX_GPIO_Init()函数内添加 GPIO_InitTypeDef gpio; gpio.Pin = GPIO_PIN_11; // 根据实际电路修改 gpio.Mode = GPIO_MODE_INPUT_PULLUP; // DHT11需要上拉 HAL_GPIO_Init(GPIOC, &gpio); // 端口号需与实际一致 ``` 3. **状态码宏定义验证** 在`dht11.h`中确认返回码定义: ```c #define D11_OK 0x00 // 操作成功 #define D11_ERROR 0x01 // 通信失败 ``` #### 三、`sprintf`未声明问题 **根本原因**:AC6编译器默认禁用非安全C库函数[^2] **修复步骤**: 1. **启用MicroLib支持** 项目配置中激活微库: ``` Options for Target > Target > Use MicroLIB ✔ ``` 2. **添加C99兼容性配置** 在编译器选项添加: ``` Misc Controls: --c99 -D__USE_C99_MATH ``` 3. **显式声明格式化函数**(备选方案) 在调用`sprintf`前添加: ```c #include <stdio.h> #pragma clang diagnostic ignored "-Wimplicit-function-declaration" ``` #### 四、编译环境验证流程 1. **预处理器输出检查** 命令行执行预处理(定位宏定义问题): ```bash armclang -E -I ./inc ../Core/Src/main.c -o main.i ``` 检查输出文件中`__ARMCLIB_VERSION`的定义位置 2. **符号表分析**(定位未声明标识符) 生成MAP文件验证符号存在性: ``` Options for Target > Linker > Generate Map File ✔ ``` 在生成的`.map`文件中搜索`humidity`和`D11_OK` #### 五、关键配置对比表 | 配置项 | 错误状态 | 修复后状态 | |-----------------------|-----------------------|----------------------| | ARM Compiler Version | V5/V6混合 | 强制指定为V6.23 | | Use MicroLIB | 未启用 | 启用 | | Include Paths | 缺失传感器驱动路径 | 添加`DHT11`目录 | | Preprocessor Macros | 无版本控制宏 | 添加`__ARMCC_VERSION`| #### 六、典型错误代码修正示例 **错误代码片段**: ```c if(D11_ReadData(&humidity, NULL) == D11_OK) { // 触发undeclared错误 sprintf(buffer, "Humi:%d", humidity); // sprintf未声明 } ``` **修正后代码**: ```c #include "dht11.h" // 添加头文件 #include <stdio.h> // 标准库支持 char buffer[32]; DHT11_Data_t humidity; // 正确定义结构体 if(D11_ReadData(&humidity, NULL) == D11_OK) { snprintf(buffer, sizeof(buffer), "Humi:%d", humidity.value); // 使用安全版本 } ``` --- ### 深度技术验证方法 1. **编译器诊断选项** 添加编译参数获取详细错误分析: ```bash armclang -Wall -Wextra -Wpedantic -save-temps=obj ``` - `-save-temps=obj` 生成预处理/汇编中间文件 - `-Wpedantic` 检查严格标准符合性 2. **硬件抽象层(HAL)一致性检查** 确认CMSIS版本匹配: ```c #if __CORTEX_M != 0x03 // 检查是否为Cortex-M3 #error "HAL库与芯片型号不匹配" #endif ``` [^1]: ARM Compiler Migration Guide Chapter 4. Macro Changes [^2]: ARMCLANG 6.23 Release Notes Section 2.3 Library Restrictions [^3]: STM32 HAL库传感器驱动开发指南 第5.2节GPIO配置规范
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值