基础
内存分区
栈: 存放函数的局部变量、函数参数、返回地址等,由编译器自动分配和释放。
堆: 动态申请的内存空间,就是由 malloc 分配的内存块,由程序员控制它的分配和释放,如果程序执行结束还没有释放,操作系统会自动回收。
全局区/静态存储区(.bss 段和 .data 段): 存放全局变量和静态变量,程序运行结束操作系统自动释放,在 C 语言中,未初始化的放在 .bss 段中,初始化的放在 .data 段中,C++ 中不再区分了。
常量存储区(.data 段): 存放的是常量,不允许修改,程序运行结束自动释放。
代码区(.text 段): 存放代码,不允许修改,但可以执行。编译后的二进制文件存放在这里。
第二种分区:
内存分区模型
代码区:存放函数的二级制代码,由操作系统进行管理的
全局区:存放全局变量和静态变量以及常量
栈区:由编译器自动分配释放,存放函数的参数值,局部变量等
堆区: 由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
内存四区意义:不同区域存放的数据,赋予不同的声明周期,给我们更大的灵活编程
————————————————
原文链接:https://blog.csdn.net/qq_51604330/article/details/118607922
程序编译四个阶段
预处理阶段
1.什么是预处理
C语言的程序中可包括各种以符号#开头的编译指令,这些指令称为预处理命令。预处理命令属于C语言编译器,而不是C语言的组成部分。通过预处理命令可扩展C语言程序设计的环境。
预处理的作用
在集成开发环境中,编译,链接是同时完成的。其实,C语言编译器在对源代码编译之前,还需要进一步的处理:预编译。预编译的主要作用如下:
●将源文件中以”include”格式包含的文件复制到编译的源文件中。
●用实际值替换用“#define”定义的字符串。
●根据“#if”后面的条件决定需要编译的代码。
预处理器(cpp)根据以字符#开头的命令,修改原始的C程序。比如hello.c中第一行的#include<stdio.h>命令告诉预处理器读取系统头文件stdio.h的内容,并把它直接插入程序文本中,结果就得到了另一个C程序,通常是以.i作为文件扩展名。
编译阶段
编译器(ccl)将文本文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序。汇编语言程序中的每条语句都以一种标准的文本格式确切的描述了一条低级机器语言指令。
汇编阶段
汇编器(as)将hello.s翻译成机器语言指令,把这些指令打包成一种可重定位目标程序的格式,并将结果保存在目标文件hello.o中。hello.o文件是一个二进制文件,它的字节编码是机器语言指令而不是字符,如果我们在文本文件中打开hello.o文件,看到的将是一堆乱码。
链接阶段
链接器(ld)负责处理合并目标代码
将主程序以及需要调用的库整合在一起,生成一个可执行目标文件,可以被加载到内存中,由系统执行。
其中,库分为动态库和静态库,他们的介绍和使用可以参考这篇文章:http://t.csdnimg.cn/GUdrx
原码,反码, 补码
原码即十进制数的二进制形式,在最高位加上一个符号位1,0代表正数,1代表负数
若某数为正数或者0,则原码=反码=补码
若某数为负数,反码等于原码除符号位之外每位取反,补码等于反码+1
引入补码的原因为解决计算机通过原码进行2+(-2)这类运算会出错的问题,在计算机中只有设计了加法运算器,所以若需要进行减法运算入2-2,则只能利用加法2+(-2)进行运算,而利用原码进行带有负数的加法运算会出错,于是引出了补码。
总结:在计算机中,存储,运算时的数据形式都为补码,输出形式为原码
浮点数
M x 2^E M为尾数,E为阶数,2为基数,计算机领域默认基数为2
C中的float和double数据类型采用IEEE 754 规则
float,共 4个字节 32位,第1位最高位为符号位,8位指数位,23位尾数位
double,共 8个字节 64位,第1位最高位为符号位,11位指数位,52位尾数位
C语言中整型数据、浮点型数据在内存中的存储(超详细)_c语言 32位浮点型 整型-CSDN博客
左值和右值的区别
- 左值是可寻址的变量,有持久性;
- 右值一般是不可寻址的常量,或在表达式求值过程中创建的无名临时对象,短暂性的。
左值(lvalue)和右值(rvalue)最先来源于编译。在C语言中表示位于赋值运算符两侧的两个值,左边的就叫左值,右边的就叫右值。
定义:
左值指的是如果一个表达式可以引用到某一个对象,并且这个对象是一块内存空间且可以被检查和存储,那么这个表达式就可以作为一个左值。
右值指的是引用了一个存储在某个内存地址里的数据。
从上面的两个定义可以看出,左值其实要引用一个对象,而一个对象在我们的程序中又肯定有一个名字或者可以通过一个名字访问到,所以左值又可以归纳为:左值表示程序中必须有一个特定的名字引用到这个值。而右值引用的是地址里的内容,所以右值又可以归纳为:右值表示程序中没有一个特定的名字引用到这个值。
++a的话因为返回结果和运算之后的a一样,所以++a返回的是真实的a,可以被重新赋值,所以可以作为左值。而a++返回的是运算之前的a,而此时a已经+1了,返回的数据其实是过去的a,它是另外复制出来的,而不是真正的a,所以无法被赋值,所以它只能是右值。
所以a++;在执行当中的顺序是,先把a的值复制出来,进行整体运算,然后再a=a+1。
————————————————
原文链接:https://blog.csdn.net/qq_33148269/article/details/78046207
结构体字节对齐
结构体字节对齐详解【含实例】-CSDN博客
强制类型转换
c语言中
强制类型转换的一般形式为:
(类型名)表达式
例如:
int a=7,b=2;
float y1,y2;
float y1=a/b;/*y1的值a/b为3.0*/
y2=(float)a/b;/*y2的值为3.5,float将a进行强制转换为实型,b也随之自动转换为实型*/
c++中
static_cast 相近类型之间的类型
reinterpret_cast 不相近类型之间的类型
const_cast 去掉对象const属性的转换
dynamic_cast 规范向下转换,转换是安全的
dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换)
向上转型:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则)
向下转型:父类对象指针/引用->子类指针/引用(用dynamic_cast转型是安全的)
注意:
1. dynamic_cast只能用于父类含有虚函数的类
2. dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0
dynamic_cast 规范向下转换,转换是安全的
dynamic_cast使用前提必须是多态,父类中必须有虚函数,无虚函数不可用
————————————————
原文链接:https://blog.csdn.net/zhang_si_hang/article/details/127239523
异常捕获
为什么存在异常处理
在程序运行时常会碰到一些错误,例如除数为 0、年龄为负数、数组下标越界等,这些运行时错误如果放任不管,系统就会执行默认的操作,终止程序运行,也就是我们常说的程序崩溃(Crash)。C++ 提供了异常(Exception)机制,让我们能够捕获运行时错误,给程序一次“起死回生”的机会,或者至少告诉用户发生了什么再终止程序。
而 C++ 异常处理机制就可以让我们捕获并处理这些错误,然后我们可以让程序沿着一条不会出错的路径继续执行,或者不得不结束程序,但在结束前可以做一些必要的工作,例如将内存中的数据写入文件、关闭打开的文件、释放分配的内存等。
程序的错误的三种分类
程序的错误大致可以分为三种,分别是语法错误、逻辑错误和运行时错误:
语法错误在编译和链接阶段就能发现,只有 100% 符合语法规则的代码才能生成可执行程序。语法错误是最容易发现、最容易定位、最容易排除的错误,程序员最不需要担心的就是这种错误。
逻辑错误是说我们编写的代码思路有问题,不能够达到最终的目标,这种错误可以通过调试来解决。
运行时错误是指程序在运行期间发生的错误,例如除数为 0、内存分配失败、数组越界、文件不存在等。C++ 异常(Exception)机制就是为解决运行时错误而引入的。
捕获异常的关键字和格式
异常提供了一种转移程序控制权的方式。C++ 异常处理涉及到三个关键字:try、catch、throw
throw: 当问题出现,程序通过throw抛出一个异常。
catch: 在你想要处理问题的地方,通过异常处理程序捕获异常。
try: try 块中的代码标识将被激活的特定异常。它后面允许跟着一个或多个 catch 块。
1.单类型异常捕获
try {
//保护区,就是可能会出错的代码
}
catch( ExceptionName e1 ) {
// 出错后,通过异常处理程序捕获异常
}
2.多类型异常捕获(使用多个catch捕获不同类型的异常)
try {
//保护区,就是可能会出错的代码
}
catch( ExceptionName e1 ) {
// 出错后,通过异常处理程序捕获异常
//如果try在不同场景会抛出不同异常,此时可尝试罗列多个 catch 语句,用于捕获不同类型异常
}
catch( ExceptionName e2 ) {
// catch 块
}
catch( ExceptionName eN ) {
// catch 块
}
3.全部类型捕获(缺点是你无法知道错误类型,只知道发送了错误)
try{
//保护区
}
catch(...) //这里使用...,表示会捕获所有类型的异常都会
{
// catch 块
}
————————————————
原文链接:https://blog.csdn.net/m1059247324/article/details/116228823
指针、引用、数组、内存
引用与指针区别
两者的定义和性质不同
指针是一个变量,存储的是一个地址,指向内存的一个存储单元;
引用是原变量的一个别名,跟原来的变量实质上是同一个东西,
进而衍生出一系列的不同,如自增运算,sizeof等
引用的本质:引用的本质在c++内部实现是一个指针常量,引用一旦被初始化之后就不能更改。
因此引用必须初始化,且不能更改
C 和 C++ 的一些区别,比如 new、delete 和 malloc、free 的区别
1.unique_ptr是c++11版本库中提供的智能指针,它直接将拷贝构造函数和赋值重载函数给禁用掉,因此,不让其进行拷贝和赋值。
2. shared_ptr的原理
shared_ptr采用的是引用计数原理来实现多个shared_ptr对象之间共享资源:
shared_ptr在内部会维护着一份引用计数,用来记录该份资源被几个对象共享。
当一个shared_ptr对象被销毁时(调用析构函数),析构函数内就会将该计数减1。
如果引用计数减为0后,则表示自己是最后一个使用该资源的shared_ptr对象,必须释放资源。
如果引用计数不是0,就说明自己还有其他对象在使用,则不能释放该资源,否则其他对象就成为野指针。
引用计数是用来记录资源对象中有多少个指针指向该资源对象。
3.weak_ptrd的使用
shared_ptr固然好用,但是它也会有问题存在。假设我们要使用定义一个双向链表,如果我们想要让创建出来的链表的节点都定义成shared_ptr智能指针,那么也需要将节点内的_pre和_next都定义成shared_ptr的智能指针。如果定义成普通指针,那么就不能赋值给shared_ptr的智能指针。
所以在定义双向链表或者在二叉树等有多个指针的时候,如果想要将该类型定义成智能指针,那么结构体内的指针需要定义成weak_ptr类型的指针,防止循环引用的出现。————————————————
原文链接:https://blog.csdn.net/sjp11/article/details/123899141
21、野指针是什么?如何避免野指针?
野指针概念:指向内存被释放的内存或者没有访问权限的内存的指针。
如何避免野指针:
对指针进行初始化
①将指针初始化为NULL。
char * p = NULL;
②用malloc分配内存
char * p = (char * )malloc(sizeof(char));
③用已有合法的可访问的内存地址对指针初始化
char num[ 30] = {0};
char *p = num;
指针用完后释放内存,将指针赋NULL。
delete(p);
p = NULL;
一些关键字的作用:static、const、volatile、extern
面向对象
面向对象的三个基本特征
面向对象的三个基本特征是:封装、继承、多态。其中,封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块(类);它们的目的都是为了——代码重用。而多态则是为了实现另一个目的——接口重用!
什么是封装?
封装可以隐藏实现细节,使得代码模块化;封装是把过程和数据包围起来,对数据的访问只能通过已定义的界面。面向对象计算始于这个基本概念,即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。在面向对象编程上可理解为:把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏
什么是继承?
继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。其继承的过程,就是从一般到特殊的过程。
通过继承创建的新类称为“子类”或“派生类”。被继承的类称为“基类”、“父类”或“超类”。要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现。在某些 OOP 语言中,一个子类可以继承多个基类。但是一般情况下,一个子类只能有一个基类,要实现多重继承,可以通过多级继承来实现。
继承的实现方式?
继承概念的实现方式有三类:实现继承、接口继承和可视继承。
1. 实现继承是指使用基类的属性和方法而无需额外编码的能力;
2. 接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力;
3. 可视继承是指子窗体(类)使用基窗体(类)的外观和实现代码的能力。
什么是多态?
指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。C++支持两种多态性:编译时多态性,运行时多态性。
- 静态多态:函数重载和运算符重载属于静态多态,复用函数名
- 动态多态:派生类和虚函数实现运行时多态
我们一般讨论的多态是动态多态。
多态性(polymorphisn)是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。
例如:现有基类B,子类S,一个基类类型的指针p可以指向子类类型S的对象,但是调用函数时如果是编译期绑定,那如果使用p去调用成员函数则会调用基类B的成员函数,但我们想要调用的是子类S的成员函数,为了解决这一问题,我们便想让函数在运行期进行绑定。要实现这一点,就得在基类B的成员函数前加上Virtual关键字,然后在子类覆盖override这个成员函数。
同时,virtual的工作原理是什么呢?只要基类中成员函数有virtual关键字,并且在子类覆盖成员函数,则该基类和所有子类都会获得一张虚函数表和虚函数指针,该表对应着各子类的虚成员函数,通过操作虚函数指针来调用这些虚成员函数。而基类指针是可以调用这个虚函数指针的,也就意味着可以通过基类指针调用各个子类的虚成员函数,于是,就能在运行期通过基类指针->虚函数指针,去找到子类的虚成员函数,这就实现了运行期绑定。也就是动态多态。
正如这篇C++实现多态的方式 (opens new window)文章所说,C++ 中的动态多态性是通过虚函数实现的。
当基类指针或引用指向一个派生类对象时,调用虚函数时,实际上会调用派生类中的虚函数,而不是基类中的虚函数。
在底层,当一个类声明一个虚函数时,编译器会为该类创建一个虚函数表(Virtual Table)。 这个表存储着该类的虚函数指针,这些指针指向实际实现该虚函数的代码地址。
每个对象都包含一个指向该类的虚函数表的指针,这个指针在对象创建时被初始化,通常是作为对象的第一个成员变量。
当调用一个虚函数时,编译器会通过对象的虚函数指针查找到该对象所属的类的虚函数表,并根据函数的索引值(通常是函数在表中的位置,编译时就能确定)来找到对应的虚函数地址。
然后将控制转移到该地址,实际执行该函数的代码。
对于派生类,其虚函数表通常是在基类的虚函数表的基础上扩展而来的。
在派生类中,如果重写了基类的虚函数,那么该函数在派生类的虚函数表中的地址会被更新为指向派生类中实际实现该函数的代码地址。
作者: 编程指北
链接: https://csguide.cn/cpp/object_oriented/virtual_function.html#%E5%8A%A8%E6%80%81%E5%A4%9A%E6%80%81%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86
来源: https://csguide.cn
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
C++基础:什么是C++的多态性_多态性c++-CSDN博客
多继承、菱形继承、虚继承等
多继承语法
C++允许一个类继承多个类
语法:
class 子类:继承方式 父类1,继承方式 父类2
- 1
多继承可能会引发父类中有同名成员出现,需要加作用域区分
菱形继承
菱形继承概念:
两个派生类继承同一个基类,又有某个类同时继承这两个派生类,这种继承称为菱形继承,或者钻石继承。
典型的菱形继承案例
菱形继承问题:
- 羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。
- 草泥马继承动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。
虚继承
1、什么是虚继承?
虚拟继承(Virtual Inheritance),解决从不同途径继承来的同名的数据成员在内存中有不同的拷贝造成数据不一致问题,将共同基类设置为虚基类。这时从不同的路径继承过来的同名数据成员在内存中就只有一个拷贝,同一个函数名也只有一个映射。
2、虚继承的语法是?
在派生类继承基类时(即在声明派生类的时候),加上一个virtual关键词则为虚拟继承.
class 派生类名:virtual 继承方式 基类名
{
。。。。。。
}
class 派生类: virtual 基类1,virtual 基类2,...,virtual 基类n
{
...//派生类成员声明
};
说明:在多继承情况下,虚基类关键字的作用范围和继承方式关键字相同,只对紧跟其后的基类起作用。声明了虚基类之后,虚基类在进一步派生过程中始终和派生类一起,维护同一个基类子对象的拷贝。
4、执行顺序及优先级
首先执行虚基类的构造函数,多个虚基类的构造函数按照被继承的顺序构造;
执行基类的构造函数,多个基类的构造函数按照被继承的顺序构造;
执行成员对象的构造函数,多个成员对象的构造函数按照申明的顺序构造;
执行派生类自己的构造函数;
析构以与构造相反的顺序执行;
从虚基类直接或间接派生的派生类中的构造函数的成员初始化列表中都要列出对虚基类构造函数的调用。但只有用于建立对象的最派生类的构造函数调用虚基类的构造函数,而该派生类的所有基类中列出的对虚基类的构造函数的调用在执行中被忽略,从而保证对虚基类子对象只初始化一次。
虚继承原理:C++虚继承的实现原理、内存分布、作用_虚继承基类与派生类对象的转换原理及内存分配结构-CSDN博客
多态,虚函数,虚函数表
1. 什么是多态?
多态就是多种形态,C++的多态分为静态多态与动态多态。静态多态就是重载,因为在编译期决议确定,所以称为静态多态。动态多态就是通过继承重写基类的虚函数实现的多态,因为是在运行时决议确定,所以称为动态多态。运行时在虚函数表中寻找调用函数的地址。
c++的多态性用一句话概括:在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数。
————————————————
原文链接:https://blog.csdn.net/m0_37715028/article/details/124758197
2. 多态的实现原理
1. 用virtual关键字声明的函数叫做虚函数,虚函数肯定是类的成员函数。
2. 存在虚函数的类都有一个一维的虚函数表叫做虚表。当类总声明虚函数时,编译器会在类中生成一个虚函数表。
3. 类的对象有一个指向虚表开始的虚指针,使调用虚函数时,能够找到正确函数。虚表和类是对应的,虚表指针和对象是对应的。
补充:虚函数表是一个存储类成员函数指针的数据结构,是由编译器自动生成和维护的。 当存在虚函数时,每个对象中都有一个指向虚函数的指针vptr,vptr一般作为类对象的第一个成员。
————————————————
原文链接:https://blog.csdn.net/m0_37715028/article/details/124758197
纯虚函数
什么是纯虚函数 纯虚函数的作用 如何定义使用纯虚函数
一 定义:
纯虚函数是一种特殊的虚函数,它的一般格式如下:
class <类名>
{
virtual <类型><函数名>(<参数表>)=0;
…
};
在许多情况下,在基类中不能对虚函数给出有意义有实现,而把它说明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。
二 引入原因:
1、为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。
2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。
为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重载以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。
三 特性:
1、多态性
指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。C++支持两种多态性:编译时多态性,运行时多态性。
a.编译时多态性:通过重载函数实现
b 运行时多态性:通过虚函数实现。
2、虚函数
虚函数是在基类中被声明为virtual,并在派生类中重新定义的成员函数,可实现成员函数的动态重载
3、抽象类
包含纯虚函数的类称为抽象类。由于抽象类包含了没有定义的纯虚函数,所以不能定义抽象类的对象。
NULL与nullptr的区别
STL
c++11
NULL与nullptr的区别
在 C++11 之前,我们通常使用 NULL 来表示空指针。
然而,在 C++ 中,NULL 的定义实际上是一个整数值 0,而不是一个真正的指针类型。
在函数重载和模板编程中这可能会导致一些问题和歧义。
为了解决这个问题,C++11 引入了一个新的关键字 nullptr,用于表示空指针。
nullptr 是一种特殊类型的字面值,类型为 std::nullptr_t,定义为: typedef decltype(nullptr) nullptr_t,可以隐式转换为任何指针类型。
与 NULL 不同,nullptr 是一个真正的指针类型,因此可以避免一些由于 NULL 是整数类型而引起的问题。
作者: 编程指北
链接: https://csguide.cn/cpp/modern_cpp/nullptr.html#%E5%87%BD%E6%95%B0%E9%87%8D%E8%BD%BD
来源: https://csguide.cn
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
RAII(资源获取即初始化)思想
C++RAII(Resource Acquisition Is Initialization 资源获取即初始化)是什么?
C++RAII是一种编程技术,旨在通过对象的生命周期来管理资源的获取和释放。RAII的核心思想是:在对象的构造函数中获取资源,在对象的析构函数中释放资源。这样可以确保资源在任何情况下都会被正确释放,从而避免资源泄漏。
RAII技术在C++中使用得广泛,其中最常见的应用是使用智能指针,如shared_ptr和unique_ptr,来管理动态内存,以确保内存的自动回收。RAII技术还可以用于管理其它类型的资源,如文件句柄、网络连接、互斥锁等。RAII的优点是使得资源管理变得简单、安全、可靠,并且代码易于维护。
RAII的原理
RAII利用了C++语言的一个特性:对象的构造函数在创建对象时自动调用,而析构函数在对象销毁时自动调用。通过将资源的获取和释放与对象的构造和析构绑定在一起,RAII确保了资源的正确管理。
当一个RAII对象被创建时,它会自动获取所需的资源,无论是通过分配内存、打开文件、建立网络连接还是其他形式的资源获取。当对象超出其作用域或者显式地被销毁时,它的析构函数会被调用,从而自动释放资源。
————————————————
原文链接:https://blog.csdn.net/Dontla/article/details/129069299
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/Dontla/article/details/129069299
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/Dontla/article/details/129069299
智能指针:shared_ptr、weak_ptr、unique_ptr的使用和原理
(1) shared_ptr
实现原理:采用引用计数器的方法,允许多个智能指针指向同一个对象,每当多一个指针指向该对象时,指向该对象的所有智能指针内部的引用计数加1,每当减少一个智能指针指向对象时,引用计数会减1,当计数为0的时候会自动的释放动态分配的资源。
1) 智能指针将一个计数器与类指向的对象相关联,引用计数器跟踪共有多少个类对象共享同一指针;
2) 每次创建类的新对象时,初始化指针并将引用计数置为1;
3) 当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;
4) 对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;
5) 调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。
(2) unique_ptr
unique_ptr采用的是独享所有权语义,一个非空的unique_ptr总是拥有它所指向的资源。转移一个unique_ptr将会把所有权全部从源指针转移给目标指针,源指针被置空;所以unique_ptr不支持普通的拷贝和赋值操作,不能用在STL标准容器中;局部变量的返回值除外(因为编译器知道要返回的对象将要被销毁);如果你拷贝一个unique_ptr,那么拷贝结束后,这两个unique_ptr都会指向相同的资源,造成在结束时对同一内存指针多次释放而导致程序崩溃。
(3) weak_ptr
weak_ptr:弱引用。 引用计数有一个问题就是互相引用形成环(环形引用),这样两个指针指向的内存都无法释放。需要使用weak_ptr打破环形引用。weak_ptr是一个弱引用,它是为了配合shared_ptr而引入的一种智能指针,它指向一个由shared_ptr管理的对象而不影响所指对象的生命周期,也就是说,它只引用,不计数。如果一块内存被shared_ptr和weak_ptr同时引用,当所有shared_ptr析构了之后,不管还有没有weak_ptr引用该内存,内存也会被释放。所以weak_ptr不保证它指向的内存一定是有效的,在使用之前使用函数lock()检查weak_ptr是否为空指针。
————————————————
原文链接:https://blog.csdn.net/lizhentao0707/article/details/81156384