本章讲简单的介绍C++的IO流和空间配置器,并学习一下C++STL库中空间配置器的底层结构,本章只是简单的了解学习,并不要求深入学习……
在我们刚学C语言的时候,我们就知道了输入和输出分别是scanf
和printf
。
C语言是格式化的输入和输出:
所以我们在用C语言输入和输出的时候,要指定格式,例如以整形的格式输入读取,以整形的格式输出,以字符串的形式输入输出……
概念:
流: 即是流动的意思,是物质从一处向另一处流动的过程,是对一种有序连续且具有方向性的数据( 其单位可以是bit,byte,packet )的抽象描述。
C++的流:
流的特性:
为了实现这种流动,C++定义了I/O标准类库,这些每个类都称为流/流类,用以完成某方面的功能。
C++库中标准IO流:
既然有继承,那么这里就可以玩多态……
C++标准库提供了4个全局流对象cin、cout、cerr、clog,使用cout进行标准输出,即数据从内存流向控制台(显示器)。
int main()
{
cout << "hello" << endl;
cerr << "hello" << endl;
clog << "hello" << endl;
return 0;
}
这里结果都是hello。
补充:
使用cin进行标准输入即数据通过键盘输入到程序中,同时C++标准库还提供了cerr用来进行标准错误的输出,以及clog进行日志的输出,从上图可以看出,cout、cerr、clog是ostream类的三个不同的对象,因此这三个对象现在基本没有区别,只是应用场景不同,在使用时候必须要包含文件并引入std标准命名空间。
cin和cout可以直接输入和输出内置类型数据,原因:标准库已经将所有内置类型的输入和输出全部重载了。
istream重载:
在我们之前的学习中,我们知道:括号()这个运算符,可以做函数调用的参数列表,也可以是控制优先级的,也可以是强制类型转换。
如果一个类要强转成其他类型,而我们之前学习的仿函数已经将括号()给重载了,所以我们要是想重载强制类型转换,就不能再用operator ()
了,C++只能退而求其次搞了一种新的玩法。
见下述代码:
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)
{}
//支持Date对象转换成bool
//重载运算符,但是怎么重载类型了呢?
//这里支持的就是将自定义类型转换成内置类型
operator bool()
{
//这里是随意写的,假设输入_year为0,则结束
if (_year < 1)
return false;
else
return true;
}
//转换成整形
operator int()
{
return _year + _month + _day;
}
//括号()这个运算符,可以做函数调用的参数列表,也可以是控制优先级的,也可以是强制类型转换
//仿函数已经用了operator()了,那么强制类型转换就不能用operator()转换了
private:
int _year;
int _month;
int _day;
};
注意:普通的operator后面接的都是运算符,而这里operator后面接的却是一个类型。
int main()
{
Date d1 = -1;
Date d2 = { 2023, 3, 23 };
//自定义类型转换成内置类型
bool ret1 = d1;
bool ret2 = d2;
//反过来转
int i1 = d1;
int i2 = d2;
cout << ret1 << endl;
cout << ret2 << endl;
//支持转成bool,本质是调用了重载函数
if (d1)
{
}
return 0;
}
这里调用和运算符重载时一样,直接调用对应的operator 类型,这里就实现了自定义类型转成内置类型。
就可以实现,内置类型的对象也能放在if
或者while
的判断中进行逻辑判断。
在我们之前的刷题中,一定遇到过多组输入的测试题目,C语言通常采用如下的方式:
我们知道scanf
函数的返回值是一个整形,返回的是读取成功数据的个数,读取失败或者是读完之后会返回回个EOF
。
EOF: 是end of file
的缩写,其值是-1,-1的补码全是1,取反则全是0,这就是第二种判断的原理
而C++中则是以下面的这种方式来写:
而我们之前在学习日期类中自己实现对日期类的输入和输出时,我们知道,ci
对象重载之后返回的还是一个cin
的对象。
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;
}
cin
对象的内部肯定是支持重载成内置类型,所以可以将cin >> str
,这种写法。
两段程序仅供参考,这里了解性学习即可,毕竟C语言中我们详细学了文件的操作,到现在也没怎么用过嘛~
int main()
{
//是读文件
//in是读取,out才是写文件
ifstream ifs("text.cpp");
//重载了operator bool
//正常读取返回true,读取失败或者读到文件结尾了就返回false
//while (ifs)
//{
// //一个字符一个字符的读,读出来之后打印
// char ch = ifs.get();
// cout << ch;
//}
//自动把空格忽略掉了
char ch;
while (ifs >> ch)
{
cout << ch;
}
return 0;
}
第一个循环可以直接将文件原封不动的读完,第二个循环则是会自动忽略掉空格~
从一个文件读到另一个文件当中:
int main()
{
ifstream ifs("text.cpp");
ofstream ofs("Copy.cpp");
char ch;
ch = ifs.get();
//while (ifs)
while (~ch)
{
//插入到文件当中去
ofs << ch;
cout << ch;
ch = ifs.get();
}
return 0;
}
序列化和反序列化:
在C语言中,我们若是想要将一个整型变量的数据转化为字符串格式,有以下方法:
int main()
{
int a = 10;
char arr[10];
sprintf(arr, "%d", a);
cout << arr << endl;
return 0;
}
将整型的a转化为字符串格式存储在字符串arr当中。
C++中的stringstream:
int main()
{
//把整形转成字符串,相当于将整形插入进去就转成字符串了
int i = 123;
double d = 44.55;
ostringstream oss;
oss << i;
cout << oss.str() << endl;
string stri = oss.str();
//赋值覆盖一下
oss.str("");
oss << d;
string strd = oss.str();
oss.str("");
Date d1(2022, 10, 11);
oss << d1;
string strdt = oss.str();
cout << strdt << endl;
istringstream iss(strdt);
Date d2;
iss >> d2;
return 0;
}
stringstream 用来转换自定义类型还是有优势,可以很方便的将整形转字符串,字符串转整形。
综上总结:
空间配置器,顾名思义就是为各个容器高效的管理空间(空间的申请与回收)的,在默默地工作。
前面在模拟实现vector、list、map、unordered_map等容器时,所有需要空间的地方都是通过new申请的,虽然代码可以正常运行,但是有以下不足之处:
所以我们需要设计一块高效的内存管理机制。
SGI-STL
以12
8作为小块内存与大块内存的分界线内存池就是: 先申请一块比较大的内存块已做备用,当需要内存时,直接到内存池中去去,当池中空间不够时,再向内存中去取,当用户不用时,直接还回内存池即可。避免了频繁向系统申请小块内存所造成的效率低、内存碎片以及额外浪费的问题。
首先我们先储备知识,我们需要空间需要向堆申请,malloc
也是个内存池,它提供服务的对象是整个程序,内空间配置器则是针对STL容器服务的。同时不同版本的malloc
实现的方式也是不一样的,malloc每次开空间大小也并不是要的大小,会多一点,比如空间前面会记录这块空间多大,留释放用。
关系图:
空间配置器就是单独开一块大的空间,留给申请小空间的用,就不用频繁的到堆上取,造成内存碎片化。
当申请的空间大于128 Byte
的时候就不会走内存池,而是直接用malloc
去直接申请,当申请的内存小于128 Byte
的时候则会走内存池申请。
示意图:
挨个挨个用链表挂起来,那待会再来申请内存的时候优先再这取,每个桶中挂的是内存块,取内存的时候直接在对应的桶中取就好。
如果申请的空间大于128Byte的话,直接走一级空间配置器,调用malloc函数,如果是小于128Byte的话,则是走二级空间配置器。
每次给的内存的大小:
malloc
,只要系统有内存就可以申请。128Byte
直接到桶里取内存就可以了。60Byte
的前40Byte
切出来留用,后20Byte
挂起来。优点:
八个Byte
存下一个内存的地址就可以了最小的内存块是4字节,不存在存不下下一个内存块地址的问题,因为无论在哪个平台下,指针的大小要么是4Byte
,要么是8个Byte
。补充:
一个进程里面只有一 个空间配置器也就是说空间配置器可以设置成单例模式库里的实现和单例一样都能保证只有一对象。
本章内容只需了解,要知道其底层大概的结构,明白其思想即可。
至此,C++和数据结构的学习暂时告一段落,感谢一路相伴,我们Linux再见~~
少年所要走的路才不会有孤独,因为每一步都有属于自己的使命与归途。往事暗沉不可追,来日之路光明灿烂。