一、运算符的重载
运算符的重载的目的是让自定义类型满足和内置类型相同的逻辑,运算符重载是由一个函数实现的重载机制,这个函数名是operator+重载的运算符构成
1、哪些运算符不能重载?
(1)类成员访问符中指向->可以重载,.不能重载
(2)作用域访问符::不能重载
(3)三目运算符不能重载
(4)sizeof不能重载
2、运算符的重载规则?
(1)不能改变运算符的优先级和结合性
(2)不能改变运算符的用法
(3)不能创造新的运算符
(4)运算符重载函数中不允许有函数的默认值
二、迭代器
class String
{
private:
char* mptr;
}
int main()
{
String str;
return 0;
}
上述代码如果想访问mptr中的数值,也就是指针所指向的数值该怎么访问?我们可以给一个指针类型,让指针类型直接指向mptr的位置,通过指针来访问,这样做是不行的,因为mptr是私有的,指针不能指向内部去访问,会破坏封装性。普通指针不能处理,我们可以通过面向对象的指针(面向对象的指针就是迭代器),这个指针指向对象的位置,容器提供两个接口,这两个接口是半开半闭的空间,一个是begin()迭代起始位置的元素,一个是end()迭代末尾位置后继位置的元素,让指针指向对象后,让指针由对象提供的begin接口给指针赋予起始的位置,让指针由对象提供的end接口给指针赋予末尾的位置。
找到了起始位置和末尾位置,如何通过当前位置找到下一个元素的位置呢?我们可以用++运算符的重载函数,这个运算符重载函数封装了每个容器找下一个位置的方式。
迭代器的核心思想:
- 迭代器就是提供一个统一的容器遍历方式,迭代器就是用来遍历容器的。
- 容器提供两个接口,这两个接口是半开半闭的空间,一个是begin()迭代起始位置的元素,一个是end()迭代末尾位置后继位置的元素,再提供一个next接口;由容器对象提供的begin接口让指针迭代到起始位置的元素,通过调用next(++)找到下一个元素来遍历元素,再通过解引用的方法获取元素,通过end保证了数据的结尾。
迭代器的优点:
1、避免了暴漏容器内部的可能
2、针对于不同数据结构提供了统一的容器遍历方式
三、写时如果要访问拷贝
下面三个拷贝用string类举例
1、浅拷贝
浅拷贝是一个简单的赋值,如果有指针存在,会让两个指针指向同一个内存块,当内存块需要被释放,会导致内存被重复释放,程序出现问题
缺点:对象销毁的时候同一内存可能会被多个对象同时释放,导致程序出现问题
优点:实现了数据共享,开销小,节省空间
2、深拷贝
深拷贝让每个对象都拥有自己独立的资源
缺点:资源占用率大,内存访问率低
3、写时拷贝:通过operator[]函数实现,比如str2[2]='b'是修改str2的2号下标的值
(1)写之前浅拷贝
内存的释放在最后一个对象销毁时释放,怎么判断最后一个对象?给一个计数器count,看该堆内有多少对象指向;这个计算器在内存中保存,因此内存由一个count区(四个字节),有一个data;每有一个新的对象生成,让这个对象指向内存,每增加一个对象,count+1;每释放一个对象count-1,直到count=0,再释放内存。
(2)写时深拷贝
第一种方式:普通方式
如果str2想要修改则需要str2对象和内存断开,count-1;str2重新开辟堆内存,再把数据拷贝过去,再做修改
第二种情况:内存上本来就只有一个对象指向
不需要开辟新的内存,直接修改
四、内存池
实现自主的内存管理机制
new:
1、开辟内存operator new(开辟内存的函数)
2、调用构造函数(系统调用)
delete:
1、调用析构函数
2、释放内存operator delete(释放内存的函数)
设计内存管理机制需要自己重载operator new和operator delete,我们如果频繁的new和delete,效率会出现问题,如果内存比较小,也会出现内存碎片(外碎片),为了解决这两个问题,也能进行自主的内存管理,我们实现了内存池。
内存池的处理方式:
申请一块大的内存,以后的开辟和释放都是从内存块进行开辟,释放也释放给这个内存块;这个大的内存释放归还给系统后,系统可以把大的内存切割成小的内存继续分配,不会产生内存碎片;第一次申请是从操作系统申请的大内存,以后再申请相当于在应用程序段,这个时候内存本身就在用用程序断,不需要过内核,因此实现这个内存池可以解决频繁的new和频繁的delete存在的问题。
我们先开辟一个大的内存块,然后以静态链表的方式把这个内存块进行简单组织,也就是以数组的形式来实现链表的管理;我们左半边存储数据的数据区,右半边来做指向下一个结点的指针域,整个内存池只管理未被分配出去的结点。
1、首先我们先对内存池进行一个初始化,让第一个内存的指针域指向第二个内存单元,第二个内存单元的指针域指向下一个,以此类推,直到最后一个内存单元的指针域置为NULL。划分好之后,我们再用一个标志来标志它的起始位置。
2、当我们需要开辟一个内存的时候,从内存池分配一个对应的内存块,如果开辟的是整型的内存,就把这个内存块的数据域分配给整型使用,而指针域是不考虑的,因此整型可以使用的只有数据域;然后让这个标志指向下一个位置。
针对已经分配出去的内存,由外部变量进行管理,内存池管理未被分配出去的结点。
3、如果p不管理对应的堆内存了,要释放p的话,会释放给内存池,我们把p对应结点再插入到内存池,可以通过p这个结点对应的指针域指向poll指向的结点,poll的位置重新指向p结点
4、如果内存池的poll已经指向空,但依然需要开辟内存,我们就要实现扩容;我们可以再开辟一个大的内存池,让poll指向第一个结点的位置,但如果这个时候想释放p3,可以继续让p3的指针域指向poll所指向的结点,然后poll回到p3
内存池的思想:
池是内存资源的集合,它的管理方式就是从池中拿资源,接着归还给池,归换给池的资源也可以重新分配,保证了资源的循环利用。
考题:以前会让左边是数据域,右边是游标域(数组的下标),为什么现在右边是指针域?
因为现在下标已经不能代表唯一的内存块了,如果扩容就会有两个内存池,两个内存池的下标都是从0开始