面试 C++向

C++ 与 C语言区别:                                                                  

C++C
特性封装 继承 多态 + 新特性(面向对象、安全性)-
面向对象过程
安全性智能指针(shared - point)、cast转换、const常量、try-catch-
可复用性模版、STL-
封装操作class:(对象数据封装,默认private(含三种权限),私有继承)struct:(数据结构 集合,默认public(仅含一种权限),无继承)
struct声明对比展开
成员函数可有(允许是虚函数)
静态成员可有
访问控制public、private、protected默认public
继承关系可继承不可继承
初始化可有无法直接
--------------------------------------------------------------------------------------------------------------------------------------
编译区别支持函数重载(考虑参数类型+函数名)函数名

new 与 malloc区别:

newmalloc
本质操作符 (对象)函数
调用步骤分配内存 -> 构造函数 -> 使用 -> 析构函数构造析构函数
内存分配无需指定内存大小,调用构造函数会自动分配申请指定内存大小
重载性可以被重载

无法重载

安全性分配内存 直接安全-
发生错误抛出异常返回null
底层实现步骤创建对象 -> 构造函数作用域给新对象(this指向新对象)-> 执行构造函数代码(为这个对象添加属性) -> 返回新对象

开辟内存空间(> 128k -> 调用mmap()),后调用brk()函数。

采用内存池管理方式:申请大块内存作为堆区,并分为多个内存块。用户申请时分配空闲块,采用隐式链表记录空闲连续的内存地址

左值与右值:

特点左值 (Lvalue)右值 (Rvalue)
定义代表可以标识的内存位置代表临时值或常量
赋值可以出现在赋值语句的左边只能出现在赋值语句的右边或参数列表中
地址访问可以获取其地址(可以被地址访问不能获取其地址(无法访问地址
例子变量、数组元素、指针引用等常量、字面量、临时表达式的结果等
C++11及以后的处理支持左值引用支持右值引用移动语(move

数组和指针:

数组指针
概念连续存放多相同数据类型数据的集合,数组名是首元素的地址变量,指针名指向内存的首地址
赋值单元素一个一个的赋值同类型指针可相互赋值
存储方式连续存放任意指向
存储空间静态区或者栈无法确定
sizeofsizeof(数组名)/ sizeof(数据类型)32bit ->  4 ; 64bit -> 8

函数指针与函数指针:

函数指针指针函数
定义指向函数入口地址的指针。一个返回值为指针的函数
写法
int (*fun)(int x, int y)
int *fun(int x, int y)
使用场景别人库调用我们的函数,称回调函数(callback)。

 指针函数与函数指针的区别_chiying9800的博客-CSDN博客

悬挂指针 内存泄漏 野指针:

野指针:指针没有做初始化操作,从而不明确指针所指向的位置,成为野指针。

指针悬挂:指针所指向的内存空间已经被删除,从而该指针所指向的空间就不确定了,容易导致错误或者崩溃。

内存泄漏(Memory Leak):是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果

避免方法: 1)初始换置NULL ;2)申请内存判空 ;3)指针释放后置NULL ;4)智能指针(自动释放内存)

内联函数、宏与函数的区别:

函数调用:函数调用是有时间和空间开销的。程序在执行一个函数之前需要做一些准备工作,要将实参、局部变量、返回地址以及若干寄存器都压入栈中,然后才能执行函数体中的代码;函数体中的代码执行完毕后还要清理现场,将之前压入栈中的数据都出栈,才能接着执行函数调用位置以后的代码。

内联函数宏函数普通函数
表示含关键字inline含关键字define(本质非函数)
调用开销无需函数调用开销 ----> 无需寻址预处理器复制宏代码的方式代替函数调用需函数调用----> 需寻址函数入口地址
特性

编译时插入函数体,代码简单。

若代码较长,占用内存代价大。

若控制语句多,函数执行时间比调用开销大

预编译时处理,将宏名用宏体表示
类型检查有,检查返回值,参数列表是否满足条件

静态局部变量、全局变量、局部变量特点及使用场景:

静态全部变量全局变量静态局部变量局部变量
初始化首次用到时首次用到时首次用到时开始函数
作用域全局作用域 + 文件作用域全局作用域 局部作用域局部作用域
空间静态存储区静态存储区静态存储区
生命周期程序结束程序结束程序结束作用域回收内存时

全局变量、静态变量、局部变量的生存周期与作用域_全局变量的生命周期_Nine_CC的博客-CSDN博客

const 相关:

const与define:

constdefine
生成阶段编译阶段预编译阶段
内存需要占用内存直接操作数,无需占用内存
类型带类型无类型
const int aconst int *aint const *aint *const aconst int *const a
a常量,不允许修改a指向内存值不变a指向内存值不变a指向内存地址不变都不变

C++代码到可执行文件过程:

C++程序从源码到执行文件过程:预编译、编译、汇编、链接
预编译:(宏 include 注释操作等)

  • 将所有的#define删除,并且展开所有的宏定义
  • 处理所有的条件预编译指令,如#if、#ifdef
  • 处理#include预编译指令,将被包含的文件插入到该预编译指令的位置。
  • 过滤所有的注释
  • 添加行号和文件名标识。

编译:(源代码生成汇编代码)

  • 词法分析:将源代码的字符序列分割成一系列的记号。
  • 语法分析:对记号进行语法分析,产生语法树。
  • 语义分析:判断表达式是否有意义。
  • 代码优化。
  • 目标代码生成:生成汇编代码
  • 目标代码优化。

汇编:这个过程主要是将汇编代码转变成机器可以执行的指令
链接:将不同的源文件产生的目标文件进行链接,从而形成一个可执行的程序
链接:静态链接、动态链接。

动态链接库静态链接库
形式

执行时找链接函数,生成exe文件只包含重定位信息。

无动态库无法运行程序

exe文件含调用函数与过程链接。

无静态库依旧可执行程序

过程调用 - > 重定位信息 -> dll库 -> 调用函数调用 -> 链接与exe(内含lib)-> 调用函数
exe速度略慢略快
装载代码速度
内存体积小,节省内存
加载时间运行时编译时

传递方式: 

值传递:传入的参数为实参的拷贝临时变量,本质为形参,作用与局部变量中,由编译器在栈内自动分配内存与释放内存。

引用传递:绑定传入参数的对象,即保证通过函数操作后,会实现引用对象的内值改变,更安全。

指针传递:传入参数为指向特定地址的内存,故没有拷贝流程,返回结果也会对指向该地址的内值出现改变。

C++内存:                                                                                     

堆与栈:

空间分配操作系统自动分配释放(局部变量)程序员自定分配释放(new)
缓存方式一级缓存(调用释放速度快二级缓存
数据结构栈结构(先进后出数组
内存分配方式介绍
函数/局部变量执行完自动被释放
new分配内存块,new-delete
自由存储区malloc分配内存快,malloc -free
全局/静态存储区全局变量与静态变量分配内存区域
常量存储区只能存放常量不能被修改

内存泄漏:

申请内存后,使用完毕没有及时释放掉 ----> 1)new 后没有delete 2)子继承父类,父析构非虚。

内存错误与对策:

问题对策
未分配成功,就用new申请内存后,检查指针值是否为NULL
分配成功,未初始化,就用数组与动态内存赋初值,防止右值使用问题。
初始化成功,操作超边界避免数组指针越界判断,尤其是+1,-1
未释放内存,内存泄漏

new - delete 配对使用,释放后记得指针置NULL,防止野指针

释放内存却使用智能指针

内存对齐:

  • 作用:使CPU对变量进行快速访问,减少访问offset对齐时间。
  • 操作:起始地址应满足“最宽基本类型成员"的整数倍

程序section:

程序内存段纪要
代码段存放程序执行代码的内存区域,只读
BSS段存放未初始化或初始化为0的全局或静态变量,读写
数据段存放已初始化的全局静态变量
堆区

动态申请内存,地址从低向高增长

栈区存储局部变量/函数参数的连续空间,地址由高向低增长
文件映射区处于堆栈之间的区域
常量存储区存放常量不许修改。

面向对象:                                                                                     

面向对象和面向过程:

面向对象面向过程
对数据和函数进行绑定封装,加快开发速度业务逻辑由上到下的写

C++三大特性:

特性名称描述
封装封装隐藏对象属性及细节(pp 安全性),通过公开接口与对象交互(包含三大对象属性(ppp)
继承父子类对象的属性继承(private  > protected > public)
多态

父类型别的指针指向子类的实例,然后通过父类的指针调用实际子类成员函数

重载函数(参数数据类型不同),重写函数(父子类函数重构)

封装方面:

构造函数类别:

类别描述
默认构造函数

无参数传入情况,直接调用默认值

初始化构造函数

有参数传入时,初始化对象属性

拷贝构造函数

赋值构造函数属于浅拷贝(指向地址相同)

复制构造函数为深拷贝(指向地址不同,防止释放同一块内存

移动构造函数

用于将其他类型变量,隐式转换为本类对象;源对象内容丢失被目标对象所占有,

目标对象接管源对象内存,无需拷贝节省时间

C++移动拷贝构造函数_视觉 李的博客-CSDN博客

移动赋值与拷贝赋值:

  • 1)拷贝构造函数的形参是一个左值引用,而移动构造函数的形参是一个右值引用;
  • 2)拷贝构造函数完成的是整个对象或变量的拷贝,而移动构造函数是生成一个指针指向源对象或变量的地址,接管源对象的内存,相对于大量数据的拷贝节省时间和内存空间。
形参类型赋值形式
拷贝赋值左值引用整个对象或变量的拷贝
移动赋值右值引用

生成一指针指向源对象或变量的地址,接管源对象的内存

避免不必要的资源复制,提高代码的效率,并减少内存使用

所有权形式,节省拷贝时间,节省内存空间

默认及初始化构造函数: 

class Student
{
public:
    //默认构造函数
    Student()
    {
        num=1001;
        age=18;
    }
    //初始化构造函数
    Student(int n,int a):num(n),age(a){}
private:
    int num;
    int age;
};
int main()
{
    //用默认构造函数初始化对象S1
    Student s1;
    //用初始化构造函数初始化对象S2
    Student s2(1002,18);
    return 0;
}

拷贝构造函数:(浅拷贝)

#include "stdafx.h"
#include "iostream.h"
class Test
{
    int i;
    int *p;
public:
    Test(int ai,int value)
    {
        i = ai;
        p = new int(value);
    }
    ~Test()
    {
        delete p;
    }
    Test(const Test& t)
    {
        this->i = t.i;
        this->p = new int(*t.p);
    }
};
//复制构造函数用于复制本类的对象
int main(int argc, char* argv[])
{
    Test t1(1,2);
    Test t2(t1);//将对象t1复制给t2。注意复制和赋值的概念不同。
    return 0;
}

深拷贝与浅拷贝:

描述
浅拷贝

引用变量名不同,但是指向同一个区域共用一份实体,地址相同

深拷贝

开辟与源对象一样的空间,指向地址不同拷贝内容相同,无重复释放统一内存的错误

注意知识:

  • 只定义析构函数,自动生成 默认构造函数 与 拷贝构造函数
  • 默认空类自动生成: 1)默认无参构造  2)拷贝构造  3)析构函数  4)赋值运算符

继承方面:

当派生类对象初始化顺序(析构顺序相反):

  • 父类构造函数
  • 派生类成员构造函数 (按照派生类中成员声明顺序)
  • 派生类构造函数
class Base {
public:
    Base() {
        cout << "Base constructor" << endl;
    }
};

class Derived : public Base {
public:
    int derivedVar;
    AnotherClass anotherObject;
    
    Derived() : derivedVar(0), anotherObject() {
        cout << "Derived constructor" << endl;
    }
};

int main() {
    Derived derivedObj;
    return 0;
}

分析:Derived 是一个派生类,它公开继承自 BaseDerived 类中有一个 int 类型的成员变量 derivedVar 和一个名为 AnotherClass 的对象 anotherObject。在 Derived 类的构造函数中,可以看到它首先调用 Base 类的构造函数,然后按照声明的顺序初始化 derivedVaranotherObject,最后才调用自身的构造函数。

转型类型:

描述
向上转型

子类转父类,利用dynamic_cast

向下转型父类转子类,利用强转会导致数据丢失 ; 

多态之向下转型(强制转型) - deng-hui - 博客园 (cnblogs.com)

不同类继承访问权限:

继承方式

Private继承

Protected继承

Public继承

基类private成员

不可见

不可见

不可见

基类protected成员

Private

protected

protected

基类public成员

Private

protected

Public

抽象类:

抽象类表示至少包含一个纯虚函数的类;抽象类只能用作其他类的基类

菱形继承问题:

问题解决方案
菱形继承:数据重复(浪费存储空间),产生二义性虚继承/虚基类:

虚继承:存在于爷辈和父辈之间,生成两个不同的虚基类表和两虚表指针,但是指向的区域内容相同,解决了二义性的问题(继承来自父类还是叔类)和数据重复问题

shellmad-c++_50 菱形继承与虚继承_哔哩哔哩_bilibili

多态方面:

重写与重载:

描述
重写

在派生类中重定义父类函数(必须要有V修饰);基类函数为虚函数,派生型重写;

函数名,参数列表,返回值类型必须同基类

重载

同一函数通过 输入参数不同 区别,得到重载函数;在编译时对其进行区分

多态类型:

调用时机形式
动态多态(晚绑定)

运行过程中,通过基类指针或虚函数调用时

重写虚函数

调用子类方法

静态多态(早绑定)

编译期间完成,编译器会根据实参类型推断该调用何函数

重载函数

调用父类方法

虚析构与虚构造 虚函数表:

名称描述

虚析构

可用,保证基类指针指向子类空间释放是内存的释放,防止内存泄漏;调用虚析构会先排查派生类构造函数是否存在,调用派生类析构,在调用基类虚构

虚构造

虚函数对应一个虚表需要分配内存空间,只有对象实例化时才能分配内存空间,逻辑相悖

解释:要构造虚函数,需要调用虚函数表,要建立虚函数表,需实例化对象,对象没有实例化,内存空间未分配,虚函数表建立不了,无法虚构造

虚函数表:

存放虚函数地址的数组,在编译阶段生成,构造函数之前写入。

注: C++ 中默认析构函数不为虚函数虚函数本身需要虚函数指针与虚表占用额外内存。 

class TimeKeeper
{
public:
    TimeKeeper() {}
    virtual ~TimeKeeper() {}
};

虚表与虚表指针:

名称描述
虚表

编译阶段生成,存放虚函数的一维虚函数表(本质数组)以NULL结尾 ----> 虚表对应类

对象实例化后,开辟内存空间,写入虚指针,在调用构造函数 。即:虚表在构造函数前写入

虚表指针类对象通过指向虚表的虚指针找到虚函数------> 虚表指针对应类对象

虚函数与纯虚函数:

虚函数纯虚函数
描述存在V函数的类对应虚表类对象有指向V表的V指针

纯虚函数:virtual void fun() = 0

即 先有名称,没有内容

调用父类定义,子类重写都可,动态绑定基类内无法实例化,需派生类里重写函数, 动态绑定
抽象必须含有纯虚函数
形式virtual func{ }virtual func{ } = 0

注:纯虚函数的目的: 使派生类仅仅只是继承函数的接口。让所有的类对象(主要是派生类对象)都可以执行纯虚函数的动作,但类无法为纯虚函数提供一个合理的缺省实现。|| 告诉子类设计者,你需要提供纯虚函数的实现,但是功能怎么实现不关我事。。。

#include<iostream>
using namespace std;
class Base
{
public:
    virtual void func() = 0;
};
class Derived :public Base
{
public:
    void func() override
    {
        cout << "哈哈" << endl;
    }
};
int main()
{
    Base *b = new Derived();
    b->func();
    return 0;
}

不支持虚函数的函数:

名称原因
普通函数(非成员函数只能被重载,不能被重写,编译过程自动绑定函数
静态成员函数属于一类,非对象,无法绑定类
内联成员函数在编译时展开,虚函数运行时动态绑定;调用时机不同
构造函数悖论逻辑
友元函数继承特性无虚函数说法。

STL部分:

STL分类:

容迭仿算适配空间

广义算法 algorithm、容器container、迭代器iterator
详细算法 algorithm、容器container、迭代器iterator、仿函数function object、适配器、空间配置器

名称

描述
Container数据结构,以模板类方法提供List、vector、deque
algorithm操作容器的数据的模板函数
iterator提供访问容器对象的方法
仿函数函数对象,重载操作符的struct
适配器接口类,作用提供新接口,或调用现用函数实现功能
空间配制器为STL提供空间配置的系统,作用:1)对象创建、销毁 ; 2)内存申请或释放

常见容器类型:

容器类型名称

实现原理
顺序容器

Vector :动态数组,内存连续存放,头尾删减较好,支持随机访问,中间插入删减不佳

Deque :双向队列,内存连续存放,头尾删减较好,支持随机访问

List :双向链表,内存不连续存,任何位置删减均方便不支持随机访问,查找速度慢

关联式容器

实现原理:平衡二叉树

Set/multiset:不能修改set值,set迭代器时const

Map/multimap:map会根据key值对元素进行由小到大排列,可以根据key检索元素

容器适配器

封装一些基本容器,增加新的函数功能;

stack:后进先出,检查更改只能是头部修改项

queue:先进先出,插入可以在尾部,删改查在头部

priority_queue: 内部保持有序,优先级最高者在头部。

名称

实现原理
map

红黑树:自动排序功能,增删查控制在 O(logn) 数量级,内部元素有序,key不可修改,value可改

存储方式:中序

红黑树及AVL树红黑树(图解+秒懂+史上最全) - 疯狂创客圈 - 博客园 (cnblogs.com)

哈希表

函数映射思想,记录存储位置与关键字关联

通过映射查找,比vector deque等比较查找效率更高

STL容器增删查时间复杂度:

容器

插入

查看

删除

Vector

O(N)

O(1)

O(N)

Deque

O(N)

O(1)

O(N)

List

O(1)

O(N)

O(1)

Map、set、mmap…

O(logN)

O(logN)

O(logN)

Unordered…

O(1)~O(N)

O(1)~O(N)

O(1)~O(N)

vector与list:

vectorlist

动态数组,在堆内存放,连续存放,减小大小不会内存释放

增大大小后扩容内存:1)申请三倍内存空间,2)拷贝数据值新内存,3)释放旧内存,4)指针指向新内存

内存空间不连续,访问效率低,

增删效率高,无[ ]操作符重载

resize与reserve:

名称概念
capacity容器初始化时赋值,代表容器能容纳的最大元素个数
size表示此时容器实际元素个数

 区别:

resizereserve
空间分配即分配空间,也创建了对象只预留了空间,需要insert或push_back()创建对象
修改即修改capacity也修改了size只修改capacity
形参包含两个形参,容器大小+初始值容器预留大小

STL空间配置器:

1. C++ 对象实例化方式

直接构造类对象

Test test();

局部变量存储于栈空间

函数代码运行结束,自动释放内存

通过new来实例化对象

Test *p = new Test

堆空间存储及释放

堆分配-构造-析构-堆释放

2. 内存分配形式

分配区域分配时机形式

静态存储区

编译时

整个运行空间均存在(全局变量,静态变量)

栈空间

程序运行时

函数执行完毕,栈空间被回收(局部变量)

堆空间

程序运行时

New/malloc创建,free/delete释放

3. 空间配置器实现 (两级配置器)

配置器级别内容
一级配置器考虑大块内存空间的配置,malloc和free
二级配置器

考虑小块碎片内存空间的配置,链表free_list维护内存池,通过union将空闲内存挂接一起。

使用小块内存空间是,从free_list中剔除,便于维护。

迭代器失效/删除(erase)情况:

容器类型

Erase后效果

Vector deque

后面元素的迭代器失效,前移一位返回到下一个有效迭代器

Map set

不会影响下一个元素的迭代器

List

不连续分配内存,返回到下一个有效迭代器

remove与erase的区别:

功能erase 方法remove 算法
作用删除指定位置或区间的元素,减少 vector 的大小将指定值从范围内移到后面,但不改变 vector 的大小
参数可接受单个位置参数或一个区间 [begin, end)接受一个值参数 val 和一个范围 [begin, end)
返回值返回被删除元素后的迭代器位置返回新的 end 迭代器位置,但不改变原 end
size 的影响减少 vector 的大小不改变 vector 的大小
原始元素顺序被删除的元素后面的元素会向前移动保持不移动 val 之外的元素顺序
复杂度平均时间复杂度 O(N)平均时间复杂度 O(N)
示例vec.erase(position);vec.erase(std::remove(vec.begin(), vec.end(), val), vec.end());

迭代器作用:

  • 用于指向顺序容器和关联容器中的元素  -----> 指向元素

  • 通过迭代器可以读取它指向的元素 ------------> 读取元素

  • 通过非const迭代器还可以修改其指向的元素 > 修改元素

  • 不暴露集合内部结构达到遍历集合的效果 ----> 安全性

#include <vector>
#include <iostream>
using namespace std;
int main() {
    vector<int> v; //一个存放int元素的数组,一开始里面没有元素
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
    vector<int>::const_iterator i; //常量迭代器
    for (i = v.begin(); i != v.end(); ++i) //v.begin()表示v第一个元素迭代器指针,++i
指向下一个元素
    cout << *i << ","; //*i表示迭代器指向的元素
    cout << endl;
    vector<int>::reverse_iterator r; //反向迭代器
    for (r = v.rbegin(); r != v.rend(); r++)
    cout << *r << ",";
    cout << endl;
    vector<int>::iterator j; //非常量迭代器
    for (j = v.begin();j != v.end();j++)
        *j = 100;
    for (i = v.begin();i != v.end();i++)
        cout << *i << ",";
        return 0;
    }
/* 运行结果:
    1,2,3,4,
    4,3,2,1,
    100,100,100,100,
*/

容器与迭代器类别:

容器容器上迭代器类别
vector随机访问
deque随机访问
list 双向
set、multi双向
map、multi双向
stack不支持
queue不支持
p_queue不支持

pushback与emplace_back区别:

  • push_back需要构建临时对象,对这个对象拷贝容器末端
  • emplace_back直接在容器尾端构造对象省去拷贝环节
int main()
{
    vector<A> vec;
    vec.reserve(10);
    for(int i=0;i<10;i++){
        vec.push_back(A(i)); //调用了10次构造函数和10次拷贝构造函数,
        // vec.emplace_back(i); //调用了10次构造函数一次拷贝构造函数都没有调用过
    }
}

C++ 11 新特性:

成员变量默认初始化

//程序实例
#include<iostream>
using namespace std;
class B
{
public:
    int m = 1234; //成员变量有一个初始值
    int n;
};
int main()
{
    B b;
    cout << b.m << endl;
    return 0;
}

auto:定义变量编译器自己自动判断(需要对其进行初始化,根据(右值推断))

//程序实例
#include <vector>
using namespace std;
int main(){
    vector< vector<int> > v;
    vector< vector<int> >::iterator i = v.begin();
    auto it = v.begin();
    return 0;
}

decltype:适用于某些auto无法自动推导的情况(根据exp表达式推断 (左值推断))

auto varname = value;
decltype(exp) varname = value;

智能指针:多个shared_ptr 可以共同使用同一块堆内存,实现采用引用计数机制,

        sp释放内存后,该内存块引用计数 -1,其他sp仍可使用;

        只有当引用计数为0,堆内存才会被自动释放。

#include <iostream>
#include <memory>
using namespace std;
int main()
{
    //构建 2 个智能指针
    std::shared_ptr<int> p1(new int(10));
    std::shared_ptr<int> p2(p1);
    //输出 p2 指向的数据
    cout << *p2 << endl;
    p1.reset();//引用计数减 1,p1为空指针
    if (p1) {
        cout << "p1 不为空" << endl;
    }
    else {
        cout << "p1 为空" << endl;
    }
    //以上操作,并不会影响 p2
    cout << *p2 << endl;
    //判断当前和 p2 同指向的智能指针有多少个
    cout << p2.use_count() << endl;
    return 0;
}
/* 程序运行结果:
    10
    p1 为空
    10
    1
*/

空指针:nullptr 是 nullptr_t 类型的右值常量,专用于初始化空类型指针,解决内存泄漏

        nullptr 是实例化对象,NULL是数据。

#include <iostream>
using namespace std;
void isnull(void *c){
    cout << "void*c" << endl;
}
void isnull(int n){
    cout << "int n" << endl;
}
int main() {
    isnull(NULL);
    isnull(nullptr);
    return 0;
}
/* 程序运行结果:
    int n
    void*c
*/

        可以通过隐式转换,转换nullptr数据类型

int * a1 = nullptr;
char * a2 = nullptr;
double * a3 = nullptr;

迭代器for循环:使用迭代器或其他容器进行遍历

unordered_map:用法和功能同map一模一样,区别在于哈希表的效率更高

lambda表达式

移动语句:

本质:所有权模式,将左值转换为右值,资源转移,资源窃取。 

​​​​​​​b​​​​​​​std::move & 左值右值 &左值引用右值引用_move 左值变右值_s11show_163的博客-CSDN博客

智能指针:

智能指针与普通指针的区别:
智能指针普通指针
定义指针变量是专门用来存放地址的,并将它定义为“指针类型”。
内存释放特性对普通指针加一层封装,可以自动释放所指对象,更方便管理一个对象的生命周期。

使用new生成对象在堆内分配内存,不需要时,需要释放内存delete并指针置NULL,如果没有正确操作,

出现 1) 内存泄漏 2) 野指针 问题

智能指针解决的问题:

针对普通指针出现内存泄露或野指针的问题,智能指针能避免这类问题。

将智能指针抽象成一个类,超出类作用域时,主动调用析构函数释放资源,避免手动释放内存。

智能指针的类别:
智能指针类型特点
shared_ptr允许多个指针共享资源的所有权,引用计数管理
unique_ptr同一时间独占资源的所有权,不能复制,只能移动
weak_ptr配合shared_ptr使用,不增加引用计数
auto_ptr (废弃)

在C++11中被废弃,不推荐使用,废弃原因:所有权模式,存在内存崩溃的风险

auto_ptr (废弃)内存崩溃问题:
auto_ptr<string> p1(new string("I reigned loney as a cloud."));
auto_ptr<string> p2;
p2=p1; //auto_ptr不会报错

如上述代码所示,P2将P1的所有权剥夺,即P1指针置NULL,再次访问P1将会报错。

unique_ptr 针对 auto_ptr问题优化:

unique_ptr:同一时间只能有一个智能指针指向该对象,避免内存崩溃的问题。

unique_ptr<string> pu1(new string ("hello world"));
unique_ptr<string> pu2;
pu2 = pu1; // #1 not allowed
unique_ptr<string> pu3;
pu3 = unique_ptr<string>(new string ("You")); // #2 allowed

分析:#情况1 pu2不能把pu1的所有权占据,B:给我用下 A:如果想要你自己去搞

      #情况2 pu3可占据临时右值,同时临时对象自行毁灭,C:欸地上有个没人要的,那我捡起来

shared_ptr(强引用):

对于auto独占性的问题,提出了引用计数机制,即可在同一时刻多个智能指针指向相同对象,

每释放一个指针所有权时,引用计数减一;当最后一个指针被reset ( ) 后,资源会被释放。

成员函数描述
use_count()返回引用计数的个数,即指向同一资源的shared_ptr对象的个数。
unique()检查是否是独占所有权,即use_count()是否为1。如果为1,则表示当前shared_ptr是该资源的唯一所有者。
swap()交换两个shared_ptr对象,即交换所拥有的对象。
reset()放弃内部对象的所有权或拥有对象的变更,会引起原有对象的引用计数的减少
get()返回内部对象的指针,由于已经重载了()方法,因此和直接使用对象是一样的。
weak_ptr:

解决问题:shared_ptr相互引用发生死锁问题 - > 引用计数永远不会降为0,资源永远不会被释放。

weak_ptr本质是一种弱引用,是一种不控制对象生命周期(构造析构不会引起计数改变)的智能指针,仅是一种对管理对象的访问手段。--------------> 无法知晓指向对象的引用计数

可由shared_ptr或weak_ptr对象构造;1)shared_ptr直接赋值给它 2)通过lock函数获得shared_ptr

class B;
class A
{
public:
    shared_ptr<B> pb_;
    ~A()
{
    cout<<"A delete\n";
}
};
class B
{
public:
    shared_ptr<A> pa_;
    ~B()
{
    cout<<"B delete\n";
}
};
void fun()
{
    shared_ptr<B> pb(new B());
    shared_ptr<A> pa(new A());
    pb->pa_ = pa;
    pa->pb_ = pb;
    cout<<pb.use_count()<<endl;
    cout<<pa.use_count()<<endl;
}
int main()
{
    fun();
    return 0;
}

1过程 全是shared:

创建B类对象pb,count B= 1

创建A类对象pa,count A= 1

pb强引用pa_ , count B = 2

pa强引用pb_ , count A = 2

跳出函数同时析构,引用计数分别 - 1,无法释放内存资源

2过程 shared + weak:

创建B类对象pb,count B= 1

创建A类对象pa,count A= 1

pb弱引用pa_ , count B = 1

pa强引用pb_ , count A = 2

跳出函数同时析构,引用计数分别 - 1,B对象引用计数为 0 ,A对象引用计数减为1同时由于B析构释放内存,A对象引用计数减为0,A对象B对象均释放。

注意:weak_ptr 无法直接访问,需要转换成shared_ptr,再进行访问!!

智能指针发生内存泄漏:

会,循环引用导致计数无法置零,导致内存泄露。

引入weak_PTR

十大排序算法:

https://blog.csdn.net/m0_62789921/article/details/131815782?spm=1001.2014.3001.5501

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值