面经-C++
第一: private,public,protected的访问范围:
private: 只能由该类中的函数、其友元函数访问,不能被任何其他访问,该类的对象也不能访问.
protected: 可以被该类中的函数、子类的函数、以及其友元函数访问,但不能被该类的对象访问
public: 可以被该类中的函数、子类的函数、其友元函数访问,也可以由该类的对象访问
注:友元函数包括两种:设为友元的全局函数,设为友元类中的成员函数三种继承
public继承,父类成员在子类中保持原有的访问方式
protected继承,父类中public成员会变成protected父类中protected成员仍然为protected 父类中private成员仍然为private
private继承,父类成员在子类中变为private成员
private成员在子类中依然存在,但是却无法访问到。不论种方式继承基类,派生类都不能直接使用基类的私有成员。
虚函数实现机制和底层原理
C++中的虚函数的作用主要是实现了多态的机制。
实现原理:虚函数表+虚表指针
编译器处理虚函数的方法是:为每个类对象添加一个隐藏成员,隐藏成员中保存了一个指向函数地址数组的指针,称为虚表指针(vptr),这种数组成为虚函数表(virtual function table, vtbl),即,每个类使用一个虚函数表,每个类对象用一个虚表指针。使用虚函数后的变化:
- 对象将增加一个存储地址的空间(32位系统为4字节,64位为8字节)。
- 每个类编译器都创建一个虚函数地址表
- 对每个函数调用都需要增加在表中查找地址的操作。
虚函数的注意事项 - 基类方法中声明了方法为虚后,该方法在基类派生类中是虚的。
- 若使用指向对象的引用或指针调用虚方法,程序将根据对象类型来调用方法,而不是指针的类型。
- 如果定义的类被用作基类,则应将那些要在派生类中重新定义的类方法声明为虚。
STL相关
- vector:底层数据结构为数组 ,支持快速随机访问。
- list:底层数据结构为双向链表,支持快速增删。
- deque:底层数据结构为一个中央控制器和多个缓冲区,支持首尾(中间不能)快速增删,也支持随机访问。
即双端队列。是一种具有队列和栈的性质的数据结构。双端队列中的元素可以从两端弹出,相比list增加[]运算符重载。
- stack:底层一般用23实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时
- queue:底层一般用23实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时(stack和queue其实是适配器,而不叫容器,因为是对容器的再封装)
- priority_queue:的底层数据结构一般为vector为底层容器,堆heap为处理规则来管理底层容器实现
- set:底层数据结构为红黑树,有序,不重复。
- map:底层数据结构为红黑树,有序,不重复。
std::vector的底层(存储)机制。
vector就是一个动态数组,里面有一个指针指向一片连续的内存空间,当空间不够装下数据时,会自动申请另一片更大的空间(一般是增加当前容量的50%或100%),然后把原来的数据拷贝过去,接着释放原来的那片空间;当释放或者删除里面的数据时,其存储空间不释放,仅仅是清空了里面的数据为什么vector的插入操作可能会导致迭代器失效?
vector动态增加大小时,并不是在原空间后增加新的空间,而是以原大小的两倍在另外配置一片较大的新空间,然后将内容拷贝过来,并释放原来的空间。由于操作改变了空间,所以迭代器失效。你怎样理解迭代器?
Iterator(迭代器)用于提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示,相当于智能指针。vector每次insert或erase之后,以前保存的iterator会不会失效?
理论上会失效,理论上每次insert或者erase之后,所有的迭代器就重新计算的,所以都可以看作会失效,原则上是不能使用过期的内存
但是vector一般底层是用数组实现的,我们仔细考虑数组的特性,不难得出另一个结论,insert时,假设insert位置在p,分两种情况:
a) 容器还有空余空间,不重新分配内存,那么p之前的迭代器都有效,p之后的迭代器都失效
b) 容器重新分配了内存,那么p之后的迭代器都无效咯
erase时,假设erase位置在p,则p之前的迭代器都有效并且p指向下一个元素位置(如果之前p在尾巴上,则p指向无效尾end),p之后的迭代器都无效vector中erase方法与algorithn中的remove方法区别
vector中erase方法真正删除了元素,迭代器不能访问了
remove只是简单地将元素移到了容器的最后面,迭代器还是可以访问到。因为algorithm通过迭代器进行操作,不知道容器的内部结构,所以无法进行真正的删除。vector、list、map、deque用erase(it)后,迭代器的变化。
vector和deque是序列式容器,其内存分别是连续空间和分段连续空间,删除迭代器it后,其后面的迭代器都失效了,此时it及其后面的迭代器会自动加1,使it指向被删除元素的下一个元素。
list删除迭代器it时,其后面的迭代器都不会失效,将前面和后面连接起来即可。
map也是只能使当前删除的迭代器失效,其后面的迭代器依然有效。红黑树的性质
- 节点是红色或黑色。
- 根节点是黑色。
- 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
- 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
智能指针
负责自动释放所指向的对象,防止内存泄漏
封装普通指针使其表现的像普通指针一样。超过类的作用域,将会自己调用析构函数,自动释放资源shared_ptr: 多个指针指向相同的对象
shared_ptr内部的引用计数是线程安全的,但是对象的读取需要加锁。 创建方式:1. 使用函数make_shared(会根据传递的参数调用动态对象的构造函数)。 2. 使用构造函数(可从原生指针、unique_ptr、另一个shared_ptr创建) 拷贝使得对象的引用计数增加1,赋值使得原对象引用计数减1,当计数为0时,自动释放内存。 注意不要用一个原始指针初始化多个shared_ptr,否则会造成二次释放同一内存。 注意避免循环引用: 两个对象互相使用一个shared_ptr成员变量指向对方的会造成循环引用。 解决循环引用:使用弱引用的智能指针打破这种循环引用。 强引用和弱引用 一个强引用当被引用的对象活着的话,这个引用也存在(就是说,当至少有一个强引用,那么这个对象就不能被释放)。 boost::share_ptr就是强引用。相对而言,弱引用当引用的对象活着的时候不一定存在。仅仅是当它存在的时候的一个引用。 弱引用并不修改该对象的引用计数,这意味这弱引用它并不对对象的内存进行管理,在功能上类似于普通指针,然而一个比较大的区别是,弱引用能检测到所管理的对象是否已经被释放,从而避免访问非法内存。
auto_ptr:不可以用于容器,不建议使用,不支持复制和赋值,如果进行了赋值和复制操作,并不报错
unique_ptr:则“独占”所指向的对象,不支持复制和赋值,直接赋值会报错,同一时刻对象仅能拥有一个unique_ptr指向自身
weak_ptr:它是一种弱引用,指向shared_ptr所管理的对象,不控制对象生命周期的智能指针,只提供了管理对象的访问手段,用于协助shared_ptr的工作,用于观测资源的使用情况。use_count()可以一观察资源的应用数。强制类型转换
static_cast 用于基本类型之间、有继承关系的类对象之间、类指针之间的转换不能用于基本类型指针之间的转换
const_cast 用于去除变量的只读属性 强制转换的目标类型必须是指针或引用
reinterpret_cast 用于指针类型之间、整数和指针类型之间的转换
dynamic_cast 用于有继承关系的类指针之间、有交叉关系的类指针之间的转换,具有类型检查的功能,需要虚函数的支持static_cast 和 dynamic_cast 的区别
static_cast 没有运行时检查- 用于类层次结构中基类和子类之间指针或引用的转换
进行上行转换(把子类的指针或引用转换成基类表示)是安全的;
进行下行转换(把基类指针或引用转换成子类表示)时,由于没有动态类型检查,所以是不安全的。 - 用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
- 把空指针转换成目标类型的空指针。
- 把任何类型的表达式转换成void类型。
dynamic_cast 有运行时检查,需要虚函数表
在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;
在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
- 用于类层次结构中基类和子类之间指针或引用的转换
C++ 11 了解多少
nullptr 关键字,专门用来区分空指针、0。
auto 和 decltype 这两个关键字实现了类型推导
序列for循环 变长參数的模板 更加优雅的初始化方法
constexpr 将变量声明为constexpr类型以便由编译器来验证变量的值是否为一个常量表达式,必须在编译期间计算出它的值并且它的值不可以被改变
Const只能保证在运行时是常量,即具有运行时数据的不可更改性。
lambada 表达式子类调用父类的构造函数
- 子类默认调用父类的无参构造
- 若父类定义了有参构造函数,没有定义无参构造函数,会报错
- 如果子类调用父类带参数的构造方法,需要用初始化父类成员对象的方式
类默认的6个方法
- 构造函数
- 析构函数
- 拷贝构造函数
- 赋值运算符号重载函数
- 取地址操作符重载
- const修饰的取地址操作符的重载
- C++primer
书-牧羊人的奇幻漂流
1、男孩圣地亚哥神学院毕业,通晓拉丁文、西班牙文和神学,但他不想成为神甫,要云游四方,于是,选择去牧羊,过着居无定所、风餐露宿的生活。
2、男孩喜欢看书,在连续几天的夜里做着同一样的梦,梦到一个小孩带着他去了埃及金字塔,说:“假如你来到这里,将会找到一处隐秘的宝藏”。
3、男孩找到了吉卜赛老妇人,遇见了撒冷王,开始了奇幻的寻宝之旅。
4、被骗身无分文时到水晶商贩处打工,然后在沙漠里遇见英国人和炼金术士,然后到了金字塔,最后,又回到了出发的地方、做梦的地方,发现了宝藏。故事很简单,但,它试图诠释着世界、万物、人生,以及人生来就有的天命!
一个叫做圣地亚哥的男孩,不想过着日复一日的牧羊生活,
在连续几天的夜里做着同一样的梦,梦到一个小孩带着他去了埃及金字塔,说:“假如你来到这里,将会找到一处隐秘的宝藏”。
决定前往金字塔附近寻找自己的宝藏。途中认识了一位圣王,圣王教会他认识到了自己的天命,坚定追寻自己的宝藏;
被骗光身上财宝时,绝望之际,从羊群那儿学会了用不同的角度看待问题;
沙漠绿洲中遇到自己喜爱的女人;
随之炼金术士引领男孩去实现自己的天命,与他结伴前行,一路上,炼金术士通过许多智慧话语让男孩自己学会了与世界灵魂的语言,与自己的心灵沟通,成为一位新的炼金术士。
最后,又回到了出发的地方、做梦的地方,发现了宝藏。
这是一场奇幻之旅会,是成长之旅,是不断聆听自己心灵的声音,追寻自己内心所想,求的内心的一片安宁。
面经-腾讯面经
https://blog.csdn.net/soipray/article/details/56279623
linux和os:
netstat tcpdump ipcs ipcrm (如果这四个命令没听说过或者不能熟练使用,基本上可以回家,通过的概率较小 这四个命令的熟练掌握程度基本上能体现面试者实际开发和调试程序的经验)
netstat
-t :TCP
-u :UDP
netstat –r 路由
netstat -p
https://www.cnblogs.com/ftl1012/p/netstat.html
tcpdump
tcpdump -i eth1 监听指定网络端口包
tcpdump host 210.27.48.1监听主机
tcpdump tcp port 23 and host 210.27.48.1
https://www.cnblogs.com/williamjie/p/9983958.html
ipcs
ipcs -a
ipcs -q 队列
ipcs -s 信号量
ipcs -m 共享内存
https://blog.csdn.net/huangyimo/article/details/80236181
ipcrm
ipcrm -m 18602 删除共享内存
https://ipcmen.com/ipcrm
cpu 内存 硬盘 等等与系统性能调试相关的命令必须熟练掌握,设置修改权限 tcp网络状态查看 各进程状态 抓包相关等相关命令 必须熟练掌握
cpu
iostat 监视系统输入输出设备和CPU的使用情况
内存
vmstat 显示虚拟内存状态
free 指令会显示内存的使用情况,包括实体内存,虚拟的交换文件内存,共享内存区段,以及系统核心使用的缓冲区等。
磁盘
df 文件系统的磁盘使用情况统计。
https://blog.csdn.net/guoxiaojie_415/article/details/80526667
awk AWK是一种处理文本文件的语言,是一个强大的文本分析工具。
sed 可依照script的指令,来处理、编辑文本文件。
共享内存的使用实现原理(必考必问,然后共享内存段被映射进进程空间之后,存在于进程空间的什么位置?共享内存段最大限制是多少?)
https://blog.csdn.net/al_xin/article/details/38602093
共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式。两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁和信号量都可以。
内存映射段
https://blog.csdn.net/ypbsyy/article/details/79915117
c++进程内存空间分布(注意各部分的内存地址谁高谁低,注意栈从高道低分配,堆从低到高分配)
栈
堆
为初始化区
初始化区
数据段
ELF是什么?其大小与程序中全局变量的是否初始化有什么关系(注意.bss段)
https://blog.csdn.net/liuzk2014/article/details/54409466
可执行链接文件
是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件。
1.可重定位文件(Relocateable)这类文件包含了代码和数据,可以被用来链接成可执行文件或共享目标文件,静态链接库也可以归为这一类,如.o文件。
2.可执行文件(Executable file)这类文件包含了直接执行的程序,如/bin/bash等。
3.共享目录(shared object file)链接器可以使用这种文件跟其他的可重定位文件和共享目标文件链接,产生新的目标文件;动态链接器可以将几个共享目标文件与可执行文件结合,作为进程映像的一部分来运行,如glibc***.so。
4.核心转储文件(core dump file)当进程意外终止时,系统可以将该进程的地址空间内容及终止时的一些其他信息转储到核心转储文件。
.bss段在elf仅占用一个占位符,不占用磁盘空间
可以减少重新编程重新编译的代码。
使用过哪些进程间通讯机制,并详细说明(重点)
管道(无名管道和有名管道)共享内存 消息队列 信号 信号量 套接字
makefile编写,虽然比较基础,但是会被问到
预处理、编译、汇编、文本链接四个步骤,每次得到的文件是:test.i,test.s,test.o,test
https://www.cnblogs.com/tp-16b/p/8955462.html
$^ 代表所有的依赖文件
$@ 代表所有的目标文件
$< 代表第一个依赖文件
Makefile的编写规则:
目标文件:执行文件
命令(注意:命令前必须以tab键开头)
gdb调试相关的经验,会被问到
如何定位内存泄露?
内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的、大小任意的(内存块的大小可以在程序运行期决定)、使用完后必须显示释放的内存。应用程序一般使用malloc、realloc、new等函数从堆中分配到一块内存,使用完后,程序必须负责相应的调用free或delete释放该内存块。否则,这块内存就不能被再次使用,我们就说这块内存泄漏了。
只能使用top指令观察进程的动态内存总额
ps、kill两个命令检测内存使用情况和进行回收。
内存泄漏的原因
* 在类的构造函数和析构函数中没有匹配的调用new和delete函数
* 程序循环new创建出来的对象没有及时的delete掉,导致了内存的泄露
* delete掉一个void*类型的指针,导致没有调用到对象的析构函数,析构的所有清理工作都没有去执行从而导致内存的泄露;
* new创建了一组对象数组,内存回收的时候却只调用了delete而非delete []来处理,导致只有对象数组的第一个对象的析构函数得到执行并回收了内存占用,数组的其他对象所占内存得不到回收,导致内存泄露;
* 指向对象的指针数组不等同于对象数组
* 缺少拷贝构造函数 如果一个类里面有指针成员变量,要么必须显示的写拷贝构造函数和重载赋值运算符,要么禁用拷贝构造函数和重载赋值运算符
* 缺少重载赋值运算符
* 没有将基类的析构函数定义为虚函数
造成野指针的原因:
* 指针变量没有被初始化(如果值不定,可以初始化为NULL)
* 指针被free或者delete后,没有置为NULL, free和delete只是把指针所指向的内存给释放掉,并没有把指针本身干掉,此时指针指向的是“垃圾”内存。释放后的指针应该被置 为NULL.
* 指针操作超越了变量的作用范围,比如返回指向栈内存的指针就是野指针。
https://blog.csdn.net/u012662731/article/details/78652651
valgrind
https://blog.csdn.net/ydyang1126/article/details/72667411
1、使用未初始化的内存
2、内存读写越界
3、内存覆盖
4、动态内存管理错误
5、内存泄漏
工具
1.Memcheck:这是valgrind应用最广泛的工具,一个重量级的内存检查器,能够给发现开发中绝大多数的内存错误使用的情况,比如:使用未初始化
2.callgrind:它主要用来检查程序中函数中调用过程中出现的问题
3.cachegrind:它主要用来检查程序中缓存使用出现的问题
4.Helgrind:它主要用来检查多线程中出现的竞争问题
5.Massif:它主要用来检查程序中堆栈使用中出现的问题
6.Extension:可以使用core提供的 功能,自己编写特定的内存调试工具
动态链接和静态链接的区别
动态链接是指在生成可执行文件时不将所有程序用到的函数链接到一个文件,因为有许多函数在操作系统带的dll文件中,当程序运行时直接从操作系统中找。 而静态链接就是把所有用到的函数全部链接到exe文件中。
动态链接是只建立一个引用的接口,而真正的代码和数据存放在另外的可执行模块中,在运行时再装入;而静态链接是把所有的代码和数据都复制到本模块中,运行时就不再需要库了。
32位系统一个进程最多多少堆内存
多线程和多进程的区别(重点 面试官最最关心的一个问题,必须从cpu调度,上下文切换,数据共享,多核cup利用率,资源占用,等等各方面回答,然后有一个问题必须会被问到:哪些东西是一个线程私有的?答案中必须包含寄存器,否则悲催)
https://www.php.cn/faq/416853.html
写一个c程序辨别系统是64位 or 32位
#include “stdio.h”
int main(int argc,char * argv[])
{
void* number = 0;
printf(“%d\n”,sizeof(&number));
}
不用sizeof判断16位还是32位的例子
写一个c程序辨别系统是大端or小端字节序
typedef union {
int i;
char c;
} myUnion;
// 1: Little Endian; 0: Big Endian.
int isLittleEndian02(void)
{
myUnion u;
u.i = 1;
return (u.i == u.c);
}
信号:列出常见的信号,信号怎么处理?
发送给进程的唯一信息通常是一个数
信号的两个主要目的:让进程知道已经发生了一个特定的事件;强迫进程执行它自己代码中的信号处理程序。
https://blog.csdn.net/u010318270/article/details/81058037
i++是否原子操作?并解释为什么?
内存到寄存器
寄存器自增
写回内存
这三个阶段中间都可以被中断分离开.
说出你所知道的各类linux系统的各类同步机制(重点),什么是死锁?如何避免死锁(每个技术面试官必问)
https://www.cnblogs.com/liuwei0773/articles/9506748.html
原子操作:
自旋锁
自旋锁的特点就是当一个线程获取了锁之后,其他试图获取这个锁的线程一直在循环等待获取这个锁,
读写自旋锁
读锁之间是共享的
互斥的
信号量
线程获取不到信号量的时候,不会像自旋锁一样循环区试图获取锁,而是进入睡眠,直至有信号量释放出来时,才会唤醒睡眠的线程,进入临界区执行。
读写信号量
互斥量
mutex
mutex 计数值只能为 1,也就是说最多允许一个线程访问临界区。
顺序锁
它的特点是,读锁被获取的情况下,写锁仍然可以被获取。
使用顺序锁的读操作在读之前和读之后都会检查顺序锁的序列值。如果前后值不服,这说明在读的过程中有写的操作发生。那么该操作会重新执行一次,直至读前后的序列值是一样的。
1.互斥条件:进程对于所分配到的资源具有排它性,即一个资源只能被一个进程占用,直到被该进程释放
2.请求和保持条件:一个进程因请求被占用资源而发生阻塞时,对已获得的资源保持不放。
3.不剥夺条件:任何一个资源在没被该进程释放之前,任何其他进程都无法对他剥夺占用
4.循环等待条件:当发生死锁时,所等待的进程必定会形成一个环路(类似于死循环),造成永久阻塞。
预防死锁:
资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件)
可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)
解除死锁:
剥夺资源:从其它进程剥夺足够数量的资源给死锁进程,以解除死锁状态;
撤消进程:可以直接撤消死锁进程或撤消代价最小的进程,直至有足够的资源可用,死锁状态.消除为止;所谓代价是指优先级、运行代价、进程的重要性和价值等。
列举说明linux系统的各类异步机制
信号,这是一种进程间通信的异步机制;
epoll,这是一种高效处理IO的异步通信机制。
exit()和_exit()的区别?
exit()会首先将所有使用atexit注册的函数进行调用以后再推出,而_exit()则是直接结束程序。
在exit()函数里会调用_exit()函数
如何实现守护进程?
守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程是一种很有用的进程。
https://blog.csdn.net/qq_26499321/article/details/72724173
linux的内存管理机制是什么?
https://blog.csdn.net/u013616945/article/details/77435607
单一连续区
固定分区
可变分区
页式存储管理方案
用户进程地址空间被划分成大小相等的部分,称为页或者页面,从0开始编号。
内存空间按同样的大小分成划分成大小相等的区域,称为页框。从0开始编号。
以页为单位来进行内存分配,按照进程需要的页数来分配;逻辑上相邻的页,物理上不一定相邻。典型的页面尺寸是4K或4M。
从进程的地址空间转换到实际的内存空间是通过查页表项(记录了逻辑页与页框号的对应关系,每一个进程一个页表,存放在内存)来实现的。CPU取到逻辑地址,自动划 分为页号和页内地址;用页号查页表,得到页框号,再与页内偏移拼接成物理地址。
段式存储管理方案
用户进程地址空间按照程序自身的逻辑关系划分为若干个程序段,每个程序段都有一个段名。
内存空间被动态划分成若干长度不相同的区域,称为物理段,每个物理段有起始地址和长度决定
内存分配规则:以段为单位进行分配,每段在内存占据连续空间,但各段之间可以不相邻。
逻辑地址为段号+段内地址。
段页式存储管理方案
用户进程地址空间先按段进行划分,每一段再按页面进行划分。
内存空间:同页式存储方案。
内存分配规则:同页式存储方案。
逻辑地址如下:
虚拟内存技术
基本思想:每个程序拥有自己的地址空间,这个空间被分割成很多个块,每一个块称为页面。每一页都有连续的地址范围。这些页被映射到物理内存,但并不是所有页都必须在内存中才能运行程序。当程序引用到一部分在物理内存中的地址空间时,由硬件立刻执行必要的映射。当程序引用到一部分不再物理内存中的地址空间时,由操作系统(缺页异常)负责将缺失的部分装入物理内存并重新执行失败的指令。
linux的任务调度机制是什么?
先来先服务(队列)
最短优先(优先队列)
优先权调度算法的类型
高响应比优先调度算法
时间片轮转法
多级反馈队列调度算法
(1) 应设置多个就绪队列,并为各个队列赋予不同的优先级。第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低。该算法赋予各个队列中进程执行时间片的大小也各不相同,在优先权愈高的队列中,为每个进程所规定的执行时间片就愈小。例如,第二个队列的时间片要比第一个队列的时间片长一倍,……,第i+1个队列的时间片要比第i个队列的时间片长一倍。
(2) 当一个新进程进入内存后,首先将它放入第一队列的末尾,按FCFS原则排队等待调度。当轮到该进程执行时,如它能在该时间片内完成,便可准备撤离系统;如果它在一个时间片结束时尚未完成,调度程序便将该进程转入第二队列的末尾,再同样地按FCFS原则等待调度执行;如果它在第二队列中运行一个时间片后仍未完成,再依次将它放入第三队列,……,如此下去,当一个长作业(进程)从第一队列依次降到第n队列后,在第n 队列便采取按时间片轮转的方式运行。
(3) 仅当第一队列空闲时,调度程序才调度第二队列中的进程运行;仅当第1~(i-1)队列均空时,才会调度第i队列中的进程运行。如果处理机正在第i队列中为某进程服务时,又有新进程进入优先权较高的队列(第1~(i-1)中的任何一个队列),则此时新进程将抢占正在运行进程的处理机,即由调度程序把正在运行的进程放回到第i队列的末尾,把处理机分配给新到的高优先权进程。
标准库函数和系统调用的区别?
系统调用是通向操作系统本身的接口,是面向底层硬件的。通过系统调用,可以使得用户态运行的进程与硬件设备(如CPU、磁盘、打印机等)进行交互,是操作系统留给应用程序的一个接口。
库函数调用则是面向应用开发的,库函数把系统调用进行封装或者组合,可以实现更多的功能
库函数是语言或应用程序的一部分,而系统调用是内核提供给应用程序的接口,属于系统的一部分
库函数在用户地址空间执行,系统调用是在内核地址空间执行,库函数运行时间属于用户时间,系统调用属于系统时间,库函数开销较小,系统调用开销较大
库函数是有缓冲的,系统调用是无缓冲的
系统调用依赖于平台,库函数并不依赖
补充一个坑爹坑爹坑爹坑爹的问题:系统如何将一个信号通知到进程?(这一题哥没有答出来)
:内核在进程所在的进程表项的信号域设置对应的信号的位,进程会维护一个未决信号的链表,处于用户态时就会处理信号。
内核给进程发送信号,是在进程所在的进程表项的信号域设置对应的信号的位。
进程检查信号的时机是:进程即将从内核态返回用户态时。如果进程睡眠了,要看睡眠能不能被中断,如果能被中断则唤醒。
进程有一个链表的数据结构,维护一个未决信号的链表。
信号在进程中注册,其实就是把该信号加入到这个未决信号链表当中。
可靠信号不管链表中是否已经有这个信号了,还是会加进去。不可靠信号,如果链表中已经有这个信号了,就会忽略。
进程处理信号的时机就是从内核态即将返回用户态的时候。
执行用户自定义的信号处理函数的方法很巧妙。把该函数的地址放在用户栈栈顶,进程从内核返回到用户态的时候,先弹出信号处理函数地址,于是就去执行信号处理函数了,然后再弹出,才是返回进入内核时的状态。
被屏蔽的信号,取消屏蔽后还会被检查。
c语言:
宏定义和展开(必须精通)
位操作(必须精通)
指针操作和计算(必须精通)
内存分配(必须精通)
sizeof必考
各类库函数必须非常熟练的实现
哪些库函数属于高危函数,为什么?(strcpy等等)
strcpy 赋值到目标区间可能会造成缓冲区溢出!
c++:
一个String类的完整实现必须很快速写出来(注意:赋值构造,operator=是关键)
虚函数的作用和实现原理(必问必考,实现原理必须很熟)
sizeof一个类求大小(注意成员变量,函数,虚函数,继承等等对大小的影响)
一个对象的大小大于等于所有非静态成员大小的总和。
1. 为类的非静态成员数据的类型大小之和.
2. 编译器额外加入的成员变量的大小,用来支持语言的某些特性(如:指向虚函数的指针).
3. 为了优化存取效率,进行的边缘调整(对齐
4. 与类中的构造函数,析构函数以及其他的成员函数无关.
指针和引用的区别(一般都会问到)
多重类构造和析构的顺序
stl各容器的实现原理(必考)
extern c 是干啥的,(必须将编译器的函数名修饰的机制解答的很透彻)
主要作用就是为了能够正确实现C++代码调用其他C语言代码
由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名。
volatile是干啥用的,(必须将cpu的寄存器缓存机制回答的很透彻)
访问寄存器比访问内存单元要快,编译器会优化减少内存的读取,可能会读脏数据。声明变量为volatile,编译器不再对访问该变量的代码优化,仍然从内存读取,使访问稳定。
特别在两个或多个进程之间通讯时,
例如:进程A有变量volatile BOOL fOK;
进程B中某个线程B1一直处于等待状态,直到进程A的变量fOK为TRUE,结束等待.
static const等等的用法,(能说出越多越好)
数据结构或者算法:
《离散数学》范围内的一切问题皆由可能被深入问到(这个最坑爹,最重要,最体现功底,最能加分,特别是各类树结构的实现和应用)
各类排序:大根堆的实现,快排(如何避免最糟糕的状态?),bitmap的运用等等
hash, 任何一个技术面试官必问(例如为什么一般hashtable的桶数会取一个素数?如何有效避免hash结果值的碰撞)
危险出现在当假设所选非素数m=x*y,如果需要hash的key正好跟这个约数x存在关系就惨了,最坏情况假设都为x的倍数
减少碰撞
开放地址法 线性探测再散列。
再哈希法
链地址法
建立一个公共溢出区
网络编程:
tcp与udp的区别(必问)
udp调用connect有什么作用?
tcp连接中时序图,状态图,必须非常非常熟练
socket服务端的实现,select和epoll的区别(必问)
select和epoll属于I/O多路复用模型,用于持续监听多个socket,获取其IO事件。
select 调用时轮询一次所有描述字,超时时再轮询一次。如果没有描述字准备好,则返回0;中途错误返回-1;有描述字准备好,则将其对应位置为1,其他描述字置为0,返回准备好的描述字个数。
缺点
1. 最大并发数限制,因为一个进程所打开的 FD (文件描述符)是有限制的,
2. 效率问题, select 每次调用都会线性扫描全部的 FD 集合,这样效率就会呈现线性下降,
3. 内核 / 用户空间 内存拷贝问题
epoll(触发)
epoll采用了中断注册回调的方式,socket IO就绪时发出中断,然后将socket加入就绪队列。由三个系统调用:epoll_create,epoll_ctl,epoll_wait。
能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率:它会复用文件描述符集合来传递结果,不需要每次等待事件之前都重新准备要被侦听的文件描述符集合;获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合。
select/epoll都需要在内核和用户空间之间复制数据,epoll使用了内存映射(mmap)技术,将内核和用户空间指向同一块内存。
1. Epoll 没有最大并发连接的限制,上限是最大可以打开文件的数目
2. 效率提升, Epoll 最大的优点就在于它只管你“活跃”的连接 ,而跟连接总数无关,
3. Epoll 使用了“共享内存 ”
epoll_create 创建一个epoll句柄,size-监听套接字的数。当创建好epoll句柄后,会占用一个fd值,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗完。
epoll_ctl 事件注册函数
epoll_wait。 等待函数
select - 如果同时建立很多连接,但只有少数事件发生,这种轮询会造成效率很低;频繁从内核拷贝、复制描述字;监听描述字受限于内核的FD_SETSIZE;
epoll - 这种回调触发式操作会保证效率;不需要频繁的拷贝;监听描述字没有限止,只与系统资源有关;epoll返回时已经明确的知道哪个sokcet fd发生了事件,不用再一个个比对。
epoll哪些触发模式,有啥区别?(必须非常详尽的解释水平触发和边缘触发的区别,以及边缘触发在编程中要做哪些更多的确认)
Edge Triggered(简称ET) 仅当状态发生变化时才会通知
Level Triggered(简称LT).,采用LT模式类似于原来的select/poll操作,只要还有没有处理的事件就会一直通知.
大规模连接上来,并发模型怎么设计
TCP循环服务器 首先TCP服务器接受一个客户端的连接请求,处理连接请求,在完成这个客户端的所有请求后断开连接,然后再接受下一个客户端的请求。
TCP并发服务器 并发服务器的思想是每一个客户端的请求并不由服务器的主进程直接处理,而是服务器主进程创建一个子进程来处理。
UDP循环服务器 UDP服务器每次从套接字上读取一个客户端的数据报请求,处理接收到的UDP数据报,然后将结果返回给客户机。
多路复用I/O并发服务器 创建子进程会带来系统资源的大量消耗,为了解决这个问题,采用多路复用I/O模型的并发服务器。采用select函数创建多路复用I/O模型的并发服务器
tcp结束连接怎么握手,time_wait状态是什么,为什么会有time_wait状态?哪一方会有time_wait状态,如何避免time_wait状态占用资源(必须回答的详细)
tcp头多少字节?哪些字段?(必问)
20 源端口 目的端口 序号 确认号 数据偏移 窗口 检验和 紧急指针 选项 填充
https://www.cnblogs.com/Mr-F/p/9167917.html
8个字节
什么是滑动窗口(必问)
conntect会阻塞,怎么解决?(必考必问,提示:设置非阻塞,返回之后用select检测状态)
1.使用定时器;(最常用也最有效的一种方法)
2.采用非阻塞模式:设置非阻塞,返回之后用select检测状态。
如果select返回可读,结果只读到0字节,什么情况?
某个套接字集合中没有准备好,可能会select内存用FD_CLR清该位为0.
udp调用connect有什么作用
1.因为UDP可以是一对一,多对一,一对多,或者多对多的通信,所以每次调用sendto()/recvfrom()时都必须指定目标IP和端口号。通过调用connect()建立一个端到端的连接,就可以和TCP一样使用send()/recv()传递数据,而不需要每次都指定目标IP和端口号。但是它和TCP不同的是它没有三次握手的过程。
keepalive 是什么东东?如何使用?
TCP的keepalive是侧重在保持客户端和服务端的连接,一方会不定期发送心跳包给另一方,当一方端掉的时候,没有断掉的定时发送几次心跳包,如果间隔发送几次,对方都返回的是RST,而不是ACK,那么就释放当前链接。设想一下,如果tcp层没有keepalive的机制,一旦一方断开连接却没有发送FIN给另外一方的话,那么另外一方会一直以为这个连接还是存活的,几天,几月。那么这对服务器资源的影响是很大的。
HTTP的keep-alive一般我们都会带上中间的横杠,普通的http连接是客户端连接上服务端,然后结束请求后,由客户端或者服务端进行http连接的关闭。下次再发送请求的时候,客户端再发起一个连接,传送数据,关闭连接。这么个流程反复。但是一旦客户端发送connection:keep-alive头给服务端,且服务端也接受这个keep-alive的话,两边对上暗号,这个连接就可以复用了,一个http处理完之后,另外一个http数据直接从这个连接走了。减少新建和断开TCP连接的消耗。
列举你所知道的tcp选项,并说明其作用。
1、kind=0,选项表结束(EOP)选项
一个报文段仅用一次。放在末尾用于填充,用途是说明:首部已经没有更多的消息,应用数据在下一个32位字开始处
2、kind=1,空操作(NOP)选项
没有特殊含义,一般用于将TCP选项的总长度填充为4字节的整数倍
3、kind=2,最大报文段长度(MSS)选项
TCP连接初始化时,通信双方使用该选项来协商最大报文段长度。TCP模块通常将MSS设置为(MTU-40)字节(减掉的这40字节包括20字节的TCP头部和20字节的IP头部)。这样携带TCP报文段的IP数据报的长度就不会超过MTU(假设TCP头部和IP头部都不包含选项字段,并且这也是一般情况),从而避免本机发生IP分片。对以太网而言,MSS值是1460(1500-40)字节。
4、kind=3,窗口扩大因子选项
TCP连接初始化时,通信双方使用该选项来协商接收窗口的扩大因子。在TCP的头部中,接收窗口大小是用16位表示的,故最大为65535字节,但实际上TCP模块允许的接收窗口大小远不止这个数(为了提高TCP通信的吞吐量)。窗口扩大因子解决了这个问题。
假设TCP头部中的接收通告窗口大小是N,窗口扩大因子(移位数)是M,那么TCP报文段的实际接收通告窗口大小是N*2M,或者说N左移M位。注意,M的取值范围是0~14。我们可以通过修改 /proc/sys/net/ipv4/tcp_window_scaling 内核变量来启用或关闭窗口扩大因子选项。
和MSS选项一样,窗口扩大因子选项只能出现在同步报文段中,否则将被忽略。但同步报文段本身不执行窗口扩大操作,即同步报文段头部的接收窗口大小就是该TCP报文段的实际接收窗口大小。当连接建立好之后,每个数据传输方向的窗口扩大因子就固定不变了。
5、kind=4,选择性确认(Selective Acknowledgment,SACK)选项
TCP通信时,如果某个TCP报文段丢失,则TCP会重传最后被确认的TCP报文段后续的所有报文段,这样原先已经正确传输的TCP报文段也可能重复发送,从而降低了TCP性能。SACK技术正是为改善这种情况而产生的,它使TCP只重新发送丢失的TCP报文段,而不用发送所有未被确认的TCP报文段。选择性确认选项用在连接初始化时,表示是否支持SACK技术。我们可以通过修改 /proc/sys/net/ipv4/tcp_sack 内核变量来启用或关闭选择性确认选项。
6、kind=5,SACK实际工作的选项
该选项的参数告诉发送方本端已经收到并缓存的不连续的数据块,从而让发送端可以据此检查并重发丢失的数据块。每个块边沿(edge of block)参数包含一个4字节的序号。其中块左边沿表示不连续块的第一个数据的序号,而块右边沿则表示不连续块的最后一个数据的序号的下一个序号。这样一对参数(块左边沿和块右边沿)之间的数据是没有收到的。因为一个块信息占用8字节,所以TCP头部选项中实际上最多可以包含4个这样的不连续数据块(考虑选项类型和长度占用的2字节)。
7、kind=8,时间戳选项。
该选项提供了较为准确的计算通信双方之间的回路时间(Round Trip Time,RTT)的方法,从而为TCP流量控制提供重要信息。我们可以通过修改 /proc/sys/net/ipv4/tcp_timestamps 内核变量来启用或关闭时间戳选项。
socket什么情况下可读?
db:
mysql,会考sql语言,服务器数据库大规模数据怎么设计,db各种性能指标
最后:补充一个最最重要,最最坑爹,最最有难度的一个题目:一个每秒百万级访问量的互联网服务器,每个访问都有数据计算和I/O操作,如果让你设计,你怎么设计?
1, 使用Linux epoll模型,水平触发模式(Level-Triggered);当socket可写时,会不停的触发socket可写的事件,如何处理?
2, 从socket读数据时,socket缓存里的数据,可能超过用户缓存的长度,如何处理? 例如,socket缓存有8kB的数据,而你的缓存只有2kB空间。
3, 向socket发送数据时, 可能只发送了用户缓存里的一半,如何处理?例如,需要向socket发送8kB数据,返回值只有2kB发送成功。
4, C++的虚函数是怎么实现的?
5, C++的虚函数有什么作用?
6, 非阻塞connect()如何实现?
7,sizeof()问题
class A
{
char c;
int val;
short sh;
}
class B
{
char c;
int val;
short sh;
void func1(void);
virtual func2(void);
}
sizeof(A), sizeof(B) 分别是多少?
8, 实现字符串比较函数 strcmp
9, 实现内存拷贝函数 strcpy(void*dst, char * src, size_t len)
10,条件变量的如何使用? 你使用的线程函数是什么?
11, deamon进程如何实现?
12, HTTP和CGI是什么?
13, TCP的三次握手, TIME_WAIT和CLOSE_WAIT状态是什么?
因为第7题之后的属于客观题,不打算在此写答案。 朋友们如有好的答案也欢迎跟贴。
本人在此写出自己对前6个问题的回答:
1, 使用linux epoll模型,水平触发模式(Level-Triggered);当socket可写时,会不停的触发socket可写的事件,如何处理?
第一种最普通的方式:
当需要向socket写数据时,将该socket加入到epoll模型(epoll_ctl);等待可写事件。
接收到socket可写事件后,调用write()或send()发送数据。。。
当数据全部写完后, 将socket描述符移出epoll模型。
这种方式的缺点是: 即使发送很少的数据,也要将socket加入、移出epoll模型。有一定的操作代价。
第二种方式,(是本人的改进方案, 叫做directly-write)
向socket写数据时,不将socket加入到epoll模型;而是直接调用send()发送;
只有当或send()返回错误码EAGAIN(系统缓存满),才将socket加入到epoll模型,等待可写事件后,再发送数据。
全部数据发送完毕,再移出epoll模型。
这种方案的优点: 当用户数据比较少时,不需要epool的事件处理。
在高压力的情况下,性能怎么样呢?
对一次性直接写成功、失败的次数进行统计。如果成功次数远大于失败的次数, 说明性能良好。(如果失败次数远大于成功的次数,则关闭这种直接写的操作,改用第一种方案。同时在日志里记录警告)
在我自己的应用系统中,实验结果数据证明该方案的性能良好。
事实上,网络数据可分为两种到达/发送情况:
一是分散的数据包, 例如每间隔40ms左右,发送/接收3-5个 MTU(或更小,这样就没超过默认的8K系统缓存)。
二是连续的数据包, 例如每间隔1s左右,连续发送/接收 20个 MTU(或更多)。
回来查了资料,发现以下两种方式:
第三种方式: 使用Edge-Triggered(边沿触发),这样socket有可写事件,只会触发一次。
可以在应用层做好标记。以避免频繁的调用 epoll_ctl( EPOLL_CTL_ADD, EPOLL_CTL_MOD)。 这种方式是epoll 的 man 手册里推荐的方式, 性能最高。但如果处理不当容易出错,事件驱动停止。
第四种方式: 在epoll_ctl()使用EPOLLONESHOT标志,当事件触发以后,socket会被禁止再次触发。
需要再次调用epoll_ctl(EPOLL_CTL_MOD),才会接收下一次事件。 这种方式可以禁止socket可写事件,应该也会同时禁止可读事件。会带来不便,同时并没有性能优势,因为epoll_ctl()有一定的操作代价。
2, 从socket读数据时,socket缓存里的数据,可能超过用户缓存的长度,如果处理?
可以调用realloc(),扩大原有的缓存块尺寸。
但是临时申请内存的有一定性能损失。
这种情况要看接收缓存的方式。
第一种方式: 使用100k的大接收缓存为例。
如果要等待数据,并进行解析。可能发生缓存不够的情况。此时只能扩充缓存,或先处理100k的数据,再接收新的数据。
第二种方式: 使用缓存队列,分成8K大小的队列。
不存在接收缓存不够的情况。 除非用户解析已出错,使用数据接收、使用脱勾。 这种方式的代价是,可能需要将缓存队列再次拷贝、拼接成一块大的缓存,再进行解析。 而在本人的系统中,只需要将socket接收的数据再次原样分发给客户, 所以这种方案是最佳方案。
3, 向socket发送数据时, 可能只发送了用户缓存里的一半,然后失败,如何处理?
记录缓存的偏移量。 下一次socket写事件时, 再从偏移的位置接着发送。
4, C++的虚函数是怎么实现的?
使用虚函数表。
回来查下资料: C++对象使用虚表, 如果是基类的实例,对应位置存放的是基类的函数指针;如果是继承类,对应位置存放的是继承类的函数指针(如果在继承类有实现)。所以,当使用基类指针调用对象方法时,也会根据具体的实例,调用到继承类的方法。
5, C++的虚函数有什么作用?
虚函数作用是实现多态,
更重要的,虚函数其实是实现封装,使得使用者不需要关心实现的细节。在很多设计模式中都是这样用法,例如Factory、Bridge、Strategy模式。 前两天在书上刚好看到这个问题,但在面试的时候却没想起来。
个人觉得这个问题可以很好的区分C++的理解水平。
6, 非阻塞connect()如何实现?
将socket设置成non-blocking,操作方法同非阻塞read()、write();
面试官是在听到我介绍之后,才问我这个问题。可惜还是问我两遍。
select epoll
聚集索引和非聚集索引
网络编程
面经-网络编程
TCP 建立连接和断开连接的过程?如果 server 没有 accept,连接会怎样?
UDP bind 和 connect 有什么用?
write 和 sendto 有什么区别?
对端异常掉线,本端调用 write 函数向 socket 写入数据,会出现什么情况?
nagle
shutdown() 和 close() 有什么区别?如何优雅关闭 socket ?
C++多重继承的对象模型?菱形继承的对象模型?
网络编程
https://blog.csdn.net/f2935552941/article/details/88807548
poll机制与select机制类似,通过管理文件描述符来进行轮询,效率更高,并且处理的连接个数不受内核的限制。
如何解决高并发
1.尽量使用缓存,包括用户缓存,信息缓存等,多花点内存来做缓存,可以大量减少与数据库的交互,提高性能。
2.html静态化也是某些缓存策略使用的手段,对于系统中频繁使用数据库查询但是内容更新很小的应用,可以考虑使用html静态化来实现,比如论坛中论坛的公用设置信息,这些信息目前的主流论坛都可以进行后台管理并且存储再数据库中,这些信息其实大量被前台程序调用,但是更新频率很小,可以考虑将这部分内容进行后台更新的时候进行静态化,这样避免了大量的数据库访问请求。能使用静态页面的地方尽量使用,减少容器的解析(尽量将动态内容生成静态html来显示)。能使用静态页面的地方尽量使用,减少容器的解析(尽量将动态内容生成静态html来显示)。
3.使用高性能的服务器、高性能的数据库、高效率的编程语言、还有高性能的Web容器.
4.使用Ngnix负载均衡
5.优化数据库查询语句,减少直接使用hibernate等工具的直接生成语句(仅耗时较长的查询做优化)。
优化数据库结构,多做索引,提高查询效率,数据库集群和库表散列
6.不要频繁得使用new对象,能使用单例模式就使用, 对于utility类型的类通过静态方法来访问。
7.使用线程安全的集合对象vector(已经过时可以使用CopyOnWriteArrayList) hashtable
8.使用线程池。
面经-操作系统
进程线程区别
进程是对运行时程序的封装,是系统进行资源调度和分配的的基本单位,实现了操作系统的并发;
线程是进程的子任务,是CPU调度和分派的基本单位,用于保证程序的实时性,实现进程内部的并发;
每个线程独自的寄存器组,指令计数器和处理器状态
进程间通信方式
管道
通信队列
信号量
信号
共享内存
socket
进程间通讯的方式:
管道中还有命名管道和非命名管道之分,非命名管道只能用于父子进程通讯,命名管道可用于非父子进程,命名管道就是FIFO,管道是先进先出的通讯方式。FIFO是一种先进先出的队列。它类似于一个管道,只允许数据的单向流动。每个FIFO都有一个名字,允许不相关的进程访问同一个FIFO,因此也成为命名管。
消息队列:是用于两个进程之间的通讯,首先在一个进程中创建一个消息队列,然后再往消息队列中写数据,而另一个进程则从那个消息队列中取数据。需要注意的是,消息队列是用创建文件的方式建立的,如果一个进程向某个消息队列中写入了数据之后,另一个进程并没有取出数据,即使向消息队列中写数据的进程已经结束,保存在消息队列中的数据并没有消失,也就是说下次再从这个消息队列读数据的时候,就是上次的数据!!!
信号量, 不能传递复杂消息,只能用来同步
共享内存,只要首先创建一个共享内存区,其它进程按照一定的步骤就能访问到这个共享内存区中的数据,当然可读可写;
几种方式的比较:
管道:速度慢,容量有限
消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题。
信号量:不能传递复杂消息,只能用来同步
共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了一块内存的。
线程间通信方式
临界区
互斥量
信号量
信号
栈区:由编译器自动释放,存放函数的参数值、局部变量等。每当一个函数被调用时,该函数的返回类型和一些调用的信息被存放到栈中。然后这个被调用的函数再为他的自动变量和临时变量在栈上分配空间。每调用一个函数一个新的栈就会被使用。栈区是从高地址位向低地址位增长的,是一块连续的内存区域,最大容量是由系统预先定义好的,申请的栈空间超过这个界限时会提示溢出,用户能从栈中获取的空间较小。 2m
堆区:用于动态分配内存,位于BSS和栈中间的地址区域。由程序员申请分配和释放。堆是从低地址位向高地址位增长,采用链式存储结构。频繁的malloc/free造成内存空间的不连续,产生碎片。当申请堆空间时库函数是按照一定的算法搜索可用的足够大的空间。因此堆的效率比栈要低的多。
大小端
https://blog.csdn.net/oqqhutu12345678/article/details/82823890
协程:
协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。
协程仅仅是一个特殊的函数
一个进程可以包含多个线程,一个线程可以包含多个协程。
一个线程内的多个协程虽然可以切换,但是多个协程是串行执行的,只能在一个线程内运行,没法利用CPU多核能力。
协程与进程一样,切换是存在上下文切换问题的。
系统调用
IO文件指令
特权指令
硬件资源请求
静态链接:
函数和数据被编译进一个二进制文件。在使用静态库的情况下,在编译链接可执行文件时,链接器从库中复制这些函数和数据并把它们和应用程序的其它模块组合起来创建最终的可执行文件。
空间浪费:因为每个可执行程序中对所有需要的目标文件都要有一份副本,所以如果多个程序对同一个目标文件都有依赖,会出现同一个目标文件都在内存存在多个副本;
更新困难:每当库函数的代码修改了,这个时候就需要重新进行编译链接形成可执行程序。
运行速度快:但是静态链接的优点就是,在可执行程序中已经具备了所有执行程序所需要的任何东西,在执行的时候运行速度快。
动态链接:
动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。
共享库:就是即使需要每个程序都依赖同一个库,但是该库不会像静态链接那样在内存中存在多分,副本,而是这多个程序在执行时共享同一份副本;
更新方便:更新时只需要替换原来的目标文件,而无需将所有的程序再重新链接一遍。当程序下一次运行时,新版本的目标文件会被自动加载到内存并且链接起来,程序就完成了升级的目标。
性能损耗:因为把链接推迟到了程序运行时,所以每次执行程序都需要进行链接,所以性能会有一定损失。
请问怎么实现线程池
1.设置一个生产者消费者队列,作为临界资源
2.初始化n个线程,并让其运行起来,加锁去队列取任务运行
3.当任务队列为空的时候,所有线程阻塞
4.当生产者队列来了一个任务后,先对队列加锁,把任务挂在到队列上,然后使用条件变量去通知阻塞中的一个线程
write 系统调用
read 系统调用
open 系统调用
close 系统调用
请你说一下多进程和多线程的使用场景
多进程模型的优势是CPU
多线程模型主要优势为线程间切换代价较小,因此适用于I/O密集型的工作场景,
因此I/O密集型的工作场景经常会由于I/O阻塞导致频繁的切换线程。同时,多线
程模型也适用于单机多核分布式场景。
多进程模型,适用于CPU密集型。同时,多进程模型也适用于多机分布式场景
中,易于多机扩展。
需要频繁创建销毁的优先用线程
这种原则最常见的应用就是Web服务器了,来一个连接建立一个线程,断了就销毁线程,要是用进程,创建和销毁的代价是很难承受的
需要进行大量计算的优先使用线程
大量计算,当然就是要耗费很多CPU,切换频繁了,这种情况下线程是最合适的。
这种原则最常见的是图像处理、算法处理。
强相关的处理用线程,弱相关的处理用进程
可能要扩展到多机分布的用进程,多核分布的用线程
https://blog.csdn.net/weixin_39731083/article/details/82015830
面经-linux命令
磁盘
df
CPU
top
系统进程
ps
内存
free
网络查看命令
netstat
io性能
iostat
iotop
查看端口
lsof
Linux内存管理方法,页面置换算法,逻辑地址和物理地址的转换
hash表解决冲突的方法
redis中的数据结构
判断大小端,int的大端转小端
去掉字符串开头和末尾的空格
大数相加
最长公共子序列LCS
最小编辑距离
二叉树中两个节点的最近公共父节点
数据流的中位数
面经-mysql
数据库基础知识
1、为什么要使用数据库
2、什么是SQL?
3、什么是MySQL?
4、数据库三大范式是什么
5、mysql有关权限的表都有哪几个
MySQL服务器通过权限表来控制用户对数据库的访问,权限表存放在mysql数据库里,由mysql_install_db脚本初始化。这些权限表分别user,db,table_priv,columns_priv和host。下面分别介绍一下这些表的结构和内容:
user权限表:记录允许连接到服务器的用户帐号信息,里面的权限是全局级的。
db权限表:记录各个帐号在各个数据库上的操作权限。
table_priv权限表:记录数据表级的操作权限。
columns_priv权限表:记录数据列级的操作权限。
host权限表:配合db权限表对给定主机上数据库级操作权限作更细致的控制。这个权限表不受GRANT和REVOKE语句的影响。
6、MySQL的binlog有有几种录入格式?分别有什么区别?
Statement:每一条会修改数据的sql
ROW不记录sql语句上下文相关信息,仅保存哪条记录被修改。
Mysql为什么用B树存储结构
mysql的数据是放到外部存储的,因此我们必须减低磁盘的IO次数,因此我们需要尽量降低树的高度,树的分叉越多越好,因此B树正好符合我们的要求
数据类型
1、mysql有哪些数据类型
引擎
1、MySQL存储引擎MyISAM与InnoDB区别
事务, 锁 外键
2、MyISAM索引与InnoDB索引的区别?
InnoDB是聚集索引,表记录的排列顺序和与索引的排列顺序是否一致。聚集索引一个表只有一个,非聚集索引一个表可以存在多个。聚集索引存储记录是物理上连续存在,非聚集索引是逻辑上的连续
在经常搜索一定范围的值时,通过索引找到第一条数据,根据物理地址连续存储的特点,然后检索相邻的数据,直到到达条件截至项。
非聚集索引 非聚集索引就像根据偏旁部首查字典一样,字典前面的目录在逻辑上也是连续的,但是查两个偏旁在目录上挨着的字时,字典中的字却很不可能是挨着的。
https://www.cnblogs.com/duzhentong/p/8639223.html
主键索引:以主键索引到整条记录
辅助索引:以另一字段索引到主键
MyISAM是非聚集索引,索引和数据文件是分离的
主键索引:以关键字索引到记录的
辅助索引:以某字段索引到记录地址
聚集索引优点:
1、以最快的速度缩小查询范围。
2、以最快的速度进行字段排序。
聚集索引使用场合:
1、此列包含有限数目的不同值。
2、查询的结果返回一个区间的值。
3、查询的结果返回某值相同的大量结果集。
非聚集索引优点:
1、非聚集索引比聚集索引层次多。
3、添加记录不会引起数据顺序的重组。
非聚集索引使用场合:
1、此列包含了大量数目不同的值。
2、查询的结束返回的是少量的结果集
3、InnoDB引擎的4大特性
一:插入缓冲 提升插入性能,change buffering是insert buffer的加强,insert buffer只针对insert有效,change buffering对insert、delete、update(delete+insert)、purge都有效
二:二次写 Doublewrite缓存是位于系统表空间的存储区域,用来缓存InnoDB的数据页从innodb buffer pool中flush之后并写入到数据文件之前,所以当操作系统或者数据库进程在数据页写磁盘的过程中崩溃,Innodb可以在doublewrite缓存中找到数据页的备份而用来执行crash恢复
三:自适应哈希
b存储引擎会监控对表上二级索引的查找,如果发现某二级索引被频繁访问,二级索引成为热数据,建立哈希索引可以带来速度的提升
四:预读
4、存储引擎选择
线性预读(linear read-ahead)和随机预读(randomread-ahead)
为了区分这两种预读的方式,
索引
1、什么是索引?
2、索引有哪些优缺点?
3、索引使用场景(重点)
4、索引有哪几种类型?
普通索引、唯一索引、全文索引、单列索引、多列索引、空间索引
5、索引的数据结构(b树,hash)
6、索引的基本原理
7、索引算法有哪些?
8、索引设计的原则?
9、创建索引的原则(重中之重)
对于查询频率高的字段创建索引
对排序、分组、联合查询频率高的字段创建索引
索引的数目不宜太多
若在实际中,需要将多个列设置索引时,可以采用多列索引
选择唯一性索引
尽量使用数据量少的索引
尽量使用前缀来索引
删除不再使用或者很少使用的索引
10、创建索引的三种方式,删除索引
11、创建索引时需要注意什么?
12、使用索引查询一定能提高查询的性能吗?为什么
13、百万级别或以上的数据如何删除
使用inner join 的方式删除起来,效率成倍的提升!
然后根据使用的删除条件建立一个临时的索引,以此建立提高删除效率的索引,打造围绕一个字段的聚集型索引,以此为基地大大提升删除效率
可以把要保留的数据备份出来。在drop表。重新创建,先不要急着创建索引、主键,把数据导回去,然后在建索引、约束之类的。
14、前缀索引
15、什么是最左前缀原则?什么是最左匹配原则
16、B树和B+树的区别
17、使用B树的好处
18、使用B+树的好处
1. B+树很好的利用了局部性原理,预读
2. B+树单次磁盘 IO 的信息量大于B树,从这点来看B+树相对B树磁盘 IO 次数少。
3. B+Tree中因为数据都在叶子节点,所以每次查询的时间复杂度是固定的,因为稳定性保证了
4. 而且叶子节点之间都是链表的结构,所以B+ Tree也是可以支持范围查询的,而B树每个节点 key 和 data 在一起,则无法区间查找。
19、Hash索引和B+树所有有什么区别或者说优劣呢?
20、数据库为什么使用B+树而不是B树
21、B+树在满足聚簇索引和覆盖索引的时候不需要回表查询数据
22、什么是聚簇索引?何时使用聚簇索引与非聚簇索引
23、非聚簇索引一定会回表查询吗?
24、联合索引是什么?为什么需要注意联合索引中的顺序?
事务
1、什么是数据库事务?
2、事物的四大特性(ACID)介绍一下?
3、什么是脏读?幻读?不可重复读?
4、什么是事务的隔离级别?MySQL的默认隔离级别是什么?
锁
1、对MySQL的锁了解吗
2、隔离级别与锁的关系
3、按照锁的粒度分数据库锁有哪些?锁机制与InnoDB锁算法
4、从锁的类别上分MySQL都有哪些锁呢?像上面那样子进行锁定岂不是有点阻碍并发效率了
5、MySQL中InnoDB引擎的行锁是怎么实现的?
6、InnoDB存储引擎的锁的算法有三种
7、什么是死锁?怎么解决?
8、数据库的乐观锁和悲观锁是什么?怎么实现的?
视图
1、为什么要使用视图?什么是视图?
2、视图有哪些特点?
3、视图的使用场景有哪些?
4、视图的优点
5、视图的缺点
6、什么是游标?
存储过程与函数
1、什么是存储过程?有哪些优缺点?
存储过程是一个预编译的代码块,执行效率比较高
存储过程在服务器端运行,减少客户端的压力
允许模块化程序设计,只需要创建一次过程,以后在程序中就可以调用该过程任意次,类似方法的复用
一个存储过程替代大量T_SQL语句 ,可以降低网络通信量,提高通信速率
可以一定程度上确保数据安全
调试麻烦(没有像开发程序那样容易)
可移植性不灵活(因为存储过程依赖于具体的数据库)
触发器
1、什么是触发器?触发器的使用场景有哪些?
2、MySQL中都有哪些触发器?
常用SQL语句
1、SQL语句主要分为哪几类
2、超键、候选键、主键、外键分别是什么?
3、SQL 约束有哪几种?
4、六种关联查询
5、什么是子查询
6、子查询的三种情况
7、mysql中 in 和 exists 区别
8、varchar与char的区别
9、varchar(50)中50的涵义
10、int(20)中20的涵义
11、mysql为什么这么设计
12、mysql中int(10)和char(10)以及varchar(10)的区别
13、FLOAT和DOUBLE的区别是什么?
14、drop、delete与truncate的区别
15、UNION与UNION ALL的区别?
SQL优化
1、如何定位及优化SQL语句的性能问题?创建的索引有没有被使用到?或者说怎么才可以知道这条语句运行很慢的原因?
通过执行计划可以了解查询方式、索引使用情况、需要扫描的数据量以及是否需要临时表或排序操作等信息。
select_type子查询的查询类型 table type访问类型
2、SQL的生命周期?
3、大表数据查询,怎么优化
4、超大分页怎么处理?
5、mysql 分页
6、慢查询日志
7、关心过业务系统里面的sql耗时吗?统计过慢查询吗?对慢查询都怎么优化过?
8、为什么要尽量设定一个主键?
9、主键使用自增ID还是UUID?
10、字段为什么要求定义为not null?
11、如果要存储用户的密码散列,应该使用什么字段进行存储?
12、优化查询过程中的数据访问
13、优化长难的查询语句
14、优化特定类型的查询语句
15、优化关联查询
16、优化子查询
17、优化LIMIT分页
18、优化UNION查询
19、优化WHERE子句
数据库优化
1、为什么要优化
2、数据库结构优化
3、MySQL数据库cpu飙升到500%的话他怎么处理?
4、大表怎么优化?某个表有近千万数据,CRUD比较慢,如何优化?分库分表了是怎么做的?分表分库了有什么问题?有用到中间件么?他们的原理知道么?
垂直分表
适用场景
缺点
水平分表:
适用场景
水平切分的缺点
5、MySQL的复制原理以及流程
6、读写分离有哪些解决方案?
https://www.cnblogs.com/mkl34367803/p/10703913.html
方案1:应用程序根据业务逻辑来判断,增删改等写操作命令发给主库,查询命令发给备库。
特点:<1>数据库和应用程序强耦合,数据库如果有变化还好影响主库。<2>应用程序复杂化。
方案2:利用中间件来做代理,负责对数据库的请求识别出读还是写,并分发到不同的数据库中。
特点:<1> 数据库和应用程序弱耦合。<2> 代理存在性能瓶颈和可靠性风险增加,相对可控。
7、备份计划,mysqldump以及xtranbackup的实现原理
8、数据表损坏的修复方式有哪些?
HTTPS工作原理
vector 参数
c++ 内存泄漏及其处理
申请的空间未释放
浅拷贝多次释放
一是在堆里创建了对象占用了内存,但是没有显示地释放对象占用的内存;
二是在类的构造函数中动态的分配了内存,但是在析构函数中没有释放内存或者没有正确的释放内存
在释放对象数组时在delete中没有使用方括号
指向对象的指针数组不等同于对象数组
对象数组是指:数组中存放的是对象,只需要delete []p,即可调用对象数组中的每个对象的析构函数释放空间
指向对象的指针数组是指:数组中存放的是指向对象的指针,不仅要释放每个对象的空间,还要释放每个指针的空间,delete []p只是释放了每个指针,但是并没有释放对象的空间,正确的做法,是通过一个循环,将每个对象释放了,然后再把指针释放了。
缺少重载赋值运算符
没有将基类的析构函数定义为虚函数
造成野指针的原因:
指针变量没有被初始化(如果值不定,可以初始化为NULL)
指针被free或者delete后,没有置为NULL, free和delete只是把指针所指向的内存给释放掉,并没有把指针本身干掉,此时指针指向的是“垃圾”内存。释放后的指针应该被置为NULL.
指针操作超越了变量的作用范围,比如返回指向栈内存的指针就是野指针。
4.使用C/C++语言开发的软件在运行时,出现内存泄漏。可以使用以下两种方式,进行检查排除:
⑴ 使用工具软件BoundsChecker,BoundsChecker是一个运行时错误检测工具,它主要定位程序运行时期发生的各种错误。
⑵ 调试运行DEBUG版程序,运用以下技术:CRT(C run-time libraries)、运行时函数调用堆栈、内存泄漏时提示的内存分配序号(集成开发环境OUTPUT窗口),综合分析内存泄漏的原因,排除内存泄漏。
解决内存泄漏最有效的办法就是使用智能指针(Smart Pointer)。使用智能指针就不用担心这个问题了,因为智能指针可以自动删除分配的内存。智能指针和普通指针类似,只是不需要手动释放指针,而是通过智能指针自己管理内存的释放,这样就不用担心内存泄漏的问题了。
shared_ptr共享的智能指针:
shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。在最后一个shared_ptr析构的时候,内存才会被释放。
注意事项:
1.不要用一个原始指针初始化多个shared_ptr。
2.不要再函数实参中创建shared_ptr,在调用函数之前先定义以及初始化它。
3.不要将this指针作为shared_ptr返回出来。
4.要避免循环引用。
unique_ptr独占的智能指针:
<1>Unique_ptr是一个独占的智能指针,他不允许其他的智能指针共享其内部的指针,不允许通过赋值将一个unique_ptr赋值给另外一个 unique_ptr。
unique_ptr不允许复制,但可以通过函数返回给其他的unique_ptr,还可以通过std::move来转移到其他的unique_ptr,这样它本身就不再 拥有原来指针的所有权了。
一条SQL语句执行得很慢,要分两种情况:
大多数情况是正常,偶尔很慢
数据库在处理数据忙时候,更新或新增数据都会暂时记录到redo log日志,等空闲时把数据同步到磁盘。假设数据库一直很忙,更新又频繁,redo log被写满,就不得不暂停其它操作把数据同步到磁盘,就有可能导致我们的SQL语句执行的很慢
执行时遇到表锁行锁,别人操作数据库用到表或者行数据加锁了,只能慢慢等待别人释放锁
这条SQL语句一直很慢
https://www.cnblogs.com/myseries/p/10719074.html
1、大多数情况下很正常,偶尔很慢,则有如下原因
(1)、数据库在刷新脏页,例如 redo log 写满了需要同步到磁盘。
(2)、执行的时候,遇到锁,如表锁、行锁。
2、这条 SQL 语句一直执行的很慢,则有如下原因。
(1)、没有用上索引:例如该字段没有索引;由于对字段进行运算、函数操作导致无法用索引。
(2)、数据库选错了索引。
接口安全性设计
Token授权机制
时间戳超时机制
签名机制
拒绝重复调用
完全开放的接口
接口参数加密
接口参数加密+接口时效性验证
接口参数加密+时效性验证+私钥
接口参数加密+时效性验证+私钥+Https
网络编程与TCP
https://blog.csdn.net/qq_28584889/article/details/88751304
select for update 是为了在查询时,对这条数据进行加锁,避免其他用户以该表进行插入,修改或删除等操作,造成表的不一致性.
面试-计算机网络
https://blog.csdn.net/weiyuefei/article/details/50413543
- 计算机网络体系结构
OSI七层模型及其包含的协议如下:
物理层: 通过媒介传输比特,确定机械及电气规范,传输单位为bit,主要包括的协议为:IEE802.3 CLOCK RJ45
数据链路层: 将比特组装成帧和点到点的传递,传输单位为帧,主要包括的协议为MAC VLAN PPP
网络层:负责数据包从源到宿的传递和网际互连,传输单位为包,主要包括的协议为IP ARP ICMP
传输层:提供端到端的可靠报文传递和错误恢复,传输单位为报文,主要包括的协议为TCP UDP
会话层:建立、管理和终止会话,传输单位为SPDU,主要包括的协议为RPC NFS
表示层: 对数据进行翻译、加密和压缩,传输单位为PPDU,主要包括的协议为JPEG ASII
应用层: 允许访问OSI环境的手段,传输单位为APDU,主要包括的协议为FTP HTTP DNS
TCP/IP 4层模型包括:
网络接口层:MAC VLAN
网络层:IP ARP ICMP
传输层:TCP UDP
应用层:HTTP DNS SMTP
TCP和UDP的区别
- TCP是面向连接的 UDP是面向无连接的
- TCP使用的是全双工可靠的信道 UDP是不可靠信道
- TCP只能进行一对一的单播 UDP可以进行组播和广播
- TCP提供可靠的交付 UDP尽最大可能进行交付
- TCP面向字节流的传输 UDP面向报文传输
TCP如何保证可靠传输
- 连接管理 保证可靠的连接
- 校验和 确保传输无误
- 序列号 将接收到的序号进行排序
- 确认应答 告诉对方已经收到
- 超时重传 保证不丢包
- 流量控制 对网络进行适配
- 拥塞控制 对网络进行适配
TCP连接的三次握手,四次挥手
为什么要四次挥手
因为 TCP 是全双工模式,客户端请求关闭连接后,客户端向服务端的连接关闭(一二次挥手),服务端继续传输之前没传完的数据给客户端(数据传输),服务端向客户端的连接关闭(三四次挥手)。所以 TCP 释放连接时服务器的 ACK 和 FIN 是分开发送的(中间隔着数据传输),而 TCP 建立连接时服务器的 ACK 和 SYN 是一起发送的(第二次握手),所以 TCP 建立连接需要三次,而释放连接则需要四次。为什么客户端释放最后需要 TIME-WAIT 等待 2MSL 呢?
为了保证客户端发送的最后一个 ACK 报文能够到达服务端。若未成功到达,则服务端超时重传 FIN+ACK 报文段,客户端再重传 ACK,并重新计时。
防止已失效的连接请求报文段出现在本连接中。TIME-WAIT 持续 2MSL 可使本连接持续的时间内所产生的所有报文段都从网络中消失,这样可使下次连接中不会出现旧的连接报文段。DNS工作流程
请你说一说HTTP和HTTPS的不同
- HTTP协议是以明文的方式在网络中传输数据,而HTTPS协议传输的数据则是经过TLS加密后的,HTTPS具有更高的安全性
- HTTPS在TCP三次握手阶段之后,还需要进行SSL 的handshake,协商加密使用的对称加密密钥
- HTTPS协议需要服务端申请证书,浏览器端安装对应的根证书
- HTTP协议端口是80,HTTPS协议端口是443
浏览器中输入URL
浏览器要将URL解析为IP地址,解析域名就要用到DNS协议,首先主机会查询DNS的缓存,如果没有就给本地DNS发送查询请求。DNS查询分为两种方式,一种是递归查询,一种是迭代查询。如果是迭代查询,本地的DNS服务器,向根域名服务器发送查询请求,根域名服务器告知该域名的一级域名服务器,然后本地服务器给该一级域名服务器发送查询请求,然后依次类推直到查询到该域名的IP地址。DNS服务器是基于UDP的,因此会用到UDP协议。
得到IP地址后,浏览器就要与服务器建立一个http连接。因此要用到http协议,http协议报文格式上面已经提到。http生成一个get请求报文,将该报文传给TCP层处理,所以还会用到TCP协议。如果采用https还会使用https协议先对http数据进行加密。TCP层如果有需要先将HTTP数据包分片,分片依据路径MTU和MSS。TCP的数据包然后会发送给IP层,用到IP协议。IP层通过路由选路,一跳一跳发送到目的地址。当然在一个网段内的寻址是通过以太网协议实现(也可以是其他物理层协议,比如PPP,SLIP),以太网协议需要直到目的IP地址的物理地址,有需要ARP协议。
请你说一说TCP拥塞控制
1、慢开始
2、拥塞避免
3、快重传
4、快恢复HTTP工作过程
- 客户端连接到Web服务器
- 发送HTTP请求
- 服务器接受请求并返回HTTP响应
- 释放连接TCP连接
- 客户端浏览器解析HTML内容
请你来说一下socket编程中服务器端和客户端主要用到哪些函数
基于TCP的socket:
基于UDP的socket:
accept()函数与三次握手的关系
面经
- C++五大储存区
程序代码区: 存放函数体的二进制代码
2. extern关键字的作用
extern关键字可以用来声明变量和函数作为外部变量或者函数供其它文件使用。
extern “C”的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern “C”后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。
https://www.cnblogs.com/banmei-brandy/p/11338314.html
extern 函数声明 定义
static关键字作用
(1)函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;
(2)在模块内的static全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;(在其他模块用需要使用using namespace);
(3)在模块内的static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内;
(4)在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;
(5)在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。volatile的作用
volatile关键字是防止在共享的空间发生读取的错误。只保证其可见性,不保证原子性;使用volatile指每次从内存中读取数据,而不是从编译器优化后的缓存中读取数据,简单来讲就是防止编译器优化。const 作用
只读不可写
new和malloc的区别
sizeof的运算结果,
sizeof运算符的结果部分地依赖于其作用的类型:
- 对char或者类型为char的表达式执行sizeof运算,结果得1
- 对引用类型执行sizeof运算符得到被引用对象所占空间的大小
- 对指针执行sizeof运算得到指针本身所占空间的大小
- 对解引用指针执行sizeof运算得到指针指向的对象所占空间的大小,指针不需有效
- 对数组执行sizeof运算得到整个数组所占空间的大小,等价于对数组中所有的元素各执行一次sizeof运算符并将所得结果求和。注意:sizeof运算不会吧数组转换为指针来处理
- 对string对象或vector对象执行sizeof运算只返回该类型固定部分的大小,不会计算对象中的元素占用了多少空间。
因为执行sizeof运算能得到整个数组的大小,所以可以用数组的大小除以单个元素的带下得到数组中元素的个数。
因为sizeof的返回值是一个常量表达式,所以可以用sizeof的结果声明数组的维度。
C++多态性与虚函数表
- 虚函数表是针对类还是针对对象的?
虚函数表是针对类的,一个类的所有对象的虚函数表都一样 - 虚指针(vptr):每个含有虚方法(虚函数)对象里有虚表指针,指向虚表。
虚函数表:虚函数表是顺序存放虚函数地址的,虚表是顺序表,表里存放了虚函数的地址。 - 虚函数和纯虚函数
用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数。
纯虚函数是虚函数再加上 = 0; - 抽象类是指包括至少一个纯虚函数的类。
- 虚析构函数:只有当一个类被定义为基类的时候,才会把析构函数写成虚析构函数。
为什么一个类为基类,析构函数就需要写成虚析构?
假设现在有一个基类指针,指向派生类。此时释放基类指针,如果基类没有虚析构函数,此时只会看指针的数据类型,而不会看指针指向的数据类型,所以此时会发生内存泄露。 - 构造函数不能声明为虚函数的原因 :
- 构造一个对象的时候,必须知道对象的实际类型,而虚函数行为是在运行期间确定实际类型的。
- 虚函数的执行依赖于虚函数表。而虚函数表在构造函数中进行初始化工作,即初始化vptr,让他指向正确的虚函数表。而在构造对象期间,虚函数表还没有被初 始化,将无法进行。
- 不要在构造函数中调用虚函数的原因:
- 在概念上,构造函数的工作是为对象进行初始化。在构造函数完成之前,被构造的对象被认为“未完全生成”。当创建某个派生类的对象时,如果在它的基类的构造函数中调用虚函数,那么此时派生类的构造函数并未执行,所调用的函数可能操作还没有被初始化的成员,
先调用父类构造,父类都没创建完,子类没有 - 包含虚函数的类对象的虚指针被安排在对象的起始地址处,并且虚函数表(vtable)的地址是由构造函数写入虚指针的。所以,一个类的构造函数在执行时,并不能保证该函数所能访问到的虚指针就是当前被构造对象最后所拥有的虚指针,因为后面派生类的构造函数会对当前被构造对象的虚指针进行重写,因此无法完成动态联编。
不要在析构函数中调用虚函数的原因:
在析构函数中调用虚函数,函数的入口地址也是在编译时静态决定的。
就是说先调用子类的析构函数,子类已经没了,再调用父类的析构函数,子类都没了,虚函数还有什么用,也已经没了
- 在概念上,构造函数的工作是为对象进行初始化。在构造函数完成之前,被构造的对象被认为“未完全生成”。当创建某个派生类的对象时,如果在它的基类的构造函数中调用虚函数,那么此时派生类的构造函数并未执行,所调用的函数可能操作还没有被初始化的成员,
- 析构函数不能抛出异常
如果析构函数抛出异常,则异常点之后的程序不会执行,如果析构函数在异常点之后执行了某些必要的动作比如释放某些资源,则这些动作不会执行,会造成诸如资源泄漏的问题。
通常异常发生时,c++的机制会调用已经构造对象的析构函数来释放资源,此时若析构函数本身也抛出异常,则前一个异常尚未处理,又有新的异常,会造成程序崩溃的问题。
不要在析构函数中抛出异常! - 构造函数不能抛出异常
构造函数中抛出异常,会导致析构函数不能被调用,但对象本身已申请到的内存资源会被系统释放
构造函数中尽量不要抛出异常,能避免的就避免,如果必须,要考虑不要内存泄露!
- 虚函数表是针对类还是针对对象的?
指针和引用的区别
相同点:- 指针和引用都可以优化传参效率
- 都是地址的概念;指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。
引用和指针的不相同点
不同点: - 指针占内存空间,引用不占内存空间
- 指针可以为空,但是引用不能为空
- 指针可以不初始化,但是引用必须初始化
- 指针可以有多级,但是引用只能是一级
(int **p合法但是int &&a是不合法的)
- 指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了,从一而终。
- 指针是一种变量,而引用只是引用。
内联函数的优缺点
优点:- 内联函数发生在程序的编译期间,程序执行又比较简短的,大大增大代码执行效率。
缺点: - 如果函数的代码较长,使用内联将消耗过多内存
- 内联函数以复制为代价,活动产函数开销
- 如果函数体内有循环,那么执行函数代码时间比调用开销大
- 内联函数发生在程序的编译期间,程序执行又比较简短的,大大增大代码执行效率。
STL set、map实现为什么要以红黑树为底层实现机制?
为什么set、map的实现要采用红黑树?
STL标准库,基础中的要点
• map和set是基于什么实现的?红黑树的特点。
红黑树是一种特殊的平衡二叉搜索树,
1.每个节点只能是红色或者黑色。
2.根节点必须是黑色。
3.红色的节点,它的叶节点只能是黑色。
4.从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。• vector和list在储存上的区别。多维数组在储存上是连续的吗?
是
底层数据结构为数组 ,支持快速随机访问
底层数据结构为双向链表,支持快速增删
• queue和stack的实现,是数组还是链表?
deque是一个双端队列
底层一般用list或deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制• map 和 set
set 底层数据结构为红黑树,有序,不重复
map 底层数据结构为红黑树,有序,不重复
• map中的元素是自定义结构体,这个结构体有什么要求?(需要重载operator<)必须在构造函数初始化式里进行初始化的数据成员
常量成员,const修饰的成员变量,因为常量只能初始化不能赋值,所以也要写在初始化列表里面;
引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面;
没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化C++ STL 中 remove 和 erase 的区别
erase一般是要释放资源,真正删除元素的,
而remove主要用在vector中,用于将不符合要求的元素移到容器尾部,而并不删除不符合要求的元素
右值引用的目的主要是为了是减少内存拷贝,优化性能。
- 数据库的三大范式
第一范式就是属性不可分割,每个字段都应该是不可再拆分的。比如一个字段是姓名(NAME),在国内的话通常理解都是姓名是一个不可再拆分的单位,这时候就符合第一范式;但是在国外的话还要分为FIRST NAME和LAST NAME,这时候姓名这个字段就是还可以拆分为更小的单位的字段,就不符合第一范式了。
第二范式就是要求表中要有主键,表中其他其他字段都依赖于主键,因此第二范式只要记住主键约束就好了。比如说有一个表是学生表,学生表中有一个值唯一的字段学号,那么学生表中的其他所有字段都可以根据这个学号字段去获取,依赖主键的意思也就是相关的意思,因为学号的值是唯一的,因此就不会造成存储的信息对不上的问题,即学生001的姓名不会存到学生002那里去。
第三范式就是要求表中不能有其他表中存在的、存储相同信息的字段,通常实现是在通过外键去建立关联,因此第三范式只要记住外键约束就好了。比如说有一个表是学生表,学生表中有学号,姓名等字段,那如果要把他的系编号,系主任,系主任也存到这个学生表中,那就会造成数据大量的冗余,一是这些信息在系信息表中已存在,二是系中有1000个学生的话这些信息就要存1000遍。因此第三范式的做法是在学生表中增加一个系编号的字段(外键),与系信息表做关联。
- 程序运行过程
进程为程序的执行提供了一个虚拟执行环境,叫做进程空间。应用程序仿佛运行在一个独立的虚拟机器上,独占CPU和内存空间,通过标准接口(系统调用)来访问系统资源。
进程概念体现了容器的核心思想,即为应用程序的执行提供独立的空间
让它不仅支持CPU和内存的隔离,还要支持其它资源的隔离
通过Hypervisor层 —— 一种运行在基础物理服务器和操作系统之间的中间软件层,可允许多个操作系统和应用共享硬件。
这就是Linux内核的命名空间功能进行计算资源的隔离
内联函数和宏很类似,而区别在于,宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。而且内联函数是真正的函数,只是在需要用到的时候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开销。你可以象调用函数一样来调用内联函数,而不必担心会产生于处理宏的一些问题。
当然,内联函数也有一定的局限性。就是函数中的执行代码不能太多了,如果,内联函数的函数体过大,一般的编译器会放弃内联方式,而采用普通的方式调用函数。这样,内联函数就和普通函数执行效率一样了。
<>表示从系统目录下开始搜索,然后再搜索PATH环境变量所列出的目录,不搜索当前目录
“”表示先从当前目录搜索,然后是系统目录和PATH环境变量所列出的目录下搜索
什么是迭代器失效(vector扩容)。
迭代器失效就是因为扩容,删除元素等缘故,导致原先容器的空间变化,进而导致迭代器(begin()和end())发生了变化,从而失效
当插入(push_back)一个元素后,end操作返回的迭代器肯定失效,因为vector只允许从尾插。
List/set/map迭代器的失效情况:
删除时,指向该删除节点的迭代器失效,
其它任何增加、删除元素的操作都不会使迭代器失效。
c++ 编译过程
预编译 宏展开
编译 单独编译每个源文件
链接 可执行文件
对象内存布局详解
https://blog.csdn.net/ljianhui/article/details/46408645
c++ 对象大小计算
sizeof计算类的大小时,只计算(非static的)数据成员变量即可,不用考虑那些成员函数
看出空类的内存所占大小为1字节而非0,这是因为c++中,规定任何不同的对象不能拥有相同的内存地址。
也就是说,如果空类不占大小的话,那么同一类定义出多个对象后就会无法分辨出地址,且在C++中规定了任何不同的对象不能拥有相同的内存地址,所以编译器给了空类一个字节来唯一标识这个类。
内存对齐的原因
1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2.性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
三五法则
- 需要析构函数的类也需要拷贝构造函数和拷贝赋值函数。
- 需要拷贝操作的类也需要赋值操作,反之亦然。
- 析构函数不能是删除的
- 如果一个类有删除的或不可访问的析构函数,那么其默认和拷贝构造函数会被定义为删除的。
- 如果一个类有const或引用成员,则不能使用合成的拷贝赋值操作。
explicit (避免隐式转换)
https://www.cnblogs.com/rednodel/p/9299251.html
类型萃取
https://www.cnblogs.com/qq329914874/p/6729481.html
类可以实现在实现写在头文件
1.如果定义在类的内部,语法上是正确的;但是如果你要修改类的实现;由于实现在头文件里,导致项目中所有包含了该头文件的cpp文件都需要重新编译;如果实现在单独的cpp文件里,修改实现,只会重新编译一个cpp文件。
2.如果定义在头文件里,在类的外部;在多个文件包含该头文件时会在链接时产生重定义。
为什么宏喜欢用do while
使用do{…}while(0)构造后的宏定义不会受到大括号、分号等的影响,总是会按你期望的方式调用运行。
c++ 模版是怎么实现的
https://blog.csdn.net/lianhunqianr1/article/details/79966911
函数模板的签名包括模板参数,返回值,函数名,函数参数, cv-qualifier;
函数模板编译顺序大致:名称查找(可能涉及参数依赖查找)->实参推导->模板实参替换(实例化,可能涉及 SFINAE)->函数重载决议->编译;
函数模板可以在实例化时候进行参数推导,必须知道每个模板的实参,但不必指定每个模板的实参。编译器会从函数实参推导缺失的模板实参。这发生在尝试调用函数、取函数模板地址时,和某些其他语境中;
函数模板在进行实例化后(template argument deduction/substitution)会进行函数重载解析(overloading resolution, 此时的函数签名不包括返回值;
函数模板实例化过程中,参数推导不匹配所有的模板或者同时存在多个模板实例满足,或者函数重载决议有歧义等,实例化失败;
为了编译函数模板调用,编译器必须在非模板重载、模板重载和模板重载的特化间决定一个无歧义最佳的模板;
SFINAE -Substitution failure is not an error ;
要理解这句话的关键点是failure和error在模板实例化中意义,模板实例化时候,编译器会用模板实参或者通过模板实参推导出参数类型带入可能的模板集(模板备选集合)中一个一个匹配,找到最优匹配的模板定义,
模板特化
模板实参推导
C++ 11 了解多少
nullptr 关键字,专门用来区分空指针、0。
auto 和 decltype 这两个关键字实现了类型推导
序列for循环 变长參数的模板 更加优雅的初始化方法
移动语义和完美转发介绍一下?
移动语义(std::move)和完美转发(std::forward)
完美转发怎么实现的?什么原理?
线程有哪些独立的资源?
栈 寄存器
线程的状态都有哪些?
新建 就绪 运行 终止 阻塞
是否了解协程?讲一讲协程的原理
进程 拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度。
线程 拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度。
协程 和线程一样共享堆,不共享栈,协程由程序员在协程的代码里显示调度。
https://www.jianshu.com/p/8dacf1e8cb7c
无锁编程接触过吗?介绍一下cas
linux 的虚拟内存是什么?
分段和分页有什么区别?
如何检测内存泄露?不使用 valgrind 类似的工具
如何避免内存泄漏?
C++ 11 智能指针用过吗?需要注意哪些?
为什么要 2 倍扩容?
假如让你写一个测试程序用来测试vector,你会从哪些角度去考虑?
C++ stl 的内存管理是怎么做的?
有没有写过线程池?如何实现一个高性能的线程池?你能想到哪些方面?
继承和组合的区别
继承是代码复用的一种方式
组合体现的是整体和部分,强调的是has-a的关系。
继承与组合的优缺点
继承的优缺点
优点:
支持扩展,通过继承父类实现,但会使系统结构较复杂
易于修改被复用的代码
缺点:
代码白盒复用,父类的实现细节暴露给子类,破坏了封装性
当父类的实现代码修改时,可能使得子类也不得不修改,增加维护难度。
子类缺乏独立性,依赖于父类,耦合度较高
不支持动态拓展,在编译期就决定了父类
组合的优缺点
优点:
代码黑盒复用,被包括的对象内部实现细节对外不可见,封装性好。
整体类与局部类之间松耦合,相互独立。
支持扩展
每个类只专注于一项任务
支持动态扩展,可在运行时根据具体对象选择不同类型的组合对象(扩展性比继承好)
大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中
short x;
char x0;
x=0x1122;
x0=((char*)&x)[0]; //低地址单元
if(x0==0x11)printf(“大端\n”);
else printf(“小端\n”);
memcpy和memmove的区别
怎么在海量数据中找出重复次数最多的一个
https://blog.csdn.net/u010601183/article/details/56481868/
堆栈溢出的原因
1.函数调用层次太深。函数递归调用时,系统要在栈中不断保存函数调用时的现场和产生的变量,如果递归调用太深,就会造成栈溢出,这时递归无法返回。再有,当函数调用层次过深时也可能导致栈无法容纳这些调用的返回地址而造成栈溢出。
2.动态申请空间使用之后没有释放。由于C语言中没有垃圾资源自动回收机制,因此,需要程序主动释放已经不再使用的动态地址空间。申请的动态空间使用的是堆空间,动态空间使用不会造成堆溢出。
3.数组访问越界。C语言没有提供数组下标越界检查,如果在程序中出现数组下标访问超出数组范围,在运行过程中可能会内存访问错误。
4.指针非法访问。指针保存了一个非法的地址,通过这样的指针访问所指向的地址时会产生内存访问错误。
左值和右值
lvalue(locator value)代表一个在内存中占有确定位置的对象(换句话说就是有一个地址)。
rvalue通过排他性来定义,每个表达式不是lvalue就是rvalue。因此从上面的lvalue的定义,rvalue是在不在内存中占有确定位置的表达式。
https://www.cnblogs.com/likaiming/p/9045642.html
模版
https://www.cnblogs.com/xuelisheng/p/9329903.html
引用计数增加主要有以下场景:
- 对象被创建时:a = “ABC”;
- 另外的别人被创建时:b = a;
- 被作为参数传递给函数时:foo(a);
- 作为容器对象(list,元组,字典等)的一个元素时:x = [1,a,’33’]。
引用计数减少主要有以下场景: - 一个本地引用离开了它的作用域时,比如上面的foo(a)函数结束时,a指向的对象引用减1;
- 对象的引用被显式的销毁时:del a 或者 del b;
- 对象的引用被赋值给其他对象时:a = 789;
- 对象从一个容器对象(例如list)中移除时:L.remove(a);
- 容器对象本身被销毁:del L,或者容器对象本身离开了作用域。
select epoll