博主介绍:爱打游戏的计算机专业学生
博主主页:夏驰和徐策
所属专栏:夏驰和徐策带你从零开始学C++
前言:
C++语言并未定义任何输入输出 (IO) 语句,取而代之,包 含了一个全面的标准库 (standard library) 来 提供IO 机制 (以 及很多其他设施)。 对于很多用途,包 括本书中的示 例来说,我们只需了解IO 库 中一部分基本概念和操作。 本书中的很多示例都使用了 iostream 库 。 iostream 库包含两个基础类型 istream 和 ostream, 分别表示输入流和输出流。一 个流就是一个字符序列,是从 IO 设备读出或写入IO 设备的。术语“流” (stream) 想要表达的是,随着时间的推移,字符 是顺序生成或消耗的。
我的理解(告诉我什么信息):
这段前言主要介绍了以下内容:
1. C++语言本身没有定义输入输出(IO)语句。
2. C++提供了一个全面的标准库来实现输入输出(以及其他功能)。
3. 对于许多场景,包括书中的示例,我们只需要理解标准库中关于IO部分的一些基本概念和操作。
4. 书中的很多示例使用了`iostream`库。
5. `iostream`库中包含了两个基础类型:`istream`(表示输入流)和`ostream`(表示输出流)。
6. 一个流是一个字符序列,可以从IO设备中读取或写入。
7. 术语“流”表示字符是随着时间的流逝而顺序生成或消耗的。
简而言之,这段前言向读者介绍了C++是如何处理输入和输出的,特别是通过`iostream`库中的`istream`和`ostream`两种基本类型。
我的疑问:
1.什么是IO机制?
IO机制,即输入输出(Input/Output)机制,是计算机系统中一个核心的概念。它涉及到数据在计算机的内部组件(如CPU和内存)与外部设备(如键盘、鼠标、显示器、硬盘、网络设备等)之间的交互。以下是IO机制的一些关键点:
1. **输入(Input)**: 这是将数据从外部设备传输到计算机的内部的过程。例如,当你在键盘上敲击键时,这个动作的信息就被送入计算机进行处理。
2. **输出(Output)**: 这是将数据从计算机内部传输到外部设备的过程。例如,当计算机需要在屏幕上显示文本或图像时,这些数据被送到显示器上。
3. **IO接口和控制器**: 为了确保数据可以从外部设备正确地输入到计算机中,或从计算机正确地输出到外部设备,计算机中有专门的硬件接口和控制器来管理这些交互。
4. **IO操作的阻塞性**: 通常,IO操作(特别是从慢速设备,如硬盘或网络)比CPU的计算要慢得多。为了有效地管理这种不平衡,操作系统提供了各种机制,如中断、DMA(直接内存访问)和异步IO,使得CPU不必在等待IO操作完成时被闲置。
5. **高级和低级IO**: 在软件层面,IO可以分为低级(通常涉及直接与硬件交互的操作)和高级(通常通过操作系统或库函数提供,并为程序员提供更友好的接口)。
6. **IO库和API**: 大多数编程语言提供了IO库或API,使得程序员可以容易地进行输入和输出操作,而不需要直接管理底层的硬件交互。例如,C++提供了`iostream`库,Python提供了`input()`和`print()`函数等。
IO机制是任何计算机系统中不可或缺的部分,它使得系统能够与外部世界交互,处理数据和提供结果。
一、标准输入输出对象
标准库定义了 4个 IO对 象。 为了处理输入, 我们使用一个名为 cin (发音为see-in)的 istream类型的对象。这个对象也被称为标准输入 (standard nput)。 对 于输出,我 们使用一个名为 cout(发音为 see-out)的 ostream类型的对象。 此对象也被称为标准输出 (standard output)。 标准库还定义了其他两个 ostream对象,名为 cerr和 clog (发音分别为 see-err和see-log)。 我们通常用 cerr 来输出警告和错误消息,因此它也被称为标准错误 (standard error)。 而 clog 用来输出程序运行时的一般性信息。系统通常将程序所运行的窗口与这些对象关联起来。 因此,当我们读取 cin, 数据将从程序正在运行的窗口读入, 当我们向 cout、 cerr 和 clog 写入数据时,将会写到同一个窗口。
我的理解:
这段话的核心是描述C++标准库中的四个输入输出对象,并说明了它们的用途。这些对象在C++中常被用于控制台的输入与输出。
1. **cin(标准输入)**: 它是一个`istream`类型的对象,主要用于从控制台获取用户输入。例如,当你想要从用户那里获取一个数值或字符串时,你可能会用到`cin`。
2. **cout(标准输出)**: 它是一个`ostream`类型的对象,主要用于向控制台输出数据。比如你编写了一个程序来计算两个数字的和,你可能会使用`cout`来显示结果。
3. **cerr(标准错误)**: 这同样是一个`ostream`类型的对象,但它主要用于输出错误消息和警告。这是一个重要的设计理念,因为将错误消息与普通输出分开可以帮助用户或开发者更快地识别问题。
4. **clog(标准日志)**: 这也是一个`ostream`对象,主要用于输出程序运行时的一般性信息,例如日志。
最后,这段话还指出,当你的程序运行在一个窗口中(例如,一个终端或命令提示符),与这些对象相关的输入和输出操作通常都在这个窗口中进行。简而言之,如果你在程序中使用`cin`来获取输入,你会在该窗口中看到一个提示,等待你输入数据;当你使用`cout`, `cerr`, 或`clog`来输出信息,这些信息会显示在同一个窗口中。
二、一个使用IO库的例子
1.求和程序
在书店程序中,我们需要将多条记录合并成单一的汇总记录。作为一个相关的,但更简单的问题,我们先来看一下如何将两个数相加。通过使用 IO 库,我们可以扩展 main程序,使 之能提示用户输入两个数,然后输出它们的和:源代码:
#include <iostream>
using namespace std;
int main()
{
std::cout << "Enter two numbers:"<<std::endl;
int v1 = 0, v2 = 0;
std::cin >> v1 >> v2;
std::cout << "The sum of" << v1 << "and" << v2
<< "is" << v1 + v2 << std::endl;
return 0;
}
运行结果:
三、向流写入数据
main的函数体的第一条语句执行了一个表达式(expressin)。在C++中,一个表达式产生一个计算结果,它由一个或多个运算对象和(通常是)一个运算符组成。这条语句中的表达式使用了输出运算符(<<)在标准输出上打印消息:
std::cout<<"Enter two numbers:"<< std::endl;
<<运算符接受两个运算对象:左侧的运算对象必须是一个ostream对象,右侧的运算对象是要打印的值。此运算符将给定的值写到给定的ostream对象中。输出运算符的计算结果就是其左侧运算对象。即,计算结果就是我们写入给定值的那个ostream对象。
我们的输出语句使用了两次<<运算符。因为此运算符返回其左侧的运算对象,因此第一个运算符的结果成为了第二个运算符的左侧运算对象。这样,我们就可以将输出请求连接起来。因此,我们的表达式等价于(std::cout<<"Enter two numbers:")<< std::endl;链中每个运算符的左侧运算对象都是相同的,在本例中是std::cout。我们也可以用两条语句生成相同的输出:std::cout <<"Enter two numbers:";std::cout << std::endl;第一个输出运算符给用户打印一条消息。这个消息是一个字符串字面值常量(string literal),是用一对双引号包围的字符序列。在双引号之间的文本被打印到标准输出。第二个运算符打印end1,这是一个被称为操纵符(manipulator)的特殊值。写入endl 的效果是结束当前行,并将与设备关联的缓冲区(buffer)中的内容刷到设备中。缓冲刷 新操作可以保证到目前为止程序所产生的所有输出都真正写入输出流中,而不是仅停留在 内存中等待写入流。
warning!
程序员常常在调试时添加打印语句。这类语句应该保证“一直”刷新流。否则, 如果程序崩溃,输出可能还留在缓冲区中,从而导致关于程序崩溃位置的错误推断。
我的理解
这段话介绍了在C++中如何使用输出运算符(<<)来进行输出操作。以下是这段话的关键信息:
1. 在C++中,表达式产生一个计算结果,由一个或多个运算对象以及通常的一个运算符组成。
2. 一个例子被给出:`std::cout<<"Enter two numbers:"<< std::endl;`。这是在标准输出上打印消息的代码。
3. 输出运算符(<<)需要两个运算对象。左侧的必须是一个`ostream`对象(如`std::cout`),而右侧是要打印的值。
4. `<<`运算符的返回值是其左侧的运算对象,这允许我们进行链式输出操作。
5. 输出语句中的文本是字符串字面值常量,由双引号括起。
6. `std::endl`是一个操纵符,它的效果是结束当前行并刷新与设备关联的缓冲区,确保内容真正被写入输出流。
7. 警告部分提到,当程序员在调试时添加打印语句,他们应确保总是刷新流。否则,如果程序崩溃,未刷新的输出可能导致关于崩溃位置的误判。
简而言之,这段话告诉我们一些输入输出使用的时候的常识,解释了C++中如何使用输出运算符进行标准输出操作,以及为何及如何使用`std::endl`来刷新输出缓冲区,确保信息正确输出。
四、使用标准库中的名字
细心的读者可能会注意到这个程序使用了std::cout和std::endl,而不是直接的cout和endl。前缀std::指出名字cout和endl是定义在名为std的命名空间(namespace)中的。命名空间可以帮助我们避免不经意的名字定义冲突,以及使用库中相同名字导致的冲突。标准库定义的所有名字都在命名空间std中。
通过命名空间使用标准库有一个副作用:当使用标准库中的一个名字时,必须显式说明我们想使用来自命名空间std中的名字。例如,需要写出std::cout,通过使用作用域运算符(::)来指出我们想使用定义在命名空间std中的名字cout。3.1节(第74页)将给出一个更简单的访问标准库中名字的方法。
我的理解:
这段话解释了C++标准库中名字的命名空间问题。以下是这段话的主要内容和要点:
1. **命名空间**:它是一个容器,其中包含了变量、函数和其他实体的定义,使得不同的部分或不同的库可以使用相同的名字而不冲突。
2. **std::前缀**:在C++中,标准库中的所有定义都放在名为`std`的命名空间中。所以,当我们想使用标准库中的`cout`或`endl`等内容时,我们使用`std::cout`和`std::endl`这样的语法。
3. **冲突避免**:命名空间的主要目的是避免名称冲突。例如,如果两个库都有一个叫做`print`的函数,但它们在不同的命名空间中,那么就不会有冲突。我们可以通过指定命名空间来选择我们想使用的`print`函数。
4. **作用域运算符(::)**:这是C++中一个重要的运算符,用于指定要使用的命名空间中的特定内容。例如,`std::cout`使用`::`来指定我们想要从`std`命名空间中获取`cout`。
5. **简化访问**:虽然每次都使用`std::`前缀可能看起来有点繁琐,但有其他方法可以简化这个过程,这在文章中提到的3.1节中会有详细的解释。
简而言之,这段话讲解了在C++中为什么要使用命名空间、它的作用和如何正确地使用标准库中的内容。
五、从流读取数据
在提示用户输入数据之后,接下来我们希望读入用户的输入。首先定义两个名为v1和v2的变量(variable)来保存输入:
int vl = 0,v2 = 0;
我们将这两个变量定义为int类型,int是一种内置类型,用来表示整数。还将它们初始化(initialize)为0。初始化一个变量,就是在变量创建的同时为它赋予一个值。下一条语句是std::cin>> vl>> v2;它读入输入数据。输入运算符(>>)与输出运算符类似,它接受一个istream作为其左侧运算对象,接受一个对象作为其右侧运算对象。它从给定的istream读入数据,并存入给定对象中。与输出运算符类似,输入运算符返回其左侧运算对象作为其计算结果。因此,此表达式等价于(std::cin>> v1)>> v2;由于此运算符返回其左侧运算对象,因此我们可以将一系列输入请求合并到单一语句中。本例中的输入操作从std::cin读入两个值,并将第一个值存入v1,将第二个值存入v2。换句话说,它与下面两条语句的执行结果是一样的std::cin>> vl;std::cin >> v2;
我的理解:
这段话描述了在C++中如何从标准输入读取用户输入并保存到变量中。以下是这段话的主要内容和要点:
1. **定义变量**:程序定义了两个名为`v1`和`v2`的变量来保存用户的输入。代码示例为`int v1 = 0, v2 = 0;`。
2. **数据类型**:这两个变量被定义为`int`类型,这是C++的一种内置数据类型,用于存储整数。
3. **初始化**:在定义变量的同时,给它们分别赋予了一个初始值0。这个过程称为初始化。
4. **读取输入**:使用`std::cin >> v1 >> v2;`从标准输入读取数据。这里,`>>`是输入运算符。
5. **输入运算符**:输入运算符与输出运算符在功能上是相反的。它从`istream`(例如`std::cin`)读取数据,并将数据保存到其右侧的运算对象中。与输出运算符类似,输入运算符也返回其左侧的运算对象。
6. **链式输入**:由于输入运算符返回其左侧的运算对象,我们可以在单一语句中合并多个输入请求。因此,`std::cin >> v1 >> v2;`与以下两条语句的效果相同:
std::cin >> v1;
std::cin >> v2;
总之,这段话解释了在C++中如何定义和初始化变量,并使用输入运算符从标准输入中读取用户输入,保存到指定变量中。
六、完成程序
剩下的就是打印计算结果了:
std::cout<<"The sum of"<<v1<<" and" << v2 <<"is"<<v1+v2<<std::endl;
这条语句虽然比提示用户输入的打印语句更长,但原理上是一样的,它将每个运算对象打印在标准输出上。本例一个有意思的地方在于,运算对象并不都是相同类型的值。某些运算对象是字符串字面值常量,例如"The sum of"。其他运算对象则是int值,如v1、v2以及算术表达式v1+v2的计算结果。标准库定义了不同版本的输入输出运算符,来处理这些不同类型的运算对象。
总结:
C++语言的输入输出操作主要通过`iostream`库进行,主要涉及`cin`和`cout`对象,以及输入运算符`>>`和输出运算符`<<`。以下是输入输出的重点和难点:
### 重点:
1. **基本对象**:使用`std::cin`进行输入,使用`std::cout`进行输出。
2. **输入/输出运算符**:`>>`用于输入,`<<`用于输出。
3. **链式操作**:由于`cin`和`cout`都会返回其本身,所以可以进行链式输入和输出,如`std::cin >> a >> b;`或`std::cout << a << " " << b;`。
4. **格式化**:可以通过`iomanip`库来对输出进行格式化,如设置宽度、精度、填充字符等。
5. **错误处理**:可以检查`cin`的状态,例如使用`cin.fail()`来检查输入是否失败。
6. **缓冲区控制**:如`std::endl`和`std::flush`可以刷新输出缓冲区。
### 难点:
1. **输入错误处理**:当用户输入与预期不符时,例如期待整数输入时用户输入了文本,这会导致`cin`进入错误状态。处理这种情况并给出合适的提示是很重要的。
2. **缓冲区管理**:理解输入输出缓冲区以及如何清除和控制缓冲区可能有些复杂,特别是在连续的输入操作中。
3. **格式化输出**:尽管`iomanip`提供了许多工具,但复杂的格式化输出仍然可能是一个挑战。
4. **文件输入输出**:除了标准输入和输出,C++还提供了文件输入和输出功能,这需要对`fstream`库有深入的了解。
5. **编码问题**:当处理非ASCII字符时,例如UTF-8或其他字符编码,需要特别注意。
6. **多线程和同步**:在多线程环境中,对输入输出的同步是一个挑战。
理解和掌握C++的输入输出操作是学习过程中的基础部分,但也需要注意其中的细节和难点,特别是在实际应用中。
易错点:
C++的输入输出操作中有一些常见的易错点,尤其是对初学者而言。以下是一些经常出现的问题和易错点:
1. **忽略命名空间**:忘记使用`std::`前缀(例如直接使用`cin`而不是`std::cin`),除非使用了`using namespace std;`。
2. **混淆输入输出运算符**:将`>>`和`<<`混淆,尤其是在复杂的链式操作中。
3. **不检查输入状态**:在读取数据后,不检查`cin`的状态可能导致错误的数据被使用。
4. **遗忘缓冲区**:例如,在读取一个数字后,立即读取一个字符串可能会遇到问题,因为数字后的换行符仍留在缓冲区中。
5. **不适当的格式化**:例如,不正确地使用`setw`、`setprecision`等格式化工具。
6. **不处理输入错误**:如果用户输入的数据类型与预期不符,程序可能会产生意外的行为。
7. **不刷新输出缓冲区**:有时,输出可能不会立即显示,这是因为它仍然在缓冲区中。忘记使用`std::endl`或`std::flush`可能导致这种情况。
8. **使用`endl`过于频繁**:虽然`endl`可以刷新缓冲区,但频繁使用可能导致效率降低。在不需要刷新缓冲区的情况下,可以考虑使用`'\n'`。
9. **文件操作中的路径问题**:在进行文件输入输出时,文件路径的问题可能会导致读写失败。
10. **不关闭文件**:完成文件操作后,忘记关闭文件可能导致数据丢失或其他问题。
11. **混合使用C和C++风格的I/O**:例如,同时使用`printf`/`scanf`和`cin`/`cout`可能导致预期之外的结果,因为它们可能有不同的缓冲机制。
为了避免这些易错点,建议始终测试I/O操作,并在实际应用中考虑到各种可能的输入情况。
知识星球扩展:
1.什么是using namespace?
我的理解:
`using namespace std;`是C++中的一条指令,用于告诉编译器,我们想要使用`std`命名空间中定义的所有名字,而无需每次都为它们加上`std::`前缀。
命名空间是C++中的一个特性,它可以组织和封装代码,避免名字冲突。标准库中的所有组件都定义在`std`命名空间中。
当使用`using namespace std;`后:
1. 我们可以直接使用`cout`、`cin`、`vector`、`string`等,而不是`std::cout`、`std::cin`、`std::vector`、`std::string`等。
2. 代码变得更加简洁。
但是,该指令也有一些潜在的问题:
1. **名字冲突**:如果自己的代码或其他库中有与`std`命名空间中同名的函数、类或变量,就会发生名字冲突。
2. **代码可读性**:对于不熟悉C++标准库的人来说,他们可能会对某些名字感到困惑,因为不清楚它是否来自标准库。
3. **未来的问题**:如果未来版本的C++标准库增加了新的组件,并且与您的现有代码中的名字冲突,那么在使用`using namespace std;`时可能会遇到问题。
因此,尽管在简短的程序或学习中使用`using namespace std;`可以使代码更简洁,但在大型项目和实际应用中,为了避免潜在的问题,推荐显式使用`std::`前缀或只引入需要的组件,例如`using std::cout;`、`using std::vector;`等。
参考书籍:
C++ Primer 第五版