cpp+数据结构+设计模式
文章目录
- O
- KR
- cpp
- # 参考文献
O
C++和数据结构不再成为面试短板
KR
cpp
+++++++++++++++++++++++++ | ++++++++++++++++++++++++ | ++++++++++++++++++++++++++++++++++++++++++ |
---|---|---|
链接 | 参考 | 备注 |
## 1 cpp | +++++++++++++++++++++++ | ++++++++++++++++++++++++++++++++++++++++++ |
**1.1 **基本认知 | ||
为什么感觉c++学了很久,没有用 | Blog: | ***C/C++ 整套的语法不具备“功能完备性”,单纯地使用这门语言本身提供的功能无法创建任何有意义的程序,必须借助操作系统的 API 接口函数来达到相应的功能。**当然,随着 C++ 语言标准和版本的不断更新升级,这种现状正在改变;而像 Java、Python 这类语言,其自带的 SDK 提供了各种操作系统的功能。举个例子,C/C++ 语言本身不具备网络通信功能,必须使用操作系统提供的网络通信函数(#include<system/socket>如 Socket 系列函数);而对于 Java 来说,其 JDK 自带的 java.net 和 java.io 等包则提供了完整的网络通信功能。我在读书的时候常常听人说,QQ、360 安全卫士这类软件是用 C/C++ 开发的,但是当我学完整本 C/C++ 教材以后,仍然写不出来一个像样的窗口程序。许多过来人应该都有类似的困惑吧?其原因是一般 C/C++ 的教材不会教你如何使用操作系统 API 函数的内容。**C/C++ 语言需要直接使用操作系统的接口功能,这就造成了 C/C++ 语言繁、难的地方。**如操作内存不当容易引起程序宕机,不同操作系统的 API 接口使用习惯和风格也不一样。接口函数种类繁多,开发者如果想开发跨平台的程序,必须要学习多个平台的接口函数和对应的系统原理。Java 这类语言,很多功能即使操作系统提供了,如果 Java 虚拟机不提供,开发人员也无法使用。 此外c++很多时候是用来编写高性能服务框架或者接口的, 涉及业务开发比较少。 |
系统大小端是什么?如何检测 | Blog/code : | **大端是低地址存高位, 小段是低地址存放低位。网络传输都是采用大字节。用union检测。 |
内存模型 | Blog:强烈推荐,这个具体代码注释也可以看看 | *** 操作系统给进程分配4g的虚拟内存, 然后c++编译器根据程序的elf文件, 对虚拟内存中各个部分添加数据(如果没有实际虚拟地址, 会产生缺页中断去先连接实际物理内存地址)。内存分为kernel space and user space. user space 占了3G , 内存中前面是保护地址不允许访问(null在这), 之后是指令.text , rodata(存储常量的), .data, .bss(未初始化或者==0的), heap, 共享库, stack (从下往上), 命令行参数和环境变量,内核空间。 需要注意普通局部变量是mov指令,存储在text端,当运行到这里的时候会把这个指令运行在栈上,做执行。 需要注意内核空间的是共享的, 用户空间是独有的。 所以进程通信是比较难得。因此会借助内核空间共享这一个特性去做共享, mmap和管道等通信方式会在内核态开辟空间,共享。 |
栈和堆的区别和程序的堆栈调用过程 | Blog:here,, 栈代码分析 | *** 栈在内存中是连续的一块空间(向低地址扩展)最大容量是系统预定好的,堆在内存中的空间(向高地址扩展)是不连续的。堆在内存中呈现的方式类似于链表(记录空闲地址空间的链表)。栈里面不仅有压栈出栈的函数调用, 还能分配局部变量。 这里面能讲解很深的~, 一定要注意。堆栈调用非常关键,深刻理解很有用。 这里面我简单说一下连接里面的代码, 局部变量不产生地址, 直接根据栈的偏移量计算的。 直接是一个move指令(注意这里move不是压栈 , 压栈是push), 例如a =10 指定自己a在栈中所在的位置存放常量10。 调用函数sum时候, 才执行第一次压栈操作,在当前栈顶上面再开辟一个空间, 栈底移动到这里, 赋值形参, 然后存放函数调用的下一句指令让调用栈调完之后能返回。 调用函数中{ 会存储之前栈底的地址,开辟sum函数的栈空间, 然后 执行函数的指令, 最后将存储return结果的形参变量内容交给一个寄存器。 右括号负责将这个调用栈回退(pop 得到之前的栈底的地址, 赋值给当前的esp, 并将call function 的下一行指令直接给pc寄存器执行,(一般就是回退到真正的main函数栈顶)) , 然后将存返回值的寄存器内容给栈中的ret , 并开始从栈顶继续添加cout。 |
动态库和静态库的定义和区别 | Blog: | 动态库是运行时候加载的,静态库是编译时候就在的,这样会导致文件过大,但是运行速度快。 一般在版本迭代的时候我们使用动态库。 |
编译连接的流程 | Blog:(感悟, 所有语言转换成可执行文件之后, 都是指令和数据了, 所以可以跨语言调用。) |
** 预处理(预处理如 #include、#define 等预编译指令的展开与解析, 生成 .i 或 .ii 文件) 编译(编译器进行词法分析、语法分析、语义分析、中间代码生成、目标代码生成、优化,生成 .s 文件) 汇编(汇编器把汇编码翻译成机器码,生成 .o 文件)。链接( 这个过程第一阶段符号表解析 是把各个.o 文件的elf中同样段进行合并, 所有的符号表中und的变量都要在这过程中查找在哪, 解析成功之后给所有的符号分配虚拟地址。 , 最后生成 .out 文件 (注意这些过程根据objdump 查看elf文件) elf文件会包括了所有的信息。 可执行文件和obj大部分都相同, 但是可执行文件还有一个program 告诉系统哪些内容加载到内存中。 一般只加载代码段和数据段。 |
全局变量、局部变量、静态全局变量、静态局部变量的区别 | Blog: | 除了局部变量,其他变量都是存储在全局区或者说静态区。 全局在局部中会被局部覆盖。 |
什么是内存对齐?内存对齐的原则?为什么要进行内存对齐,有什么优点? | Blog:这个视频讲的非常清楚, 这个博客也很好。 | 内存对齐是避免cpu多次io造成的浪费。 结构体变量的首地址能够被其最宽基本类型成员大小整除;不能整除补0 。 |
1.2 C++ 基础 | ++++++++++++++++++++++++++++ | ++++++++++++++++++++++++++++++++++++++++++ |
C 和 C++区别 | Blog: ref1 |
** C++ 对 C 的“增强”,表现在以下几个方面:类型检查更为严格。增加了面向对象的机制、泛型编程的机制(Template)、异常处理、运算符重载、标准模板库(STL)、命名空间(避免全局命名冲突)。
c++是面向对象,c是面向过程的。 |
带默认参数的函数理解 | 已经默认值得参数可以不用传 , 汇编指令会默认push 固定的值。 对比loop(a,b) ,少一句move eax dsp[b] ,push eax, 带默认参数的话直接push 40 (假设这是默认参数) 此外要从右向左给, 因为栈的调用就是这个顺序。 | |
inline | Blog: | 最大的区别就是不在符号表中生成函数, 例如a+b这个函数, 本来a+b这很简单的mov 和add指令, 使用函数还要做栈操作, 浪费了很多时间。因此多次运行的简单函数建议申明成inline . 内联只是一个建议。 具体还得看编译器。 debug版本inline 没用, 因为要调试。 |
为什么c++才支持函数重载? | c++符号表产生函数时候通过函数名和参数列表。 而c只根据函数名, 规则不一样。重载一定是在一个作用域中。 const 不影响 | |
C++中怎么调用c? | c++没办法直接调用c, 因为符号表规则都不一样。extern “C” 告诉c++编译器用c的编译规则。 | |
const和define | Blog: |
编译阶段:define 是在编译预处理阶段进行替换,const 是在编译阶段确定其值。const一般在函数声明, 函数传参的时候要加上。
|
const怎么理解? | c中const还是一个变量, 但是c++就是常量了。 会出现编译中替换的现象。 const修饰的变量不能作为左值了,也不能给其他普通指针间接修改。 这里面可以说很深~ | |
指针常量和常量指针的区别 | Blog: | const 修饰最近的类型。 指向常量的指针不能改变数值(因为修饰了int * ,但是可以重新更换地址), 指针常量不能修改地址,但是可以修改数据(因为* 没被const)。 |
nullptr | 专门给指针用的。 | |
指针和引用的区别? | Blog: | 指针可以有多级,但是引用只能一级。(是否能为多级)引用必须初始化, 指针可以设置成野指针, 但是不提倡。 引用是更安全的指针,它会自动调用解引 *。引用变换内容值和指针解引用变换内容值的汇编指令是一模一样的。 |
左值、右值引用和 move 语义 | Blog:这个视频讲的非常好 | 右值没名字(临时变量),没内存(类似20这种数据在寄存器中)。 左值引用不能绑定到要转换的表达式、字面常量或返回右值的表达式。右值引用恰好相反,可以绑定到这类表达式。右值引用底层是先在栈上创建一个临时变量拿到右值,然后把这个地址给右值引用。 int && b = 30 他和const int & b = 30 汇编是一样的, 但是更自由。2 右值引用变量本身是一个左值。 很多时候我们需要赋值a = b时候, 是调用了拷贝构造, 把b的值取出来, 拷贝到a中,实际上根本不需要b, 只是需要b里面存储的值。 因此a = move(b)会将b中的内容直接偷出来,不做拷贝到a,直接赋值给a,让a占用这个空间。 b里面的值清空。 右值引用可以用在大的数据量对象中,避免拷贝。 |
new 和 malloc 的区别,delete 和 free 的区别,delete和delete[]区别? | Blog: | new/delete是C++运算符,需要编译器支持。malloc/free是库函数,返回void* 类型,而new返回具体类型(通过运算符重载函数返回的,所以会这样)。 malloc通过通过null判断失败,new通过异常去做失败判断。 开辟数组 new int 20; 数组的释放要加delete[] (这样会释放多个空间,但是调用一次free, 为什么要加[] , 因为如果遇到对象数组的话(普通的话不会, 所以可以混用【】和不加【】),使用[]得到存储对象个数首地址,这个new[]的时候定义的隐藏的。 )。new先malloc, 再调用构造函数, delete 先调用对象析构, 再用free释放自身。 |
内存泄漏的定义?如何防止 | Blog:ref1 |
使用 malloc等之后要释放内存,否则这块内存就会造成内存泄漏。
此外指针指向其他地方,但是当前地方没有人维护。也会造成泄漏。 |
sizeof 和 strlen 的区别 | Blog: |
strlen 本身是库函数,因此在程序运行过程中,计算长度;而 sizeof 在编译时,计算长度;
sizeof 的参数可以是类型,也可以是变量;strlen 的参数必须是 char* 类型的变量。 |
memcpy 函数的底层原理?手写一下 | Blog: | 已经实现了。 这里面有覆盖的危险, 所以还有一种安全的实现方式。 注意对于对象使用这个function拷贝可能会有问题的。 |
函数指针和指针函数的区别 | Blog: | d |
参数传递时,值传递、引用传递、指针传递的区别? | Blog: | d |
回调函数是什么 | Blog: | d |
static从函数,类,变量三个角度说一下 | Blog: | 存储于静态区, 不属于局部变量内部。static 在类中使用的注意事项(定义、初始化和使用)。。。这里面能回答的非常多,还是需要再准备一下。看一下ref1。静态成员变量是在类内进行声明,在类外进行定义和初始化 |
volatile 的作用?是否具有原子性,对编译器有什么影响? | Blog: | 多线程中他好像是一个锁。 |
1.3 面向对象OOP | ++++++++++++++++++++++++++++ | ++++++++++++++++++++++++++++++++++++++++++ |
class 和 struct 的异同 | ||
类,对象, this指针 | 类是模板, 对象是实体。类体内定义的成员函数自动是inline . 所有的成员方法都会默认添加this指针参数, 去找到这个对象的地址,获取其成员变量。你不加编译器就会加, 你加了编译器就不加了。 ,delete this需要保证this是new 出来的才行。如果对象是const, 只能调用const类型的成员函数。 *this 就是对象。 | |
类大小的计算 | Blog: | 类大小只和成员变量有关, 成员方法都共享的。 按照结构体对齐来算, 并且有虚函数指针的话还要加上指针的大小虚函数指针一般是4字节,有的是8位。 |
对象创建限制在堆或栈,数据段。堆栈上对象有什么区别 | Blog: |
堆化直接私有化构造不行, 将析构函数设置为私有,编译器检查无法析构便会在堆上。 栈上请将 operator new() 设置为私有。数据段就初始化成全局或者静态。 注意堆上一定要手动释放, 栈上因为出栈作用域的原因会自动调用。
|
深拷贝和浅拷贝的区别(运算符赋值函数和拷贝构造函数) | Blog: | 有指针的话(也就是有外部资源,堆)浅拷贝析构时候会报错。 深拷贝的时候如果拷贝的是成员数据不占用外部资源,也可以理解不是带有外部资源的对象, 直接memcopy就会造成浅拷贝, 因此要for循环拷贝。 赋值构造函数赋值之前需要把之前的空间都释放了, 才能更好接收赋值过来的。 但是拷贝不用,是新的。 |
构造函数列表初始化为什么更为方便一些?列表初始化有什么应用? | 如果有成员对象,用构造函数初始化列表进行初始化成员对象。 需要注意列表初始化相当于初始化, 构造函数内再定义相当于赋值。 相当于 =0 之后再 赋值。 另外列表初始化顺序只和声明顺序有关。 | |
能否通过初始化列表初始化静态成员变量 | Blog: | 不能,静态成员变量最好类内声明,类外初始化.静态成员是单独存储的,并不是对象的组成部分。如果在类的内部进行定义,在建立多个对象时会多次声明和定义该变量的存储位置。在名字空间和作用域相同的情况下会导致重名的问题。 |
静态成员变量和静态成员方法, | 静态成员方法没有this指针,变量也是,不属于对象,属于类。 静态成员方法只能调用静态成员变量。 常成员方法只能调用常方法,因为const对象传递的this指针不能直接给一个非const去修改。 | |
常成员函数与常对象 | 常对象调用普通成员函数时候, 实参是const * ,但是普通的函数形参是*this, const不能直接赋值给非const去修改。常对象访问一般的成员函数会出错, 因此只要一个成员函数不修改, 都要申明成const , 便于调用。 所有这些都在完this指针。 | |
指向类成员的指针 | 普通成员变量这里需要加作用域, 然后实例化之后调用这个指针就相当于调用这个成员变量。 但是如果是静态成员变量的不用实例化就能调用这个成员变量。 类方法也是这样。 | |
1.3 模板编程 | ++++++++++++++++++++++++++++ | ++++++++++++++++++++++++++++++++++++++++++ |
怎么理解模板编程? | 使用typedef T 实现通用的变量, 可以使用默认值。 也可以加入正常参数。 | |
vector底层实现是什么?数据存放太多会怎么样?push_back底层实现是怎么做的?** | Blog:vector底层的实现 | 使用模板编程方式, 有first , last, end三个指针。 去做判断。 拷贝构造时候只拷贝有效元素的个数。 扩容首先开辟两倍新地址, 把原来的数据放进去, 放进去之后原来的删除。 |
空间配置器allocator | 之前我们用手动空间分配的地址由于是用new , delete, 这会导致很多时候我们只是为了分配空间,并没有调用构造析构对象的意思, 也就是在最前面我们只需要malloc , 释放只需要析构(可能会删除资源), 不需要free(因为内存属于从first-end) 。 因此allocator 负责四件事: 内存开辟和释放, 构造和析构。 帮助含有对象的vector等容器底层实现。 | |
1.3 运算符重载 | ++++++++++++++++++++++++++++ | ++++++++++++++++++++++++++++++++++++++++++ |
运算符重载意义 | 让对象实现普通数据类型的操作。 运算符重载等成员函数返回过程中, 如果返回的局部变量的指针或者引用是有危险的,因为栈帧回退局部变量会释放 。 这时候要返回零时变量, 做一下拷贝。 | |
迭代器的作用? | Blog:Blog: | 实质就是统一的方式透明的遍历容器。能够访问所有类型容器中的元素。它是容器中的嵌套类, 里面有begin end ++重载, 记录地址。 for each 就是通过const 迭代器去遍历(注意不能修改, 因此引用时候要加const去接收)。 |
迭代器失效的原理? | 迭代器失效分三种情况考虑,也是分三种数据结构考虑,分别为数组型,链表型,树型数据结构。(自己试着从插入删除与存储方式结合起来分析) 如果是线性容器, 删除一个节点, 那么当前以及之后的节点就失效了。需要重新获取插入删除之后的有效迭代器(it = erase(it)) | |
new 和delete 重载实现对象池 | 这里我还没怎么看,属于应用的。 这里对于一些频繁new, delete对象, 开辟对象池,并重载new和delete去指向对象池, 以空间换时间。 | |
1.4类继承相关的内容 | +++++++++++++++++++++++++ | ++++++++++++++++++++++++++++++++++++++++++d |
什么是面向对象?面向对象的三大特性 | Blog: | d |
说一说继承理解, 继承三种方式?派生的构造析构顺序? | 如果一个类所需要的属性和方法在一个类中都有, 完全可以使用继承, 不用自己再手写一遍。 类和类的关系有组合和继承。 继承的话会有作用域区别, 因此可以覆盖(通过作用域的不同)。 想被派生类访问但是不想被外部访问, 定义成protect , 不想被派生和外部访问定义成public 。 class 默认继承是私有, struct 默认是public。 派生类不容许直接初始化继承来的成员, 需要调用基类构造。 派生过来的成员通过基类的构造(这里放在派生类的构造表上)和析构去释放。派生是基类先, 因为可能派生类会用到基类, 析构 是自身先, 然后才是基类。(他们通过出作用域自动调用, 也就是出栈操作。 ) | |
重载(同一作用域情况)、重写(虚函数)、隐藏(作用域的隐藏)的区别 | Blog: | 重载overload发生在同一个类中。 覆盖override 子类重新定义父类中有相同名称和参数的虚函数。隐藏overwrite子类重新定义父类中有相同名称的非虚函数 ( 参数列表可以不同,先找自己的,找不到彩照 ) |
派生类和基类的互转理解包括指针 | 基类不能转成派生类, 派生类指针不能指向基类, 避免访问派生类的独有属性, 但是基类指针可以访问派生类, 当然这时候只能调用继承过的内容。 | |
静态绑定和动态绑定是怎么实现的? | Blog: | 静态绑定检测如果不是虚函数, 直接调用call 作用域下的function . 动态绑定是指检测这是虚函数,会触发查找虚函数表, 由于基类指针拿到了派生类地址, 会先去这个派生地址头部前四个字节拿到虚函数表指针,这找到他的虚函数指针, 这样找到派生类的虚函数具体地址了。最后call ecx( 这里面放的虚函数表中函数的地址,因为寄存器编译时候不能知道具体值, 要注意虚函数指针是运行时候才生成的,虚函数表编译时候就有。所以只有运行的时候才知道寄存器放的什么地址。(这时候就又有一个问题, 虚函数表指针为啥运行时候才产生) ) |
纯虚函数和虚函数区别 | Blog: | d |
虚函数的实现机制和结构, | Blog: | 虚函数表在编译阶段就有了 。 里面存放了当前的类名 , 偏移量, 虚函数的地址。 构造函数执行完成之后会有一个vfptr。 指向公共的虚函数表。 当用基类的指针来操作一个派生类的时候,虚函数表就指明了实际应该调用的函数。虚表指针和对象绑定。即类的不同的对象的虚函数表是一样的。当子类没有虚函数,但是父类存在虚函数的时候,子类依旧会新建一个虚表,虚表里面存放的是父类的虚函数地址指针。 |
**1 构造函数、析构函数的作用区别, 2 构造析构能否定义成虚函数?3 构造函数中能否调用虚函数?4 静态成员能否使虚函数, 5 虚函数指针和表什么时候创建的 ** | Blog: | 1 构造负责分配空间和初始化, 析构负责释放堆中的数据(注意析构不代表不能访问了, 只是清理资源,但是也意味着不希望再访问了。)。 虚析构函数定义为虚析构函数是为了当用一个基类的指针删除一个派生类的对象时。2 虚析构可以释放派生类的资源,当发生多态时,父类指针本身查找指向地址前四位, 拿到虚函数指针地址, 对应到虚函数表, 找到子类的虚析构进行销毁, 避免内存泄漏(这里限于堆上, 栈上的出作用域都会析构)。 构造函数无法弄成虚函数是因为 虚函数指针在这个过程中初始化。 35 同时我们还可以注意到,虚表指针的初始化确实发生在构造函数的调用过程中, 但是在执行构造函数体之前,即进入到构造函数的"{“和”}"之前,所以是不能调用虚函数,因为继承时候基类如果调用虚函数,派生还没初始化完成呢。 但是从语法上没问题转换成了静态绑定。4 静态成员不能是虚函数, 因为静态成员函数不属于对象。构造中可以调用虚函数, |
动态绑定发生的条件? | 必须由指针引用(引用是隐藏的指针)访问才能产生动态绑定, 不然虚函数在汇编中没用的, 直接看成call back 。 | |
多态的定义? | Blog: | 动态多态是基类指针调用派生类对象实现的(注意没有指针引用情况下, 动态多态无效的)。静态多态包含模板和函数重载。动态多态可以把很多重复功能不同输入的函数合并成一个通过基类指针去动态访问的。 |
抽象类,派生类的定义 | 抽象类, 没有实质定义的类, 只是为了规范下一派生调用,作为基类指针去实现多态。 | |
1 虚继承的继承过程, 2 基类指针堆上虚继承虚函数情况下, delete为什么会出错。 | 虚继承会有一个虚继承指针vb(base的意思, 注意vfptr中f 代表function)ptr(这也会占四个字节哦),指向虚继承表, 表中存放了继承过来的变量存放的地址(一般在类内存最后面)。2 虚继承基类地址放到派生后面, 为了调用虚函数, 基类指针指向了基类的虚函数指针, delete时候会出现上面的漏了。 | |
1 粼形多重继承时会出现什么状况?2 如何解决?,3 多重虚继承要注意什么? | Blog:粼形继承 | 如果多继承的多个类依赖公共类, 命名重复(但是作用域不同哈),就需要使用虚继承。 让共同基类成为一份, 但是注意还是可以通过作用域去访问, 但是这多继承的类的vbptr都指向了一个成员变量(两个指针根据自己的位置, 偏移地址不一样)。 所有需要直接继承baseA的, 都要声明成虚继承。 3 需要注意原始base A 需要最终继承去承担初始化(需要注意这里是要写的,只是我们默认调用了默认的构造,但是如果我们关闭了默认构造, 要自己做的,注意。 )还要注意虚继承和虚函数是两种工作形式。 没有什么大的关系。 |
mutable 是什么 | mutalbe的中文意思是“可变的,易变的”,跟constant(既C++中的const)是反义词。 在C++中,mutable也是为了突破const的限制而设置的 | |
explicit 的作用 | Blog: |
(如何避免编译器进行隐式类型转换)A ex = 10;在编译时,进行了隐式转换,将 10转换成 A类型的对象,然后将该对象赋值给 ex; // 发生了隐式转换d
|
如何让类不能被继承?如何删除函数 | Blog: | final , delete |
友元函数的作用及使用场景 | Blog: | d |
四种类型转换方式(相比较c而言指导类型转换是否合法) | Blog: | static_cast基本类型转换(遵循相关的, 不强制更改内存大小等规则,是静态类型转换),派生类到基类之间的转换等编译器认为安全的转换,百分之90 的情况用这个转换。 const_cast只用于去除指针和引用常量性(相当于 在const int a = 10 ; int * p = (int * )&a, 为了编译通过强行把&a转成int* 了, 本来编译不通过。 但是const_cast 内部害会避免我们强转因为地址大小的差异,产生不可预知的错误得问题。 )reinterpret_cast 是c语言类型, 不安全。 dynamic_cast动态类型转换, 只能用于带有虚函数的基类或派生类的指针或者引用对象的转换,支持从基类指针到派生类指针返回(但是会判断当前基类指针是不是指向了这个派生类,如果用static_cast 会直接强行转, 不做判断, 这可能会有风险的 )。 |
1.5 STL | ++++++++++++++++++++++++++++ | ++++++++++++++++++++++++++++++++++++++++++ |
知道哪些容器,简单说一说 | Blog: | vector、list、deque、prior_deque , set、map、unorderedmap |
说一下vector的增删改查 | 可以看出自己之前理解的很浅。 | insert时间复杂度是n, 从末尾插入的push_back时间复杂度为1, pop_back 删除复杂度为1 , erase 删除的复杂度为n, 查询的话 []方式复杂度是1 , 迭代器遍历是n, find , for-each (底层通过迭代器实现的) 。 对vector进行insert(迭代器, 数值)和erase(迭代器)操作,迭代器要重新获取, 因为之前的会失效。 reserve预留空间但是last还是null, resize也会预留空间, 但是last 会走到finsh |
deque底层实现, cudr | 双端队列每一个方向都可以增加和插入。这个底层实现还挺复杂的。 底层结构是动态开辟的二维数组(行与行之间内存 是不连续的),第二维长度是4096/ size(T),它是固定的,。 初始时候收尾在一个一维数组的中间, 满了之后两端扩容, 将维度*2 , 将之前的数据放入中间。 push_back 底部增加复杂度1 , push_front 也是1 , insert复杂度n, pop_back 1 , pop_front 1 , erase 复杂度 n , 查询的话, n 。 | |
List容器 | pre, next 的双向链表, 删除插入的时间复杂度为1 。 | |
链表和数组, list的之间的异同,如何根据业务场景进行选择 | deuqe 和vector删除插入的复杂度对比, 都是n , 但是deuqe第二维不是连续的, 所以会更慢一些, vector是绝对的连续。 vector和list复杂度 就不说了, | |
容器适配器理解?底层基于什么数据结构? | 它是一种适配器模式,或者说代理模式。 适配器底层没有自己的数据结构, 全部依赖封装了另外一个容器。没有实现迭代器。因为没有实例的容器。stack 中pop ,push , top 。 queue 中push , pop , front , back. priority_deque 中操作和栈一样, 不能从末尾push了, 因为是优先队列。 2. stack和queue 底层依赖deque, 这样不用重复的拷贝,而且出队效率高, 而且不用大片的连续数据。proirity_quene 是大根堆,大根堆通过节点进行下标的, 所以是vector。 因为deque 维度之间不连续。 | |
1 关联容器分类, 2 无序容器的api 3 无序应用场景 , 4 有序的关联容器 | 1 关联容器分为有序(红黑树)和无序(哈希函数),存储形式分为map和set ,2 对于set容器insert, erase不用指定位置, 是数据结构本身决定的。 count返回当前数据的数量。 注意如果迭代器遍历过程中还是要更新迭代器的。 find 返回迭代器,如果找不到返回end。 对于map , insert 去插入pair结构体数据, 不能直接插入两个。 map还提供【】运算符重载, 支持key下标访问value([]如果key不存在,会自动创建新key). 3 大部分业务对于数据没有顺序的要求情况下使用无序的容器,对于去重, 查重需要统计数据和个数用map , 不统计个数的去重用set。4 有序容器插入,遍历方法和无序都是一样的, 但是结果不一样,有序容器遍历是有序的,无序随机打乱了。 需要注意如果传入的key(注意只是key哦)是自定义对象,需要定义重载函数< 。 | |
迭代器 | 普通迭代器能读能写, 有时候不用写的话可以const_iterator . rbegin 是反向迭代器开始, rend 是反向迭代器的结束,是正向的开始。 const_reverse_iterator | |
1 函数对象(仿函数也是一个意思)定义,2 应用与优势? | 函数对象就有小括号重载的对象。 对象封装()运算符重载函数 (难怪我之前看代码很多对象进行传参, 很不明白为啥要构造这么多对象, 原来是函数对象)。 less,greator这些就是函数对象实现的, 定义对比模板之后, 将对比模板作为对比函数中的一个函数指针模板, 供函数进行调用接收, 根据传入的指针, 判断比较函数中到底做大于还是小于的操作, 这样避免了定义多种对比函数。 我们在写代码时候也可以定义这样的函数,将函数指针传入进去,实现对比。 这样有一个问题就是函数指针无法inline, 频繁调用函数会有很多消耗, 我们改成函数对象, 也就是改成带有()重载函数的对象,这样可以内联(因为函数对象()在类内部定义的)。而且还可以添加额外成员变量用来记录信息。在stl中很多时候如果更爱有序容器的排序方式, 或者添加自定义对象的排序方式, 就用这个(但是对象需要自定义)。 lambda 就是简短的函数对象,封装了绑定器和函数对象, 让代码看起来简单, | |
泛型算法和绑定器bind | 泛型算法接收的都是迭代器,这样才可以遍历整个元素执行算法的具体功能。 bind绑定器+ 二元函数 = 一元函数, 用来在泛型算法中二元函数绑定成一元。 | |
1.6 对象优化 | ++++++++++++++++ | ++++++++++++++++++++++++++++++++++++++++++ |
对象优化主要是想讲什么? | Blog: | 很多人说c++的性能不如c , 这其实是我们在写代码的过程中,用了很多编译器帮助我们优化而添加的额外代码, 因此我们在这里主要想讲一下如何写对象, 能够达到性能和c匹配的同时, 还享用面向对象的特性。 |
对象使用过程中背后调用了什么?2 赋值运算符重载和拷贝构造函数的区别 3 临时对象是什么4 函数传值对象调用调用太多了5 如何优化对象 6 如何进一步优化对象 7 vector中push_back用的左值还是右值? move和forward 是什么? 以vector中push_back为例 | Blog: | 1. 主要考察一些构造析构的顺序, 编译器的优化, 临时对象的操作。 我们需要明白各种形式的一个类背后构造析构拷贝运算符重载的时间,这样我们才知道如何优化它,避免无必要的优化。2. 赋值运算符重载函数和拷贝构造函数有时候看起来一样,但是拷贝构造= 出现在第一次定义时候。 3.生存周期只存在在这个语句中的对象。临时对象产生新对象时候, 编译器就会优化掉临时变量,直接构造新对象。 4 如果define t2 , 设t2 = fc(t1)要注意对象传入函数形参相当于拷贝构造了一个新的函数, 返回函数的函数局部变量时候还要拷贝到到main函数栈帧上一个临时变量, 然后再调用=运算符. 这一共调用了11个函数, 太多了, 得优化一些。 5 三句话, 函数参数传递过程中对象优先按照引用传递。 函数返回时候优先返回临时对象, 不要返回局部对象。接收函数返回值时候,建议使用初始化方式。也就是 class a = loop(b), 而不是 a = loop(b);这三句能避免很多额外的运行。 6 很多函数返回局部变量时候都要创建一个新的临时变量去拷贝局部变量, 这时候建议使用move去优化 。 此外还可以添加右值拷贝构造用来直接将临时对象的内容在函数内部全部转移到本对象,清空右值引用里面的内容, 其中右值引用的拷贝构造也是如此。 二者结合可以让对象的操作中数据不会复制, 只会转接权限。 7 如果传入左值,就用左值构造, 右值就是右值拷贝构造。 自从c++11之后容器其实不怎么消耗计算了。 8. move 底层以static_cast强转成右值引用,解决传参给右值引用函数。 forward可以判断输入并自动返回左值或者右值(注意右值引用本身还是左值, 所以右值引用传入进去调用左值, 所以在这个场景中使用。他用在不知道当前值是左值还是右值情景中,自动判断) |
Blog: | ||
Blog: | d | |
Blog: | d | |
1.7 智能指针专题 | ++++++++++++++++++++++++++++ | ++++++++++++++++++++++++++++++++++++++++++d |
**1 C++11 中智能指针作用 2 说一下四个指针3 shared , weak 是线程安全的嘛 4. 怎么避免互相引用 5 智能指针删除器 ** | Blog:weak指针解决了什么问题?,这里解释了相互引用Blog: |
智能指针是利用了一种叫做RAII(资源获取即初始化)的技术对普通的指针进行封装。他通过栈的特性, 做到自动释放地址。 2 shared_ptr ,(查看连接的数量) ,对shared_ptr进行初始化时不能将一个普通指针直接赋值给智能指针,因为一个是指针,一个是类,可以通过make_shared函数或者通过构造函数传入普通指针。auto_ptr只让最后一个指针指向地址, 其他的都变成野指针,这还是很有风险的,转移所有权。而unique_ptr(借助 std::move() 可以实现将一个 unique_ptr 对象赋值给另一个 unique_ptr 对象,其目的是实现所有权的转移。),虽然和autopointer 一样,但是语法上的move能够显性的表达我们想要的操作, 不会像auto那种隐藏了。 weak_ptr只是观察者(解决两个shared类型互相引用无法释放资源) 不能直接获得对象所有权需要获得所有权时需要转化为 shared_ptr (这里转的时候会加锁且判断能不能转, 能用在多线程中判断是否存活,解决线程安全问题), 因为没有-> , * 重载), 弱智能指针不会计数, 3 . 是线程安全的, 因为他们count用automic_int 4 定义对象的时候使用强指针shared, 引用对象的时候使用weak 。 5 这个是函数对象, 自定义释放资源的方式, 因为有些指针指向的资源是文件或者其他, 不能直接用delete 这种直接的释放方式,得用closefd等方式。
|
1.8 函数绑定功能 | ++++++++++++++++++++++++++++ | +++++++++++++++++++++++++++++++++++++++d |
1. bind1st2nd 的作用?2 绑定器是什么?3 function是什么?4 function应用点在哪里?5. 模板的完全特例化和实参推演是啥?6 function的原理 7 bind 是什么? 8 bind 和function 应用点 9 lambda 表达式如何理解 | Blog: | 通过固定一个值, 把多元函数转换成一元函数对象。2绑定器是函数对象,底层还是调用二元函数对象。 3 . 希望将传入的实例化参数设置成对象的()重载。4. 他可以接收函数,函数对象, 绑定器, lambda 函数对象, 相比较函数指针只能接收普通函数, 具有很好的泛化型。 此外还可以保存起来, 让一些只存在于语句中lambda表达式等存储起来了。 5 一个模板T范围太大了, 统一的处理方式可能对所有的数据结构并不适用, 这里我们要适用实参推演等,定义自定义的模板函数。 6 ,利用函数实参推演等机制, 封装了一个接收函数类型和参数的模板对象, 模板对象中定义适用了函数对象再定义运行函数(这里面搞成了指针)。此外还通过可变参数实现了多种长度参数的传入。 7 bind 是一个函数模板返回的是函数对象, 它传入的是地址和输入, 只能用于语句中, 但是function 可以存储。 所以可以将bind和function结合起来,保存绑定器的函数对象。 8 线程池就是一个很好的例子 ,把成员方法通过bind和function传递, 这是c++11的特性。 9 . 之前的函数对象一般用于泛型算法, 需要一个自定义的算法还有定义一个函数对象, 灵活性太差了, 但是lambda表达式可以自动产生一个函数对象, 不用自己定义函数对象了,底层实现好了。这个还是比较高级的用法,自己只能简单了解。(lambda 可以和function结合突破语句级别的限制) |
1.9 C++ 复习加多线程部分 | ++++++++++++++++++++++++++++ | +++++++++++++++++++++++++++++++++++++++d |
复习auto , nullptr , for_each , 右值引用, typename …A可变参, bind , function , lamnda 表达式,智能指针, 容器, 空间分配器 | Blog:、 | d |
**1. 多线程的基本流程 2 多线程的问题 3 为什么要线程间的同步通信?4 . 生产者和消费者模型 5. lock_guard 和unique_lock的区别 6 atomic 7 volatile在多线程应用 ** | 1. 定义一个函数, 将函数名和参数加入到thread类中, 主线程调用join等待或者detach分离。不然会产生僵尸进程符号。 2. 多线程要对临界资源进行加锁 , 但是要注意如果临界资源中间return了, 要像智能指针那样能够自动在析构中释放锁, 不然会产生死锁。 这时候可以使用unique_lock 和lock_guard等。 3. 线程执行的顺序如果不通信, 因为cpu调度, 很难知道哪里在运行。 无法控制和调试。 4. 条件变量和互斥锁(unique_lock)实现生产消费者模型。 5 互斥锁只有一把, 而且容易忘记解锁了。lock_guard出作用域析构, 但是不能用于拷贝构造和赋值函数, 因此不能用于函数传参。 但是unique_lock 是可以的。 刚好condition是需要传参的,因此只能用unique_lock 6 互斥锁比较重 ,额外的代码影响业务阅读。 使用atomic 这种用硬件操作总线加锁方式api, 直接硬件操作, 速度更快。 7 对于一些原子操作的数据类型, 可能编译器会因为代码频繁操作直接加载到缓存或者寄存器中, 这样一些临界资源没有更新到最新的, 使用volatile防止其被编译器优化到缓存中,让一个线程的临界资源操作能够马上让另外一个线程收到。 | |
1.10 设计模式 | +++++++++++++++++++++++++++ | +++++++++++++++++++++++++++++++++++++++ |
0 设计模式分为哪些?1 单例模式应用场景?2 单例模式是什么, 怎么设计 ? 3 手写一下 | 0 , 创建型(对象创建), 结构类型(对象组合), 行为类型(关注对象的通信)。 一些资源因为要频繁调用,没必要每次用的时候创建析构, 直接提前初始化好, 通过一个方法统一调用这些资源。例如日志和数据库模块 2 私有化构造赋值重载不让用户构造, 设置获取资源接口并设置成static 类型(这里初始化汇编代码中会自动调用加锁和解锁mutex。),每次返回唯一的对象,没有其他方式产生第二个实例对象了。 单例分为饿汉和懒汉, 饿汉main函数之前就初始化了, 这并不好, 会影响程序初始加载的速度。 懒汉会有线程安全的问题, 注意加锁。 | |
工厂模式是什么? 应用场景? | 1 简单工厂模式封装了对象的创建, 将多个类的创建合并到一个工厂类, 通过输入不同的参数返回不同的类。 但是这不符合软件设计的开闭原则, 因此工厂方法通过设置虚基类然后多个派生工厂继承的方式将多个工厂分开。这样增加新的产品不影响原来的代码, 只需要添加新的。但是这个工厂还是有问题, 假如我们的一个工厂不生产一个产品, 而是要生产一系列产品。 抽象工厂负责一个工厂生产多个相关的产品。这时候工厂抽象类要声明多个纯虚函数。 但是还是有一个问题, 如果一个产品有了特殊的产品, 其他类都不得不实现,不然其他类成了抽象类。 | |
代理模式 | 代理模式代理一个委托类的接口, 对这个类创建多个权限的代理类, 去按照自己代理类的属性去执行对应接口。 他们都是从基类继承过来的。客户直接访问代理,不能访问维托类。 | |
装饰器模式 | 和代理类非常相似, 但是主要是增加现有类的功能。 按照普通的想法是对现有类进行派生多个add function的子类, 但是装饰器将想要添加的方法重写了一个类,添加到现有类。 不过这只是运行时候的装饰, 实际原始代码并没有变。只不过将当前类传入到装饰器中, 装饰器类通过基类指针调用这个类, 而且还会加自身的功能。 | |
适配器模式 | 让不兼容的接口可以一起工作。 当一个接口定义只支持一种输入时候, 新的输入需要添加一个适配器类将新输入转换成接口支持的类。 其实这时候插入进去是适配器类。 | |
发布订阅模式(也是观察者模式) | 设置多个观察者类, 然后设置主题类, 主题类里面创建一个map存储自己的主题, 每一个主题的map second存储了那几个观察者类订阅了, 主题类提供添加和主题发布api, 当观察者订阅了一个主题, 主题类会更新该主题的观察者列map列表。当发布主题时候, 取出订阅了当前的主题的观察者列表(里面存储的观察者对象指针), 然后遍历通知。 | |
1.10 数据结构 | +++++++++++++++++++++++++++ | +++++++++++++++++++++++++++++++++++++++ |
Blog: | d | |
map和hashmap区别 | Blog: | d |
hashmap怎么解决冲突? | Blog: | 开放寻址, 链表法。 开放寻址可以采用线性探测等, 链表法更好一些, 可以使用跳表加速,或者弄成红黑树。 |
容器是线程安全的嘛?如何保证线程安全 | Blog: | 不安全, 加锁。 |
介绍一下红黑树的特性,插入,删除? | Blog: | 不会 |
如何去找ip? | Blog: | 二分查找 |
知道哪些排序算法?并说一下时间复杂度? | Blog:强烈推荐 | 一定要看这个博客。 除了冒泡和插入以及0n复杂度的排序, 其他都是不稳定的。 |
二叉搜索树是什么? | Blog: | 左节点小于根节点,根节点小于右节点。 这刚好是中序遍历。 二叉平衡搜索树是左右节点长度误差小于1的。 |
红黑树本质以及各种时间复杂度? | Blog: | 红黑树是非严格平衡的二叉搜索树, 相比较avl性能更好一些。 时间复杂度统一nlongn |
堆排序怎么操作?堆插入删除怎么实现? | Blog: | 插入新数据时候从下往上堆化, 删除的时候如果直接对比会出现空洞点,这里面删除后把最后一个点插入顶层,从上往下堆化一次,就可以解决这个问题。排序时候分为建堆和排序,注意这里大顶堆可不知道左边和右边哪个更大,所以一个大顶堆不能直接输出成有序的。 因此堆排序只是一种输出算法。建堆就是建设一个大顶堆, 排序类似于不停的删除, 把堆顶取出到末尾左边,再把n-1位置数据放入的堆顶继续做大顶堆构造。再取出最大,直到全部弄完。 |
有 10GB 的订单数据,我们希望按订单金额(假设金额都是正整数)进行排序,但是我们的内存有限,只有几百 MB,没办法一次性把 10GB 的数据都加载到内存中。这个时候该怎么办呢? | Blog: | 先用桶排序让数据在磁盘上有规律(这里桶设计要合理), 然后取出内存范围以内的桶快排。 |
1亿个数据求求前top100 | Blog: | 堆排 |
什么是稳定排序?为什么需要稳定排序? | Blog: | 快排在哨兵转接时候, 如果是交换,则不稳定,如果是插入则稳定(不过数组的插入本身效率低) 举个例子: 5 8 5 2 9 ,如果用选择排序, 每次取最小的交换 第一次排成了2 5 8 5 9 ,这两个5为位置换了, 因此是不稳定的。 |
原地排序算法是啥? | Blog: | 只借助一个空间复杂度用于交换的算法。 |
1.11 超出目前能力的难点 | ++++++++++++++++++++++++++++ | ++++++++++++++++++++++++++++++++++++++++++ |
如何进行debug,查看内存泄漏 | Blog: | d |
迭代器失效原理深层怎么解决? | Blog: | d |
lambda | Blog: | d |
红黑树,二叉平衡搜索树 | Blog: | d |
算法题短板 | Blog: | 二叉搜索树, 栈队列的使用, 动态规划(求最优解,有少部分是求全部解), 回溯(求全部解的时候), 非树类型的dfs(其实这里面最优解大部分都是dp问题,多看看)。多练一下吧,重点练习回溯和dp问题。 |
# 参考文献
- leetcode总结
- gitbook总结
- 面试总结
- 牛客总结
- c++后台必看的笔记
- 腾讯课程
cpp+数据结构+设计模式相关推荐
- 算法与数据结构 设计模式
2021年 [算法与数据结构](C++实现)从入门到大神(持续更新ing) 2021年 [算法与数据结构](C++实现)从入门到大神(持续更新ing)_哔哩哔哩_bilibili 清华大学2021最新 ...
- 【设计模式学习】工厂方法模式
cpp学习设计模式:工厂方法模式 在学习工厂方法模式之前,先回忆前面学的简单工厂模式: 简单工厂模式就是将对象的创建和逻辑的判断都交给了一个工厂类去做,这样做的优点是客户端不需要知道具体产品类的类名和 ...
- 拓扑排序 php,数据结构与算法(周测7-拓扑排序和AOV网络)
判断题 1.AOE图的关键路径就是最长的路径 T F 2.AOE图的权值最大的边(活动)一定是关键活动. T F 两条边相加可能比最大的边还要大. 3.在AOE-网工程中,减少任一关键活动上的权值后, ...
- Sphinx+gitee+Read the Docs搭建在线文档系统
本文介绍一种在线文档系统的搭建,需要借助Sphinx.gitee和Read the Docs. Sphinx是一个功能强大的文档生成器,具有许多用于编写技术文档的强大功能 gitee是一种版本管理系统 ...
- Ebooks C/C++
C++软件下载 http://www.codeguru.cn/fav/soft.htm C函数实例参考手册 http://www.codeguru.cn/CPP/CExample/ 十部算法经典著作 ...
- 2020-2021考研南京大学软件学院学习经验分享(英语90,842自命题110+)
© copyright by Jesse 写在前面 我是某东北985软工科班,20年一战南京大学软件学院专硕,初试348(政治64,英语83,数学112,专业课89),由于专业课分数不太理想,20南软 ...
- 初学者必读:如何学习VC++和C++及其推荐书目和网站
本文转自博主: EbowTang 一,如何学习VC++ 1 ,vc的用处 我感觉下面一些领域比较适合于用vc: 操作系统编程, game, 图形设计, corba编程, com编程, 网络编程. 我谈 ...
- 外国开发者都知道的Android-筑基导论,作为一个Android程序员
你是不是曾经这样苦恼过,并且百思不得其解: 为啥大厂校招比社招的人多? 为啥大厂只要基础好的毕业生? 为啥硅谷面试只问数据结构和算法? 为啥大厂要给新人安排导![](https://www.huali ...
- 外国开发者都知道的Android-筑基导论,程序员必看
基础不稳,地动山摇! 好比我们学习数学,大家都是先学: 阿拉伯数字 -> 数学符号 -> 加减法 -> 乘除法 -> 九九乘法表 -> 方程式 -> 公式定理 从小 ...
最新文章
- 从Eclipse转移到IntelliJ IDEA一点心得
- 初步理解Python进程的信号通讯
- 开发文档之 概要设计说明书 详细设计说明书 数据库设计说明书
- html运行代码出现问号乱码_Java 0基础入门(初识Html)
- IIS连接数、IIS并发连接数、IIS最大并发工作线程数、应用程序池的队列长度、应用程序池的......
- 如何异步的处理restful服务(基础)
- 主从同步设置的重要参数log_slave_updates
- l洛谷P4779 【模板】单源最短路径(标准版)(dijkstra)
- linux基础命令rpm,rpm常用命令集合1
- Maven精选系列--标准目录结构
- WebView学习笔记
- GT注册大全-终结动态修订版
- java程卫琴_43名基层工会干部在“机关开放日”走进安徽省总工会参观并座谈
- [安全攻防进阶篇] 三.OllyDbg和Cheat Engine工具逆向分析植物大战僵尸游戏
- 百度网盘下载加速方法(免会员免破解官方正规)
- 牛客练习赛73 遥远的记忆(理解)
- android 监听锁屏 权限,Android中监听锁屏变化和防止锁屏
- 如何在wince下添加和删除驱动(作者:wogoyixikexie@gliet)
- Win获取本地SVN帐号密码
- REST 接口 原文翻译