文章目录
- 类型转换
- C语言的类型转换
- 隐式类型转换:
- 强制类型的转换:
- C类型转换缺点
- C++强制类型转换
- static_cast
- reinterpret_cast
- const_cast
- dynamic_cast
- explicit
- 空间配置器(STL专用内存池)
- SGI-STL空间配置器实现原理
- 一级空间配置器
- 二级空间配置器
- 内存池
- SGI-STL中二级空间配置器设计
- 空间配置器应用(list)
- 空间配置器应用(list)
类型转换
C语言的类型转换
在C语言中,如果赋值运算符左右两端类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不一致时,就需要发生类型转化.
C/C++,java,Python等都是强类型的语言。js是弱类型的语言。
隐式类型转换:
编译器在编译阶段自动进行,能转就转,不能转就编译失败 .
相近的类型意义相似的类型。int->char short只是不一般大,都是表示数据大小的。像指针就不能转换成整数,意义都不一样。
如果两运算操作数数的类型不一样,会进行截断,有符号碰到无符号就会升级为无符号类型。
强制类型的转换:
需要用户自己处理.
C类型转换缺点
C风格的转换格式很简单,但是有不少缺点的:
- 隐式类型转化有些情况下可能会出问题:比如数据精度丢失
- 显式类型转换将所有情况混合在一起,代码不够清晰
因此C++提出了自己的类型转化风格,注意因为C++要兼容C语言,所以C++中还可以使用C语言的转化风格.为了加强类型转换的可视性,引入四种命名的强制类型转换符。
C++强制类型转换
static_cast
相近类型转换.用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用static_cast,但是必须是相近类型,不相关的不可以.
int main()
{
double d = 12.34;
int a = static_cast<int>(d);
cout << a << endl;//12
return 0;
}
reinterpret_cast
通常为操作数的位模式提供较低层次的重新解释,将一种类型转化为另一种不同的类型。
int a=10;
int* p = reinterpret_cast<int*>(a);
typedef void (*Func)();
int DoSomething()
{
cout << "DoSomething()" << endl;
return 1;
}
int main()
{
//下面转换函数指针的代码是不可移植的,所以不建议这样用
Func pf = reinterpret_cast<Func>(DoSomething);
pf();
}
const_cast
某种意义上删除了const修饰的变量不能修改的属性,const修饰的是常变量const属性去掉后小心被修改。
const 修饰的常变量直接打印
void test()
{
volatile const int a = 2;//告诉编译器不要优化3,3
//const int a = 2;//在栈,不在常量区(不让修改因为指令信息也在那里)
int* p = const_cast<int*>(&a);
*p = 3;
cout << a << endl;//2
//源于编译器优化,取值的时候并没有从内存取而是从之前压栈的地方取
cout << *p << endl;//3
//实际的内存值已经发生了改变
}
int main()
{
test();
}
dynamic_cast
用于将父类对象指针/引用转换为子类指针/引用(动态转换)。
-
向上转型:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则)
-
向下转型:父类对象指针/引用->子类指针/引用(用dynamic_cast转型是安全的)
注意:
- dynamic_cast只能用于含有虚函数的类
- dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0
对象是不允许转的。指针和引用可以,父类必须实现多态,或在虚表上面标识是父类还是子类,不是虚函数就没有虚表.
class A
{
public:
virtual void f() {}
};
class B :public A
{
public:
int _b = 0;
};
//pa指向父类对象或则子类对象
void func(A* pa)
{
//如果pa指向父类对象那么不做任何处理
//如果指向子类对象,那么转回子类并且访问子类的对象
//dynamic_cast 如果指向子类对象就转换成功
// 如果指向父类对象就转换失败返回nullptr
B* pb1 = dynamic_cast<B*> (pa);
if (pb1 == nullptr)
{
cout << "转换失败" << endl;
}
else
{
cout << "pb1:" << pb1 << endl;
pb1->_b++;
}
}
int main()
{
A a;
B b;
func(&a);
func(&b);
//test();
}
四种类型转换的意义:不要再用C的隐士类型转换和强制类型转换,而且全部使用规范的,实际中嫌麻烦项目中不用,很多公司写进了文档规范,谷歌。
explicit
explicit关键字阻止经过转换构造函数进行的隐式转换的发生
class A1
{
public:
explicit A1(int a)
{
cout << "A1(int a)" << endl;
}
A1(const A1& a)
{
cout << "A1(const A& a)" << endl;
}
private:
int _a;
};
int main()
{
A1 a1(1);//success 形参匹配
// 隐式转换-> A tmp(1); A a2(tmp);
A1 a2=1;//error 默认隐士类型转换
}
空间配置器(STL专用内存池)
前面在模拟实现vector、list、map、unordered_map等容器时,所有需要空间的地方都是通过new申请的,虽然代码可以正常运行,但是有以下不足之处,
- 空间申请与释放需要用户自己管理,容易造成内存泄漏
- 频繁向系统申请小块内存块,容易造成内存碎片
- 频繁向系统申请小块内存,影响程序运行效率
- 直接使用malloc与new进行申请,每块空间前有额外空间浪费
- 申请空间失败怎么应对
- 代码结构比较混乱,代码复用率不高
- 未考虑线程安全问题
容器通常去堆去申请大量小块内存,需要内存的只有容器.所以空间配置器的作用:
效率问题:空间配置器就是个专用的内存池,STL就是自己用的蓄水池。
缓解内存碎片,有足够的内存但是内存中间没有还回来部分间隔开了,空间碎片化,在下一次申请大内存连续的空间的时候申请不出来。
SGI-STL空间配置器实现原理
以上提到的几点不足之处,最主要还是:频繁向系统申请小块内存造成的。那什么才算是小块内存?SGI-STL以128作为小块内存与大块内存的分界线,将空间配置器其分为两级结构,一级空间配置器处理大块内存,二级空间配置器处理小块内存.
一级空间配置器
就是对malloc进行一次封装:
二级空间配置器
二级空间配置器专门负责处理小于128字节的小块内存。
- 如何才能提升小块内存的申请与释放的方式呢?
SGISTL采用了内存池的技术来提高申请空间的速度以及减少额外空间的浪费,采用哈希桶的方式来提高用户获取空间的速度与高效管理 .
内存池
先申请一块比较大的内存块已做备用,当需要内存时,直接到内存池中去去,当池中空间不够时,再向内存中去取,当用户不用时,直接还回内存池即可。避免了频繁向系统申请小块内存所造成的效率低、内存碎片以及额外浪费的问题 .
哈希桶,大于128字节就去调用一级配置器,小于128就在对应大小的哈希桶中拿空间。
SGI-STL中二级空间配置器设计
SGI-STL中的二级空间配置器使用了内存池技术,但没有采用链表的方式对用户已经归还的空间进行管理(因为用户申请空间时在查找合适的小块内存时效率比较低),而是采用了哈希桶的方式进行管理。
- 为什么要设计成哈希表?而不是直接在一大块空间中申请和归还?用链表连接
哈希表的意义就是方便O(1)的方式查找,单给一个内存池管理直接切很方便,但是容器删除数据归还内存就不如哈希桶。如果是现在的设计就只能把发出去的不同大小的内存块用指针连接起来形成链表,但是要在去链表中查找大小对应的内存块,效率就太低了.所以内存块哈希表就是方便管理归还内存的.
最小一个是8字节,前一个内存块中足以存的下同一个桶中下一块的地址。对应一个的哈希桶中内存块有空间就用,没有空间就去一个大块内存中找多个对应大小的内存块,给他一个,自己再将剩下的挂起来,然后如果大内存中也没有了,那就去堆上开辟新的大内存。
- 进程结束的时候页表销毁了,对应的挂起的内存空间也就释放了。
空间配置器应用(list)
方便管理归还内存的.
最小一个是8字节,前一个内存块中足以存的下同一个桶中下一块的地址。对应一个的哈希桶中内存块有空间就用,没有空间就去一个大块内存中找多个对应大小的内存块,给他一个,自己再将剩下的挂起来,然后如果大内存中也没有了,那就去堆上开辟新的大内存。
- 进程结束的时候页表销毁了,对应的挂起的内存空间也就释放了。