[1] I/O基础
大多数计算机语言的输入输出的实现都是以语言本身为基础的,但是C/C++没有这样做。C语言最初把I/O留给了编译器实现人员。这样做的一个原因是可以提供足够的自由度,使之最适合目标机器的硬件条件。但是大多数实现人员都将I/O建立在了Unix库函数中,之后C才将该库引入了C标准中,被C++继承了下来。
但是C++也有自己的I/O解决方案,将其定义于iostream和fstream中。这两个库不是语言的组成部分,只是定义了一些类而已。
C++把输入和输出看作字节流。对于面向文本的程序,一个字节代表一个字符。流充当了源于目标之间的桥梁,因此对流的管理包含两个方面:从哪里来,到哪里去。其中,缓冲区作为临时存储空间,可以提高流处理的速度。
C++中专门定义了一些类来管理流和缓冲区:
C++98定义了一些模板类,用以支持char和wchar_t类型;
C++ 11添加了char16_t和char32_t类型(实则就是一些typedef来模拟不同的类型)。
C++程序中若包含了iostream类库,则自动创建8个流对象:4个窄的,4个宽的:
cin: 标准输入流,默认关联到键盘[与之对应的宽型为wcin(处理wchar_t)];
cout: 标准输出流,默认关联到显示器[与之对应的宽型为wcout(处理wchar_t)];
cerr: 标准错误输出流,默认关联到显示器,这个流没有缓冲区,意味着信息会直接发送到显示器[与之对应的宽型为wcerr(处理wchar_t)];
clog: 标准错误输出流,默认也关联到显示器,但是这个流对应着缓冲区[与之对应的宽型为wclog(处理wchar_t)]。
[1.1] cout 的使用
C++将输出看作是字节流(可能是8位/16位/32位),但在将字节流发送给屏幕时,希望每个字节表示一个字符值。比如,要显示-2.34,需要5个字符表示,分别为-/2/./3/4,并不是这个值的浮点数表示形式。因此,ostream类的最重要的任务之一就是将数值类型转换为文本形式表示的字符流。为了达到这个目标,C++使用了如下方法:
重载的<<运算符:C++的重载操作除了基本的内置类型,还有如下几种:const signed char*/const unsigned char*/const char*/void*。
对于其他类型的指针,C++将其看作void*,然后打印地址的数值。如果要想获得字符串的地址,必须将其强转为void*类型。
ostream类的put()和write()方法,前者用于显示字符,后者用于显示字符串。
ostream& put(char);
// cout.put(‘B’).put(‘T’);
basic_ostream<charT, traits>& write(const char_type* s, streamsize n);
// write()的第一个参数是要显示的字符串的地址,第二个参数是要显示字符个数。
1 const char* name = "benxintuzi"; 2 for(int i = 0; i <= strlen(name); i++) 3 { 4 cout.write(name, i); 5 cout << endl; 6 } 7 8 /** output */ 9 b 10 be 11 ben 12 benx 13 benxi 14 benxin 15 benxint 16 benxintu 17 benxintuz 18 benxintuzi
说明:
write()方法不会在遇到空白字符就停止打印,而是打印指定数目的字符,即使超出了字符串的边界。write()也可将数字转换为相应的形式打印,此时需要将数字的地址转换为char*,但是不会打印字符,而是内存中表示数值的位,可能是乱码,这个再正常不过了。
由于ostream类对数据进行了缓冲,因此利用cout也许并不会立即得到输出,而是被存储在缓冲区中,直至缓冲区填满为止。通常,缓冲区大小为512字节及其整数倍。如果需要将尚未填满的缓冲区进行刷新操作,需要执行如下代码:
cout << flush; // 立即刷新
或者:
cout << endl; // 立即刷新并插入换行符
控制数值显示的格式:
dec/hex/oct,虽然这些都是函数,但是其通常的使用方式是:
cout << hex;
cout << value << endl;
使用width成员函数将不同的字段放到宽度相同的字段中:有两个函数版本:
int width()/int width(int i);
第一个返回当前字段所占的空格数;
第二个函数将宽度设置为i个空格后,返回旧的空格数。但是注意:该函数只影响紧挨着它的一项显示输出,然后将字段宽度恢复为默认值。
说明:
C++不会截断数据,比如如果在宽度为2的字段中打印7位的值,那么该字段将被增大,C++的原则是:内容比形式更重要。
默认情况下,C++的对其方式为右对齐,并且默认的填充字符是空格。可以使用fill()成员函数来更改填充字符,例如:cout.fill(‘*’);
1 string birth("19890608"); 2 cout.width(20); 3 cout << birth << endl; 4 cout.fill('*'); 5 cout.width(20); 6 cout << birth << endl; 7 8 /** output */ 9 19890608 10 ************19890608
默认情况下,浮点数的显示精度指的是总位数;
在定点模式和科学模式下,指的是小数点后的位数;
C++默认的精度为6位,并且末尾的0不显示。可以使用cout.precision(int)设置精度的位数,一旦设置,直到第二次设置前永远有效。
对于有些输出,必须保留末尾的0,此时需要借助于ios_base类中的setf()函数:cout.setf(ios_base::showpoint);
为了简化格式设置工作,C++专门提供了一个工具(头文件<iomanip>),3个最常用的格式控制符分别为:setprecision()、setfill()、setw(),分别用来设置精度、填充字符、字段宽度。
1 for(int i = 1; i <= 10; i++) 2 { 3 cout << setw(5) << i << setw(10) << i * 10 << endl; 4 } 5 6 /** output */ 7 1 10 8 2 20 9 3 30 10 4 40 11 5 50 12 6 60 13 7 70 14 8 80 15 9 90 16 10 100
[1.2] cin 的使用
cin对象将标准输入表示为字节流,通常情况下,键盘生成的是字符流,cin根据接受值的变量的类型,将字符序列转换为所需的类型。
再次强调:C++解释输入的方式取决于变量的数据类型。istream类重载了>>,可以识别如下基本类型:
signed char&/unsigned char&/char&/short&/unsigned short&/int&/unsigned int&/long&/unsigned&/long long&/unsigned long long&/float&/double&/long double&。
除此之外,还支持:
signed char*/char*/unsigned char*。
[1.3] 流状态
cin和cout对象各自包含一个描述流状态的数据成员(从ios_base中继承的)。流状态被定义为iostate类型,实则是bitmask类型,由3个ios_base元素组成:eofbit/badbit/failbit。其中每个元素都是一位:
当cin到达文件末尾时,置位eofbit;
当cin未能读取到预期的字符时,置位badbit;
当I/O故障失败时,置位failbit。
当全部三个状态位设置为0时,说明一切顺利。程序可以根据当前的流状态决定下一步的动作。
[1.4] 字符的输入
首先应该确定是否希望跳过空白符,希望跳过空白则选择>>;如果希望检查输入的每个字符,则使用get(void)或者get(char&)。如果要将标准C程序改写为C++程序,可以使用cin.get()替换getchar(), 使用cin.put(ch)替换putchar(ch)。
字符串输入函数getline()、get()、ignore():
istream& get(char*, int, char); istream& get(char*, int); istream& getline(char*, int, char); istream& getline(char*, int); 其中: char*: 存放字符串的内存单元地址; int: 内存单元大小(比字符串长度大1,用于存放’ |