文章目录
流的特点:一维、单方向
Extractors: ****>>
- read a value from the stream
- overload the >> operator
Inserters: <<
- Insert a value into a stream
- overload the << operator
manipulators: (操纵器)
- change the stream state;
others
- some functions
Kinds of streams
1. text streams
(输入) 解析 / (输出)格式化
- deal in ASCII text
2. Binary streams(不以人的阅读为目的的文件) - 不经过解析和格式化的过程
Predefined streams
- cin
standard input - cout
standard output - cerr
unbuffered error output - clog
buffered error outpit
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jyn3bC4C-1657814432263)(2022-07-07-20-53-09.png)]
Defining a stream extractor
istream operatror>>(istream & is, T & obj) {
...
return is;
}
流、缓冲区和iostream文件
C++程序把输入和输出看做字节流。输入时,程序从输入流中抽取字节,输出时,程序将字节插入到输出流中。
流充当了程序和流源(文件或键盘等输入设备)或流目标(文件或显示器)之间的桥梁。C++只是检查字节流,使得程序处理输入输出与来源和方向相互独立,不需要关心不同的设备去向具体的底层实现的差别。
缓冲区作为设备和程序之间的媒介,可以起到平衡输入输出的处理速度差异的作用,提高效率。
流和缓冲区的管理工作交由一些专门设计的类来完成。
- ios_base类表示流的一般特征,如是否可读取,是二进制流还是文本流
- ios类基于ios_base,其中包括了一个指向streambuf类对象的指针成员
- streambuf类为缓冲区提供了内存,并提供了用于填充缓冲区、访问缓冲区内容、刷新缓冲区和管理缓冲区内存的类方法。
- istream和ostream类都是从ios类派生而来的,提供了输入和输出方法
- iostream是基于istream和ostream类,多重继承了这两个类的输入和输出方法。
创建这样的对象将打开一个流,自动创建缓冲区,并将其与流关联起来,同时使得能够使用类成员函数。
C++的iostream类库管理了很多细节,在程序中包含iostream文件将自动创建8个流对象(4个用于窄字符流,4个用于宽字符流)
cin,cout,cerr,clog \wcin,wcout,wcerr,wclog
使用cout进行输出
ostream类的最重要的任务之一数据编码格式的转换:即将数值类型(如int、float等计算机内部的数据表示格式)转换成文本形式表示的字符流(如ASCII码,显示器能直接显示的、给人看的字符数据格式)
ostream的类方法
- ostream重载了<< 运算符 (插入运算符) 使之能够识别C++中所有的基本类型。
- ostream类还为下面的指针类型定义了插入运算符
- const signed char*
- const unsigned char*
- const char*
- void* //其他类型的指针C++都将其对应于void*,并打印地址的数值表示
C++使用指向字符串存储位置的指针来表示字符串。指针的形式可以是char数组名、显式的char指针、或用括号引起的字符串。
因此,下面的所有cout语句都显示字符串
char name[20] = "hello world";
char *pn = "dejifeede";
cout << "hello"; //括号引起的字符串
cout <<name; //char数组名
cout << pn; //显式的char指针
方法使用字符串中的终止空字符(“\0”)来确定何时停止显示字符
重载 << 运算符
ostream类中的<<运算符重载的写法:
ostream & operator<<(T & obj)
{
...
return *this;
}
但是在ostream类中进行<<运算符重载是危险的,所以一般要在自定义的类中进行<<运算符的重载。使用友元来解决参数顺序的问题。
Creating a stream inserter
class T
{
friend ostream & operator<<(ostream & os, const T & obj);
}
ostream & operator<<(ostream & os, const T & obj){
...
return os;
}
put()方法和write方法
除了各种operator<<()方法外,ostream类还提供了put()方法和write方法,前者用于显示字符,后者用于显示字符串。
put方法:显示字符
//返回对象的引用,支持拼接输出
ostream & put(char);
cout.put('w');
cout.put('I'),put('t');
cout.put(65); //displaty the A character
cout.put(66.3);// 66.3 → 66 —→ B
wiite()方法:显示字符串的前n个字符
需要注意的是,write()方法并不会在遇到空字符时自动停止打印字符,而只是打印指定数目的字符
//函数模板原型
//使用cout调用write()时,将调用char具体化,因此返回类型为ostream&, 也支持拼接输出
basic_ostream<charT,traits>& write(const char_Type* s, streamsize n);
//使用演示
#include<iostream>
int main()
{
using std::cout;
using std::endl;
const char* str1 = "0123456789";
const char* str2 = "**********";
const char* str3 = "hello world";
const char* str4 = "niubi";
for (int i = 1; i <= std::strlen(str1); i++) //递增循环输出
{
cout.write(str1, i);
cout << endl;
}
cout << "-----------------"<<endl;
for (int i = std::strlen(str2); i >= 1; i--)//递减循环输出
{
cout.write(str2, i);
cout << endl;
}
cout << "-----------------" << endl;
cout.write(str3, std::strlen(str3) - 5).write(str4, std::strlen(str4))<<endl; //输出可以进行拼接
//将wirte()方法用于数值数据
long val = 560031841; //long数据占用4个字节传输,
cout.write((char*)&val, sizeof(long));
//将数字的地址强转为char*,传递给write()函数
//每个字节被翻译成ASCII码进行解释,因此在屏幕上,数值560031841将被显示为4个字符的组合
}
刷新输出缓冲区
由于ostream类对cout对象处理的输出进行缓冲,所以输出不会立即发送到目标地址,而是被存储在缓冲区中,知道缓冲区填满。然后,程序将刷新缓冲区,把内容发送出去,并清空缓冲区,以存储新的数据。通常,缓冲区为字节及其整数倍。
- 当标准输出连接的是硬盘上的文件时,缓冲可以节省大量的时间。将大量字符一次性写入硬盘的效率会更高。
- 对于屏幕来说,程序不必等到缓冲区被填满才进行刷新,例如,将换行符发送到缓冲区后,将刷新缓冲区。
另外,多数C+会在输入即将发生时刷新缓冲区。
cout << "dewfewf";
float num;
cin >> num;
因为下面有cin,程序期待输入这一事实,所以即使输出字符串中没有换行符,也会刷新缓冲区,立刻输出。
有两个控制符可以对缓冲区进行强行刷新
- 控制符flush刷新缓冲区
- 控制符endl刷新缓冲区,并插入一个换行符。
cout << "efgewfgw" << flush;
cout << "ferwerewf"<< endl;
用cout进行格式化
1. 修改计数系统 cout << hex;
使用控制符控制显示证整数时使用的计数系统,以十进制、十六进制、还是八进制可以显示可以使用dec,hex,oct控制符。
使用方式如下:
//控制符不是成员函数,不必通过对象来调用
hex(cout);
dec(cout);
oct(cout);
//或 ostream类重载了<<运算符,也可以这么使用控制符
cout << hex;
cout << dec;
cout << oct;
2.调整字段宽度 cout.width(5);
- 可以使用width成员函数将长度不同的数字放到宽度相同的字段中。
- width()方法只影响下一个项目,然后字段宽度将恢复为默认值
- C++总会增长字段,以容纳数据,因此这种值适用于所有的数据。
- 右对齐是默认的
使用示例:
int width(); //用来返回当前字段宽度的设置
int width(int i); //用来设置字段段杜,并返回之前的字段宽度
#include <iostream>
int main()
{
using std::cout;
int old = cout.width(); //默认宽度为0
cout << "default field width = " << old << ":\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";
}
// std::cin.get();
return 0;
}
3. 填充字符 cout.fill(‘*’);
- cout.fill(‘*’);将使用 * 来填充字段中未使用的部分
- 默认情况下使用空格填充
- 注意:与字段宽度不同的是,新的填充字符将一直有效,直到更改它为止。
4. 设置浮点数的显式精度 cout.precision(2);
浮点数精度的含义取决于输出模式
- 在默认模式下,指的是显式的总位数
- 在定点模式和科学模式下,指的是小数点后面的位数
- cout.precision(2);设置精度为2
- 与fill()类似,新的精度设置将一直有效,直到被重新设置。
- C++的默认精度为6位,但末尾的0将不显示
setf()方法
ios_base类提供了一个setf()函数,能够控制多种格式化特性。这个类还提供了多个常量,可用作该函数的参数。
setf()成员函数有两个原型:
//fmtflags是bitmask类型的typedef名。bitmask是一种用来存储各个位值的类型,每一位都是可以单独访问的,都有自己的含义。iostream软件包使用bitmask来存储状态信息。
fmtflags setf(fmtflags);
fmtflags setf(fmtflags, fmtflags);
要点:
- ios_base类位于std名称空间中
- 这些格式常量都是在ios_base类中定义的,使用时需要加上作用域解析符
- 修改将一直有效,直到被覆盖位置
- setf()是ios_base类的一个成员函数,由于这个类是ostream的基类,因此可以用cout对象来调用该函数
setf()一个参数设置格式,并返回以前的设置
一个参数setf()使用的格式常量:
- ios_base::boolalpha 输入和输出bool值
- ios_base::showbase 输出使用基数前缀0,,0x
- ios_base::showpoint 显示末尾的小数点
- ios_base::uppercase 对于16进制输出,使用大写字母,E表示法
- ios_base::showpos 在正数前面加+号显示
使用示例:
#include <iostream>
int main()
{
using std::cout;
using std::endl;
using std::ios_base;//预编译指令
int temperature = 63;
cout.setf(ios_base::showpos); // 加+号
cout << temperature << endl;
cout << std::hex << temperature << endl; // 使用hex控制符
cout.setf(ios_base::uppercase); // 16进制大写
cout.setf(ios_base::showbase); // 使用基数前缀
cout << "or\n";
cout << temperature << endl;
cout << true ; //输出1
cout.setf(ios_base::boolalpha); //输出bool值true
cout << true << "!\n";
return 0;
}
setf()原型接受两个参数,并返回以前的设置
第一位指出要设置哪些位,第二位指出要清除哪些位。
第2个参数 | 第一个参数 | 含义 |
---|---|---|
ios_base::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::internal | 符号或基数前缀左对齐,值右对齐 |
定点法和科学计数法都有下面两个特征:
- 精度是值小数位数,而不是总位数
- 显示末尾的0
使用示例:
//调整左对齐
ios_base::fmtflags old = cout.setf(ios_base::left,ios_base::adjustfield);
//恢复以前的设置
cout.setf(old,ios::base::adjustfield);
// setf2.cpp -- using setf() with 2 arguments to control formatting
#include <iostream>
#include <cmath>
int main()
{
using namespace std;
//左对齐、加+号、显示小数点、精度为3位
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); //在循环输出中调整字段宽度,因为width()用完一次之后就恢复默认值了
cout << n << "|";
cout.width(12);
cout << sqrt(double(n)) << "|\n";
}
// 使用internal对齐方式
cout.setf(ios_base::internal, ios_base::adjustfield);
// 恢复默认的浮点数计数法(总共显示三位)
cout.setf(old, ios_base::floatfield);
cout << "Internal Justification:\n";
for (n = 1; n <= 41; n += 10)
{
cout.width(4);
cout << n << "|";
cout.width(12);
cout << sqrt(double(n)) << "|\n";
}
// 右对齐、定点计数法(显示小数点后3位)
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";
}
// std::cin.get();
return 0;
}
7. 标准控制符
C++提供了多个控制符,能够调用setf()函数,并自动提供正确的参数,使用起来更简单。
使用方式如下:
cout << left << fiexd; //使用左对齐、定点计数法
一些标准控制符如下:
boolalpha / noboolalpha 使用bool值
showbase / noshowbase 使用基数前缀0,,0X
showpoint/ noshowpoint 显示末尾的小数点
showpos / noshowpos 整数加+号
uppercase/ nouppercase 16进制大写E表示法
left / right / internal 左对齐、右对齐、内部表示法
dec / oct / hex 10进制、8进制、16进制
fixed / scientific 定点计数法、科学计数法
8. 头文件 iomanip
iomanip提供了几个常用的控制符,如setprecision(int i),setfill(char c),setw(int i),它们分别用来设置精度、填充字符和字段宽度。相比较cout.precision()、cout.fill()、cout.width(),它们在某些情况下使用起来更方便,可以直接插入到输出序列中使用,如cout<<setw(10) << setfill() << n; 它们都是控制符,可以直接用cout语句链接起来。
#include <iostream>
#include <)iomanip>
#include <cmath>
int main()
{
using namespace std;
// 使用标准控制符 右对齐、定点计数法(小数点后位数、显示末尾的0)
cout << fixed << right;
// 使用 iomanip 控制符
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 //第1列
<< setfill(' ')
<< setw(10) << setprecision(3) << root //第2列
<< setw(14) << setprecision(5) << sqrt(root) //第3列
<< endl;
}
return 0;
}
使用cin进行输入
键盘 → 字符流 → 缓冲区 —→(读入字节) cin(>>进行数据类型的转换,get、getline不转换) → 内存单元
>> 抽取运算符 进行单词的输入(涉及到基本数据类型的转换)
istream类重载了抽取运算符 >>,基本数据类型都能识别
eg: istream & operator>>(int &);
参数都使用了引用,因为这样可以直接修改该参数变量的值,而不是修改其拷贝。
返回值使用引用是可以连续输入。
通常可以这样使用cin
cin >> value_holder
istream类还为下列字符指针类型重载了>>抽取运算符:
- char*
- signed char*
- unsigned char*
对于这种类型的参数,抽取运算符将读取输出流中的下一个单词(连续的字符流,中间没有空格),将它放置到指定的地址上,并加上一个空值字符(\0),使之成为一个字符串。
每个抽取运算符都返回调用对象的引用,能够实现拼接输入。
使用 >> 进行连续输入 不同类型的数据 输入到时候使用空格分隔或换行符分隔均可。
using namespace std;
char name[20];
float fee;
int group;
//使用 >> 进行连续输入 不同类型的数据 输入到时候使用空格分隔或换行符分隔均可
cin >> name >> fee >> group;
cout << left<< fixed;
cout.precision(3);
cout << setw(10) << name << setw(10) << fee << setw(10) << group << endl;
return 0;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TvNtFeNR-1657814432266)(2022-07-12-00-54-30.png)]
cin>>如何检查输入
当cin>>从缓冲区中读取数据时,若缓冲区第一个字符是空格、tab或换行符这些分隔符时,cin>>会将其忽略并删除,继续读取下一个字符.若缓冲区为空,则继续等待。但如果读取成功,字符后面的分隔符是会残留在缓冲区中的,cin>>不做处理。也就是说,它读取从非空白字符开始,到第一个与目标类型不匹配的字符结束这两者之间的全部内容。
int elevation;
cin >> elevation;
-
键入 -123z时,运算符将读取 - 1 2 3 等4个int型的有效字符,z将留在缓冲区中,下一个cin语句将从这里开始读取。
-
但是假设输入的是acaer,而不是-123z。在这种情况下,抽取运算符不会修改elevation的值,返回0.(如果istream对象的错误状态被设置,if或while语句将判定该对象为false,返回值false让程序能够检查输入是否满足要求)
条件状态
使用cin读取键盘输入时,难免发生错误,一旦出错,cin将设置条件状态(condition state).条件状态有:
1 | goodbit(0x0):无错误
2 | eofbit (0x1):已到达文件尾
3 | failbit(0x2):非致命的输入/输出错误,可挽回
4 | badbit (0x4):致命的输入/输出错误,无法挽回
与这些条件对应的就是设置、读取、判断条件状态的流对象的成员函数。它们有:
1 | s.eof():若流s的eofbit置位,则返回true
2 | s.fail():若流s的failbit置位,则返回true
3 | s.bad():若流s的badbit置位,则返回true
4 | s.good():若流s的goodbit置位,则返回true
5 | s.clear(flags):清空状态,然后把状态设置为flags,返回void
6 | s.setstate(flags):不清空状态,设置给定的状态为flags,返回void
7 | s.rdstate():返回流s的当前状态,返回值类型为ios_base::iostate
8 | s.eof():若流s的eofbit置位,则返回true
其他istream类方法
- 方法get(char&)和get(void)提供不跳过空白字符的单字符输入功能
- 函数get(char*,int ,char)和getline(char*, int ,char)在默认情况下读取整行而不是一个单词.
他们被称为非格式化输入函数,因为他们只读取字符输入,而不会跳过空白,也不进行数据转换。
单字符输入
get()方法读取下一个输入的字符,即使该字符是空格、制表符、或换行符。get(char&)版本将输入字符赋给其参数,get(void)版本将输入字符转换成整型(通常是int,ASCII码)并将其返回。
成员函数get(char &)
int ct = 0;
char ch;
cin.get(ch);
while (ch != '\n'){ //直到程序将回车键作为换行符处理,终止循环。
cout << ch;
ct++;
cin.get(ch);
}
cout << ct << endl;
输入: hello world !
输出: hello world !15
这里的重点是,通过get(char&),代码读取、显示并考虑空格和可打印字符。
假设程序使用 >> 抽取运算符,将自动跳过空格、制表符、或换行符。
int ct = 0;
char ch;
//cin.get(ch);
cin >> ch;
while (ch != '\n'){
cout << ch;
ct++;
//cin.get(ch);
cin >> ch;
}
cout << ct << endl;
输入: hello world !
输出:
helloworld!
_(光标)
1. 代码会跳过空格,将输入压缩
==2. 更糟糕的是,>> 会自动跳过换行符,不会将换行符赋值给ch,所以while()循环将不会终止。
istream & get(char &)返回引用,所以可以实现拼接输出。
cin.get(c1).get(c2);
读取文件时:
char ch;
while(cin.get(ch))
{
//process input;
}
只要存在有效输入,cin.get(ch)的返回值都将是cin,此时结果判定为true,因此循环将继续。到达文件尾时,返回值判定为false,循环终止。
成员函数get(void)
get(void)也读取空白,但是使用返回值的方式将输入传递给程序。
int get(void)
char ch;
ch = cin.get();
while(ch != '\n')
{
cout << ch;
ct++;
ch = cin.get();
}
cout << st << endl;
读取文件时:
到达文件尾时,cin,get(void)都将返回值EOF——头文件iostream提供的一个符号常量。
int ch;
while ((ch = cin.get()) != EOF )
{
//process input
}
这里应将ch的类型声明为int,而不是char,因为值EOF可能无法使用char类型来表示。
特征 | cin.get(ch) | ch= cin.get() |
---|---|---|
输入字符的方法 | 赋给参数ch | 将函数返回值赋给ch |
字符输入时函数的返回值 | 指向istream对象的引用 | 字符编码(int值) |
到达文件尾时函数的返回值 | 转换为false | 返回EOF |
使用哪种字符?
希望输入跳过空格、制表符、或换行符时,选择>>抽取运算符
希望检查所有字符时,使用get()方法,一般来说,get(char&)方法更佳,而get(void)方法与C语言的兼容性更好。
字符串输入:getline()、get()和ignore() (整行读取,包括空格,遇到换行符结束)
getline()和get()字符串读取版本都读取字符串,它们的函数特征标相同。
它们在默认情况下读取整行,而不是一个单词。
istream & get(char*,int , char);
istream & get(char*,int);
istream & get(char*, int, char);
istream & getline(char*, int);
- 参数一:放置输入字符串的内存单元的地址
- 参数二:存放的字节数:读取的字符数 + 1(额外的一个字符用于存储结尾的空字符,以便将输入存储为一个字符数)
- 参数三: 指定用作分界符的字符,只有两个参数的版本 默认将换行符用作分界符 。
上述函数都在读取最大数目的字符或遇到换行符后为止。
get()和getline()的主要区别在于get()将换行符留在输入流中,这样接下来的操作首先看到的将是换行符,而getline()抽取并丢弃输入流中的换行符。
第三个参数用于制定分界符,get()将分界符留在输入流中,而getline()不保留(抽取并丢弃)
ignore()成员函数:从输入流抽取并丢弃一定数量的字符
istream & ignore(int i, int = EOF);
//默认参数EOF导致ignore()读取指定数目的字符或读取到文件尾。
- 参数一:要读取的最大字符数
- 参数二:字符参数: 输入分解符
eg: cin.ignore(255,‘\n’);读取并丢弃接下来的255个字符或直到达到第一个换行符。
读取字符串getline()、get()的使用
// get_fun.cpp -- using get() and getline()
#include <iostream>
const int Limit = 255;
int main()
{
using std::cout;
using std::cin;
using std::endl;
char input[Limit];
cout << "Enter a string for getline() processing:\n";
cin.getline(input, Limit, '#');
//使用#结束,不会跳过换行符
//getline()抽取并丢弃了分界符(结束符)#
cout << "Here is your input:\n";
cout << input << "\nDone with phase 1\n";
char ch;
cin.get(ch);
cout << "The next input character is " << ch << endl;
if (ch != '\n')
cin.ignore(Limit, '\n'); // discard rest of line
cout << "Enter a string for get() processing:\n";
cin.get(input, Limit, '#');
//get()并未抽取分界符 #
cout << "Here is your input:\n";
cout << input << "\nDone with phase 2\n";
cin.get(ch);//所以这里能够读取到分界符
cout << "The next input character is " << ch << endl;
/* keeping output window open */
/*
cin.clear();
while (cin.get() != '\n')
continue;
cin.get();
*/
return 0;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6mvE1pgi-1657814432269)(2022-07-11-21-54-00.png)]
意外字符串输入
待扩充,先暂时跳过…
其他istream方法
- istream & read(char*, int)
从输入流中读取int个字符,并将它们插入到char*位置处。与get()和getline()的区别是不会在输入后加上空字符,不能将输入转换为字符串。用途主要是与ostream write()结合起来,用于文件的输入和输出。
- char ch = cin.peek() ,ch = ?
函数返回输入流中的下一个字符,但并不抽取输入流中的字符。用于查看下一个输入的字符,但并不影响输入流,可以用来做循环的判断条件。
- gcount(),统计非格式化输入(不是>>,没有数据类型转换)的字符数。没有strlen()直接统计目的数组来得快。
- istream & putback(char ch) :将ch插回到输入流中。
peek()的效果相当于先get()再putback()。
文件的输入与输出
#inclue < fstream >
C++ IO类软件包处理文件的输入输出与标准输入输出非常相似。创建一个流对象,使一端连接文件一端连接程序即可。ifstream和ofstream继续自istream和ostream类,所以标准输入输出的方法,这些文件类也可以正常使用。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xw5i8cv0-1657814432271)(2022-07-12-15-41-19.png)]
简单的文件 I/O
写入文件:
- 创建ofstream对象(如fout)来管理输出流
- 将该对象与特定的文件关联起来(流对象的一端(流目的端)与文件相连)/ 或者叫打开文件
- 使用cout的方式使用该对象,唯一的区别是输出将进入文件而不是屏幕。
close()方法只是显式的断开流对象与文件的链接,并不会删除输入输出流的缓冲区。也不会删除流对象(需要析构函数来销毁),关闭之后仍可以重新打开流对象进行连接其他文件。
输出时,没有文件就创建文件,有文件默认以截短方式打开文件(清空文件,输出到一个空文件中)
读文件:
- 创建一个ifstream对象管理输入流
- 将流对象的流源与特定的文件关联起来 /(打开文件)
- 以使用cin的方式使用该对象
需要进行文件打开成功检测,不能创建文件,没有文件会打开失败。
- 创建流对象
ofstream fout; //创建输出流(程序输出到文件)
ifstream fin; // 创建输入流(文件输入到程序)
- 打开文件(打开流与文件链接): 使用open()方法或构造函数完成流与文件的关联
方式一:
fstream fs; //创建一个未绑定的流
fs.open(s,mode);//创建时没有绑定,使用open打开
s : 可以是string或c风格的字符串
mode: 默认的模式依赖于fstream的类型
***请养成检测open是否成功的习惯*******
方式二:
fstream fs(s);
//创建一个流,并打开名为s的文件,s可以是string或C风格字符串
使用构造函数将这两步(创建流对象和关联文件)合并成一句。
方式三:
fstream fs(s,mode);
//同上,并以mode模式打开
读写操作总结
fout写:
- 使用ostream方法如 << 或 put(char) , write(const char*,int)
fin读:
-
使用istream方法如
抽取运算符>> ,读入时跳过空白字符
读入字符串:
get(char* ,int,‘#’),不抽取分界符
getline(char*, int size ,‘#’),抽取并丢弃分界符两参数版本默认以换行符作为分解符,每次只能读一行
get(char* ,int)
getline(char*, int size)
每次读取整行:
while(getline(fin,str))//一行一行读
{
cout<<str<<endl;
}
或
while(!fin.eof())//一行一行读
{
fin.getline(buffer,n,delim);//读n-1个字符到buffer,直到碰到换行或EOF或delim
}
读入单字符:
get(ch&) 更好 false
int get(void)
while(fin.get(ch)) //到结尾退出 fin → false
{
逐个字节读入
}
- 关闭文件
流状态检查和is_open()
对于文件流来说,检查文件是否打开,使用is_open()方法是比较好的。(可以检测到文件模式配置不合理时打开失败的请情形)
if ( !is_open() )
{
...
}
打开多个文件
for () //循环打开多个文件
{
fin.open(...); //1. 打开文件
if ( !is_open() )// 2. 打开成功检测
{
}
while(fin.get(ch))//3. 读取文件
{
}
fin.clear(); //重置条件状态,不一定需要这句话
fin.close(); //4. 关闭文件
}
命令行处理技术
// count.cpp -- counting characters in a list of files
#include <iostream>
#include <fstream>
#include <cstdlib> // or stdlib.h
int main(int argc, //argc是命令行中的参数个数,包括命令名本身
char* argv[]) //argv[0]是命令行中的第一个字符串,以此类推。
//命令行中的参数可以传入程序
{
using namespace std;
if (argc == 1) //没有参数时退出
{
cerr << "Usage: " << argv[0] << " filename[s]\n";
exit(EXIT_FAILURE);
}
ifstream fin; // open stream
long count;
long total = 0;
char ch;
for (int file = 1; file < argc; file++) //按命令行中的参数个数进行循环 (减掉了命令名本身)
{
fin.open(argv[file]); // 逐个打开命令行中的文件参数
if (!fin.is_open()) //检查是否打开
{
cerr << "Could not open " << argv[file] << endl; //使用cerr表示错误消息
fin.clear(); //重置条件状态
continue; //跳出本次循环,重新检测
}
count = 0;
while (fin.get(ch))
count++;
cout << count << " characters in " << argv[file] << endl;
total += count;
fin.clear(); //重置条件状态
fin.close(); //关闭文件
}
cout << total << " characters in all files\n";
return 0;
}
文件模式
使用ios_base类中定义的常量来指定模式,这个模式也是bitmask类型,需要使用 | (按位或)来叠加。
常量 | 含义 |
---|---|
ios_base::in | 打开文件,读 |
ios_base::out | 打开文件,写 |
ios_base::ate | 打开文件,并移到文件尾 |
ios_base::app | 追加到文件尾 |
ios_base::trunc | 如果文件存在,则截短文件 |
ios_base::binary | 二进制文件 |
默认模式:
ifstream 默认 读
ofstream 默认 截短并写
位运算符OR( | )用于将两个位置合并成一个可用于设置两个位的值。
fstream类不提供默认模式值,因此必须显式的提供模式。
app和ate的区别在于,app只允许将数据添加到文件尾,而ate模式将指针放到文件尾。
二进制文件
对于字符来说,二进制和文本的表示是一样的,都是字符的ASCII码表示。区别主要在数字上,二进制文件的整数浮点数等表示方式,而文本文件是数字的ASCII码,可能存在转换精度问题,而且占用内存也更多。对于结构体和其他非基本的数据结构,也可以使用二进制文件比较方便。
以二进制的方式对文件进行读写操作
打开文件要指定为ios::binary
写文件:
二进制写文件主要利用流对象调成员函数write
函数原型: ostream & write(const char buffer, int len)*;
参数解释:字符串指针buffer指向内存中一段存储空间,len是读写的字节数。
读文件:
二进制读文件主要利用的是流对象调用成员函数read
函数原型:istream & read(char * buffer, int len);
参数解释:字符串指针buffer指向内存中一段存储空间,len是读写的字节数
注意:
- 第一个参数可能需要把其他数据类型的地址(指针)强转成write和read要求的类型
- 第二个参数使用sizeof()运算符来计算读取或写入的字节数。
eg:
const int LIM = 20;
struct planet
[
char name[LIM];
double population;
double g;
]
planet p1;
//以文本文件格式保存
ofstream fout("planet.dat", ios_base::in | ios_base::app);
fout << p1.name << " "<<p1.population<<" "<<p1.g<<"\n";
必须使用成员运算符显式的提供每个结构成员,还必须将相邻的数据分隔开,以便区分。
//以二进制文件格式保存
ofstream fout("planet.dat", ios_base::app | ios_base::binary);
fout.write((const char*)&p1, sizeof(p1));
上述代码将使用计算机的内部数据表示,将整个结构作为一个整体保存。
与文本文件相比,信息的保存更为紧凑、精确。
同样的方法也适用于不使用虚函数的类(隐藏指针也会参与简单复制,保存这种地址而不是数据没有意义。),,在这种情况下,只有数据成员会保存,而方法不会保存。(类成员有指针会就出问题,因为读和写操作是简单复制,不是深度复制)
二进制文件读写示例:
// binary.cpp -- binary file I/O
#include <iostream> // not required by most systems
#include <fstream>
#include <iomanip>
#include <cstdlib> // (or stdlib.h) for exit()
inline void eatline() { while (std::cin.get() != '\n') continue; }
struct planet
{
char name[20]; // name of planet
double population; // its population
double g; // its acceleration of gravity
};
const char* file = "planets.dat";
int main()
{
using namespace std;
planet pl;
cout << fixed << right;
// show initial contents
ifstream fin;
fin.open(file, ios_base::in | ios_base::binary); // binary file
//NOTE: some systems don't accept the ios_base::binary mode
if (fin.is_open())
{
cout << "Here are the current contents of the "
<< file << " file:\n";
while (fin.read((char*)&pl, sizeof pl))
{
cout << setw(20) << pl.name << ": "
<< setprecision(0) << setw(12) << pl.population
<< setprecision(2) << setw(6) << pl.g << endl;
}
fin.close();
}
// add new data
ofstream fout(file,
ios_base::out | ios_base::app | ios_base::binary);
//NOTE: some systems don't accept the ios::binary mode
if (!fout.is_open())
{
cerr << "Can't open " << file << " file for output:\n";
exit(EXIT_FAILURE);
}
cout << "Enter planet name (enter a blank line to quit):\n";
cin.get(pl.name, 20);
while (pl.name[0] != '\0')
{
eatline();
cout << "Enter planetary population: ";
cin >> pl.population;
cout << "Enter planet's acceleration of gravity: ";
eatline(); //吃掉一次换行符
//因为之前的get()将换行符留在输入流中,cin >> pl.g;结束时输入流中会输入一个换行符,吃掉这个换行符
// 接下来用换行符退出时,才能准确识别出来这个退出的换行符
//eatline()读取并丢弃输入流中换行符之前的内容(包括换行符),这样接下来的get看到的第一个字符就不是换行符
fout.write((char*)&pl, sizeof pl);
cout << "Enter planet name (enter a blank line "
"to quit):\n";
cin.get(pl.name, 20);
//如果读取的第一个字符是换行符,将把换行符留在输入流中,返回0个字符,并在pl.name[0]后面加一个 '\0'
//这样就将退出循环
//而换行符被吃掉一次之后,get()可以顺利读取pl.name
}
fout.close();
// show revised file
fin.clear(); // not required for some implementations, but won't hurt
fin.open(file, ios_base::in | ios_base::binary);
if (fin.is_open())
{
cout << "Here are the new contents of the "
<< file << " file:\n";
while (fin.read((char*)&pl, sizeof pl))
{
cout << setw(20) << pl.name << ": "
<< setprecision(0) << setw(12) << pl.population
<< setprecision(2) << setw(6) << pl.g << endl;
}
fin.close();
}
cout << "Done.\n";
// keeping output window open
// cin.clear();
// eatline();
// cin.get();
return 0;
}
这个程序不能使用string对象(应该使用固定容量的字符数组形式),因为string的实现实际上包含了一个字符指针,但是读和写操作只是简单复制,并不能进行深度复制,会出现乱码和程序崩溃。
随机读取
随机存取常被用于数据库文件,程序维护一个独立的索引文件,该文件指出数据在主数据文件的位置。这样程序便可以直接跳转到这个位置,读取或修改其中的数据。
读写流
fstream finout;
读写流对象继承了两个缓冲区,一个用于输入,一个用于输出
并能同步这两个缓冲区的处理。也就是说,当程序读写文件时,它能协调地移动输入缓冲区中的输入指针和输出缓冲区的输出指针。
fstream类是从iostream类派生而来的,而iostream类是基于istream类和ostream类的。因此fstream类能够使用它们的所有方法
文件模式的设置
finout.open(file,ios_base::in | ios_base::out | ios_base::binary)
- 文件记录对应于程序结构或类,因此用二进制格式(.dat)存储起来更方便。
- 使用out模式可以修改原始数据,而使用app追加模式,不能修改原始数据,指正追加新数据,所以这里选择使用out模式。
文件中的移动方式:读指针seekg()和写指针seekp()
由于fstream类使用缓冲区来存储中间数据,因此指针指向的是缓冲区中的位置,并不是实际的文件。
fstream继承了两个方法:
seekg()用于istream对象
seekp()用于ostream对象
读指针移动函数seekg()原型如下:它们都是模板
basic_istream<charT,traits>& seekg(off_type,ios_bas::seekdir);
basic_istream<charT,traits>& seekg(pos_typ);
本章使用的是char类型的模板具体化。
istream & seekg(streamoff,ios_base::seekdir); //文件相对位置
istream & seekg(streampos); //文件绝对位置
原型一:读指针定位到距离第二个参数指定的文件位置…字节的位置。
- streamoff值被用于度量相对于文件特定位置(第二个参数提供)的偏移量(单位为字节,整型)。
- seekdir参数也是ios_base类中定义的另一种整型,有三个预定义的常量共选择:
ios_base::beg 文件开头
ios_base::cur 当前位置
ios_base::end 文件结尾
使用示例:
fin.seekg(30,ios_base::beg); //将读指针定位到相对于文件开头处30个字节处。
fin.seekg(-1,ios_base::cur);//当前读指针向前移动一个字节
fin.seeg(0,ios_base::end);//将读指针定位到文件尾
原型二:读指针定位到里开头特定距离(…字节)的位置。
- streampos的值表示文件中的绝对位置,(文件开始处的第一个字节编号为0),即相对于开头的第多少个字节处。
使用示例:
fin。seekg(112); 将读指针指向文件的第113个字节(从0计数开始算第一个字节)
注:早期streamoff和streampos类型实际上是一些标准整形(如long)的typedef,后来随着类型升级,它们作为off_type和pos_type的char具体化继续存在。
检查文件指针的当前位置:读指针 tellg() ;写指针tellp()
tellg()和tellp()都返回一个表示当前绝对位置的streampos值(以字节为单位的整型,从文件开始处的第一个字节称为0字节处)。
- 创建fstream对象时,输入指针和输出指针将一前一后的移动,因此tellg和tellp返回的值相同。
- 如果分别创建istream对象和ostream对象,两指针将相互独立的移动,返回不同的值。
重置流对象条件状态
if (finout.eof())
finout.clear(); //clear eof flag
else
{
cerr <<"Error in reading"<<file<<".\n";
exit(EXIT_FAILURE);
}
上述代码解决的问题是:程序读取并显示整个文件后,将设置eofbit元素。这使程序相信,它已经处理完文件,并禁止对文件做进一步的操作。使用clear方法重置流状态,并打开eofbit后,程序便可以再次访问该文件。
eatline()和get()配合使用?
随机修改二进制文件程序示例
// random.cpp -- random access to a binary file
#include <iostream> // not required by most systems
#include <fstream>
#include <iomanip>
#include <cstdlib> // (or stdlib.h) for exit()
const int LIM = 20;
struct planet
{
char name[LIM]; // name of planet
double population; // its population
double g; // its acceleration of gravity
};
const char* file = "planets.dat"; // ASSUMED TO EXIST (binary.cpp example)
inline void eatline() { while (std::cin.get() != '\n') continue; }
int main()
{
using namespace std;
planet pl;
cout << fixed;
// show initial contents
fstream finout; // 创建读写流
finout.open(file,
ios_base::in | ios_base::out | ios_base::binary); //读、写、二进制
//NOTE: Some Unix systems require omitting | ios::binary
int ct = 0;
//文件成功打开
if (finout.is_open())
{
finout.seekg(0); // 读指针 跳转到文件开始
cout << "Here are the current contents of the "
<< file << " file:\n";
while (finout.read((char*)&pl, sizeof pl))
{
cout << ct++ << ": " << setw(LIM) << pl.name << ": "
<< setprecision(0) << setw(12) << pl.population
<< setprecision(2) << setw(6) << pl.g << endl;
}
//程序读取显示整个文件之后,将设置eofbit元素
//这使程序相信,它已经处理完文件,并禁止对文件进一步的读写。
//使用clear方法重置条件状态,并打开eofbit之后,程序便可以再次访问该文件。
if (finout.eof())
finout.clear(); // clear eof flag
//else部分处理程序程序因到达文件尾之外的其他原因(如硬件故障)而停止读取的情况
else
{
cerr << "Error in reading " << file << ".\n";
exit(EXIT_FAILURE);
}
}
//文件打开失败
else
{
cerr << file << " could not be opened -- bye.\n";
exit(EXIT_FAILURE);
}
// 修改记录
cout << "Enter the record number you wish to change: ";
long rec;
cin >> rec;
eatline(); // get rid of newline
if (rec < 0 || rec >= ct)
{
cerr << "Invalid record number -- bye\n";
exit(EXIT_FAILURE);
}
streampos place = rec * sizeof pl; // convert to streampos type
finout.seekg(place); // 读指针 根据输入 进行随机跳转
if (finout.fail()) //跳转失败
{
cerr << "Error on attempted seek\n";
exit(EXIT_FAILURE);
}
//跳转成功 开始读文件,并显示所选部分文件
finout.read((char*)&pl, sizeof pl);
cout << "Your selection:\n";
cout << rec << ": " << setw(LIM) << pl.name << ": "
<< setprecision(0) << setw(12) << pl.population
<< setprecision(2) << setw(6) << pl.g << endl;
if (finout.eof())
finout.clear(); //读取完毕 , 重置条件状态
cout << "Enter planet name: ";
cin.get(pl.name, LIM); //重新输入名称
eatline();
cout << "Enter planetary population: ";
cin >> pl.population; //重新输入,进行了数据类型的转换。这里使用<<抽取运算符,所以后面不需要加eatline()吃掉换行符
cout << "Enter planet's acceleration of gravity: ";
cin >> pl.g; //重新输入,进行了数据类型的转换。这里使用<<抽取运算符,所以后面不需要加eatline()吃掉换行符
//将修改写入文件
finout.seekp(place); // 写指针位置 跳到所更改记录处
finout.write((char*)&pl, sizeof pl) << flush;//主动刷新输出缓冲区
if (finout.fail()) //输出成功检验
{
cerr << "Error on attempted write\n";
exit(EXIT_FAILURE);
}
// 显示更改后的文件
ct = 0;
finout.seekg(0); //读指针 跳转到文件开头
cout << "Here are the new contents of the " << file
<< " file:\n";
while (finout.read((char*)&pl, sizeof pl)) //循环读取
{
cout << ct++ << ": " << setw(LIM) << pl.name << ": "
<< setprecision(0) << setw(12) << pl.population
<< setprecision(2) << setw(6) << pl.g << endl;
}
finout.close(); //关闭读写流
cout << "Done.\n";
// keeping output window open
// cin.clear();
// eatline();
// cin.get();
return 0;
}
内核格式化
读取string对象中的格式化信息或者将格式化信息写入string对象被称为内核格式化(incore formatting)
头文件 < sstream >定义了三个类:istringstream,ostringstream和stringstream,分别用来进行流的输入、输出和输入输出操作。根据派生关系,这些类能够使用istream和ostream方法来抽取字符串中的信息,并对要放到字符串中的信息进行格式化。(主要用来进行数据类型转换的。)