首页 > 代码库 > C++及数据结构笔试面试常见知识点总结
C++及数据结构笔试面试常见知识点总结
一些常考的基础知识点个人总结,大神勿喷,欢迎指正。
1.广义表的表尾是指除去表头后剩下的元素组成的表,表头可以为表或单元素值.表尾或为表,或为空表。
2.构造函数不能声明为虚函数。
构造函数为什么不能是虚函数?
1. 从存储空间角度,虚函数对应一个指向vtable虚函数表的指针,这大家都知道,可是这个指向vtable的指针其实是存储在对象的内存空间的。问题出来了,如果构造函数是虚的,就需要通过 vtable来调用,可是对象还没有实例化,也就是内存空间还没有,怎么找vtable呢?所以构造函数不能是虚函数。
2. 从使用角度,虚函数主要用于在信息不全的情况下,能使重载的函数得到对应的调用。构造函数本身就是要初始化实例,那使用虚函数也没有实际意义呀。所以构造函数没有必要是虚函数。虚函数的作用在于通过父类的指针或者引用来调用它的时候能够变成调用子类的那个成员函数。而构造函数是在创建对象时自动调用的,不可能通过父类的指针或者引用去调用,因此也就规定构造函数不能是虚函数。
3. 构造函数不需要是虚函数,也不允许是虚函数,因为创建一个对象时我们总是要明确指定对象的类型,尽管我们可能通过基类的指针或引用去访问它但析构却不一定,我们往往通过基类的指针来销毁对象。这时候如果析构函数不是虚函数,就不能正确识别对象类型从而不能正确调用析构函数。
4. 从实现上看,vbtl在构造函数调用后才建立,因而构造函数不可能成为虚函数从实际含义上看,在调用构造函数时还不能确定对象的真实类型(因为子类会调父类的构造函数);而且构造函数的作用是提供初始化,在对象生命期只执行一次,不是对象的动态行为,也没有必要成为虚函数。
5. 当一个构造函数被调用时,它做的首要的事情之一是初始化它的VPTR。因此,它只能知道它是“当前”类的,而完全忽视这个对象后面是否还有继承者。当编译器为这个构造函数产生代码时,它是为这个类的构造函数产生代码——既不是为基类,也不是为它的派生类(因为类不知道谁继承它)。所以它使用的VPTR必须是对于这个类的VTABLE。而且,只要它是最后的构造函数调用,那么在这个对象的生命期内,VPTR将保持被初始化为指向这个VTABLE, 但如果接着还有一个更晚派生的构造函数被调用,这个构造函数又将设置VPTR指向它的 VTABLE,等.直到最后的构造函数结束。VPTR的状态是由被最后调用的构造函数确定的。这就是为什么构造函数调用是从基类到更加派生类顺序的另一个理由。但是,当这一系列构造函数调用正发生时,每个构造函数都已经设置VPTR指向它自己的VTABLE。如果函数调用使用虚机制,它将只产生通过它自己的VTABLE的调用,而不是最后的VTABLE(所有构造函数被调用后才会有最后的VTABLE)。
3.二叉树的相关概念:
深度:树的层数;
度:(对节点来说是)一个节点的孩子数;(对整棵树来说是)其节点的度的最大值。
完全二叉树: 将满二叉树从右向左删除节点所得的二叉树为完全二叉树。
如果一棵树有 n 个 叶子节点,那么 度为2的节点 就有 n -1个。如果我们知道一棵完全二叉树有 n 个叶子节点,那么它的节点数最多 2n 个。
任何二叉树中度为0的结点比度为2的结点多一个。
4.堆的插入元素是在最后插入,然后进行调整堆;删除元素是把最后的元素放到删除元素的地方,然后进行调整堆。
5.由先序遍历和中序遍历可以唯一确定二叉树,确定方法是:由先序序列确定根节点;按根节点把中序序列分为两端,前面的是左子树,后面的是右子树;对左右子树重复前面的步骤还原树形结构。
6.printf是从右向左压入栈,先运行右侧项,再运行左侧项。例如printf(“%d,%d”,a,++a);输出的结果应该是两个相等的值(都是a+1以后的值)。
7.在写判断语句时最好将一个右值放在= =左侧,而把左值放在右侧,因为这样可以检查出误写成赋值运算符的情况。
8.所有的ASCII码都可以用“\”加数字(一般是8进制数字)来表示。因此看到\后跟三位数字的时候要明白这是一个字符。
9.在二叉树结点的前序序列、中序序列和后序序列中,所有叶结点的先后顺序完全相同。
10.一种类型a至少提供另外一种类型t的行为,那么a类型就是b类型的子类型。公有继承的派生类就是基类的子类型。类a是类b的子类型,意味着类a适应类b,即:类a对象可以使用的场合同样适合类b的对象。
11.拓扑排序常用来确定一个依赖关系集中,事物发生的顺序。拓扑排序得出的线性序列,只要能满足排在前面的任务先完成即可,例如V1→V2,序列中只要V1在V2前面即可(所以得出的序列可能不唯一)。
12.一棵非空的二叉树的先序遍历序列与后序遍历序列正好相反,则该二叉树一定满足只有一个叶子节点。
13.++ 是一目运算符,自增运算,它只能用于一个变量,即变量值自增1, 不能用于表达式。++运算符有个很有意思的特点,前置++返回的为左值对象,可以在一个运算式中多次使用例如++++i,但是后置++则返回右值,无法在一个运算式中多次使用后置++(对于- -是一样的),由于无法对右值进行自增运算,因此一个对象一旦后置自增后就不能再被当前式中自增。(可以在下次自增)可以这样理解:前置运算将对象返回了,该对象的值为自增以后的,而后置操作返回的是一个值,而不是一个对象,对象自身自增了,但是并没有返回。
14.对任意一棵树,设它有n个结点,这n个结点的度数之和为n-1.
15.多型数据类型是指包含的数据元素的类型并不确定。
16.一个char占用一个字节,一个short占用2个字节,一个int占用4个字节。
17.三种数据的存储区:局部变量存储在栈,全局变量及静态变量存储在静态存储区,动态分配对象存储在堆区(动态存储区)。
18.对一个数组使用取地址符号,返回的是一个指向整个数组(而不是首元素)的指针,对于指向数组首元素的指针,只需要直接将数组赋值给指针即可。例如int a[]={1,2,3,4};&a 返回的是int (*)[4]即数组指针类型。
19.字符类型占1字节, 可以从任何地址开始,short类型占2字节, 必须从2字节倍数地址开始,int类型占4字节,必须从4字节倍数地址开始。
20.结构体在内存组织上是顺序式的,联合体则是重叠式,各成员共享一段内存,所以整个联合体的sizeof也就是每个成员sizeof的最大值。(需要考虑对齐)。
21.在无向图中,如果从顶点vi到顶点vj有路径,则称vi和vj连通。如果图中任意两个顶点之间都连通,则称该图为连通图,否则,称该图为非连通图,则其中的极大连通子图称为连通分量,这里所谓的极大是指子图中包含的顶点个数极大。
22.纯虚函数是在基类声明的虚函数,它在基类中没有定义,但是要求派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后面添加“=0”
23.比如 virtual void f()=0;
24.而c++中包含纯虚函数的类称为抽象类,由于抽象类中包含了没有定义的纯虚函数,所以不能定义抽象类的对象。总结:1.抽象类只能用作其他类的基类,不能定义抽象类的对象。2.抽象类不能用于参数类型、函数返回值或显示转换的类型3.抽象类可以定义抽象类的指针和引用,此指针可以指向它的派生类,进而实现多态性。
25.堆内存和栈内存的区别:1.栈内存由操作系统分配,堆内存由程序员自己分配。2栈内存空间大小是固定的,堆的大小受限于系统中有效地虚拟内存。3栈的空间由系统决定合适释放,堆内存需要自己决定何时释放。4堆的使用容易产生碎片,但是使用方便。
26.宏定义问题:#define是宏定义,该定义只是做简单转换,不会执行高级数学运算包括运算律,例如#defineA 2;#define B A+1;#define C (B+1)*B/2将执行操作(2+1+1)*2+1/2=8,而不是按照正常的数学运算优先级。
27.不同的数据类型在32位和64位下所占字节的区别
32位编译器:
char :1个字节
char*(即指针变量): 4个字节(32位的寻址空间是2^32, 即32个bit,也就是4个字节。同理64位编译器)
short int : 2个字节
int: 4个字节
unsigned int : 4个字节
float: 4个字节
double: 8个字节
long: 4个字节
long long: 8个字节
unsigned long: 4个字节
64位编译器:
char :1个字节
char*(即指针变量): 8个字节
short int : 2个字节
int: 4个字节
unsigned int : 4个字节
float: 4个字节
double: 8个字节
long: 8个字节
long long: 8个字节
unsigned long: 8个字节
28.将树T转换成二叉树B之后,那么对于B中的某个节点N:
(1)如果存在左子树,则该左子树一定来自于N在树T中的长子;
(2)如果存在右子树,则该右子树一定来自于N在树T中邻接右兄弟;
29.自动变量,只在定义它们的时候才创建,在定义它们的函数返回时系统回收变量所占存储空间。对这些变量存储空间的分配和回收是由系统自动完成的。一般情况下,不作专门说明的局部变量,均是自动变量。自动变量也可用关键字auto作出说明。
30.对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。
31.线索化二叉树利用了一个节点数为n的二叉树种空指针域有n-1个的特点,将空指针域指向特定类型的前驱或后继(先序、中序、后序)若一个节点左子树为空,则将左子树指向其前驱,若右子树为空,则将右子树指针指向其后继(根据序的要求)。
若向平衡二叉树中插入一个新结点后破坏了平衡二叉树的平衡性。首先要找出插入新结点后失去平衡的最小子树根结点的指针。然后再调整这个子树中有关结点之间的链接关系,使之成为新的平衡子树。当失去平衡的最小子树被调整为平衡子树后,原有其他所有不平衡子树无需调整,整个二叉排序树就又成为一棵平衡二叉树。
失去平衡的最小子树是指以离插入结点最近,且平衡因子绝对值大于1的结点作为根的子树。假设用A表示失去平衡的最小子树的根结点,则调整该子树的操作可归纳为下列四种情况。
(1)LL型平衡旋转法
由于在A的左孩子B的左子树上插入结点F,使A的平衡因子由1增至2而失去平衡。故需进行一次顺时针旋转操作。即将A的左孩子B向右上旋转代替A作为根结点,A向右下旋转成为B的右子树的根结点。而原来B的右子树则变成A的左子树。
(2)RR型平衡旋转法
由于在A的右孩子C的右子树上插入结点F,使A的平衡因子由-1减至-2而失去平衡。故需进行一次逆时针旋转操作。即将A的右孩子C向左上旋转代替A作为根结点,A向左下旋转成为C的左子树的根结点。而原来C的左子树则变成A的右子树。
(3)LR型平衡旋转法
由于在A的左孩子B的右子数上插入结点F,使A的平衡因子由1增至2而失去平衡。故需进行两次旋转操作(先逆时针,后顺时针)。即先将A结点的左孩子B的右子树的根结点D向左上旋转提升到B结点的位置,然后再把该D结点向右上旋转提升到A结点的位置。即先使之成为LL型,再按LL型处理。
如图中所示,即先将圆圈部分先调整为平衡树,然后将其以根结点接到A的左子树上,此时成为LL型,再按LL型处理成平衡型。
(4)RL型平衡旋转法
由于在A的右孩子C的左子树上插入结点F,使A的平衡因子由-1减至-2而失去平衡。故需进行两次旋转操作(先顺时针,后逆时针),即先将A结点的右孩子C的左子树的根结点D向右上旋转提升到C结点的位置,然后再把该D结点向左上旋转提升到A结点的位置。即先使之成为RR型,再按RR型处理。
如图中所示,即先将圆圈部分先调整为平衡树,然后将其以根结点接到A的左子树上,此时成为RR型,再按RR型处理成平衡型。
平衡化靠的是旋转。参与旋转的是3个节点(其中一个可能是外部节点NULL),旋转就是把这3个节点转个位置。注意的是,左旋的时候p->right一定不为空,右旋的时候p->left一定不为空,这是显而易见的。
如果从空树开始建立,并时刻保持平衡,那么不平衡只会发生在插入删除操作上,而不平衡的标志就是出现bf == 2或者bf == -2的节点。
32.栈有两种存储方法:一是顺序栈;二是链式栈。栈的顺序存储结构是利用一组地址连续的存储单元依次存储自栈底到栈顶的数据元素,同时附设指针top指示栈顶元素的位置。由于栈的操作是线性表操作的特例,相对而言,链式栈的操作更易于实现。
33.注意逻辑与或运算的规则,若与运算左操作数为假,则不再执行右操作数(直接返回假),同理,若或运算左操作数为真,则不再执行右操作数(直接返回真)。
34. 排序算法特点总结:
35.final修饰符
如果一个类被声明为final,意味着它不能再派生出新的子类,不能作为父类被继承。因此一个类不能既被声明为abstract的,又被声明为final的。将变量或方法声明为final,可以保证它们在使用中不被改变。被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取,不可修改。
finally
异常处理时提供 finally 块来执行任何清除操作。如果抛出一个异常,那么相匹配的 catch 子句就会执行,然后控制就会进入 finally 块(如果有的话)。一般异常处理块需要方法名。
finalize
Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在Object 类中定义的,因此所有的类都继承了它。子类覆盖finalize() 方法以整理系统资源或者执行其他清理工作。finalize() 方法是在垃圾收集器删除对象之前对这个对象调用的。 Java中所有类都从Object类中继承finalize()方法。当垃圾回收器(garbage colector)决定回收某对象时,就会运行该对象的finalize()方法。
36.注意free一个指针和delete一个指针是不同的,前者释放一个指针,free 掉一个指针后,指针仍然指向原来的地址。free 的意义在于告诉系统目标地址可以被回收。而delete则是将指针删除。
37. 即使是最简单的虚函数调用,编译器的内联处理程序对它也爱莫能助。(这一点也不奇怪。virtual的意思是"等到运行时再决定调用哪个函数",inline的意思是"在编译期间将调用之处用被调函数来代替",如果编译器甚至还不知道哪个函数将被调用,当然就不能责怪它拒绝生成内联调用了)。
38. 宏定义和const的区别:
(1) 编译器处理方式不同,define宏是在预处理阶段展开,const常量在编译运行阶段展开。
(2) 类型和安全检查不同,define宏没有类型,不做任何检查,仅仅是展开,const常量具有具体的类型,在编译阶段会执行类型检查。
(3) 存储方式不同,define宏仅仅是展开,有多少地方使用就展开多少次,const常量会存储在内存中分配,仅仅生成一次。(const可以节省空间,避免不必要的内存分配,提高了效率)。
在C++ 程序中只使用const常量而不使用宏常量,即const常量完全取代宏常量。
39. 函数reserve(size)将字符串的容量设置为至少size。如果size所指定的数值小于当前字符串中的字符数,容量将被设置为恰好容纳字符的数值。reserve()以线性时间运行。其最大用处是为了避免反复重新分配缓冲区内存而导致效率降低,或者在使用某些STL操作之前保证缓冲区足够大。
40. 关于final的重要知识点;
(1) final关键字可以用于成员变量、本地变量、方法以及类。
(2) final成员变量必须在声明的时候初始化或者在构造器中初始化,否则就会报编译错误。
(3) 你不能够对final变量再次赋值。
(4) 本地变量必须在声明时赋值。
(5) 在匿名类中所有变量都必须是final变量。
(6) final方法不能被重写。
(7) final类不能被继承。
(8) 没有在声明时初始化final变量的称为空白final变量(blank final variable),它们必须在构造器中初始化,或者调用this()初始化。不这么做的话,编译器会报错“final变量(变量名)需要进行初始化”。
41. 只要两个操作数中有一个是double类型的,另一个将会被转换成double类型,并且结果也是double类型,如果两个操作数中有一个是float类型的,另一个将会被转换为float类型,并且结果也是float类型,如果两个操作数中有一个是long类型的,另一个将会被转换成long类型,并且结果也是long类型,否则(操作数为:byte、short、int 、char),两个数都会被转换成int类型,并且结果也是int类型。
42. 非法指针是指向的内存已被回收,或者指向一个错误的地址。
43. 拥有虚函数的类存储时首地址存储为虚函数指针,其次是成员变量。
44. 一般的当将一个类对象指针转化为其他类型指针时,指针自身不变,只是将读取地址的方式进行了转变,指针指向还是之前那个位置。
45. 在重载一个运算符为成员函数时,其参数表中没有任何参数,这说明该运算符是前缀一元运算符,若参数列表中有一个int,则为后缀一元运算符。
46. 对字符串进行sizeof操作的时候,会把字符串的结束符"\0"计算进去的,进行strlen操作求字符串的长度的时候,不计算\0。
47. B树的定义是这样的,一棵m阶的B树满足下列条件:(1)每个结点至多有m棵子树;(2)除根结点外,其他每个非叶子结点至少有m/2棵子树;(3)若根结点不是叶子结点,则至少有两棵子树;(4)所有叶结点在同一层上。B树的叶结点可以看成一种外部结点,不包含任何信息;(5)所有的非叶子结点中包含的信息数据为:(n,p0,k1,p1,k2,P2,…,kj-1,Pj-1)其中,ki为关键字,且满足kiki+1;pi为指向子树根结点的指针,并且Pi-1所指的子树中的所有结点的关键字均小于ki,Pj-1所指的子树中的所有结点的关键字均大于kj-1。B+树是应文件系统所需而出现的一种B树的变型树,其主要区别是一棵非叶子结点有n个子树就有n个关键字,这些关键字的作用是索引;所有的叶子结点包含了全部关键字的信息,以及指向这些关键字记录的指针,且叶子结点本身的关键字的大小自小而大顺序链接。从上述的特点中我们知道,这两种树都是平衡的多分树,它们都可以用于文件的索引结构,但B树只能支持随机检索,而B+树是有序的树,既能支持随机检索,又能支持顺序检索。
48. 在同一个运算式中使用对同一个变量使用后置++,在当前表达式中调用的为变化前的对象,不管使用多少次。而前置++则每次都会改变变量。但是在当前运算式结束后,原变量均发生变化。例如x=1;y=(x++)+(x++);得到的y=2;x=3
49. 在c++STL中list使用了链式数据结构,而map 使用了红黑树的数据结构。
50. Byte是字节,bit是位。
51. 字节对齐规则指出对一个结构体而言,其起始地址和大小必须是对齐数的正数倍,而其中成员的所占空间满足各自的对其规则,在需要时进行必要的填充。
C++及数据结构笔试面试常见知识点总结