C++软件开发经典面试题目

来源:互联网 发布:知识付费行业数据 编辑:程序博客网 时间:2024/06/03 13:06

1、 Static有什么用途?

(1)函数体内static变量的作用范围是该函数体,该变量的内存只被分配一次,因此它的值在下次调用时不变;
(2)模块内的static全局变量同样只能在该模块内的函数访问和调用,不能被模块外的其他函数访问;
(3)在类中的static成员变量属于整个类所有,对类的所有对象只有一份拷贝,这个函数不接受this指针,因为只能范围类的static成员函数。

2、 const

1)不管在函数声明修饰形参、还是修饰类的成员变量,表示该成员变量不能被改变,而且通常需要进行初始化,因为之后不能再改变;
2)对于指针来说,可以修饰指针所指向的变量(在*左边,即指针指向内容为常量),也可以指定指针本身为const(在*右边,指针本身是常量),或者两者同时指定为const(都是常量)。

3、 this指针

1)this指针本质是一个函数参数,只是编译期隐藏起形式的,语法层面上的参数,且this指针只能在成员函数中使用,全局函数、静态函数都不能使用;
2)this在成员函数开始前构造,在成员结束后清楚;
3)This指针不占用对象的空间。

4、 ifndef/define/endif的作用

防止头文件被重复引用和定义;

5、 C和C++的区别

1)C主要面向过程,C++面向对象;
2)C是一种结构化语言,重点在于算法和数据结构。C主要考虑通过一个过程将输入进行各种运算后得到输出,C++主要考虑的是如何构造一个对象模型,契合与之相对应的问题域,这样就可以通过获得对象的状态信息得到输出。

6、 C++函数值传递的方式

值传递、指针传递和引用传递

7、 extern “C”的作用

实现C和C++的混合编程;
因为函数被C++编译后在湖中的名字会变长,与C生成的不一致,造成C++不能直接调用C函数。

8、 struct 和class 的区别

1)struct的成员默认是公有的,而类的程园默认是私有的;
2)C中的struct不能包含成员函数,C++中的class可以包含成员函数。

9、 new和malloc

1)都可用来申请动态内存和释放内存,都是在堆(heap)上进行动态的内存操作。
2)malloc和free是c语言的标准库函数,new/delete是C++的运算符。
3)new会自动调用对象的构造函数,delete 会调用对象的析构函数, 而malloc返回的都是void指针。

10、 heap与stack(堆与栈)的差别

1)heap是堆,stack是栈;
2)stack的空间由操作系统自动分配和释放,存放函数的参数值、 局部变量的值等。heap上的空间一般由程序员分配和释放,并要指明大小;
3)栈空间有限而且是一块连续的内存区域,堆是很大的自由存储区;
4)C中的malloc函数分配的内存空间就是在堆上,C++是new;
5)程序在编译期对变量和函数分配内存都在栈上进行,且程序运行过程中函数调用时的参数传递也在栈上进行。

堆栈溢出原因

数组越界, 没有回收内存, 深层次递归调用

11、 Vector、list和deque的区别

Vector:表示一段连续的内存区域,每个元素被顺序存储在这段内存中,对vector的随机访问效率很高,但对非末尾元素的插入和删除则效率非常低。
list:表示非连续的内存区域并通过一对指向首尾元素的指针双向链接起来,插入删除效率高,随机访问效率低。
deque:也表示一段连续的内存区域,但与vector不同的是它支持高效地在其首部插入和删除元素,它通过两级数组结构来实现,一级表示实际的容器,第二级指向容器的首和尾。

12、 Vector和list的区别

vector拥有一段连续的内存空间,能很好的支持随机存取,因此vector::iterator支持“+”,“+=”,“<”等操作符。
list的内存空间可以是不连续,它不支持随机访问,因此list::iterator则不支持“+”、“+=”、“<”等
vector::iterator和list::iterator都重载了“++”运算符。
总之,如果需要高效的随机存取,而不在乎插入和删除的效率,使用vector;如果需要大量的插入和删除,而不关心随机存取,则应使用list。

13、 内联函数和宏的差别

内联函数和普通函数相比可以加快程序运行的速度,因为不需要中断调用,在编译的时候内敛函数可以直接被镶嵌到目标代码中。而宏只是一个简单的替换。
内联函数要做参数类型检查,这是内联函数的优势;
inline是指嵌入代码,就是在调用函数的地方不是跳转,而是把代码直接写到那里去。
对于短小的代码来说inline增加空间消耗换来的是效率提高,这方面和宏是一模一样的,但是inline在和宏相比没有付出任何额外代价的情况下更安全。 至于是否需要inline函数,就需要根据实际情况来取舍了。

inline一般只用于如下情况:

(1)一个函数不断被重复调用。
(2)函数只有简单的几行,且函数内不包含for、 while、 switch语句。
一般来说,我们写小程序没有必要定义成inline,但是如果要完成一个工程项目,当一个简单函数被调用多次时,则应该考虑用inline。
宏在C语言里极其重要,而在C++里用得就少多了。 关于宏的第一规则是绝不应该去使用它,除非你不得不这样做。 几乎每个宏都表明了程序设计语言里、 程序里或者程序员的一个缺陷,因为它将在编译器看到程序的正文之前重新摆布这些正文。 宏也是许多程序设计工具的主要麻烦。 所以,如果你使用了宏,就应该准备只能从各种工具(如排错系统、 交叉引用系统、 轮廓程序等)中得到较少的服务。
宏是在代码处不加任何验证的简单替代,而内联函数是将代码直接插入调用处,而减少了普通函数调用时的资源消耗。
宏不是函数,只是在编译前(编译预处理阶段)将程序中有关字符串替换成宏体。
关键字inline必须与函数定义体放在一起才能使函数成为内联,仅将inline放在函数声明前面不起任何作用。
inline是一种“用于实现的关键字”,而不是一种“用于声明的关键字”。 内联能提高函数的执行效率,至于为什么不把所有的函数都定义成内联函数?如果所有的函数都是内联函数,还用得着“内联”这个关键字吗?内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。 如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。

以下情况不宜使用内联:

1 如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。 2 如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。 类的构造函数和析构函数容易让人误解成使用内联更有效。 要当心构造函数和析构函数可能会隐藏一些行为,如“偷偷地”执行了基类或成员对象的构造函数和析构函数。 所以不要随便地将构造函数和析构函数的定义体放在类声明中。 一个好的编译器将会根据函数的定义体,自动地取消不值得的内联(这进一步说明了inline不应该出现在函数的声明中)。

14、 引用和指针的区别

1)指针是一个变量,存放地址的变量,指向内存的一个存储单元,引用仅是别名;
2)引用必须初始化,指针不必;
3)不存在指向空值的引用,但是存在指向空值的指针;
4)sizeof引用对象得到的是所指对象,变量的大小,sizeof指针得到是指针本身的大小;
5)内存分配上,程序为指针分配内存,不用为引用分配内存。

15、 数组和链表的区别

C++语言中可以用数组处理一组数据类型相同的数据,但在使用数组之前必须确定数组的大小。而在实际应用中,用户使用数组之前有时无法准确确定数组的大小,只能将数组定义成足够大小,这样数组中有些空间可能不被使用,从而造成内存空间的浪费。
链表是一种常见的数据组织形式,它采用动态分配内存的形式实现。需要时可以用new分配内存空间,不需要时用delete将已分配的空间释放,不会造成内存空间的浪费。

从逻辑结构来看:

数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况,即数组的大小一旦定义就不能改变。当数据增加时,可能超出原先 定义的元素个数;当数据减少时,造成内存浪费;
链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。(数组中插入、删除数据项时,需要移动其它数据项)。

从内存存储来看:

静态数组从栈中分配空间(用NEW创建的在堆中),对于程序员方便快速,但是自由度小;
链表从堆中分配空间, 自由度大,但是申请管理比较麻烦。

从访问方式来看:

数组在内存中是连续存储的,因此,可以利用下标索引进行随机访问;
链表是链式存储结构,在访问元素的时候只能通过线性的方式由前到后顺序访问,所以访问效率比数组要低。

16、 链表

链表是否有环

算法的思想是使用追赶的方法设定两个指针p, q,其中p每次向前移动一步,q每次向前移动两步。那么如果单链表存在环,则p和q相遇;否则q将首先遇到null退出。

如何知道环的长度

记录下上个问题的碰撞点p,slow、fast从该点开始,再次碰撞所走过的操作数就是环的长度s。

如何找出环的连接点

有定理:碰撞点p到连接点的距离=头指针到连接点的距离,因此,分别从碰撞点、头指针开始走,相遇的那个点就是连接点。

带环链表的长度

根据已经求出连接点距离头指针的长度,加上求出的环的长度,二者之和就是带环单链表的长度

单链表的逆置

单链表模型

17、 重载和重写(覆盖)的区别

从定义来说:
重载:是指存在多个重名函数,而这些函数的参数表不同(参数个数,类型不同);
重写:是指子类重新定义父类虚函数的方法。
从实现原理上来说:
重载:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数。而函数的调用,在编译时就已经确定是静态的,也就是说它们的地址在编译期就绑定(早绑定),因此重载与多态无关。
重写:和多态有关。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态地调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,它们的地址是在运行期绑定的(晚绑定)。

18、 封装、继承、多态、虚函数

封装

封装是实现面向对象程序设计的第一步,封装就是将数据或函数等集合在一个个的单元(类)中。其意义在于保护或者放着代码被无意中破坏

继承

继承主要实现重用代码,扩展已存在的代码,节省开发时间。子类可以继承父类的一些东西。

多态

定义:“一个接口,多种方法”,程序运行时才决定调用的函数
实现:C++多态主要是通过虚函数实现。虚函数允许子类重写。
目的:封装可以使代码模块化,继承可以扩展已存在的代码,它们的目的都是为了代码重用。而多态的目的是为了接口重用,将接口与实现分离。

多态的基础是继承,需要虚函数的支持。子类继承父类大部分的资源,不能继承的有构造函数,析构函数,拷贝构造函数, operator=函数,友元函数等等

虚函数

定义:被virtual关键字修饰的成员函数,就是虚函数。其作用就是实现多态性。

为什么虚函数效率低?

因为虚函数需要一次间接的寻址,而一般的函数可以在编译时定位到函数的地址,虚函数(动态类型调用)要根据某个指针定位到函数的地址。多增加了一个过程,效率虽然会低一些,但带来了运行时的多态。

纯虚函数

为什么要用纯虚函数?

在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。为了解决这个问题,方便使用类的多态性,引入了纯虚函数的概念,将函数定义为纯虚函数。则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。

使用纯虚数的情况:

1)当想在基类中抽象出一个方法,且该基类只做能被继承,而不能被实例化;
2)这个方法必须在派生类(derived class)中被实现;
虚函数与纯虚函数的区别
虚函数是为了重载和多态。在基类中是有定义的,即便定义为空。在子类中可以重写。
纯虚函数在基类中没有定义,必须在子类中加以实现。

多态的基础是继承,需要虚函数的支持。子类继承父类大部分的资源,不能继承的有构造函数,析构函数,拷贝构造函数, operator=函数,友元函数等等。

19、 内存

内存类别

栈 –由编译器自动分配释放, 局部遍历存放位置
堆 –由程序员分配和释放.
全局区(静态区) –全局变量和静态变量的存储是放在一起的, 初始化的全局变量和static静态变量在一块区域.
程序代码区 –存放二进制代码.

内存分配方式

静态存储区,程序编译时便分好, 整个运行期间都存在,比如全局变量,常量;
栈上分配;
堆上分配。

内存泄漏

原因:动态分配的内存没有手动释放完全。
避免:使用的时候应记得指针的长度; 分配多少内存应记得释放多少, 保证一一对应的关系; 动态分配内存的指针最好不要再次赋值.

20、 进程和线程的差别

进程是最小的分配资源单位,线程是最小的执行单位。
进程是程序的一次执行。线程可以理解为进程中执行的一段程序片段。 在一个多任务环境中下面的概念可以帮助我们理解两者间的差别。
进程间是独立的,这表现在内存空间、 上下文环境上;线程运行在进程空间内。 一般来讲(不使用特殊技术),进程无法突破进程边界存取其他进程内的存储空间;而线程由于处于进程空间内,所以同一进程所产生的线程共享同一内存空间。
同一进程中的两段代码不能够同时执行,除非引入线程。
线程是属于进程的,当进程退出时该进程所产生的线程都会被强制退出并清除。 线程占用的资源要少于进程所占用的资源。 进程和线程都可以有优先级。

21、 TCP和UDP

TCP是传输控制协议,提供的是面向连接、 可靠的字节流服务。 当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。 TCP提供超时重发、 丢弃重复数据、 检验数据、 流量控制等功能,保证数据能从一端传到另一端。
UDP是用户数据报协议,是一个简单的面向数据报的运输层协议。UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不保证它们能到达目的地。 由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快。

22、 编写Socket套接字

Socket相当于进行网络通信两端的插座,只要对方的Socket和自己的Socket有通信联接,双方就可以发送和接收数据了。 如果你要编写的是一个服务程序,那么先调用socket()创建一个套接字,调用bind()绑定IP地址和端口,然后启动一个死循环,循环中调用accept()接受连接。对于每个接受的连接,可以启动多线程方式进行处理,在线程中调用send()、 recv()发送和接收数据。
如果你要编写的是一个客户端程序,那么就简单多了。 先调用socket()创建一个套接字,然后调用connect()连接服务器,之后就是调用send()、 recv()发送和接收数据了。

服务器端程序编写:

(1)调用ServerSocket(int port)创建一个服务器端套接字,并绑定到指定端口上。
(2)调用accept(),监听连接请求,则接收连接,返回通信套接字。
(3)调用Socket类的getOutStream()和getInputStream获取输出流和输入流,开始网络数据的发送和接收。
(4)关闭通信套接字.Socket.close()。

客户端程序编写:

(1)调用Socket()创建一个流套接字,并连接到服务器端。
(2)调用Socket类的getOutputStream()和fetInputStream获取输出流和输入流,开始网络数据的发送和接收。
(3)关闭通信套接字.Socket.close()。

23、 三次握手和四次挥手

1、建立连接协议(三次握手)

(1)客户端发送一个带SYN标志的TCP报文到服务器。这是三次握手过程中的报文1。
(2)服务器端回应客户端的,这是三次握手中的第2个报文,这个报文同时带ACK标志和SYN标志。因此它表示对刚才客户端SYN报文的回应;同时又标志SYN给客户端,询问客户端是否准备好进行数据通讯。
(3)客户必须再次回应服务段一个ACK报文,这是报文段3。

2、连接终止协议(四次握手)

   由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
(1) TCP客户端发送一个FIN,用来关闭客户到服务器的数据传送(报文段4)。
(2) 服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1(报文段5)。和SYN一样,一个FIN将占用一个序号。
(3) 服务器关闭客户端的连接,发送一个FIN给客户端(报文段6)。
(4) 客户段发回ACK报文确认,并将确认序号设置为收到序号加1(报文段7)。

24、 排序

http://www.cnblogs.com/eniac12/p/5329396.html#s3
http://www.cnblogs.com/eniac12/p/5332117.html

25、 linux基本命令

http://www.cnblogs.com/laov/p/3541414.html#grep

26、 gdb调试

http://www.jb51.net/article/36393.htm

参考:
http://www.cnblogs.com/bozhicheng/p/6259784.html
程序员面试宝典