在学习算法与数据结构之前,希望大家打好这一方面的基础,标准类库对于之后的学习尤其重要。
流和缓冲区
为了防止歧义,这里将操作系统下运行的程序统称为文件,对于C++而言,只需要关心输入输出的字节流,而不必关心字节来自何处。为了更高效地处理这样地字节流,例如从硬盘提取字节,单次提取一个字节,重复提取费时费力,通常一次提出512个字节(可能更多),而输入C++程序则通过字节流的方式。那么,每次存放这512个字节的内存块称为缓冲区。
该图可以将输入部分左侧看作信息来源(硬盘等),较快速地进入缓冲区并将之填满,程序永远以单字节的流水形式从缓冲区处理字节流。输出时,而输出也是同理,将缓冲区填满一并送给硬盘并清空缓冲区。这就是flush the buffer的由来。
对于符合ANSI标准的C++,程序会在下一次输出到来之前或者换行符到来时刷新输出缓冲区。
关于iostream类
streambuf类:为缓冲区提供内存,并提供用于填充,访问,刷新和管理内存块的类方法。
ios_base类:表示流的一般特征,例如是否可读可写,二进制流还是文本流。
ios类:基于base类,其中提供一个指向streambuf对象的指针。
os/istream类:从ios类派生,提供输入输出方法。
iostream类:基于os/istream类。
iostream类通常自动生成8个对象:
- cin和cout类被关联到输入输出设备(通常是键盘和显示器)。类似的wcin和wcout类处理的是wchar_t类型。
- cerr对象处理标准错误流,注意这个类没有被缓冲,故直接输出到目标设备上,不需等待换行或缓冲区填满。wcerr类同理
- clog对象也处理标准错误流,会被缓冲。wclog类同理。
重定向:
操作系统可以对输入输出重定向以让输入输出关联到不同的设备或者文件上。部分操作系统甚至可以重定向标准错误流 。
使用cout进行输出
正如前面所说,C++将输出看作字节流。如果存在数值类型等其他类型的数据,需要转换成文本形式表示的字符流。例如int数据2.5。那么输出会处理‘2’,‘.’,‘5’三个字符。
重载的<<运算符:
该运算符原本为左移运算符,X<<3译为按二进制向左移动三位。但在ostream类中将其重载为插入运算符,使之可以识别C++中所有的基本数据类型。
函数原型ostream& operator << (int);
现在的cout也支持输出标准数据类型的指针,注意以’/0’结束字符串。获取字符串等类型的指针地址时,请转换为(void *)类型,对于整形等标准型可以直接&取地址。
其他cout方法:
put()与write(),简而言之前者显示字符,后者显示字符串,put()可以将合适的类型如int识别为char,例如put(66.3)将显示ASCI码为66的字符。
ostream& put(char);
basic_ostream<charT,traits>& write(const char_type* s, streamsize n);//write原型
注意write方法不会再遇到空字符时停止,而会坚持打印指定数目的字符。
刷新输出缓冲区:
通常缓冲区大小为512字节或其的整数倍。刷新缓冲区目前的C++大致有三种方法。
- 即将到来新的输入。
- 输入换行符。
- 执行flush函数
关于flush,原本是一个函数,刷新缓冲区。cout对<<进行了重载,使得cout << flush可以顺利执行。
cout格式化:
ostream插入运算符转换为文本形式。
对于浮点类型,最新的浮点被显示为6位,末尾的0不显示。当指数大于6或小于5时将采用科学计数法,字段宽度刚好容纳数字和负号。参考%g的C标准库函数fprintf()。在过去,浮点为带小数点后6位,末尾的0不显示。那么如何修改这种输出呢,答案是通过使用控制符,可以控制显示时的计数系统。
ios_base类现在作为ios的基类,描述格式状态信息,包含了标准输入输出中的非模板特性,而在新系统中,ios包含char和wchar_t的具体化模板。
dec、hex、oct控制符:
hex(out); //以十六进制输出
cout << dec; //以十进制输出
//oct表示八进制
调整字段宽度:
使用width()成员函数:
int width();
int width(int); //函数原型
cout.width(12); //在显示<=12字节数据的时候宽度为12,大于则补充。
默认采用右对齐方式,并且该函数只对cout的下一个项目生效,之后恢复默认。
填充字符:
cout.fill(char);
通常与width(int)搭配使用,不过fill函数将一直生效,除非更改。
例如填充*,结果会变成:
cout.width(5);
cout.fill('*');
cout << 'A' << endl;
//显示结果:****A
设置浮点数显示精度:
可以使用cout.precision(int);
int为整数
原本浮点数默认精度为6位,修改为精度为int指定的位数。(小数点不占位)设定除非被重置,否则一直有效。
setf()函数:
关于浮点数,是否显示末尾0及正好为整时是否保留小数点,可以使用cout.setf(ios_base::showpoint);
此处的showpoint定义在ios_base类中,为类级静态常量。
setf()函数更多的时用于设置输出系统运行模式的标志位,通过位操作来修改DIP开关以及配置相应硬件。例如hex(cout);
就是设置其中的1个位。
关于setf函数有两个原型,第一个是:
fmtflags setf(fmtflag);
这里的fmtflags是bitmask类型的typedef名。而函数的返回值是进入该函数之前的标志位设置,如果需要还原,可以保留这个值。关于标志位设置见下表:
常量 | 含义 |
---|---|
ios_base::boolalpha | 输入输出bool值,可以为ture和false |
ios_base::showbase | 对于输出,使用C++基础前缀(0, 0x) |
ios_base::showpoint | 显示末尾小数点和全0 |
ios_base::uppercase | 十六进制输出采用大写 |
ios_base::showpos | 在正数前加+ |
注意bitmask类型用以存储各个位置,可以是整型,枚举或者STL的bitset容器,每一位都可以单独访问。iostream软件包利用该类型存储状态信息。
第二个set函数原型是fmtflags setf(fmtflags, fmtflags);
简而言之,第一个参数代表你要设置的位,第二个参数表示你要清除的位。
第二个参数 | 第一个参数 | 含义 |
---|---|---|
ios_base::basefiled | ios_base::dec/oct/hex | 进制 |
ios_base::floatfield | ios_base::fixed/scientific | 定点/科学计数 |
ios_base::adjustfield | ios_base::left/right/internal | 对齐方式 |
在计数模式中,默认的C++对应于C的%g,而定点法对应%f,科学计数法对应于%e。setf()是ios_base类中的成员函数,但由于继承关系可以在cout中使用它。
在C++中不论定点还是科学,都显示末尾的0,且精度指小数的位数而非总位数。而在默认模式中,精度则是指总位数。
与之对应的是unset();
操作与setf()函数相反,setf将目标标志位置为1,而unsetf则将其置为0;
标准控制符:
朋友们看了上述的setf函数是不是感觉非常难以记忆 ,别着急,如同dec,hex,oct等控制符可以直接调用正确的setf函数配置。
boolalpha
noboolalpha
showbase
noshowbase
showpoint
noshowpoint
showpos
noshowpos
uppercase
nouppercase
internal
left
right
dec
hex
oct
fixed
scientific
头文件iomanip
关于以上的讨论,可以用更简单的形式输出。
cout << setw(6) << setfill('*') << setprecision(2) << a << endl; // 设置宽度6,*填充,精度为2.
使用cin进行输入
cin也将标准输入表示为字节流,并且在输入到接收对象的时候实现类型转换。可以形象地理解为:
cin >> value_holder; //甚至可以是变量,引用,被解除引用的指针。
istream& operator >> (int &); //常见的函数原型
注意对于字符串指针类型,<<被重载了以适应连续输入,直至该字符串结束。在检查输入流的时候,该运算符跳过空格、换行和制表符,直到遇到非空白字符。在对象输入时,主动检查类型是否匹配,并将不合适的类型转入下一个输入流等待,或者返回一个false(istream对象有标志位)。
#include <iostream>
using namespace std;
int Num;
char Chr;
int main()
{
Num = 0;
cout << "test start!!" << endl;
cin >> Num >> Chr;
cout << "num = " << Num << endl;
cout << "char = " << Chr << endl;
cin >> Num; //此时故意输入一个非整型数据q。
cout << "err = " << Num<< endl;
cout << "test end!!" << endl;
return 0;
}
显示结果如下:
由此判断cin会根据输入判断是否符合其数据类型,若不符合,则cout会返回0提示错误。cin中的存在的不符合类型的数据将作为下一次输入的队列顶。
cin的流状态:
流状态继承自ios_base类,被定义为iostate类型,而iostate也是一种bitmask类型。流状态有3个元素组成,每个元素都是1位。他们分别是eofbit、badbit或failbit。
成员标志位 | 含义 |
---|---|
eofbit | 到达文件尾,置1 |
badbit | 流被破坏,例如文件损坏,置1 |
failbit | 输入操作未能读取预期字符或者没有写入预期字符,置1 |
goodbit | 另一种表示0的方法 |
下面是一些报告或改变流状态的方法:
成员方法 | 描述 |
---|---|
good() | 如果流可以使用(所有位都被清除),返回true |
bad() | 如果badbit被设置,返回true |
fail() | badbit或failbit被设置,返回true |
rdstate() | 返回流状态 |
exceptions() | 返回位掩码,指出哪些标记导致异常被触发 |
exceptions(isostate ex) | 设置哪些状态将导致clear()函数发生异常 |
clear(iostate s) | 将流状态置为s;s的默认值为0(goodbit),如果rdstate()与exceptions()返回值均不为0,则发生异常 |
setstate(iostate s) | 调用clear(rdstate()|s)将状态位置为s的高位且其他位保持不变 |
clear(eofbit); //另两位将被清除
setstate(eofbit); //另两位不受影响
关于异常处理:
#include <iostream>
#include <exception>
using namespace std;
int main()
{
cin.exceptions(ios_base::failbit);
cout << "Enter Numbers: ";
int sum = 0;
int input;
try
{
while(cin >> input)
{
sum += input;
}
}
catch (ios_base::failure& bf)
{
cout << bf.what() <<endl;
cout << "error occurs\n";
}
cout << "Last value entered = " << input << endl;
cout << "Sum = " << sum << endl;
cin.clear(); //此时已经抛出异常,必须清除该异常,才能继续执行。
char *p = new char[5];
cin >> p;
cout << "Rest of them = " << p << endl;
delete[] p;
return 0;
}
流状态在发生异常的时候将会关闭,上次cin当中的异常数据仍会保留在队列中,当异常清除之后,需要合适地处理即将到来的cin。处理这些数据通常采用如下方法:
while(cin.get() != '\n')
continue; //一直读取直至读到换行符,但有缺陷,无法一次处理多个换行存在的情形。
isspace(); //该函数是一个cctype类型的函数
while(!isspace(cin.get()))
continue;
istream类的其他方法:
非格式化输入函数get()
和getline()
有几种不同的原型,可以查阅相关API手册,这里只做简单介绍:
1.单字符输入:
get(); //空格,制表,换行符依然被读取。
get(char&); //将读取的字符参数传递给该引用。
get(void); //将字符转换为整型并返回,同get()。
区别于传统的cin >>
,get()会处理任何字符。注意除get(void)
,其余的cin.get()都会返回cin对象的引用,因此可以拼接。而get(void)
返回的是一个整型,所以不能拼接。
int ch
cin.get(ch); //返回cin对象的引用,到达文件尾时,赋值不会成功且cin置为false。
ch = cin.get(); //到达文件尾时返回eof。
三种方式的选择:
cin >> ch; //需要跳过空格,制表,换行符时的首选。
get(char&); //比空参数更佳,但在处理eof时会比较麻烦。
get(void); //希望检查每一个字符。
cin.get(); //可以等效为C语言的getchar()
cout.put(ch); //替换putchar(ch);
2.字符串输入:
istream& get(char *, int ,char);
istream& get(char *, int);
istream& getline(char *, int , char);
istream& getline(char *, int);
以上原型通过更为通用的模板简化声明而来。第一个参数是接收字符串的内存地址,第二个参数比原字符串长度+1(为了存放结尾的空字符),第三个参数指定用作分界符的字符,只有两个参数的版本用’\0’作为分界符。以上函数都在读取到最大数目或者遇到换行符后停止。
get(...)
与getline(...)
的区别在于get(...)
将换行符留在输入流中,必须单独处理,而getline(...)
直接丢弃换行符。
istream& ignore(int = 1, int = EOF); //用于忽略指定长度的字符串或到达第一个分界符。
具体解释如下:
cin.ignore(255 , '\0');
会抛弃长度为255字节,或者第一个’\0’之前的字符串(包含这个字符本身)。
关于getline()在QT下的测试:
cin.getline(ch, 20); //在没有超过20字节的情况下,默认以'\n'作为分界符。
cin.getline(ch, 20, x);其中x为除了换行之外的任意分界符,此时无论接收到几个'\n'都会接收。
另外getline在停止的时候会丢弃结尾的分界符,而get会将该分界符保留并等待下一次输入的时候将其输出。
意外字符串输入:
参见之前的三个标志位,以及内存溢出的问题。
其他的istream方法:
read();//读取,与get函数类似,但不会在结尾加上空字符。
peek(); //返回字节流中的下一个字节,但并不抽取(实质是抽取再放回)。
gcount(); //返回最后一个非格式化读取的字符数。
putback(); //将字符插入字符串。