为什么构造函数不能是虚函数,析构函数往往是虚函数?

静态存储区。无论在那里构建,其过程都是两步:首先,分配一块内存;其次,调用构造函数。好,问题来了,如果构造函数是虚函数,那么就需要通过vtable 

调用,但此时面对一块 raw memeory,到哪里去找 vtable 呢?毕竟,vtable 是在构造函数中才初始化的啊,而不是在其之前。因此构造函数不能为虚函数。 

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。

析构函数可以是虚函数,且常常如此,这个就好理解了,因为此时 vtable 已经初始化了;况且我们通常通过基类的指针来销毁对象,如果析构函数不为虚的话,就

不能正确识别对象类型,从而不能正确销毁对象。

 

线程和进程

概念:

进程是表示资源分配的基本单位,又是调度运行的基本单位。

线程是进程中执行运算的最小单位,亦即执行处理机调度的基本单位。

 

好处:

(1)易于调度。

(2)提高并发性。通过线程可方便有效地实现并发性。进程可创建多个线程来执行同一程序的不同部分。

(3)开销少。创建线程比创建进程要快,所需开销很少。。

(4)利于充分发挥多处理器的功能。通过创建多线程进程(即一个进程可具有两个或更多个线程),每个线程在一个处理器上运行,从而实现应用程序的并发性,使每个处理器都得到充分运行。

 

关系:

(1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位。

(2)资源分配给进程,同一进程的所有线程共享该进程的所有资源。 同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。

(3)处理机分给线程,即真正在处理机上运行的是线程。

(4)线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。

 

多态

多态分为静态多态和动态多态。

静态多态:函数重载,泛型编程

动态多态:虚函数

(1)静态多态

静态多态是编译器在编译期间完成的,编译器会根据实参类型来选择调用合适的函数,如果有合适的函数可以调用就调,没有的话就会发出警告或者报错。。。比较简单,不做多介绍。 

 

(2)动态多态

显然这和静态多态是一组反义词,它是在程序运行时根据基类的引用(指针)指向的对象来确定自己具体该调用哪一个类的虚函数。

动态多态的条件: 
●基类中必须包含虚函数,并且派生类中一定要对基类中的虚函数进行重写。 
●通过基类对象的指针或者引用调用虚函数。

 

总结一道面试题:那些函数不能定义为虚函数?

①友元函数,它不是类的成员函数 
②全局函数 
③静态成员函数,它没有this指针 
构造函数,拷贝构造函数,以及赋值运算符重载(可以但是一般不建议作为虚函数)

摘自:https://blog.csdn.net/qq_39412582/article/details/81628254

 

有了 malloc/free 为什么还要 new/delete  ?

malloc 与 free 是 C++/C 语言的标准库函数,new/delete 是 C++的运算符。它们都可用于申请动态内存和释放内存。 

对于非内部数据类型的对象而言,光用 maloc/free 无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。

由于malloc/free 是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于 malloc/free

因此 C++语言需要一个能完成动态内存分配和初始化工作的运算符 new,以及一个能完成清理与释放内存工作的运算符 delete。

注意 new/delete 不是库函数

 

重载,重写和重定义

重载overload:是函数名相同,参数列表不同 重载只是在类的内部存在。但是不能靠返回类型来判断。   重写override:也叫做覆盖。 子类重新定义父类中有相同名称和参数的虚函数。函数特征相同。但是具体实现不同,主要是在继承关系中出现的 。   重写需要注意: 1 被重写的函数不能是static的。 必须是virtual的 2 重写函数必须有相同的类型,名称和参数列表 3 重写函数的访问修饰符可以不同。尽管virtual是private的,派生类中重写改写为public,protected也是可以的  

重定义 (redefining)也叫做隐藏:

子类重新定义父类中有相同名称的非虚函数 ( 参数列表可以不同 ) 。

 

C++内存管理

摘自:https://www.cnblogs.com/mrlsx/p/5411874.html,很全面。

在c++中内存主要分为5个存储区:

全局/静态存储区:全局变量静态变量分配到该区,到程序结束时自动释放,包括DATA段(全局初始化区)与BBS段(全局未初始化段)。其中,初始化的全局变量和静态变量存放在DATA段,未初始化的全局变量和静态变量存放在BBS段。BBS段特点:在程序执行前BBS段自动清零,所以未初始化的全局变量和静态变量在程序执行前已经成为0.

栈(Stack):局部变量函数参数等存储在该区,由编译器自动分配和释放.栈属于计算机系统的数据结构,进栈出栈有相应的计算机指令支持,而且分配专门的寄存器存储栈的地址,效率分高,内存空间是连续的,但栈的内存空间有限。

堆(Heap):需要程序员手动分配和释放(new,delete),属于动态分配方式。内存空间几乎没有限制,内存空间不连续,因此会产生内存碎片。操作系统有一个记录空间内存的链表,当收到内存申请时遍历链表,找到第一个空间大于申请空间的堆节点,将该节点分配给程序,并将该节点从链表中删除。一般,系统会在该内存空间的首地址处记录本次分配的内存大小,用于delete释放该内存空间。

 

文字常量区:存放常量,而且不允许修改。程序结束后由系统释放。

程序代码区:存放程序的二进制代码

 

使用存储区的三种方式:
1)静态存储区(Static Memory)

全局变量,静态变量及静态类成员存储在该区,在编译期间就进行分配,生存期到程序结束。存储在该区的对象只初始化一次,且在程序运行期间地址固定不变。

2)自动存储区(Autormatic Memory)

局部变量,函数参数等存储在该区,由编译器自动分配和释放

3)自由存储区(Free Store)

由程序员手动分配和释放内存(new,delete)

 

堆和栈的区别:

1)空间大小:栈的内存空间是连续的,空间大小通常是系统预先规定好的,即栈顶地址和最大空间是确定的;而堆得内存空间是不连续的,由一个记录空间空间的链表负责管理,因此内存空间几乎没有限制,在32位系统下,内存空间大小可达到4G

2)管理方式:栈由编译器自动分配和释放,而堆需要程序员来手动分配和释放,若忘记delete,容易产生内存泄漏。

3)生长方向不同:对于栈,他是向着内存地址减小的方向生长的,这也是为什么栈的内存空间是有限的;而堆是向着内存地址增大的方向生长的

4)碎片问题:由于栈的内存空间是连续的,先进后出的方式保证不会产生零碎的空间;而堆分配方式是每次在空闲链表中遍历到第一个大于申请空间的节点,每次分配的空间大小一般不会正好等于申请的内存大小,频繁的new操作势必会产生大量的空间碎片

5)分配效率:栈属于机器系统提供的数据结构,计算机会在底层对栈提供支持,出栈进栈由专门的指令执行,因此(栈)效率较高。而堆是c/c++函数库提供的,当申请空间时需要按照一定的算法搜索足够大小的内存空间,当没有足够的空间时,还需要额外的处理,因此效率较低。

 

使用内存时几点注意事项:

1)用new和malloc申请内存时,在使用前要检查内存是否分配成功

char *p=new char[10];
if(p==NULL)
return;

2)使用内存之前要进行初始化

3)在对内存进行操作时,防止越界,如数组操作要注意下标范围

4)对于动态分配的内存,一定要手动释放,否则程序每运行一次就会丢失一部分内存,造成内存泄漏

5)防止内存释放后继续使用它,主要有以下三种情况:

a.程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。

b.函数的return语句写错了,注意不要返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。

c.使用free或delete释放了内存后,没有将指针设置为NULL。导致产生“野指针”。

野指针:“野指针”不是NULL指针,是指向“垃圾”内存的指针。人们一般不会错用NULL指针,因为用if语句很容易判断。但是“野指针”是很危险的,if语句对它不起作用。

“野指针”的成因主要有三种:

(a)指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存

char *p;   //此时p为野指针

 

(b)指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针.

char *p=new char[10];  //指向堆中分配的内存首地址
cin>> p;
delete []p; //p重新变为野指针

 

(c)指针操作超越了变量的作用范围。

char *p=new char[10];  //指向堆中分配的内存首地址
cin>> p;
cout<<*(p+10);  //可能输出未知数据

 

6)指针的注意点:

a.指针指向常量存储区对象

char *p="abc";

此时p指向的是一个字符串常量,不能对*p的内容进行写操作,如srtcpy(p,s)是错误的,因为p的内容为“abc”字符串常量,该数据存储在常量存储区,但可以对指针p进行操作,让其指向其他的内存空间。

b.资源泄漏

char *p=new char[3];  //分配三个字符空间,p指向该内存空间

p="ab";   //此时p指向常量“ab”,而不再是new char分配的内存空间了,从而造成了资源泄漏

delete []p;         //释放时报错

c.内存越界

char *p=new char[3];  //分配三个字符空间,p指向该内存空间

strcpy(p,"abcd");  //将abcd存处在分配的内存空间中,由于strlen("abcd")=4>3,越界

delete []p;  //释放时出错

 

注:p="ab"和strcpy(p,"ab"),含义不一样,前者指针p指向常量“ab”存储区域的首地址,改变了p最开始指向的new申请的内存空间;而后者是将“ab”分配到new申请的内存空间中;

关于指针具体看:http://www.cnblogs.com/mrlsx/p/5419030.html

 

const和#define(宏常量),const有哪些优势?

(1)数据类型:const常量有数据类型,而宏常量没有。因此编译器可以对前者进行安全检查。后者只是进行字符替换,没有类型安全检查,可能会有意料之外的错误。

(2)调试功能:有些集成化的调试工具可以对const常量进行调试,但不能对宏进行调试。inline内联函数也有类型检查。

 

 

 

引用与指针的区别是什么?

指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作。程序中使用指针,程序的可读性差;

引用本身就是目标变量的别名,对引用的操作就是对目标变量的操作。

 

1) 引用必须被初始化,指针不必。

2) 引用初始化以后不能被改变,指针可以改变所指的对象。

3) 不存在指向空值的引用,但是存在指向空值的指针,引用会更安全。

 

 volatile表示什么?有什么作用?

易变的,不会被编译器进行优化,让程序取数据直接去内存中的。

用来解决变量在“共享”环境下容易出现读取错误的问题。

摘自:https://www.jianshu.com/p/2de5b739178a

 

例: volatile int i=10;

volatile 指出 i 是随时可能发生变化的,每次使用它的时候必须从 i的地址中读取。

摘自:https://www.cnblogs.com/yc_sunniwell/archive/2010/07/14/1777432.html

 

STL中map和set的原理(关联式容器)

map和set的底层实现主要通过红黑树来实现

红黑树是一种特殊的二叉查找树

1)每个节点或者是黑色,或者是红色 

2)根节点是黑色

3) 每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]

4)如果一个节点是红色的,则它的子节点必须是黑色的

5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

 

特性4)5)决定了没有一条路径会比其他路径长出2倍,因此红黑树是接近平衡的二叉树。

 

 

 

 

还在不断整理。。。。。

有不少是整理自《经典C++面试题目100例》,不一一列举,原出处:https://blog.csdn.net/weixin_41168353/article/details/80083861

 

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄