面试笔试大概会出现的问题其二
1.编写一函数void fun(char srt[]),统计并输出字符串中字母,数字空格和其他字符的个数。
#include <stdio.h> void fun(char str[]) { int nChar=0; int nNum=0; int nOther=0; int nSpace=0; while(*str!=\'\0\'){ if(*str>=\'0\' && *str<=\'9\'){ nNum++; }else if((*str>=\'a\' && *str<=\'z\')||(*str>=\'A\'&&*str<=\'Z\')){ nChar++; }else if(*str==\' \'){ nSpace++; } } printf("字母数为:%d\n",nChar); printf("空格数为:%d\n",nSpace); printf("数字数为:%d\n",nNum); printf("其他为:%d\n",nOther); } int main() { fun("123abc%^&"); return 0; }
2.说明调用动态链接库的两种方式
一、静态调用
1、添加动态库函数声明头文件
2、在.h文件中添加如下语句
#pragma comment(lib, “.\\****.lib”)
.\\****.lib为动态库的绝对路径。
就可以调用动态库中的函数了。
二、动态调用
1、定义一个与动态库函数接受参数类型和返回值均相同的函数指针类型。
typedef int (* lpAddFun)(int ,int);
lpAddFun addFun;
2、HINSTANCE hDll;
hDll=LoadLibrary(“****.dll”);//动态加载DLL模块句柄
3、得到所加载DLL模块中函数的地址
addFun=(lpAddFun) GetProcAddress(hDll,”Add_new”);
之后就可以使用
3.类的成员函数重载、覆盖、隐藏的区别
重载:(1)相同范围(在同一个类中)
(2)函数名字相同
(3)参数不同
(4)virtual关键字可有可无
覆盖:指派生类的函数覆盖基类的函数
(1)不同的范围(位于基类与派生类)
(2)函数名字相同
(3)参数相同
(4)基类函数必须有virtual关键字
隐藏:指派生类的函数屏蔽了同名的基类函数
(1)只要派生类的函数名字与基类的相同,不管参数是否相同,有没有virtual关键字,基类的函数都会被隐藏。
4.引用与指针的区别
(1)引用不能为空,指针可以
(2)指针声明时可以不用指向任何对象,但是引用不行
(3)引用不可以改变指向,指针可以
(4)引用的大小是所指向的变量的大小,因为引用只是一个别名,指针的大小是系统位数(32位系统是4个字节)
(5)引用比指针安全
5.linux系统的常见命令
(1)查看内存使用情况:/proc/meminfo;atop;free;htop;memstat;ps;vmstat;top
(2)结束进程:kill 进程ID号
(3)显示内核版本:uname -r
(4)显示日期:date
(5)显示2007年的日历表:cal 2007
(6)设置时间 月日时分年.秒:date 041217002007.00
(7)关机:shutdown -h now;重启:reboot;注销:logout
(8)文件搜索 根目录下搜索file:find / -name file;搜索根目录下.bin结尾的文件:find / -name \*.bin;寻找.ps结尾的文件:locate *.ps
(9)查看文件内容:正向查看:cat file;反向查看:tac file;查看长文件的内容:more file
(10)网络 查看一个以太网卡的配置:ifconfig eth0;启用一个“eth0”的网络设备:ifup eth0;禁用eth0网络设备:ifdown eth0;
6.数组的访问
二维数组可以通过一维数组的方式去访问,因为二维数组实际上是一维数组,只是为了让我们更好的使用才写成二维的形式,例如:int a[2][2],a[1][1]==a[3];
7.ping命令使用的是ICMP报文
8.static修饰的局部变量的存储位置
static修饰的局部变量存储在静态区域,与全局变量的存储位置相同。
9.二叉树
节点层:根节点是第一层,根的子节点是第二层,以此类推。
树的深度:树最大的节点层数。
节点的度:节点子树的个数。
树的度:树中最大的节点度。
叶子节点:也称为终端节点,度为0.
分支节点:度不为0的节点。
二叉树的性质:
(1)第i层上的节点的个数最多为2^(i-1) (i>=1)
(2)深度为k的二叉树最多有2^(k-1)个子节点 (k>=1)
(3)包含n个节点的二叉树的高度至少为(log2n)+1
(4)满二叉树:高度为h,并且由2^h-1个节点构成的二叉树。
10.互斥锁、自旋锁、读写锁
互斥锁:当一个线程要访问共享资源的之前会对这片资源进行加锁。如果加锁之后还没有释放锁,其他线程如果要访问这片资源,就会进入阻塞休眠状态,直到解锁。解锁的时候如果有多个线程阻塞,那么只有第一个变成就绪态的线程可以访问这片资源,其他线程继续阻塞休眠。
读写锁:也叫互斥共享锁,有三种状态:读加锁状态,写锁状态和不加锁状态。只有一个线程可以占用写模式的读写锁,但是有多个线程可以占用读模式的读写锁。
在读加锁状态下,任何线程都可以进行读加锁,但是无法进行写加锁,除非解锁。写加锁状态下,任何线程想要进行加锁操作都会被阻塞。
自旋锁:自旋锁和互斥锁很像,但是自旋锁访问加锁的资源时,会一直循环地查看是否释放锁,效率比较高,但是会占用cpu,还会造成死锁。
11.判断下列表达式是否正确,若正确,写出表达式中a的值。
int a=4;
A.a+=(a++) B.a+=(++a) C.(a++)+=a D.(++a)+=(a++)
解题:
A. a=a+(a++)===>a=(a+1)+a=9
B. a=a+(++a)===>a=(a+1)+(a+1)=10
C. 左值操作错误,a先运算,回来后发现a+1了
D. (a=4+1)==a+(a++)====>a=(a+1)+a=11
12.C/C++中switch的参数类型
switch的参数是一个整数类型,参数可以是int类型或者integer包装类型,由于byte,short,char类型都可隐式转换成int类型,所以这些类型的包装类型也是可以作为参数的。
结论:switch的参数类型应该是int、short、char或者byte、enum类型,long、string类型不能作为参数。
13.const char *p char const *p char *const p 三者有什么区别?
第一种:根据优先级,p先与*结合成为指针,指向char类型,char前面有个const修饰,所以指针p指向的类型为const char类型的数据。
第二种:实际上没有const *这种操作,所以const实际上还是修饰前面的,所以char const *p跟前面的const char *p是一样的。
第三种:const修饰的是p,表明这个指针p不能被修改,指向char型数据。
14.面向对象程序的优点?
(1)易维护。
采用面向对象思想设计的结构,可读性高,由于继承的存在,即使改变需求,那么维护也只是在局部模块,所以维护起来是非常方便和较低成本的。
(2)质量高
在设计时,可重用现有的,在以前的项目的领域中已被测试过的类使系统满足业务需求并具有较高的质量。
(3)效率高
在软件开发时,根据设计的需要对现实世界的事物进行抽象,产生类。使用这样的方法解决问题,接近于日常生活和自然的思考方式,势必提高软件开发的效率和质量。
(4)易扩展
由于继承、封装、多态的特性,自然设计出高内聚、低耦合的系统结构,使得系统更灵活、更容易扩展,而且成本较低。
15.写出bool、int、float、指针类型与零值的比较。
int类型:if(n==0) if(n!=0)
bool类型:由于编译器不同,对于TRUE的值也没有统一的标准,所以我们不可以直接将bool变量与 TRUE、FALSE或者1、0进行比较。
标准答案:if(flag) if(!flag)
float:由于float和double类型都有精度的限制,所以不可以直接用”==”或者”!=”与任何数字进行比较,应该设法转化成”>=”或者”<=”的形式。
标准答案:if((x>=-EPSINION)&&(x<=EPSINION))
指针类型:char *p
标准答案:if(p==NULL) if(p!=NULL)
16.静态特性、动态特性
静态特性:程序的功能在编译的时候就已经确定了。
动态特性:程序的功能是在运行的时候才确定。
17.全局变量可不可以被包含在多个头文件中?为什么?
可以。可以在不同的头文件中用static进行修饰,但是只能有一个.c文件对其进行赋值。
18.写一个函数计算long类型数据中有多少位是1.
解法:不停地除2取余,把1累加起来。
19.简述处理器中断处理的过程(中断向量、中断保护现场、中断嵌套、中断返回)
中断向量:
请求中断
中断响应
服务子程序。对于外部中断,CPU在执行当前指令的最后一个时钟周期去查询INTR引脚,若查询到中断请求信号有效,同时在系统开中断(即IF=1)的情
况下,CPU向发出中断请求的外设回送一个低电平有效的中断应答信号,作为对中断请求INTR的应答,系统自动进入中断响应周期。
保护现场
中断服务
恢复现场
中断返回
IP/EIP和CS值弹出,从而恢复主程序断点处的地址值,同时还自动恢复标志寄存器FR或EFR的内容,使CPU转到被中断的程序中继续执行
中断嵌套
20.写一个标准宏,返回两个参数中的最小值
#define MIN(A,B) ((A)>(B)?(A):(B))//A、B都要加括号是为了防止他们是表达式
21.根据变量a给出以下定义。
(1)一个整型数:int a;
(2)一个指向整型数的指针:int *a;
(3)一个指向指针的指针,它指向的指针指向一个整型数:int **a;
(4)一个有十个整型数的数组:int a[10]
(5)一个有10个指针的数组,该指针指向一个整型数:int *a[10]
(6)一个指向有10个整型数数组的指针:int (*a)[10]
(7)一个指向函数的指针,该函数有一个整形参数并且返回一个整型数:int (*a)(int)
(8)一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并且返回一个整型数:int (*a[10])(int)
22.watchdog有什么作用?
看门狗,又叫watchdog,是一个定时器电路,一般有一个输入,叫做喂狗,一个输出,正常工作的时候,每隔一段时间就会喂一次狗,如果超出规定的时间不喂狗(程序跑飞),就会发送一个复位信号让系统复位。看门狗的作用就是防止程序发生死循环,或者说程序跑飞。
23.什么函数不能被声明为虚函数?
不能被继承、不能被重写的函数就不能声明为虚函数。例如:(1)普通函数;(2)友元函数;(3)构造函数;(4)内联成员函数;(5)静态成员函数。
24.内联函数的作用是什么?
不是在调用时发生控制转移,而是在编译的时候将函数体嵌入在每一个调用处,适用于功能简单、规模小、作用频繁的函数。递归函数无法内联处理,内联函数不能有循环体,switch语句,不能进行异常接口声明。
25.内联函数与宏定义的区别
宏定义是由处理器对宏进行替代,而内联函数是通过编译器控制来实现的,内联函数是真正的函数,只是在调用的时候,内联函数会像宏一样展开。
26.一个类A中没有任何的成员变量和成员函数,sizeof(A)等于多少?
sizeof(A)=1;
编译器不允许一个类的大小为0,会为它分配1字节的内存。试想,若,不这样做,那2个类A的实例在内存中将会无法区分。一个空类对象的大小是1byte。这是被编译器安插进去的一个字节,这样就使得这个空类的两个实例得以在内存中配置独一无二的地址
27.虚函数的调用。
28.面字节跳动的后端开发时,问了(1)linux中查找内部包含某几行函数的文件,按照大小遍历文件;(2)tcp通信时的poll,epoll,select的具体内容;(3)进程与线程各种同步与互斥方式的实现;(4)二叉树的非递归遍历,可惜我只记得递归遍历。都没到算法的部分我就已经凉了,太难了呀。
(1)grep -rn \’stream\’ . –include=\’*.cpp\’;ls -lht 查看当前目录下文件的大小;ls -lhtR 递归查看当前目录下所有文件的大小;du -sh 查看当前文件夹的大小
(4)二叉树的非递归遍历
#include <stdlib.h> #include <stdbool.h> #include <stdlib.h> typedef struct BTree { char data; struct BTree *lchild; struct BTree *rchild; }BiTree; //创建一个栈--链表 typedef struct Stack { struct Btree *node; struct BTree *next; }Stack; //创建一个栈 Stack *create_node(BiTree *node) { Stack *mstack = malloc(sizeof(Stack)); mstack->node = node; mstack->next = NULL; return mstack; } //压栈 bool push_stack(Stack *top,BiTree *node) { if(top == NULL)return false; Stack *snode = create_node(node0; snode->next = top->next; top->next = snode; return true; } //出栈 BiTree *pop_stack(Stack *top) { if(top == NULL || top->next == NULL)return NULL; Stack *snode = top->next; top->next = snode->next; BiTree *tree = snode->data; free(snode); return tree; } //前序创建树 BiTree *create_tree() { char ch = \'\0\'; scanf("%c",&ch); printf("*************%c\n",ch); if(ch == \'#\') { return NULL; }else{ BiTree *root = malloc(sizeof(BiTree)); root->data = ch; root->lchild = create_tree(); root->rchild = create_tree(); return root; } } int main() { //创建树 BiTree *tree = create_tree(); //创建栈 Stack *top = create_node(NULL); //前序遍历 push_stack(top,tree); BiTree oTree = NULL; while(oTree = pop_stack(top)) { printf("%c",oTree->data); //压右子树 if(oTree->rchild != NULL) { push_stack(top,oTree->rchild); } if(oTree->lchild != NULL) { push_stack(top,oTree->lchild); } }
29.宏定义,不使用中间值交换两个变量的值。
#define swap(x,y) {x=x+y;y=x-y;x=x-y;}
30.问:用过或很熟悉的设计模式有哪些?
工厂模式,通过简单工厂生成NPC对象,简单处理的话可通过“字符串匹配”动态创建对象。如果有“反射机制”就可以直接传class来实现。当然可以进一步使用抽象工厂,处理不同的生产对象。
单例,实现全局唯一的一个对象。构造函数、静态指针都是私有的,使用前提前初始化或者加锁来保证线程安全。
Adaptor适配器,代码适配原来的相机移动最后调用的是原来的移动,现在加了适配器继承里面放了当前引擎的摄像机,然后覆盖原来摄像机的移动逻辑。
Observer,一个对象绑定多个观察者,然后这个对象一旦有消息就立刻公布给所有的观察者,观察者可以动态添加或删除。在UE4里面,行为树任务节点请求任务后进入执行状态,然后会立刻注册一个观察者observer到行为树(行为树本身就相当于前面提到的那个对象)的observer数组里面同时绑定一个代理函数。行为树tick检测消息发送给所有观察者,观察者收到消息执行代理函数。
3.类成员函数的重载、覆盖、隐藏部分摘自:https://blog.csdn.net/weibo1230123/article/details/82012779
19.中断处理部分来自:https://www.nowcoder.com/questionTerminal/2e85847c06684c2faeaf8728f72e5045?toCommentId=426573
30.设计模式部分摘自:https://blog.csdn.net/u012999985/article/details/89758786