C++ 零碎笔记
常引用(Const Reference)
- 引用可以被
const
修饰,这样就无法通过引用修改数据了,可以称为常引用。 - const必须写在&符号的左边,才能算常引用
- const引用的特点
- 可以指向临时数据(常量、表达式、函数返回值等)
- 可以指向不同类型的数据
- 作为函数参数时(此规则也适用于const指针)
√可以接受const和非const实参(非const引用,只能接受非const实参)
√可以跟非const引用构成重载
(如果不是指针或引用,const和非const不构成重载)
当引用指向了不同类型的数据时,会产生临时变量,即引用指向的并不是初始化时的那个变量。
image.png
const修饰它的右边
image.png
image.png
输出的是30和10
image.png类
- C++中可以用
struct、class
来定义一个类
struct和class的区别
- struct的默认成员权限是public
- class的默认成员权限是private
C++的struct可以定义函数,C语言是只能写成员变量
成员变量(属性)
成员函数(方法)
自己的成员函数可以访问自己的成员变量
⭐ 以前都是用struct模拟类,用函数指针
image.png image.png image.pngimage.png
上面代码中person对象、pPerson指针的内存都是在函数的栈空间,自动分配和回收的。
❗ struct和class的区别就在权限上
实际开发中,用class表示类比较多
对象的内存布局
image.png(内存对齐不大会...)
image.png
编译器在成员函数里提供一个指针this ,比如利用person1去调用run的时候就会将person1的地址值传进去。也即,this指针存储着函数调用者的地址,this指向了函数调用者。
image.png
image.png
点左边只能是对象
image.png
汇编代码,中括号里放的绝对是地址
原理:如何利用指针间接访问所指向对象的成员变量?
1.从指针中取出对象的地址
2.利用对象的地址 + 成员变量的偏移量计算出成员变量的地址
3.根据成员变量的地址访问成员变量的存储空间
如果用对象调用函数,会将对象的地址值传进去
如果通过指针间接的调用函数,会将指针中存储的地址值传进去。而不是将指针自己的地址传进去。
中断: interrupt
cc -> int3 : 起到断点的作用
(因为函数栈空间不小心被当成栈空间来执行也会被停止)
ip指针(寄存器)指向下一条需要执行的机器指令的地址。
一旦执行完一条指令 ip +=g刚执行完的机器指令的大小
调用函数、执行函数代码,其实就是CPU在访问代码区的内存(指令)
调用函数的时候,需要分配额外的存储空间来存储函数内部的局部变量。
函数在代码段的空间,是用来放函数代码(机器指令),代码区是只读的。也就是在代码区执行的同时,会在栈空间分配连续的一段存储空间给函数用。
函数代码存储在代码区,局部变量存储在栈空间。
封装
-
成员变量私有化,提供公共的getter和setter给外界去访问成员变量。
(struct默认访问权限是public)
set方法和get方法.png
内存空间的布局
每个应用都有自己独立的内存空间,其内存空间一般都有一下几大区域。
image.png
- 代码段(代码区): 用于存放代码(只读)
- 数据段(全局区) : 用于存放全局变量等
- 栈空间: 每调用一个函数就会给它分配一段连续的栈空间(存放函数中的局部变量),等函数调用完毕后会自动回收这段栈空间。(自动分配和回收)
-堆空间:需要主动区申请和释放
堆空间
在程序运行过程,为了能够自由控制内存的生命周期、大小,会经常使用堆空间的内存。
堆空间的申请/释放
- malloc/free
//指针的类型,完全看你想要什么类型的
int * p = (int *)malloc(4);//返回这四个字节的首地址(void*)
*p = 10;//这样就将10放入了堆空间
free(p);//malloc一次就free一次,申请4个字节也释放4个字节,不能只释放一部分
- new/delete
int *p = new int;
*p = 10;
delete p;
char *p = new char;//申请一个字节
*p = 10;
delete p;
- new[]/delete[]
char *p = new char[4];
delete[] p;
注意
- 申请堆空间成功后,会返回那一段内存空间的地址
- 申请和释放必须是1对1的关系,不然可能会存在内存泄漏
- 现在的很多高级编程语言不需要开发人员去管理内存(比如Java),屏蔽了很多内存细节,利弊同时存在。利:提高开发效率,避免内存使用不当或泄露。弊:不利于开发人员了解本质,永远停留在API调用和表层语法糖,对性能优化无从下手。
堆空间初始化
image.png//malloc是没有进行初始化的
memset(p,0.size);//从p地址开始,连续的size个字节中的**每一个字节**❗都设置为0
image.png
其他元素内初始化为0
int* p1 = new int;
int* p2 = new int();//右边多个小括号,会调用memory set。但是可能编译器不同比如Mac上的Xcode new int可能会初始化
int* p3 = new int(3);
image.png
对象的内存
对象的内存可以存在于3种地方
- 全局区(数据段):全局变量
- 栈空间:函数里面的局部变量
- 堆空间:动态申请内存(malloc、new等)
//全局区
Person g_person;
int main(){
//栈空间
Person person;
//堆空间
Person *p = new Person;//指针变量p在栈空间,Person变量在堆空间
return 0;
}
构造函数(Constructor)
构造函数(也叫构造器),在对象创建的时候自动调用,一般用于完成对象的初始化工作。
特点:
- 函数名与类同名,无返回值(void都不能写),可以有参数,可以重载,可以有多个构造函数
- 一旦自定义了构造函数,必须用其中一个自定义的构造函数来初始化对象。
注意
- 通过malloc分配的对象不会调用构造函数。(毕竟这个函数从C语言就开始有,malloc就只申请堆空间)
❗一个广为流传的、很多教程\书籍都推崇的错误结论:
默认情况下,编译器会为每一个类生成空的无参的构造函数原因:24分左右
正确理解:在某些特定的情况下,编译器才会为类生成空的无参的构造函数。
(哪些特定情况?以后再提)
默认情况下成员变量的初始化
析构函数(Destructor)
析构函数(也叫析构器),在对象销毁的时候自动调用,一般用于完成对象的清理工作。
特点
函数名以~开头, 与类同名,无返回值(void都不能写),无参,不可以重载,有且只有一个析构函数
注意
- 通过malloc分配的对象,free的时候不会调用析构函数
- 构造函数和析构函数声明于public,才能被外界正常使用
内存管理
懒得截图24min左右
声明和实现分离
类的声明一般放在.h文件
类的实现.cpp文件
对于引用:
系统自带的< >
我们实现的""
命名空间
命名空间可以用来避免命名冲突
命名空间不允许内存布局❓❓
using MJ::g_age;//using后不用加namespace 吗?可以
不可以,因为有二义性。如果对g_age加上前缀则可以。
命名空间是可以嵌套的
image.png
有个默认的全局命名 空间
1624546640(1).png
命名空间的合并
image.png
C++不能靠文件夹解决命名冲突
继承
继承,可以让子类拥有父类的所有的成员(变量\函数)
image.png
Java
//基类: 最基本的类。其他所有的类最终都会继承自它。类的老祖宗。
从父类继承的成员变量会排布再前面:
子类内部访问父类成员的权限,是以下2项中权限最小的那个
- 上一级父类的继承方式
-
成员本身的访问权限
开发中用的最多的继承方式是public ,这样能保留父类原来的成员访问权限
访问权限不影响对象的内存布局(因为就是直接拿过来)
(一般写C++类都会用公有继承,因为这样原来访问权限是什么后面就是什么)
image.png
class的继承默认是private继承,struct是public继承
初始化列表
特点
- 一种便捷的初始化成员变量的方式
- 只能用在构造函数中
- 初始化顺序只跟成员变量的声明顺序有关
构造函数的互相调用 (不太明白❗)
父类的构造函数
子类的构造函数默认会调用父类的无参构造函数
如果子类的构造函数显示地调用了父类的有参构造函数,就不会再去默认调用父类的无参构造函数
如果父类缺少无参的构造函数,子类的构造函数必须显示调用父类的有参构造函数。
构造、析构顺序
父类指针、子类指针
父类指针可以指向子类对象,是安全的,开发中经常用到(继承方式必须是public)⭐
多态⭐
- 默认情况下,编译器只会根据指针调用对应的函数,不存在多态。
- 多态是面对对象非常重要的一个特性。
- 同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。
- 在运行时,可以识别出真正的对象类型,调用对应子类的函数。
多态的要素
- 子类重写父类的成员函数(override)
- 父类指针指向子类对象
-
利用父类指针调用重写的成员函数
❓❓❓
看起来是猫实际是狗,调用输出是猫.png
虚函数
C++中的多态通过虚函数(virtual function)来实现
虚函数:被virtual
修饰的成员函数
如果父类是虚函数,子类重写自动是虚函数
只要再父类中声明为虚函数,子类中重写的函数也自动变成虚函数(也就是说子类可以省略virtual)。
只要看到E8开头的机器指令,对应就是直接call(直接写死的地址)。
FF开头的call,间接call(取出寄存器、内存中的东西,然后call)
虚表
image.png(把函数地址和对象绑定在一起)
(有虚函数就有虚表,虚表里放的是虚函数地址)
猫有猫的虚表,猪有猪的虚表。猫和猪的虚表是分开的。
所有的cat对象(不管在全局区、栈、堆)公用同一份虚表
F9然后按下F5,来看一下汇编
一个虚函数都没有,那就没有虚表,直接看指针类型是什么。
只有一个虚函数, 就放了一个
两个都是虚函数,子类只重写了一个。放两个Animal一个Cat一个
父类是虚函数,子类重写也是虚函数。但是子类是虚函数,父类可不是。
如果想执行父类的方法后再执行自己的方法.png
虚析构函数
含有虚函数的类,应该将析构函数声明为虚函数(虚析构函数)
如果存在父类指针指向子类对象的情况,樱桃该将析构函数声明为虚函数.png
(构造函数会先调用父类再调用子类)
纯虚函数
纯虚函数:没有函数体且初始化为0的虚函数,用来定义接口规范。
动物是能跑能叫的.png
抽象类(Abstract Class)
- 含有纯虚函数的类(被称为抽象类),不可以实例化(不可以创建对象)
- 抽象类也可以包含非纯虚函数、成员变量
- 如果父类是抽象类,子类没有完全实现(重写)纯虚函数,那么这个子类依然是抽象类
Java:抽象类、接口
OC:协议
多继承
C++允许一个类可以有多个父类(不建议使用,会增加程序设计复杂度)
(先继承哪个父类,内存中 哪个父类在前面)
多继承构造函数
多继承-虚函数
如果子类继承的多个父类都有虚函数,那么子类对象就会产生对应的多张虚表。
同名函数/成员变量
菱形继承
菱形继承带来的问题
- 最底层子类(Undergraduate)从基类(Person)继承的成员变量冗余、重复。
-
最底层子类无法访问基类的成员,有二义性❓
菱形继承.png
虚继承 ❓❓❓
虚继承可以解决菱形继承带来的问题
Person称为虚基类
image.png 虚继承代码示例.png
菱形继承的应用
静态成员(static)
静态成员:被static
修饰的成员变量\函数
可以通过对象(对象.静态成员)、对象指针(对象指针->静态成员)、类访问(类名::静态成员变量)
静态成员变量
- 存储在数据段(全局区,类似于全局变量),整个程序运行过程中只有一份内存。
- 对比全局变量,它可以设定访问权限(public、protected、private),达到局部共享的目的。
- 必须初始化,必须在类外面初始化,初始化时不能带
static
,如果类的声明和实现分离(在实现.cpp中初始化)
静态成员函数
-
内部不能使用this指针(this指针只能用在非静态成员函数内部)(成员函数,写在类里面的)
-
不能是虚函数(虚函数只能是非静态成员函数) (虚函数-->多态,多态是什么?父类指针指向子类对象,这就牵扯到了对象。静态函数允许用类去调用,而虚函数是通过类去调用)
-
内部不能访问非静态成员变量\函数,只能访问静态成员变量\函数。(∵非静态成员函数中隐含this指针,利用类调用静态函数,无法传地址给this)
-
非静态成员函数内部可以访问静态成员变量\函数
-
构造函数、析构函数不能是静态的
-
当声明和实现分离时,实现部分不能带static
(学东西要多问几个为什么)
(外面的全局变量和静态变量都是放在data segment)
static经典应用场景
一、统计创建了多少量车
二、单例模式
单例模式:设计模式的一种,保证某个类永远只创建一个对象。
1.构造函数\析构函数 私有化
2.定义一个私有的static成员变量指向唯一那个单例对象
3.提供一个公共的访问单例对象的接口
(用指针是因为堆空间更加灵活)
❗** 实际开发中需要考虑多线程问题。(一般共享的东西,都需要考虑线程安全问题)**
image.png
delete ms_rocket
,仅仅是将指针指向的堆空间回收掉。但是指针变量依然存储着对空间的地址值。
delete的误区
delete后依然是有值的,需要自己去清空。即使P = NULL
也只是指针为NULL,原来指向堆空间存放的东西也没有清空,因为没必要。
const成员
const成员:被const修饰的成员变量、非静态成员函数。
const成员变量
- 必须初始化(类内部初始化),可以在声明的时候直接初始化赋值。
- 非static 的const成员变量还可以在初始化列表中初始化
const成员函数(非静态)
-const 关键字写在参数列表后,函数的声明和实现都必须要带const
- 内部不能修改非static成员变量
- 内部只能调用const成员函数、static成员函数
- 非const成员函数可以调用const成员变量
- const成员函数和非const成员函数构成重载
- 非const对象(指针)优先调用非const成员函数
-
const对象(指针)只能调用const成员函数、static成员函数
image.png
image.png
引用类型成员
引用类型成员变量必须初始化(不考虑static)
-
在声明的时候直接初始化
image.png
拷贝构造函数(Copy Constructor)
浅拷贝:指针类型的变量只会拷贝地址值
深拷贝:将指针指向的内容拷贝到新的存储空间
- 拷贝构造函数是构造函数的一种
- 当利用已存在的对象创建一个新对象时(类似于拷贝),就会调用新对象的拷贝构造函数进行初始化
-
拷贝构造函数的格式是固定的,接收一个const引用作为参数
(对于基本数据类型不写拷贝构造函数,也可以实现对应功能,所以拷贝构造函数据需求写或不写)
image.png
调用父类的拷贝构造函数
image.png默认,会将已经存在对象的所有字节覆盖新对象的所有字节。
这里并没有调用拷贝构造函数(仅仅是简单的赋值操作).png
构造函数是在对象创建完马上调用的
❓ ??没有赋值就应该是0xcc?
子类的构造函数,默认会去调用父类无参的构造函数
浅拷贝、深拷贝
- 编译器默认的提供的拷贝是浅拷贝(shallow copy)
浅拷贝
- 将一个对象中所有成员变量的值拷贝到另一个对象
- 如果某个成员变量是个指针,只会拷贝指针中存储的地址,并不会拷贝指针指向的内存空间
- 可能会导致堆空间多次free的问题
如果要实现深拷贝,就需要自定义拷贝构造函数
- 将指针类型的成员变量所指向的内存空间,拷贝到新的内存空间
// 有问题的代码
const char* name = "changliang"; //注意这是个常量字符串,char前面需要加上const
//C语言里字符串本质就是字符数组,\0是字符串的结束标志
char name2 []= { 'a','b' ,'v','\0'};
cout << name2<<strlen(name2) << endl;
栈空间指向堆空间,堆空间又指向栈.png
❗ 堆空间指向栈空间都很危险,因为栈空间是不能自己去控制生命周期的,它随时都可能会被干掉。而堆空间可以自己控制。就可能指向已经被回收的内存。
所以应该这么做:
image.png
如果new一个东西的时候,右边如果加了大括号或小括号,会将申请的堆空间的数据清零。那为什么要进行清零操作呢,我要保证申请的一段内存空间最后一个字节是/0。
image.png
#include<iostream>
using namespace std;
class Car{
int m_price;
char* m_name;
public:
//Car(int price =0,char* m_name=NULL):m_price(price),m_name(m_name){}
Car(int price = 0, char* name = NULL) :m_price(price) {
if (name == NULL) return;
//申请新的空间
m_name = new char[strlen(name)+1] {};
//拷贝字符串数据到新的堆空间
memcpy(m_name, name,sizeof(name));
}
~Car() {
if (m_name == NULL)return;
delete[] m_name;
}
void display() {
cout << "Price is " <<m_price << ",name is " << m_name << endl;
}
};
int main() {
const char* name = "changliang"; //注意这是个常量字符串,char前面需要加上const
//C语言里字符串本质就是字符数组,\0是字符串的结束标志
char name2 []= { 'a','b' ,'v','\0'};
cout << name2<<strlen(name2) << endl;
Car* car = new Car(100, name2);
car->display();
getchar();
return 0;
}
image.png
上述代码还是存在问题,如图
可能会有double free问题.png
对象类型参数和返回值(不建议这么干,一般用引用)
使用对象类型作为函数的参数或者返回值,可能会产生一些不必要的中间对象。
作为函数参数.png 作为返回值.png
(main函数会预留一个car对象的地址,并传给test2,test2在销毁前将car拷贝构造到main的地址空间。然后car2 = test2();就仅仅是赋值,不存在拷贝构造)
image.png(直接把test2返回的car拷贝构造给car3)
匿名对象(临时对象)
匿名对象:没有变量名、没有指针指向的对象,用完后马上调用析构。
匿名对象(一次性对象,用完就扔).png 回顾一下.png 让构建出来的对象直接变成参数了.png image.png隐式构造
C++中存在隐式构造的现象:某些情况下,会隐式调用单参数的构造函数。
会调用单参数的构造函数.png
image.png image.png
编译器自动生成的构造函数
很多教程都说:编译器会为每一个类都是生成空的无参的构造函数。错❌
说白了根本没有调用Person构造函数.png
C+的编译器在某些特定情况下,会给类自动生成无参的构造函数,比如 :
- 成员变量在声明的同时进行了初始化。
- 有定义虚函数
- 虚继承了其它类
- 包含了对象类型的成员,且这个成员有构造函数(编译器生成或自定义)
- 父类有构造函数(编译器生成或自定义) (∵父类一旦有构造函数,子类需要优先调用父类构造函数 )
总结一下
- 对象创建后,需要做一些额外操作时(比如内存操作、函数调用),编译器一般都会为其自动生成无参的构造函数
(我们说的构造函数,都是说创建完对象的那一刻,你要不要做什么事情。就这一句的时刻Student student;
)
[图片上传失败...(image-2fa9b5-1625309958498)]
image.png image.png 且最前面的四个字节是用来存储虚表地址.png友元
- 友元包括友元函数和友元类。
- 如果将函数A(非成员函数)声明为类C的友元函数,那么在函数A内部就能直接访问类C对象的所有成员(无论私有还是protected)。
内部类
如果将类A定义在类C的内部,那么类A就是一个内部类(嵌套类)
内部类的特点
- 支持
public
、protedted
、private
权限 - 成员函数可以直接访问其外部类对象的所有成员(反过来则不行)
- 成员对象可以直接不带类名、对象名访问其外部类的
static
成员 - 不会影响外部类的内存布局
- 可以在外部类内部声明,在外部类外面定义
局部类
在一个函数内部定义的类,称为局部类
局部类的特点
- 作用域仅限于所在的函数内部
- 其所有的成员必须定义在类内部,不允许
static
成员变量。 - 成员函数不能直接访问函数的局部变量(
static
变量除外) (相当于一个栈空间怎么能直接用另个栈空间的局部变量呢)
(执行test函数只有 Class Car不会执行,下面两句才是会被执行的)
局部类和内部类,仅仅是访问权限的问题。把类放在函数内,就表示只有函数里面访问。不会影响内存布局。
运算符重载(operator overload)
- 运算符重载(操作符重载):可以为运算符增加一些新的功能
- 全局函数、成员函数都支持运算符重载
最好加上引用和const.png
(运算符默认从左到右)
为了保存重载前的一些特性.png
(const对象只能调用const函数)
重载+=.png
为了能够(p1+=p2)=Point(50,60)这种操作.png
==.png
8分钟
++.png
image.png 左移运算符的重载得是全局的,否则左边就跟类有关了。但是这样实现不能连续打印.png
为了能够连续打印.png
运算符重载输入
image.png十五分钟左右,(cout<<p1) = count为什么会报错的问题
单例模式的完善(好早之前讲的单例模式,说讲完重载再完善,还真就!)
因此拷贝构造函数也需要私有化.pngimage.png image.png
运算符重载父类
image.png仿函数
仿函数: 将一个对象当作一个函数来使用
▪ 对比普通函数,它作为对象可以保存状态。
运算符重载注意点
image.png模板(template)
泛型,是一种将类型参数化以达到代码复用的计数,C++中使用模板来实现泛型。
(写一份代码,编译器帮忙生成多份。如果没有用到模板,编译器不会去生成,用到什么生成什么)
模板的使用格式如下
template<typename\class T>
-
typename
和class
是等价的 - 模板没有被使用时,时不会被实例化出来的
- 模板的声明和实现如果分离到.h和.cpp中,会导致链接错误
- 一般将模板的声明和实现统一放到一个.hpp文件中
image.png image.png image.png image.png
模板 - 动态数组
抛出异常.png image.png模板 - 类
void* 万能指针,只要对象是地址就行
image.png
类型转换
image.png(1) const_cast
image.png没有区别,只是不同语言的风格骗骗编译器.png
(2) dynamic_cast ⭐
一般用于多态类型的转换,有运行时安全检测
image.png
image.png
(3) static_cast
- 对比
dynamic_cast
,缺乏运行时的安全检测。 - 不能交叉转换(不是同一继承体系的,无法转换)
- 常用于基本数据类型的转换、非const转成const
-
使用范围较广
image.png
image.png
reinterpret_cast
- 属于比较底层的强制转换,没有任何类型检查和格式转换,仅仅是简单的二进制数据拷贝。
- 可以交叉转换
-
可以将指针和整数互相转换
image.png
image.png
int转double不是简简单单的将a中的二进制字节拷贝给d.png
image.png
image.png
C++标准的发展
C++标准的发展.pngC++11新特性
auto
- 可以从初始化表达式中推断出变量的类型,大大简化编程工作
-
属于编译器特性,不影响最终的机器码质量,不影响运行效率
image.png
decltype
image.pngnullptr
-
可以解决NULL的二义性问题
image.png
image.png
int a = NULL;//
int *b = NULL;//以后对指针用建议用nullptr⭐
快速遍历
image.png更加简洁的初始化方式
image.pngLambda表达式 ⭐⭐
有点类似于JavaScript中的闭包、IOS中的Block,本质就是函数
image.png
//这样只是定义了一个lambda函数
[]{
cout<<"func"<<endl;
};
//写个小括号去调用一下
([]{
cout<<"func"<<endl;
};)()
image.png
image.png
image.png 默认的捕获是值捕获.png❗ 想到函数指针还不大会
//地址捕获
auto func = [&a] {
cout<< a <<endl;
};
Lambda表达式 - 外部变量捕获
image.pngimage.png
Lambda表达式 - mutable
image.png image.pngC++14(并未普及到企业开发中,了解即可)
image.pngC++17(并未普及到企业开发中,了解即可)
image.png设置C++版本.png
(只要限制作用域的都是编译器特性)
错误
常见错误异常
- 异常是一种在程序运行过程中可能会发生的错误(比如内存不够)
-
异常没有被处理,会导致程序终止
image.png
一旦有抛出异常,后面的代码都不会执行。除非有人把它catch住了。 (如果没有catch会一直往外抛,直至操作系统 )
image.png
image.png
image.png
throw异常后,会在当前函数中查找匹配的catch,找不到就终止当前代码,去上一层哈纳树中查找,如果都找不到匹配的catch,整个程序就会终止。
异常的抛出声明
image.png自定义异常类型
image.png image.png image.png const对象也只能调用const函数.png那子类的函数也都得加个const.png
拦截所有异常
image.png标准异常
image.png image.png智能指针(Smart Pointer)
传统指针存在问题
- 需要手动管理内存
- 容易发生内存泄漏(忘记释放、出现异常等)
- 释放之后产生野指针
智能指针就是为了解决传统指针存在的问题
(智能指针内存在栈空间)
auto_ptr: 属于C++98标准,在C++11中已经不推荐使用(有缺陷, 比如不能用于数组 )
//可以理解为:智能指针p指向了对空间的Person对象
auto_ptr<Person> p(new Person);
//Person对象的生命周期跟随智能指针
shared_ptr: 属于C++11标准
unique_ptr: 属于C++11标准
内存泄漏.pngdelete p;
p = nullptr;//防止利用p再指
智能指针的自实现.png
如果加上explicit则不能隐式调用.png
注意智能指针千万别指向栈空间的东西,要指堆空间
double free.png
自制智能指针.png
shared_ptr
shared_ptr的设计理念
多个shared_ptr可以指向同一个对象,当最后一个shared_ptr在作用范围内结束时,
- 可以通过一个已存在的智能指针初始化一个新的智能指针
shared_ptr<Person> p1(new Person());
shared_ptr<Person>p2(p1);
- 针对数组的用法
shared_ptr<Person> ptr1(new Person[5]{},[](Person* p){delete[] p;});//如果非不些[]就得传lambda表达式
shared_ptr<Person[]>persons(new Person[5]{});//√
shared_ptr的原理
- 一个
shared_ptr
会对一个对象产生强引用(strong reference) - 每个对象都有个与之对应的强引用计数,记录着当前对象被多少个
shared_ptr
强引用着 - 当有一个新的
shared_ptr
指向对象时,对象的强引用计数就会+1 - 当有一个
shared_ptr
销毁时(比如作用域结束),对象的强引用计数就会-1 -
当一个对象的强引用计数为0时(没有任何shared_ptr指向对象时),对象就会自动销毁(析构)
1 2 3 4.png
shared_ptr两次析构问题
两次析构.pngshared_ptr循环引用问题
导致的问题就是对象无法销毁
image.png
image.png
image.png
image.png
weak_ptr
image.pngweak_ptr解决循环引用.png
unique_ptr
- unique_ptr也会对一个对象产生强引用,它可以确保同一时间只有1个指针指向对象
- 当unique_ptr销毁时(作用域结束时),其指向的对象也就自动销毁了
- 可以使用
std::move
函数转移unique_ptr的所有权
image.png
image.png
外挂项目
- 外挂界面
- 事件处理
- 跨进程访问
外挂界面
Windows平台的桌面开发
- C++: MFC、Qt
-
C#:WinForm、WPF
这里选用最古老的MFC,不用引入其它外部的框架
要先安装好 MFC组件
image.png
image.png
image.png
image.png
ctrl+F