文章目录
第四章 C++
输入输出流
一、C++
输入输出机制
1.输入输出含义
在编程语言中的输入输出含义有所不同。程序的输入指的是从输入文件将数据传送给程序(内存),程序的输出指的是从程序(内存)将数据传送给输出文件。
2.“流”的概念
C++
的I/O
发生在流中,流是字节序列。如果字节流是从设备(如键盘、磁盘驱动器、网络连接等)流向内存,这叫做输入操作。如果字节流是从内存流向设备(如显示屏、打印机、磁盘驱动器、网络连接等),这叫做输出操作。
就C++
程序而言,I/O
操作可以简单地看作是从程序移进或移出字节,程序只需要关心是否正确地输出了字节数据,以及是否正确地输入了要读取字节数据,特定I/O
设备的细节对程序员是隐藏的。
3.C++
常用流类型
C++
的输入与输出包括以下3方面的内容:
- 对系统指定的标准设备的输入和输出。即从键盘输入数据,输出到显示器屏幕。这种输入输出称为标准的输入输出, 简称标准
I/O
。 - 以外存磁盘文件为对象进行输入和输出,即从磁盘文件输入数据,数据输出到磁盘文件。以外存文件为对象的输入输出称为文件的输入输出, 简称文件
I/O
。 - 对内存中指定的空间进行输入和输出。通常指定一个字符数组作为存储空间(实际上可以利用该空间存储任何信息)。这种输入和输出称为字符串输入输出,简称串
I/O
。
C++
标准库提供了一组丰富的具有输入/输出功能的流类型。常用流类如下:
4.流的状态
IO
操作与生俱来的一个问题是可能会发生错误,一些错误是可以恢复的,另一些是不可以的。在C++
标准库中,用iostate
来表示流的状态,不同的编译器iostate
的实现可能不一样,不过都有四种状态:
badbit
表示发生系统级的错误,如不可恢复的读写错误。通常情况下一旦badbit
被置位,流就无法再使用了。failbit
表示发生可恢复的错误,如期望读取一个数值,却读出一个字符等错误。这种问题通常是可以修改的,流还可以继续使用。eofbit
。当到达文件的结束位置时,eofbit
和failbit
都会被置位。goodbit
被置位表示流未发生错误。如果badbit
、failbit
和eofbit
任何一个被置位,则检查流状态的条件会失。
这四种状态都定义在类ios_base
中,作为其数据成员存在。在GNU GCC7.4的源码 中,流状态的实现如下:
5.管理流的状态
C++
标准库还定义了一组成员函数来查询或者操作这些状态。
bool bad() const; //若流的badbit置位,则返回true;否则返回false
bool fail() const; //若流的failbit或badbit置位,则返回true;
bool eof() const; //若流的eofbit置位,则返回true;
bool good() const; //若流处于有效状态,则返回true;
iostate rdstate() const; //获取流的状态
void setstate(iostate state); //设置流的状态
//clear的无参版本会复位所有错误标志位*(重置流的状态)
void clear(std::ios_base::iostate state = std::ios_base::goodbit);
6.流的通用操作
//----以下输入流操作---
int_type get();//读取一个字符
istream & get(char_type & ch);
//读取一行数据
istream & getline(char_type * s, std::streamsize count, char_type delim ='\n');
//读取count个字节的数据
istream & read(char_type * s, std::streamsize count);
//最多获取count个字节,返回值为实际获取的字节数
std::streamsize readsome(char_type * s, std::streamsize count);
//读取到前count个字符或在读这count个字符进程中遇到delim字符就停止,并把读取的这些东西丢掉
istream & ignore(std::streamsize count = 1, int_type delim = Traits::eof());
//查看输入流中的下一个字符, 但是并不将该字符从输入流中取走
//不会跳过输入流中的空格、回车符; 在输入流已经结束的情况下,返回EOF。
int_type peek();
//获取当前流中游标所在的位置
pos_type tellg();
//偏移游标的位置
basic_istream & seekg(pos_type pos);
basic_istream & seekg(off_type off, std::ios::seekdir dir);
二、C++
标准IO
1.标准输入流
istream
类定义了1个输入流对象,即cin
, 代表的是标准输入,它从标准输入设备(键盘)获取数据,程序中的变量通过流提取符“>>
”从流中提取数据。流提取符“>>
”从流中提取数据时通常跳过输入流中的空格、tab
键、换行符等空白字符。只有在输入完数据再按回车键后,该行数据才被送入键盘缓冲区,形成输入流,提取运算符“>>
”才能从中提取数据。需要注意保证从流中读取数据能正常进行。
2.标注输出流
ostream
类定义了3个全局输出流对象,即cout
,cerr
,clog
,平常用的最多的就是cout
,即标准输出。cout
将数据输出到终端,它与标准C输出stdout
关联。 cerr
是标准错误流(非缓冲),clog
也是标准错误流(带缓冲)。注意:在C语言中,标准输入、标准输出和标准错误分别用0, 1, 2文件描述符代表。
#include <iostream>
#include <string>
#include <limits>
using std::cout;
using std::cerr;
using std::endl;
using std::cin;
using std::string;
void printStreamStatus() //流的状态查看
{
cout << "cin's badbit = " << cin.bad() << endl
<< "cin's failbit = " << cin.fail() << endl
<< "cin's eofbit = " << cin.eof() << endl
<< "cin's goodbit = " << cin.good() << endl;
}
void test()
{
int number = 0;
printStreamStatus();
cin >> number;
printStreamStatus();
cin.clear(); //重置流的状态
printStreamStatus();
//cin.ignore(1024,'\n'); //清空缓冲区
cin.ignore(std::numeric_limits<std::streamsize>::max(),'\n'); //清空缓冲区
cout << "number = " << number << endl;
string s1;
cin >> s1;
cout << "s1 = " << s1 << endl;
}
void test1() //test的修改
{
int number = 0;
cout << "please input a number : " << endl;
while(cin >> number , !cin.eof()) //EOF,crtl+d 终止
{
if(cin.bad())
{
cerr << "stream is bad!" << endl;
return;
}
else if(cin.fail())
{
cin.clear();
cin.ignore(std::numeric_limits<std::streamsize>::max(),'\n');
cout << "please input a valid int number :" << endl;
}
else
{
cout << "number = " << number << endl;
}
}
}
int main()
{
//test();
test1();
return 0;
}
3.缓冲区
①缓冲区的概念
缓冲区又称为缓存,它是内存空间的一部分。也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区。缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区。
缓冲区就是一块内存区,它用在输入输出设备和CPU之间,用来缓存数据。它使得低速的输入输出设备和高速的CPU能够协调工作,避免低速的输入输出设备占用CPU,解放出CPU,使其能够高效率工作。
②缓冲区的类型
缓冲区分为三种类型:全缓冲、行缓冲和不带缓冲。
- 全缓冲:在这种情况下,当填满标准I/O缓存后才进行实际I/O操作。全缓冲的典型代表是对磁盘文件的读写。 (缓冲区大小:1024)
- 行缓冲:在这种情况下,当在输入和输出中遇到换行符时,执行真正的I/O操作。这时,我们输入的字符先存放在缓冲区,等按下回车键换行时才进行实际 的I/O操作。典型代表是键盘输入数据。
- 不带缓冲:也就是不进行缓冲,标准出错情况cerr/stderr是典型代表,这使得出错信息可以直接尽快地显示出来。
③输出缓冲区
输出缓冲区内容刷新的意思是:输出缓冲区的内容写入到真实的输出设备或者文件。
如下几种情况会导致输出缓冲区内容被刷新:
-
程序正常结束(有一个收尾操作就是清空缓冲区);
-
缓冲区满(包含正常情况和异常情况);
-
使用操纵符显式地刷新输出缓冲区,如:
endl
、ends
。flush
cout << "123" << flush; //flush有刷新功能 cout << "123" << endl; //enl有刷新功能,而且能换行 cout << "123" << ends; //ens没有刷新功能,但是会在最后面添加一个空格
-
使用
unitbuf
操纵符设置流的内部状态;unitbuf: 在每次执行完写操作后都刷新输出缓冲区 nounitbuf: 让流回到正常的缓冲方式
-
输出流与输入流相关联,此时在读输入流时将刷新其关联的输出流的输出缓冲区。
三、C++
文件IO
文件流是以外存文件为输入输出对象的数据流。文件输入流是从外存文件流向内存的数据,文件输出流是从内存流向外存文件的数据。每一个文件流都有一个内存缓冲区与之对应。文件流本身不是文件,而只是以文件为输入输出对象的流。若要对磁盘文件输入输出,就必须通过文件流来实现。
C++对文件进行操作的流类型有三个: ifstream
(文件输入流), ofstream
(文件输出流), fstream
(文件输入输出流)
explicit //防止隐式转换
0.文件模式
根据不同的情况,对文件的读写操作,可以采用不同的文件打开模式。文件模式在GNU GCC7.4源码实现中,是用一个叫做openmode 的枚举类型定义的,它位于ios_base类中。文件模式一共有六种,它们分别是:
-
in
: 输入,文件将允许做读操作;如果文件不存在,打开失败 -
ate
: 末尾,读入最初在文件的末尾 -
out
: 输出,文件将允许做写操作;如果文件不存在,则直接创建一个 -
app
: 追加,写入将始终发生在文件的末尾 -
trunc
: 截断,如果打开的文件存在,其内容将被丢弃,其大小被截断为零 -
binary
: 二进制,读取或写入文件的数据为二进制形式
string filename("test.txt");
ifstream ifs(filename,std::ios::in | std::ios::ate);
ofstream ofs(filename,std::ios::out | std::ios::app);
1.文件输入流ifstream
①读取文件内容
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
using std::cout;
using std::cerr;
using std::endl;
using std::ifstream;
using std::string;
using std::vector;
void test()
{
//默认情况,当文件不存在的时候,创建失败
ifstream ifs("test.txt"); //文件打开
if(!ifs.good())
{
cerr << "ifs open file error !" << endl;
}
string word;
while(ifs >> word) //对于文件输入流,默认以空格为分割符
{
cout << word << endl;
}
ifs.close(); //文件关闭
}
void test1()
{
//默认情况,当文件不存在的时候,创建失败
ifstream ifs("test.txt");
if(!ifs.good())
{
cerr << "ifs open file error !" << endl;
}
string word;
while(getline(ifs,word)) //按行读取文件,一次获取一行
{
cout << word << endl;
}
ifs.close();
}
void test2()
{
//默认情况,当文件不存在的时候,创建失败
ifstream ifs("test.txt");
if(!ifs.good())
{
cerr << "ifs open file error !" << endl;
}
string word[100]; //数组,提前知道文件有多少行
size_t idx = 0;
while(getline(ifs,word[idx]))
{
//cout << word << endl;
++idx;
}
size_t idy = idx;
cout << "idy = " << idy << endl;
for(idx = 0; idx < idy; ++idx)
{
cout << word[idx] << endl;
}
ifs.close();
}
void test3()
{
//默认情况,当文件不存在的时候,创建失败
ifstream ifs("test.txt");
if(!ifs.good())
{
cerr << "ifs open file error !" << endl;
}
//include <vector>
//using std::vector;
vector<string> vec; //vector,自动扩容
vec.reserve(100); //事先预定100的大小
string word;
while(getline(ifs,word))
{
vec.push_back(word); //往vec尾部插入元素
}
for(size_t idx = 0; idx != vec.size(); ++idx)
{
cout << vec[idx] << endl;
}
ifs.close();
}
int main()
{
test3();
return 0;
}
②vector
#include <iostream>
#include <vector>
using std::cout;
using std::endl;
using std::vector;
void printVectorCapacity(const vector<int> &vec)
{
cout << "vec.size() = " << vec.size() <<endl
<< "vec.capacity() = " << vec.capacity() << endl;
}
void test()
{
//针对的是GCC是扩容机制是两倍
//vector扩容的机制:当 size() == capacity() 的时候,如果还有元素加进来,就需要申请2*size()大小的空间,然后把老的空间里面的元素拷贝到新的空间,然后把旧的空间释放掉,接着把新的元素放到新的空间里面来
vector<int> numbers;
numbers.reserve(100); //一次性申请100个capacity(),避免vector多次申请扩容(如果提前知道有多少数据的话)
printVectorCapacity(numbers);
//numbers[0] = 10; //不能直接使用下标赋值
numbers.push_back(1);
printVectorCapacity(numbers);
numbers.push_back(2);
printVectorCapacity(numbers);
//遍历vector的方式
for(auto &elem : numbers)
{
cout << elem << " ";
}
cout << endl;
}
int main()
{
test();
return 0;
}
2.文件输出流ofstream
void test()
{
//默认情况,当文件不存在的时候,创建失败
ifstream ifs("test.txt");
if(!ifs.good())
{
cerr << "ifs open file error !" << endl;
}
//对于文件输出流,当文件不存在的时候,就去创建文件
//当文件名存在的时候,先去清空文件内容,然后再写内容
string filename("wuhan.txt");
ofstream ofs(filename);
if(!ofs) //如果文件打开失败
{
cerr << "ofs open"<< filename << "error!" << endl;
ifs.close();
return;
}
string word;
while(getline(ifs,word))
{
ofs << word << endl;
}
ifs.close();
ofs.close();
}
3.文件输入输出流fstream
tellg/tellp seekg/seekp 流中文件指针的位置
void test4()
{
//文件输入输出流,在作为文件输入流时候,当文件不存在时,打开失败
fstream fs("test1.txt");
if(!fs.good())
{
cerr << "fs open file error!" << endl;
return;
}
int number =0;
for(size_t idx = 0; idx != 5; ++idx)
{
cin >> number;
fs << number << " ";
}
// 文件指针的位置tellg/tellp (teekg是输入流(get)/teekp是输出流(put))
size_t pos = fs.tellg();
cout << "pos = " << pos << endl;
// seekg/seekp (seekg是输入流(get)/seekp是输出流(put))
fs.seekg(0); //绝对路径
//beg cur end
//ios_base::beg 文件开头
//ios_base::cur 当前指针位置
//ios_base::end 文件结尾位置
//fs.seekg(0,std::ios::beg);//相对路径
for(size_t idx = 0; idx != 5; ++idx)
{
fs >> number;
cout << number << " ";
}
fs.close();
}
四、C++
字符串IO
字符串流是以内存中的字符串类对象或者字符数组为输入输出对象的数据流,也即是将数据输出到字符串流对象或者从字符串流对象读入数据,也称之为内存流。
C++
对字符串进行操作的流类型有三个:istringstream
(字符串输入流),ostringstream
(字符串输出流), stringstream
(字符串输入输出流),他们的构造函数形式都很类似:
//字符串I/O经常用来做格式转换
//int->string 类型转化 .str()
void test()
{
int value = 100;
//字符串输出流
ostringstream oss;
oss << value;
string str1 = oss.str(); //int型转换为string
cout << "str1 = " << str1 << endl;
}
void test1()
{
int number1 = 10;
int number2 = 20;
stringstream ss;
ss << "number1= " << number1 << ",number2= " << number2 << endl;
string str1 = ss.str();
cout << str1;
string word;
int value;
while(ss >> word >> value,!ss.eof()) //对于输入流,默认以空格为分隔符
{
cout << word << "-----" << value << endl;
}
}
void test2()
{
ifstream ifs(filename);
string line;
while(getline(ifs,line))
{
string key, value;
isstringstream iss(line);
iss >> key >> value;
cout << key << " ---> " << value << endl;
}
ifs.close();
}
附:log4cpp日志系统
参考博客:log4cpp日志系统
- Layout:布局,负责设定日志的格式
- ->BasicLayout: “以时间戳 + 优先级 + 内容”
- ->SimpleLayout:“优先级 + 日志信息”
- ->PatterLayout: “使用类似 printf 的格式化模式”(推荐使用这 种)
- Appender:附加目的地,负责指定日志的目的地
- OstreamAppender //输出到一个 ostream 类
- StringQueueAppender //内存队列
- FileAppender //文件
- RollingFileAppender //回滚文件
- Category:种类,负责向日志中写入信息
- Priority:优先级,用来指定 Category 优先级和日志优先级
#include <log4cpp/SimpleLayout.hh>
#include <log4cpp/BasicLayout.hh>
#include <log4cpp/PatternLayout.hh>
#include <log4cpp/OstreamAppender.hh>
#include <log4cpp/FileAppender.hh>
#include <log4cpp/RollingFileAppender.hh>
#include <log4cpp/Category.hh>
#include <log4cpp/Priority.hh>
#include <iostream>
using std::cout;
using std::endl;
using namespace log4cpp;
void test1()
{
//设置日志格式
SimpleLayout *pSimpleLayout = new SimpleLayout();
//设置日志的目的地
OstreamAppender *pOstreamApppender = new OstreamAppender("OstreamAppender11", &cout);
pOstreamApppender->setLayout(pSimpleLayout);
//设置日志的种类
Category &root = Category::getRoot();
root.setAppender(pOstreamApppender);
/* root.setPriority(Priority::DEBUG); */
root.setPriority(Priority::INFO);
root.emerg("This is a emerg message");
root.fatal("This is a fatal message");
root.alert("This is a alert message");
root.crit("This is a crit message");
root.error("This is a error message");
root.warn("This is a warn message");
root.notice("This is a notice message");
root.info("This is a info message");
root.debug("This is a debug message");
//回收
Category::shutdown();
}
void test2()
{
//设置日志格式
PatternLayout *pPatternLayout1 = new PatternLayout();
pPatternLayout1->setConversionPattern("%d %c [%p] %m%n");
PatternLayout *pPatternLayout2 = new PatternLayout();
pPatternLayout2->setConversionPattern("%d %c [%p] %m%n");
//设置日志的目的地
OstreamAppender *pOstreamApppender = new OstreamAppender("OstreamAppender11", &cout);
pOstreamApppender->setLayout(pPatternLayout1);
FileAppender *pFileAppender = new FileAppender("fileAppender11", "test.log");
pFileAppender->setLayout(pPatternLayout2);
//设置日志的种类
Category &root = Category::getRoot();
root.addAppender(pOstreamApppender);
root.addAppender(pFileAppender);
/* root.setPriority(Priority::DEBUG); */
root.setPriority(Priority::INFO);
root.emerg("This is a emerg message");
root.fatal("This is a fatal message");
root.alert("This is a alert message");
root.crit("This is a crit message");
root.error("This is a error message");
root.warn("This is a warn message");
root.notice("This is a notice message");
root.info("This is a info message");
root.debug("This is a debug message");
//回收
Category::shutdown();
}
#if 1
void test3()
{
//设置日志格式
PatternLayout *pPatternLayout1 = new PatternLayout();
pPatternLayout1->setConversionPattern("%d %c [%p] %m%n");
PatternLayout *pPatternLayout2 = new PatternLayout();
pPatternLayout2->setConversionPattern("%d %c [%p] %m%n");
//设置日志的目的地
OstreamAppender *pOstreamApppender = new OstreamAppender("OstreamAppender11", &cout);
pOstreamApppender->setLayout(pPatternLayout1);
RollingFileAppender *pRollingFileAppender = new RollingFileAppender("fileAppender11","test.log",5 * 1024,3);
pRollingFileAppender->setLayout(pPatternLayout2);
//设置日志的种类
Category &myCategory = Category::getRoot().getInstance("myCategory");
//Category &sub1 = Category::getRoot().getInstance("sub1");
//Category &sub2 = Category::getRoot().getInstance("sub2");
myCategory.addAppender(pOstreamApppender);
myCategory.addAppender(pRollingFileAppender);
/* myCategory.setPriority(Priority::DEBUG); */
myCategory.setPriority(Priority::ERROR);
for(size_t idx = 0; idx < 300; ++idx)
{
myCategory.emerg("This is a emerg message");
myCategory.fatal("This is a fatal message");
myCategory.alert("This is a alert message");
myCategory.crit("This is a crit message");
myCategory.error("This is a error message");
myCategory.warn("This is a warn message");
myCategory.notice("This is a notice message");
myCategory.info("This is a info message");
myCategory.debug("This is a debug message");
}
//回收
Category::shutdown();
}
#endif
int main(int argc, char **argv)
{
test3();
return 0;
}
RROR);
for(size_t idx = 0; idx < 300; ++idx)
{
myCategory.emerg("This is a emerg message");
myCategory.fatal("This is a fatal message");
myCategory.alert("This is a alert message");
myCategory.crit("This is a crit message");
myCategory.error("This is a error message");
myCategory.warn("This is a warn message");
myCategory.notice("This is a notice message");
myCategory.info("This is a info message");
myCategory.debug("This is a debug message");
}
//回收
Category::shutdown();
}
#endif
int main(int argc, char **argv)
{
test3();
return 0;
}