1. streambuf类为缓冲区提供了内存, 并提供了用于填充缓冲区, 访问缓冲区内容, 刷新缓冲区和管理缓冲区内存的类方法;
2. ios_base类表示流的一般特征, 如是否可读取, 是二进制流还是文本流等.
3. ios类基于ios_base, 其中包括了一个指向streambuf对象的指针成员;
4. ostream类时从ios类派生而来的, 提供了输出方法;
5. istream类也是从ios类派生而来的, 提供了输入方法;
6. iostream类时基于istream和ostream类的, 因此继承了输入和输出方法.
要使用这些工具, 必须使用适当的类对象. 例如: 使用ostream对象(如cout)来处理输出.
C++的iostream类库管理了很多细节. 例如, 在程序中包含iostream文件将自动创建8个流对象(4个用于窄字符流, 4个用于宽字符流).
cin 对象对应于标准输入流, 在默认情况下, 这个流被关联到标准输入设备(通常为键盘). wcin 对象与此类似, 但处理的是wchar_t类型.
cout 对象与标准输出流相对应. wcout对象与此类似, 但处理的是wchar_t类型
cerr 对象与标准错误流相对应, 可用于显示错误信息. 这个流没有被缓冲, 也就是信息将直接被发送给屏幕, 而不会等到缓冲区满了或者新的换行符. wcerr对象用于处理wchar_t类型.
clog 对象也对应这标准错误流, 这个流被缓冲. wclog用于处理wchar_t类型
对象代表流: 当iostream文件尾程序声明了一个cout对象时, 该对象将包含存储了与输出有关信息的数据成员, 如显示数据时使用的字段宽度, 小数位数, 显示整数时采用的计数方法以及描述用来处理输出流的缓冲区的streambuf对象的地址. 下面的语句通过指向的streambuf对象将字符串"Bjarna free"中的字符放到cout管理的缓冲区中.
cout << "Bjarne free";
cout重载了operator<<()函数, 并返回一个指向ostream对象的引用, 因此可以将输出链接起来:
cout << "hello " << "word." << endl;
输出和指针
ostream类还为下面的指针类型定义了插入运算符函数:
const signed char*;
const unsigned char*;
const char*;
void *;
C++用指向字符串存储位置的指针来表示字符串, 指针的形式可以使char数组名, 显示的char指针, 或用引号括起来的字符串. 看下面这个例子:
char sen[20] = "first sentence";
char * psen = "second sentence";
cout << "Hello";
cout << sen;
cout << psen;
方法使用字符串中的终止空字符来确定何时停止显示字符.
对于其他类型的指针, C++将其对应于void *, 并打印地址的数值表示. 如果要获取字符串的地址, 则必须将其强制转换为其他类型, 举个例子:
int eggs = 12;
char * amount = "dozen";
// 打印eggs变量的地址
cout << &eggs;
// 打印dozen
cout << amount;
// 打印"dozen"字符串的地址
cout << (void *)amount;
其他ostream方法:
除了各种operator<<()函数外, ostream类还提供了put()方法和write()方法, 前者用于显示字符, 后者用于显示字符串. 与<<运算符一样, 该函数也返回一个指向调用对象的引用, 因此也可以用于拼接输出:
cout.put('I').put('I');
我们还可以将数值型参数(如int)用于put(), 让函数原型自动将参数转换为正确的char值, 例如:
// A
cout.put(65);
// B
cout.put(66.3);
write()方法显示整个字符串, 其模板原型如下:
basic_ostream & write(const char_type * s, streamsize n);
write的第一个参数提供了要显示的字符串的地址, 第二个参数指出要显示多少个字符.
来看一个完整的例子:
// wirte.cpp
#include
#include
int main()
{
using std::cout;
using std::endl;
const char * state1 = "Florida";
const char * state2 = "Kansas";
const char * state3 = "Euphoria";
int len = std::strlen(state2);
cout << "Increasing loop index:\n";
int i;
for(i = 1; i <= len; i++)
{
cout.write(state2, i);
cout << endl;
}
cout << "Decreasing loop index:\n";
for(i = len; i > 0; i--)
cout.write(state2, i) << endl;
cout << "Exceeding string length:\n";
// 超出state2的边界了 , 但是write不会停止打印, 会继续打印直到达到指定的字符数
cout.write(state2, len + 5) << endl;
return 0;
}
程序运行结果为:
需要注意的是, write()方法并不会在遇到空字符时自动停止打印字符, 而是打印指定数目的字符, 即使超出了字符串的边界.
来看下面这段代码:
cout << "Enter a number: ";
float num;
cin >> num;
程序期待输入这个情况, 将导致它立即显示cout的信息, 即立即刷新"Enter a number: "这个消息, 即使输出字符串中没有换行符.
我们还可以使用两个控制符中的任意一个来进行强制刷新缓存区, 控制符flush和endl来对缓冲区进行刷新:
cout << "Hello, good-looking" << flush;
cout << "wait just a moment" << endl;
实际flush和endl也是函数, 例如, 可以直接调用flush()来刷新cout缓冲区:
flush(cout);
用cout进行格式化
我们先来看默认的cout输出的格式:
// defaults.cpp
#include
int main()
{
using std::cout;
cout << "12345678901234567890\n";
char ch = 'K';
int t = 273;
cout << ch << ";\n";
cout << t << ";\n";
cout << -t << ";\n";
double f2 = 1.200;
cout << f2 << ";\n";
f2 += 1.0 / 9.0;
cout << f2 << ";\n";
cout << (f2 * 1.0e4) << ";\n";
double f3 = 2.3e-4;
cout << f3 << ";\n";
cout << f3 / 10 << ";\n";
return 0;
}
程序运行结果为:
要控制整数以十进制, 十六进制还是八进制显示, 可以使用dec, hex和oct控制符, 例如:
hex(cout);
这条语句将cout对象的输出格式设置为十六进制. 此后程序将以十六进制形式打印整数值, 直到将格式状态设置为其他选项为止.
注意, 控制符不是成员函数, 因此不比通过对象来调用, 虽然控制符实际上是函数, 但他们通常的时候方式为:
cout << hex;
来看个完整的例子:
// manip.cpp
#include
int main()
{
using namespace std;
cout << "Enter an integer: ";
int n;
cin >> n;
cout << "n n*n\n";
cout << n << " " << n * n << " (decimal)\n";
// 设置为十六进制
cout << hex;
cout << n << " ";
cout << n * n << " (hexadecimal)\n";
// 设置为八进制
cout << oct;
cout << n << " " << n * n << " (octal)\n";
dec(cout);
cout << n << " " << n * n << " (decimal)\n";
return 0;
}
程序运行结果为:
我们可以使用width成员函数将长度不同的数字放到宽度相同的字段中, 该方法的原型如下:
int width();
int width(int i);
第一种格式返回字段宽度的当前设置, 第二种格式将字段的宽度设置为i个空格, 并返回以前的字段宽度值. 这使得能够保存以前的值, 以便以后恢复宽度值时使用.
width()方法只影响将显示的下一个项目, 然后字段宽度将恢复默认值. 看下面的代码:
cout << "#";
cout.width(12);
cout << 12 << "#" << 24 << "#\n";
运行结果为:
12被放到宽度为12个字符的字段的最右边也就是右对齐, 然后字段宽度将恢复默认值, 并将两个#符号和24放在宽度与它们的长度相等的字段中.
C++不会截短数据, 因此如果试图在宽度为2的字段中打印一个7位值, C++将增宽字段, 以容纳新数据.
来看一个完整的例子:
// width.cpp
#include
int main()
{
using std::cout;
// 存储之前的宽度设置
int w = cout.width(30);
cout << "default field width = " << w << "\n";
cout.width(5);
cout << "N" << ";";
cout.width(8);
cout << "N * N" << ";\n";
for(long i = 1; i <= 100; i*= 10)
{
cout.width(5);
cout << i << ";";
cout.width(8);
cout << i * i << ";\n";
}
return 0;
}
程序运行结果为:
在默认情况下, cout用空格填充字段中未被使用的部分, 可以使用fill()成员函数来改变填充字符. 例如:
cout.fill('*');
这行代码指出cout将用"*"来填充空白区域
来看个完整的例子:
// fill.cpp
#include
int main()
{
using std::cout;
cout.fill('*');
const char * staff[2] = {"first sentence", "Second sentence"};
long bonus[2] = {900, 1350};
for(int i = 0; i < 2; i++)
{
cout << staff[i] << ": $";
cout.width(7);
cout << bonus[i] << "\n";
}
return 0;
}
程序运行结果为:
注意: 与字段宽度不同的是, 新的填充字符将一直有效, 直到更改它为止.
读点书精度的含义取决于输出模式, 在默认情况下, 它指的是显示的总位数, 在定点模式和科学模式下, 精度指的是小数点后面的位数.
C++默认精度为6位(但末尾的0将不显示), precision()成员函数使得能够选择其他值. 例如:
cont.precision(2);
这行代码将精度设置为2.
注意: 和width()不同, 新的精度设置将一直有效, 直到被重新设置.
看个完整的例子:
// precise.cpp
#include
int main()
{
using std::cout;
float price1 = 20.40;
float price2 = 1.9 + 8.0 / 9.0;
cout << "Furry Friends is $" << price1 << "!\n";
cout << "Fiery Friends is $" << price2 << "!\n";
cout.precision(2);
cout << "Furry Friends is $" << price1 << "!\n";
cout << "Fiery Friends is $" << price2 << "!\n";
cout.precision(1);
cout << "Furry Friends is $" << price1 << "!\n";
cout << "Fiery Friends is $" << price2 << "!\n";
return 0;
}
程序运行结果为:
需要注意两点: 第三行没有打印小数点及以后的内容, 第四行显示的总位数为2位.
打印末尾的0和小数点
有时我们想保留末尾的0, ios_base类提供了一个setf()函数, 能够控制多种格式化特性, 还定义类多个常量, 可用作该函数的参数, 例如:
cout.setf(ios_base::showpoint);
这行代码可以显示末尾的小数点
我们来看个例子:
// showpt.cpp
#include
int main()
{
using std::cout;
using std::ios_base;
float price1 = 20.40;
float price2 = 1.9 + 8.0 / 9.0;
cout.setf(ios_base::showpoint);
cout << "Furry Friends is $" << price1 << "!\n";
cout << "Fiery Friends is $" << price2 << "!\n";
cout.precision(2);
cout << "Furry Friends is $" << price1 << "!\n";
cout << "Fiery Friends is $" << price2 << "!\n";
cout.precision(1);
cout << "Furry Friends is $" << price1 << "!\n";
cout << "Fiery Friends is $" << price2 << "!\n";
return 0;
}
程序运行结果为:
setf()方法控制了小数点被显示时其他几个格式选项,
ios_base::boolalpha 输入和输出bool值, 可以为ture或false
ios_base::showbase 对于输出, 使用c++基数前缀(0, 0x)
ios_base::showpoint 显示末尾的小数点
ios_base::uppercase 对于16进制输出, 使用大写字母, E表示法
ios_base::showpos 在正数前面加上+
来看个例子:
// setf.cpp
#include
int main()
{
using std::cout;
using std::endl;
using std::ios_base;
int temperature = 63;
cout << "Today's water temperature: ";
// 在正数前加+
cout.setf(ios_base::showpos);
cout << temperature << endl;
cout << "For our programming friends, that's\n";
// 以十六进制展示
cout << std::hex << temperature << endl;
// 对于16进制输出, 使用大写字母, E表示法
cout.setf(ios_base::uppercase);
// 对于输出, 使用c++基数前缀(0, 0x)
cout.setf(ios_base::showbase);
cout << "or\n";
cout << temperature << endl;
cout << "How " << true << "! oops -- How ";
cout.setf(ios_base::boolalpha);
cout << true << "!\n";
return 0;
}
程序运行结果为:
注意: 仅当基数为10时才使用加号, 而十六进制和八进制都视为无符号的.
第二个setf()原型接收两个参数, 并返回以前的设置:
fmtflags setf(fmtflags, fmtflags);
第一个参数指出要设置哪位, 第二个参数指出要清除哪些位
要修改基数, 可以将常量ios_base::basefield用作第二参数, 将ios_base::hex用作第一参数:
cout.setf(ios_base::hex, ios_base::basefield);
这行代码的作用和使用十六进制控制符的作用相同.
下面看一下第一个参数和第二个参数各有哪几种情况:
第二个参数 |
第一个参数 |
iosbase::basefield |
ios_base::dec 使用基数10 ios_base::oct 使用基数8 ios_base::hex 使用基数16 |
ios_base::floatfield |
ios_base::fixed 使用定点计数法 ios_base::scientific 使用科学计数法 |
ios_base::adjustfield |
ios_base::left 使用左对齐 ios_base::right 使用右对齐 ios_base::interna 符号或基数前缀左对齐, 值右对齐 |
ios_base类定义了可按这种方式处理的3组格式标记. 每组标记都由一个可用做第二参数的常量和两三个可用作第一参数的常量组成. 第二参数清除一批相关的位, 然后第一参数将其中一位设置为1. 例如要选择左对齐, 可将ios_base::adjustfield用作第二参数, 将ios_base::left用作第一参数.
定点表示法意味着使用格式123.4来表示浮点值, 而不管数字长度如何, 科学表示法意味着使用格式1.23e04, 而不考虑数字的长度.
C++标准中, 定点表示法和科学表示法都有如下两个特征:
1.精度指的是小数位数, 而不是总位数
2.显示末尾的0
setf()函数时ios_base类的一个成员函数. 由于这个类时ostream类的基类, 因此可以使用cout对象来调用该函数, 例如: 要左对齐:
ios_base::fmtflags old = cout.setf(ios::left, ios::adjustfield);
要恢复以前的设置, 可以这样做:
cout.setf(old, ios::adjustfield);
来看一个例子:
// setf2.cpp
#include
#include
int main()
{
using namespace std;
// 左对齐
cout.setf(ios_base::left, ios_base::adjustfield);
// 在正数前面加上+
cout.setf(ios_base::showpos);
// 显示末尾小数点
cout.setf(ios_base::showpoint);
// 规定精确到几位
cout.precision(3);
// 使用科学计数法, 并保存之前的设置
ios_base::fmtflags old = cout.setf(ios_base::scientific, ios_base::floatfield);
cout << "Left Justification:\n";
long n;
for(n = 1; n <= 41; n += 10)
{
cout.width(4);
cout << n << "|";
cout.width(2);
cout << sqrt(double(n)) << "|\n";
}
// 右对齐
cout.setf(ios_base::right, ios_base::adjustfield);
// 使用定点小数法
cout.setf(ios_base::fixed, ios_base::floatfield);
cout << "Right Justification:\n";
for(n = 1; n <= 41; n += 10)
{
cout.width(4);
cout << n << "|";
cout.width(12);
cout << sqrt(double(n)) << "|\n";
}
return 0;
}
程序运行结果为:
调用setf()效果可以通过unsetf()消除, 原型如下:
void unsetf(fmtflags mask);
其中, mask是位模式, mask中所有的位都设置为1, 将使得对应的位被复位. 也就是说, setf()将位设置为1, unsetf()将位恢复为0:
cout.setf(ios_base::showpoint);
cout.unsetf(ios_base::boolshowpoint);
cout.setf(ios_base::boolalpha);
cout.unsetf(ios_base::boolalapha);
注意: 没有专门知识浮点数默认显示模式的标记, 系统的工作原理如下: 仅当只有定点位被设置时使用定点表示法; 仅当只有科学为被设置时使用科学表示法; 对于其他组合, 如果没有位被设置或两位都被设置时, 将使用模式模式. 因此, 我们可以通过下面这种方式来启用默认设置:
cout.setf(0, ios_base::floatfield);
第二个参数关闭这两位, 而第一个参数不设置任何位.
还可以通过下面这种方式:
cout.unsetf(ios_base::floatfield);
使用iostream工具来设置一些格式(如字段宽度)不太方便, 为简化工作, C++提供了iomanip头文件. 其中最常用的控制符分别是 setprecision(), setfill()和setw(), 他们分别用来设置精度, 填充字符和字段宽度. 由于他们是控制符, 因此可以用cout来链接起来. 来看一下使用的例子:
// iomanip.cpp
#include
#include
#include
int main()
{
using namespace std;
// 使用标准的控制符
cout << fixed << right;
cout << setw(6) << "N" << setw(14) << "square root" << setw(15) << "fourth root\n";
double root;
for(int n = 10; n <= 100; n += 10)
{
root = sqrt(double(n));
cout << setw(6) << setfill('.') << n << setfill(' ') << setw(12) << setprecision(3) << root << setw(14) << setprecision(4) << sqrt(root) << endl;
}
return 0;
}
程序运行结果为: