1. 概述

栈和队列都属于线性表,它们均可使用顺序或链式来实现。二者之间的比较:
image.png

栈与队列在开发中的实际应用场景:
image.png

1.1 栈

栈(Stack):一个有穷线性数据结构,具有后进先出(LIFO)的特性

栈中元素从栈顶(top)压入,也从栈顶弹出

栈的操作:

  • isFull:判断栈是否已满

  • isEmpty:判断是否为空栈

  • push:将元素压栈,添加元素到栈顶

  • pop:将元素出栈,删除栈顶元素

新添加的元素永远存储在栈顶,而删除的也永远是栈顶元素,所以栈是后进先出的数据结构。而栈的top指针,只要是非空栈,它会永远指向栈顶元素
image.png

栈的top指针在压栈和出栈时,会跟随栈中的数据时时变化。如果是空栈,则top = -1
image.png

1.2 队列

队列(Queue):有穷线性数据结构,具有先进先出(FIFO)的特性

队列中的元素,从队尾(rear)入队,从队头(front)出队
image.png

队列的操作:

  • isFull:判断栈是否已满

  • isEmpty:判断是否为空栈

  • enqueue:将元素入队,添加到队列的队尾

  • dequeue:将元素出队,删除队头的元素

一般来说,空队列的frontrear都为0,而非空队列front指向队头,rear指向队尾的下一个元素

当出队速度大于入队速度,会产生“假溢出”问题。使用循环队列,可以充分利用向量空间,克服“假溢出”现象

循环队列永远不能让它的头尾相遇,否则通过front == rear将无法区分队列是满或是空

正确的做法,可以牺牲队列中一个存储单元,当front == rear表示队列为空,而(rear + 1) % MAXSIZE == front,则表示队列已满

2. 用栈实现队列

请使用两个栈实现先进先出队列,只能使用标准的栈操作是合法的。队列应当支持一般队列支持的所有操作(pushpoppeekempty

实现MyQueue类:

  • void push(int x):将元素x推到队列的末尾

  • int pop():从队列的开头移除并返回元素

  • int peek():返回队列开头的元素

  • boolean empty():如果队列为空,返回 true。否则,返回 false

示例:

  1. MyQueue myQueue = new MyQueue();
  2. myQueue.push(1); // queue is: [1]
  3. myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
  4. myQueue.peek(); // return 1
  5. myQueue.pop(); // return 1, queue is [2]
  6. myQueue.empty(); // return false

使用pushStackpopStack两个栈,实现队列的特性。入队逻辑非常简单,永远只会往pushStackpush数据。而出队逻辑相对复杂,因为要符合队列先进先出的特性

popStack负责pop操作,由于数据都被push到pushStack中,所以popStack为空栈,我们先将pushStack中的数据导入到popStack中。此时,数据在popStack中的顺序会改变,原本先入栈的数据会存储在栈顶

所以,只要popStack为空栈,我们就先从pushStack中导入数据,然后再pop数据。否则,只要popStack不为空栈,直接从中pop数据即可

切记,只要popStack为非空栈,就不能从pushStack中导入新的数据,因为会破坏popStack的先进先出结构

代码实现:

  1. class MyQueue {
  2. fileprivate var pushStack : LinkedStack<Int>;
  3. fileprivate var popStack : LinkedStack<Int>;
  4. init() {
  5. pushStack = LinkedStack<Int>();
  6. popStack = LinkedStack<Int>();
  7. }
  8. func enter(x : Int){
  9. pushStack.push(element: x);
  10. }
  11. func out() -> Int?{
  12. let front = peek();
  13. if(front == nil){
  14. return front;
  15. }
  16. return popStack.pop();
  17. }
  18. func peek() -> Int?{
  19. if(isEmpty()){
  20. return nil;
  21. }
  22. if(popStack.isEmpty()) {
  23. while(!pushStack.isEmpty()){
  24. popStack.push(element: pushStack.pop()!);
  25. }
  26. }
  27. return popStack.getTop();
  28. }
  29. func isEmpty() -> Bool {
  30. if(popStack.isEmpty() && pushStack.isEmpty()){
  31. return true;
  32. }
  33. return false;
  34. }
  35. }
  • 时间复杂度:入队操作O(1),出队操作O(1)

  • 空间复杂度:O(n)

单次出队操作,可能需要先进行数据的导入。这种情况操作代价可能很大,但是最大执行次数跟它之前执行过入队操作的次数有关,并且在导入的数据全部出队之前,不会再触发这种最坏的情况。所以平均复杂度应为O(1)

3. 用队列实现栈

请使用两个队列实现一个后进先出(LIFO)的栈,并支持普通栈的全部四种操作(pushtoppopempty)。

实现MyStack类:

  • void push(int x):将元素x压入栈顶

  • int pop():移除并返回栈顶元素

  • int top():返回栈顶元素

  • boolean empty():如果栈是空的,返回 true。否则,返回false

示例:

  1. MyStack myStack = new MyStack();
  2. myStack.push(1);
  3. myStack.push(2);
  4. myStack.top(); // 返回 2
  5. myStack.pop(); // 返回 2
  6. myStack.empty(); // 返回 False

3.1 两个队列实现

定义queue1queue2两个队列,当queue1为空队列,直接将数据入队queue1即可。而queue1为非空队列,先将数据入队queue2,然后再将queue1中的数据入队queue2,最后交换queue1queue2的指针

此时,最后添加的数据在队头,而之前添加的数据,会被导入在队尾,这符合栈的后进先出结构

使用这种方式,有数据的一定只有queue1,所以toppop操作,只需要针对queue1进行peekout操作即可

代码实现:

  1. class MyStack {
  2. fileprivate var queue1 : LinkedQueue<Int>;
  3. fileprivate var queue2 : LinkedQueue<Int>;
  4. init() {
  5. queue1 = LinkedQueue<Int>();
  6. queue2 = LinkedQueue<Int>();
  7. }
  8. func push(x : Int){
  9. if(queue1.isEmpty()){
  10. queue1.enter(element: x);
  11. return;
  12. }
  13. queue2.enter(element: x);
  14. while(!queue1.isEmpty()){
  15. queue2.enter(element: queue1.out()!);
  16. }
  17. let tmp = queue1;
  18. queue1 = queue2;
  19. queue2 = tmp;
  20. }
  21. func pop() -> Int?{
  22. let top = getTop();
  23. if(top == nil){
  24. return top;
  25. }
  26. return queue1.out();
  27. }
  28. func getTop() -> Int?{
  29. if(isEmpty()){
  30. return nil;
  31. }
  32. return queue1.peek();
  33. }
  34. func isEmpty() -> Bool {
  35. if(queue1.isEmpty()){
  36. return true;
  37. }
  38. return false;
  39. }
  40. }
  • 时间复杂度:入栈操作O(n),其余操作O(1)

  • 空间复杂度:O(n)

3.2 一个队列实现

我们需要记录添加到队列中的数据数n,有新数据入队,同时进行n + 1。入队后,将队列遍历n - 1次,将队列中的数据依次出队再入队,这样就会将队头的数据导入到队尾,同样实现了栈的后进先出结构

代码实现:

  1. class MyStack {
  2. fileprivate var queue : LinkedQueue<Int>;
  3. init() {
  4. queue = LinkedQueue<Int>();
  5. }
  6. func push(x : Int){
  7. queue.enter(element: x);
  8. for _ in 0..<queue.length - 1 {
  9. queue.enter(element: queue.out()!);
  10. }
  11. }
  12. func pop() -> Int?{
  13. let top = getTop();
  14. if(top == nil){
  15. return top;
  16. }
  17. return queue.out();
  18. }
  19. func getTop() -> Int?{
  20. if(isEmpty()){
  21. return nil;
  22. }
  23. return queue.peek();
  24. }
  25. func isEmpty() -> Bool {
  26. if(queue.isEmpty()){
  27. return true;
  28. }
  29. return false;
  30. }
  31. }
  • 时间复杂度:入栈操作O(n),其余操作O(1)

  • 空间复杂度:O(n)

4. 自动释放池

自动释放池是一个存储指针的栈结构,指针要么是一个要释放的对象,要么是自动释放池的边界,俗称:哨兵对象。自动释放池的栈空间,被分成一个双向链接结构的页面列表,可添加和删除页面

  • 栈原则,后进先出。对象最后被push,最先被pop

  • 双向链表的特点,一个页中同时存在父结点和子结点。可向前找到父页面,也可向后找到子页面

4.1 定义默认值

  1. #import <Foundation/Foundation.h>
  2. #include <pthread.h>
  3. #include <malloc/malloc.h>
  4. #import <objc/runtime.h>
  5. // 哨兵对象
  6. #define POOL_BOUNDARY nil
  7. // 线程局部存储的key
  8. static pthread_key_t const key = 39;
  9. // 空池占位符
  10. #define EMPTY_POOL_PLACEHOLDER ((id*)1)
  11. // 自动释放池中对象销毁,使用0xA3填充
  12. static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasing
  13. // 自动释放池一页预设大小
  14. static size_t const SIZE = PAGE_MIN_SIZE;

4.2 页的数据结构

  1. @class AutoreleasePoolPage;
  2. @interface AutoreleasePoolPageData : NSObject {
  3. // 存储对象的指针域
  4. @package __unsafe_unretained id* _next;
  5. // 父页面指针域
  6. AutoreleasePoolPage *_parent;
  7. // 子页面指针域
  8. AutoreleasePoolPage *_child;
  9. }
  10. @end
  11. @implementation AutoreleasePoolPageData
  12. @end

4.3 页的初始化

  1. @interface AutoreleasePoolPage : AutoreleasePoolPageData
  2. @end
  3. @implementation AutoreleasePoolPage
  4. // 开辟空间
  5. + (instancetype)allocWithZone:(struct _NSZone *)zone {
  6. // 起始地址为4k整数倍 + 4k
  7. void *as = malloc_zone_memalign(malloc_default_zone(), SIZE, SIZE);
  8. // 用来对一段内存空间全部设置为某个字符。使用0填充as首地址之后4k长度
  9. memset(as, 0, SIZE);
  10. // 开辟空间
  11. void *src = [super allocWithZone:zone];
  12. // 正常开辟空间,我们无法控制对象所在地址,但我们可以在它开辟后,将其拷贝到指定空间中
  13. // 从src空间中移动n个元素,将其赋值给as的内存中。从内存的第一个地址所指向的数据开始复制,直到赋值n个数据
  14. // 该函数也就调用结束,同时返回as
  15. // 如果源区域和目的区域有重叠部分的话,可以在源区域未被目的区域覆盖的时候拷贝到目的区域中
  16. memmove(as, src, sizeof(src));
  17. // 拷贝后将src释放
  18. [(id)src release];
  19. return as;
  20. }
  21. // 初始化
  22. - (instancetype)initWithPage:(AutoreleasePoolPage *)page {
  23. self = [super init];
  24. if (self) {
  25. // 将next默认设置为:当前对象首地址 + 对象大小
  26. _next = [self begin];
  27. // 连接双向链表,设置当前对象的父结点
  28. _parent = page;
  29. if (_parent) {
  30. // 将父结点的子结点设置为自身
  31. _parent->_child = self;
  32. }
  33. }
  34. return self;
  35. }
  36. // 当前页的起始地址,结构体首地址 + 结构体大小
  37. - (id *)begin {
  38. return (id *)((uint8_t *)self + class_getInstanceSize(self.class));
  39. }
  40. // 当前页的结束地址,结构体首地址 + 预设大小
  41. - (id *)end {
  42. return (id *) ((uint8_t *)self + SIZE);
  43. }
  44. // next指向结束地址,当前页已满
  45. - (BOOL)full {
  46. return _next == [self end];
  47. }
  48. // next指向起始地址,当前页为空
  49. - (BOOL)empty {
  50. return _next == [self begin];
  51. }
  52. @end

4.4 将对象添加至自动释放池

  1. @implementation AutoreleasePoolPage
  2. // 添加哨兵对象(边界)
  3. + (void *)push {
  4. // 添加哨兵对象到自动释放池,返回哨兵对象在池子中的地址
  5. id *dest = [self autoreleaseFast:POOL_BOUNDARY];
  6. // 返回的地址不是空池占位符,并且里面存储的不是哨兵对象,程序终止
  7. assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
  8. // 返回哨兵对象的地址
  9. return dest;
  10. }
  11. // 添加对象
  12. + (id)autorelease:(id)obj {
  13. // 对象为空,程序终止
  14. assert(obj);
  15. // 添加对象到自动释放池,返回对象在池子中的地址
  16. id *dest __unused = [self autoreleaseFast:obj];
  17. // 返回地址非空,并且地址不是空池占位符,并且里面存储的不是传入的对象,程序终止
  18. assert(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
  19. // 返回传入的对象
  20. return obj;
  21. }
  22. // 将对象添加到自动释放池
  23. - (id *)add:(id)obj {
  24. // 当前页已满,程序终止
  25. assert(!self.full);
  26. // 得到当前next地址
  27. id *ret = _next;
  28. // 将对象存储在next中
  29. *_next = obj;
  30. // next向下平移
  31. _next++;
  32. // 返回存储对象的地址
  33. return ret;
  34. }
  35. @end

添加对象到自动释放池:

  1. @implementation AutoreleasePoolPage
  2. // 快速创建自动释放池
  3. + (id *)autoreleaseFast:(id)obj {
  4. // 获取热页
  5. AutoreleasePoolPage *page = [self hotPage];
  6. if (page && ![page full]) {
  7. // 存在,并且未满,将对象加入自动释放池
  8. return [page add:obj];
  9. } else if (page) {
  10. // 存在,并且已满,先添加新页,再添加对象
  11. return [self autoreleaseFullPage:obj page:page];
  12. } else {
  13. // 不存在,先创建,然后添加对象
  14. return [self autoreleaseNoPage:obj];
  15. }
  16. }
  17. // 创建自动释放池,然后添加数据
  18. + (id *)autoreleaseNoPage:(id)obj {
  19. // 存在热页,终止程序
  20. assert(!self.hotPage);
  21. // 加入哨兵对象的标记,默认为false
  22. BOOL pushExtraBoundary = false;
  23. // 判断当前线程是否已存在空池占位符
  24. if (self.haveEmptyPoolPlaceholder) {
  25. // We are pushing a second pool over the empty placeholder pool
  26. // or pushing the first object into the empty placeholder pool.
  27. // Before doing that, push a pool boundary on behalf of the pool
  28. // that is currently represented by the empty placeholder.
  29. // 已存在,先插入哨兵对象,修改标记为true
  30. pushExtraBoundary = true;
  31. }
  32. else if (obj == POOL_BOUNDARY) {
  33. // We are pushing a pool with no pool in place,
  34. // and alloc-per-pool debugging was not requested.
  35. // Install and return the empty pool placeholder.
  36. // 不存在,且当前插入的对象为哨兵对象,当前线程先创建一个空池占位符
  37. return [self setEmptyPoolPlaceholder];
  38. }
  39. // 创建一个新页,当前新页为根结点,所以父结点为nil
  40. AutoreleasePoolPage *page = [[AutoreleasePoolPage alloc] initWithPage:nil];
  41. // 将当前新页设置为热页
  42. [self setHotPage:page];
  43. // 判断插入哨兵对象的标记
  44. if (pushExtraBoundary) {
  45. // 标记为true,插入哨兵对象
  46. [page add:POOL_BOUNDARY];
  47. }
  48. // Push the requested object or pool.
  49. // 将对象加入自动释放池
  50. return [page add:obj];
  51. }
  52. // 热页已满,先添加新页,再添加对象
  53. + (id *)autoreleaseFullPage:(id)obj page:(AutoreleasePoolPage *)page {
  54. // The hot page is full.
  55. // Step to the next non-full page, adding a new page if necessary.
  56. // Then add the object to that page.
  57. // 当前传入的页不是热页,程序终止
  58. assert(page == self.hotPage);
  59. // 当前页未满,程序终止
  60. assert([page full]);
  61. do {
  62. // 判断当前页是否存在子页
  63. if (page->_child) {
  64. // 存在,继续向下找
  65. page = page->_child;
  66. }
  67. else {
  68. // 不存在,创建新页,传入当前页作为父结点,新页为当前页的子页
  69. page = [[self alloc] initWithPage:page];
  70. }
  71. // 如果找到未写满的页,停止循环
  72. } while ([page full]);
  73. // 将未写满的页设置为热页
  74. [self setHotPage:page];
  75. // 将对象加入自动释放池
  76. return [page add:obj];
  77. }
  78. @end

空池占位符:

  1. @implementation AutoreleasePoolPage
  2. // 当前线程是否已存在空池占位符
  3. + (BOOL)haveEmptyPoolPlaceholder {
  4. // 从线程局部存储中获取
  5. id *tls = (id *)pthread_getspecific(key);
  6. // 结果为空池占位符,返回true
  7. return (tls == EMPTY_POOL_PLACEHOLDER);
  8. }
  9. // 当前线程创建一个空池占位符
  10. + (id*)setEmptyPoolPlaceholder {
  11. // 线程局部存储中有值,程序中断
  12. assert(pthread_getspecific(key) == nil);
  13. // 将空池占位符写入线程局部存储中
  14. pthread_setspecific(key, (void *)EMPTY_POOL_PLACEHOLDER);
  15. // 返回空池占位符
  16. return EMPTY_POOL_PLACEHOLDER;
  17. }
  18. // 获取冷页
  19. + (AutoreleasePoolPage *)coldPage {
  20. // 获取热页
  21. AutoreleasePoolPage *result = self.hotPage;
  22. // 判断结果是否为空
  23. if (result) {
  24. // 结果非空,一直遍历到根结点
  25. while (result->_parent) {
  26. result = result->_parent;
  27. }
  28. }
  29. // 返回结果
  30. return result;
  31. }
  32. //获取热页
  33. + (AutoreleasePoolPage *)hotPage {
  34. // 从线程局部存储中获取
  35. AutoreleasePoolPage *result = (AutoreleasePoolPage *)pthread_getspecific(key);
  36. // 返回的结果为空池占位符,返回nil
  37. if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
  38. // 返回结果
  39. return result;
  40. }
  41. // 设置热页
  42. + (void)setHotPage:(AutoreleasePoolPage *)page {
  43. // 写入线程局部存储中
  44. pthread_setspecific(key, page);
  45. }
  46. @end

冷页 & 热页:

  1. @implementation AutoreleasePoolPage
  2. // 获取冷页
  3. + (AutoreleasePoolPage *)coldPage {
  4. // 获取热页
  5. AutoreleasePoolPage *result = self.hotPage;
  6. // 判断结果是否为空
  7. if (result) {
  8. // 结果非空,一直遍历到根结点
  9. while (result->_parent) {
  10. result = result->_parent;
  11. }
  12. }
  13. // 返回结果
  14. return result;
  15. }
  16. //获取热页
  17. + (AutoreleasePoolPage *)hotPage {
  18. // 从线程局部存储中获取
  19. AutoreleasePoolPage *result = (AutoreleasePoolPage *)pthread_getspecific(key);
  20. // 返回的结果为空池占位符,返回nil
  21. if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
  22. // 返回结果
  23. return result;
  24. }
  25. // 设置热页
  26. + (void)setHotPage:(AutoreleasePoolPage *)page {
  27. // 写入线程局部存储中
  28. pthread_setspecific(key, page);
  29. }
  30. @end

4.5 将对象从自动释放池中移除

  1. @implementation AutoreleasePoolPage
  2. // 移除并销毁对象
  3. + (void)pop:(void *)token {
  4. AutoreleasePoolPage *page;
  5. id *stop;
  6. // 判断传入的地址是否等于空池占位符
  7. if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
  8. // Popping the top-level placeholder pool.
  9. // 当前地址为空池占位符,获取热页
  10. page = self.hotPage;
  11. // 热页不存在
  12. if (!page) {
  13. // Pool was never used. Clear the placeholder.
  14. // 将其标记为nil
  15. return [self setHotPage:nil];
  16. }
  17. // Pool was used. Pop its contents normally.
  18. // Pool pages remain allocated for re-use as usual.
  19. // 获取冷页(根结点)
  20. page = self.coldPage;
  21. // 记录根结点页面的起始地址
  22. token = page.begin;
  23. } else {
  24. // 获取地址所属页的首地址
  25. page = [self pageForPointer:(uintptr_t)token];
  26. }
  27. // 将stop赋值
  28. stop = (id *)token;
  29. // 将对象依次销毁,到指定位置结束
  30. return [self popPage:token page:page stop:stop];
  31. }
  32. @end

获取地址所属页的首地址:

  1. @implementation AutoreleasePoolPage
  2. // 获取地址所属页的首地址
  3. + (AutoreleasePoolPage *)pageForPointer:(uintptr_t)p {
  4. AutoreleasePoolPage *result;
  5. // 当前地址和4K取余,得到偏移量,也就是已使用的容量
  6. uintptr_t offset = p % SIZE;
  7. // 如果偏移量小于AutoreleasePoolPage类的容量,程序终止
  8. assert(offset >= sizeof([AutoreleasePoolPage class]));
  9. // 使用当前地址 - 偏移量,得到页的首地址
  10. result = (AutoreleasePoolPage *)(p - offset);
  11. // 返回结果
  12. return result;
  13. }
  14. @end

将对象依次销毁,到指定位置结束:

  1. @implementation AutoreleasePoolPage
  2. // 将对象依次销毁,到指定位置结束
  3. + (void)popPage:(void *)token page:(AutoreleasePoolPage *)page stop:(id *)stop {
  4. // 将对象依次销毁,到指定位置结束
  5. [page releaseUntil:stop];
  6. // 判断当前页是否存在子页
  7. if (page->_child) {
  8. // hysteresis: keep one empty child if page is more than half full
  9. // 存在子页,判断当前页使用的容量是否小于一半
  10. if (page.lessThanHalfFull) {
  11. // 小于一半,释放当前页之后的全部子页
  12. [page->_child kill];
  13. }
  14. else if (page->_child->_child) {
  15. // 超过一半,仅保留一个子页,释放子页之后的全部子页
  16. [page->_child->_child kill];
  17. }
  18. }
  19. }
  20. // 判断当前页的使用量不满一半
  21. - (BOOL)lessThanHalfFull {
  22. // 已使用地址 - 起始地址 < (结束地址 - 起始地址) / 2
  23. return (_next - self.begin < (self.end - self.begin) / 2);
  24. }
  25. // 将对象依次销毁,到指定位置结束
  26. - (void)releaseUntil:(id *)stop {
  27. // Not recursive: we don't want to blow out the stack
  28. // if a thread accumulates a stupendous amount of garbage
  29. // next不等于指定地址,持续遍历
  30. while (self->_next != stop) {
  31. // Restart from hotPage() every time, in case -release
  32. // autoreleased more objects
  33. // 获取热页
  34. AutoreleasePoolPage *page = self.class.hotPage;
  35. // fixme I think this `while` can be `if`, but I can't prove it
  36. // 判断当前热页是否为空
  37. while (page.empty) {
  38. // 已经是空页,说明当前页中的数据全部销毁,但还未达到指定位置
  39. // 获取父页,继续销毁父页中的对象
  40. page = page->_parent;
  41. // 将其设置为新的热页
  42. [self.class setHotPage:page];
  43. }
  44. // next向上平移
  45. page->_next--;
  46. // 获取next中的对象
  47. id obj = *page->_next;
  48. // 将next存储的数据使用0xA3填充
  49. memset((void*)page->_next, SCRIBBLE, sizeof(*page->_next));
  50. // 判断当前对象是否为哨兵对象
  51. if (obj != POOL_BOUNDARY) {
  52. // 不是哨兵对象,调用它的release方法
  53. [obj release];
  54. }
  55. }
  56. // 将当前页设置为热页
  57. [self.class setHotPage:self];
  58. }
  59. @end

销毁当前页以及之后的子页:

  1. @implementation AutoreleasePoolPage
  2. // 销毁当前页以及之后的子页
  3. - (void)kill
  4. {
  5. // Not recursive: we don't want to blow out the stack
  6. // if a thread accumulates a stupendous amount of garbage
  7. // 将当前页赋值给临时变量
  8. AutoreleasePoolPage *page = self;
  9. // 遍历,找到最后一个子页
  10. while (page->_child) {
  11. page = page->_child;
  12. }
  13. AutoreleasePoolPage *deathptr;
  14. do {
  15. // 从最后的子页开始销毁,一直到当前页停止
  16. deathptr = page;
  17. page = page->_parent;
  18. if (page) {
  19. page->_child = nil;
  20. }
  21. [deathptr release];
  22. } while (deathptr != self);
  23. }
  24. @end

4.6 入口函数

  1. // 添加边界
  2. void * objc_autoreleasePoolPush(void) {
  3. return [AutoreleasePoolPage push];
  4. }
  5. // 将对象添加至自动释放池
  6. id objc_autorelease(id obj) {
  7. return [AutoreleasePoolPage autorelease:obj];
  8. }
  9. // 将对象从自动释放池中移除
  10. void objc_autoreleasePoolPop(void * _Nonnull context) {
  11. return [AutoreleasePoolPage pop:context];
  12. }

总结

概述:

  • 栈(Stack):一个有穷线性数据结构,具有后进先出(LIFO)的特性

    • 栈中元素从栈顶(top)压入,也从栈顶弹出
  • 队列(Queue):有穷线性数据结构,具有先进先出(FIFO)的特性

    • 队列中的元素,从队尾(rear)入队,从队头(front)出队

用栈实现队列:

  • 使用pushStackpopStack两个栈,实现队列的特性。入队逻辑非常简单,永远只会往pushStackpush数据。而出队逻辑相对复杂,因为要符合队列先进先出的特性

  • popStack负责pop操作,由于数据都被push到pushStack中,所以popStack为空栈,我们先将pushStack中的数据导入到popStack中。此时,数据在popStack中的顺序会改变,原本先入栈的数据会存储在栈顶

  • 所以,只要popStack为空栈,我们就先从pushStack中导入数据,然后再pop数据。否则,只要popStack不为空栈,直接从中pop数据即可

  • 切记,只要popStack为非空栈,就不能从pushStack中导入新的数据,因为会破坏popStack的先进先出结构

用队列实现栈:

  • 两个队列实现:定义queue1queue2两个队列,当queue1为空队列,直接将数据入队queue1即可。而queue1为非空队列,先将数据入队queue2,然后再将queue1中的数据入队queue2,最后交换queue1queue2的指针

  • 一个队列实现:我们需要记录添加到队列中的数据数n,有新数据入队,同时进行n + 1。入队后,将队列遍历n - 1次,将队列中的数据依次出队再入队,这样就会将队头的数据导入到队尾,同样实现了栈的后进先出结构

自动释放池:

  • 是一个存储指针的栈结构,指针要么是一个要释放的对象,要么是自动释放池的边界,俗称:哨兵对象。自动释放池的栈空间,被分成一个双向链接结构的页面列表,可添加和删除页面

    • 栈原则,后进先出。对象最后被push,最先被pop

    • 双向链表的特点,一个页中同时存在父结点和子结点。可向前找到父页面,也可向后找到子页面