当前位置:   article > 正文

《C++ Primer Plus第五版》

《C++ Primer Plus第五版》

智能指针

要介绍智能指针需要先搞明白为什么需要它。
普通指针存在一个比较重要的问题就是我们在动态分配内存后,有可能会忘记调用delete去释放内存或者说在释放内存代码的前面发生了异常导致释放内存的代码不能执行,那这就会导致这部分内存无法被回收和再次使用,从而造成系统资源浪费 其实也就是内存泄漏。
智能指针是在 头文件中的 std 命名空间中定义的
智能指针实际上是使用了RAII原理(Resource Acquisition Is Initialization)资源获取即初始化,我们通过在对象的构造函数中获取资源并在析构函数中释放资源,这样来确保资源在不再需要时被及时释放,从而避免资源泄漏。

  • 常用智能指针:C++98里的auto_ptr、C++11里的unique_ptr、shared_ptr 和weak_ptr

- 1、auto_ptr

- 但是它的拷贝操作会改变资源的所有权,导致使用了源对象变得无效,所以C++11后不建议使用auto_ptr,使用unique_ptr替代!
  • 1

- 2、unique_ptr

- unique_ptr 和 auto_ptr用法几乎一样,也是一种独占式智能指针,只不过更严谨一些。我看过它的源码,它是直接把自己的拷贝构造函数和拷贝赋值运算符给delete禁用掉了,只能进行移动操作,也就直接禁止两个指针指向同一个资源,提高了代码的严谨性和安全性。
  • 1

unique_ptr(const unique_ptr& sp) = delete;不能通过拷贝构造函数来创建一个新的std::unique_ptr对象,即不允许复制另一个std::unique_ptr对象的资源所有权
unique_ptr& operator=(const unique_ptr& sp) = delete;这意味着你不能通过拷贝赋值运算符将一个std::unique_ptr对象赋值给另一个std::unique_ptr对象

- 3、shared_ptr

- auto_ptr和unique_ptr中有个问题就是它们都具有排他性,也就是都不支持多个智能指针指向同一块资源,比较局限。而shared_ptr就是为了解决这个问题,shared_ptr会允许多个智能指针指向同一块资源,并且能够保证共享的资源只会被释放一次。
  • 1

shared_ptr采用的是引用计数原理来实现多个shared_ptr对象之间共享资源:
shared_ptr会维护一份引用计数,用来记录当前资源被几个对象共享。
当拷贝的时候,引用计数加+1,当一个shared_ptr对象被销毁的时候,会调用析构函数把这个计数-1。
当计数为零的时候,代表已经没有指针指向这块内存,那么我们就释放它。
- 循环引用问题
- 循环引用是指使用多个智能指针share_ptr时,出现了指针之间相互指向,从而形成环,有点类似于死锁的情况。假设我们定义一个双向链表,把链表上节点都定义成shared_ptr智能指针,当其中两个节点互相引用的时候,就会出现循环引用的现象。
- std::shared_ptr 是线程安全的,在多线程情况下它的引用计数是通过原子操作来进行增减的
我们创建了两个节点A和B,初始引用都是1。让A的next指向B,B的pre指向A,引用变成2.
想要释放A空间必须让引用计数等于0,也就是必须先析构B空间的_pre
那就要求B空间的引用计数为0,前提是析构A空间的_next。
这样A等着B,B等着A形成了死锁,后果是析构函数无法调用,出现内存泄漏(虽然可以正常退出程序)

- 4、weak_ptr

weak_ptr也叫弱引用,引入weak_ptr的初衷就是为了解决刚才说的shared_ptr可能出现的循环引用问题,它只能从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会影响到引用数。它可以观察到 std::shared_ptr 所管理对象的生命周期,但是并不会控制对象的生命周期。
  • 1

C++内存分区

  • C++中的内存分区,从高地址到低地址,分别是栈、自由存储区、堆、全局数据区、常量区和代码区

- 栈:

存放局部变量和函数参数的地方,它是一块连续的内存区域由系统自动分配,函数执行结束时这些空间由系统自动释放。栈内存分配的计算优点是效率很高,有寄存器的支持,但是缺点是分配的内存容量有限(MB级别)

- 堆:

堆是我们通过new关键字来动态申请的内存空间,堆上分配的内存需要我们手动管理,堆是不连续的内存区域,以链表的形式分配的,如果我们没有正确释放堆上的内存可能会出现内存泄漏。

- 全局/静态存储区:

通常用来存储全局变量和静态变量,在该区定义的变量若没有初始化,则会被自动初始化,例如int型变量自动初始为0

- 常量存储区:

通常用于存储常量数据,这部分内存是只读的

- 代码区:

存放函数体的二进制代码,包括静态函数。通常代码区是可以共享的,因为对于频繁被执行的程序,只需要在内存中有一份代码就行了。
C++中类的数据成员和成员函数内存分布情况

  • 对象的内存空间中只有非静态数据成员和虚表指针+为了内存对齐加入的偏移。空类的对象的size为1(因为C++要求每个对象的地址在内存中是唯一的)
  • 成员函数不占用对象的内存。因为所有的函数都是存放在代码区的,不管是全局函数,还是成员函数,还是静态static函数。

虚函数表

虚函数表是用于实现C++多态性的关键机制之一。它用于存储类中每个虚函数的地址,类的每个实例化对象都有一个指向本类虚表的指针,在构造函数执行时会对虚表指针进行初始化。
当类被继承时,子类会继承父类的虚函数表。
当子类对父类的虚函数没有重写时,子类的虚表指针指向的是父类的虚表;当子类对父类的虚函数重写时,子类的虚表指针指向的是自身的虚表;

构造函数和析构函数能否声明为虚函数

  • 析构函数需要。主要是为了确保在使用多态时正确地调用析构函数。有这么一种情况:子类中有资源的释放操作,如果父类的析构函数不是虚函数,那么当通过父类指针删除一个子类对象时,只会调用父类的析构函数,而不会调用子类的析构函数。这会导致子类中分配的资源无法正确释放,可能会导致资源泄漏。而如果将父类的析构函数声明为虚函数,当通过父类指针删除子类对象时,会先调用子类的析构函数,然后再调用父类的析构函数,从而确保子类中的资源能够正确地释放。
  • 构造函数不行。虚函数运行需要依赖对象的虚函数表,而虚函数表是在构造函数执行完毕后才被设置的。也就是没有调用构造函数之前不能调用虚函数,所以把构造函数本身定义为虚函数是错误的行为。

STL标准模板库

STL标准模板库,是 C++ 标准库的重要组成部分,包含了许多常用的数据结构和算法的软件框架。
STL提供了六大组件,彼此之间可以组合套用,这六大组件分别是:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器。

1、vector

  1. 底层动态数组,可以自动调整大小,时间复杂度为 O(1)。
  2. 线性连续空间,支持通过下标随机访问元素
    为什么是1.5而不是2扩容
    首先vector的扩容原理是申请新空间,拷贝元素,释放旧空间。
    理想的分配方案是在第n次扩容时,前面的n-1次释放的空间还能够使用。
    如果按照2倍方式扩容:1、2、4、8…,如果8字节还需要进行扩容,那之前释放的空间1+2+4=7字节,还是小于需要的至少8字节,所以按2倍方式扩容时,每次扩容前释放的旧空间都不能复用。而1.5倍:1 1.5 2.25 3.375
    vector释放空间:vector在释放空间方面有两种方式:使用成员函数clear()和shrink_to_fit()

2、list

  1. 底层双向链表,插入或删除都是O(1)
  2. 空间不连续,所以不支持通过下标直接访问元素,需要迭代器。

3、deque

默认情况下,stack和queue使用 deque 作为其底层容器
deque是一种双向开口的连续线性空间。头尾两端都可以元素插入和删除,中间插入元素比较费时,因为必须移动中间其他的元素。
连续分段空间,由一段一段的连续空间构成的,用一小块连续的内存空间map作为主控(不是STL的map容器),里面的每一个元素都是一个指针,指向另一段连续性内存空间,称作缓冲区,默认使用 512 字节大小。Deque最大的工作就是维护这些分段连续的内存空间的整体性的假象。

4、stack & queue

默认情况下,stack和queue使用 deque 作为其底层容器

  • stack先进后出,只有顶端可以进&出元素。
  • Queue先进先出,一端新增元素,从另一端移除元素。只有顶端可以出元素。
  • stack和queue不能遍历,也没有迭代器。

5、set 、unordered_set

  • 有序、无重复元素,它的元素既是键也是值。
  • set:底层实现的是红黑树,理想情况下,插入、删除和查找的时间复杂度O(log n)。
  • unordered_set:基于哈希表实现, 平均情况下,插入、删除和查找O(1)。哈希冲突用链地址法
    除了 vector 和 string 之外的 STL 容器都不支持 *(it+i) 的访问方式

6、map、unordered_map

  1. map:基于红黑树,插入删除和查找的时间复杂度为 O(log n);元素按照键的比较结果有序存储。
  2. unordered_map:基于哈希表,插入删除和查找的时间复杂度为 O(1)。

C++11 新特性

智能指针
NULL和nullptr

  • NULL 是宏定义,通常被定义为整数零/ 空指针常量。因此,NULL 不是一个真正的指针类型,而是一个能够隐式转换为任何指针类型的整数值,使用 NULL 可能会引发类型不匹配的问题。
  • nullptr 是 C++11 引入的关键字,是一个特殊的空指针常量,它是一个真正的指针类型,用来解决NULL存在的类型不匹配的安全问题。
    Lambda 表达式(匿名函数)
  • Lambda函数是C++ 11的新特性之一,使用Lambda我们可以编写内嵌的匿名函数,以此来简化编程工作。我们平时调用函数的时候,都是需要被调用函数的函数名,但是使用lambda函数就不需要函数名,直接写在需要调用的地方就可以
  • Lambda可以忽略参数列表和返回值,但必须包含捕获列表和函数体;
声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号