C++面精彩试题汇总情况.docx
- 文档编号:4856494
- 上传时间:2022-12-10
- 格式:DOCX
- 页数:13
- 大小:43.97KB
C++面精彩试题汇总情况.docx
《C++面精彩试题汇总情况.docx》由会员分享,可在线阅读,更多相关《C++面精彩试题汇总情况.docx(13页珍藏版)》请在冰豆网上搜索。
C++面精彩试题汇总情况
C++知识点整理
空指针与野指针的区别:
空指针也就是通常指向为NULL的指针,野指针就是指向一块未知的内存区域(可以是通过malloc或new申请空间后,释放后没有将指针置为空),也有可能定义了一个指针没有初始化,由于内存空间中的值在未赋值之前是随机数,所以也有可能诞生野指针。
malloc函数为C语言中的标准函数,标准中规定:
在分配内存失败时会返回“NULLPointer”空指针,而非为初始化的指针。
C++在分配内存失败时会抛出BAD_ALLOC异常。
野指针:
指向垃圾内存的指针,而非空指针。
野指针产生原因:
1.声明的指针未被初始化,指针默认值随机产生。
创建指针应该将其初始化为NULL或者指向某一内存。
2.free和delete掉的指针未重置为NULL,free后的指针仍指向该内存,但该内存已变为垃圾内存。
另:
空指针不指向任何实际的对象或函数,反过来说对象或函数的指针也不可能为空指针。
,auto是默认类型,每次调用sum函数时auto类型的变量重新赋值为0,static是静态变量,如果在函数内部进行定义,则只在第一次调用时进行赋初值,其作用范围是sum函数内部,在函数内部可以改静态变量的值;
先说宏和函数的区别:
1.宏做的是简单的字符串替换(注意是字符串的替换,不是其他类型参数的替换),而函数的参数的传递,参数是有数据类型的,可以是各种各样的类型.
2.宏的参数替换是不经计算而直接处理的,而函数调用是将实参的值传递给形参,既然说是值,自然是计算得来的.
3.宏在编译之前进行,即先用宏体替换宏名,然后再编译的,而函数显然是编译之后,在执行时,才调用的.因此,宏占用的是编译的时间,而函数占用的是执行时的时间.
4.宏的参数是不占内存空间的,因为只是做字符串的替换,而函数调用时的参数传递则是具体变量之间的信息传递,形参作为函数的局部变量,显然是占用内存的.
5.函数的调用是需要付出一定的时空开销的,因为系统在调用函数时,要保留现场,然后转入被调用函数去执行,调用完,再返回主调函数,此时再恢复现场,这些操作,显然在宏中是没有的.
内联函数与宏的区别:
1.内联函数在运行时可调试,而宏定义不可以;
2.编译器会对内联函数的参数类型做安全检查或自动类型转换(同普通函数),而宏定义则不会;
3.内联函数可以访问类的成员变量,宏定义则不能;
4.在类中声明同时定义的成员函数,自动转化为内联函数。
external作用之一:
当它与“C”一起连用时,如:
extern“C”voidfun(inta,intb);会告诉C++编译器在编译fun这个函数名是按着C的规则去翻译相应的函数名而不是C++的,C++的规则在翻译这个函数名时会把fun这个名字变得面目全非。
。
。
回调函数就是一个通过函数指针调用的函数。
如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
回调函数不是由该函数的实现方法直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应
回调函数不能是特定的类成员函数。
因为类成员函数含有this指针。
核心就是类成员函数需要this指针访问函数,而全局或者静态函数不需要this指针。
简言之,类的成员函数需要隐含的this指针 而回调函数没有办法提供。
int**a[3][4],则变量占有的内存空间为每个元素存放的都是二级指针,每个指针在32位系统下占4个字节的内存,共3*4=12个元素,则共占内存12*4=48字节
C++中使用模板类的原因:
(1)可用来创建动态增长和减小的数据结构;
(2)它是类型无关的,因此具有很高的可利用性;
(3)它在编译时检查数据类型,保证了类型安全;
(4)它是平台无关的,具有可移植性;
(5)可用于基本数据类型。
A.由于编译后的名字不同,C++程序不能直接调用C函数
B.extern"C"既可以修饰函数也可以修饰变量
C.c++提供关键字extern“C”,被extern"C"修饰的是按照C语言方式编译和连接的
它是ifnotdefine的简写,是宏定义的一种,实际上确切的说,这应该是预处理功能三种(宏定义、文件包含、条件编译)中的一种----条件编译。
在c语言中,对同一个变量或者函数进行多次声明是不会报错的。
所以如果h文件里只是进行了声明工作,即使不使用#ifndef宏定义,多个c文件包含同一个h文件也不会报错。
但是在c++语言中,#ifdef的作用域只是在单个文件中。
所以如果h文件里定义了全局变量,即使采用#ifdef宏定义,多个c文件包含同一个h文件还是会出现全局变量重定义的错误。
使用#ifndef可以避免下面这种错误:
如果在h文件中定义了全局变量,一个c文件包含同一个h文件多次,如果不加#ifndef宏定义,会出现变量重复定义的错误;如果加了#ifndef,则不会出现这种错误。
根据语言定义,在指针上下文中的常数0会在编译时转换为空指针。
也就是说,在初始化、赋值或比较的时候,
如果一边是指针类型的值或表达式,编译器可以确定另一边的常数0为空指针并生成正确的空指针值。
因此下边的代码段完全合法:
char*p=0;
if(p!
=0)
然而,传入函数的参数不一定被当作指针环境,因而编译器可能不能识别未加修饰的0“表示”指针。
在函数调用的上下文中生成空指针需要明确的类型转换,强制把0看作指针。
例如,Unix系统调用execl接受变长的以空指针结束的字符指针参数。
它应该如下正确调用:
execl("/bin/sh","sh","-c","date",(char*)0);
如果省略最后一个参数的(char*)转换,则编译器无从知道这是一个空指针,从而当作一个0传入。
(注意很多Unix手册在这个例子上都弄错了。
)
堆和栈
A.堆的大小仅受操作系统的限制,栈的大小一般一般较小
B.在堆上频繁的调用new/delete容易产生内存碎片,栈没有这个问题
A.堆和栈都可以动态分配
所谓动态内存分配就是指在程序运行的过程中动态地分配或者回收存储空间的分配内存的方法。
动态内存分配不象数组等静态内存分配方法那样需要预先分配存储空间,而是由系统根据程序的需要即时分配,且分配的大小就是程序要求的大小。
从以上动、静态内存分配比较可以知道动态内存分配相对于静态内存分配的特点:
1、不需要预先分配存储空间;
2、分配的空间可以根据程序的需要扩大或缩小。
一是时间不同。
静态分配发生在程序编译和连接的时候。
动态分配则发生在程序调入和执行的时候。
二是空间不同。
堆都是动态分配的,没有静态分配的堆。
栈有2种分配方式:
静态分配和动态分配。
静态分配是编译器完成的,比如局部变量的分配。
动态分配由函数alloca()进行分配。
不过栈的动态分配和堆不同,他的动态分配是由编译器进行释放,无需我们手工实现。
对于一个进程的内存空间而言,可以在逻辑上分成3个部份:
代码区,静态数据区和动态数据区。
动态数据区一般就是“堆栈”。
“栈(stack)”和“堆(heap)”是两种不同的动态数据区,栈是一种线性结构,堆是一种链式结构。
进程的每个线程都有私有的“栈”,所以每个线程虽然代码一样,但本地变量的数据都是互不干扰。
一个堆栈可以通过“基地址”和“栈顶”地址来描述。
全局变量和静态变量分配在静态数据区,本地变量分配在动态数据区,即堆栈中。
程序通过堆栈的基地址和偏移量来访问本地变量。
一般,用static修饰的变量,全局变量位于静态数据区。
函数调用过程中的参数,返回地址,EBP和局部变量都采用栈的方式存放。
内存管理常用区间和区间中的数据
静态区(static):
存放(初始化的)全局变量、静态变量和(未初始化的)全局变量和静态变量;
栈区(stack):
存放局部变量和函数的形参。
栈中的内存空间由编译器自动申请和释放。
堆区(heap):
存放动态分配内存函数申请的变量。
堆中的内存空间需要程序员手动释放,否则会引发内存泄露。
5.分配效率:
栈的效率比较高。
堆的效率比栈要低得多。
6.增长方向:
堆的增长方向是从程序低地址到高地址向上增长,而栈的增长方向刚好相反(实际情况可能不是这样的,与CPU的体系结构有关)
堆是不连续的,生长方向是向上的,即向着内存地址增大的方向增长;栈是连续的,生长方向是向下的,即向着内存地址减小的方向增长。
栈:
在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。
这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。
因此,能从栈获得的空间较小。
堆:
堆是向高地址扩展的数据结构,是不连续的内存区域。
这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。
阐述C++STL中vector,set,multiset,list,map,multimap,deque的特点.
vector和built-in数组类似,它拥有一段连续的内存空间,并且起始地址不变,因此
它能非常好的支持随即存取,即[]操作符,但由于它的内存空间是连续的,所以在中间
进行插入和删除会造成内存块的拷贝,另外,当该数组后的内存空间不够时,需要重新
申请一块足够大的内存并进行内存的拷贝。
这些都大大影响了vector的效率。
list就是数据结构中的双向链表(根据sgi stl源代码),因此它的内存空间可以是不连续
的,通过指针来进行数据的访问,这个特点使得它的随即存取变的非常没有效率,因此它
没有提供[]操作符的重载。
但由于链表的特点,它可以以很好的效率支持任意地方的删除
和插入。
deque是一个double-ended queue,它的具体实现不太清楚,但知道它具有以下两个特点:
它支持[]操作符,也就是支持随即存取,并且和vector的效率相差无几,它支持在两端的
操作:
push_back,push_front,pop_back,pop_front等,并且在两端操作上与list的效率
也差不多。
Map是STL的一个关联容器,它提供一对一(其中第一个可以称为关键字,每个关键字只能在map中出现一次,第二个可能称为该关键字的值)的数据处理能力,由于这个特性map内部的实现自建一颗红黑树(一种非严格意义上的平衡二叉树),这颗树具有对数据自动排序的功能。
set是集合,set中不会包含重复的元素,这是和vector的第一个区别,第二个区别是set内部用平衡二叉树实现,便于元素查找,而vector是使用连续内存存储,便于随机存取。
因此在实际使用时,如何选择这几个容器中哪一个,应根据你的需要而定,一般应遵循下面的原则:
1、如果你需要高效的随即存取,而不在乎插入和删除的效率,使用vector
2、如果你需要大量的插入和删除,而不关心随即存取,则应使用list
3、如果你需要随即存取,而且关心两端数据的插入和删除,则应使用deque。
4、如果你要存储一个数据字典,并要求方便地根据key找value,那么map是较好的选择
5、如果你要查找一个元素是否在某集合内存中,则使用set存储这个集合比较好
∙vector,译为“向量”,可以理解为线性代数中的向量,也可以理解为动态数组。
为了实现动态,有的编译器会默认为vector生成一个长度,当插入的元素的个数大于该长度时,就会生成一个长度为原长度二倍的数组,并将原来数组的数据拷贝在新数组中。
插入操作的平均复杂度为O
(1).因此,插入操作有可能使前面获取的迭代器失效。
∙deque,有双向(de)队列(queue)的意味,是可实现双向插入数据的动态数组。
为了实现双向插入的功能,编译器一般会把其数据结构设置成一段一段的数组,每段数组链接起来构成一个超级大链表,然后为之重载数组的接口。
如果前面的数组填满了,就会在大链表的前面生成一段数组,如果后面的数组填满了,也一样。
因此,插入操作一般不会影响前面获取的迭代器。
∙list:
双向链表。
有的编译器底层用环形链表来实现的。
插入操作一般不会影响已获取的迭代器。
∙set,multiset:
分别实现的是无重复元素的集合和有重复元素的集合。
一般由红黑树实现
∙map,multimap:
分别实现的是无重复元素的映射关系,和有重复元素的映射关系。
一般由红黑树实现。
进程和线程
1.定义
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
2.关系
一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.
相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。
3.区别
进程和线程的主要差别在于它们是不同的操作系统资源管理方式。
进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。
线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。
但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
1) 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
2) 线程的划分尺度小于进程,使得多线程程序的并发性高。
3) 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
4) 线程在执行过程中与进程还是有区别的。
每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。
但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
5) 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。
但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。
这就是进程和线程的重要区别。
4.优缺点
线程和进程在使用上各有优缺点:
线程执行开销小,但不利于资源的管理和保护;而进程正相反。
同时,线程适合于在SMP机器上运行,而进程则可以跨机器迁移。
并行
计算机操作系统中的并行,指的是同时存在于内存中的多道作业都处于运行状态。
实际上都是宏观上并行,微观上串行,因为这些作业都是开始各自的运行,但都没运行完毕,只是交替地使用cpu。
在操作系统中是指,一组程序按独立异步的速度执行,不等于时间上的重叠(同一个时刻发生)。
要区别并发。
并发是指:
在同一个时间段内,两个或多个程序执行,有时间上的重叠(宏观上是同时,微观上仍是顺序执行)。
并行也指8位数据同时通过并行线进行传送,这样数据传送速度大大提高,但并行传送的线路长度受到限制,因为长度增加,干扰就会增加,数据也就容易出错。
并发
在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。
在关系数据库中,允许多个用户同时访问和更改共享数据的进程。
SQLServer使用锁定以允许多个用户同时访问和更改共享数据而彼此之间不发生冲突。
操作系统并发程序执行的特点:
并发环境下,由于程序的封闭性被打破,出现了新的特点:
①程序与计算不再一一对应,一个程序副本可以有多个计算
②并发程序之间有相互制约关系,直接制约体现为一个程序需要另一个程序的计算结果,间接制约体现为多个程序竞争某一资源,如处理机、缓冲区等。
③并发程序在执行中是走走停停,断续推进的。
并发和并行的区别和联系
“并行”是指无论从微观还是宏观,二者都是一起执行的,就好像两个人各拿一把铁锨在挖坑,一小时后,每人一个大坑。
而“并发”在微观上不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行,从宏观外来看,好像是这些进程都在执行,这就好像两个人用同一把铁锨,轮流挖坑,一小时后,两个人各挖一个小一点的坑,要想挖两个大一点得坑,一定会用两个小时。
从以上本质不难看出,“并发”执行,在多个进程存在资源冲突时,并没有从根本提高执行效率
并发,是在同一个cpu上同时(不是真正的同时,而是看来是同时,因为cpu要在多个程序间切换)运行多个程序。
并行,是每个cpu运行一个程序。
打个比方,并发,就像一个人(cpu)喂2个孩子(程序),轮换着每人喂一口,表面上两个孩子都在吃饭。
并行,就是2个人喂2个孩子,两个孩子也同时在吃饭。
并发和并行是即相似又有区别的两个概念,并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔内发生。
在多道程序环境下,并发性是指在一段时间内宏观上有多个程序在同时运行,但在单处理机系统中,每一时刻却仅能有一道程序执行,故微观上这些程序只能是分时地交替执行。
倘若在计算机系统中有多个处理机,则这些可以并发执行的程序便可被分配到多个处理机上,实现并行执行,即利用每个处理机来处理一个可并发执行的程序,这样,多个程序便可以同时执行。
同步
同步就是协同步调,按预定的先后次序进行运行。
如:
你说完,我再说。
“同”字从字面上容易理解为一起动作。
其实不是,“同”字应是指协同、协助、互相配合。
如进程、线程同步,可理解为进程或线程A和B一块配合,A执行到一定程度时要依靠B的某个结果,于是停下来,示意B运行;B依言执行,再将结果给A;A再继续操作。
互斥
两个或两个以上的进程,不能同时进入关于同一组共享变量的临界区域,否则可能发生与时间有关的错误,这种现象被称作进程互斥.
链地址法:
将哈希值相同的元素用链表进行相连
线性探测再散列法:
冲突后依次向下循环查找空位进行放置
BC为哈希值构造方法,并非解决冲突方法
哈希函数的构造方法:
①数字分析法
②平方取中法
③除留取余法
④分段叠加法
处理冲突的方法:
①开放地址法(包括线性探测法、二次探测法、伪随机探测法)
②链地址法
树、森林和二叉树的转换
树转换为二叉树
1)加线。
在所有兄弟结点之间加一条连线。
(2)去线。
树中的每个结点,只保留它与第一个孩子结点的连线,删除它与其它孩子结点之间的连线。
(3)层次调整。
以树的根节点为轴心,将整棵树顺时针旋转一定角度,使之结构层次分明。
(注意第一个孩子是结点的左孩子,兄弟转换过来的孩子是结点的右孩子
森林转换为二叉树
(1)把每棵树转换为二叉树。
(2)第一棵二叉树不动,从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树的根结点的右孩子,用线连接起来。
二叉树转换为树
是树转换为二叉树的逆过程。
(1)加线。
若某结点X的左孩子结点存在,则将这个左孩子的右孩子结点、右孩子的右孩子结点、右孩子的右孩子的右孩子结点…,都作为结点X的孩子。
将结点X与这些右孩子结点用线连接起来。
(2)去线。
删除原二叉树中所有结点与其右孩子结点的连线。
(3)层次调整。
二叉排序树
二叉排序树又称“二叉查找树”、“二叉搜索树”。
二叉排序树:
或者是一棵空树,或者是具有下列性质的二叉树:
1.若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
2.若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
3.它的左、右子树也分别为二叉排序树。
二叉排序树通常采用二叉链表作为存储结构。
中序遍历二叉排序树可得到一个依据关键字的有序序列,一个无序序列可以通过构造一棵二叉排序树变成一个有序序列,构造树的过程即是对无序序列进行排序的过程。
每次插入的新的结点都是二叉排序树上新的叶子结点,在进行插入操作时,不必移动其它结点,只需改动某个结点的指针,由空变为非空即可。
搜索、插入、删除的时间复杂度等于树高,期望O(logn),最坏O(n)(数列有序,树退化成线性表,如右斜树)。
虽然二叉排序树的最坏效率是O(n),但它支持动态查找,且有很多改进版的二叉排序树可以使树高为O(logn),如AVL、红黑树等。
二元排序树的查找算法
在二元排序树b中查找x的过程为:
1.若b是空树,则搜索失败,否则:
2.若x等于b的根节点的数据域之值,则查找成功;否则:
3.若x小于b的根节点的数据域之值,则搜索左子树;否则:
4.查找右子树。
二叉排序树的删除算法
在二叉排序树中删去一个结点,分三种情况讨论:
1.若*p结点为叶子结点,即PL(左子树)和PR(右子树)均为空树。
由于删去叶子结点不破坏整棵树的结构,则只需修改其双亲结点的指针即可。
2.若*p结点只有左子树PL或右子树PR,此时只要令PL或PR直接成为其双亲结点*f的左子树(当*p是左子树)或右子树(当*p是右子树)即可,作此修改也不破坏二叉排序树的特性。
3.若*p结点的左子树和右子树均不空。
在删去*p之后,为保持其它元素之间的相对位置不变,可按中序遍历保持有序进行调整。
比较好的做法是,找到*p的直接前驱(或直接后继)*s,用*s来替换结点*p,然后再删除结点*s。
一、原反补码 1.设X=21
X= +0010101 Y=-0010101
原码:
正数的原码:
符号为0,数值部分不变[X]原=00010101
负数的原码:
符号为1,数值部分不变[Y]原=10010101
反码:
正数的反码等于原码 [X]反=00010101
负数的反码:
符号为1,数值部分按位取反 [Y]反=11101010
补码:
正数的补码等于原码 [X]补=00010101
负数的补码:
符号为1,数值部分按位取反,末位加1 —— 负数的补码等它的反码加1
[Y]补=11101011
2.和的补码等于补码的和
差的补码等于被减数的补码加上减数取负后的补码
移码(增码)是机器数的另一种表示方法,最适合表示浮点数的阶码
3.定点整数的取值范围
原反码 -(2n-1-1) 到 2n-1-1
补移码 -2n-1到 2n-1-1
1、谈谈你对面向对象编程的认识
面向对象编程强调抽象、封装、继承、多态
抽象:
我们在定义一个抽象类的时候,实际上就是把一类事物共有的属性和行为提取出来,形成一个物理模型(模版),这种研究问题的方法称为抽象。
你可以这样来想,抽象就是一个类的最基础的东西,比方说人,他的抽象类可能就是都从母体出来,有皮肤。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- C+ 精彩 试题 汇总 情况