目录
C++的IO流
在C++中,输入数据和输出数据用的是cin >> 和cout <<,cin的>>表示设备的数据流入对应变量,cout的<<表示对应变量数据流出到设备,因此这种输入输出的过程被形象的比喻为"流"。
istream/ostream
cin 是标准库中定义的istream类型的全局对象 ;cout 是标准库中定义的ostream类型的全局对象
iostream继承了istream和ostream,因此用iostream实例化的对象可以输入+输出

它们调用的>>、<<本质上是调用的operator>>() 与**operator<<()**方法
cpp
int x;
cin >> x;
cin.operator>>(x);//与上面等价
cout << x;
cout.operator<<(x);//与上面等价
cout和cin不需要向C语言的scanf和printf一样手动指定类型,是因为cin的>>,cout的<<对每个内置类型都做了重载
cpp
int x;
while(cin >> x)
{
//
}
对于上面代码,可以保证有数据时一直循环,当输入Ctrl + Z (EOF标志)时再跳出循环,这本质是得益于istream类重载的operator bool 方法,允许流对象在布尔上下文中自动转换为bool值(istream的>>重载原本返回的是istream&类型)

ifstream/ofstream
ifstream 和ofstream 是C++中用于文件IO流的两个类,前者仅可以读取,后者仅可以写入 ,若想读取+写入可以用 fstream
cpp
//模式默认是out,即以输出模式打开
ofstream ofs("text.log"/*,ios::out*/);//fopen("text.log","w");
ofs << "eee";//向文件中写入
ofs.close();//关闭文件
//模式默认是in,即以输入模式打开
string str;
ifstream ifs("text.log"/*,ios::in*/);//fopen("text.log",r);
ifs >> str;//读取到str中
cout << str;
若不在构造函数中指定文件,也可以后续用**open()**方法
cpp
ofs.open("text.log"/*,ios::out*/);
ifs.open("text.log"/*,ios::in*/);
需要注意的是,ofstream的<<只会写入文本 ,例如ofs << 123,写入的也是1,2,3的ASCII码
而ofstream的write() 方法可以写入二进制码,例如ofs.write(123),写入的是4字节二进制值 :
0x7B 0x00 0x00 0x00(小端序)
ifstream的>>也只会读取文本 ,若写入时是以二进制写入,读取也需要以二进制读取,可以用 read() 方法,但要用read读取就必须在打开文件时指定模式ios::binary ,表示以二进制方式打开。
ifstream/ofstream的>>/<<的优点就是,++在想以字符串格式写入数字型数据时,不需要先把数字转换成字符串(例如tostring())++
cpp
info student("张三",20);
ofstream ofs("text.log");
ofs << student._name << endl;
ofs << student._age << endl;//不需要转成字符串再传入
ofs.close();
info s;
ifstream ifs("text.log");
ifs >> s._name;
ifs >> s._age;
cout << s._name << ' ' << s._age << endl;
ps: fstream继承自iostream,而iostream又继承了ifstream和ofstream,因此fstream实例化的对象可读可写
istringstream/ostringstream
istringstream/ostringstream类用于将整型数据转换为字符串(例如itoa(),tostring()等)或将字符串转换为对应数据类型,前者可以从字符串中读取数据(输入流) ,常用于字符串解析和类型转换 ,后者用于向字符串写入数据 (输出流),常用于格式化拼接和类型转换
若要将不同类型的数据都转为字符串,就可以用ostringstream向字符串 写入数据
若要将字符串再分割为基本类型,就可以用istringstream
cpp
//序列化(转换为字符串)
info student("张三",20);
ostringstream ost;
ost << student._name << endl;
ost << student._age << endl;
//反序列化(从字符串转换为数据)
info st;
istringstream ist;
ist.str(ost.str());
ist >> st._name >> st._age;
cout << st._name << ' ' << st._age << '\n';
ps: stringstream继承自iostream,而iostream又继承了istringstream和ostringstream,因此stringstream是支持读写的双向流,可解析可拼接
该对象常用于网络字符串拼接与解析
空间配置器
STL的空间配置器简单来说是内存池 ,负责在容器申请内存时分出内存,这避免了频繁申请内存导致的效率开销问题 。空间配置器本质也是以空间换时间 的策略,因为池化技术的缺点就是不用时也会占用资源。空间配置器分为一级空间配置器二级空间配置器
一级空间配置器
一级空间配置器就是malloc和free的封装 ,并处理失败抛异常机制。
在malloc开辟失败时,会先检测使用者有无设置失败的处理函数(一个函数指针),如果有就执行,否则抛异常。默认情况下没有设置该失败的函数指针句柄,即跟operator new基本一样,失败抛异常
要申请超过128字节 的内存时,才会使用一级空间配置器,若在128字节以内,会用二级空间配置器
二级空间配置器
二级空间配置器即为内存池

当容器申请内存时,内存池就分给容器对应字节的内存。但当容器用完内存时,不能单独释放这一小块内存 ,当时申请的多大内存就要释放多大内存,为了管理这些用完的内存,二级空间配置器加入了哈希桶(开散列)
索引以8字节为间隔 ,到128字节 ,当容器将内存还回来时,就会根据大小选择挂在哪个索引下面,当后续有容器想要申请内存时,就会先从哈希桶内查找有无匹配的内存

若容器申请的内存在哈希桶中没有,从内存池中申请时,也不会只切出对应字节的内存,而是直接++切出20个该对象内存,返回一个,剩下19个挂在哈希桶下面。++
由于容器申请的内存一般都为小块内存,二级空间配置器就是专门为了解决申请小块内存而出现的,因此这样做可以通过批量预分配显著减少系统调用频率,从而在频繁申请小块内存的场景下提升性能
一个进程中有一个空间配置器,进程中所有的容器需要内存,都会找空间配置器
内存碎片问题
内碎片
在二级空间配置器中,如果申请的不是8字节的整数倍,也会向上内存对齐 到8字节整数倍,而这样就会导致用于对齐的字节用不上,这就是内碎片问题
外碎片
当在堆上开辟了多个小块内存后,若后续其中的几块内存换回来,也有可能不连续

紫色×表示已释放,若此时再申请48字节内存,即使我们之前释放的空间也有48字节,但不是连续的 ,就不能用这两块空间申请,这就是外碎片问题,因此对于STL容器(常申请小块内存),就有了空间配置器来管理内存
内核中针对大量小块内存申请的碎片化问题,会使用slab分配器解决,它的结构类似于二级空间配置器
既然内核已经有slab分配器管理小块内存,为什么STL还需要二级空间配置器?
- 内核是针对整个系统的所有程序的,并且每个都去堆申请,消耗特别大
- STL的容器需要的全是小块内存,而且需求大小集中,因此自己设计一个自己用会效率更高,顺便解决内存碎片问题(解决了外碎片,但有内碎片)