c++开发面经收集

c++基础知识部分

c++:浅拷贝、深拷贝
当对象存在指针类型的成员,不能用浅拷贝,否则拷贝对象指针类型数据改变,被拷贝对象的指针类型数据也会改变
要用深拷贝,在复制构造函数中定义对指针类型数据成员的值拷贝。

c++:虚函数
运行时多态机制,能够实现对象的动态绑定。函数内可以根据传入的对象的类型调用该传入对象的成员函数。

c++:为什么有析构函数?
用于释放动态申请的内存,避免内存泄露。

c++:智能指针
庞大的工程中可能会忘记释放动态申请的内存,造成内存泄露。使用智能指针可以避免这个问题,它会自动释放内存。
有unique指针,它所指向的对象不能共享;shared指针,它所指向的对象可以共享;weak指针,可复制shared指针,但其构造或释放不影响资源。

c++:左值和右值
左值代表一个在内存中占有确定位置的对象(换句话说就是有一个地址),它可以被赋值。
右值是在内存中不占有确定位置的表达式,它不能被赋值
int *p1 = &(++i); //正确
int *p2 = &(i++); //错误 因为i++ 最后返回的是一个临时变量,是右值,没有地址。
++i = 1; //正确
i++ = 5; //错误 因为i++ 最后返回的是一个临时变量,而临时变量是右值。

c++:左值引用和右值引用
传统的c++引用被称为左值引用,用法如下:
int i = 10;
int & ii = i;
C++ Primer Plus 第6版18.1.9中说到,c++11中增加了右值引用,右值引用关联到右值时,右值被存储到特定位置,
右值引用指向该特定位置,也就是说,右值虽然无法获取地址,但是右值引用是可以获取地址的,该地址表示临时对象的存储位置。语法如下:
int && iii = 10;
左值和右值是相对于赋值表达式而言的。左值是能出现在赋值表达式左边的表达式。左值表达式可以分为可读写的左值和只读左值。
右值是可以出现在赋值表达式右边的表达式,他可以是不占据内存空间的临时量或字面量,可以是不具有写入权的空间实体
如果写如下代码,定义一个左值引用,将其值置为一个常量值,则会报错:
int & i = 10;
原因很明显,左边是一个左值引用,而右边是一个右值,无法将左值引用绑定到一个右值上。
但是如果是一个const的左值引用,是可以绑定到右值上的。即如下写法是符合语法规范的:
const int & i = 10;

能将右值引用赋值给左值引用,该左值引用绑定到右值引用指向的对象,在早期的c++中,引用没有左右之分,
引入了右值引用之后才被称为左值引用,所以说左值引用其实可以绑定任何对象。这样也就能理解为什么const左值引用能赋予常量值。
int&& iii = 10;
int& ii = iii; //ii等于10,对ii的改变同样会作用到iii

c++ move函数:我们可以通过调用std::move函数来获得绑定到左值上的右值引用
std :: move用于指示对象obj可以“移动”,即允许资源从obj到另一个对象的有效传输
move告诉编译器:我们有一个左值,但我们希望像一个右值一样处理它
int && rr3 = std::move(rr1);
我们必须认识到,调用move就意味着:除了对 rr1 赋值或销毁外,我们不再使用它

c++:不能被继承的类
1.使用final关键字,该类为最终类,不能被继承
2.将构造函数放在private里,这样无法构造出子类对象。同时新增一个static修饰的get_instance函数,用来调用构造函数

c++:c++下如何调用c的包?
extern “C”,通知编译器其所包含的代码用c的方式编译

什么是相对路径,什么叫绝对路径?
以“/"开头的就是相对路径,它代表从根目录出发。反之是相对路径

linux下查看进程的相关命令:
ps -e 查看所有进程信息
kill -9 pid 强行杀死进程
top -p pid 查看进程信息

linux下查看内存、磁盘信息:
top
free
df

linux常用命令:
ls -al 显示当前目录下所有文件目录信息,包括隐藏的
mkdir 常见文件夹
cat 查看文件内容
cp 拷贝
rm -rf 删除文件夹下所有文件
find 查找文件
grep 正则匹配
pwd 显示当前文件路径
ln 创建文件链接 -s 软连接
chmod 修改文件权限
netstat -a | grep查看网络状态
top 查看内存、进程信息

c++:如何防止头文件重复引用: ifdef endif

c++:内联函数和宏的区别:
内联函数发生在编译阶段,宏发生在预处理阶段。
内联函数本身是函数,宏是字符串的简单替换
内联函数有自己明确的作用域和访问权限,宏是在全局进行替换
在使用简单宏定义时,当字符串中不只一个符号时,加上括号表现出优先级,
如果是带参数的宏定义,则要给宏体中的每个参数加上括号,并在整个宏体上再加一个括号。

c++:struct 和 class的区别:
struct的数据成员默认访问权限是public, class默认访问权限是private
struct继承默认是public继承, class默认继承时private
struct更像是数据结构的实现体, class更像是对象的实现体

c++:构造函数能不能是虚函数?
不能,因为类对象占前4个字节的虚表指针需要在构造函数完成后才能生成,通过虚表指针才能找到
虚函数表,访问调用对应的虚函数。如果构造函数是虚函数,那么只能通过虚表指针才能访问到,问题是
需要让构造函数产生出虚表指针。陷入了鸡生蛋蛋生鸡的问题。

c++:析构函数可不可以是虚函数?
可以。而且如果这个类不是final类,那么析构函数必须是虚函数。
因为如果不是虚函数,那么子类对象的父类组成部分将无法得到释放,造成资源泄露。

c++:析构函数可不可以是纯虚函数?
最好不要把析构函数当作纯虚函数。因为纯虚函数没有实现体,那么子类对象在析构时,父类组成部分
无法释放,资源泄露。但是也可以在类外写纯虚函数的实现体,从语法上不会报错

c++:数组和指针的区别
1.数组体现出来的就是一个指针常量的特性,就是不能对数组的首地址进行修改,内存上地址已经确定了。
而指针本身是一个变量,指向的地址是可以变化的。
2.当调用sizeof的时候,数组得到元素个数*数据类型的字节大小,指针得到指针类型的大小,取决于机器的
位数,比如32位机,就是4个字节大小。
3.相同之处时当作为形参是,定义成指针还是数组都一样,因为传入的是首地址,反映出来的就是一个指针。

c++:malloc和new的区别
malloc只是简单地分配了内存空间,而new在分配了内存空间后调用了对象的构造函数。
malloc分配的是堆内存;new分配的是自由存储区
malloc申请内存失败返回NULL;new申请内存失败抛出异常
malloc分配内存需要显示制定字节数;new编译器根据类型计算
malloc需要指定数组的大小后进行内存分配;有处理数组的new[]

c++:引用和指针的区别
引用是对象的别称,指针是一个地址,都用于绑定对象。
引用不能为空,指针可以为空。编码的时候,引用我们不需要判断,但指针需要判断非空性
当对象可能会发生改变的时候,往往使用指针。

c++:多态机制
静态多态:函数的重载、函数模板
动态多态:虚函数,主要可以通过父类指针指向一个子类,实现对子类函数的调用,体现了多态。
在调用过程中,是通过对象前4个字节的虚表指针找到虚函数表,此时的对象是子类对象,那么找到
的虚函数表是子类的虚函数表,所以此时通过虚表指针访问的虚函数就是子类的虚函数。

c++指针的大小:32位机4字节,64位机8字节

计算机网络

请你说说网络模型?
OSI七层网络模型:物理层、链路层、网络层、传输层、会话层、表示层、应用层
TCP/IP五层模型:物理层、链路层、网络层(IP)、传输层(TCP、UDP)、应用层(HTTP)

操作系统

中断/异常:中断是实现程序并发的机制,是CPU从用户态进入核心态的唯一途径;
中断有内中断和外中断,内中断又称为异常、陷入。
每条指令执行结束后,CPU检查是否有外部中断信号,有外部中断信号,则需要保护进程的CPU环境,
然后进入中断处理。然后执行中断处理程序后再恢复CPU环境退出中断,继续执行原进程。

系统调用:应用程序通过系统调用来请求获得操作系统的服务。系统调用会让CPU从用户态进入核心态。
凡是与资源有关的操作、会直接影响到其他进程的操作,一定需要操作系统介入,即需要通过系统调用来实现。

进程:进程是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位;由PCB、程序段、数据段组成。
进程的状态:(创建态)、就绪态、运行态、阻塞态、(终止态)

进程控制:进程控制实现进程状态的转换,用原语实现,原语用关/开中断实现,原语的执行必须一气呵成,不可中断。

进程通信:共享存储(要互斥地访问共享空间)、管道通信(半双工通信,写满可读、读完可写)、消息传递(直接通信、间接通信(信箱))

线程:轻量级进程,进程可包括多个线程,可增加并发度,减少并发带来的开销。线程是处理机调度的单位、进程是资源分配的单位。
线程分用户级线程和内核级线程,内核级线程才是处理机调度的单位。多线程模型有多对一(开销小、并发度低)、一对一(开销大、并发度高)、多对多。

进程同步:并发带来异步性,有时各进程的推进需要按照先后顺序,所以要通过进程同步解决异步问题。
进程互斥:对临界资源的访问,需要互斥进行,同一时间段内只允许一个进程访问该资源。(空闲让进,忙则等待)

死锁:各进程互相等待对方手里的资源,导致都处于阻塞态,无法推进。
死锁产生的必要条件:互斥条件、不剥夺条件、请求和保持条件、循环等待条件(循环等待未必死锁,死锁必然循环等待)
什么适合会产生死锁:对不可剥夺的资源不合理分配,就可能产生死锁
死锁、饥饿、死循环的区别:死锁至少两个进程死锁,都处于阻塞态;饥饿只有一个进程饥饿,处于阻塞态或就绪态;死循环是应用程序设计不合理造成的,进程可在运行态。
前两者才是操作系统要解决的问题。
死锁的解决办法:预防死锁(破坏四个必要条件)、避免死锁(银行家算法)、死锁的检查和解除(允许发生死锁,但系统会检测并解除)

算法

100亿个整数如何找到中位数?内存足够和内存不足两个场景
1)当内存足够时:
采用快排,找到第n大的数。
• 随机选取一个数,将比它小的元素放在它左边,比它大的元素放在右边
• 如果它恰好在中位数的位置,那么它就是中位数,直接返回
• 如果小于它的数超过一半,那么中位数一定在左半边,递归到左边处理(还是第几大)
• 否则中位数一定在右半边,根据左半边的元素个数计算出中位数是右半边的第几大(重新算第几大),然后递归到右半边处理
2)内存不够:
假设100亿个数字保存在一个大文件中,依次读一部分文件到内存(不超过内存的限制),将每个数字用二进制表示,比较二进制的最高位(第32位,符号位,0是正,1是负),如果数字的最高位为0,则将这个数字写入 file_0文件中;如果最高位为 1,则将该数字写入file_1文件中。
从而将100亿个数字分成了两个文件,假设 file_0文件中有 60亿 个数字,file_1文件中有 40亿 个数字。那么中位数就在 file_0 文件中,并且是 file_0 文件中所有数字排序之后的第 10亿 个数字。(file_1中的数都是负数,file_0中的数都是正数,也即这里一共只有40亿个负数,那么排序之后的第50亿个数一定位于file_0中)
现在,我们只需要处理 file_0 文件了(不需要再考虑file_1文件)。对于 file_0 文件,同样采取上面的措施处理:将file_0文件依次读一部分到内存(不超内存限制),将每个数字用二进制表示,比较二进制的 次高位(第31位),如果数字的次高位为0,写入file_0_0文件中;如果次高位为1,写入file_0_1文件 中。
现假设 file_0_0文件中有30亿个数字,file_0_1中也有30亿个数字,则中位数就是:file_0_0文件中的数字从小到大排序之后的第10亿个数字。
抛弃file_0_1文件,继续对 file_0_0文件 根据 次次高位(第30位) 划分,假设此次划分的两个文件为:file_0_0_0中有5亿个数字,file_0_0_1中有25亿个数字,那么中位数就是 file_0_0_1文件中的所有数字排序之后的 第 5亿 个数。
按照上述思路,直到划分的文件可直接加载进内存时,就可以直接对数字进行快速排序,找出中位数了。

问题:有哪些常用的排序算法?
冒泡排序 O(n2),快排O(nlogn),插入排序O(n2),归并排序O(nlogn),选择排序O(n^2),希尔排序O(nlogn),堆排序O(nlogn),桶排序,计数排序n,基数排序d(n+r)

问题:快排的时间复杂度是多少?
O(nlogn)

问题:还有那些排序算法复杂度是nlogn?
归并排序, 堆排序

要熟悉各种排序算法的代码:快排(几乎必考)、归并排序、堆排

你可能感兴趣的:(c++开发面经收集)