C++ 零碎笔记

image.png

常引用(Const Reference)

  • 引用可以被const修饰,这样就无法通过引用修改数据了,可以称为常引用。
  • const必须写在&符号的左边,才能算常引用
  • const引用的特点
  • 可以指向临时数据(常量、表达式、函数返回值等)
  • 可以指向不同类型的数据
  • 作为函数参数时(此规则也适用于const指针)
    √可以接受const和非const实参(非const引用,只能接受非const实参)
    √可以跟非const引用构成重载
    (如果不是指针或引用,const和非const不构成重载)
    当引用指向了不同类型的数据时,会产生临时变量,即引用指向的并不是初始化时的那个变量。
    image.png

    const修饰它的右边
    image.png

    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.png

image.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.提供一个公共的访问单例对象的接口
(用指针是因为堆空间更加灵活)
❗** 实际开发中需要考虑多线程问题。(一般共享的东西,都需要考虑线程安全问题)**

单例模式示例.png

image.png

delete ms_rocket,仅仅是将指针指向的堆空间回收掉。但是指针变量依然存储着对空间的地址值。

delete的误区

delete后依然是有值的,需要自己去清空。即使P = NULL也只是指针为NULL,原来指向堆空间存放的东西也没有清空,因为没必要。

image.png

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)。
友元函数(friend只要放class的大括号里就行)).png

内部类

如果将类A定义在类C的内部,那么类A就是一个内部类(嵌套类)

内部类的特点

  • 支持publicprotedtedprivate权限
  • 成员函数可以直接访问其外部类对象的所有成员(反过来则不行)
  • 成员对象可以直接不带类名、对象名访问其外部类的static成员
  • 不会影响外部类的内存布局
  • 可以在外部类内部声明,在外部类外面定义
内部类,可以控制类的访问权限.png
不影响外部类内存布局.png
内部类声明和实现分离1.png
内部类声明和实现分离2.png

局部类

在一个函数内部定义的类,称为局部类

局部类的特点

  • 作用域仅限于所在的函数内部
  • 其所有的成员必须定义在类内部,不允许static成员变量。
  • 成员函数不能直接访问函数的局部变量(static 变量除外) (相当于一个栈空间怎么能直接用另个栈空间的局部变量呢)
一编译函数代码都会放在代码区class Car和栈空间一点关系没有.png

(执行test函数只有 Class Car不会执行,下面两句才是会被执行的)

局部类和内部类,仅仅是访问权限的问题。把类放在函数内,就表示只有函数里面访问。不会影响内存布局。

运算符重载(operator overload)

  • 运算符重载(操作符重载):可以为运算符增加一些新的功能
  • 全局函数、成员函数都支持运算符重载
    最好加上引用和const.png

    (运算符默认从左到右)

    为了保存重载前的一些特性.png

    (const对象只能调用const函数)
    重载+=.png

    为了能够(p1+=p2)=Point(50,60)这种操作.png

    ==.png

image.png

8分钟
++.png

image.png

左移运算符的重载得是全局的,否则左边就跟类有关了。但是这样实现不能连续打印.png

为了能够连续打印.png

运算符重载输入

image.png

十五分钟左右,(cout<<p1) = count为什么会报错的问题

image.png

单例模式的完善(好早之前讲的单例模式,说讲完重载再完善,还真就!)

因此拷贝构造函数也需要私有化.png

image.png
image.png

运算符重载父类

image.png

仿函数

仿函数: 将一个对象当作一个函数来使用
对比普通函数,它作为对象可以保存状态。

image.png
image.png

运算符重载注意点

image.png

模板(template)

泛型,是一种将类型参数化以达到代码复用的计数,C++中使用模板来实现泛型。
(写一份代码,编译器帮忙生成多份。如果没有用到模板,编译器不会去生成,用到什么生成什么)

模板的使用格式如下

  • template<typename\class T>
  • typenameclass是等价的
  • 模板没有被使用时,时不会被实例化出来的
  • 模板的声明和实现如果分离到.h和.cpp中,会导致链接错误
  • 一般将模板的声明和实现统一放到一个.hpp文件中
image.png

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 ⭐

一般用于多态类型的转换,有运行时安全检测

stu1直接变空.png

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++标准的发展.png

C++11新特性

auto

  • 可以从初始化表达式中推断出变量的类型,大大简化编程工作
  • 属于编译器特性,不影响最终的机器码质量,不影响运行效率


    image.png

decltype

image.png

nullptr

  • 可以解决NULL的二义性问题


    image.png

    image.png
int a = NULL;//
int *b = NULL;//以后对指针用建议用nullptr⭐

快速遍历

image.png

更加简洁的初始化方式

image.png

Lambda表达式 ⭐⭐

有点类似于JavaScript中的闭包、IOS中的Block,本质就是函数

Lambda表达式.png

image.png

//这样只是定义了一个lambda函数
[]{
  cout<<"func"<<endl;
 };
//写个小括号去调用一下
([]{
  cout<<"func"<<endl;
 };)()
image.png

image.png

❗ 想到函数指针还不大会

image.png
默认的捕获是值捕获.png
//地址捕获
auto func = [&a] {
  cout<< a <<endl;
 };

Lambda表达式 - 外部变量捕获

image.png

image.png

Lambda表达式 - mutable

image.png
image.png

C++14(并未普及到企业开发中,了解即可)

image.png

C++17(并未普及到企业开发中,了解即可)

image.png

设置C++版本.png

(只要限制作用域的都是编译器特性)

错误

常见错误

异常

  • 异常是一种在程序运行过程中可能会发生的错误(比如内存不够)
  • 异常没有被处理,会导致程序终止


    image.png
image.png
卡一段时间就闪退.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标准

内存泄漏.png
delete 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两次析构问题

两次析构.png

shared_ptr循环引用问题

导致的问题就是对象无法销毁


image.png

image.png

image.png

image.png

weak_ptr

image.png

weak_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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

友情链接更多精彩内容