目录
1、c语言的输入与输出
c语言中,我们用到的最频繁的输入输出方式是scanf与printf。
**scanf:**从标准输入设备(键盘)读取数据,并将值存放在变量中。
**printf:**将指定的文字/字符串输出到标准输出设备(屏幕、终端)
其中要注意宽度输出和精度输出控制。
设备A将数据输入缓存区,编译器根据代码从缓存区接受数据,之后处理数据,输出结果输出到缓冲区,结果从缓存区输出到设备A上。
缓存区:
1、可以屏蔽掉低级I/O的实现,低级I/O的实现依赖操作系统本身本身内核的实现,所以如果能够屏蔽掉这部分差异,就可以写出可移植的程序。
2、可以使用这部分的的内容实现"行"读取的行为。因为计算机没有行的概念,所以把缓冲区的内容通过标识符等分割实现行,这样读取的时候可以读入一行、输出一行。
2、流是什么
流:即流动的意思,是物质从一处向另一处流动的过程,是对一种有序连续且具有方向性的数据(单位:bit,byte,packet)的抽象描述
c++流是指信息从外部输入设备(如键盘)向计算机内部(如内存)输入和从内存向外部输出设备(显示器)输出的过程。这种输入输出的过程被形象的比喻为"流"。
特点:有序连续、具有方向性。
为了实现这种流动,c++定义了I/O标准类库,这些类都成为流/流类,用以完成某方面的功能、
3.C++IO流
c++实现了很庞大的类库,ios是基类。其他类都是直接或间接派生自ios类。
3.1标准IO流
c++标准库提供了4个全局流对象cin、cout、cerr、clog。
cout:标准输出,即数据从内存流向控制台(显示器)。
cin:标准输入即数据通过键盘输入到程序中。
cerr:标准错误的输出。
clog:进行日志的输出。
cout、cerr、clog基本没区别,只是应用场景不同。
在使用的时候,必须包含文件并引入std标准命名空间。
include<iostream>
using namespace std;
**1、cin为缓冲流。**键盘输入的数据存在缓冲区,当要提取时,是从缓冲区拿。
如果一次输入多了,会留在那等待下一次提取,如果输入错了,必须在回车前修改,如果回车按下就无法挽回,只有输入缓冲区中的数据取完后,才要求输入新的数据。
**2、输入的数据类型必须与要提取的数据类型一致,否则出错。**出错只是在流的状态字state中队员位置位(置1),程序继续。
3、空格和回车都可以作为数据之间的分格符 ,所以多个数据可以在一行输入,也可以分行输入。但如果是字符型和字符串,则空格(ascll为32),无法用cin输入 ,字符串中也不能有空格。回车符也无法读入。
**4、cin和cout可以直接输入和输出内置类型数据,**原因:标准库已经将所有内置类型的输入和输出全部重载,一些库自带的容器也已经做了重载,比如string
cin:
cout:
5.对于自定义类型(非官方库的容器),如果要支持cin和cout,需要重载<<和>>,具体可看我《类与对象》的文章。
6、在线oj的输入
cppwhile(cin>>n>>m) { .... }数据是多行的输入,那么我们自己测试的时候,怎么结束这个循坏,ctrl+c(是很暴力,直接把进程干掉了),一般是ctrl+z+换行。
可以看到,string是有重载>>的,返回的是cin的引用,那么怎么让cin的引用转换成bool呢,就如我《类型转换》的文章中,c++是有自定义类型转内置类型的方法,而库里也是有实现的。
cin在读取的时候并返回的时候,因为while的条件类型是bool,所以cin会进行隐式类型转换,当读取到特定错误字符(接受流失败)或者读取到文件末尾(结束符)的时候,就会转换为false,其他情况都是true。
通过这种方式,在线oj会直接让程序读取文件,读到文件末尾,自然就结束了。
而我们自己测试的时候,ctrl+z本身也是特定字符,会返回false。
下面这份是基于我类与对象的那个日期类的基础上再加了个operator bool
cppclass 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) {} operator bool() { if (_year == 0) return false; else return true; } 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; } // C++ IO流,使用面向对象+运算符重载的方式 // 能更好的兼容自定义类型,流插入和流提取 int main() { // 自动识别类型的本质--函数重载 // 内置类型可以直接使用--因为库里面ostream类型已经实现了 int i = 1; double j = 2.2; cout << i << endl; cout << j << endl; // 自定义类型则需要我们自己重载<< 和 >> Date d(2022, 4, 10); cout << d; while (d) { cin >> d; cout << d; } return 0; }还有一种情况,cin和cout的速度不够快
cppios::sync_with_stdio(0); cin.tie(0);cout.tie(0); c++因为要兼容c和c++,所以cin和cout都要考虑c和c++的缓冲区(保证混着用的时候输入输出是正常顺序),但c只需要考虑c的缓冲区。 所以cin和cout的速度就比scanf和printf慢很多。 ios::sync_with_stdio(0);就是直接关闭了cin和cout兼容c的机制,让他们只考虑c++自己的缓冲区。 这样的话速度可以跟scanf和printf差不多。 cin.tie(0);就是让cin和cout不再绑定但注意,这样写了之后就不要混用c++和c的输入输出
3.2c++文件IO流
c++根据文件内容的数据格式分为二进制文件和文本文件。采用文件流操作文件的一般步骤:
1、定义一个文件流对象(ifstream输入、ofstream输出、fstream输入输出,这3个都继承自iostream、istream、ostream)
2、使用文件流对象的成员函数打开一个磁盘文件,使得文件流对象和磁盘文件之间建立联系。
3、使用提取和插入运算符对文件进行读写操作(cin、cout),或使用成员函数读写
4、关闭文件。
头文件<fstream>
正常使用
关于成员函数,这里不多说,可以直接去文档里查。
cppifstream isf; isf.open("te2.cpp"); char x; while (isf.get(x)) { cout << x; }get也是返回流对象,也有重载operator bool,也可以做到隐式转换成bool类型。
下面是比较简洁的写法。
cppifstream isf("te2.cpp"); //流对象的析构会自动close,如果要提前关闭,再isf.close(); char x; while (isf>>x)//流对象也有重载>>,内置类型可以直接输入,自定义类型再自己重载。 { cout << x; }但这个写法,>>是不接受空格和回车的。
所以如果格式要求严格的话,还是推荐get。
getline读一行。read读n个字符。
cppifstream isf("te2.cpp",ios::in | ios::binary);//以二进制的方式读入 //默认是文本类型 //这个写法,本质就是提前设置了值,不同的值对应不同的读写方式,通过或的方式,形成不同的值传入tie的问题
第一种形式(1)返回一个指向被绑定输出流的指针。
第二种形式(2)将该对象绑定到tiestr,并返回在调用之前被绑定的流的指针(如果有的话)。
被绑定的流是在此流对象中的每次输入/输出操作之前都会被刷新的输出流对象
cpp#include <iostream> // std::ostream, std::cout, std::cin #include <fstream> // std::ofstream int main () { std::ostream *prevstr; std::ofstream ofs; ofs.open ("test.txt"); std::cout << "tie example:\n";//这段话是输出到cout的缓冲区的 *std::cin.tie() << "This is inserted into cout"; //这段话也是cout的缓冲区。 // 因为cin默认是绑定到cout的,tie()返回被绑定的流对象的指针,所以这里返回是cout的指针 //所以这里输出在cout的缓冲区。 prevstr = std::cin.tie (&ofs); //因为ofstream是ostream的派生类,所以可以直接派生类对象的地址给父类对象的指针 //因为tie(有参数)返回是调用前被绑定的流对象的指针,所以这里返回是cout的指针 //此时cin绑定到了ofs *std::cin.tie() << "This is inserted into the file"; //所以此时的tie()返回的是ofs的指针,解引用后,因为ofstream有重载<<,所以直接输出到text.txt文件中了。 std::cin.tie (prevstr); //此时又重新绑定回了cout。 ofs.close(); std::cin.tie(0);//这个就是绑定了空指针。 return 0; }典型的二进制读写和文本读写
cpp#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<ll, ll> pll; typedef pair<pll, ll> ppl; const ll N = 2001, M = 2 * 1e5 + 1; ll n, m; class ps { friend ostream& operator <<(ostream& out, const ps& s); friend istream& operator >>(istream& in, ps& s); public: ps(int _p1=1,int _p2=2,int _p3=3) :p1(_p1) ,p2(_p2) ,p3(_p3) {} private: int p1, p2, p3; }; ostream& operator <<(ostream& out, const ps& s) { out << s.p1 << " " << s.p2 << " " << s.p3; return out; } istream& operator >>(istream& in,ps& s) { in >> s.p1 >> s.p2 >> s.p3; return in; }//如果是自定义类型,要记得为其重载>>和<< struct pt { //string s;不能用string,因为string内部针对字符串,短的存数组,长的会在堆上开辟空间, // 这样的话二进制读写的时候,如果字符串长的话,读写的内容只是string内部存字符串的指针,而进程结束后,旧指针所指向的空间是被释放的。 //这样就算二次读写进来,解引用该指针,访问的是野指针,就会报错。 //因此涉及到深拷贝的,不能进行二进制读取 char a1[32]; int s; ps da; }; class ALLRW { public: ALLRW(const char* filename) :_filename(filename) {} //二进制写出到文件,本质就是强行将自定义类型的指针强转成const char*,然后以一个字节一个字节写出。 void writebinary(const pt& ob) { ofstream ofs(_filename, ios::out | ios::binary); ofs.write((const char*)&ob, sizeof(ob)); } //二进制文件写入到程序,本质就是强行将自定义类型的指针强转成char*,然后以一个字节一个字节写入。 void readbinary( pt& ob) { ifstream ifs(_filename, ios::in | ios::binary); ifs.read((char*)&ob, sizeof(ob)); } //从程序写出到文本文件 void writetext(const pt& ob) { ofstream ofs(_filename, ios::out); ofs << ob.a1 << endl;//记得加换行,因为>>不读空格和换行,否则的话txt文件里内容是挤在一起的,第一个>>会全部读完,第二个>>只能读个随机值 ofs << ob.s << endl; ofs << ob.da << endl; } //从文本文件写入到程序 void readtext( pt& ob) { ifstream ifs(_filename, ios::in); ifs >> ob.a1 >> ob.s >> ob.da; } private: string _filename; }; int main() { ALLRW v("test.txt"); pt x = { "2222222",2,{2,3,4} }; v.writetext(x); pt x2; v.readtext(x2); cout << x2.a1 << " " << x2.s << " " <<x2.da<< endl; return 0; }另外,c++io流还提供了个一些标志位,用于表示各种流操作的状态。
如果发生了fail错误,这种错误一般是可以重新来的。比如
cppcin>>i; //发生fail错误 cin.clear();//清除标志位,一定要先清标志位,因为流操作前会先检查标志位。 cin.get();//注意,必须先读一个,因为之前fail错误,导致输入缓冲区的内容没有被读走 //如果此时直接cin>>i,会出现不等我们输入,又重新试图读缓冲区的内容 //但是因为缓冲区的内容是不合法的,必然又导致了fail错误。 //所以先读光缓冲区,再输入 cin>>i;
4.stringstream的简单介绍
在C语言中,如果想要将一个整形变量的数据转化为字符串格式,如何去做?
1.使用itoa()函数
2.使用sprintf()函数
但是两个函数在转化时,都得需要先给出保存结果的空间,那空间要给多大呢,就不太好界定,而且转化格式不匹配时,可能还会得到错误的结果甚至程序崩溃。
cppint main() { int n = 123456789; char s1[32]; _itoa(n, s1, 10); char s2[32]; sprintf(s2, "%d", n); char s3[32]; sprintf(s3, "%f", n); return 0; }在C++中,可以使用stringstream类对象来避开此问题。
在程序中如果想要使用stringstream,必须要包含头文件。在该头文件下,标准库三个类:istringstream、ostringstream 和 stringstream,分别用来进行流的输入、输出和输入输出操作。
stringstream的作用如下
4.1.将数值类型数据格式化为字符串
cpp#include <iostream> #include <sstream> using namespace std; int main() { int a = 12345678; string sa; // 将整型变量转换为字符串 stringstream s; s << a; s >> sa; // 清除状态和内容,为下一次转换做准备 s.str(""); // 清空底层字符串 s.clear(); // 清除错误状态 // 将双精度浮点数转换为字符串 double d = 12.34; s << d; s >> sa; // 获取stringstream中管理的字符串 string svalue = s.str(); cout << svalue << endl; return 0; }w
4.2.字符串拼接
cpp#include <iostream> #include <sstream> using namespace std; int main() { stringstream sstream; // 将多个字符串放入 sstream 中 sstream << "first" << " " << "string,"; sstream << " second string"; cout << "strResult is: " << sstream.str() << endl; // 清空 sstream sstream.str(""); sstream << "third string"; cout << "After clear, strResult is: " << sstream.str() << endl; return 0; }w
4.3.序列化和反序列化结构数据
cppstruct ChatInfo { string _name; //名字 int _id; // id Date _date; // 时间 string _msg; //聊天信息 }; int main() { //结构信息序列化为字符串 ChatInfo winfo = {"张三", 135246, {2022,4,10}, "晚上一起看电影吧"}; ostringstream oss; oss << winfo._name << " " << winfo._id << " " << winfo._date << " " << winfo._msg; string str = oss.str(); cout << str << endl << endl; // 我们通过网络这个字符串发送给对象,实际开发中,信息相对更复杂, // 一般会选用Json、xml等方式进行更好的支持 // 字符串解析成结构信息 ChatInfo rInfo; istringstream 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; }w
1.stringstream实际是在其底层维护了一个string类型的对象用来保存结果。2.多次数据类型转化时,一定要用clear()来清空,才能正确转化,但clear()不会将
stringstream底层的string对象清空。
3.可以使用s.str("")方法将底层string对象设置为"空字符串。
4.可以使用s.str()让stringstream返回其底层的string对象。
5.stringstream使用string类对象代替字符数组,可以避免缓冲区溢出的危险,而且其会对参数类型进行推演,不需要格式化控制,也不会出现格式化失败的风险,因此使用更方便,更安全。










