美文网首页
C++ 零碎笔记

C++ 零碎笔记

作者: 吃掉夏天的怪物 | 来源:发表于2021-07-03 19:03 被阅读0次
    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

    相关文章

      网友评论

          本文标题:C++ 零碎笔记

          本文链接:https://www.haomeiwen.com/subject/nnbbyltx.html