1. C++输入和输出概述
C++依赖于C++的I/O解决方案,包含在头文件iostream和fstream中。C语言的I/O解决方案包含在stdio.h中。
1.1 流和缓冲区
C++把输入和输出看作字节流。输入时,程序从输入流中抽取字节;输出时,程序将字节插入到输出流中。输出流中的字节可能来自键盘、存储设备(如硬盘)或其他程序。同样,输出流中的字节可以流向屏幕、打印机、存储设备或其他程序。所以输入包含两步(两端连接):
将流与输入去向的程序关联起来
将流与文件连接起来
通常,通过使用缓冲区可以更高效地处理输入和输出。缓冲区是用作中介的内存块,可以匹配两种不同的信息传输速率(程序每次只能处理一个字节,而磁盘驱动器以512字节的块为单位来传输)。
键盘输入时,每次按下回车键时才刷新缓冲区。对于屏幕输出,C++程序通常在用户发送换行符时刷新缓冲区(也可能在其他情况刷新)。
1.2 流、缓冲区和iostream文件
- streambuf类为缓冲区提供了内存,并提供了用于填充缓冲区、访问缓冲区内容、刷新缓冲区和管理缓冲区内存的类方法;
- ios_base类表示流的一般特征,如是否可读取、是二进制还是文本流等;
- ios类基于ios_base,其中包括了一个指向streambuf对象的指针成员;
- ostream类是从ios类派生而来的,提供了输出方法;
- istream类也是从ios类派生而来的,提供了输入方法;
- 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处理wchat_t类型。
下面的语句通过指向的streambuf对象将字符创“hello”中的字符放到cout管理的缓冲区中。cout对象凭借streambuf对象的帮助,管理着流中的字节流:
cout<<"hello";
1.3 重定向
标准输入输出对应着键盘和屏幕。很多操作系统(UNIX、Linux和Windows)都支持重定向,这个工具使得能够改变标准输入和标准输出。
2. 使用cout进行输出
2.1 重载的<<运算符
<<运算符,默认情况下为左移运算符。但ostream类重新定义了该运算符,在这种情况下,该运算符叫插入运算符。插入运算符被重载,使之能够识别C++中所有的基本类型。也就是说对于每种基本数据类型,ostream类都提供了<<()函数的定义。
注意指针的输出,需要将其转换为void*类型,输出地址:
int eggs = 12;
char* amount = "dozen";
cout<<&eggs;//输出地址
cout<<amount;//输出"dozen"
cout<<(void *)amount;//输出地址
2.2 其他ostream方法
除了各种operator<<()函数,ostream类提供了put()方法和write()方法,前者用于显示字符,后者用于显示字符串。他们的原型为:
//范围类型为ostream&类型,因此可以连续输出
ostream& put(char);
//第一个为显示字符串的地址,第二个为显示多少个字符
basic_ostream<charT, traits>& write(const char_type* s, streamsize n);//
put举例说明:
cout.put('W');//输出W
cout.put('I').put('t');//输出It
cout.put(66.3);//输出B,因为转化为66,然后在转换为B
write举例:
#include<iostream>
#include<cstring>
using namespace std;
int main()
{
const char* state = "hello";
int len = strlen(state);
for(int i = 1; i <= len; i++)
{
cout.write(state, i);
cout<<endl;
}
return 0;
}
输出:
h
he
hel
hell
hello
2.3 刷新缓冲区
使用cout发送时,先将其存入缓冲区,直到缓冲区填满。然后程序刷新缓冲区,把内容发出去,并清空缓冲区,以存储新的数据。通常缓冲区为512字节或其整数倍。
然而对于屏幕输出来说,填充缓冲区的重要性要低得多。所以在屏幕输出时,程序不必等到缓冲区被填满。例如换行符发送到缓冲区后,将刷新缓冲区。多数C++实现都会在输入即将发生时刷新缓冲区,比如:
cout<<"Enter a number:";
cin>>num;
如果实现不能在所希望时刷新输出,可以使用两个控制附中的一个来强行进行刷新。控制符flush刷新缓冲区,而控制符endl刷新缓冲区,并插入一个换行符。
cout<<"hello",<<flush;//不会换行
cout<<"hello"<<endl;
另外flush也是函数。例如可以调用flush来刷新cout缓冲区:
flush(cout);
//或者使用
cout<<flush;
2.4 用cout格式化
2.4.1 修改显示时的计数系统
控制整数以10、16还是8进制显示,可以使用dec、hex和oct控制符,使用后,后面的显示系统将不会改变,直到格式状态设置为其他选项为止。
hex(cout);
//或者使用
cout<<hex;
2.4.2 调整字段宽度
可以使用width成员函数将长度不同的数字放到宽度相同的字段中(只会影响下一个输出)。原型为:
int width();//返回字段宽度的当前设置
int width(int i);//将字段设置为i个空格,并返回以前的字段宽度值。以便用于恢复。
举例:
#include<iostream>
#include<cstring>
using namespace std;
int main()
{
cout<<'#';
cout.width(12);
cout<<12<<'#'<<12<<"#\n";
return 0;
}
输出结果为:
# 12#12#
可以看到只影响到了第一个输出12(右对齐),下一个#又恢复正常输出。注意,如果有13个字符输出,则输出13个字符,而不会删除字符来适应12的长度。
2.4.3 填充字符
可以使用fill()成员函数来填充字符(默认填充为空格)。而且填充字符一旦确定,将一直保留有效,直到更改它为止:
cout.fill('*');//用*号填充,width使用
2.4.4 设置浮点数的显示精度
定点模式:精度指的是总的位数
C++默认精度为6位。可以使用precision进行设置。而且设置精度一直有效,直到改变。
cout.precision(2);//精度为2
#include<iostream>
#include<cstring>
using namespace std;
int main()
{
float price1 = 12.40;
float price2 = 1.9+8.0/9;
cout<<price1<<endl;
cout<<price2<<endl;
cout.precision(2);
cout<<"After change precision: "<<endl;
cout<<price1<<endl;
cout<<price2<<endl;
return 0;
}
输出:
12.4
2.78889
After change precision:
12
2.8
2.4.5 打印末尾的0和小数点
如果为了美观,想要打印小数点后面的0,可以使用ios_base类提供的setf()函数。
cout.setf(ios_base::showpoint);//显示末尾小数点,而且输出输出0
上面的12没有显示小数点,如果使用它,则会显示为"12."。如果精度为4,则还会输出"12.40"。
setf()函数还有另外的一些功能,这里就不一一介绍。
2.4.6 标准控制符
使用setf()不是最友好的方式,C++提供了多个控制符,能够调用setf()。前面已经介绍了dec、hex和oct了。下面列举这些控制符:
cout<<left<<fixed;//左对齐和定点选型;
2.4.7头文件iomanip
使用iostream工具来设置一些格式值(如字段宽度)不太方便。文件化工作,C++在头文件iomanip提供了其他一些控制符。
setprdcision(4);//控制精度4
setfill('*');//填充字符'*'
setw(6);//字段宽度为6
3. 使用cin进行输入
输入可以是字符串的一部分、int值、float等。下面的value_holder是内存单元,它可以是变量、引用、被解除引用的指针,也可以是类或结构的成员。
cin >> value_holder;
典型的运算符函数的原型为:
istream& operator>> (int &);
也可以将hex、oct和dec控制符结合cin一起使用。下面的语句,如果输入12或这0x12解释为16进制的12或者十进制的18.
cin>>hex;
和cout一样,可以进行拼接输入:
cin >> name >> fee >> group;
3.1 cin>>如何检查输入
一般情况下,不同版本的运算符查看输入流的方法是相同的。它们会跳过空白(空格、换行符和制表符),知道遇到非空白字符。在其他模式下>>运算符将读取一个指定类型的数据,知道目标不匹配结束。
int elevation;
cin >> elevation;
上面代码,如果输入-123Z,elevation为-123。如果输入Z-123,则elevation为0(这种情况,如果是if或者while的判断语句,则判定为false)。
#include<iostream>
using namespace std;
int main()
{
int sum = 0;
int input;
while(cin >> input)
{
sum +=input;
}
cout<<"sum = "<<sum<<endl;
return 0;
}
测试程序输出:
1
3
34c
sum = 38
3.2 流状态
流状态由3个ios_base元素组成:eofbit、badbit或failbit,其中每个元素都是一位,可以是1或0。
3.2.1 设置状态
上表中设置状态有两种方法:clear和setstate。
clear()//清楚全部3个状态位(eofbit、badbit和failbit)
clear(eofbit);//设置eofbit,其他2个状态位清除
setstate(eofbit);//只设置eofbit,其他2个状态位不影响
3.2.2 I/O和异常
3.2.3 流状态的影响
3.3 其他istream类方法
方法get(char&)和get(void)提供不跳过空白的单字符输入功能;
函数get(char*, int, char) 和getline(char*, int, char)在默认情况下读取整行。
它们被称为非格式化输入函数,因为它们只读取字符输入,而不会跳过空白,也不进行数据转换。
3.3.1 单字符输入
(1)成员函数get(char& )
下面代码演示成员函数cin.get(ch)输入。该方法不会跳过空白,空白也算一个字符,下面程序正常进行。
#include<iostream>
using namespace std;
int main()
{
int ct = 0;
char ch;
cin.get(ch);
while(ch != '\n')
{
cout<<ch;
ct++;
cin.get(ch);
}
cout<<ct<<endl;
return 0;
}
但是下面的代码不会包括空白,而且程序不会结束,因为换行符被舍弃:
#include<iostream>
using namespace std;
int main()
{
int ct = 0;
char ch;
cin>>ch;
while(ch != '\n')
{
cout<<ch;
ct++;
cin>>ch;
}
cout<<ct<<endl;
return 0;
}
get(char &)成员函数返回一个指向用于调用它的istream对象的引用,因此,可以进行拼接:
char c1, c2, c3;
cin.get(c1).get(c2) >> c3;//有效
cin.get(ch)到达文件末尾后,ch将不会被赋值,而且判定为false。
while(cin.get(ch))
{ ... }
(2) 成员函数get(void)
get(void)成员函数也是读取空白,但是用返回值来将输入传递给程序:
#include<iostream>
using namespace std;
int main()
{
int ct = 0;
char ch;
ch = cin.get();
while(ch != '\n')
{
cout<<ch;
ct++;
ch = cin.get();
}
cout<<ct<<endl;
return 0;
}
cin.get()返回值为int,所以不能进行拼接,所以下面代码非法:
cin.get().get()>>c3;//错误
cin.get(c1).get();//可以
到达文件尾后,cin.get(void)都将返回EOF(iostream头文件提供的一个符号常量)。
while((ch = cin.get()) != EOF){ ... }
3.3.2 选择哪种单字符输入形式
- >>:跳过空白
- get(char&)的接口更好。get(void)与标准C语言中的gethcar()函数类似,用cin.get()替换getchar(),用cout.put(ch)替换putchar(ch)。
3.3.3 字符串输入:getline()、get()和ignore()
istream& get(char*, int, char);
istream& get(char*, int);//换行符(分界符)留在输入流中,下一次输入可以看到
istream& getline(char*, int, char);
istream& getline(char*, int);//丢弃换行符(分界符)
第一个参数:字符串的内存地址;第二个参数:比读取的最大字符数大1(结尾的空字符);第三个参数:分界符的字符,只有两个参数则用换行符用作分界符。上述函数都是读取最大数目的字符或换行符后为止。
char line[50];
cin.get(line, 50);//读取到第49个字符或遇到换行符(默认情况)为止。
ignore接受两个参数,第一个表示读取的最大字符数,第二个表示输入分界符。
cin.ignore(255, '\n');
它的原型为:
istream& ignore(int = 1, int = EOF);//默认为读取指定数目的字符或读取到文件末尾。
3.3.4 意外字符串输入
3.4 其他istream方法
其他istream方法包括read()、peek()、gcount()和putback()。
- read():读取指定数目的字节,并存入指定的位置。该方法不会在输入后加入空字符,因此不能将输入转换为字符串。经常与ostream write()函数结合使用,来完成文件输入和输出。该方法返回istream&。
- peek():返回输入中的下一个字符,但不抽取输入流中的字符。用于查看作用。
- gcount(): 返回最后一个非格式化抽取方法读取的字符数。也就是说非抽取运算符(>>)读取的。
- putback(): 讲一个字符插入到输入字符串中,被插入的字符将是下一条输入语句读取的第一个字符。
read()举例:
char gross[144];
cin.read(gross, 144);//读取144个字符,并存入gross中。
peek()举例:
char great_input[80];
char ch;
int i = 0;
while((ch == cin.peek()) != '.' && ch !='\n')
cin.get(great_input[i++]);
great_input[i] = '\0';
4. 文件的输入和输出
重定向可以提供一些文件支持,但它有局限性,而且重定向来自于操作系统,而非C++。ifstream类、ofstream类和fsteam类都是从头文件iostream中的类派生而来的,因此这些新类的对象可以使用前面介绍过的方法。
4.1 简单的文件I/O
要写入文件,必须这样做:
- 创建一个ofstream对象来管理输出流;
- 将该对象与特定的文件关联起来;
- 以使用cout的方式使用该对象,唯一区别是输出将进入文件,而不是屏幕。
#include<fstream>
int main()
{
ofstream fout;//创建输出流对象
//关联起来,如果没有,则新建该文件,如果有则清空文件
fout.open("jar.txt");
//ofstream fout("jar.txt");//合并操作
fout << "hello world";
return 0;
}
读取文件的要求与写入文件相似:
- 创建一个ifstream对象来管理输入流;
- 将该对象与特定的文件关联起来;
- 以使用cin的方式使用该对象
#include<fstream>
using namespace std;
int main()
{
ifstream fin;//创建输入流对象
//打开文件,关联起来。
fin.open("jar.txt");
//ifstream fin("jar.txt");//合并操作
char ch;
fin >> ch;//读取一个字符
char buf[80];
fin >> buf;//读取一个word
fin.getline(buf, 80);//读取一行
string line;
getline(fin, line);
return 0;
}
如果输入或输出流过期,则文件的连接自动关闭。另外,也可以使用close()方式来显式的关闭文件的连接(只是关闭连接,流管理装置仍保留,可以重新连接文件):
fout.close();
fin.close();
下面是一个完整的输入输出例子,自己创建文件名,并写入内容后输出:
#include<iostream>
#include<fstream>
#include<string>
using namespace std;
int main()
{
string filename;
cout<<"Enter name for new file: ";
cin >> filename;
ofstream fout(filename.c_str());//创建并关联
fout <<"For your eyes only!\n";//写入文件
cout <<"Enter your secret number: ";
float secret;
cin >> secret;
fout << "Your secret number is "<< secret <<endl;
fout.close();
ifstream fin(filename.c_str());
cout<<"Here are the contents of "<<filename<<":\n";
char ch;
while(fin.get(ch))
cout<<ch;
cout<<"Done"<<endl;
fin.close();
return 0;
}
4.2 流状态检查和is_open()
如果一切顺利,则流状态为0。其他状态都是通过特定位设置1来记录的。是否打开文件可以这么判断:
fin.open(argv[file]);
if(fin.fail())//方法1,如果打开失败
if(!fin)//方法2,如果打开失败
if(!fin.is_open())//方法3,这种方式更好
之所以说is_open更好,是因为它可以检测试图以不合适的文件模式打开时失败。老式C++实现没有is_open()。
4.3 打开多个文件
打开多个文件,可以创建多个流。然而也可以打开一个流,并将它一次关联到各个文件。这种方式需要使用open方法将这个流与文件关联起来。例如:
ifstream fin;
fin.open("fat.txt");//关联
fin.close();//关闭关联
fin.clear();//重置fin,也不一定需要
fin.open("rat.txt");//关联
...
4.4 命令行处理技术
将命令行中的参数传递给程序的文件名。如下,其中argc为命令行中的参数个数,包括命令名本身。argv变量为一个指针,它指向一个指向char的指针,也就是说argv[0]表示一个指针,指向存储第一个命令行参数的字符串的第一个字符。
int main(int argc, char *argv[]);
例如:argc为4,argv[0]为wc,agrv1为report1。。。
wc report1 report2 report3
for(int i = 1;i < argc; i++)
cout<<argv[i]<<endl;
在Linux系统中,编译为a.out的可执行文件后,可以这么操作:
a.out report1 report2 report3
4.5 文件模式
文件模式描述的是文件将如何使用:读、写、追加等。将流与文件关联时设置。
ifstream fin("banjo", model1);
ofstream fout();
fout.open("harp", mode2);
- ifstream open()默认为ios_base:in(打开文件以读取);
- ofstream open()方法默认为ios_base::out|ios_base::trunc(打开文件,以读取并截短文件);
- fstream不提供默认模式值。
ios_base::ate和ios_base::app都是将文件指针指向打开的文件尾。二者的区别在于,ios_base::app只允许将数据添加到文件尾,而ios_base::ate模式将指针放到文件尾。
将文件存储在文件中时,可以选择文本格式或二进制格式。以-2.34216e+07为例:
- 文本格式:将所有内容都存储为文本,所以上例将存储13个字符。
优点:便于读取,便于两个计算机系统的传输。- 二进制格式:存储值的内部表示。所以上例中存储的不是字符,而是64位double表示。
优点:速度更快,通常占用空间也较小。但是两个计算机系统或者同一个系统的不同编译器可能使用不同的内部结构布局表示。
要以二进制格式(而非文本格式)存储数据,可以使用write()成员函数(同时要以read()进行读取,它们是一对)。
5. 内核格式化
C++库还提供了sstream族,它们使用相同的接口提供程序和string对象之间的I/O。也就是说使用cout的ostream方法将格式化信息写入对象中,并使用istream方法来读取string对象。这种方式称为内核格式化。
头文件sstream定义了一个从ostream类派生而来的ostringstream类(还有一个给予wostream的wostringstream类,这个类用于宽字符集)。下面看看ostringstream的应用:
#include<sstream>
#include<string>
#include<iostream>
using namespace std;
int main()
{
ostringstream outstr;
string hdisk;
cout<<"What's the name of your hard disk? ";
getline(cin, hdisk);
int cap;
cout << "What's its capacity in GB? ";
cin >> cap;
//输出到outstr中
outstr<<"The hard disk "<<hdisk<<" hsa a capacity of "<<cap <<" gigabytes.\n";
//使用ostringstream的成员函数str()读取outstr中的内容
string result = outstr.str();
cout<<result;
return 0;
}
下面是istringstream的例子:
#include<sstream>
#include<string>
#include<iostream>
using namespace std;
int main()
{
string lit = "It was a dark and stromy day";
istringstream instr(lit);//使用string对象进行初始化
string word;
while (instr >> word)//每次读取一个单词
cout << word <<endl;
return 0;
}
输出:
It
was
a
dark
and
stromy
day
还可以使用stringstream进行类型转换
#include<iostream>
#include<algorithm>
#include<sstream>
using namespace std;
int main(){
//1.字符串转int
string s="0123";//我们需要把字符串s转化为int类型
int a;//用于存储之后转化的int值
stringstream ss;//定义一个变量名为ss的串流变量ss;
ss<<s;//简单理解为输入给ss
ss>>a; //简单理解为输出给a
printf("%d\n",a);
//输出结果:123
//2.int类型转字符串
string s1;
int b=100;
ss.clear();//因为ss在上面已经使用过,所以需要清空再重复使用,ss.str("")效果相同
ss<<b;
ss>>s1;
cout<<s1<<endl;
//输出结果:100
return 0;
}