文章目录
- C语言的输入与输出
- 流是什么?
- C++IO流
- C++标准IO流
- C++文件流
- stringstream的简单介绍
C语言的输入与输出
在C语言中,我们使用最频繁的输入输出方式为: scanf 和 printf.
- scanf : 从输入设备(键盘)读取数据,并将值存放在变量中.
- printf: 将指定的文字/字符串输出到标准输出设备(屏幕).
流是什么?
- "流"即是流动的意思,是物质从一处向另一处流动的过程,是对一种有序连续且具有方向性的数据的抽象概述.
- C++流是指信息从输入设备(如键盘)向内存输入和信息从内存向输出设备输出的过程,这种输入输出的过程被形象的比喻为"流".
流的特征为: 有序连续,具有方向性.
C++IO流
C++系统实现了一个庞大的类库,其中ios为基类,其他类都是直接或者间接继承自ios类.
C++标准IO流
C++标准库提供了4个全局流对象(cin、cout、cerr、clog).
- 使用cout进行标准输出,即数据从内存流向控制台(显示器).
- 使用cin进行标准输入,即数据通过键盘输入到程序中.
- 使用cerr进行标准错误的输出.
- 使用clog进行日志的输出.
注意:
- cin为缓冲流,键盘输入的数据保存在缓冲区中,当要提取时,是从缓冲区中提取,如果一次性输入过多,则多余的数据会保存在缓冲区中以供之后提取,如果输错了,必须在回车之前进行修改,如果回车键按下就无法修改了,只有将输入缓冲区的数据读取完之后的数据才要求输入新的数据.
int main()
{int a , b;cin >> a; //向放缓冲区中输入 1 , 2cout << a << endl;cin >> b; //直接从输入缓冲区提取2,不用认为输入cout << b << endl;return 0;
}
2.输入的数据类型必须与要提取的数据类型一致.
3.回车和空格都可以作为数据之间的分隔符,所以多个数据可以在一行输入,也可以分行输入.但是如果是字符型和字符串,则空格无法用cin输入,字符串中也不能有空格.回车键也无法读入.
int main()
{string s;cin >> s; //输入:"hello yzh"cout << s << endl; //输出:"hello"return 0;
}
对于含有空格的字符串,我们可以尝试采用getline函数进行读取,因为getline函数只有遇到’\n’才会停止读取.
int main()
{string s;getline(cin, s); //输入: "hello yzh";cout << s << endl; //输出:"hello yzhreturn 0;
}
4:对于内置类型,cin和cout可以直接输入和输出相关数据,原因:标准库已经将所有内置类型的输入和输出全部重载了,
流插入: >>
流提取: <<
5:对于自定义类型,如果要支持cin和cout的标准输入输出,需要对<<和>>进行重载.
class Date
{friend ostream& operator << (ostream& out, const Date& d);friend istream& operator >> (istream& in, Date& d);
public:Date(int year = 1, int month = 1, int day = 1):_year(year), _month(month), _day(day){}
private:int _year;int _month;int _day;
};
istream& operator >> (istream& in, Date& d)
{in >> d._year >> d._month >> d._day;return in;
}
ostream& operator << (ostream& out, const Date& d)
{out << d._year << " " << d._month << " " << d._day;return out;
}
int main()
{Date de;cin >> de; //2023 7 6cout << de; //2023 7 6return 0;
}
6: 在线OJ中的输入和输出.
- 对于IO类型的算法,一般需要循环输入.
- 输出:严格按照题目的要求进行,不能多一个空格或者少一个空格.
- 连续输入时,VS系列编译器在输入ctrl+Z时结束.
例如: C语言中的cin和C++中的scanf默认都是用空格和换行分割的.
输入格式:2023 07 06
int main()
{int year, month, day;cin >> year >> month >> day; // 2023 07 06scanf("%d%d%d", &year, &month, &day); // 2023 07 06return 0;
}
输入格式:20230706
要达到输入格式C语言可以利用sancf中规定年为4个位置,月为2个位置,日为2个位置.
int main()
{int year, month, day;scanf("%4d%2d%2d", &year, &month, &day); //输入格式:20230706return 0;
}
C++只能通过stoi函数将数字字符串按照年,月,日的格式进行分割,然后将数字字符串格式转换为整数.
int main()
{int year, month, day;string str;cin >> str;year = stoi(str.substr(0, 4));month = stoi(str. substr(4, 2));day = stoi(str.substr(6, 2));cout << year << "年" << month << "月" << day<<"日";
}
并且,C++中还可以通过while循环支持多行测试用例,当然,我们可以使用ctrl+z+换行给一个流读取结束的标志.
int main()
{int year, month, day;string str;while (cin >> str){year = stoi(str.substr(0, 4));month = stoi(str.substr(4, 2));day = stoi(str.substr(6, 2));cout << year << "年" << month << "月" << day<<"日";}return 0;
}
C++文件流
二进制读写; 在内存中如何存储,就如何写到磁盘文件上.
- 优点: 读写快.
- 缺点: 写入打文件,但是文件内容看不见.
文本读写: 序列化成字符串写出来,读回来也是字符串,
- 可以看见具体内容.
- 存在一个转换过程,读写速度慢.
C++根据文件内容的数据格式分为二进制文件和文本文件。采用文件流对象操作文件的一般步骤.
- 定义一个文件流对象
- istream ifile(只输入用)
- ofstream ofile(只输出用)
- fstream iofile(既输入又输出用)
2.使用文件流对象的成员函数打开一个磁盘文件,使的文件流对象和磁盘文件之间建立联系.( 输入或者输出).
3.利用提取和插入运算符对文件进行读写操作,或者使用成员函数进行读写.
以下为C++IO流文本读,写以及二进制读写.
struct ServerInfo
{//char _address[32]; //用于二进制读写,必须采用字符数组,不能采用string类.string _address; //用于文本读写,可以采用string类.int _port;};
class ConfigManager
{
public:ConfigManager(const char* filename = "sever.config"):_filename(filename){}//二进制写:将数据写道文件里面.void WriteBin(ServerInfo& info){ofstream ofs(_filename, ios_base::out | ios_base::binary); //以二进制覆盖写的方式打开文件.ofs.write((const char*)&info, sizeof(info)); //将目标大小的数据写入.}//二进制读,将文件里面的数据读取到内存中.void ReadBin(const ServerInfo& info){ifstream ifs(_filename, ios_base::in | ios_base::binary);//以二进制读的方式打开文件.ifs.read((char*)&info, sizeof(info)); //将目标大小的数据读取.}//文本读(输入到内存中)void ReadText1(ServerInfo& info){ifstream ifs(_filename, ios_base::in);ifs >> info._address >> info._port;}//文本写(输出到文件中)void WriteText1(ServerInfo& info){ofstream ofs(_filename, ios_base::out);ofs << info._address << endl;ofs << info._port << endl;}private:string _filename; //配置文件.};
测试案例如下:
int main() {//写入数据(二进制)ServerInfo winfo = { "127.0.0.1",888 };ConfigManager cm;cm.WriteBin(winfo);//读取数据(二进制)/*ServerInfo rinfo;ConfigManager cm;cm.ReadBin(rinfo);cout << rinfo._address << endl; //打印cout << rinfo._port << endl;*///文本写.//ServerInfo winfo = { "127.0.0.1",888};//ConfigManager cm;//cm.WriteText1(winfo);//文本读.//ServerInfo rinfo;//ConfigManager cm;//cm.ReadText1(rinfo);//cout << rinfo._address << endl;//cout << rinfo._port << endl;return 0;
}
注意:
- 使用二进制读写时,如果目标数据由string类,vector类存储,那么写入到文件的时候并不是将数组的内容写入,而是将string字符串数组的地址,capacity等写入.该地址也对应着"写入文件"时这一进程.可是,当我们将从文件读取(输入)到内存中时,会将上一进程中写入的地址读取,可是,在新的进程中,该地址是无效的,对应着野指针.
- 所以二进制读写不适合深浅拷贝的类,一般采用字符数组存储数据.
二进制读,写的自己实现
对文本读来说,我们可以调用ifstream类对象中的成员函数读取文本数据,这一过程也成为输入,步骤如下:
- 创建ifs对象并二进制以读的形式当打开文件.
- 创建输入缓冲区
- 依次调用getline函数对文本数据行读取到缓冲区中.
- 依次将缓冲区的数据放入对应的变量中.
void ReadText( ServerInfo& info){ifstream ifs(_filename, ios_base::in | ios_base::binary);char buff[128];ifs.getline(buff, 128); //1info._address = buff; //将读取的数据放在对应的变量中.ifs.getline(buff, 128); //2info._port = stoi(buff);//将读取的数据转回整型变量中.}
对于文本写来说,我们也可ofstream类对象中的成员函数将数据写入文件中,这一过程也成为输出.步骤如下:
- 创建ofs对象并二进制以写的形式当打开文件.
- 依次将目标数据写入到文件中
void WriteText(const ServerInfo& info){ofstream ofs(_filename, ios_base::out | ios_base::binary);ofs.write(info._address.c_str(), info._address.size()); //1ofs.put('\n'); //'n'分割数据,读取时方便分辨.const string str = to_string(info._port);//2ofs.write(str.c_str(), str.size());}
利用istream类对象中的get()函数读取Test.cpp文件中的数据(输入),然后再利用cout将打印出来.
int main()
{ifstream ifs("Test.cpp");char ch = ifs.get();while (ifs){cout << ch;ch = ifs.get();}return 0;
}
stringstream的简单介绍
在C++中,我们可以使用stringstream来将整型变量的数据达转换为字符串形式.主要有两种使用方式:
1.将数值数据格式转化为字符串.
int main()
{//方法一:int a = 10;string sa;stringstream s;s << a; //将int类型的a放入输入流s >> sa; //从s中抽取前面插入的int类型的值,赋值给string类型cout << sa << endl;//方法二:s.str(""); //将stringstream底层管理的string对象设置为""。s.clear(); //将上次转换状态清空掉//进行下一次转换double b = 3.14;s << b;sa = s.str(); //获取stringstream中管理的string类型(方式二)cout << sa << endl;return 0;
}
2.字符串拼接.
int main()
{//方法一:string rets;stringstream s;s << "hello" << "yzh"; //将多个字符串放入stringstream中s >> rets; cout << rets << endl;//方法二:s.str(""); //将stringstream底层管理的string对象设置为空字符串s.clear(); //将上次转换状态改变.s << "welcome" << " " << "to" << " " << "C++"; //将多个字符串放入stringstream中rets = s.str(); cout << rets << endl;return 0;
}
3.序列化和反序列化结构数据,将结构体多个内置类型数据转换为字符串类型.当然,如果自定义类型自定义写了流插入和流提取,那么便可以直接使用.
class Date
{friend ostream& operator << (ostream& out, const Date& d);friend istream& operator >> (istream& in, Date& d);
public:Date(int year = 1, int month = 1, int day = 1):_year(year), _month(month), _day(day){}
private:int _year;int _month;int _day;
};
istream& operator >> (istream& in, Date& d)
{in >> d._year >> d._month >> d._day;return in;
}
ostream& operator << (ostream& out, const Date& d)
{out << d._year << " " << d._month << " " << d._day;return out;
}
struct ChatInfo
{string _name;int _id;Date _date;string _msg;
};
int main()
{// 结构信息序列化为字符串ChatInfo winfo = { "张三", 135246, { 2022, 4, 10 }, "晚上一起看电影吧"};stringstream oss;oss << winfo._name << " " << winfo._id << " " << winfo._date << " "<< winfo._msg;string str = oss.str();cout << str << endl << endl;// 我们通过网络这个字符串发送给对象,实际开发中,信息相对更复杂,// 一般会选用Json、xml等方式进行更好的支持// 字符串解析成结构信息ChatInfo rInfo;stringstream iss(str);iss >> rInfo._name >> rInfo._id >> rInfo._date >> rInfo._msg;cout << "-------------------------------------------------------"<< endl;cout << "姓名:" << rInfo._name << "(" << rInfo._id << ") ";cout << rInfo._date << endl;cout << rInfo._name << ":>" << rInfo._msg << endl;cout << "-------------------------------------------------------"<< endl;return 0;
}
注意:
- 序列化与反序列化也可以使用ostringstream和istringstream操作,效果与用法stringstream相同.
- stringstream实际是在底层维护了一个string类型的对象用来保存结果。
- stringstream在转换结尾时(即最后一个转换后),会将其内部状态设置为badbit,因此在下一次转换前必须调用clear将状态重置为goodbit才可以转换,但clear不会将stringstream底层的string对象清空。
- 所以,我们可以使用s.str(“”)的方式将stringstream底层的string对象设置为空字符串,否则多次转换时,会将结果全部累积在底层string对象中。
- 使用stringstream转换后可以使用 >> 运算符 也可以 使用使用s.str()将让stringstream返回其底层的string对象。