C++开发工程师面试问题总结
面试了一周的C++工程师,面试过程也是一个学习的过程,正好帮忙梳理一下知识盲点,所以做一个总结,方便以后查阅。
主要有以下几个方面的内容
- 进程和线程
- 进程,线程通信
- 多态,继承,重载
- C++11新特性
- socket网络编程,多线程编程,计网
- 编译原理,数据结构相关问题
进程和线程
1 . 进程是一段程序代码在计算机中的动态运行状态,是系统资源分配的单位(包括CPU,内存,寄存器等),也是资源调度的单位,一个进程由一个程序以及与它相关的状态信息所组成,在计算机中一般有三种状态就绪,等待,执行,只有进程无法实现真正的并行,每个进程有自己独立的逻辑地址空间;
2 .线程是进程中运行的实体,是轻量型进程,是CPU资源分配的单位,是处理器调度的最小单位,运行同一进程的线程具有相同地址空间(线程通信和进程通信的相关问题下面有提到),线程对象包含一个程序计数器(负责处理在下一次线程获取处理器时间时要执行的指令),一组寄存器(储存线程正在操控的变量值),一个栈(储存与函数调用和参数相关的数据)等;
3 .相对进程而言,线程的创建和管理的开销要小得多,在进程内创建多个线程可以提高系统的并行处理能力
4 .每个进程都至少有一个线程执行,一个进程可以交给多个线程执行,一个程序也可以对应多个进程
进程,线程通信
- 进程和线程的根本区别在于 多进程中每个进程有自己的地址空间,线程则共享地址空间。由于这个根本原因,实际上只有进程间需要通信,同一进程的线程共享地址空间,没有通信的必要,但要做好同步/互斥,保护共享的全局变量。
- 通信有以下几种方式
消息队列,共享内存,pipe管道,信号量
多态,继承,重载
- 重载:
函数的重载的规则:
1. 函数名称必须相同。
2. 参数列表必须不同(个数不同、类型不同、参数排列顺序不同等)。
3. 函数的返回类型可以相同也可以不相同。
4. 仅仅返回类型不同不足以成为函数的重载。
- 继承与多态:
C++的多态性用一句话概括就是:
在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象
类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数
1:用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数。
2:存在虚函数的类都有一个一维的虚函数表叫做虚表,类的对象有一个指向虚表开始的虚指针。虚表是和类对应的,虚表
指针是和对象对应的。
3:多态性是一个接口多种实现,是面向对象的核心,分为类的多态性和函数的多态性。
4:多态用虚函数来实现,结合动态绑定.
5:纯虚函数是虚函数再加上 = 0;
6:抽象类是指包括至少一个纯虚函数的类。
纯虚函数:virtual void fun()=0;即抽象类!必须在子类实现这个函数,即先有名称,没有内容,在派生类实现内容。
总结(基类有虚函数的):
1:每一个类都有虚表
2:虚表可以继承,如果子类没有重写虚函数,那么子类虚表中仍然会有该函数的地址,只不过这个地址指向的是基类的
虚函数实现,如果基类有3个虚函数,那么基类的虚表中就有三项(虚函数地址),派生类也会虚表,至少有三项,如果重写了
相应的虚函数,那么虚表中的地址就会改变,指向自身的虚函数实现,如果派生类有自己的虚函数,那么虚表中就会添加该项。
3:派生类的虚表中虚地址的排列顺序和基类的虚表中虚函数地址排列顺序相同。
这就是c++中的多态性,当c++编译器在编译的时候,发现Father类的Say()函数是虚函数,这个时候c++就会采用晚绑定技
术,也就是编译时并不确定具体调用的函数,而是在运行时,依据对象的类型来确认调用的是哪一个函数,这种能力就叫做c++
的多态性,我们没有在Say()函数前加virtual关键字时,c++编译器就确定了哪个函数被调用,这叫做早期绑定。
c++的多态性就是通过晚绑定技术来实现的。
C++11新特性
1 .auto根据程序上下文自动推导变量类型
2 .智能指针,是一个对象,封装了指针的操作(new,delete),可以自动释放动态申请的内存空间
3 .for循环使用更加方便,可以像脚本语言一样使用,如下:
int main()
{
int numbers[] = { 1,2,3,4,5 };
std::cout << "numbers:" << std::endl;
for (auto number : numbers)
{
std::cout << number << std::endl;
}
}
4 .nullptr的引入;
- NULL,0,nullptr的区别:
C++ 中NULL的定义就是0,但是0可以表示一个整形,也可以表示一个指针,这样会在类型转换的时候出现不安全的情况,而nullptr解决了这个问题,他就是一个空指针,类型转换中不会出现二义性,所以以后尽量用nullptr表示空指针
5 .线程
在C++11以前,C++的多线程编程均需依赖系统或第三方接口实现,一定程度上影响了代码的移植性。C++11中,引入了boost库中的多线程部分内容,形成C++标准,形成标准后的boost多线程编程部分接口基本没有变化,这样方便了以前使用boost接口开发的使用者切换使用C++标准接口,把容易把boost接口升级为C++接口。
一句话就是thread类更好用,仿佛在写Java一样
6 .移动构造函数和移动赋值运算符
首先要插入一点左值,右值的问题:
左值与右值的定义
C++( 包括 C) 中所有的表达式和变量要么是左值,要么是右值。通俗的左值的定义就是非临时对象,那些可以在多条语句中使
用的对象。所有的变量都满足这个定义,在多条代码中都可以使用,都是左值。右值是指临时的对象,它们只在当前的语句中
有效。请看下列示例 :
1. 简单的赋值语句
如:int i = 0;
在这条语句中,i 是左值,0 是临时值,就是右值。在下面的代码中,i 可以被引用,0 就不可以了。立即数都是右值。
2. 右值也可以出现在赋值表达式的左边,但是不能作为赋值的对象,因为右值只在当前语句有效,赋值没有意义。
如:((i>0) ? i : j) = 1;
在这个例子中,0 作为右值出现在了”=”的左边。但是赋值对象是 i 或者 j,都是左值。
在 C++11 之前,右值是不能被引用的,最大限度就是用常量引用绑定一个右值,如 :
const int &a = 1;
- 移动构造函数
传递数组,结构体,类等时,编译器严格执行复制构造函数,降低了性能,为了解决这个问题,提出移动构造函数,能够直接使用临时对象已经申请的资源,既能节省资源,有能节省资源申请和释放的时间。编译器将自动使用它移动‘“临时资源”,从而避免深复制
我们先定义转移构造函数。
MyString(MyString&& str) {
std::cout << "Move Constructor is called! source: " << str._data << std::endl;
_len = str._len;
_data = str._data;
str._len = 0;
str._data = NULL;
}
- 移动赋值运算符
现在我们定义移动赋值运算符。
MyString& operator=(MyString&& str) {
std::cout << "Move Assignment is called! source: " << str._data << std::endl;
if (this != &str) {
_len = str._len;
_data = str._data;
str._len = 0;
str._data = NULL;
}
return *this;
}
有几点需要注意:
- 参数(右值)的符号必须是右值引用符号,即“&&”。
- 参数(右值)不可以是常量,因为我们需要修改右值。
- 参数(右值)的资源链接和标记必须修改。否则,右值的析构函数就会释放资源。转移到新对象的资源也就无效了。
socket网络编程,多线程编程,计网
- TCP连接的建立过程,三次握手,四次挥手,双方如何通信等
- 多线程编程如何实现,信号量互斥,同步等等
- 计算机网络的层次结构:
TCP/IP模型:应用层(包括应用层,表示层,会话层),TCP层,IP层,网络接口层(包括数据链路层和物理层)
OSI模型:应用层,表示层,会话层,运输层,网络层,数据链路层,物理层
编译原理,数据结构相关问题
- 堆,栈,队列,链表的常用操作,树的遍历及实现方法,构建插入删除一个平衡二叉树,常用排序算法及复杂度(直接插入,选择插入,冒泡,堆排序,快排,希尔排序,归并排序等)
- 编译原理:词法分析,语法分析,语义分析,中间代码生成(三地址),代码生成,代码优化
- 程序在运行时分为五个区:
代码段 --text(code segment/text segment)
text段在内存中被映射为只读,但.data和.bss是可写的。
text段是程序代码段,在AT91库中是表示程序段的大小,它是由编译器在编译连接时自动计算的,当你在链接定位文件中将该
符号放置在代码段后,那么该符号表示的值就是代码段大小,编译连接时,该符号所代表的值会自动代入到源程序中。
数据段 -- data
data包含静态初始化的数据,所以有初值的全局变量和static变量在data区。段的起始位置也是由连接定位文件所确定,大小
在编译连接时自动分配,它和你的程序大小没有关系,但和程序使用到的全局变量,常量数量相关。数据段属于静态内存分配。
全局变量段--bss
bss是英文Block Started by Symbol的简称,通常是指用来存放程序中未初始化的全局变量的一块内存区域,在程序载入时
由内核清0。BSS段属于静态内存分配。它的初始值也是由用户自己定义的连接定位文件所确定,用户应该将它定义在可读写的
RAM区内,源程序中使用malloc分配的内存就是这一块,它不是根据data大小确定,主要由程序中同时分配内存最大值所确定,
不过如果超出了范围,也就是分配失败,可以等空间释放之后再分配。BSS段属于静态内存分配。
stack:栈
栈(stack)保存函数的局部变量(但不包括static声明的变量, static 意味着 在数据段中 存放变量),参数以及返回值。
是一种“后进先出”(Last In First Out,LIFO)的[数据结构]。对于哪些暂时存贮的信息,和不需要长时间保存的信息来说,
LIFO这种数据结构非常理想。在调用函数或过程后,系统通常会清除栈上保存的局部变量、函数调用信息及其它的信息。栈另外
一个重要的特征是,它的地址空间“向下减少”,即当栈上保存的数据越多,栈的地址就越低。栈的顶部在可读写的RAM区的最后。
heap:堆
堆(heap)保存函数内部动态分配内存,是另外一种用来保存程序信息的数据结构,更准确的说是保存程序的动态变量。
堆是“先进先出”(First In first Out,FIFO)数据结构。它只允许在堆的一端插入数据,在另一端移走数据。堆的地址空间
“向上增加”,即当堆上保存的数据越多,堆的地址就越高。