文章目录
- 前言
- 一、C++相关
- 1.dynamic_cast的实现机制
- 2.函数重载的底层原理?为什么C++可以重载?
- 3.原始指针、unique_ptr和shared_ptr的使用场景
- 4.C和C++的适用性和场合?C++面向对象好在哪里?为什么嵌入式用C多?
- 5.share_ptr 直接类构造和用 make_shared 区别?
- 6.POSIX库
- 7.static_cast 和 reinterpret_cast 的区别?
- 8.模板在哪个阶段进行处理?
- 9.private继承有什么用?在什么场景下使用?
- 10.extern关键字的作用?
- 11. vector 的 push_back() 和 emplace_back() 有什么区别?
- 12. 指针和引用如何选择?
- 13.一个vector赋值给另外一个vector有哪些方法?
- 14.brk()、sbrk()、mmap()的区别以及底层实现?
- 15.share-ptr销毁了但还有weak-ptr指向那个对象,weak-ptr怎么知道这个对象已销毁?
- 16. C++ STL容器中不能存储引用类型的原因?
- 17.多线程程序编译 -lpthread 的作用?
- 18.Python和C++的区别是什么?
- 19. Python常用库
- 20.如何定位内存泄漏?常用的内存泄漏工具?
- 21. virtual函数缺省默认参数规则
- 22. 函数的返回值存放在哪里呢?
- 23. vector成员函数push_back的平均时间复杂度是多少?
- 24 delete this的使用场景
- 二、操作系统
- 三、计算机网络
- 四、Linux相关
- 五、设计模式
- 六、其他
前言
一、C++相关
1.dynamic_cast的实现机制
dynamic_cast的实现机制是根据class对应的vtbl虚函数表来实现的。
首先要知道一点,那就是dynamic_cast无法应用在缺乏虚函数的类型身上。
dynamic_cast通过寻找需要转型的对象的虚函数表指针,从而定位到相应的虚函数表,虚函数表中包含了该对象类型的专属信息type_info,从而检验该对象的动态类型,通过这种方式来保证向下转型的安全性。
2.函数重载的底层原理?为什么C++可以重载?
在编译的过程中,函数名将被转换成符号。
比如现在有两个重载函数:
void func(int i);
void func(double j);
那么它们可能被C++编译器转换为 “函数名+参数变量” 的形式的符号
void func(int i); -> __funci
void func(double j); -> __funcd
所以对于计算机而言,每个函数的符号都具有唯一性。
3.原始指针、unique_ptr和shared_ptr的使用场景
答案见注释,答案来源:
https://blog.csdn.net/calmreason/article/details/88994106
class T3;
class T2;
class T1;
class A
{
T3* m_t2;//创建和释放都由A之外的代码管理,A只负责(借用)使用;业务逻辑上保证A在使用m_t2指向的对象的时候,对象始终是存在的
shared_ptr<T2> m_t2;//由加载程序创建m_t2指向的对象,执行时,交给A来管理,涉及动态对象的管理权的交接
unique_ptr<T1> m_t1;//A管理该对象的生命周期,A的构造函数构造m_t1, A的析构函数释放m_t1;
};
4.C和C++的适用性和场合?C++面向对象好在哪里?为什么嵌入式用C多?
C和C++的适用性和场合?
C语言:C语言是结构化和模块化的语言,是面向过程的。当程序的规模较小时,C语言运用起来得心应手。但是当问题比较复杂、程序的规模比较大的时候,C语言就会展现出它的局限性。
C++:正是因为有大规模的程序需要去处理,C++就应运而生了。C++是由C发展而来的,与C语言兼容。C++既可用于面向过程的结构化程序设计,也可用于面向对象的程序设计,是一种功能强大的混合型的程序设计语言。
C++面向对象好在哪里?
面向对象技术的出现就是为了解决软件的大规模可拓展性问题。
面向对象编程是一种编程经验的抽象集合,面向对象编程有三大优势:模块化、对象结构和组合/聚合思想,它们的核心理念都是在提升代码的可拓展性、可重用性和可维护性。
(具体见收藏)
为什么嵌入式用C多,而不是C++?
1 从移植性来说,C的移植性比较好,C语言在不同的软件平台拥有相同的语法。
2 从运行效率上来讲,C++编译器会做很多额外的工作,比如为了使程序通过编译暗自隐式类型转换、虚函数机制的虚函数指针的开销等等,因此比起C++,C语言的效率更高。
5.share_ptr 直接类构造和用 make_shared 区别?
他们的区别在于make_shared只有一次内存申请操作,而shared_ptr构造函数会有两次(引用计数、堆区数据)。
make_shared可以分配单个内存块来保存这两个内存;从指向已分配对象的指针构造共享指针将需要分配第二个块来存储引用计数。除了这种效率之外,官方鼓励用make_shared函数来创建对象,而不要手动去new,这样就可以防止我们去使用原始指针创建多个引用计数体系。使用make_shared意味着您根本不需要处理new和原始指针,从而提供更好的异常安全性,在分配对象之后但在将其分配给智能指针之前不可能抛出异常。
6.POSIX库
Portable Operating System Interface(可移植操作系统接口) 的缩写,X表示UNIX,它是 ISO C 的延伸,明定了一个可移植的操作系统所应具备的种种条件,其范围不只有系统函数库而已。POSIX库 就是C POSIX library。C POSIX library是C语言的POSIX系统下的标准库。包含了一些在C语言标准库之外的函数。为了OS(比如windows 和 linux)之间的可移植性,POSIX标准规定了一些标准的API。而这些API标准的集合就是POSIX库。(标准库除外)
如
math.h
string.h
pthread.h
fcntl.h
poll.h
sys/socket.h
7.static_cast 和 reinterpret_cast 的区别?
static_cast 和 reinterpret_cast 主要区别在于:
static_cast 在类型转换的时候会带来内存中二进制数据的变化,会带来数位损失,比如说一个float类型的变量使用static_cast转换为int类型,那么底层二进制数据会发生变化。
而reinterpret_cast为操作数的位模式提供较低层的重新解释,也就是说原来内存中二进制数据是什么样的,转换后还是什么样,不会带来数位损失。
8.模板在哪个阶段进行处理?
编译阶段,会根据模板实参实例化出一份相应的模板代码。
9.private继承有什么用?在什么场景下使用?
最大的用处是通过“继承”的纵向逻辑建立了一种**“has-a”逻辑**。更直白点说,就是从基类继承来的成员,具有对内可用但是对外不可见的特点,这和组合的逻辑很像。所以说,在私有继承的语境下,可以把基类看成派生类的数据成员对象。
10.extern关键字的作用?
extern关键字的主要作用是引用其他文件中的全局变量或函数。
11. vector 的 push_back() 和 emplace_back() 有什么区别?
1 push_back() 向容器尾部添加元素时,首先会创建这个元素,然后再将这个元素拷贝或者移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素);而 emplace_back() 在实现时,则是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。
2 在c++11出现之前,push_back只能接受T、T&作为参数,出现了右值引用之后,push_back还可以接受T&&作为参数,所以push_back函数也进化了。而emplace_back与push_back的区别就在于emplace_back不仅支持T、T&、T&&,还支持传入classT的构造函数的参数。
12. 指针和引用如何选择?
More Effective C++ 条款1中有以下结论:
“当你知道你需要指向某个东西,而且绝不会改变指向其他东西,或是当你实现一个操作符而其语法需求无法由pointers达成,你就应该选择references。任何其他时候,请采用pointers。”
13.一个vector赋值给另外一个vector有哪些方法?
1 暴力,for循环赋值;
vector<int> v1 = {1, 2, 3};
vector<int> v2;
for(auto &num: v1){
v2.push_back(num);
}
2 拷贝构造函数;
vector<int> v1 = {1, 2, 3};
vector<int> v2(v1);
3 赋值运算符
vector<int> v1 = {1, 2, 3};
vector<int> v2;
v2 = v1;
4 移动构造函数
vector<int> v1 = {1, 2, 3};
vector<int> v2(std::move(v1));
5 swap函数
vector<int> v1 = {1, 2, 3};
vector<int> v2;
swap(v1, v2);
6 assign成员函数
vector<int> v1 = {1, 2, 3};
vector<int> v2;
v2.assign(v1.begin(), v2.end());
14.brk()、sbrk()、mmap()的区别以及底层实现?
malloc()函数底层是通过系统调用brk()和mmap()来实现的。如果申请的内存小于128k,就会调用brk()系统调用来增加mm_struct的brk值来分配小块内存返回。如果申请的内存大于128k,就会调用mmap()将磁盘空间映射到内存空间,供用户使用。
sbrk()不是系统调用,而是C库函数,它是由brk()实现的,作用是申请一块内存空间返回给用户。
15.share-ptr销毁了但还有weak-ptr指向那个对象,weak-ptr怎么知道这个对象已销毁?
从使用上来说,可以通过expired()方法判断观测的资源是否销毁。
从底层来看,weak_ptr内部有两个引用计数,一个是记录use_count记录shared_ptr对象的数量,一个是weak_count记录weak_ptr对象的数量,通过use_count来判断被管理资源是否销毁,通过weak_count来判断weak_ptr管理的引用计数资源是否销毁。
16. C++ STL容器中不能存储引用类型的原因?
首先,我们要了解到,在C++中,不能声明或定义指向引用的指针。
以vector容器为例,vector的迭代器就是原始指针,那么在访问vector元素时,指针就指向了一个引用,这时候就会出错。
17.多线程程序编译 -lpthread 的作用?
pthread是动态库,需要用-lpthread来链接到pthread动态库。
18.Python和C++的区别是什么?
C++执行效率高,开发效率低,因为C++语法比较严格;python执行效率低,开发效率快,原因是Python是解释性语言,它与CPU之间还有一层解释器,所以效率要比C++低。
另外Python使用自动垃圾收集器进行内存管理。
19. Python常用库
numpy:数据和矩阵运算
opencv:图像处理
os:文件相关操作
20.如何定位内存泄漏?常用的内存泄漏工具?
mtrace工具
mtrace原理:
mtrace工具的主要思路是在我们调用内存分配和释放的函数中装载“钩子(hook)”函数,通过“钩子(hook)”函数打印的日志来帮助我们分析对内存的使用是否存在问题。
对该工具的使用包括两部分内容,一个是要修改源码,装载hook函数,另一个是通过运行修改后的程序,生成特殊的log文件,然后利用mtrace工具分析日志,判断是否存在内存泄漏以及定位可能发生内存泄漏的代码位置。
21. virtual函数缺省默认参数规则
virtual函数缺省参数值是静态绑定的,依赖类型的静态类型。因此应当避免在重写的时候,重新定义继承而来缺省参数值。
C++编译器这么做的原因是什么?答案在于运行期效率。
如果缺省参数是动态绑定,编译器就必须有某种方法在运行期为virtual函数决定适当的参数缺省值。这比目前实行的“在编译期决定”的机制更慢更复杂。为了程序的执行速度和编译器实现上的简易度,C++做了这样的取舍,其结果就是你如今享受的执行效率。
22. 函数的返回值存放在哪里呢?
1 结构体大小不超过4字节,那么仍然使用EAX寄存器传递返回值
2 结构体超过4字节但不等于8字节时,调用者将首先在栈上分配一块能容纳结构体的临时内存块,然后在传递完函数参数后将该临时内存块的首地址作为隐含的第一个参数最后(因为压栈顺序是从右到左)压栈,接下的动作同前所述。当被调用函数返回时,它会通过第一个隐含参数寻址到临时内存块并将返回值拷贝到其中,然后将保存有返回值内容的临时内存块的首址存进eax寄存器中
3 结构体大小刚好为8个字节时编译器不再于栈上分配内存,而直接同时使用EAX和EDX两个寄存器传递返回值,其中EAX保存低4字节数据,EDX保存高4字节数据。
23. vector成员函数push_back的平均时间复杂度是多少?
先说答案 均摊时间复杂度为O(1)
原因
场景:假设vector容量从1开始,不断push_back,容量变为n。
1 不扩容的情况下,push_back的时间复杂度为O(1)
2 扩容的情况下,push_back的时间复杂度为O(m),m为当前vector的size
3 结合场景,分别在1,2,4,8,16,…,2^logn的时候需要扩容,可得均摊时间复杂度为常量时间复杂度。分析过程如下图。
24 delete this的使用场景
有些时候我们只希望在堆区创建类对象,一个方法就是把析构函数设置为私有的。
这个时候我们希望这个类有自我销毁的能力,那就需要用到delete this了。
#include<bits/stdc++.h>
using namespace std;
class A{
public:
void destroy(){
cout << "delete this" << endl;
delete this;
}
private:
~A(){}
};
int main(){
A *pa = new A;
delete pa; //错误写法
pa->destroy(); //正确执行
return 0;
}
二、操作系统
1.共享内存怎么实现?
1 mmap() 实现共享内存;
2 shmat() 和 shmctl() 实现共享内存;
原理都是将普通文件映射到多个进程的地址空间,即做一个文件磁盘地址与进程虚拟内存地址空间的映射关系。这样进程就可以通过访问这个文件达到共享内存的目的。
2.生产者消费者模式
生产者和消费者通过队列来通信,队列满时,不能生产;队列空时,不能消费。
condition_variable::wait(lock);
condition_variable::wait(lock, lambda);
如果只有一个参数,那么lock如果被占用,wait就会被阻塞。
如果两个参数,需要满足lock不被占用以及lambda返回true才能够获取锁。
#include<iostream>
#include<mutex>
#include<thread>
#include<unistd.h>
#include<deque>
#include<condition_variable>
using namespace std;
mutex m_lock;
condition_variable m_cv;
deque<int> m_data;
int g_data;
void Consumer(){
while(true){
unique_lock<mutex> m_lck(m_lock);
while(m_data.empty()){
m_cv.wait(m_lck); //阻塞该线程,直到其他线程notify_all
}
int data=m_data.front();
m_data.pop_front();
cout<<"get data: "<<data<<endl;
m_lck.unlock();
//sleep(1);
}
}
void Producer(){
while(true){
unique_lock<mutex> m_lck(m_lock);
m_data.push_back(g_data);
cout<<"add data: "<<g_data<<endl;
g_data=(g_data+1)%1000;
m_lck.unlock();
m_cv.notify_all();
sleep(1);
}
}
int main(){
g_data=0;
thread t1(Producer);
thread t2(Consumer);
t1.join();
t2.join();
return 0;
}
3 有哪些锁?介绍它们的实现?
互斥锁、自旋锁和读写锁。
1 互斥锁,就是一个整型,要么是0,要么是1,申请一块共享内存存放锁,使用关中断、硬件层面实现原子操作、锁内存总线等方式实现互斥锁。
2 自旋锁,可以通过 int test_and_set (int *lock); 这个原子操作来实现。
bool test_and_set(int *lock)
{
bool old=*lock;
*lock=true;
return old;
}
3 读写锁,当已经被加了读锁时,其他的读锁请求仍然可以访问,但是写模式锁不能访问(读写互斥);当写模式锁加锁时,其他的请求都不能访问。
#include<iostream>
#include<mutex>
using namespace std;
mutex read_mtx;
mutex write_mtx;
int reader_cnt = 0;
void Read(){
read_mtx.lock();
if(++reader_cnt == 1){
write_mtx.lock();
}
read_mtx.unlock();
//read something
read_mtx.lock();
if(--reader_cnt == 0){
write_mtx.unlock();
}
read_mtx.unlock();
}
void Write(){
write_mtx.lock();
//write something
write_mtx.unlock();
}
4.单核CPU使用多线程是否能提高效率?
首先要了解为什么要引入多线程?
假设有这样一个场景,程序只有一个单线程,当线程执行到某一步,需要等待IO资源或者其他资源时,程序将阻塞,CPU将空转,这样就浪费了CPU资源。
但是如果是多线程呢,就可以调度另外一个线程执行其他操作,这样就避免了CPU的空转,提高了CPU利用率,也减少程序整体的运行时间。
回到我们的问题,单核CPU使用多线程是否能提高效率呢?
对于CPU密集型的程序,多线程并不能提高运行效率。
对于IO密集型的程序,多线程可以提高运行效率。
原因上面已经解释了。
5.用户栈和内核栈的区别?用户态和内核态的区别?
用户态进程使用的栈空间,是在各个进程自己的地址空间中。而当进程从用户态陷入内核态时,需要使用内核态来保存堆栈信息,因为不同进程是共享内核空间的。
用户程序都运行在用户态,像访问硬件这样的操作如果由用户程序来做,可能会导致系统处于不安全的状态,所以提供了系统调用陷入内核态,由操作系统来完成一些权限等级较高的操作。
所以用户态和内核态的区别就是它们的权限等级不同,用户态只能访问受限的内存,而内核态可以访问所有的内存。
6.条件变量的作用、使用场景?
条件变量适用于这样的情形:两个线程完成的任务之间有明确的先后顺序。比如三个线程顺序输出 abcabcabc…
7.系统调用是通过什么机制陷入内核的?
通过软中断的机制。
内核中实现了很多的系统调用,这些系统调用的地址会按顺序存放在一个系统调用表中,是一个名为sys_call_table的数组。
在调用系统调用的时候,会执行一条特殊的指令并传递一个系统调用号作为索引,从sys_call_table中选择对应的系统调用。
8.open与fopen的区别是什么?
open是unix系统调用;而fopen是C库函数。所以fopen的移植性更良好。另外open没有缓冲区,fopen有缓冲区,减少了磁盘IO的次数。一般用fopen打开普通文件,用open打开设备文件(如网络套接字)。
9.exec函数族的作用?
exec函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的实体(包括代码段、数据段和堆栈等虚拟地址空间)。
更普遍的情况是,如果一个进程fork出一个子进程,然后调用exec,这样看起来就好像通过执行应用程序而产生了一个新进程。
10.什么场景下用多进程?什么场景下用多线程?
如果两个程序相关性很低或者可能要拓展到多机分布的应用程序,因为进程与进程之间的地址空间是相互独立的,所以我们可以选用多进程。
如果程序IO密集型应用,比如涉及到需要与用户交互的程序,需要用多线程,提高程序的运行响应速度。
但是实际中更常见的是进程加线程的结合方式,并不是非此即彼的
11.什么是虚拟内存技术、虚拟内存的作用?
虚拟内存技术是操作系统的一种内存管理技术,它允许进程在运行时不必完全在内存中,这种方案使得大程序可以运行在只有有限的物理内存的机器上。
采用虚拟内存技术,每个进程都有自己的地址空间,等到真正要用到哪个页面的数据时,在通过页面置换算法置换到物理内存中。
虚拟内存的三大作用:
答案一:
缓存:将主存看作一个存储在磁盘空间上的高速缓存,等要用到某个页面的数据时,才通过页面置换算法放进主存。
内存保护:使每个进程拥有独立的内存空间,互不影响。
内存管理:为每个进程提供了一致的地址空间,简化内存管理。
答案二:
在早期,呈现给编程人员的存储模型就是简单的物理内存。在这种情况下,想在内存中通时运行两个程序是行不通的。如果一个程序在某个位置写入一个新的值,将会擦掉第二个程序存放在相同位置的所有内存,所以同时运行两个程序是行不通的,这两个程序会立刻崩溃。
所以有了一种存储器抽象:地址空间。
虚拟内存技术的作用:
1 使多个进程有自己独立的内存空间,相互独立,互不干扰。
2 可以处理内存超载,也就是可以使得大型程序运行在有限的物理内存中,允许程序在只有一部分被调入内存的情况下运行。
3 使用分页技术,使得每个进程都可以被分割成很多块,一方面便于管理,另一方面比起分段式可以减少内存碎片,效率也更高。
12. 逻辑地址,线性地址,物理地址的理解,以及关系?
虚拟地址又称线性地址,将线性地址转换到物理地址,需要用到页式MMU;而将逻辑地址转换到虚拟地址,需要用到段式MMU。
逻辑地址就是进程地址空间中的相对地址,其实就是段内偏移量。
页号/段号+偏移就是线性地址。
线性地址经过MMU转换,得到物理地址。
13. 共享内存如何进行同步?
通过互斥锁、信号量和信号进行同步。(详情见收藏)
1 利用POSIX有名信号量实现共享内存的同步
有名信号量既可用于线程间的同步,又可用于进程间的通信。
两个进程,对同一个共享内存读写,可利用有名信号量来进行同步。一个进程写,另一个进程读,利用两个有名信号量 ”semr",“semw”。semr信号量控制能否读,初始化为0。semw信号量控制能否写,初始化1。
2 利用互斥锁
定义共享内存数据结构,内部携带互斥锁。
struct sm_msg
{
int flag;
pthread_mutex_t sm_mutex; // 定义互斥锁
char buf[SM_BUF_SIZE];
};
14.缺页异常的流程
当进程访问的虚拟内存页面不在物理内存中时,就会发生缺页中断,此时进程阻塞,此时系统会向磁盘发出读盘信号,通过柱面号,磁头号,扇区号定位磁盘位置,找到数据的起始位置并向后连续读取一页或几页载入内存中,操作系统将使用页面置换算法将页面置换进物理内存中,然后使该进程就绪,可以被调度继续运行。
15.什么是守护进程?
守护进程是运行在后台的一种特殊进程,守护进程独立于控制终端,周期性地执行某种任何或者等待处理某些事情的发生。它的本质是一个孤儿进程。
Linux下很多服务器都是守护进程,比如FTP服务器、SSH服务器、WEB服务器等。
16.操作系统为什么要进行分页?
分页是在分段后的一段时间出的。
分页是为了解决分段粒度大,因为段需要整段的加载进内存以及整段换出,造成内存碎片大,不易于管理,虽然可以通过将段置换出磁盘再加载的方式减少碎片,但是效率实在太低。
分页管理通过划分物理空间为一块块固定大小的页与之对应,能够将程序分割成一页一页加载进内存,提升了内存的利用率。
17. 内存碎片怎么处理
1 将小的空闲区域合成一大块,也就是内存紧缩技术。但是通常不进行这个操作,因为它要耗费大量的CPU时间。例如一台有8GB内存的计算机可以每8ns复制8个字节,它紧缩全部内存大约要花费16s。
三、计算机网络
1. 为什么有了流量控制还要有拥塞控制?
因为二者的作用不一样。
流量控制主要是告诉数据的发送方,接收方这边缓存中还能接收多少数据,请发送方控制发送速率,从而减少丢包率。
拥塞控制是结合当前网络拥塞情况,来控制窗口大小,减少网络拥塞程度,从而提高数据传输的效率。
两者作用不同,但又相辅相成。
2.DHCP作用
在网络中的主机,需要IP、子网掩码、网关等资源,而人工分配这些资源容易出错,所以DHCP协议的作用就是为网络中的主机自动分配这些资源。
3.TCP连接中端口的作用?
一个IP其实就可以标识一台主机了,但是一台主机上面可能运行多个应用程序,如果没有端口号,那么大量地数据过来,怎么知道哪个数据应该分发给哪个应用程序的呢?
为了解决这个问题,可以用 “IP+端口号” 的方式来标识,应用程序只监听相应端口号的数据就可以了!
4.什么场景用短连接,什么场景用长连接?
长连接多用于请求操作频繁,而且连接数不能太多的情况;而在并发量大并且请求操作不频繁的情况下,应选用短连接。
因为大量的长连接会占用端口号、套接字等资源,而服务器端的资源是有限的。如果在并发大的情况下使用长连接,会导致服务器端资源被耗尽而无法为后来的连接正常提供服务。
5.网络带宽是什么,它受哪些因素影响
网络带宽是指单位时间内(一般指的是1秒钟)内传输的最大数据量。
影响因素:网络设备;拓扑结构;用户的数量;客户机与服务器。
6.ping一个ip地址不同,但是HTTP可以访问是什么原因?反过来呢?
网站服务器为了防止DoS攻击,通常在防火墙里设置拦截ICMP报文,而ping报文正是ICMP报文的一种,当然ping不通了。
7.ping不通127.0.0.1是什么原因?
1、Ping 127.0.0.1:
127.0.0.1是本地循环地址,如果本地址无法Ping通,则表明本地机TCP/IP协议不能正常工作。重装tcp/ip协议,127.0.0.1就是测试tcp/ip是否装好的。
2、Ping本机的IP地址
用IPConfig查看本机IP,然后Ping该IP,通则表明网络适配器(网卡或MODEM)工作正常,不通则是网络适配器出现故障。
四、Linux相关
1.grep的用法
在Linux系统中,grep命令通常用来实现行的过滤。
grep命令的常用格式为:grep 选项 模式 文件。
常用选项:
-i:大小写不敏感
-n:显示行号
-v:只打印与模式不匹配的
-o:只显示被模式匹配到的字符串
2.kill的作用以及kill -9
Linux中的kill命令是用来终止指定的进程的运行。
在默认情况下,采用编号为15的SIGTERM信号。SIGTERM信号将终止所有不能捕获该信号的进程。对于那些可以捕获该信号的进程就要采用编号为9的SIGKILL信号,强行杀掉该进程。(SIGKILL杀死进程信号,linux规定进程不可以忽略这个信号)
3.Linux查看进程内存占用
方法1:top -p PID,查看MEM进程内存占用率。
方法2:ps -aux | grep filename ,查看第五个数字。
4.Linux下如果创建和使用动态库和静态库?
博客收藏
创建动态库 .so 文件
g++ 文件名 -fPIC -shared 动态库文件名.so
//-fPIC选项:表示编译为独立的代码,不用此选项编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。
//-shared选项:指定生成动态链接库
使用动态库
g++ 文件名 -L. -l动态库文件名 -o 可执行文件名
//-L.:表示链接的库在当前目录中
//动态库文件名需要掐头去尾
sudo cp libdynamic.so /usr/lib/
//系统默认会去/usr/lib/下找动态库
./main
//如果没有上面一步,这里会找不到动态库
创建静态库
使用ar命令创建静态库文件
ar cr 静态库文件名.a filename
//cr标志告诉ar将object文件封装
链接静态库
g++ main.cpp -lstatic -L. -o main
5 怎么查看进程打开了哪些文件?
**lsof (list open files)**工具可以查看一个进程当前打开了哪些文件?
使用方法:lsof -p PID 列出进程PID所打开的所有文件
五、设计模式
1.工厂模式相关
抽象工厂的基类是抽象基类,内部包含生产对象的纯虚函数。按照产品的不同派生多个派生类,每个类中生产自己的产品族。
1.1设计模式六大原则;工厂模式相关原则
1 开闭原则
一个软件的实体应当对拓展开放,对修改关闭。开闭原则,就是为了使程序的拓展性好,易于维护和升级。
2 里氏代换原则
面向对象设计的基本原则之一。里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。
3 依赖倒转原则
针对接口编程,而不要阵对实现编程,依赖于抽象而不依赖于具体。
4 接口隔离原则
使用多个隔离的接口,比使用单个接口要好。降低类之间的耦合度。
5 迪米特法则(最少知道原则)
一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
6 合成复用原则
尽量使用聚合的方式,而不是使用继承。
1.2工厂模式有关的原则:
开闭原则;依赖倒转原则;迪米特原则。
1.3工厂模式的作用
1 封装了new的代码,将调用者与被调用者的关系由强耦合变成弱耦合。
2 复杂对象创建对象时代码量过多,封装之后提供代码的重用性。
2 适配器模式
在软件开发中,有的时候系统的数据和行为都正确,但接口不符合,我们应该考虑使用适配器模式,目的是使控制范围之外的一个原有对象与某个接口匹配。举个例子:在开发一个模块的时候,有一个功能点实现起来比较费劲,但是,之前有一个项目的模块实现了一样的功能点;但是现在这个模块的接口和之前的那个模块的接口是不一致的。此时,作为项目经理的你,该怎么办啦?当然是在中间加一层Wrapper了,也就是使用适配器模式,将之前实现的功能点适配进新的项目了。为什么呢?主要是使用适配器模式有以下优点:
降低了去实现一个功能点的难度,可以对现有的类进行包装,就可以进行使用了;
提高了项目质量,现有的类一般都是经过测试的,使用了适配器模式之后,不需要对旧的类进行全面的覆盖测试;
总的来说,提高了效率,降低了成本。
六、其他
2.Qt信号和槽机制
3. 如何判断一个数是否在40亿个整数中?
4.gdb常用命令
r / run :使程序跑起来
b / break:打断点,b后面可以跟行号和函数名
n / next :执行下一条命令,函数会直接执行
s / step :执行下一条命令,会进入函数执行
i / info :查看信息,比如断点信息局部
l / list:查看代码
p / print :打印相关信息
q / quit:退出程序
要使用gdb必须要在编译的时候加上 -g 选项;
g++ -g main.cpp -o main
然后用gdb调试可执行文件;
gdb main
如果产生core dump错误,使用backtrace查看错误信息;
gdb [可执行文件名] [core文件名]
4.1产生core dump错误可能是?
数组越界;访问非法指针;堆栈溢出;多线程没有做到线程安全。
5.YUV的含义以及YUV的常见类型?
Y:亮度分量(相当于灰度)
UV:色度分量(U蓝色投影、V红色投影)
YUV格式,采用A:B:C表示法用于描述UV色度分量相对于Y分量的采样率。
YUV 4:4:4,存储方式
YUV 4:2:2,存储方式
YUV 4:2:0,存储方式