美团9.7后端面试
岗位
应该是成都的搞文件存储
流程
- 自我介绍
- C++怎么实现虚函数的
- 为了实现虚函数, C++使用了虚函数表来动态查找实际所需要执行的函数.
- 每个拥有虚函数的类(包括其派生类), 都有一个虚函数表, 这个虚函数表是编译器在编译时就已经建立好的
- 虚函数表中包含了类的实例所能调用的每个虚函数的指针, 指针指向了该类可以访问的最高级的对应函数.
- 编译器还向基类中隐式地添加了虚函数表指针, 当在对象上调用虚函数时, 通过虚函数表指针从虚函数表中查找正确调用的函数.
- 析构函数可以是虚函数吗?构造函数呢?
- 析构函数可以是虚函数, 当一个基类指针指向派生类, 并通过该指针删除对象时, 如果析构函数是虚函数, 就可以确保先调用的是派生类的析构函数, 保证派生类的资源被正确释放, 然后再调用基类的析构函数.
- 构造函数不能是析构函数, 当一个类的构造函数被执行时, 其虚函数表指针还为被正确初始化, 因此不能正确进行虚函数的执行, 所以构造函数不能是虚函数.
- C++面向对象的三大特性
- 封装
- 数据和代码捆绑在一起, 避免外界干扰和不确定性访问
- 把客观事物封装成抽象的类, 类还可以将自己的数据和方法只让可信的类或者对象操作, 对不可信的进行信息隐藏.
- 继承
- 让某种类型的对象获得另外一个类型对象的属性和方法
- 实现继承(使用基类的方法和属性)
- 接口继承(仅使用基类属性和方法的名称, 但子类必须自己进行实现)
- 可视继承(子窗体)使用基窗体(类)的外观和实现代码的能力.
- 多态
- 同一事物表现出不同的能力(向不同的对象发送同一消息, 不同的对象在接受时会产生不同的行为)
- 重载实现编译时多态, 虚函数实现运行时多态
- 覆盖: 子类重新定义父类虚函数的做法
- 重载: 存在多个重名函数, 但这些函数的参数表不同
- 封装
- 我重载了一个函数, 使用基类指针调用派生类的这个函数,
实际上调用的哪个?
- 取决于该函数是否被声明为虚函数
- 如果函数不是虚函数, 则会调用基类的版本(静态绑定)
- 如果函数是虚函数, 则回去调用派生类的版本(动态绑定)
- 介绍下智能指针
- shared_ptr: shared_ptr可以与其他共享一个实例的访问权, 且多个指向同一个实例的shared_ptr共享一个引用计数, 在生成新的shared_ptr时, 指向的实例的引用计数要+1, 销毁shared_ptr要将引用计数-1, 并判断是否需要销毁指向的实例.
- unique_ptr: 独占对于一个实例的访问权, 在销毁时也会销毁所指向的实例, (但是可以通过使用release的方法转移一个即将被销毁的指针的实例)
- weak_ptr: 不控制器所指向对象生命周期的指针, 为了解决shared_ptr可能导致的循环引用问题而设计, 需要和shared_ptr一起使用.
- 使用智能指针可以帮助避免内存泄漏和空悬引用, 仍然需要谨慎对待内存管理问题
- weak_ptr不增加引用计数, 是为了解决什么问题?
- 循环引用问题, 两个实例都包含一个指向对方的指针, 导致两者的引用计数最小是1, 形成内存泄漏.
- malloc和new的区别
- malloc只分配内存空间, new同时执行构造函数
- malloc需要显式声明空间, new不需要
- malloc是标准库函数, new是运算符, 还可以重载
- malloc分配失败返回NULL, new分配失败抛出bad_alloc异常
- malloc返回的指针必须强制转换为目标实例的指针, new直接返回的是目标实例的指针.
- C++内存分布
- 从低地址到高地址
- 代码区--存放机器码
- 数据区--存放已初始化的全局和静态变量, 常量数据
- BSS(Block Started By Symbol)--存放未初始化的全局和静态变态
- 堆--动态分配内存的区域, 低地址向高地址增长, 容量大于栈
- 栈--从高地址向低地址增长, 存放局部变量, 参数函数, 返回变量.
- 进程和线程的区别
- 进程是操作系统资源分配的基本单位, 线程是处理器任务调度和执行的基本单位
- 线程共享本进程的地址空间和资源, 而内存之间有各自独立的地址空间和资源
- 多进程比多线程要更健壮, 多线程中一个进程崩溃会导致整个进程崩溃, 而一个进程崩溃并不会直接导致其他进程崩溃.
- 每个进程有一个程序运行的入口, 顺序执行序列, 但是线程不能独立执行, 必须依赖进程管理
- 两者都可以并发执行
- 进程切换时, 消耗的资源大, 效率低
- 进程和线程进行调度的时候, 谁开销大, 为什么?
- 进程切换的开销大
- 因为每个进程都有独立的代码和数据空间, 在进程切换时, 操作系统需要保存和恢复更多的信息, 例如切换硬件上下文, 刷新TLB, 切换内核态堆栈
- 因为同一进程中的线程共享代码和数据空间, 所以线程的切换主要涉及用户态与内核态的转化. 发生调度时, 先触发中断进入内核态, 切换完成后再回到用户态.
- Linux使用什么系统调用来创建新的进程?
- fork: 创建一个新的进程, 是原有进程的一个子进程, 也是原有进程的一个副本, 在父进程中, fork返回子进程的PID, 子进程复制了父进程的task_struct, 系统堆栈空间和页面表.
- vfork: 创建一个新的进程, 但是新的进程与父进程共享内存, 堆栈和数据资源都是共享的, 避免了大量的内存复制. 必须执行完毕后, 父进程才会被继续调度
- clone: 可以选择性地将资源赋值给子进程, 而没有复制的资源则会使用指针来共享给子进程.
- 创建了新的进程之后, 子进程是否会共享父进程的资源?
- vfork会共享内存, clone可以指定共享的资源, 也可以全部共享或不共享. 但同一组父子进程会,共享一些文件描述符, 进程组ID
- 什么是僵尸进程?
- 一个进程fork了一个子进程, 如果子进程退出, 而父进程没有调用wait或者waitpid来获取子进程的状态信息, 那么子进程的进程描述符仍然保存在系统当中, 成为僵尸进程.
- 僵尸进程会一直占用进程号, 系统的进程号是有限的, 如果产生了大量的僵尸进程, 可能会导致没有可用的进程号来继续系统的运行.
- 还用过哪些系统调用?
- 进程控制: fork, clone, execve(运行可执行文件), exit(终止), getpid(获取进程标识号), getppid(获取父进程标识号)
- 文件系统控制: open, read, write, close等
- 系统控制: ioctl, reboot
- 内存管理: brk(改变数据段空间的分配), mmap(映射虚拟内存页), munmap(去除内存页映射)
- 网络管理: gethostname(获取主机名称), sethostname(设置主机名称)
- Socket控制: bind(绑定端口), connect(连接远程主机)
- 有用过MySQL? KV存储?
- Linux下软链接和硬链接是什么? 有什么区别?
- 硬链接是通过索引节点进行连接, Linux的文件系统中, 每个文件都会有个编号, 称为索引节点号, 索引节点用于标识这个文件. 硬链接就是通过这个索引节点进行连接, 只有在同一文件系统中的文件之间才能创建硬链接. 可以理解为硬链接就是让一个文件拥有多个有效路径名, 只要有一个以上有效的链接, 文件的数据块就不会被释放.
- 软链接类似于Windows的快捷方式, 其本身是个文本文件, 包含了另一文件的位置信息. 可以跨文件系统使用.
手撕的二叉树层序遍历, 6分钟撕完.
结束一小时后回人才库.