当前位置: 首页 > news >正文

C++知识点汇总

预处理\编译\汇编\链接

静态链接:在生成可执行文件的时候(链接阶段),把所有需要的函数的二进制代码都包含到可执行文件中去

优点:在程序发布的时候就不需要依赖库,也就是不再需要带着库一块发布,程序可以独立执行。

缺点:

  1. 浪费内存空间。在多进程的操作系统下,同一时间,内存中可能存在多个相同的公共库函数。
  2. 程序的开发与发布流程受模块制约。 只要有一个模块更新,那么就需要重新编译打包整个代码。

动态链接:在编译的时候不直接拷贝可执行代码,而是通过记录一系列符号和参数,在程序运行或加载时将这些信息传递给操作系统,操作系统负责将需要的动态库加载到内存中,然后程序在运行到指定的代码时,去共享执行内存中已经加载的动态库可执行代码,最终达到运行时连接的目的。

优点: 解决了静态链接的缺陷,更适应现代的大规模的软件开发

  1. 更加节省内存并减少页面交换;
  2. DLL文件与EXE文件独立,只要输出接口不变(即名称、参数、返回值类型和调用约定不变),更换DLL文件不会对EXE文件造成任何影响,因而极大地提高了可维护性和可扩展性;
  3. 不同编程语言编写的程序只要按照函数调用约定就可以调用同一个DLL函数
  4. 适用于大规模的软件开发,使开发过程独立、耦合度小,便于不同开发者和开发组织之间进行开发和测试

缺点:

  1. 结构复杂
    1. 对于静态链接来说,系统只需要加载一个文件(可执行文件)到内存即可,但是在动态链接下,系统需要映射一个主程序和多个动态链接模块,因此,相比于静态链接,动态链接使得内存的空间分布更加复杂。
    2. 不同模块在内存中的装载位置一定要保证不一样
  2. 由于是运行时加载,可能会影响程序的前期执行性能
  3. 引入了安全问题,这也是我们能够进行PLT HOOK的基础。

 

1.封装\继承\多态

1.1.概念介绍

        1. 封装:将类的某些信息隐藏在内部,不允许外部程序直接访问

        2. 继承:子类继承父类的特征和行为,提高代码复用性

        3. 多态:同一个行为(函数接口),具有多个不同的表现形式

1.2.封装修饰符private\protected/public

        1. public:public表明该数据成员、成员函数是对所有用户开放的,所有用户都可以直接进行调用
        2.private:private表示私有,私有的意思就是除了class自己之外,任何人都不可以直接使用,私有财产神圣不可侵犯嘛,即便是子女,朋友,都不可以使用。
        3.protected:protected对于子女、朋友来说,就是public的,可以自由使用,没有任何限制,而对于其他的外部class,protected就变成private。

1.3.继承

1.3.1.构造函数和析构函数

构造函数:先构造父类,再构造子类

析构函数:先析构子类,再析构父类

1.3.2.成员初始化列表的初始化顺序

结论:与成员函数定义的顺序有关(与成员初始化列表中书写的顺序无关)

1.4.多态

详解

实现:①父类的指针指向子类的对象 ②父类有virtual函数 ③子类重写了父类的virtual函数

原理:虚函数表VTable、虚函数指针Vptr

1.4.1.虚函数表VTable

        1.1. 虚函数表是编译器在编译时创建的,一个类只存在一份,所有的类对象共用这一张表

        1.2. 虚函数表的内容,有下面2种情况

        ①如果派生类中重写了基类的虚函数,那么派生类虚函数表中的函数地址就是派生类的函数地址

        ②如果没有重写基类的虚函数,那么派生类虚函数表中的函数地址依然是基类的函数地址

1.4.2. 虚函数指针vptr

        2.1. 所有类对象存在Vptr指针,该指针处于该类对象存储空间的起始位置偏移量为0

        2.2. Vptr指向该虚函数表

1.4.3. 父类的指针指向子类的对象,调用重写的方法,发生多态

        如Animal* p = New Cat(); 因为指针p指向的是子类Cat,所以,指针p的虚表指针Vptr指向的是Cat子类的虚函数表(而不是Animal父类的虚函数表)===> 这样,如果调用了重写的方法,就会发生多态

1.5.纯虚函数

纯虚函数:vritual=0 声明的函数,是纯虚函数

抽象类:包含纯虚函数的类,是抽象类

抽象类:不能被实例化,只能被继承

1.6.友元函数/友元类 note

2.重载\覆盖\隐藏

2.1.重载

        1. 同一个作用域范围

        2. 函数名相同

        3. 参数列表不同 (参数个数不同、参数类型不同 或 两个皆不同)

        注意:返回值类型可不同(只有返回值不同是不能重载的)

重载的好处:

        1. 不用因为参数类型或参数个数不同,而写多个函数。

        2. 可以是一些函数,特定的运算符具有多种功能(最常用的就是,运算符重载)

2.2.覆盖(又叫重写)

        覆盖是指派生类方法覆盖从基类继承过来的方法(覆盖也叫重写)

        覆盖其实就是实现多态

覆盖特征:

        1. 不同的作用域范围(分别位于派生类与基类)

        2. 成员函数名相同,且参数相同,返回类型相同

        3. 基类函数必须有virtual关键字(虚函数)

2.3.隐藏

        1. 不同的作用域范围(分别位于派生类与基类)

        2. 两种情况

        case1: 派生类的函数与基类的函数同名,但不同参。此时,无论基类是否有无virtural关键字,基类的函数将被隐藏;(与重载不同的是:重载发生在同类中)

        case2: 派生类的函数与基类的函数同名,且参数相同,但基类中无virtural关键字。此时,基类的函数被隐藏(与重写不同的是:重写必须加virtural关键字)

3.空类

4.内存布局

4.1.内存空间/用户空间

4.2.C++内存布局(5个)

 4.2.1.堆栈区别

5.内存管理

5.1.章节一

5.1.1.概念

1. 物理内存:实际存在的内存,程序最终运行的地方

2. 程序直接操作物理内存,是非常危险的。为了做到进程隔离,引出了虚拟内存,进程只能看到虚拟内存,由OS通过MMU实现虚拟内存到物理内存之间的映射

5.1.2.虚拟内存和物理内存之间的映射

1. OS将物理内存按照4KB为单位分成一个个页,同时,也将进程的虚拟地址空间以4KB为单位分成一个个页

2. 每个进程有进程控制块都对应一个task_struct结构体,该结构体中有一个指针(见下图)

        2.1. 该指针首先指向当前进程的“页目录”,页目录里面存放的也是指针,该指针指向页表

        2.2. 页表中存放的也是指针,指向最终的物理页(每个物理也就是上面说的4KB大小的页面了)

        

        在32位操作系统下,页目录X页表X物理内存页 = 1024X1024X4KB=4GB,刚好等于4GB(也就是说,32位操作系统下,只需要2级页表,就可以寻址4GB大小的空间了)。

        通过上面的操作,进程的虚拟地址空间的页面,就能够和物理空间的页面一一的对应上了!

5.1.3.线性地址空间到物理地址空间的转换

线性地址由10bit\10bit\12bit的二进制位组成

        第一个10bit:可以从页目录中定位到一个页表

        第二个10bit:可以从页表中定位到一个物理页

        最后的12bit:存储的是offset,12bit刚好可以覆盖4KB个偏移,就可以找到具体的物理地址了

        

        这样就实现了一个虚拟内存页到物理内存页之间的映射,线性地址可以转换为物理地址。

5.1.4.内存申请

        在内核态申请内存:内核认为一旦有内核函数申请内存,那么就必须立刻满足该申请;

        而在用户态申请时,内核总是尽量延后分配物理内存,用户进程总是先获得一个虚拟内存的使用权,最终通过缺页异常获得一块真正的物理内存

5.1.5.内部碎片\外部碎片

内部碎片的产生:因为所有的内存分配必须起始于可被4、8或16整除(内存对齐,视处理器体系结构而定)的地址或者因为MMU的分页机制的限制,决定内存分配算法仅能把预定大小的内存块分配客户)。就是分配满足上面对齐条件的最小的大小内存,如果申请的不满足对齐条件,势必会多分配一点不需要的多余内存空间,造成内部碎片。如:申请43Byte,因为没有合适大小的内存,会分配44Byte或48Byte,就会存在1Byte或3Byte的多余空间。

外部碎片的产生:频繁的分配与回收物理页面会导致大量的、连续且小的页面块夹杂在已分配的页面中间,从而产生外部碎片。比如有一块共有100个单位的连续空闲内存空间,范围为0~99,如果从中申请了一块10 个单位的内存块,那么分配出来的就是0~9。这时再继续申请一块 5个单位的内存块,这样分配出来的就是 10~14。如果将第一块释放,此时整个内存块只占用了 10~14区间共 5个单位的内存块。然后再申请20个单位的内存块,此时只能从 15开始,分配15~24区间的内存块,如果以后申请的内存块都大于10个单位,那么 0~9 区间的内存块将不会被使用,变成外部碎片。

为什么要做内存对齐?

        

5.2.章节二: 伙伴算法

优点:避免外部碎片的产生

伙伴算法:就是将内存分成若干块,然后尽可能以最适合的方式满足程序内存需求的一种内存管理算法(分配的是页框)

        所有的空闲页框分组为11块链表,每个链表分别包含大小为1,2,4,8,16...,1024个连续的页框,每个页框4KB。(最大的连续内存块为1024x4KB=4M)

分配过程:

        1. 先从空闲内存中搜索比申请的内存大的最小的内存块。存在,直接就分配;

        2. 不存在,则会寻找更大的空闲内存块。然后将这块内存平分成2部分:一部分返回给程序使用,另外一部分作为空闲的内存块等待下一次被分配

释放过程:将大小为b的一对空闲伙伴块合并为一个大小为2b的块,满足一下条件的2个块为伙伴,可以进行合并

        1. 两个块具有相同的大小,记作b

        2. 它们的物理地址是连续的

        3. 第一块的第一个页框的物理地址是2*b*2^12的倍数

        说明:这个算法是迭代的,如果它成功合并,合并会会试图合并2b的块,以再次试图形成更大的块

补充:伙伴算法可以解决外部碎片,但是解决不了内部碎片

5.3.章节三: slab分配器

        解决内部碎片,支持分配小尺寸object

5.3.1.概括

1. 将分配的内存分割成各种尺寸的块,并把相同尺寸的块分成组

2. 分配的内存不会释放,而是返回到对应的组,重复利用

        

5.3.2.高速缓存、slab、对象

        

每个高速缓存Cache_chain:包含3个链表slabs_full、slabs_partial、slabs_empty

slab列表:包含了多个page,即多个slab组

每个page:包含多个object,相同page中存放的object大小尺寸相同

        进行分配时,①如果没有Cache_chain,就会去创建一个;②如果有Cache_chain,依次从Cache_chain中的slabs_partial、slabs_empty申请(存在多个slab组,每个slab组中存放多个对象的集合,同一个slab中对象的尺寸大小相同)

6.菱形继承 \ 虚继承

        

菱形继承存在的问题

        1. 冗余数据:D中存在2份对象A的数据

        2. 访问的二义性:d对象直接访问A中的变量和函数,编译会报错(可以使用对象.类名称::访问)

解决方案:虚继承

        1. 实现方式:B和C在继承A的时候,加上virtual(注意:D继承B和C时,无需加上virtual)

        2. 实例D中,不是存放2份A的数据,而是存放“一个指针+偏移量”,指针指向A

补充问题:虚函数和虚继承有什么关系?如果没有虚函数可不可以用虚继承?

答:虚函数和虚继承无任何关系(虚函数是为了多态设计的,虚继承是为了解决菱形继承设计的)。没有虚函数,也可以使用虚继承。

7.C/C++语法关键字

7.1.extern 'c' note

#ifdef __cplusplus //而这一部分就是告诉编译器,如果定义了__cplusplus(即如果是cpp文件, extern "C"{ //因为cpp文件默认定义了该宏),则采用C语言方式进行编译

一言以蔽之,将C++代码,按照C语言的方式编译

7.2.const / mutable

Q1: const是函数签名么

A1: 是,区分只读操作和赋值操作。const函数和非const函数是重载(本质上是因为参数变量被const修饰了)

Q2: const作用

A2: 

     

Q3: const成员函数(使用场景)

A3:

        1. 使用场景:如果一个成员函数不对对象的任何成员数据的进行修改(最常见的为打印成员信息的函数),那么我们可以将这个成员函数设置为const函数,以保护对象数据。 如果在该函数里面修改对象的成员数据,则编译器就会报错。

        2. const函数可以使用类中的所有成员变量、但是不能修改成员变量的值(但是也有例外,如果一个变量被mutable修饰了,即使在const成员函数中,也可以修改该mutable变量的值)

Q4: mutable 可变的

A4: 作用是可以打破在const成员函数中不能修改成员变量的限制

7.2.const/define对比、枚举\宏定义

7.3.static

Q1: static作用

A1:

        1. 修饰全局变量/函数,表示它的作用域只在该文件中

        2. 修饰局部变量,表示该变量的值不会因为函数终止而丢失(数据存储在内存静态区的数据段,其生命周期和整个程序的生命周期一致)

        3. 修饰类成员变量时,表明该类的所有对象对该成员变量只有一个实例,static成员函数不存在this指针,不能访问非static变量

Q2: 为什么static和const不能同时修饰函数?

A2: const修饰的变量/函数必须是含有this指针的,而static本质是共有一个实例,没有this指针

Q3: 为什么静态成员函数不能申明为const?

A3: 这是C++的规则,在类中,const修饰符用于表示函数不能修改成员变量的值,并且const实际修饰的就是指向类的this函数,而类中的static函数本质上是全局函数,不能用const来修饰它。一个静态成员函数可以访问的值是其参数、静态数据成员和全局变量,而这些数据都不是对象状态的一部分。而对成员函数中使用关键字const的作用是表明:函数不会修改该函数访问的目标对象的数据成员。既然一个静态成员函数根本不访问非静态数据成员,那么就没必要使用const了

Q4: 在头文件把一个变量声明为static变量,那么引用该.h文件的源文件能够访问到该变量么?

A4: 可以。声明static变量一般是为了在本cpp文件中的static变量不能被其他的cpp文件引用,但是对于头文件,因为cpp文件中包含了头文件,故相当于该static变量在本cpp文件中也可见。当多个cpp文件包含该头文件中,这个static变量将在各个cpp文件中将是独立的,彼此修改不会对相互有影响。

Q5: 为什么不能在类的内部定义以及初始化static成员变量,而必须要放到类的外部定义

A5: 因为静态成员属于整个类,而不属于某个对象,如果在类内初始化,会导致每个对象都包含该静态成员,这是矛盾的。

Q6: static关键字为什么只能出现在类内部的声明语句中,而不能重复出现在类外的定义中。

A6: 如果类外定义函数时在函数名前加了static,因为作用域的限制,就只能在当前cpp里用,类本来就是为了给程序里各种地方用的,其他地方使用类是包含类的头文件,而无法包含类的源文件。

7.4.new/malloc

    

     

7.5.struct和union的区别、struct和class的区别、struct位域

7.6.inline函数

1.inline函数的原理

        在使用时(和宏类似)是代码替换,执行效率高,是一种空间换时间的思想

        编译器使用inline函数,首先会检查参数问题,保证调用正确性

2.探讨inline的使用场景

        因为inline还是是将代码段展开的,所以具有for循环的代码,不要声明为内联,这样会占用过多的空间

        一般,声明为inline函数的代码行数最好控制的在5行内

7.7.constexpr常量表达式

constexpr表达式是指值不会改变并且在编译过程就能得到计算结果的表达式。

        声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化。一般来说,如果你认定变量是一个常量表达式,那就把它声明成constexpr类型。

1. 不能用普通函数作为constexpr变量的初始值,只能用constexpr函数去初始化constexpr变量。这种函数足够简单,以使得编译时就可以计算其结果。

2. constexpr函数是指能用于常量表达式的函数。该函数要遵循几项约定:函数的返回类型及所有形参的类型都得是字面值类型,而且函数体中必须有且只有一条return语句

7.8.explicit隐式转换构造函数

7.9.strlen/size区别

7.10.不要对有指针成员的类对象/struct对象做memset(__,0,sizeof___)操作

memset会将这个对象成员清空,进而导致:指针成员被设为NULL,导致①该指针对象分配的内存之后不能被释放,造成内存泄漏;②该指针被设置为NULL,之后就不能再被使用了

8. C++11新特性

8.1.右值引用\移动语义\完美转发

8.1.1.右值引用 \ move移动语义

8.1.2.完美转发forward   note

8.2.Lambda表达式

8.3.智能指针:C++中的RAII机制

 shard_ptr几个小问题

#include <iostream>
using namespace std;

// https://blog.csdn.net/weixin_44980842/article/details/121843881
void func(shared_ptr<int> ptr)
{
    cout << "use_count=" << ptr.use_count() << endl;
    return;
}

// case1: 慎用裸指针来构造shared_ptr
//  凡是使用了裸指针来构造shared_ptr智能指针时,那么在后续的代码中。就不要再使用该裸指针了。因为会造成不安全的case!
void test1()
{
    // 错误用法
    {
        int *p = new int(100); //裸指针
        cout << "1." << *p << endl;
        func(shared_ptr<int>(p));
        //这里传入func函数中的参数是一个临时的shared_ptr
        //用一个裸指针显式地构造这个临时的shared_ptr
        cout << "2." << *p << endl;
        //但是,这个裸指针p的内存在传入func后被释放了
        //此时你无法对它的内存空间do事情了!
        *p = 45; //×!此时p的内存空间已经被释放了!
    }
    // 正确用法1
    {
        int *p = new int(100); //裸指针
        cout << "1." << *p << endl;
        shared_ptr<int> p2(p); //把裸指针绑定到shared_ptr上
        cout << "2." << *p2 << endl;
        func(p2); //传入一个shared_ptr指针,即:我把内存交给p2管理了!很好!
        cout << "3." << *p2 << endl;
        *p2 = 188; // ok!没问题的!安全的!
    }
    // 正确用法2
    {
        shared_ptr<int> p2(new int(100)); //把裸指针绑定到shared_ptr上
        cout << "1." << *p2 << endl;
        func(p2); //传入一个shared_ptr指针,即:我把内存交给p2管理了!很好!
        cout << "2." << *p2 << endl;
        *p2 = 188; // ok!没问题的!安全的!
    }
}

// case2: 使用裸指针构造shared_ptr时候,一个裸指针只能绑定到一个shared_ptr中,不能绑定到多个shared_ptr中!
//  当一个裸指针被绑定到多个shared_ptr智能指针上时,就会出现
//      ①一旦某个智能指针被释放时,别的绑定到该裸指针上的智能指针就会指向一个乱码的对象。(因为你已经释放了裸指针p了,p所指向的内存别的指向它的shared_ptr指针没有权限访问了)
//      ②如果别的指向裸指针的shared_ptr指针释放时,还会导致另外一个问题:重复释放同一内存空间。这会导致你的程序产生异常!
void test2()
{
    // 错误用法
    {
        int *p = new int(100); // 裸指针
        shared_ptr<int> p1(p); // 把裸指针p绑定到p1上
        shared_ptr<int> p2(p); // 把裸指针p绑定到p2上
        // 当退出作用域时,程序会崩溃!
        // 因为p1先自动释放它指向的内存p,之后p2也自动释放它指向的内存p,两块内存被释放两次,就会出现core
    }
    // 正确用法
    {
        int *p = new int(100);  // 裸指针
        shared_ptr<int> p1(p);  // 把裸指针p绑定到p1上
        shared_ptr<int> p2(p1); // 把p1绑定到p2上
    }
}

// case2: 慎用get()返回的指针
//  慎用理由:对shared_ptr智能指针用.get()方法得到的裸指针你千万不能随意delete!因为这个权限你要交给shared_ptr去管理!(若你随意delete后,shared_ptr就没办法帮助我们正常管理该内存了)
void test3()
{
    // {
    //     shared_ptr<int> p1(new int(100));
    //     shared_ptr<int> p2(p1);
    //     auto pp = p1.get();
    //     delete pp; //错误!你delete了,那还用智能指针shared_ptr帮你管理内存干嘛?
    // }
    // {
    //     shared_ptr<int> p1(new int(100));
    //     auto pp = p1.get(); // 获取裸指针
    //     {
    //         shared_ptr<int> p3(pp);
    //     }
    //     //相当于将裸指针pp既绑定到shared_ptr指针p1上,也绑定到p3上了
    //     //这和前面的一个裸指针被绑定到多个shared_ptr指针上产生异常的case是一样的!
    // }
    {
        shared_ptr<int> sp1(new int(1));
        shared_ptr<int> sp2(std::move(sp1));
        //移动语义:移动构造一个新的智能指针对象
        //此时,sp1就不再指向该int型的val==1的对象了(变为空)
        //引用计数依旧为1
        shared_ptr<int> sp3(std::move(sp2));
        //此时sp2就变为空,但其引用计数仍然为1
        //用std::move()函数移动赋值肯定比单纯的赋值块:因为单纯的赋值时你还需要增加引用计数,但是移动则不需要!
        cout << sp1.use_count() << sp2.use_count() << sp3.use_count() << endl; // 0 0 1
    }
}

// class CT : public enable_shared_from_this<CT>
// {
// public:
//     shared_ptr<CT> getself()
//     {
//         return shared_from_this();
//         // 通过模板类enable_shared_from_this中的方法shared_from_this将this指针用作shared_ptr指针来do返回!
//     }
// };

class CT
{
public:
    shared_ptr<CT> getself()
    {
        return shared_ptr<CT>(this); // 用裸指针初始化了多个shared_ptr!
    }
};

// case4: 不要把类对象指针(this指针)作为shared_ptr返回,改用enable_shared_from_this
// 当你在类中将this指针绑定给shared_ptr时,又在类的外部将该shared_ptr给别的shared_ptr指针做初始化的话,就又会造成上述使用裸指针时出现的问题:用一个裸指针绑定到多个shared_ptr上时,会造成重复释放用一份内存空间的异常问题!
void test4()
{
    CT *ct = new CT();
    // ct裸指针给pct1
    shared_ptr<CT> pct1(ct);
    // this裸指针给pct1
    shared_ptr<CT> pct2 = pct1->getself();
}
int main()
{
    test4();
}

 8.4.override

该关键字声明在子类的成员函数中,表示该成员函数一定是继承父类的virtual

class A
{
    virtual void foo(){};
};
class B : A
{                                 // override声明该函数是对父类virtual的重写!
    virtual void foo1() override; // 编译失败,防止程序员在重写父类虚函数virtual时,因为手抖,写错函数名,闹出乌龙!
    //'foo1' marked 'override' but does not override any member functions
};

 8.5.final

修饰类名:该类不可以被继承

修饰类中的成员函数:该函数不能被子类覆盖实现(即,不允许重载,但是允许被重写,实现多态) 

8.6.类型转换: static_cast\reinterpret_cast\const_cast\dynamic_cast

C语言中的类型转换: TYPE A = (TYPE)B

static_cast:相当于C语言中的类型转换:编译器会做类型检查,有的转换会失败,更加安全

reinterpret_cast:强制类型转换,不要使用!

const_cast:删除const属性

dynamic_cast
    1.相同类型: 转换一定成功
    2.子类转父类,一定成功。(子类转父类,无论父类是否有虚函数,都能编译通过 && 转换成功)
    3.父类转子类
        2.1.编译问题: 父类没有virtual函数,编译失败
        2.2.转换问题: 当前仅当父类指针指向子类对象时,父类指针才能成功动态转化为子类指针,否则转换失败,结果为nullptr

8.7.类型获取/推导/判断: typeid\decltype\auto

8.7.1.typeid

介绍:

        1. typeid是一个运算符,类似与sizeof
        2. 功能是可以打印目标的类型
        3. typeid可用于动态类型(多态时,父类的指针指向子类的对象:判断该父类的指针指向子类对象的类型),也可以用于静态类型,静态类型和动态类型分别对应的是编译和运行。
        4. typeid多数运用于class和继承中

8.7.2.decltype

        声明表达式类型,声明变量时不必赋初值。类型由编译器根据表达式自动推导

8.7.3.auto

        自动类型推导,声明变量时必须赋初值,类型由右值的决定。换句话说:auto让编译器通过初始值来推算变量的类型,显然,auto 定义的变量必须有初始值。

8.7.4.decltype\auto区别

  • 编译器推断出来的auto类型有时候和初始值的类型并不完全一样,编译器会适当地改变结果类型使其更符合初始化规则。例如,auto一般会忽略掉顶层 const,而把底层const保留下来。与之相反,decltype会保留变量的顶层const。
  • 与auto不同,decltype 的结果类型与表达式形式密切相关,如果变量名加上了一对括号,则得到的类型与不加括号时会有不同。如果 decltype 使用的是一个不加括号的变量,则得到的结果就是该变量的类型;如果给变量加上了一层或多层括号,则编译器将推断得到引用类型
  • auto类型说明符用编译器计算变量的初始值来推断其类型,而decltype虽然也让编译器分析表达式并得到它的类型,但是不实际计算表达式的值。

9.Effective C++

9.1.多态父类的析构函数要声明为virtual

9.2.绝不在构造函数/析构函数中调用vritual函数

9.3. =default、=delete

        上面我们知道,C++会为一个类声明默认的6个函数,C++11提供了2个关键字,可以控制它们会不会默认生成这6个函数。

=default: 可以让编译器自动为我们生成函数体(程序员只需在函数声明后加上=default,就可将该函数声明为 default 函数,编译器将为显式声明的default函数自动生成函数体,该函数使用时有如下两点要注意的:

  • Defaulted 函数特性仅适用于类的特殊成员函数,且该特殊成员函数没有默认参数。
  • Defaulted 函数既可以在类体里(inline)定义,也可以在类体外(out-of-line)定义
    • class Fruit
      {
      public:
          Fruit() = default;
          Fruit(int n1) : a1(n1) {}
      
      private:
          int a1;
      };
      

=delete

  • 禁止类使用默认生成的成员函数,最好设置为private,同时设置为=delete
    • class A{
      public:
      	A(){}
      	~A(){}
      private:
      	A(const A&) = delete;//拷贝构造函数
      	A& operator=(const A&) = delete;//赋值运算符
      	A* operator&() = delete;//取值运算符
      	const A* operator&()const = delete;//取址运算符 const	
      }
      
  • 禁止类使用其他类成员函数
    • #include <iostream>
      using namespace std;
      
      class A{
      public:
          A(){}
          int fun1(int a){return a;}
          int fun2(int a) = delete;
          
      };
      
      int main(){
          A* temp = new A();
          cout << temp->fun1(1) << endl; //正确
          //temp->fun2(1); //使用错误
          return 0;    
      }
      

9.4. 别让异常从析构函数中抛出 

1. 如果析构函数中可能抛出异常,应该捕获该异常,然后吞下它们或结束程序

2. 如果客户需要对某个操作函数运行期间抛出的异常作出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该函数

9.5.参数传递/返回值

Q1: 返回值为引用/指针时,该引用/指针不能指向local变量

A1: 引用和指针执行local,该local被返回给外部后,就会被销毁,那么,返回出去的值没法用

Q2: 返回值为引用时,该引用不能指向堆上分配的内存

A2: 不知道如何去释放它,导致内存泄漏

10. STL

10.1. STL六大部件综述

        1.算法/容器/迭代器:Alogrithm看不见Container,对其一无所知,所以,算法需要的一切信息都必须从Iterator中获得,Iterator是连接Alogrithm和Container的桥梁

        2.分配器:为容器服务,分配内存空间

        3.仿函数:为算法服务,是类对象,重写了operator()函数,比较大小的准则

        4.适配器:容器适配器/迭代器适配器/仿函数适配器

10.1.1.仿函数

​​​​​​​C++11function仿函数

​​​​​​​C++11bind

10.1.2.​​​​​​​适配器

10.1.3.分配器

一级分配器

二级分配器

 

10.2.容器vector/list/map/unorder_map

​​​​​​​

相关文章:

  • wordpress 后台无法登录/如何在百度做推广
  • 西安设计网站公司/第三方营销平台有哪些
  • 包装产品做网站/新东方教育培训机构官网
  • 怎么利用网站赚广告费/沧州网站seo公司
  • 怎样使用自己的电脑做网站/软文什么意思
  • 做网站累吗/百度上海推广优化公司
  • 【完全背包】完全背包模板套用
  • 蚌埠住了!一份硬核的阿里P8高并发实战笔记,吊打面试官不在话下
  • c++学习25qt(一)qt下载使用和简单的信号与槽介绍
  • joomla 反序列化漏洞利用
  • 商业银行系统体系
  • webpack优化系列六:vue项目配置 terser-webpack-plugin 压缩 JS,去除console
  • LVM管理磁盘
  • python地图库(一)—folium
  • [LeetCode周赛复盘] 第 315 场周赛20221016
  • 每天五分钟机器学习:基于正则化方法解决算法模型的过拟合问题
  • k8s证书过期
  • python数据分析及可视化(八)pandas数据规整(层级索引、数据重塑、数据合并、数据连接)