之前毕业找工作的时候做的总结。
1、static(静态)变量有什么作用?
答:在C语言中,关键字static的意思是静态,它有3个明显的作用:
1. 在函数体内,静态变量具有“记忆功能”,即一个被声明为静态的变量在这一函数被调用的过程中其值保持不变。
2. 在模块内(函数体外),它的作用域范围是有限的,即如果一个变量被声明为静态的,那么该变量可以被模块内的所有函数访问,但是不能被模块外的其它函数访问。它是一个本地的全局变量,如果一个函数被声明为静态,那么该函数与普通函数作用于不同,其作用域仅在本文件中,它只可被这一模块的其它函数调研,不能被模块外的其它函数调用,也就是说这个函数被限制在声明它的模块的本地范围内使用。
3. 内部函数应该在当前源文件中声明和定义,对于可在当前源文件以外使用的函数,应该在头一个文件中声明,使用这些函数的源文件要包含这个头文件。
具体而言,static全局变量和普通全局变量的区别在于static全局变量只初始化一次,这样做的目的是为了防止在其他文件但愿中被引用。Static局部变量和普通局部变量的区别在于static局部变量只被初始化一次,下一次的运算依据是上一次的结果值。Static()函数与普通函数的区别在于作用域不一样,static(函数)只在一个源文件中有效,不能被其他源文件使用。
C++中,在类内数据成员的声明前面加上关键字static,该数据成员就是类内的静态数据成员。静态数据成员有以下特点:
1. 对于非静态数据成员,每个类对象都有自己的复制品,而静态数据成员被当做类的成员,无论这个类的对象被定义了多少个,静态数据成员在程序中也只有一个复制品,由该类的所有对象共享访问。
2. 静态数据成员存储在全区数据区,定义时要分配空间,所以不能在类声明中定义。
3. 静态数据成员和普通成员函数一样遵从public、protected、private访问规则。
4. static成员变量的初始化是在类外,此时不能再带上static的关键字。Private、protected的static成员虽然可以在类外初始化,但是不能再类外被访问。
5. 类的静态数据成员必须初始化,因为它是在程序初始化的时候分配的。
与全局变量相比,使用静态数据成员的有以下两个优势:
1. 静态数据成员没有进入程序的全局名字空间,因此不存在与程序中其他全局名字冲突的可能性。
2. 可以实现信息隐藏。静态数据成员可以是private成员,而全局变量不能。
对于所有类的对象(不仅仅是静态对象),初始化都只有一次,而由于静态变量具有“记忆”功能,初始化后,一直没有被销毁,而是保存在内存区域中,所以不会再次初始化。
2. const 有哪些作用?
答:常类型也称const类型,是指使用类型修饰符const说明的类型。常类型的变量或对象的值是不能被更新的。
一般而言,const有以下几个方面的作用:
1. 定义const常量,具有不可变性。
2. 进行类型检查,使编译器对处理内容有更多的了解,消除一些隐患。
3. 保护被修饰的东西,防止被意外的修改,增强程序的健壮性。
4. 节省空间,避免不必要的内存分配。
5. 提高程序的效率。编译器通常不为普通的const常量分配存储空间,而是将它们保存在符号中,这使得它成为一个编译期间的常量,没有存储与读内存的操作,使得它效率提高。
3.什么是常引用?
答:常引用叶城const引用。引入常引用是为了避免在使用变量的引用时,在毫不知情的情况下改变了变量的值,从而引起程序错误。常引用主要用于定义一个普通变量的只读属性的别名,作为函数的传入形参,避免在实参调用函数中被意外的改变。
如果既要利用引用提高程序的效率,又要保护传递给函数的数据在函数中不被改变,就应该使用常引用。常引用的主要用途有:
1. 用作普通变量的只读别名。
2. 用于函数的形参。
4. static关键字至少有下列n 个作用:
答:(1)函数体内static 变量的作用范围为该函数体,不同于auto 变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;
(2)在模块内的static 全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;
(3)在模块内的static 函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内;
(4)在类中的static 成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;
(5)在类中的static 成员函数属于整个类所拥有,这个函数不接收this 指针,因而只能访问类的static 成员变量。
5. const 关键字至少有下列n 个作用:
答:(1)欲阻止一个变量被改变,可以使用const 关键字。在定义该const 变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;
(2)对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const;
(3)在一个函数声明中,const 可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;
(4)对于类的成员函数,若指定其为const 类型,则表明其是一个常函数,不能修改类的成员变量;
(5)对于类的成员函数,有时候必须指定其返回值为const 类型,以使得其返回值不为“左值”.
6. C++中为什么用模板类。
答:(1)可用来创建动态增长和减小的数据结构
(2)它是类型无关的,因此具有很高的可复用性。
(3)它在编译时而不是运行时检查数据类型,保证了类型安全
(4)它是平台无关的,可移植性
(5)可用于基本数据类型.
7. 程序什么时候应该使用线程,什么时候单线程效率高。
答:1.耗时的操作使用线程,提高应用程序响应
2.并行操作时使用线程,如C/S 架构的服务器端并发线程响应用户的请求。
3.多CPU 系统中,使用线程提高CPU 利用率
4.改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。
其他情况都使用单线程。
8. 什么是“引用”?申明和使用“引用”要注意哪些问题?
答:引用就是某个目标变量的“别名”(alias),对应用的操作与对变量直接操作效果完全相同。申明一个引用的时候,切记要对其进行初始化。引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,不能再把该引用名作为其他变量名的别名。声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。不能建立数组的引用。
9. 将“引用”作为函数参数有哪些特点?
答:(1)传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。
(2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。
(3)使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。
10.在什么时候需要使用“常引用”?
答:如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。常引用声明方式:const 类型标识符&引用名=目标变量名.
11. 将“引用”作为函数返回值类型的格式、好处和需要遵守的规则?
答:格式:类型标识符&函数名(形参列表及类型说明){ //函数体}
好处:在内存中不产生被返回值的副本;(注意:正是因为这点原因,所以返回一个局部变量的引用是不可取的。因为随着该局部变量生存期的结束,相应的引用也会失效,产生runtime error!
注意事项:
(1)不能返回局部变量的引用。
(2)不能返回函数内部new分配的内存的引用。
(3)可以返回类成员的引用,但最好是const。
12. “引用”与指针的区别是什么?
答:指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作。程序中使用指针,程序的可读性差;而引用本身就是目标变量的别名,对引用的操作就是对目标变量的操作。此外,就是上面提到的对函数传ref 和pointer 的区别。
13.volatile在程序设计中有什么作用?
答:volatile是一个类型修饰符,它用来修饰被不同线程访问和修改的变量。被volatile类型定义的变量,系统每次用到它的时候都是直接从对应的内存中提取,而不会利用cache中的原有数值,以适应它的未知何时会发生的变化,系统对这种变量的处理不会做优化,所以volatile一般用于修饰多线程间被多个任务共享和并行设备硬件寄存器等。
14. 断言ASSERT() 是什么?
答:ASSERT()一般被称为断言,它是一个调试程序是经常使用的宏。它定义在<assert.h>头文件中,通常用于判断程序中出现了非法的数据,在程序运行时它计算括号内的表达式的值。
15. C++里面是不是所有的动作都是main()引起的?如果不是,请举例.
答:在运行c++程序时,通常从main()函数开始执行。因此如果没有main(),程序将不完整,编译器将指出未定义main()函数。例外情况:如, 在windows 编程中,可以编写一个动态连接库(dll)模块,这是其他windows 程序可以使用的代码。由于DLL 模块不是独立的程序,因此不需要main().用于专用环境的程序--如机器人中的控制器芯片--可能不需要main().但常规的独立程序都需要main().
16.为什么有时候main() 函数会带参数?参数argc与argv含义是什么?
答:命令行参数有时用来启动一个程序的执行,如 int main( int argc, char *argv[]),其中第一个参数argc表示命令行参数的数目,它是int型的;第二个参数argv是一个指向字符串的指针数组,由于参数的数目并没有内在的限制,所有argv指向这组参数值的第一个元素,这些元素中的每个都是指向下一个参数文本的指针。
17.C++里面是不是所有的动作都是main()函数引起的?
答:不是。对于C++而言,静态变量、全局变量、全局对象的分配早在main()函数之前就已经完成了,所以并不是所有的动作都是由main()函数引起的,只有编译器室友main()开始执行的,main()只是一个约定的函数入口,在main()函数中的显示代码执行之前,会调用一个有编译器生成的_main()函数,而_main()函数会经行所有全局对象的构造及初始化工作。
在main()函数退出之后,全局变量必须销毁,自然会调用全局对象的析构函数,所以剩下的就同构造函数一样了。
18.new/delete与malloc/free的区别是什么?
答: 在C++中,申请动态内存与释放动态内存,用new/delete和malloc/free都可以,而且它们的存储方式相同,new与malloc动态申请的内存都位于堆中,无法被操作系统自动回收,需要对应的delete与free来释放空间,对于一般的数据类型,它们的效果是一样的。
malloc/free是C/C++语言的标准库函数,在C语言中需要头文件<stdlib.h>支持。new/delete是C++运算符。对于类的对象而言,malloc/free无法满足动态对象的要求,在对象创建的同时要自动执行构造函数,对象消亡之前要自动执行析构函数,而malloc/free不在编译器控制权限之内,无法执行构造函数与析构函数。
具体而言,new/delete与malloc/free的区别主要表现在以下几个方面:
1. new能够自动计算需要分配的内存空间,而malloc需要手工计算字节数。
2. new与delete直接带具体类型的指针,malloc与free返回void类型的指针。
3. new是安全类型的,而malloc不是。
4. new一般由两步构成,分别是new操作和构造。new操作对应于malloc,但new操作可以重载,可以自定义内存分配策略,不做内存分配,甚至分配到非内存设备上,而malloc不行。
5. new能调用构造函数,malloc不行,delete能调用析构函数,而free不能。
6. malloc/free需要库文件<stdlib.h>支持, new/delete则不需要库文件支持。
由于C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存,所以仍然保留了malloc/free。
19. 将引用作为返回值需要注意什么?
答:将引用作为返回值的有点是内存中不产生被返回值的副本,从而大大提高了程序的安全性和效率。
将引用作为函数返回值类型的格式一般需要注意以下4点内容:
1. 不能返回局部变量的值。
2. 不能返回函数内部new分配内存的引用。
3. 可以返回类成员的引用,但最好是常引用类型。
4. 流操作符<<和>>. 一般这两个操作符连续使用,因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。
20. 在C++中如何实现模板函数的外部调用?
答:export是C++新增的关键字,其作用是实现模板函数的外部调用,类似于extern关键字。为了访问其他代码文件中的变量或对象,对普通类型(包括基本数据类,结构和类)以利用关键字extern来使用这些变量或对象,但对于模板类型,则可以在头文件中声明模板类和模板函数,在代码文件中使用关键字export来定义具体的模板类对象和模板函数,然后在其他用户代码文件中,包含声明头文件后,就可以使用这些对象和函数了。
21. 在C++中,关键字explicit有什么作用?
答:C++引入关键字explicit,可以阻止不应该允许的经过转换构造函数进行的隐式转换的发生,声明为explicit的构造函数不能在隐式转换中使用。
22. C++异常的处理方法以及使用了哪些关键字?
答:C++异常处理的关键字有try,catch,throw。C++中的异常处理机制只能处理由throw捕获的异常,没有捕获的将被忽略。使用try、catch语句来捕获异常,把可能发生异常的语句放在try语句块中,后面若干个catch负责处理具体的异常类型,这样一组有try块和不少于一个catch块就构成了一级异常捕获。如果本级没有带适当参数的catch块,将不能捕获异常,异常就会向上一级传递,函数调用处如果没有捕获住异常,则直接跳到更高一层的调用者,如果一直没有捕获该异常,C++会使用默认的异常处理函数,该函数可能让程序最终跳出main函数并导致程序异常终止,
24. 如何定义和实现一个类的成员函数为回调函数?
答:回调函数就是被调用者回头调用的函数,它是一个通过函数指针调用的函数。如果把函数的指针作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,此时就可以称它为回调函数。回调函数不是由该函数的实现方直接调用的,而是在特定的事件或条件发生时由另外的一方调用,用于对该事件或条件进行响应。
要声明和实现一个类的和成员函数为回调函数需要做3件事:
1. 声明
2. 定义
3. 设置触发条件,就是在函数中把回调函数名作为一个参数,以便系统调用。
回调函数与应用接口程序(API)非常接近,它们都是跨层调用,但区别是API是底层提供给高层的调用,一般这个函数对高层都是已知的;而回调函数是高层给底层的调用,对于底层是未知的,必须有高层进行安装,安装后底层不知道这个回调的名字,但它通过一个函数指针来保存这个回调函数,在调用时,只需引用这个函数指针和相关的参数指针即可。
25. 如何定义和实现一个类的成员函数为回调函数?
1). 不使用成员函数,直接使用普通C 函数,为了实现在C 函数中可以访问类的成员变量,可以使用友元操作符(friend),在C++中将该C 函数说明为类的友元即可。这种处理机制与普通的C 编程中使用回调函数一样。
2). 使用静态成员函数,静态成员函数不使用this 指针作为隐含参数,这样就可以作为回调函数了。
26. 内存分配的形式有哪些?
答:一个C/C++编译的程序所占用的系统内存一般分为以下几个部分的内容:
1. 由符号起始的区块(BSS)段:BSS段通常是指用来存放程序中未初始化的全局数据 和静态数据的一块内存区域。BSS段属于静态内存分配,程序结束后静态变量资源由系统自动释放。
2. 数据段:数据段通常是指用来存放程序中已初始化的全局变量的一块内存区域。数据段也属于静态内存分配。
3. 代码段:代码段也称文本段,通常是指用来存放程序执行代码(包括类成员函数和全局函数以及其它函数代码)的一块存储区域。
4. 堆(heap):堆用于存放进程中被动态分配的内存段,其大小并不固定,可动态的扩张或缩减。
5. 栈(stack):栈用户存放程序临时创建的局部变量,栈由编译器自动分配释放,存放函数的参数值、局部变量的值等。
需要注意的是,代码段和数据段之间有明确的分隔,但是数据段和堆栈段之间没有,而且栈是向下增长的,堆是向上增长的。
27. 描述内存分配方式以及它们的区别?
1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。
2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。
3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意多少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多。
28..C++中什么数据分配在栈或堆中,New 分配数据是在近堆还是远堆中?
答:栈: 存放局部变量,函数调用参数,函数返回值,函数返回地址。由系统管理
堆: 程序运行时动态申请,new 和malloc 申请的内存就在堆上近堆还是远堆不是很清楚.
29.什么是内存泄露?
答:所谓内存泄露是指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。一般常说的内存泄露是指堆内存的泄露,内存泄露其实并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该内存段的控制,因而造成内存的浪费。
30. 什么是缓冲区溢出?
答:缓冲区是程序运行时机器内存中的一个连续快,它保存了给定类型的数据,随着动态分配变量会出现问题。缓冲区溢出是指当向缓冲区内填充数据位数超过了缓冲区自身的容量限制时,发生的溢出的数据覆盖在合法数据上的情况。
人为的缓冲区溢出一般是由于攻击者写一个超过缓冲区长度的字符串植入到缓冲区,然后再向一个有限空间的缓冲区植入超长的字符串,这时可能会出现两个结果:一是过长的字符串覆盖了相邻的存储单元,因其程序运行失败,严重的可能导致系统崩溃;另一个结果就是利用这种漏洞执行任意指令,甚至取得系统root特级权限,进而危害系统安全。
31.使用指针有哪些好处?
答:使用指针有以下几个方面的好处
1. 可以动态分配内存。
2. 进行多个相似变量的一般访问。
3. 为动态数据结构,尤其是数和链表提供支持。
4. 遍历数组,如解析字符串。
5. 高效的按引用“复制”数组与结构,提高开发效率。
32. 引用与指针
答: 引用与指针有着相同的地方,即指针指向一块内存,它的内容是所指内存的地址,引用是内存的别名。其区别如下:
1. 从本质上讲,指针是存放变量地址的一个变量,逻辑上是独立的,可以被改变,即其指向的地址可以被改变,其指向的地址中存放的数据也可以被改变。而引用则是一个别名,逻辑上不独立,它的存在具有依附性,所以引用必须一开始就被初始化,而且引用的对象在其整个生命周期中都是不能被改变的,即自始至终都只能依附于同一个变量。
2. 作为参数传递时,两者不同。指针传递参数本质上是值传递的方式,它所传递的是一个地址值。值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,即在栈中开辟内存空间以存放由主调函数放进来的实参的值,从而成为实参的一个副本。值传递的特点是被调函数对形参的任何操作都是作为局部变量进行的,不会影响主调函数的实参变量的值。而引用在传递过程中,被调函数的形参虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址,被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。
3. 引用使用时不需要解引用(*),而指针需要解引用
4. 引用不可以为空,而指针可以为空。引用必须与存储单元相对应,一个引用对应于一个存储单元,引用只能在定义时初始化一次。
5. 如果返回动态分配的对象或者内存,必须使用指针,引用肯呢个引起内存泄露。
32. c++中引用和指针有什么不同?指针加上什么限制等于引用?
答:1 引用被创建的时候必须被初始化,而指针不是必需的。
2 引用在创建后就不能改变引用的关系,而指针在初始化后可以随时指向
其它的变量或对象。
3 没有NULL 引用,引用必须与合法的存储单元关联,而指针可以是NULL。
34. 野指针?空指针?
答:野指针是指指向不可用内存的指针。造成野指针的三种情况:
1. 指针变量再被创建时未初始化。
2.指针被释放后,未能将其设置为NULL。
3. 指针操作超越了变量的作用范围。
空指针是一个特殊的指针,也是唯一一个对任何类型都合法的指针。指针变量具有空指针,表示它当时处于闲置状态,没有指向有意义的内容。
35、头文件中的ifndef/define/endif 干什么用?(5 分)
答:防止该头文件被重复引用。
36.#include<filename.h>和#include”filename.h”有什么区别?
答:对于#include<filename.h>,编译器先从标准库路径开始搜索filename.h,使得系统文件调用较快;而对于#include”filename.h”,编译器先从用户的工作路径开始搜索filename.h,然后去寻找系统路径,使得自定义文件较快。
头文件的作用主要表现在以下两个方面:
1. 通过头文件来调用库函数
2. 头文件能加强类型安全检查
37. 头文件的作用是什么?
答:一、通过头文件来调用库功能。在很多场合,源代码不便(或不准)向用户公布,只要向用户提供头文件和二进制的库即可。用户只需要按照头文件中的接口声明来调用库功能,而不必关心接口怎么实现的。编译器会从库中提取相应的
代码。
二、头文件能加强类型安全检查。如果某个接口被实现或被使用时,其方式与头文件中的声明不一致,编译器就会指出错误,这一简单的规则能大大减轻程序员调试、改错的负担。
38.#define有哪些缺陷?
答:由于宏定义在预处理阶段进行,主要做的事字符替换工作,所以它存在一些固有的缺陷:
1. 它无法进行类型检查。宏定义是在编译前进行字符的替换,因为还没编译,不能检查类型是否匹配,故而不具备类型检查功能。
2. 由于优先级不同,在使用宏定义时,可能会存在副作用。
3. 无法但不掉时。
4. 会导致代码膨胀,存在较多的冗余代码。
5. 在C++中,使用宏无法操作类的私有数据成员。
39. 含参数的宏与函数有什么区别?
答:1. 函数调用时,首先求出实参表达式的值,然后带入形参,而使用带参数的宏只是进行简单的字符替换。
2. 函数调用时在程序运行时处理的,它需要分配临时的内存单元;而宏展开则是在编译前进行的,在展开时并不分配内存单元,也不进行值的传递处理。
3. 函数中的实参和形参都要定义类型,且类型必须一致;宏不存在类型问题。
4. 宏替换不占用运行时间,而函数调用占用运行时间。
40. C++中宏定义与内联函数有什么区别?
答: 宏定义本身不是函数,用起来却像函数,预处理器用复制宏定义的方式替代函数调用,省去了参数压栈、生成汇编语言的CALL调用、返回参数。执行return等过程,从而提高了速度。
内联函数是代码被插入到调用者代码处的函数,它的使用是有限制的,只适合函数体内代码简单的函数使用,不能包含复杂的结构控制语句,且且本身不能直接调用递归函数。
两者的主要区别在于:
1. 宏定义在预处理阶段进行代码替换,而内联函数是在编译阶段插入代码。
2. 宏定义没有类型检查,而内联函数有类型检查。
内联函数与普通函数最大的区别在于其内部的实现方面,普通函数在被调用时,系统首先要跳跃到该函数的入口地址,执行函数体,执行完后,再返回到函数调用的地方,函数始终只有一个复制;而内联函数则不需要进行一个寻址的过程,当执行到内联函数时,此函数展开,如果在N处调用了次内联函数,则此函数就会有N个代码段的复制。
41. 定义常量谁更好?#define还是const
答:define既可以替代常数值,又可以替代表达式,甚至代码段,但是容易出错;而const的引入可以增强程序的可读性,它使程序的维护和调试变得更加方便。它们的差异在于:
1. define只是用来进行单纯的文本替换,define常量的生命周期止于编译期,不分配内存空间,存在于程序的代码段;而const常量存在于程序的数据段,并在堆栈中分配了空间,const常量在程序中确确实实的存在,并可以被调用、传递。
2.const常量有数据类型,define常量没有数据类型。
3. 很多DE支持调试const常量,而不支持define定义的常量。
由于const修饰的变量可以排除程序之间的不安全性因素,保护程序的常量不被修改,而且对数据类型也会进行相应的检查,极大地提高了程序的健壮性,所以一般倾向于用const来定义常量类型。
42. 请说出const 与#define 相比,有何优点?
答案:1) const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。
2) 有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试。
43. C语言中struct与union的区别是什么?
答:两者都是常见的复合结构,其区别主要表现在以下两个方面:
1. 结构体与联合体虽然都是由多个不同的数据类型成员组成,但联合体中所有成员共用一块地址空间,即联合体只存放一个被选中的成员,而结构体中所有成员占用空间是累加的吗,其所有成员都存在,不同成员会放在不同的地址。在计算一个结构型变量的总长度时,其内存空间大小等于所有成员长度之和(需要考虑字节对齐),而在联合体中,所有成员不能同时占用内存空间,它们不能同时存在,所以一个联合型变量的长度等于其最长的成员变量的长度。
2. 对于联合体的不同成员赋值,将会对它的其它成员重写,原来成员的值就不存在了,而对结构体的不同成员赋值是互不影响的。
44. C和C++中struct的区别是什么?
答:C语言中的struct和C++中的struct的区别表现在以下3个方面:
1. C语言的struct不能有函数成员,而C++的struct可以有。
2. C语言的struct数据成员没有访问权限的设定,而C++的struct有。
3. C语言的struct没有继承关系,而C++的struct有。
45. C++中struct和class的区别是什么?
答:在C++中struct和class做类型定义时的区别有两点:
1. class的继承默认是private的,而struct继承默认是public
2. class还用于定义模板参数,就像typename,但关键字struct不用于定义模板参数。
46. C++函数传递参数的方式有哪些?
答:C++函数传递参数的方式有以下4种:
1. 值传递。 当进行值传递时,就是将实参的值复制到形参中,而形参和实参不是同一个存储单元,所以函数调用结束后,实参的值不会发生改变。
2. 指针传递 当进行指针传递时,形参是指针变量,实参是一个变量的地址,调用函数时,形参(指针变量)指向实参变量单元。这种方式还是“值传递”,只不过实参的值是变量的地址,函数改变的不是实参的值而是实参地址所指向的变量的值。
3. 传引用 实参地址传递到形参,使形参的地址取实参的地址,从而使形参和实参共享同一单元的方式。
4. 全局变量传递 全局变量传递的优点是效率高,但它对多线程的支持不好,如果两个进程同时调用同一个函数,而用过全局变量进行传递参数,该函数就不能总是得到想要的结果。
47. 重载和覆盖有什么区别?
答: 重载是指函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数。对于重载函数的调用,在编译期间就已经确定,是静态的,它们的地址在编译期间就绑定了,与多态无关。重载不关心函数的返回值类型。
成员函数被重载的特征如下:
1. 相同的范围—同一个类中。
2. 函数名字相同
3. 参数不同—参数的类型、个数、顺序不同
4. virtual关键字可有可可无
覆盖是指派生类中存在重新定义基类的函数,其函数名、参数列表、返回值类型必须同父类中的相应被覆盖的函数严格一致,覆盖函数和被覆盖函数只有函数体不同,当派生类对象调用子类中该同名函数时会自动调用子类中的覆盖版本,而不是父类中的覆盖函数版本,和多态真正相关。
覆盖的特征如下:
1. 不同的范围—分别位于派生类和父类
2. 函数名字和参数相同
3. 基类函数必须有virtual关键字
重载与覆盖的区别如下:
1. 覆盖是子类和父类之间的关系,是垂直关系;重载是同一个类中函数之间的关系,是水平关系。
2. 覆盖只能由一个函数或只能有一对函数产生关系,;方法的重载是多个函数之间的关系。
3. 覆盖要求参数列表相同,重载要求参数列表不同
4. 覆盖关系中,调用函数是根据对象的类型(对象对应存储空间类型)来决定的,重载函数是根据调用时实参表与形参表来选择函数体的。
48. 隐藏
答:隐藏是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
1. 如果派生类函数与基类函数同名,但参数不同,则无论有无virtual关键字,基类的函数都被隐藏。
2. 如果派生类的函数与基类函数同名,且参数也相同,但是基类函数没有virtual关键字,此时基类的函数被隐藏。
隐藏的关系只有如下的可能:
1. 必须分别位于派生类和基类中。
2. 必须同名
3. 参数不同是本身已经不构成覆盖关系了,所以此时是否是virtual函数已经不重要了。
49. 类成员函数的重载、覆盖和隐藏区别?
答案:
a.成员函数被重载的特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。
b.覆盖是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual 关键字。
c.“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual 关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。
50. C++中函数调用有哪几种方式?
答:编译器一般使用堆栈实现函数调用。堆栈是存储器的一个区域,嵌入式环境有时需要程序员自己定义一个数组作为堆栈。Windows为每个线程自动维护一个堆栈,堆栈的大小可以设置。编译器使用堆栈来存放每个函数的参数、局部变量等信息。
不同CPU,不同编译器的堆栈布局、函数调用方法都可能不同,但堆栈的基本概念是一样的。当一个函数被调用时,进程内核对象为其在进程的地址空间的堆栈部分分配一定的栈内存给该函数使用,函数堆栈功能如下:
1. 在进入函数之前,保存“返回地址”和环境变量。返回地址是指该函数结束后,从进入该函数之前的那个地址继续执行下去。
2. 进入函数后,保存实参或实参复制、局部变量。
在Win32 下有以下4种调用:
1. _cdecl: 它是C/C++的默认调用方式。实参是以参数列表从右依次向左入栈,出栈相反,函数堆栈由调用方来释放,主要用在那些带有可变参数的函数上,对于传送参数的内存栈是由调用者来维护。
2. _stdcall: 它是Win API的调用约定,其实COM接口等只要是声明定义接口都要显示指定其调用约定为_stdcall。实参以参数列表从右依次向左入栈,出栈相反。函数堆栈是由被调用方自己释放的。
3. _thiscall:它是类的非静态成员函数默认的调用约定,其不能用在含有可变参数的函数时,否则编译会出错。实参以参数列表从右依次向左入栈,出栈相反。函数堆栈是由被调用方自己释放的。但是类的非静态数据成员函数内部都隐含有一个this指针,该指针不是存放在函数堆栈上,而是直接存放在CPU寄存器上。
4. _fastcall:快速调用。他们的实参并不是存放在函数堆栈上,而是直接存放在CPU寄存器上,所有不存在入栈、出栈、函数堆栈释放。
需要注意的是,全局函数或类静态成员函数,若没指定调用,约定默认是_cdecl或是IDE设置的。
51. 什么是可重入函数? C语言中如何写可重入函数?
答: 可重入函数是指能够被多个线程“同时”调用的函数,并且能保证函数结果正确性的函数。
在C语言中编写可重入函数时,尽量不要使用全局变量或静态变量,如果使用了全局变量或静态变量,就需要特别注意对这类变量访问的互斥。一般采用以下几种措施来保证函数的可重入性:信号量机制、关调度机制、关中断机制等方式。
52. 构造函数和析构函数是否可以被重载,为什么?
答:构造函数可以被重载,析构函数不可以被重载。因为构造函数可以有多个且可以带参数,而析构函数只能有一个,且不能带参数。
53. 一个类的构造函数和析构函数什么时候被调用,是否需要手工调用?
答:构造函数在创建类对象的时候被自动调用,析构函数在类对象生命期结束时,由系统自动调用.
54. 编译和链接的区别是什么?
答: 在多道程序环境中,要想将用户源代码变成一个可以在内存中执行的程序,通常分为3个步骤:编译、链接和载人。
1:编译: 将预处理生成的文件,经过词法分析、语法分析、语义分析以及优化后编译成若干个目标模板。可以理解为将高级语言翻译成计算机可以理解的二进制代码,即机器语言。
2:链接: 有链接程序将编译后形成的一组目标模块以及它们所需要的库函数链接在一起,形成一个完整的载入模型。链接主要解决模块间的相互引用问题,分为地址和空间分配、符号解析和重定位几个步骤。在编译阶段生成目标函数时,会暂时搁置那些外部引用,而这些外部引用就是在链接时进行确定的,链接器在链接时,会根据符号名称去相应模块中寻找对应符号。待符号确定之后,链接器会重写之前那些未确定符号的地址,这个过程就是重定位。链接一般分为静态链接、载入时动态链接和运行时动态链接3种。
3:载入: 由载入程序将载入模块载入内存。
编译时,编译器需要的是语法正确,函数与变量的声明正确。而一般来说,每个源文件都应该对应于一个中间目标文件(.o 文件.或obj文件)。链接时,主要是链接函数和全局变量,所以可以使用这些中间目标文件来链接应用程序。链接就是那些目标文件之间相互链接自己所需要的函数和全局变量,而函数可能来源于其他目标文件或库文件。
55. 全局变量和静态变量有什么异同?
答:全局变量的作用域是整个程序,它只需要在一个源文件中定义,就可以作用于所有的源文件,其他不包含全局变量定义的源文件需要用extern关键字再次声明这个全局变量。若某一个局部重新定义了这个变量,则全局变量作用于是除了这个局部外的整个程序,它的生命期与程序生命期一样长。
全局变量、静态局部变量与静态全剧变量都在静态存储区分配空间,而局部变量在栈上分配空间。
静态变量存储在静态存储区,它的生命周期与程序生命期相同。非静态变量的生命期与子程序的生命期相同,进入子程序,分配单元,退出则消失。下次调用子程序时非静态变量消失,静态变量却保留上次调用的结果。
总的来说,它们的相同点是都保留在静态存储区,生命周期与程序生命周期相同。而不同点在于全局变量具有全局作用域,静态变量具有稳健作用域。
静态变量包含静态局部变量和静态全局变量。静态局部变量具有局部作用域,只被初始化一次,自从第一次被初始化知道程序运行结束一直存在。它和全局变量的区别在于全局变量对于所有的函数都是可见的,而静态局部变量只对定义自己的函数体始终可见。静态全局变量也具有全局作用域,它与全局变量的区别在于如果程序包含多个文件的话,它作用于定义它的文件里,不能作用到其他文件里,即被static关键字修饰过的变量具有文件作用域,这样即使两个不同的源文件都定义了相同名字的静态全局变量,它们也是不同的变量。
把局部变量改变为静态变量后是改变了它的存储方式,即改变了它的生命周期;把全局变量改变为静态变量后是改变了它的作用域,限制了它的适用范围。
56. 局部变量需要”避讳”全局变量么?
答:局部变量可以和全局变量重名,但是局部变量会屏蔽全局变量。要使用全局变量,需要使用操作符::。在函数内引用变量会用到同名的局部变量,而不会使用到全局变量,对于有些编译器来说,在同一个函数内可以定义多个同名的局部变量。
具体来说,全局变量与局部变量的区别有以下4个方面:
1. 全局变量的作用域为这个程序块,而局部变量的作用域为当前函数。
2. 内存存储方式不同,全局变量分配在全局数据去,后者分配在栈区。
3. 生命周期不同。全局变量岁主程序创建而创建,随主程序销毁而销毁,局部变量在局部函数内部,甚至局部循环体等内部存在,退出就不存在了。
4. 使用方式不同。通过声明后全局变量程序的各个部分都可以用到,局部变量只能在局部使用。
需要注意的是,局部变量不可以赋值为同名全局变量。
57. 变量定义与变量声明有什么区别?
答: 在C/C++程序设计中,任何变量在使用前都需要进行定义或声明,定义为变量分配存储空间,还可以为变量制定初始值,在一个程序中,变量有且仅有一个定义。而声明是指向程序表明变量的类型和名字。定义也是声明,当定义变量时声明了它的类型和名字。可以通过使用extern关键字声明变量而不定义它,它所说明的并非自身,而是描述其他地方创建的对象,可以多次出现。
“定义也是声明”,这说明定义包括声明,对于int a 来说,它既是定义又是声明,对于extern int a 来说,它是声明不是定义。一般为了叙述方便,把建立存储空间的声明称为定义,而把不建立存储空间的声明称为声明。
58. C与C++变量初始化有什么不同?
答:在C语言中,只能用常数对全局变量和静态变量进行初始化,否则编译器会报错,全局变量如果不初始化,默认为0. C语言中静态变量和全局变量(extern外部变量属于全局变量)的分配内存空间和初始化是在编译阶段完成的,而其他变量是在编译阶段进行内存空间分配,在程序运行时执行本函数时赋予初值的。
而在C++中全局对象、变量的初始化是独立的,C++中全局对象、变量的构造函数调用顺序是跟声明有一定关系的,即在同一个文件中先声明的先调用。对于不同文件中的全局对象、变量,它们的构造函数调用顺序是未定义的,取决于具体的编译器,。
全局变量放在内存的全局数据区,由编译器建立,如果在定义的时候不做初始化,则系统将自动为其初始化,数值型为0,字符型为NULL,即0,指针变量也被赋值为NULL。静态变量的情况与全局变量类似。而非静态局部变量如果不显示初始化,那么其内容是不可预料的,将是随机数,会很危险,对系统的安全造成非常大的隐患。
59. 全局变量和局部变量有什么区别?实怎么实现的?操作系统和编译器是怎么知道的?
答:全局变量是整个程序都可访问的变量,谁都可以访问,生存期在整个程序从运行到结束(在程序结束时所占内存释放),而局部变量存在于模块(子程序,函数)中,只有所在模块可以访问,其他模块不可直接访问,模块结束(函数调用完毕),局部变量消失,所占据的内存释放。
全局变量分配在全局数据段并且在程序开始运行的时候被加载. 局部变量则分配在堆栈里面。
60. 编译型语言与解释型语言的区别是什么?
答:编译型语言:编译是在应用源程序执行之前,就将程序源代码“翻译”成目标代码(机器语言)。因此其目标程序可以脱离其语言环境独立执行,使用比较方便、效率更高。但应用程序一旦需要修改,必须先修改源代码,再重新编译生成新的目标文件(*.obj)才能执行,只有目标文件而没有源代码,修改很不方便。现在大多数编程语言都是编译型。编程程序将源程序翻译成目标程序后背保存在另一个文件中,该目标程序可脱离编译程序直接在计算机上多次运行。大多数软件产品都是以目标程序行使发行给用户的,不仅便于直接运行,同时又使他人难于盗用其中的技术。
解释型语言:解释型语言的实现中,翻译器并产生目标机器代码,而是产生抑郁执行的中间代码。这种中间代码与机器代码是不同的,中间代码的解释是由软件支持的,不能直接使用硬件,软件解释器通常会导致执行效率较低。用解释型语言编写的程序是由另一个可以理解中间代码的解释程序执行的。与编译程序不同的是,解释程序的任务是逐一将源程序的语句解释成可执行的机器指令,不需要将源程序翻译成目标代码后在执行。解释程序的优点是当语句出现语法错误,可以立即引起程序员的注意,而程序员在程序开发期间就能进行校正。这种解释型语言每执行一次就要翻译一次,因而效率低下。
61. 在C++程序中调用被C编译器编译后的函数,为什么要加extern “C”?
答: C++语言是一种面向对象编程语言,支持函数重载,而C语言是面向过程的编程语言,不支持函数重载,所以函数被C++编译后在库中的名字与C语言不同。C++提供了C语言替代连接说明符号 extern “C”来解决名字匹配问题。 Extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以再模块和其他模块中使用。
62、在C++ 程序中调用被C 编译器编译后的函数,为什么要加extern “C”? (5 分)
答:C++语言支持函数重载,C 语言不支持函数重载。函数被C++编译后在库中的名字与C 语言的不同。假设某个函数的原型为: void foo(int x, int y);该函数被C 编译器编译后在库中的名字为_foo,而C++编译器则会产生像_foo_int_int 之类的名字。C++提供了C 连接交换指定符号extern“C”来解决名字匹配问题。
63. 两段代码共存于一个文件,编译时有选择地编译其中的一部分,如何实现?
答: 可以通过以下两种方式:
1.在源码中使用条件编译语句,然后在程序文件中定义宏的形式来选择需要的编译代码。
2.在源码中使用条件编译语句,然后在编译命令的命令中加入宏定义命令来实现选择编译。
64. 面向对象和面向过程有什么区别?
答:具体而言,两者有以下几个方面的不同之处:
1. 出发点不同。
2. 层次逻辑关系不同。
3. 数据处理方式和控制程序方式不同。
4. 分析设计与编码转换方式不同。
65. 面向对象的基本特征有哪些?
答:面向对象方法首先对需求进行合理分层,然后构建相对独立的业务模块,最后通过整合各模块,达到高举和、低耦合的效果,从而满足客户要求。具体而言,它有3个基本特征:封装、继承和多态。
1. 封装是指将客观事物抽象成类,每个类对自身的数据和函数实行保护。
2. 继承可以使用现有类的所有功能,而不需要重新编写原来的类,它的目的是为了进行代码复用和支持多态。一般有3种形式:实现继承、可视继承和借口继承。
3. 多态是指同一个实体同时具有多种形式,主要体现在类的继承体系中。它是将父对象设置成为和一个或多个他的子对象相等的技术,赋值以后,父对象就可以根据当前负责给它的子对象的特性以不同的方式运作。简单地说,就是允许将子类类型的指针赋值给父类类型的指针。编译时多态为静态多态,在编译时就可以确定对象使用形式。
66.什么是深拷贝?什么是浅拷贝?
答: 如果一个类拥有资源(堆或者其它系统资源),当这个类的对象发生复制过程时,资源重新分配,这个过程就是深拷贝;反之对象存在资源,但复制过程并未复制资源的情况为浅拷贝。
67. 什么是友元?
答: 类具有封装、继承、多态、信息隐藏的特性,只有类的成员函数才可以访问类的标记为private的私有成员,分成员函数可以访问类的公有成员,但是无法访问私有成员,为了使非成员函数可以访问类的成员,唯一的做法就是将成员都定义为public,但是如果将数据成员都定义为公有的,这又破坏了信息隐藏的特性。而且,对某些成员函数多次调用时,由于参数传递,类型检查和安全性检查等都需要时间开销,从而影响程序的运行效率。
友元正好解决了这一问题,在使用友元函数时,一般需要注意以下几个方面的问题:
1. 必须再累的说明中说明友元函数,说明时以关键字friend开头。
2. 友元函数不是类的成员函数。
3. 友元函数不能直接访问类的成员,只能访问对象成员。
4. 友元函数可以访问对象的私有成员,但普通函数不行。
5. 调用友元函数时,在实际参数中需要指出要访问的对象。
7. 类与类之间的友元关系不能继承。
68. 复制构造函数与赋值运算符的区别是什么?
答: 复制构造函数时一种特殊的构造函数,在生成一个实例的时候,一般会同时生成一个默认的复制构造函数,复制构造函数完成一些基于同一类的其他对象的构建及初始化工作。具体而言,复制构造函数有如下特点:
1. 该函数名与类同名,并且不指定返回类型值。
2. 该函数只有一个参数,并且是对某个对象的引用。
3. 每个类都必须有一个复制构造函数。
4. 如果程序员没有显示的定义一个复制构造函数,那么C++编译器会自动生成一个缺省的复制构造函数。
5. 复制构造函数的目的是建立一个新的对象实体,所以一定要保证新创建的对象有独立的内存空间,而不是与先前的对象共用。
具体而言,复制构造函数相比赋值运算符有以下3个方面的不同。
1. 复制构造函数生成新的类对象,而赋值运算符不能。
2. 由于复制构造函数是直接构造一个新的类对象,所以在初始化这个对象之前不用检验源对象是否和新建对象相同。而赋值运算符则需要这个操作,另外赋值运算中如果原来的对象中有内存分配,要先把内存释放掉。
3. 当类中有指针类型的成员变量时,一定要重写复制构造函数和赋值构造函数,不能使用默认的。
简单点说,当进行一个类的实例初始化时,也就是构造时,调用的是构造函数,但如是用其他实例来初始化,则调用复制构造函数,非初始化时对这个实例进行赋值调用的事赋值运算符
69. 类的成员变量的初始化顺序是按照声明顺序么?
答: 在C++中,类的成员变量的初始化顺序只与变量在类中的声明顺序有关,与在构造函数中的初始化列表的顺序无关。且静态成员变量先于实例变量,父类成员变量先于子类成员变量,父类构造函数先于子类构造函数。
从全局看,变量的初始化顺序如下:
1. 基类的静态变量或全局变量。
2. 派生类的静态变量或全局变量。
3. 基类的成员变量。
4. 派生类的成员变量。
70. C++中有哪些情况只能用初始化列表而不能用赋值?
答:在C++中,赋值与初始化列表的原理不一样,赋值是删除原值,赋予新值,初始化列表开盘空间和初始化时同时完成的,直接给予一个值。
只能用初始化列表而不能用赋值的情况一般有以下3种:
1. 当类中含有const(常量)、reference(引用)成员变量时,只能初始化,不能对他们赋值。常量不能被赋值,只能被初始化,所以必须在初始化列表中完成,C++的引用也一定要初始化,所以必须在初始化列表中完成。
2. 基类的构造函数都需要初始化列表。构造函数的意思是先开辟空间然后为其赋值,只能算赋值,不能算初始化。
3. 成员类型是没有默认构造函数的类。
71. C++中virtual 与inline 的含义分别是什么?
答:在基类成员函数的声明前加上virtual 关键字,意味着将该成员函数声明为虚函数。
inline 与函数的定义体放在一起,使该函数称为内联。inline 是一种用于实现的关键字,而不是用于声明的关键字。
虚函数的特点;如果希望派生类能够重新定义基类的方法,则在基类中将该方法定义为虚方法,这样可以启用动态联编。
内联函数的特点;使用内联函数的目的是为了提高函数的运行效率。内联函数体的代码不能过长,因为内联函数省去调用函数的时间是以代码膨胀为代价的。内联函数不能包含循环语句,因为执行循环语句要比调用函数的开销大。
72. 什么是虚函数?
答: 指向基类的指针在操作它的多态类对象时,会根据不同的类对象调用其相应的函数,这个函数就是虚函数。虚函数用virtual修饰函数名。虚函数的作用是在程序的运行阶段动态的选择合适的成员函数,在定义了虚函数后,可以在基类的派生类中对虚函数进行重新定义 。在派生类中重新定义的函数应与虚函数具有相同的形参个数和类型及参数顺序。以实现统一的接口。如果在派生类中没有对虚函数重新定义,则它集成其基类的虚函数。
在使用虚函数时,需要注意以下几个方面的内容:
1. 只需在声明函数的类体中使用关键字virtual将函数声明为虚函数,而定义时不用。
2. 当基类中的某一成员函数声明为虚函数后,派生类中的同名函数会自动成为虚函数。
3. 如果声明了某个成员函数为虚函数,则在该类及其派生类中不能出现与这个成员函数同名并且返回值。参数个数和类型都相同的非虚函数。
4. 非类的成员函数不能定义为虚函数,全局函数以及类的成员函数中的静态成员函数和构造函数也不能定义为虚函数,但可以将析构函数定义为虚函数。
5. 普通派生类对象,先调用基类构造再调用派生类构造。
6. 基类的析构函数应该定义为虚函数,这样可以在实现多态的时候不造成内存泄露。
7. 基类指针动态建立派生类对象,普通调用派生类构造函数
8. 指针声明不调用构造函数。
73. 请讲一讲析构函数和虚函数的用法和作用?
答:析构函数的作用是当对象生命期结束时释放对象所占用的资源。析构函数用法:析构函数是特殊的类成员函数它的名字和类名相同,没有返回值,没有参数不能随意调用也没有重载。只是在类对象生命期结束时有系统自动调用。
虚函数用在继承中,当在派生类中需要重新定义基类的函数时需要在基类中将该函数声明为虚函数,作用为使程序支持动态联遍。
74. C++如何实现多态
答:C++通过虚函数实现多态。虚函数的本质就是通过基类访问派生类定义的函数。每一个含有虚函数的类,其实例对象内部都有一个虚函数表指针。该虚函数表指针被初始化为奔雷虚函数表的内存地址。所以在程序中,不管对象类型如何转换,但该对象内部的虚函数表指针是固定的,这样才能实现动态地对对象函数进行调用,这就是C++多态性的原理。
75. 多态的作用?
主要是两个:1. 隐藏实现细节,使得代码能够模块化;扩展代码模块,实现代码重用;2. 接口重用:为了类在继承和派生的时候,保证使用家族中任一类的实例的某一属性时的正确调用
76. C++中继承、虚函数、纯虚函数分别指的是什么?
答:继承是指一种食物自动获得另一种事物的全部东西(属性、能力)。
用virtual修饰的函数就是虚函数。如果需要使用多态特性,就必须使用虚函数。以基类的身份调用的虚函数,如果对象是派生类的,派生类的对应函数会被调用,从而可以实现通过完全相同的调用形式让不同的类型的对象作为自己不同的对应。
对于纯虚函数,编译器要求在派生类中予以重载以实现多态性。含有纯虚函数的类称为抽象类,抽象类不能生成对象。纯虚函数永远不会被调用,它们主要用来归一管理子类对象。
77. C++中的多态种类有哪几种?
答:C++中的多态种类包括参数多态、引用多态、过载多态以及强制多态等。
参数多态是指采用参数化模板,通过给定不同的类型参数,使得一个结构有多重类型、模板。引用多态是指同样的操作可以用于一个类型及其子类型。过载多态是指同一个名字在不同的上下文中有不同的类型。而强制多态则是指把操作对象的类型强加以变换,以符合函数或操作符的要求。
78. 什么函数不能声明为虚函数?
答: 一个类中将所有的成员函数都尽可能地设置为虚函数总是有益的,但设置虚函数需要注意已下5个方面的内容。
1. 只有类的成员函数才能声明为虚函数。
2. 静态成员函数不能声明为虚函数。因为调用静态成员函数不要实例,但调用虚函数需从一个实例中指向虚函数表的指针以得到函数的地址,因此调用虚函数需要一个实例,两者互相矛盾。
3. 内联函数不能为虚函数。
4. 构造函数不能为虚函数。因为构造函数是在对象完全构造之前运行的,换句话说,运行构造函数前,对象还没有生成,更谈不上动态类型了。构造函数是初始化徐表指针,而虚函数放到虚表里面,当要调用虚函数的时候首先要知道虚表指针,这就存在矛盾的地方,所以构造函数不可能是虚函数。构造函数虽然不能是虚函数,但构造函数可以调用虚函数。
5. 析构函数可以为虚函数,而且通常为虚函数。
79. 数组和链表的区别是什么?
答:数组和链表是两种不同的数据存储方式。链表的特性是在中间任意位置添加元素、删除元素都非常的快,不需要移动其他的元素。通常对于单链表而言,链表中每一个元素都要保存一个指向下一个元素的指针;而对于双链表,每个元素既要保存一个指向下一个元素的指针,还要保存一个指向上一个元素的指针;循环链表则在最后一个元素中保存一个指向第一个元素的指针。
数组是一组具有相同类型和名称的变量的集合,这些变量成为数组的元素,每个数组元素都有一个编号,这个编号成为数组的下标,可以通过下标来区别并访问这些元素,数组元素的个数也称为数组的长度。
具体而言,数组和链表的区别主要表现在以下几个方面:
1. 逻辑结构。 数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况。链表采用动态分配内存的形式实现,可以适应数据动态地增减的情况,需要时可以new/malloc分配内存空间,不需要时用delete/free将已分配的空间释放,不会造成内存空间浪费,且可以方便地插入,删除数据项。
2. 内存结构。(静态)数组从栈中分配空间,对于程序员方便快捷,但是自由度小。链表从堆中分配内存,自由度大,但是申请管理比较麻烦。
3. 数组中的数据内存中时顺序存储的,而链表是随机存储的。数据的随机访问效率高,可以直接定位,但插入、删除操作的效率比较低。链表在插入、删除操作上相对数组有很高的效率。而如果要访问链表中的某个元素,那就得从表头逐个遍历,知道找到所需要的元素为止,所以链表的随机访问效率比数组低。
4. 链表不存在越界问题,数组有越界问题。数组便于查询,链表便于插入、删除。数组节省空间但是长度固定,链表虽然变长但是占了更多的存储空间。
所以,由于数组存储效率高、存储速度快的优点,如果需要频繁访问数据,很少插入删除操作,则使用数组。反之,如果频繁插入删除,则应使用链表,两者各有用处。
80、何时选择顺序表、何时选择链表作为线性表的存储结构为宜?
答:顺序表按照顺序存储,即数据元素存放在一个连续的存储空间之中,实现顺序存取或直接存取。链表按照链表存储,即存储空间一般在程序的运行过程中动态分配与释放,且只要存储器中还有空间,就不会产生存储溢出的问题。
顺序表和链表各有长短,在选择时通常有以下几方面的考虑:
1. 空间。 顺序表的存储空间是静态分配的,链表的存储空间是动态分配的。当线性表的长度变化大,难以估量其存储规模时,采用动态链表,否则采用顺序表。
2. 时间。 顺序表是随机存取结构,若线性表的操作主要是进行查找,很少做插入和删除操作时,采用顺序表做存储结构为宜;反之,若需要对线性表进行频繁的插入或删除等操作时,宜采用链表做存储结构。并且,若链表的插入和删除主要发生在表的首尾两端,则采用尾指针表示的单链表为宜。
81. 栈与队列的区别有哪些?
答: 栈与队列是在程序设计中被广泛使用的良种重要的线性数据结构,都是在一个特定范围的存储单元中存储的数据,这些数据都可以重新被取出使用,与线性表相比,它们的插入和删除操作受到更多的约束和限制,故又称为限定性的线性结构。不同的是,栈是先进后出,队列是先进先出。
82.vector 与list的区别有哪些?
答:vector为存储的对象分配一块连续的地址空间,对vector中的元素随机访问效率很高。在vector中插入或者删除某个元素,需要将现有元素进行复制、移动。如果vector中存储的对象很大,或者构造函数复杂,则在对现有元素进行拷贝时开销较大,因为拷贝对象要调用拷贝构造函数。对于简单的小对象,vector的效率优于list。Vector在每次扩张容量的时候,将容量扩展两倍,这样对于小对象来说,效率是很高的。
Vector 内部使用顺序存储,访问速度快,答你是删除数据比较耗费性能。List内部使用脸色存储,访问速度慢,但是删除数据较快。
一般应遵循下面的原则:
1. 需要高效的随机存取,而不在乎插入和删除的效率,使用vector。
2. 需要大量的插入和删除,而不关心随机存取,则应使用list。
3 需要随机存取,而且关心两端数据的插入和删除,则应使用deque.