文章目录
- 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 , 2
cout << 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 yzh
return 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 6
cout << de; //2023 7 6
return 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 06
scanf("%d%d%d", &year, &month, &day); // 2023 07 06
return 0;
}
输入格式:20230706
要达到输入格式C语言可以利用sancf中规定年为4个位置,月为2个位置,日为2个位置.
int main()
{
int year, month, day;
scanf("%4d%2d%2d", &year, &month, &day); //输入格式:20230706
return 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); //1
info._address = buff; //将读取的数据放在对应的变量中.
ifs.getline(buff, 128); //2
info._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()); //1
ofs.put('\n'); //'n'分割数据,读取时方便分辨.
const string str = to_string(info._port);//2
ofs.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对象。