文章目录
- 一、标准输入输出流
- 1.1提取符>>(赋值给)与插入符<<(输出到)
- 理解cin >> a
- 理解ifstream(读) >> a
- 例子
- 1.2get系列函数
- get与getline函数细小但又重要的区别
- 1.3获取状态信息函数(处理流错误)
- 例子:检测输入输出状态
- 例子:确保一个整型数给变量a
- 二、文件输入输出流
- 2.1文件打开
- 2.2关闭文件
- 2.3文本文件的读写
- 例子:写文本文件,把学生信息保存到文件当中
- 例子:读文本文件并显示在屏幕上
- 2.4二进制文件
- 例子:写二进制文件
- 例子:读二进制文件
- 二进制文件和普通文件的区别
- 2.5寻找输入输出流缓冲(这样就可以不用给缓冲区流向数据了)
- 2.6定位输入输出流
- 例子:先写文件再读文件(用rdbuf读)
- 三、字符串输入输出流
- 3.1 例子:反解字符串给各变量赋值
- 3.2例子:合并不同类型的数据到字符串
- 3.3例子:字符切割(类3.1)
- 3.4例子:使用stringstream进行类型转换
- 3.5综合例子:***\*【例4.15】\**** 编写一个程序,从文本文件中读入每个学生的各科成绩,并在屏幕上显示 学生各科成绩及总成绩。假设文本文件格式如表4.1所示。
- 四、总结
一、标准输入输出流
流的概念:若干个字节组成的一个字节序列,代表信息从源到目的的流动
头文件 iostream
从标准输入读取流 cin >> //把空格、换行作为分隔符(不读空格)
从标准输出写入流 cout <<
1.1提取符>>(赋值给)与插入符<<(输出到)
首先,我们的这个分标题是插入符 << , 这样一看大家是不是都懵了:cin>>a,为什么不是 >> 这个符号是插入符呢?这个不应该是写是插入吗?
请听我娓娓道来:我们将cin理解成写 是从用户的角度理解 因为我们一写 cin 我们就会从黑框中写入东西 所以我们觉得 >> 这个是写 但是cin >> a 的本质是:(注:cin在这里先当作ifstream )我们先从黑框中写入,黑框的数据拷贝给了ifsream流文件:a.txt 接着我们把 a.txt 的文件内容 >> a 流向了变量a 对于内存a来说 是写 但是对于文件a.txt 是读(图片里面的instream写错了,应该是ifstream)
理解cin >> a
粗暴点讲:我们的cin就是调用键盘然后写入,但是我们的操作符 >> 这个可是提取,提取我们键盘输入的信息
cin >> a :1.调用键盘 2.赋值
理解ifstream(读) >> a
把ifstream>>a理解成一个过程 而且这个过程正是cin>>a的第二个过程,我们探讨的数据的流向和键盘与显示器无关,而是探讨数据内存之间的流向
总的来说:
- 运算符<<常用 做输入输出流的插入符,表明“输出到”,例如 cout<<“Hello”,是把字符串“Hello”输出到屏 幕上
- 运算符>>常用做提取符,表明“赋值给”,例如:cin>>i,是把键盘输入的信息赋值给i
例子
//应用
class Stu{
string name;
int score;
public:
Stu(string n="",int s=0){name=n,score=s;} //构造
//小羊谨记:写这俩个运算符重载的时候 一定得在返回值和俩个参数都加上引用!
friend istream& operator>>(istream& in,Stu& s);
//声名友元函数 要加关键字friend friend使得外部函数可以访问
friend ostream& operator<<(ostream& out,Stu& s);
};
istream& operator>>(istream& in,Stu& s)
{
in >> s.name >> s.score;
return in;
}
ostream& operator<<(ostream& out,Stu& s)
{
out << s.name << " " << s.score;
return out;
}
void test3(){
Stu s;
cin>>s; //但是cin不理解怎么读取Stu型,所以要对cin>>进行重载(详情见上面)
cout<<s;
}
1.2get系列函数
-
get() 函数:
istream& get (char& c);
get
是istream
类的成员函数,用于从输入流中获取单个字符。get
只获取一个字符,并且不包括换行符(‘\n’)在内,它不会将换行符留在输入流中。get
函数通常用于从流中获取字符,而不是整行文本。- 语法示例:
cin.get(character);
-
getline() 函数:
getline
也是istream
类的成员函数,用于从输入流中获取一整行文本。getline
获取整行文本,包括换行符,然后将整行文本存储在字符串中。getline
可以指定一个定界符(默认为换行符’\n’),以指示何时停止读取。- 语法示例:
cin.getline(str, size);
联系和建议使用情况:
- 如果你只需要获取单个字符或者有特定需求,那么使用
get
是更合适的选择。 - 如果你需要获取整行文本,通常用于读取用户输入或从文本文件中读取一行数据,那么使用
getline
更为方便,因为它会一次性获取整行,包括换行符,不需要担心换行符的处理。
get与getline函数细小但又重要的区别
当遇到输人流中的界定符(delim, 即结束字符)时,get()停止执行,但是并不从输入流中提取界定符,直接在字符串缓冲区尾部加结束标志“\0”,从而把界定符放在缓冲区;函数 getline()则相反,它将从输入流中提取界定符,但不会把它存储到结果缓冲区中。
我们先验证get函数最后是\0:
void test()
{
unsigned char buf1[5];
cout << "请输入abcde" << endl;
cin.get((char*)buf1, 5);
cout << "get() read: " << buf1 << endl;
}
因为存放的是一个字符串,因此在9个字符之后要加入一个字符串结束标志,实际上存放到数组中的是10个字符
关于定界符缓冲区的问题,我们发现:get写在getline上面,那么回车会被getline接收,导致getline无法进行
但是如果getline写在get上面,会发现回车这个定界符被getline接收了,而不会影响到get
getline在上面:
void test()
{
unsigned char buf1[10];
unsigned char buf2[10];
cin.getline((char*)buf2, 10);
cout << "getline() read: " << buf2 << endl;
cin.get((char*)buf1, 10);
cout << "get() read: " << buf1 << endl;
}
get在上面:
void test()
{
unsigned char buf1[10];
unsigned char buf2[10];
cin.get((char*)buf1, 10);
cout << "get() read: " << buf1 << endl;
cin.getline((char*)buf2, 10);
cout << "getline() read: " << buf2 << endl;
}
1.3获取状态信息函数(处理流错误)
获取状态信息的函数如下:
int rdstate(): 无参数,返回值即是状态信息特征值。
- 0:正确状态 good()
- 1:系统错误 bad()
- 2:非法数据读入 fail()
- 4:到达文件结束,流已经读完 eof()
为什么没有3咧,是因为:在状态函数中,二进制里面只可以有一个1,所以就导致了数字3的不存在
000-0
001-1
010-2
100-4
使用下面函数来检测相应输入输出状态:
- bool good(): 若返回值 true, 一切正常,没有错误发生。
- bool bad(): 发生了(或许是物理上的)致命性错误,流将不能继续使用。
- bool fail(): 若返回值 true,表明I/O 操作失败,主要原因是非法数据(例如读取数字时遇到字母)。但流可以继续使用。
- bool eof(): 若返回值 true, 表明已到达流的末尾。
要想重置以上成员函数所检查的状态标志,你可以使用成员函数clear(),没有参数。
课本上的例子,例4.4用的是cin.rdstate(),为了避免cin.good()等操作是cin专属这个问题的争议,我这里先不用标准输出输出流,我用istream的另一个文件流来重新命名一个参数进行操作(通过替换txt中文件的值,来达到不同的效果)
例子:检测输入输出状态
void test4_4()
{
int a;
ifstream file("D:\\vs code_code\\Teacher_DiXW\\a.txt");
file >> a;
cout<<"状态值为:"<<file.rdstate()<<endl;
if(file.good())
{
cout<<" 输入数据的类型正确,无错误!"<<endl;
}
if(file.fail())
{
cout<<" 输入数据类型错误,非致命错误,可清除输入缓冲区挽回!"<<endl;
}
}
接着我们将txt文件从10替换成a,我们继续看看结果:
例子:确保一个整型数给变量a
void test4_5()
{
char BUFF[60];
int a;
while(1)
{
cin>>a;
if(cin.fail())
{
cout<<"输入有错!请重新输入"<<endl;
cin.clear(); //清空状态表示位
cin.get();
//cin.getline(BUFF,60); //清空流缓冲区
}
else
{
cout<<a<<endl;
break;
}
}
}
优化:将get换成getline,就不用有那么多报错信息了,只显示一次提示信息
cin.get() --> cin.getline(BUFF,60);
二、文件输入输出流
2.1文件打开
#include <fstream>
fstream
ofstream ifstream
在fstream类中,成员函数open()实现打开文件的操作,从而将数据流和文件进行关联,通过ofstream,ifstream,fstream对象进行对文件的读写操作
函数:open()
void open (const char* filename, ios_base::openmode mode = ios_base::in);
参数: filename 操作文件名
openmode 打开文件的方式
打开文件的方式在ios类(所以流式I/O的基类)中定义,有如下几种方式:
ios::in | 为输入(读)而打开文件 |
ios::out | 为输出(写)而打开文件 |
ios::ate | 初始位置:文件尾 |
ios::app | 所有输出附加在文件末尾 |
ios::trunc | 如果文件已存在则先清空该文件,若文件不存在则创建文件 |
ios::binary | 二进制方式打开,不做字符转换(别用string 有大bug) |
These flags can be combined with the bitwise OR operator (|
)
这些方式是能够进行组合使用的,以“或”运算(“|”)的方式:例如
ofstream out;
out.open("Hello.txt", ios::in|ios::out|ios::binary)
特别强调以下内容:
很多程序中,可能会碰到ofstream out(“Hello.txt”), ifstream in(“…”),fstream file(“…”)这样的的使用,并没有显式的去调用open()函数就进行文件的操作,直接调用了其默认的打开方式,因为在stream类的构造函数中调用了open()函数,并拥有同样的构造函数,所以在这里可以直接使用流对象进行文件的操作,默认方式如下:
ofstream out("...", ios::out);ifstream in("...", ios::in);fstream file("...", ios::in|ios::out);
当使用默认方式进行对文件的操作时,你可以使用成员函数is_open()对文件是否打开进行验证
2.2关闭文件
当文件读写操作完成之后,我们必须将文件关闭以使文件重新变为可访问的。成员函数close(),它负责将缓存中的数据排放出来并关闭文件。这个函数一旦被调用,原先的流对象就可以被用来打开其它的文件了,这个文件也就可以重新被其它的进程所访问了。为防止流对象被销毁时还联系着打开的文件,析构函数将会自动调用关闭函数close。
2.3文本文件的读写
类ofstream, ifstream 和fstream 是分别从ostream, istream 和iostream 中引申而来的。这就是为什么 fstream 的对象可以使用其父类的成员来访问数据。
一般来说,我们将使用这些类与同控制台(console)交互同样的成员函数(cin 和 cout)来进行输入输出。如下面的例题所示,我们使用重载的插入操作符<<(下面这段代码是写文件):
#include<iostream>
#include<fstream>
using namespace std;
int main ()
{
ofstream out("D:\\vs code_code\\Teacher_DiXW\\lzy.txt");
if (out)
{
out << "This is a line.\n";
out << "This is another line.\n";
}
out.close();
return 0;
}
测试结果:
从文件中读入数据也可以用与 cin>> 的使用同样的方法(下面这段代码是读文件):
#include<iostream>
#include<fstream>
using namespace std;
int main ()
{
char buffer[256];
ifstream in("D:\\vs code_code\\Teacher_DiXW\\lzy.txt");
if (in)
{
while(!in.eof())//确保访问到文件末尾
{
in.getline(buffer,100);//in >> buffer ;
cout << buffer << endl;//可以看出 想用ifstream读文件 还是得借助 cout
}
}
return 0;
}
别看小小一段代码,东西可多着呢:首先,我们文件内部的内容是俩行,这就表明如果我们不用eof判断的话,我们读完第一行就结束了;其次,如果我们不用getline,而是用 in >> buffer 那么我们遇到空格就夭折了;最后,我们想要真正的看到结果,还得需要利用cout,这是为了显示在显示器上,文件本身已经有内容了
上面的例子读入一个文本文件的内容,然后将它打印到屏幕上。注意我们使用了一个新的成员函数叫做 eof ,它是ifstream 从类 ios 中继承过来的,当到达文件末尾时返回true 。
例子:写文本文件,把学生信息保存到文件当中
#include<fstream>
#include<iostream>
using namespace std;
struct STUDENT
{
char strName[20];
int nGrade;
};
int main()
{
ofstream out;
out.open("D:\\vs code_code\Teacher_DiXW\lzy.txt");
STUDENT st1={"张三",90};
STUDENT st2={"李四",80};
out << st1.strName << "\t" << st1.nGrade << endl;
out << st2.strName << "\t" << st2.nGrade << endl;
out.close();
return 0;
}
例子:读文本文件并显示在屏幕上
#include<fstream>
#include<iostream>
using namespace std;
int main()
{
char szBuf[80];
ifstream in("D:\\vs code_code\\Teacher_DiXW\\lzy.txt");
if(in)
{
while(in.getline(szBuf,80))
{
cout<<szBuf<<endl;
}
}
in.close();
return 0;
}
很明显,这个利用getline当作while循环,比上面的eof使用起来更加优秀,而且为什么getline这么写呢?
cin.getline(buff,size);这个的意思就是读取我们键盘给cin的值
那么in.getline(buff,size);这个意思正是将txt文件当中的内容利用getline流向buff
2.4二进制文件
在二进制文件中,使用<< 和>>,以及函数(如getline)来操作符输入和输出数据,没有什么实际意义,虽然它们是符合语法的。
文件流包括两个为顺序读写数据特殊设计的成员函数:write 和 read。第一个函数 (write) 是ostream 的一个成员函数,都是被ofstream所继承。而read 是istream 的一个成员函数,被ifstream 所继承。类 fstream 的对象同时拥有这两个函数。它们的原型是:
write ( char* buffer, streamsize size );
read ( char* buffer, streamsize size );
这里 buffer 是一块内存的地址,用来存储或读出数据。参数size 是一个整数值,表示要从缓存(buffer)中读出或写入的字符数。
例子:写二进制文件
void test_binary_write()
{
struct Worker w[3]={"zhang",2500,20,"lisi",4500,22,"wang",2500,23};
ofstream out("D:\\vs code_code\\Teacher_DiXW\\lzy.txt",ios::binary);//后缀表明是二进制文件
for(int i=0;i<3;i++)
{
//out << w[i]; 这么写就成了标准输入输出流了
out.write((char *) &w[i],sizeof(Worker)); //(要写的数据首地址,要写数据的大小)
}
out.close();
}
out.write() 函数用于将指定的二进制数据写入到文件流中。第一个参数是指向要写入的数据的指针,该指针通常需要是 char* 类型,因为 write() 函数按字节处理数据,而 char 类型正好是一个字节。在这里,将 &w[i] 强制转换为 char* 类型,这样就可以将 Worker 类型的数据按字节写入到文件流中。把0X62fd60等变成一个字节的,才可以给write使用!!
例子:读二进制文件
void test_binary_read()
{
//读文件
ifstream in("D:\\vs code_code\\Teacher_DiXW\\lzy.txt",ios::binary);
Worker temp;
for(int i=0;i<3;i++)
{
in.read((char*)&temp,sizeof(Worker));
cout << temp.name <<" "<<temp.wage <<" "<<temp.age <<endl;//放在显示器上
}
in.close();
}
将in中的数据内存拷贝给temp,并且每拷贝一个输出一次,循环三次就可以得到我们之前写入文件的全部内容!
二进制文件和普通文件的区别
文本文件与二进制文件的区别_二进制文件和文本文件的区别-CSDN博客
可以看看这篇博客,讲的比较清楚
2.5寻找输入输出流缓冲(这样就可以不用给缓冲区流向数据了)
C++ 标准库封装了一个缓冲区类 streambuf, 以供输入输出流对象使用。每个标准 C++ 输入输出流对象都包含一个指向streambuf 的指针,用户可以通过调用rdbuf() 成员函数获得该指针,从而直接访问底层streambuf 对象;可以直接对底层缓冲区进行数据读写,从而跳过上层的格式化输入输出操作。但由于类似的功能均可由上层缓冲区类实现,因 此就不再加以论述了。streambuf 最精彩的部分在于它重载了 operator<< 及 operator>>。这个缓存(buffer)实际是一块内存空间,作为流(stream)和物理文件的媒介。例如,对于一个输出流, 每次成员函数put(写一个单个字符)被调用,这个字符不是直接被写入该输出流所对应的物理文件中的,而是首先被插入到该流的缓存(buffer)中。
对 operator<<来说,它以 streambuf 指针为参数,实现把 streambuf对象中的所有字符输 出到输出流中;对 operator>>来说,可把输入流对象中的所有字符输入到 streambuf 对象中。
换句话说,我们可以不需要定义变量从而将内容流向对象再进行输出,我们可以直接输出文件内容
void test_read()
{
ifstream in("D:\\vs code_code\\Teacher_DiXW\\lzy.txt");
if(in)
{
cout << in.rdbuf() < <endl;
}
in.close();
}
2.6定位输入输出流
俩个定位函数:
istream&seekg(long relativepos,ios::seek_dir dir)
针对输入流。第一个参数是要移动的字符数目,可正可负;第二个参数是移动方向,是 ios::begin、ios::cur、ios::end 中的一个值。含义是:字符指针相对于移动方向向前或向后 移动了多少个字符。(如果用beg和end的话,第一个参数一般给0就好了)
ostream&seekp(long relativepos,ios::seek_dir dir)
针对输出流。含义同上
流的三个位置标识符:
ios::beg | 流开始位置 |
ios::cur | 流指针当前位置 |
ios::end | 流结束位置 |
例子:先写文件再读文件(用rdbuf读)
#include<fstream>
#include<iostream>
using namespace std;
int main()
{
string a("hello");
fstream in_out;
in_out.open("D:\\vs code_code\\Teacher_DiXW\\lzy.txt",ios::in | ios::out | ios::trunc);//trunc保证了没有该文件,则自动创建该文件
in_out << a;
//in_out.write("Hello",5);
in_out.seekg(0,ios::beg); //读指针移到文件头
cout<<in_out.rdbuf();
in_out.close();
return 0;
}
代码分析:
- 正常来说,我们读文件即使用rdbuf读,也得起码用ifstream创建对象,但是如果我们掌握了更改文件指针的定位方法,我们甚至可以写完直接用seekg修改位置输出
- 采用了 fstream 输入输出流类,既可读又可写
- 注意open 打开标志 ios::in | ios!:out | ios::trunc, 特别是ios::trunc, 它保证了若没有该文件,则自动创建该文件。
- 文件打开或创建成功后,文件指针均指向文件头,当写完字符串“Hello” 后,文件指针已经偏移了,若想完全显示全部文件内容,必须把指针移到文件头。文中用到了 seekg 函 数,其实用 seekp 函数也是等效的,这是因为fstream 是输入输出流。若是单独的输入流,则 只能用seekg 函数;若是单独的输出流,则只能用 seekp 函数。
- 所以在这个代码中一定不可以在写完后就关闭文件,那么就无法修改文件指针了
- 同时我们也认识到为什么读文件可以读到全部内容,正是因为文件打开或创建成功后,指向文件头,这个知识点是我之前不懂的
我们还可以将seekg用cur重新写:
in_out.seekg(-5,ios::cur); //读指针移到文件头
效果出来是如出一辙的~
三、字符串输入输出流
字符串输入输出流类直接对内存而不是对文件和标准输出进行操作,它使用与 cin 及 cout 相同的读取和格式化函数来操纵内存中的数据,所有字符串流类的声明都包含在标准头文件 中
- istringstream: 字符串输入流,提供读 string 功能。
- ostringstream: 字符串输出流,提供写 string 功能。
- stringstream: 字符串输入输出流,提供读写 string 功能。
利用字符串输入输出流,可以方便地把多种基本数据类型组合成字符串,也可以反解字符串给各种变量赋值。
3.1 例子:反解字符串给各变量赋值
void test()
{
int n;
float f;
string str;
string strText="13.14 hello";
istringstream s(strText);
s >> n;//n是int类型
s >> f;//f是float类型
s >> str;//str是字符串类型
cout << "n=" << n << endl;
cout << "f=" << f << endl;
cout << "str=" << str << endl;
}
代码分析:
通过字符串输入流 istringstream 读取字符串“13.14 hello”给了s,接着依次赋给整型、浮点型、字符串变量
3.2例子:合并不同类型的数据到字符串
void test()
{
cout<<"输入一个int一个float一个string:";
int i;
float f;
string stuff;
cin >> i;//输入整形
cin >> f;//输入float形
getline(cin,stuff);//输入字符串形
ostringstream os;
os << "integer=" << i << endl;
os << "float=" << f << endl;
os << "string=" << stuff << endl;
string result=os.str();
cout << result << endl;
}
代码分析:
首先分别输入三个变量,并且定义一个 ostringstream 字符串流插入流 使得数据全部流向os 接着利用这俩段代码:
string result=os.str();
cout << result << endl;
很明显,字符串流的读和文件是不同的
代码测试结果:
3.3例子:字符切割(类3.1)
void test()
{
//2.用的多的情况 是用来做类型转换 或者 字符切割
//to_string 数字转换成字符串
//cout << to_string(1234) << endl;//此时1234是字符串
istringstream ip("192.168.1.1");//读取
//要求:拆分ip地址每个数字
int ipNum[4];//由于这个是int类型的 所以我们在输入的时候无法输入.字符
for(int i=0;i<4;++i)
{
char get_char;
ip >> ipNum[i];//ip >> ipnum[1] >> ipnum[2] >> ipnum[3] >> ipnum[4];
if(i < 3)//一共三个点
{
ip >> get_char;//获取字符
}
}
for(int i=0;i<4;++i)
{
cout << ipNum[i] << " ";
}cout << endl;
}
代码分析:
int ipNum[4];//由于这个是int类型的 所以我们在输入的时候无法输入.字符
if(i < 3)//一共三个点
{
ip >> get_char;//获取字符
}
如果不接收的话,就会使得.无法接收,无法继续进行读取
3.4例子:使用stringstream进行类型转换
void test()
{
stringstream data("");
int num=12345;
data << num;//把数据放到流里面 num 给了 data
char result[20]="";
data >> result;//data给了result
cout << result;//输出result
}