C++面试题(基础)

2018秋招经历的一些面试题以及网上整理的面试题

一、Tcp的三次握手和四次挥手

三次握手:

四次挥手:

二、Tcp为什么要三次握手?(详情参考https://www.jianshu.com/p/e7f45779008a,或计算机网络这本书)

三次握手的目的是“为了防止已经失效的连接请求报文段突然又传到服务端,因而产生错误”,这种情况是:一端(client)A发出去的第一个连接请求报文并没有丢失,而是因为某些未知的原因在某个网络节点上发生滞留,导致延迟到连接释放以后的某个时间才到达另一端(server)B。本来这是一个早已失效的报文段,但是B收到此失效的报文之后,会误认为是A再次发出的一个新的连接请求,于是B端就向A又发出确认报文,表示同意建立连接。如果不采用“三次握手”,那么只要B端发出确认报文就会认为新的连接已经建立了,但是A端并没有发出建立连接的请求,因此不会去向B端发送数据,B端没有收到数据就会一直等待,这样B端就会白白浪费掉很多资源。如果采用“三次握手”的话就不会出现这种情况,B端收到一个过时失效的报文段之后,向A端发出确认,此时A并没有要求建立连接,所以就不会向B端发送确认,这个时候B端也能够知道连接没有建立。
 
三、TCP、UDP的区别?
TCP:传输控制协议,提供的是面向连接、可靠的字节流服务;在客户端和服务器彼此交换数据前,必须先在双方建立一条tcp连接,之后才可以传输数据;tcp能够提供超时重传,丢弃重复数据,校验数据,流量控制等功能,保证数据能从一端传到另一端。
UDP:用户数据报协议,是一个简单的面向数据报的运输层协议;UDP不提供可靠性,它只是把程序传给ip层的数据报发出去,但并不能保证能到达目的地;由于UDP再传输数据前不需要建立客户端和服务器的连接,没有超时重传机制,故传输速度很快。
 
四、二叉树相关的等式
n0=n2+1;//叶子节点=度为2的非叶子节点个数+1;
pow(2,n-1);//第n层的节点数(满二叉树)
pow(2,n)-1;到n层总共的节点数(满二叉树)
 
五、C++内存管理?
栈:存放函数参数、局部变量,由系统自动分配,执行完毕,系统自动释放;
堆:由new/malloc定义的变量,存储再堆上,由程序员手动分配和释放;
全局/静态区:存放全局变量、静态变量,程序结束时释放;
代码区:存放二进制的代码;
常量区:存放字符常量;
 
六、指针和引用的区别?
指针:存放的是一个地址,不必在定义的适合初始化,可以有多级;如char* p;char** q;
引用:跟原变量是同一个,只不过是原变量的别名,不占用内存空间;必须在定义的适合初始化;只能有一级。
 
七、各种指针
整形指针:int* p;
指向存放10个整数的数组的指针:int (*p)[10];
存放10指针变量的数组:int* p[10];
指针函数:int *p(int x); // 指针函数是指带指针的函数,即 本质是一个函数,函数返回类型是某一类型的 指针。此例子返回一个int*
函数指针:int (*p)(int x);// 函数指针是指向函数的指针变量 本质是一个指针变量。该指针指向参数为一个int,返回值为int的函数
常量指针:int const *p;  //指针的指向可以变(如 int a=8;int *q=&a;p=q;),内容不能变
指针常量:int * const p; //指向不能变,内容可以变(如*p=8;)
int ((*a)[10])(int) 一个指针指向10个存放参数类型为int,返回值类型为int的函数的数组
 
八、extern 关键字的作用?
1.与“c”连用,如extern "c" void fun,则表示编译fun函数时用c的规则区翻译
2.extern int g;声明函数或者全局变量的作用范围的关键字,其声明的函数或变量可以在本模块或者其他模块中使用。
 
九、volatile关键字的作用?
修饰变量,表明改变量的值可能会随时被外部改变,因此该变量不能被缓存到寄存器,每次使用需要重新读取。
 
十、内联函数、宏定义的区别?
内联函数:要做参数检查,在调用内联函数的时候,不是跳转到该函数,而是把代码写到执行该函数的地方;内联函数在编译的时候把代码写入;更安全可靠(相对于宏定义);如果内联函数定义在调用函数的后面,则编译器会当作普通函数调用
宏定义:预编译的时候就把所有宏名用宏体替换;没有类型,不做安全检查。
 
十一、在类构造函数中,一定要用列表初始化成员变量的情况?
1.常成员函数(const),因为只能初始化不能赋值;
2.引用类型,也不能赋值,只能在定义的时候初始化;
3.需要初始化的数据成员是一个对象(如 class A,A a,a就是一个对象)。
 
十二、explicit关键字的作用?
写在构造函数之前,如class A{
public:
  A(){}
  explicit A(int x){}
}
表示不能隐式调用其构造函数,必须显示调用,例:A* a = new a(2);//显示调用      A* a=2;//隐式调用;
 
十三、内存溢出的原因?
内存溢出是指在程序申请内存时,没有足够的内存空间使用;原因:
1.内存中加载的数据量过于庞大;2.代码中存在死循环或循环产生太多的重复的对象实体;
3.递归调用太深,导致堆栈溢出;4.内存溢出最终导致内存泄漏。
内存泄漏是指;向系统申请分配内存,但用完并没有手动释放,导致占用有效的内存;常见的情况:1.没有用delete 2.没给数组对象用delete[] 3.没有将基类析构函数定义为虚函数,关于这点,如果一个基类指针指向了派生类,如果基类的析构函数不是虚函数,则释放基类指针的内存空间的时候,只会对基类部分析构,派生类部分并不会调用析构函数,导致内存泄漏。
 
十四、字符数组和字符串
字符数组,如char a[]={’a‘,'b','c'};sizeof(a)==3
字符串,如 char str[]={"abc"}; sizeof(str)==4
字符串str比字符数组多一个’\0‘,假设a占用的空间大小为3,则字符串str占用的空间为4 (abc\0);
 
十五、a=1,b=4,c=3,d=2; 求a<b?a:c<d?c:d; 
当a<b成立时,取a,就不考虑后面的表达式,因此上式的结果为a。
 
十六、c语言标识符不能以数字开头。
 
十七、对基本有序的数组进行排序时,不能用快排,此时快排的复杂度为O(N^2),这种情况一般使用直接插入排序;
 
十八、不稳定的排序算法
不稳定是指在排序过程中相同值的数在排序后相对位置可能发生了变化;不稳定的排序有:快速排序、堆排序、选择排序、希尔排序。
 
十九、如何定义类模板?
template <typename T,class N> class A{};//在这里tyepname 、class功能都一样
 
二十、C++中的类型转换
static_cast<T*>(content):静态类型转换,可以实现c++内置基本数据类型之间的转换,如果涉及到类,只能在相互联系的类型中进行转换;
dynamic_cast<T*>(content):动态类型转换,也是向下安全转换(基类转为派生类),运行时执行;基类中一定要有虚函数。
const_cast<T*>(content):去常转换(去const),编译时执行。
reinterpret_cast<T*>(content):重解释类型转换。
 
二十一、虚函数和纯虚函数的区别?
虚函数是为了继承接口和默认行为,纯虚函数只是继承接口。
 
二十二、什么是面向对象?
面向对象是一种对现实世界理解和抽象的思想、方法;将现实世界的事物抽象成对象,现实世界中的关系抽象成类、继承,帮助人们实现对现实世界的抽象和数字建模。
 
二十三、面向对象的三个基本特征?
封装:利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体。数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。用户无需知道对象内部的细节,但可以通过对象对外提供的接口来访问该对象。
继承:可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
多态;允许将子类类型的指针赋值给父类类型的指针,多态的体现:虚函数(在运行时绑定)。(我认为重载不属于多态,重载是在编译时绑定的)
 
二十四、写出string类的构造函数、拷贝构造函数、赋值?
//private:char* data;
String (const char* str){
  if(str==nullptr){
    data=new char[1];data[0]='\0';
  }
  else{
    int len =strlen(str);
    data=new char[len+1];
    strcpy(data,str);
  }
}
 
String (const String& other){
  int len=strlen(other.data);
  data=new char[len+1];
    strcpy(data,other.data);
}
 
String& operator = (const String& other){
  if(other==this)
    return *this;
   delete[] data;
  int len=strlen(other.data);
  data=new char[len+1];
    strcpy(data,other.data);
  return *this;
}
 
二十五、不能被重载的运算符?
1. 点(.) 2.*   3. ::  4. sizeof  5. ?:
 
二十六、数组作为函数参数的时候,会退化为指针。
 
二十七、预处理包含?
1.宏定义指令 2.条件编译指令 3.头文件包含指令 4.编译程序可识别的特殊符号
 
二十八、命名空间有什么用?
命名空间作用:通过在某个命名空间定义库的名字,库的作者(以及用户)可以避免全局名字的固有限制。当应用程序用到多个库时,不可避免的会发生某些名字冲突,多个库放置在全局命名空间下将引发命名空间污染。
 
二十九、gcc下编译,默认的对齐长度时4字节,而不是最大长度,即使有double的情况下,对齐也是4字节。
 
三十、stl容器
顺序容器:1.vector:可变大小数组。支持快速随机访问;在尾部以外的地方插入或删除可能很慢;
     2.deque:双端队列,支持快速随机访问;在头尾插入删除很快;
       3.list:双向链表,支持双向访问,在任何位置插入删除都很快;
       4.forward_list:单向链表,只支持单向访问,在任何位置插入删除都很快;
       5.array:固定大小数组
       6.string:字符串,与vector相似,但用于保存字符。
关联容器:按关键字有序保存元素:1.map:关联数组,保存键值对,不允许重复,自动排序 2.set:集合,关键字即值,不允许重复,自动排序。
     按关键字无序保存元素:1.unordered_map:用哈希函数组织的map,不会自动排序 2.unordered_set:哈希函数组织的set,不会自动排序
                3.unordered_multimap:可以重复的无序的map  4.unordered_multiset:可重复的无序的set
 
三十一、数据库保护问题?
1.安全性保护 2.完整性 3. 故障恢复 4.并发控制
 
三十二、什么是进程,线程以及区别?
进程:是并发执行的程序在执行过程中分配和管理资源的基本单位,竞争计算机系统资源的基本单位,每一个进程都有自己的地址空间。
线程:是进程的一部分,一个没有线程的进程可以看作是单线程,有多个线程的进程是多线程;线程又被叫做轻量级进程,也是cpu调度的一个基本单位。
区别:进程拥有一个完整的虚拟地址空间,不依赖线程而独立存在;线程是进程的一部分,没有自己的地址空间,与进程内其他线程共享分配给该进程的所有资源。
 
三十三、进程间的通信方式?
1.管道:它是半双工的(即数据只能在一个方向上流动)具有固定的读端和写端;只能用于具有情缘关系的进程之间的通信(也就是父子进程或者兄弟进程之间)。
2.命名管道(FIFO):可以在无关进程之间交换数据
3.消息队列:消息队列是面向记录的,独立于发送与接收进程,进程中止时,消息队列及其内容并不会被删除;存放在内核中并由消息队列标识符标识;它可以实现消息的随机查询;但要注意第一次读的时候,要考虑上一次没有读完数据的问题。
4.信号量:不能传递复杂消息,一般用来同步,用来控制多个进程对资源的访问。
5.共享内存:映射一段能被其他进程访问的内存,这段共享内存由一个进程创建,但多个进程可以访问,一般和信号量共同使用,控制同步。
6.信号:时一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
 
三十四、什么是死锁?如何避免?
死锁:两个或两个以上的进程在执行过程中,由于竞争资源或者彼此通信而造成阻塞的一种现象。
预防:1.用银行家算法 2.打破互斥条件 3.打破不可抢占条件 4.打破占有且申请条件 5.打破循环等待条件
 
 
三十五、class A;
A* p=nullptr;//此时p只能调用不含成员变量的函数或者静态函数、静态变量,不能访问有成员变量的函数或者虚函数;
A* p=(A*)malloc(sizeof(A));//此时p能访问含有成员变量的函数,但不能访问虚函数。
 
三十六、调用函数时,参数压栈顺序为从右往左。 
 

猜你喜欢

转载自www.cnblogs.com/darkif/p/10092459.html