C/C++2019秋招面试题集合02
8.24 【深信服】优招 - C++ 研发(物联网方向)
1 C++ 内存分区,未初始化的全局变量放在哪?如果编译了在二进制文件里会有他的位置吗?
答:C++虚拟内存包括栈区,MMP文件映射区,堆区,全局区,代码区(Text).其中全局区包括未初始化的bss区(此区默认值为0)和初始化的data区;代码区Text分为二进制文本区和只读存储区;文本区存放二进制代码,只读存储区存放字符串常量。 所以应该不会存在。
2 野指针是什么?有什么工具可以检测吗?
答:野指针:野指针就是指向一个未申请访问权限的指针,或者指向一个已经被删除的内存区域。一般在Linux会造成非法访问内存,报出段错误。所以我们申请的空间记得删除并且置为NULL。 具体的检测工具可以自己网上查一下。
3 进程间通信方式,知道互斥锁和自旋锁吗?
答:现今进程间常用通信方式有:1 管道 2 信号 3 mmp映射区 4本地套接字。 其中管道分为无名管道(pipe())和有名管道(makefifo()),无名管道用于有血缘关系的父子间,兄弟间通信;有名则不需要血缘关系。信号开销最小,但最复杂,不建议用。mmap有无血缘都可以用,但其API函数使用要很清楚,否则容易出错。 套接字通信是最常用的且最稳定的。
互斥锁:也叫互斥量,实际上是一个结构体,当多个对象或线程对同一个资源进行访问时,都需要上锁,访问完毕后解锁,防止数据出现混乱。下面以互斥锁配合条件变量为例子,写出最典型的生产者消费者案例(Linux实现):
先说一下消费者生产者要做的事情。
消费者:
1 访问数据之前先加锁
2 判断数据是否存在,存在则消费,不存在则阻塞在条件变量,并且解锁等待生产者生产。
3 访问消费数据
4 解锁
生产者:
1 先生产数据
2 访问数据之前先加锁
3 添加数据
4 解锁
5 通知消费者,使条件变量解除阻塞
6 最后循环生产数据
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
//数据节点
struct msg {
struct msg *next;
int num;
};
struct msg *head;
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER; //这里使用静态方法给条件变量与锁直接赋值 没有调用函数动态赋值
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
//消费者的回调函数
void *consumer(void *p)
{
struct msg *mp; //对数据操作的临时变量
for (;;) {
pthread_mutex_lock(&lock); //1 上锁
while (head == NULL) { //2 先判断是否存在数据,头指针为空,说明没有数据节点 条件阻塞并解锁(wait函数的功能)
//可以为if吗 不可以否则多个消费来临时阻塞在条件变量,当生产者生产一个数据而被唤醒全部,数据不够分导致访问错误
pthread_cond_wait(&has_product, &lock);
}
mp = head;
head = mp->next; //3 访问消费掉一个产品
pthread_mutex_unlock(&lock); //4 解锁
printf("-Consume ---%d\n", mp->num);
free(mp); //打印并释放被消费掉的数据
sleep(rand() % 5);
}
}
void *producer(void *p)
{
struct msg *mp;
while (1) {
mp = malloc(sizeof(struct msg));
mp->num = rand() % 1000 + 1; //1 生产一个数据
printf("-Produce ---%d\n", mp->num);
pthread_mutex_lock(&lock); //2 访问数据之前加锁
mp->next = head;
head = mp; //3 访问添加数据
pthread_mutex_unlock(&lock); //4 解锁
pthread_cond_signal(&has_product); //5 将等待在该条件变量上的至少一个线程唤醒
sleep(rand() % 5);
} //最后加个循环生产
}
int main(int argc, char *argv[])
{
pthread_t pid, cid;
srand(time(NULL));
pthread_create(&pid, NULL, producer, NULL); //创建两个线程
pthread_create(&cid, NULL, consumer, NULL);
pthread_join(pid, NULL); //等待线程结束
pthread_join(cid, NULL);
return 0;
}
自旋锁有兴趣的自己搜查学习。
4 一个结构体,能够用 memcpy 判断两个结构体存的东西是不一样的吗?
答:
5 知道哈希表吗?怎么解决冲突,如果只有 32 个槽,怎么存放几千个数据?
答:
6 路由器和二层交换机的区别?
答:路由器是工作在网络层,根据IP地址寻址,软件实现速度慢;而交换机工作在数据链路层,根据MAC地址寻址,硬件实现速度快。
7 用过 shell 脚本吗?
答:用过,推荐看鸟哥的私房菜这本书。用过shell脚本的例子:写makefile编译程序;给日志文件增加“i”选项保护文件;截取ifconfig的某个内容,循环创建多个用户等等。(个人而言)
8 知道二叉树有哪些遍历的方式吗?
答:递归的前中后序遍历和非递归遍历。
这里顺道给出非递归的遍历代码:(这里看不懂可以不看)
非递归思想:二叉树传过来后,先将根节点放进栈中,然后取出栈来判断他的节点标志位是否为true,是就打印显示;不是就重新入栈,标志位改为true,并且将其左右子节点也放进栈中,由于栈为先进后出,所以按前序就应放右节点后到左节点再到根节点。
//二叉树的非递归遍历
int UnRecursion(BinaryNode *root){
//创建栈
LinkStack *stack=Init_LinkStack(); //该栈为我自己写的企业链表模式
//先创建一个二叉树节点放进栈中 MyStackNode为创建栈节点函数
Push_LinkStack(stack,(LinkSNode*)MyStackNode(root,false)); //必须是放带有二叉树节点的节点进去
//循环放与弹出
while(Size_LinkStack(stack)>0){
//先取出栈中元素
BinTreeStackNode *pcur=(BinTreeStackNode*)Top_LinkStack(stack);
Pop_LinkStack(stack); //有这个就不用删除malloc的节点,里面做了
//若二叉树节点为空则不作处理
if(pcur->root==NULL){
continue;
}
//若为真则显示
if(pcur->flag==true){
printf("%c",pcur->root->ch);
}else{
//否则就将它左右子树和本身放进栈中,并且本身变为true
//前序,但因为栈是先进后出所以倒转
Push_LinkStack(stack,(LinkSNode*)MyStackNode(pcur->root->rchild,false));
Push_LinkStack(stack,(LinkSNode*)MyStackNode(pcur->root->lchild,false));
pcur->flag=true;
Push_LinkStack(stack,(LinkSNode*)pcur);
//这四句改一下就是中序,后序
}
}
printf("\n");
return 0;
}
9 后序遍历的实现?
答:递归对比非递归比较简单。`
int BinaryRecursion(BinaryNode *root){
//递归的条件
//任何一个节点作为根节点时,为空就返回,证明到尽头了
if(root==NULL){
return -1;
}
//后序递归遍历-- DECBHGFA
//先遍历左子树
BinaryRecursion(root->lchild);
//再右子树
BinaryRecursion(root->rchild);
//最后输出相应的左子树节点
printf("%c",root->ch);
return 0;
}
`
10 假设 4 个人过河,每个人的过桥时间为 1,2,5,8。只有一个手电筒,一次最多过两个,怎么过桥速度最快?
答:1和2先过去,1回来,3分钟;然后5和8过去,2回来,10分钟;最后1和2过去,2分钟。3+10+2=15分钟。
11 如果是 n 个人怎么计算他的过河时间?
答:
12 arp 协议的功能干嘛用的?
答:以太网协议:根据MAC地址,完成数据包传输。ARP协议:根据IP地址寻址MAC地址。所以ARP协议是在数据链路层的以太网协议工作的。当数据封装好在以太网协议发包时,需要MAC地址,由于只知道数据包的IP地址,所以这时就需要ARP协议来将IP转为MAC,ARP首先发送ARP请求,被某个路由器接收后,路由器有一个路由表,记录着与他相连的多个路由器的IP地址,这样每一个路由器都能接收到这个请求,路由器IP地址与请求的IP地址一样就会相应,回发一个带有自己的MAC地址,这样就找到了MAC。IP不同的路由器会将该包扔掉。
13 快速排序的思想?
答: 思想:快速填坑。
1 挖坑填坑 第一个元素设为基准数被挖出来 然后从右到左找小于基准数的数 找到就填坑。
2 然后填坑的那个数相当于被挖了 这时从左往右找大于基准数的 找到就填坑 一直循环下去。
3 直到i<j就退出 然后将基准数填进i或者j的位置上 。
4 以上只是进行一次基准数填进 最后要递归将左右部分调用原函数排序。
5 所有的判断条件都要i<j。
//快速排序 --挖坑填坑
int QuickSort(int arr[],int start,int end){
int i=start;
int j=end;
int tmp=arr[start];
if(i<j){
while(i<j){
//先从右往左 找小于基数的数
while(i<j && arr[j]>tmp){
j--;
}
//填坑
if(i<j){
arr[i]=arr[j];
i++; //填完记得将i加1
}
//再从左往右 找大于基数的数
while(i<j && arr[i]<tmp ){
i++;
}
//填坑
if(i<j){
arr[j]=arr[i];
j--;
}
}
//最后将tmp填进i或j的下标中-----这样就进行了一次基数填进
arr[j]=tmp;
//然后循环填基数
//把左半部分进行快速排序 确定的基数就不必再排序
QuickSort(arr,start,i-1);
//把右半部分进行快速排序
QuickSort(arr,i+1,end);
}
return 0;
}
注:以上均为个人的答案,没写的欢迎大家补充。
题目来源LeetCode的ID为陈乐乐用户。答案自己查阅资料。