1. 常量必须在构造函数的初始化列表里面初始化。c++
class A { const int size = 0; }; 是错误的。 须要改为 class A{ A(){ const int size = 10; } };程序员
或者改为 class A { static const int size = 10; };算法
2. 构造函数的初始化列表初始化变量的顺序是根据成员变量的声明顺序来执行的。编程
好比:windows
1 class base 2 3 { 4 private: 5 6 int m_i; 7 8 int m_j; 9 10 public: 11 12 base(int i): m_j(i), m_i(m_j) {}
就有错误,这里的m_i将会被初始化成一个随机值。数组
3. 基类的析构函数必须是虚函数。由于这样处理后,全部子类的析构函数都将会被自动变为virtual类型,这就保证了在任何状况下,不会出现因为析构函数未被调用而致使的内存泄露。安全
4. 为何不能将构造函数定义为虚函数?数据结构
虚拟函数是采用一种虚调用的办法。虚调用是一种能够在只有部分信息的状况下工做的机制,特别容许咱们调用一个只知道接口而不知道其准确对象类型的函数。可是若是建立一个对象,你应该知道对象的准确对象,所以构造函数不能为虚函数。函数
5. 为何不能把每一个函数都声明为虚函数?优化
虚函数是由代价的:因为每一个虚函数都维护一个v表,所以在使用虚函数的时候会产生一个系统开销。若是仅仅是一个很小的类,且不想派生其余类,那么根本就没有必要使用虚函数。
6. 析构函数能够是内联函数吗?
答案是能够的。
7. 单参数的构造函数若是不添加explicit关键字,会定义一个隐含的类型转换,添加explicit关键字会消除这种类型转换。
8. 若是类中包含指针的话,通常须要编写拷贝构造函数和拷贝赋值运算符,以免浅拷贝。
9. 为何须要友元?
类具备封装和信息隐藏的特性。只有类的成员函数才能访问类的私有成员,程序中的其余函数是没法访问私有成员的。非成员函数能够访问
类的公有成员,可是若是将数据成员都定义成公有的,这又破坏了隐藏的特性。另外,应该看到在某些状况下,特别是在对某些成员函数屡次调
用时,因为参数传递,类型检查和安全性检查都须要时间开销,而影响程序的运行效率。
为了解决这个问题,提出了一种使用友元的方案。友元是一种定义在类外部的普通函数。但它须要在类体内进行说明,为了与该类的成员函数加
以区别,在说明时前面加以关键字friend。友元不是成员函数,可是能够访问类的私有成员。做用在于提升程序的运行效率,可是,它破坏了类
的封装性和隐藏性,使得非成员函数能够访问类的私有成员。
10. C++中如何阻止一个类被实例化?
使用抽象类,或者构造函数被声明为private
11. 何时编译器会生成copy constructor呢?
只要本身没有编写,程序中须要,都会生成
12. C++引入的开销体如今如下两个方面:
1.编译时的开销
模板,类层次结构,强类型检查等新特性,以及大量使用了这些新特性的C++模板,算法库都明显地增长了C++编译器的负担。可是应当看到,这些新技能在不增长程序执行效率的前提下,明显下降了广大C++程序员的工做量。
2.运行时的开销
相对于传统的C程序而言,C++中可能引入额外运行时的开销特性包括:
虚基类,虚函数,RTTI(dynamic_cast, typeid), 异常以及对象的构造和析构
虚基类,从直接虚继承的子类中访问虚基类的数据成员或其虚函数时,将增长两次指针引用(大部分状况下能够优化为一次)和一次整型加法的时间开销。定义一个虚基类表,定义若干虚基类表指针的空间开销。
虚函数的运行开销有进行整形加法和指针引用的时间开销。定义一个虚表,定义若干个虚表指针的空间开销。
RTTI的运行开销主要有进行整形比较和取址操做(可能还会有一两次整形加法)所增长的时间开销。定义一个type_info对象(包括类型ID和类名称)的空间开销。"dynamic_cast"用于在类层次结构中漫游,对指针或者引用进行自由地向上,向下或者交叉转化。"typeid"则用于获取一个对象或者引用的确切类型。通常地讲,能用虚函数解决的问题就不要用"dynamic_cast",可以用"dynamic_cast"解决的就不用"typeid".
关于异常,对于几乎全部的编译器来讲,在正常状况下,try块中的代码执行效率和普通代码同样高,并且因为不须要使用传统的经过返回值函数调用来判断错误的方式,代码的执行效率还会进一步提升。抛出和捕捉异常的开销也只是在某些状况下会高于函数返回和函数调用的开销。
关于构造和析构,开销也不老是存在的。对于不须要初始化/销毁的类型,并无构造和析构的开销,相反对于那些须要初始化/销毁的类型来讲,即便使用传统的C方式来实现,也至少须要与之至关的开销。
固然,RTTI是有用的。但由于一些理论上以及方法论上的缘由,它破坏了面向对象的纯洁性。
首先,它破坏了抽象,使得一些原本不该该被使用的方法和属性被不正确地使用。其次,由于运行时类型的不肯定性,它把程序变得更脆弱。第三点,也是最重要的一点,它使得程序缺少扩展性。当加入一个新的特性时候,你也许须要仔细阅读你的dynamic_cast或者instanceof的代码,必要时改动它们,以保证这个新的类型加入不会致使问题。而这个过程当中,编译器将不会给你任何帮助。
13. 请描述heap与stack的区别
在进行c/c++编程时,须要程序员对内存的了解比较精准。常常须要操做的内存能够分为:
栈区:由编译器自动分配和释放,存放函数的参数值,局部变量的值等。其操做方式相似于数据结构中的栈
堆区:通常由程序员分配和释放,若程序员不释放,程序结束时可能由操做系统回收。注意它与数据结构中的堆是两回事,分配方式却是相似链表。
全局区(静态区): 全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未释放的静态变量在相邻的另外一块区域。程序结束后由系统释放。
文字常量区:常量字符串就是放这里的,程序结束后由系统释放。
程序代码区:存放函数体的二进制代码。
1 int a = 0; // 全局初始化区 2 char *p1; // 全局未初始化 3 main() 4 { 5 int b; // 栈 6 char s[] = "abc"; // 栈 7 char *p2; // 栈 8 char *p3 = "123456"; // "123456"在常量区,p3在栈上 9 static int c = 0; // 全局(静态)初始化区 10 p1 = (char *)malloc(10); 11 p2 = (char *)malloc(20); 12 // 分配得来的10和20字节的区域就在堆区 13 strcpy(p1, "123456"); 14 //"123456"放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方 15 }
堆和栈的理论知识:
1. 申请方式
栈:由系统自动分配。好比,声明在函数中的一个局部变量int b,系统自动在栈中为b开辟空间。
堆:须要程序员本身申请,并指明大小,在C中使用malloc函数。
好比 p1 = (char *)malloc(10);
在C++中使用new运算符 ,好比:
p2 = new char;
可是,注意p1,p2自身在栈中的。
2. 申请后的系统的响应
栈:只要栈的剩余空间大于所申请的空间,系统将为程序提供内存,不然将报以长提示栈溢出。
堆:首先应该知道操做系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历链表,寻找第一个空间大于所申请空间的堆节
点,而后将该节点从空闲节点链表中删除,并将该节点的空间分配给程序。对于大多数系统,会在这块内存空间中的首地址处记录本次分配的
大小,这样,代码的delete语句才能正确地释放本内存空间。另外,因为找到的堆节点的大小不必定正好等于申请的大小,系统会自动将多余的
那部分从新加入空余链表中。
3. 申请大小的限制
栈:在windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在windows下,栈的大小是2MB,也有的说是1MB,总之是一个编译时候就肯定的常数。若是申请的空间超过栈的剩余空间,将提示overflow。所以,能从栈得到的空间较小。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是因为系统是链表存储空闲内存地址的,天然是不连续的。而链表的遍历方向是由低地址向高地址,堆的大小受限于计算机系统中有效的虚拟内存。因而可知,堆得到的空间比较灵活,也比较大。
申请效率的比较:
栈:由系统自动分配,速度较快。但程序员没法控制。
堆:使用malloc/new分配的,通常速度比较慢,并且容易产生内存碎片,不过用起来最方便。另外,在windows下,最好的方式是用virtualAlloc分配内存。不是在堆,也不是在栈,而是直接在进程的地址空间中保留一块内存,虽然用起来最不方便,可是速度最快,也最灵活。
堆和栈中的存储内容
栈:在函数调用时候,第一个进栈的是主函数中的下一条指令(函数调用语句的下一条可执行语句)的地址,而后是函数的各个参数。在大多数的
C编译器中,参数是由右往左入栈的,而后是函数的局部变量。注意静态变量是不入栈的。
当本次函数调用结束后,局部变量先出栈,而后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运
行。
堆:通常是在堆得头部用一个字节表示存放堆的大小。堆中的具体内容由程序员安排。
存取效率的比较
char s1[] = "aaaaaa";
char *s2 = "bbbbb";
"aaaaaa"是在运行时刻赋值的,而"bbbbb"是在编译时刻就肯定的。可是,在之后的存取中,在栈上的数组比指针所指向的字符串快。