最近收集了许多嵌软的面试题,内容都是在很多文章中剪下来的!
目录
1.OSI七层模型?TCP/IP四层模型?每一层有什么常用协议?
4.从在浏览器地址栏中输入www.baidu.com到看到百度首页,这个过程中间经历了什么?
11.C++内存区域分为5个区,分别为栈、堆、自由存储区、全局/静态存储区和常量存储区。请说出各区存储的是什么?
13.printf可以接受多个参数吗,为什么,请写出printf的原型。
14.请列举常用的串行通信方式(两种以上),并简述串行通信和并行通信的不同之处、优缺点。
19.程序的局部变量存在于(堆栈)中,全局变量存在于(静态区)中,动态申请数据存在于(堆)中。
27. 5[“abcdef”]能编译通过,请问编译后的结果是什么?
面试不仅仅是C、C++的问题,相关涉广,还有系统、网络和数据库等。
1.OSI七层模型?TCP/IP四层模型?每一层有什么常用协议?
OSI七层模型
TCP/IP四层模型
应用层、传输层、网络层、数据链路层。
2.ARP和RARP协议是什么?
地址解析协议(ARP)的作用是将IP地址转换成MAC地址;反地址解析协议(RARP)则负责将MAC地址转换成IP地址。
3.常用端口对应协议
4.从在浏览器地址栏中输入www.baidu.com到看到百度首页,这个过程中间经历了什么?
按照时间顺序:
1)客户端浏览器获取用户在地址栏输入的域名。
2)客户端浏览器将域名发送给DNS域名系统,请求解析。
3)DNS解析域名得到相应的IP,返回给客户端浏览器。
4)客户端浏览器根据IP向服务器发起TCP三次握手,建立TCP连接。
5)客户端浏览器向服务器发送HTTP请求,请求百度首页。
6)服务器通过HTTP响应向客户端浏览器返回百度首页文件。
7)释放TCP连接。
8)客户端浏览器解析HTML文件,根据文件内容获取CSS、JS等资源文件,将页面渲染展示给用户。
5.编写strcpy函数(字符串复制)
#include "assert.h"
// 将源字符串加const,表明为只读,其值不会变
char *new_strcpy(char *dest, char const *src)
{
assert(dest&src); //括号内为0时向stderr打印错误信息,调用abort终止程序
// 等同于if(!(dest && src)) return NULL;
while(*src != '\0')
{
*dest++ = *src++;
} // src直到结束
return dest;
}
为什么要char * 的返回值:
主要是为了实现链式表达式。如strcpy(buf,strcat(dest,src));。
strcpy()函数的原型是char *strcpy(char *strDest,const char *strSRC)。其中,strDest是目标字符串,strSRC是源字符串。
6.编写strcat函数(字符串追加)
#include "assert.h"
// 将源字符串加const,表明为只读,其值不会变
char *new_strcat(char *dest, char const *src)
{
assert(dest&src); //括号内为0时向stderr打印错误信息,调用abort终止程序
// 等同于if(!(dest && src)) return NULL;
// '\0'为字符串结束标志
while(*dest != '\0')
{
dest++;
} // dest指针移到最后
while(*src != '\0')
{
*dest++ = *src++;
} // src直到结束
return dest;
}
为什么要char * 的返回值:
主要是为了实现链式表达式。如strcpy(buf,strcat(dest,src));。
strcpy()函数的原型是char *strcpy(char *strDest,const char *strSRC)。其中,strDest是目标字符串,strSRC是源字符串。
对于断言关键字assert,下面进行简单讲解:
#include "assert.h"
void assert( int expression );
assert的作用是现计算表达式 expression,如果其值为假(即为0),那么它先向 stderr 打印一条出错信息,然后通过调用 abort 来终止程序运行。
使用 assert 的缺点是,频繁的调用会极大的影响程序的性能,增加额外的开销。
在调试结束后,可以通过在包含 #include 的语句之前插入 #define NDEBUG 来禁用 assert 调用。
7.实现把字符串转化为整数
// 实现把字符串转化为整数
int new_atoi(char *str)
{
int sign = 1;
int sum;
if(str == NULL) /*判断是否为空*/
{
return 0;
}else if(*str == '-') /*判断是否负号*/
{
sign = -1;
str++;
}
while(*str != '\0')
{
sum = sum*10 + (*str-'0');
str++;
}
return sum * sign;
}
8.验证系统的大小端存储格式
大端存储模式:数据的低位,存放在地址的高位。
小端存储模式:数据的低位,存放在地址的低位。例如:int a = 0x12345678;(a首地址为0x2000)
0x2000 0x2001 0x2002 0x2003
0x12 0x34 0x56 0x78 大端格式0x78 0x56 0x34 0x12 小端格式
我们的pc机一般都是小端模式,在低位置存放的就是低位数据。
/* 方法1 */
typedef union{
int i;
char c;
}my_union;
int test(void)
{
my_union u;
u.i = 1;
return (u.i == u.c); //1-小端,0-大端
//return u.c; //1-小端,0-大端
}
/* 方法2 */
int test(void)
{
int i = 1;
return *(char *)&i; //1-小端,0-大端
//&i表示i在内存中的4字节地址
//(char *)&i表示将4字节地址的低1字节地址进行类型转换为符号指针
//*(char *)&i表示将4字节地址的低1字节地址进行类型转换为符号指针的值
}
/* 方法3同方法2 */
void test(void)
{
int i = 0x12345678;
char *c = (char *)&i;
if(*c == 0x78)
{
printf("小端");
}else
{
printf("大端");
}
}
9.进程间通信的方式有哪些?
有名管道
无名管道
信号
信号量
共享内存
消息队列
...
10.堆和栈的区别
1)申请方式不同:栈由系统自动分配,堆是由人为自行开辟(new,delete)
2)申请的大小不同:栈是从高地址向低地址分配的,分配空间较小,堆是由低地址向高地址分配的,空间较大。
3)申请效率不同:栈由系统分配,分配速度较快,堆一般较慢
4)队列和栈都是线性存储结构,栈是”先进后出”,队列是”先进先出”,都占用连续的地址空间,而堆不是连续的地址空间,堆随意存储,很容易产生内存碎片,造成内存泄露。
5)分配方式不同:堆都是动态分配,而栈分静态和动态分配。静态分配由编译器完成,比如局部变量的分配。动态分配由alloca函数进行。但栈的动态分配和堆是不同的,它的动态分配由编译器进行释放,无需我们手工实现。
11.C++内存区域分为5个区,分别为栈、堆、自由存储区、全局/静态存储区和常量存储区。请说出各区存储的是什么?
全局变量和静态变量存储在全局/静态存储区。常量存储在常量存储区。
栈存储的是函数参数值、局部变量等。
12.进程和线程的区别
进程:指在系统中正在运行的一个应用程序。程序一旦运行就是一个进程。进程是资源分配的最小单位。
线程:程序执行的最小单位。
这里借阅了其他文章的想法,其他博主写的很好。
做个简单的比喻:进程=火车,线程=车厢
线程在进程下运行(单纯的车厢无法运行)
一个进程可以包含多个线程(一辆火车可以有多个车厢)
不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘)
同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)
进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)
进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢)
进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上)
进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-"互斥锁"
进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量”。
13.printf可以接受多个参数吗,为什么,请写出printf的原型。
int printf(const char *fmt,...);
14.请列举常用的串行通信方式(两种以上),并简述串行通信和并行通信的不同之处、优缺点。
串行通信 | 并行通信 | |
传输原理 | 数据按位顺序传输 | 数据各个位同时传输 |
优点 | 占用引脚资源少 | 速度快 |
缺点 | 速度相对较慢 | 占用引脚资源多 |
列举 | UART、SPI、IIC、RS485、CAN | SDIO、FSMC |
并行速度快,串行口线间干扰小。
15.C++中,引用和指针的区别
在C++中,引用和指针都是用来间接访问变量的工具,但它们有一些重要的区别:
1.定义和初始化:
引用必须在定义时就被初始化,并且一旦指向了某个变量,就不能再改变其指向的变量。
指针可以先定义,后赋值,也可以重新赋值给其他对象,甚至可以指向空(NULL)。
2.操作方式:
引用不需要使用解引用操作符,直接通过引用名访问对象,由于其简洁性,通常用于函数参数和返回值。
指针需要使用解引用操作符'*'来访问所指向的对象,这使得指针的操作相对引用来说更加灵活,但也更容易出错。
3.空值:
引用不能为NULL,必须在初始化时指向一个有效的对象,因此如果需要表示可能为空的情况通常应该使用指针而不是引用。
指针可以为空,或者指向不同的对象,这使得指针在某些情况下更有用,例如可以通过指针实现可选的参数或者可空的返回值。
16.已知数组table,用宏求元素个数
#define COUNT(table) ( sizeof(table) / sizeof(table[0]) )
// 或
#define COUNT(table) ( sizeof(table) / sizeof(*(table)) )
17.全局变量和局部变量的区别
全局变量,储存在全局/静态存储区。进入main函数之前就被创建,生命周期为整个源程序;
局部变量,在栈中分配。在函数被调用时才被创建,生命周期为函数内。
全局变量和静态变量存储在全局/静态存储区;栈存储的是函数的参数值,局部变量的值等。
18.如何引用一个已经定义过的全局变量
答:可以用引用头文件的方式,也可以用extern关键字。
如果用引用头文件方式来引用某个在头文件中声明的全局变量,假定你将那个变量写错了,那么在编译期间会报错。
如果你用extern方式引用时,假定你犯了同样的错误,那么在编译期间不会报错,而在链接期间报错。
19.程序的局部变量存在于(堆栈)中,全局变量存在于(静态区)中,动态申请数据存在于(堆)中。
20.堆栈溢出一般是由什么原因导致的?
没有回收垃圾资源。
21.用两个栈实现一个队列的功能?要求给出算法和思路!
设2个栈分别为栈A、栈B, 一开始均为空。
入队: 直接依次push新元素入栈A;
出队:判断栈B是否为空;
如果栈B为空,则将栈A的元素依次POP出,并PUSH进栈B,将栈B的TOP元素PUSH出。
如果栈B不为空,则直接将栈B的TOP元素PUSH出。
22.数组和链表的区别
数组:顺序存储,插入删除效率低,访问元素效率高。
链表:随机存储,插入删除效率高,访问元素效率低。
23.语句for(;1;)有什么问题?它是什么意思?
答:没问题,和while(1)相同。
24.写出float x与’零值’比较的if语句
#include <stdio.h> int main() { float x; // 比较 x 与零值 if (x < 1e-6 && x > -1e-6) { // 如果 x 接近零值,执行这里的代码 printf("x 接近零值\\n"); } else { // 如果 x 不接近零值,执行这里的代码 printf("x 不接近零值\\n"); } return 0; }
25.阅读下面代码,写出答案
#include <stdio.h>
int main(void)
{
int a[5] = {1, 2, 3, 4, 5};
int *ptr = (int *)(&a + 1); //&a相当于变成了行指针,加1则变成了下一行首地址
printf("%d, %d",*(a+1), *(ptr-1));
}
在C语言中,
&a
表示整个数组a
的地址,而&a + 1
将得到一个指向整个数组后面某个位置的指针。*(a+1)就是a[1],*(ptr-1)就是a[4],执行结果是2,5
26.阅读下面代码,写出答案
unsigned char *p=(unsigned char *)0x0801000
p+5 =? 0x0801005
27. 5[“abcdef”]能编译通过,请问编译后的结果是什么?
printf("%d\n",5["abcdef"]);输出'f'的ACSII值,如果是4["abcdef"]则输出'e'的ACSII的值。
28.中断活动的全过程大致为?
1)中断请求:中断事件一旦发生或者中断条件一旦构成,中断源提交“申请报告”,请求CPU暂时放下目前的工作而转为中断源作为专项服务。(中断源发出中断请求)
2)中断屏蔽:虽然中断源提交了“申请报告”,但是,是否得到CPU的响应,还要取决于“申请报告”是否能够通过2道或者3道“关卡”(中断屏蔽)送达CPU。(中断请求经过中断屏蔽)
(相应的中断屏蔽位等于1,为关卡放行;反之相应的中断屏蔽位等于0,为关卡禁止通行);
3)中断响应:如果一路放行,则CPU响应中断后,将被打断的工作断点记录下来(把断点地址保护到堆栈),挂起“不再受理其他申请报告牌”(清除全局中断标志位GIE为0),跳转到中断服务子程序。
4)保护现场:在处理新任务时可能破坏原有的工作现场,所以需要对工作现场和工作环境进行适当保护。即SCB_VTOR的XPSR、PC、LR、R12、R3~R0由硬件自动压入适当的堆栈中。如果当响应中断时,当前的代码正在使用PSP,则压入PSP,即使用线程堆栈。否则压入MSP,使用主堆栈。一旦进入了中断服务,就将一直使用主堆栈;
5)调查中断源:检查“申请报告”是由哪个中断源提交的,以便作出有针对性的服务;
6)中断处理:开始对查明的中断源进行有针对性的中断服务;
7)清除标志:在处理完毕相应的任务之后,需要进行撤消登记(清除中断标志),以避免造成重复响应;
8)恢复现场:恢复前面曾经被保护起来的工作现场,以便继续执行被中断的工作;
9)中断返回:将被打断的工作断点找回来(从堆栈中恢复断点地址),并摘下“不再受理其他申请报告牌”(清除全局中断标志位GIE为1),继续执行原先被打断的工作。
29.C和C++的不同
1)c++源于c,c++最重要的特性就是引入了面向对象机制,class关键字。
2)c++中,变量可以在任何地方声明;c中,局部变量只能在函数开头声明。
3)c++中,const型常量是编译时常量;c中,const常量只是只读的变量。
4)c++有&引用;c没有。
5)c语言的main函数可以递归调用;c++中则不可以
6)c中,void *可以隐式转换成其他指针类型;c++中要求显式转换,否则编译通不过
30.IP地址的编码分为哪两部分?
IP地址由两部分组成:网络号、主机号。
31.什么是平衡二叉树?
当且仅当两个子树的高度差不超过1时,这个树是平衡二叉树。
32.死锁的四个条件和处理方法
1)互斥条件:一个资源每次只能被一个进程使用。
2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
3)不可剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
解决死锁的方法:银行家算法。
33.写一个冒泡排序算法
假设有一个数组a[n]。
冒泡排序思路:
第一轮:
a[0]和a[1]比较,a[1]存放大值。
a[1]和a[2]比较,a[2]存放大值。
...
a[n-2]和a[n-1]比较,a[n-1]存放大值。此时a[n-1]为数组最大值。
第二轮:
a[0]和a[1]比较,a[1]存放大值。
a[1]和a[2]比较,a[2]存放大值。
...
a[n-3]和a[n-2]比较,a[n-2]存放大值。此时a[n-2]为数组次最大值。
...
第n-1轮:
a[0]和a[1]比较,a[1]存放大值。此时a[0]为数组最小值,a[1]为数组次最小值。
即假设数组a[4] = {8, 4, 1, 9}
第一轮:
4 1 8 9
第二轮:
1 4 8
第三轮:
1 4
此时结果出来了: 1 4 8 9
uint32_t table[n]; uint32_t temp = 0; for(uint32_t i = n; i > 1; i--) { for(uint32_t j = 0; j < (i-1); j++) { if(table[j] > table[j+1]) { temp = table[j]; table[j] = table[j+1]; table[j+1] = temp; } } }
34.写一个快速排序算法
快速排序是从冒泡排序算法演变而来的,实际上是在冒泡排序基础上的递归分治法。
原理:找一个基准值,分别将大于和小于基准值的数据放到基准值左右两边,即一次划分。由于处在两边的数据也是无序的,所以再用同样的划分方法对左右两边的序列进行再次划分,直到划分元素只剩1个时结束。
void quick_sort(int a[], int start, int end)
{
if(start < end)
{
int i = start; // 记录指针左位置
int j = end; // 记录指针右位置
int temp = a[start]; // 记录基准
while(i < j)
{
while(i < j && a[j] < temp)
j--;
if(i < j)
a[i++] = a[j]; // 把小于基准值放在左边
while(i < j && a[i] >= temp)
i++;
if(i < j)
a[j--] = a[i]; // 把大于基准值放在右边
}
a[i] = temp;
quick_sort(a, start, i-1);
quick_sort(a, i+1, end);
}
}