📄 《c++编程思想》-- 第6章 笔记.txt
字号:
作者:rick1126
email: rickzhang@sina.com
日期:8/10/2001 10:10:22 AM
第6章 输入输出流介绍
6.1 为什么需要输入输出流
【传统C库的文件操作】
. 文件的打开/关闭这类安全性操作需要开发者自己调用, 没有一种强制的自动化机制保证
【类型封装】
. 如果需要使用类的构造和析构自动化机制进行C库的文件访问的封装, 就需要涵盖所有功能
- 整个文件I/O函数都作为对象的成员载入, 无法减小程序使用的空间
- 使用类的运行时解释机制不及传统的硬编码函数调用那样可以在编译阶段进行预处理
- 编译时不进行错误检查, 有可能带来潜在的程序错误
- 具体的针对不同类型的处理因为使用数据类型一致的格式化字符串处理, 造成无法扩展
=> 紧扣现有的C库无法这种实现文件I/O的封装
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6.2 解决输入/输出流问题
.
【操作符重载的认识】
. 针对C++而言, 操作符不过是不同语法的函数调用; 必须有一个事先说明过的函数匹配操作和哪些特别的变量类型, 否则编译器无法接收该表达式.
. C++的设计目的: C++可以编译C编译的程序, 并且根据新的机制给出相应的警告和错误; 重新编译不会改变原先C程序的行为.
【插入符和提取符】
. C++中重载了"<<"和">>"
. 针对每一个数据类型都重载
【操纵算子】
. 作用就是类似printf的格式化字符串
. endl, ends 用于添加新行并且可选的清空流
. 使用和pringf一样的格式化字符串进行转换
. ws 用于跳过空格
【真正意义】
. 使用空格作为输入的分隔符
. 仅仅是为了完整性考虑
. 下列情况下, cin, cout 无用武之地
- 文件输入替代标准输入, 针对图形GUI也工作的很好 ==> 持续性数据
- 只是进行转换, 不进行输出. C库里面有针对的sprintf...
- 非标准输出, 字符界面的输出可以使用其他方式替代
【面向行输入】
. get 和 getline 区别在于遇到分隔符, getline会继续探索
. read 和 write 分别从istream和向ostream获得和提供输入输出
. EOF, good(), eof(), fail(), bad
测试错误, 或者使用 !cin 和 !cout 进行判断
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6.3 文件输入输出流
【ifstream和ofstream的特点】
. 文件的打开和关闭动作有类的跟在/析构自动完成
. ifstream的构造函数通过指定标志决定打开方式
. VC中的ifstream是一个basic_ifstream的模板类实例, 即专门处理字符流. basic_ifstream和basic_ofstream模板类处理不同类型的流
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6.4 输入输出缓冲流
【对象封装的真实含义】
. 封装基本实现, 展现调用接口
【输入输出的关键 -- IO缓冲类】
. 无论是什么输入输出流, 最终的数据都由一个缓冲类进行处理 -- streambuf
. 访问缓冲类 -- 每一个输入输出类都有一个rdbuf()成员函数得到streambuf指针
* 在VC之中, 缓冲类最终产生三个派生类 -- filebuf, strstreambuf, stdiobuf
. 缓冲类的get()方法: 允许向另一个streambuf直接写入, 所以其参数就是目标streambuf的引用
* VC中的缓冲(派生)类的get方法在MSDN里面没有显式介绍, 但是的确存在
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6.5 在输入输出流中查找
【输入输出流的定位概念】
. 有关字符的定位机制
. 处理方式:
- 流定位 使用 ifstream::tellg, ofstream::tellp 得到当前的文件位置
- 相对查找 使用 ifstream::seekg, ofstream::seekp 移动文件指针
〖ifstream的查找实现 -- ifstream::tellg, ifstream::seekg〗
使用一个 streampos 类执行文件指针的定位操作. 微软的 streampos 定义如下
typedef fpos<mbstate_t> streampos;
typedef o-type mbstate_t;
typedef T1 streamoff; // T1 是一个有符号整数, 描述了可以存储流定位操作的偏移量的任意对象.
template <class St>
class fpos {
public:
fpos(St state, fpos_t fposn);
fpos(streamoff off);
fpos_t get_fpos_t() const;
St state() const;
void state(St state);
operator streamoff() const;
streamoff operator-(const fpos<St>& rhs) const;
fpos<St>& operator+=(streamoff off);
fpos<St>& operator-=(streamoff off);
fpos<St> operator+(streamoff off) const;
fpos<St> operator-(streamoff off) const;
bool operator==(const fpos<St>& rhs) const;
bool operator!=(const fpos<St>& rhs) const;
};
可见其实质还是一个文件指针的原理.
〖ifstream/ofstream的文件读写操作 -- ifstream::tellg, ifstream::seekg〗
. 首先需要声明一个可读可写的ifstream, 虽然ifstream本身不能写入, 但是其为ofstream提供了ofstream的写入对象
. ofstream构造函数的参数指明对于ifstream的操作是追加, 覆盖还是复制操作, 同时利用ifstream的streambuf指针作为
. 使用 ofstream::seekp 放到起始处, 就变成使用输入的内容覆盖旧内容
. 使用 ifstream::seekg 放到起始处, 就变成使用输入的内容插入旧内容前
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6.6 strstreams
【应用范围】
. 直接和内存而不是文件或者标准输出一起工作, 即使用同样的方式读写内存
. 派生出类似ifstream/ofstream的istrstream/ostrstream用于读/写内存
【为用户分配的存储 -- 构造函数】
. istrstream的构造函数
- ( char* buf ) 表示取一个零终结字符串的长度
- ( char* buf, int size ) 表示以size
. 数据类型自动匹配
istrstream s("1.414 47 this a test");
int i;
float f;
s >> i >> f;
char buf2[100];
s >> buf2;
cout << "i = " << i << endl << "f = " << f << endl;
cout << "buf2 = " << buf2 << endl;
cout << "the rest content: " << s.rdbuf() << endl;
上述代码演示了如何将字符串中的不同数据类型匹配到相应的变量. 因为使用空格作为分隔符, 所以1.414的整数和小数部分被自动匹配到整型变量i和浮点变量f, 47被匹配给字符串数组指针buf2, 剩余的部分通过s.rdbuf()得到, 我们可以看到以下注意点:
1) 空格作为分隔符, 连续空格不会影响匹配, 即使下一个非空格数据中间存在多个空格, 也不会发生中间的某个空格被匹配到变量的情况
2) 和文件一样, 在内存字符串之中也存在文件指针的概念, 上述的匹配过程也是指针移动的过程, 由此验证了strstream和fstream一样的操作机制
. ostrstream读写
cout << "type an int, a float and a string:";
int i;
float f;
cin >> i >> f;
cin >> ws;
char buf[100];
cin.getline( buf, 100 );
// 此时的变量内容为: i = 整数 f = 小数 buf = 后一个零终结符之后的一行字符串
ostrstream os( buf, 100, ios::app );
os << endl;
os << "integer = " << i << endl;
os << "float = " << f << endl;
os << ends;
// 开始os的内容即buf的指定范围的内容, 其后又分别加入了 [换行符], ["integer = 整数变量值"], ["float = 浮点变量值"], 利用控制算子ends插入结束零终止符
cout << "ostrstream变量os的内容:" << endl << os.rdbuf();
cout << "缓冲区buf内容:" << endl << buf;
【自动存储分配】
. 使用 ostrstream 的构造函数得到的变量自己关心需要存储空间, 自动追加分配
. "冻结"和"解冻"
- ostrstream::str() 得到内部的存储地址并且冻结自动存储分配, 不能再进行自动化分配; 而且需要用户使用 delete 释放内存
- ostrstream::rdbuf()::freeze() 解冻被冻结的自动存储分配机制, 这样对象撤销的时候还是可以自动释放内存
ostrstream s;
s << "'The time has come', the walrus said.";
s << ends;
cout << s.str() << endl << endl;
s.seekp( -1, ios::cur );
s.rdbuf()->freeze(0);
s << " 'To speak of many things' " << ends;
cout << s.rdbuf() << endl;
- 解冻以后添加内容
ostrstream s;
s << "hi";
char* old = s.str();
s.rdbuf()->freeze(0);
for ( int i=0; i<100; i++ )
s << "howdy";
// 此时内部的存储地址已经发生移动了
cout << "old = " << (void*)old << endl;
cout << "new = " << (void*)s.str() << endl;
delete s.str();
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6.7 输出流格式化
【内部格式化数据】
. 格式化范围( 对比 C 语言的 printf 函数 ): 浮点精度, 输出宽度和填充字符
. 相关流类的方法
fmtflags ios::fags( fmtflags newfalgs ); //取得或者设置格式串
【格式化方法及其标志】
. 相关流类方法
ftmflags ios::setf( fmtflags ored_flags ); //修改指定格式化位串但是维持原先位串
ftmflags ios::setf( fmtflags bits, fmtflags field );//修改指定格式化位串而且先清空第二个参数的指定位串
ftmflags ios::unsetf( ftmflags clear_flag ); //清空指定格式化位串
. 开关方式标志
ios::skipws 跳过空白字符 ios::showbase 打印表明进制
ios::showpoint 显示整数浮点数的小数点 ios::upercase 16进制A-F显示大写
ios::showpos 显示(+)表示正值 ios::unibuf 使用"设备缓冲区"
ios::stdio 将流同I/O系统同步
. 分组方式标志
进制 ios::basefield = ios::dec, ios::hex, ios::oct
科学记数 ios:floatfield = ios::scientific, ios::fixed, automatic
字符对齐 ios::adjustfield = ios::left, ios::right, ios::internal
【域宽, 填充字符和精度函数】
. 宽度存取
int ios::width();
int ios::width(int n);
. 填充字符
int ios::fill();
int ios::fill(int n);
. 浮点精度
int ios::precision();
int ios::precision(int n);
〖个人理解〗
关键是抓住格式化字符串的3个要点: 精度, 宽度, 填充字符
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6.8 格式化操纵算子
【相关算子】
. 开关操作算子
showbase/noshowbase showpos/noshowpos uppercase/nouppercase
showpoint/noshowpoint skipws/noskipws left/right
internal scientific/fixed
. 带参数的操纵算子
setiosflags( fmtflags n )/resetiosflags( fmtflags n ) 利用标志位的逻辑组合设置显示格式
setbase( base n ) 设置数据进制
setfill( char n )/setprecision( int n )/setw( int n ) 设置填充字符, 精度, 宽度
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6.9 建立操纵算子
【操纵算子的实质和应用算子机制】
. 操纵算子实质
一个将ostream引用作为参数的函数而已. 例如:
ostream& endl( ostream& );
. 应用算子
建立操纵算子的是应用算子.
【效用算子】
. 效用算子的提出主要是为了解决构造带参数的操纵算子
. 效用算子是一个类, 该类的构造函数和被重载的操作符"<<"一起工作
. 基本实现就是利用构造函数和析构函数处理输入的参数, 利用外部的友元操作符重载输出参数
//截取指定长度子串输出
class Cfixw
{
char* s;
public:
Cfixw( const char* S, int width );
virtual ~Cfixw();
friend ostream& operator<<( ostream&, Cfixw& );
};
Cfixw::Cfixw( const char* S, int width )
{
s = ( char* )malloc( width + 1 );
assert( s );
strncpy( s, S, width );
s[width] = 0;
}
Cfixw::~Cfixw()
{
free(s);
}
ostream& operator << ( ostream& os, Cfixw& fw )
{
return os << fw.s;
}
//将输入的整数使用2进制输出
class bin{
unsigned long n;
public:
bin( unsigned long N );
friend ostream& operator << ( ostream&, bin& );
};
bin::bin( unsigned long N )
{
n = N;
}
ostream& operator << ( ostream& os, bin& b )
{
unsigned long bit = ~( ULONG_MAX >> 1 );
while(bit){
os<<(b.n & bit?'1':'0');
bit >>= 1;
}
return os;
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -