1, 信号的声明周期
答: 信号的产生 -> 信号在进程中注册 -> 信号在进程中的注销 -> 执行信号处理函数
2, 信号的产生方式
答: 1, bash
命令行下的kill
命令; 2, C
中的信号发送函数kill(), sigqueue(), raise(), alarm(), setitimer(), pause(),abort()
等; 3, 一些软硬件异常产生的信号
3, 信号的处理方式
答: 1, 默认处理方式, 比如PIPE
信号会导致进程终止; 2, 忽略处理或者自定义函数处理(signal
或者signaction
函数)
4, 隐式类型转换以及如何消除?
答: 如果可以通过一个实参创建一个类, 那么我们可以隐式的完成该实参类型到该类类型的转换, 通过该构造函数前加上explicit
避免这种隐式的转换
5, 重载/重写/隐藏的区别
答: 重载指的是函数的重载(函数名相同和参数不同的函数), 重写指的是虚函数, 隐藏指的是子类的函数名与父类相同就会隐藏
6, volatile功能
答: 在实际中我们可能遇到一个线程等待一个参数在另外一个线程发生改变; 但是由于代码优化每次都从寄存器中取值, 而另外线程的修改没有影响到此线程的寄存器, 导致无法检测变化; 该关键字指的就是每次从内存去取值.
7, malloc/new/free/delete区别
答: new/delete是描述符, 仅属于C++; 而后两者是库函数, C中可以使用. new
错误会抛出bad_alloc
异常, 而malloc
失败返回NULL; new
无需指明大小, 其返回的是指定的类型, malloc
必须指明大小并返回void
指针; new
会调用构造函数, 后者不会; 都是在堆中分配释放内存.
8, free
是怎么知道释放多大内存的呢?
答: 一般在分配的内存前几个字节有一个结构体保存一些信息, 在redis
中实现的动态字符串就是这种形式.
-------------------2019年11月9日16点35分----------------------
9 __stdcall
和__cdecl
的区别
答: 函数参数均从右到左入栈, 但是前者被调用的函数自行清理堆栈, 后者是主动调用该函数的进行清理堆栈。 也因此, 前者需要确定的参数数量, 不能用于变参数.
10, Linux有哪些调试宏
答: __FILE__
/__LINE__
/__FUNCTION__
分别指明文件名/行数/函数名
11, 线程安全的单例模式
饿汉式
class Singleton
{
private:
Singleton();
Singleton(Singleton const &);
Singleton & operator = (Singleton const &);
static Singleton *instance;
public:
static Singleton* getInstatce()
{
return instance;
}
};
Singleton Singleton::instance = new Singleton();
懒汉式
class Singleton
{
private:
Singleton();
Singleton(Singleton const &);
Singleton & operator = (Singleton const &);
static Singleton *instance;
static std::mutex _mutex;
public:
static Singleton* getInstatce()
{
if(nullptr == instance)
{
std::lock_guard<std::mutex> lk(_mutex);
if(nullptr == instance)
{
instance = new Singleton();
}
}
return instance;
}
};
std::mutex Singleton::_mutex;
注意:可以额外添加以静态成员用以销毁资源
12, 引用和指针的区别?
- 1 引用只是一个别名, 必须初始化, 不占用额外空间; 指针本身就是一种数据结构可以为
nullptr
, 占用4/8字节 - 2 引用只能为一个成员的引用, 指针如果不加
const
限制的话可以修改其指向
13, 出现异常时, try
和catch
分别干了什么?
答: try{}
用于包含可能抛出异常的语句; catch{}
用于包含处理异常的语句
14, C++如何处理多个异常
try
{
//可能抛出异常的语句
}
catch (异常类型1)
{
//异常类型1的处理程序
}
catch (异常类型2)
{
//异常类型2的处理程序
}
// ……
catch (异常类型n)
{
//异常类型n的处理程序
}
15, intptr_t
的用处
答: intptr_t
通常是和指针关联起来的, 再不同的平台上定义是不一样的和电脑的位数一致; 可以跨平台的用于保存指针
16, 常对象的成员变量一定不可修改吗? 为什么?
答: 常对象的成员变量不可修改, 和普通使用const
修饰的基础类型一样; 尽可以调用常成员函数.
17, 虚函数的调用过程?
答: 对象指针指向的地址前四个字节存储的就是虚函数表的首地址(如果有虚函数的话), 获得了该地址即可获得所有虚函数的地址.
18, 单继承,多继承,菱形继承,虚继承时,对象内存中的差异区别?如果存在虚函数呢?
答: C++使用的对象模型: nostatic对象放置于每一个类的对象之中, static则放在类对象之外, static和nostatic也放在类对象之外, 对于virtual函数使用虚函数表+虚函数指针来支持:
1, 每一个类生成一个表格, 称为虚函数表; 用于存储一堆指针指向类的虚函数, 按照声明的顺序.
2, 每个类的对象都有一个虚函数表的指针vptr, 一般放在类对象的前端.
3, 虚函数表的前端 设置了一个指向type-info
的指针, 用于dynamic_cast等运行时类型判断.
vector为什么采用成倍增长而不采用固定容量呢? VS采用1.5/GCC采用2, 为什么?
注意: 绝大多数分配时使用的时malloc
再复制的方式.
答: 假设采用成倍增长:
如果最后数量为n
, 倍增因子为m
, 那么需要重新分配空间的次数为logm(n)
, 每次复制所需的时间复杂度为m^i
; 所以整的时间复杂度为:
1*m^1 + 2*m^2.....logm(n)*m^(logm(n)) = 1*m^1+2*m^2....logm(n)*n
时间复杂度是常数级.
如果采用固定容量增长:
如果最后数量为n
, 增长容量为为m
, 那么需要重新分配空间的次数为n/m
, 每次复制所需的时间复杂度为m*i
; 所以整的时间复杂度为:
1*m*1+2*2*m..(n/m)^2*m = m+m*2^2..m*(n/m)^2
时间复杂度为O(N)
采用1.5分配的方式, 能够使得第N
次分配的空间, 能够重用前N-1
分配的空间. 采用2的方式可能是方便寻址.
map底层使用了什么
答: 红黑树
如果用map删除了一个元素,迭代器还能用吗?为什么?怎样做可以接着用?
答:
for(auto it = map.begin(); it != map.end();)
{
if(xxx)
map.erase(it++);
else
it++;
}
- 如果对一个iterator使用了erase, 那么此iterator就失效了; 之后任何操作是未定义的.
-
it++
会对, it进行后移操作, 同时返回原本的值;erase
销毁的it
已经是另外一个了.
: 如果对于
vector
呢?
for(auto it = vec.begin(); it != map.end();)
{
if(xxx)
vec.erase(it); //erase后自动后移
else
it++;
}
线程同步的几种方式?
答: 互斥锁(mutex), 条件变量(condition_variable), 信号量(Semaphore)
Do{}while(0)的用法有哪些?
答: 通常用于仅执行一次的代码, 用于宏定义当中比较多;
手写快排?时间复杂度?空间复杂度?能进行优化吗?还有吗?能进行尾递归优化吗?
答:
void core(int num[], int r, int l)
{
if(i >= j-1) return;
if(i+1==j)
{
if(num[i]>num[j])
swap(num[i], num[j]);
return;
}
int mid = r;
int i = r+1;
int j = l;
while(i<j)
{
while(i<j&&num[i]<=mid) i++;
while(i<j&&num[j]>=mid) j--;
if(i < j)
swap(num[i], num[j])
}
swap(num[k], num[j]);
core(num, i, j-1);
core(num, j+1, l);
}
void quick_sort(int num[], int n)
{
if(n <= 1) return;
core(num, 0, n - 1);
}
//迭代做法
while(i<j)
{
int k = partition(num, i, j);
core(num, i, k-1);
i = k+1;
}
空间复杂度是O(1)
, 时间复杂度为O(nlogn)
,
线程池的作用是什么?
答: 减少资源开销, 提高响应速度, 同时提高管理性
TCP三次握手和四次挥手及各自的状态?
答: 三次握手
A -----CLOSE
LISTEN------B
->SYN
A -----SYS_SEND
<-ACK/SYN
SYS_RECV----B
A ----- ESTAB-LISHED
->ACK
ESTAB-LISHED--B
四次挥手:
A ----- ESTAB-LISHED
ESTAB-LISHED--B
->FIN
A ----- FIN_WAIT1
<-ACK
CLOSE_WAIT---B
A ----- FIN_WAIT2
<-FIN
LAST_ACK --- B
ACK->
A ----- TIME_WAIT
CLOSE--------B
TCP如果两次握手会出什么问题?那三次握手又会造成什么问题?有什么好的解决方法没?
答: 两次握手可能会导致一方建立连接而另外一方却不是; 三次握手可能引起SYN攻击
TCP四次挥手为什么要有TIME_WAIT状态?为什么?
答: 2MSL, 保证重传
排序稳定的算法,你知道那些?
答: 冒泡排序, 插入排序, 归并排序(合并时左边再前)
HASH冲突的解决办法?
答: 链表法, 再HASH法
C++内存存储
答: 堆、栈、自由存储区(new)、全局/静态存储区, 常量
如果new申请内存失败了,如何去解决?
答: bad_alloc错误
如何得到一个结构体内成员的偏移量?
答: #define offset(type, mem) ((size_t)(&(((type*)0)->mem)))
进程和线程的区别
答: 一个进程可以有多个线程, 普通程序属于单进程单线程; 不同进程间数据不可见, 同一个进程中的线程处于一块内存区.
逐层打印二叉树?
答:
void core(ListNode *root)
{
if(!root) return;
print(root);
core(root->left);
core(root->right);
}
void print_tree(List Node *root)
{
queue<ListNode*> _queue;
_queue.push(root);
while(_queue.size())
{
ListNode *tmp = _queue.top();
_queue.pop();
if(tmp->left)
_queue.push(tmp->left);
if(tmp->right)
_queue.push(tmp->right);
print(tmp);
}
}
构造函数能不能虚函数?为什么?那拷贝构造函数能不能为虚函数?为什么?
答: 不可以为虚函数, 因为虚函数需要虚函数表; 构造函数实在对象完全构造之前运行的,但是只有创建了对量才能获得虚函数指针. 拷贝构造函数也一样
析构函数能不能虚函数?为什么?
答: 子类可能存在其自身的数据, 仅靠父类的构造函数无法完全进行.
模板和实现可不可以不写在一个文件里面?为什么?
答: 必须写在一个文件中, 模板类的实现,脱离具体的使用,是无法单独的编译的; 不同类型的调用函数其实是不一样的.
为什么要字节对齐?
答: 字节对齐, 可以提高访问效率
在成员函数中调用delete this会出现什么问题?对象还可以使用吗?
答: this所指向的空间会被释放, 但是如果不涉及到this还是可以运行的; 但是要考虑到空间虽然被释放了, 但是内容未必会被立即覆盖.
如果在构造函数中调用memset(this, 0, sizeof(*this))
来初始化内存空间,有什么问题吗?
答: 的确可以使用this指针, 但是这样覆盖的话会影响虚函数表和已经初始化的数据.
对一个数组而言,delete a和delete[] a有什么区别?为什么?
答: 对于普通的数据类型没有差别, 但是对于类对象来说仅调用a[0]
的析构函数.
Dynamic_cast是如何实现运行时类型转换的?
答: 没有虚函数的类型是无法判断的, 可以猜到是利用的虚函数表中的type-info
数据结构来判断.
Extern “C”是什么意思?他有什么作用?
答: 表示该部分代码按照C代码格式进行编译.
进程间的通信方式有哪些?线程间的通信方式呢?
答: 进程间通讯包括socket、pipe、信号量等; 线程间数据通用, 仅需要考虑同步的问题.
IO模型主要有哪些?
答: 阻塞IO/非阻塞IO/多路复用IO
阻塞和非阻塞?同步与异步的区别?
答:
1,同步,就是我调用一个功能,该功能没有结束前,我死等结果。
- 异步,就是我调用一个功能,不需要知道该功能结果,该功能有结果后通知我(回调通知)
- 阻塞,就是调用我(函数),我(函数)没有接收完数据或者没有得到结果之前,我不会返回。
- 非阻塞,就是调用我(函数),我(函数)立即返回,通过select通知调用者
Select,poll和epoll的区别?为什么?
答:
select多平台通用, epoll仅用于Linux平台
select存在连接数限制, Linux下默认是1024个; poll没有最大连接数限制, 使用的是链表
select扫描方式是轮询的, 效率较低; 当事件后也需要遍历传入的socket
select需要维护一个较大的数组, 每次传入内核.
epoll存在边缘触发(如果事件触发后不解决, 之后不再提醒), 属于事件驱动, 不需要每次复制数据到内核. 仅考虑活跃连接, 仅有活跃连接会调用回调函数。
Struct{char a[0];}的作用?有什么好处?
答: 数组扩展
Struct A a;
A *b = &a;
char buffer[128] = {};
b = buffer;
判断浮点数相等
答: abs(a-b)<1e-6
栈上分配内存和堆上分配内存有什么区别?
答: 栈的内存较少, 堆的内存加多; 栈分配的内存再代码段结束时会自动回收.
变量的存储方式有哪些?
答: C语言中的变量有四种存储类型,这四种存储类型的关键字分别是auto(自动),extern(外部),static(静态)和register(寄存器)。
register无法用于全局变量且无法取址. 速度更快.
线程私有和共享那些资源?进程私有和共享那些资源?
答: 线程共享数据: 进程代码段、进程的公有数据(利用这些共享的数据,线程很容易的实现相互之间的通讯)、进程打开的文件描述符、信号的处理器、进程的当前目录和进程用户ID与进程组ID
私有数据: 线程ID;寄存器组的值;线程的堆栈;错误返回码;线程的信号屏蔽码;线程的优先级。
什么是守护进程?如何查看守护进程?什么是僵尸进程?如何查看僵尸进程?
答: 脱离控制台, 后台运行的进程; 使用ps查看. 父进程没有等待子进程就结束了, 子进程就成为了僵尸进程; 可以使用top查看.
2019年12月30日21点58分
网友评论