文章目录
- 1. C和C++的区别
- 2. 有什么区别
- 3. C++文件编译与执行的四个阶段
- 4. 堆和栈有什么区别
- 5. 深拷贝和浅拷贝的区别
- 6. 哪些成员函数不能被继承
- 7. 基类的析构函数为什么要用virtual虚析构函数
- 8. 哪些函数不能声明成虚函数
- 9. 解释多态,虚函数,纯虚函数
- 10. 如何初始化const和static数据成员
- 11. 关键字static有什么作用
- 12. 指针和引用的区别
- 13. delete与 delete []区别
- 14. C++中new/delete 和malloc/free的区别
- 15. 简单介绍vector内存分配方式
- 16. 简单介绍STL中的map和unordered_map(hash_map)
- 17. 红黑树的特性与其在C++ STL中的应用
- 18. “常量指针”和“指针常量”有什么区别?
- 19. C++ vector和list的区别
- 20. 解释静态绑定和动态绑定
- 21. 描述内存分配方式以及它们的区别
- 22. `extern “C”`有什么作用
- 23. 栈溢出是什么
- 24. sizeof() 用法总结
- 25. C++空类默认有哪些成员函数?
- 26. 哪一种成员变量可以在一个类的实例之间共享
- 27. 为什么构造函数不能为虚函数
- 28. 描述进程和线程的区别
- 29. static数据成员和static成员函数区别
- 30. 多态类中的虚函数表是编译时建立,还是运行时时建立的?
- 31. C++函数值的传递方式有哪几种
- 32. 内联函数在编译时是否做参数类型检查?
- 33. 全局变量和局部变量有什么区别?操作系统和编译器是怎么知道的?
- 34. static全局变量与普通的全局变量有什么区别?static局部变量和普通局部变量有什么区别?static函数与普通函数有什么区别?
- 35. 排序算法比较
1. C和C++的区别
-
C是面向过程的语言,是一个结构化的语言,考虑如何通过一个过程对输入进行处理得到输出;C++是面向对象的语言,主要特征是封装、继承和多态。 封装隐藏了实现细节,使得代码模块化;派生类可以继承父类的数据和方法,扩展了已经存在的模块,实现了代码重用;多态则是“一个接口,多种实现”,通过派生类重写父类的虚函数,实现了接口的重用。
-
C和C++动态管理内存的方法不一样,C是使用malloc/free,而C++除此之外还有new/delete关键字。
-
C++支持函数重载,C不支持函数重载。
-
C++中有引用,C中不存在引用的概念。
2. 有什么区别
#include<file.h>#include “file.h” 有什么区别
前者是从标准库路径寻找,后者是从当前工作路径
3. C++文件编译与执行的四个阶段
1)预处理:根据文件中的预处理指令来修改源文件的内容
2)编译:编译成汇编代码
3)汇编:把汇编代码翻译成目标机器指令
4)链接:链接目标代码生成可执行程序
4. 堆和栈有什么区别
栈 stack:存放函数的参数值、局部变量,由编译器自动分配释放
堆heap:是由new分配的内存块,由应用程序控制,需要程序员手动利用delete释放,如果没有,程序结束后,操作系统自动回收
-
因为堆的分配需要使用频繁的new/delete,造成内存空间的不连续,会有大量的碎片
-
堆的生长空间向上,地址越大,栈的生长空间向下,地址越小
5. 深拷贝和浅拷贝的区别
深拷贝和浅拷贝可以简单的理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,如果资源重新分配了就是深拷贝;反之没有重新分配资源,就是浅拷贝(如下图所示)。
6. 哪些成员函数不能被继承
C++中,并不是所有的成员函数都能被子类继承,有三类成员函数不能被子类继承,分别是:构造函数(包括拷贝构造)、析构函数、赋值运算符重载函数。
7. 基类的析构函数为什么要用virtual虚析构函数
防止内存泄露:在virtual虚析构函数情况下,delete p(基类)的时候,它很机智的先执行了派生类的析构函数,然后执行了基类的析构函数。
如果基类的析构函数不是虚函数,在delete p(基类)时,调用析构函数时,只会看指针的数据类型,而不会去看赋值的对象,即只调用了基类的析构函数,这样就会造成内存泄露。
8. 哪些函数不能声明成虚函数
在C++,有五种函数不能被声明成虚函数,分别是:非成员函数、构造函数、静态成员函数、内联成员函数、友元函数这五种,下面分别解释为什么这五种函数不能被声明成虚函数。
- 非成员函数
非成员函数只能被重载(overload),不能被继承重写(override),而虚函数主要的作用是在继承中实现动态多态,非成员函数早在编译期间就已经绑定函数了,无法实现动态多态,那声明成虚函数还有什么意义呢?
- 构造函数
要想调用虚函数必须要通过“虚函数表”来进行的,但虚函数表是要在对象实例化之后才能够进行调用。而在构造函数运行期间,还没有为虚函数表分配空间,自然就没法调用虚函数了。
- 静态成员函数
静态成员函数对于每个类来说只有一份,所有的对象都共享这一份代码,它是属于类的而不是属于对象。虚函数必须根据对象类型才能知道调用哪一个虚函数,故虚函数是一定要在对象的基础上才可以的,两者一个是与实例相关,一个是与类相关。
- 内联成员函数
内联函数是为了在代码中直接展开,减少函数调用花费的代价,虚函数是为了在继承后对象能够准确的执行自己的动作,并且inline函数在编译时被展开,虚函数在运行时才能动态地绑定函数。
- 友元函数
因为C++不支持友元函数的继承,对于没有继承特性的函数没有虚函数的说法。友元函数不属于类的成员函数,不能被继承。
9. 解释多态,虚函数,纯虚函数
多态:是对于不同对象接收相同消息时产生不同的动作。C++的多态性具体体现在运行和编译两个方面:
(1)在程序运行时的多态性通过继承和虚函数来体现;
(2)在程序编译时多态性体现在函数和运算符的重载上;
虚函数:在基类中冠以关键字 virtual 的成员函数。 它提供了一种接口界面。允许在派生类中对基类的虚函数重新定义。
纯虚函数:在基类中为其派生类保留一个函数的名字,以便派生类根据需要对它进行定义。作为接口而存在纯虚函数不具备函数的功能,一般不能直接被调用。
从基类继承来的纯虚函数,在派生类中仍是虚函数。只要类中存在纯虚函数,那么这个类被称为抽象类(abstract class)。
抽象类中不仅包括纯虚函数,也可包括虚函数。抽象类必须用作派生其他类的基类,而不能用于直接创建对象实例。但仍可使用指向抽象类的指针支持运行时多态性。
10. 如何初始化const和static数据成员
通常在类外初始化static数据成员,但是 static const 的整型(bool,char,int,long)可以再类声明中初始化,static const的其他类型也必须在类外初始化(包括整型的数组)。
static表示的是静态的。类的静态成员函数、静态成员变量是和类相关的,而不是和类的具体对象相关的。即使没有具体对象,也能调用类的静态成员函数和成员变量。一般类的静态函数几乎就是一个全局函数,只不过它的作用域限于包含它的文件中。
在C++中,static静态成员变量不能在类的内部初始化。在类的内部只是声明,定义必须在类定义体的外部,通常在类的实现文件中初始化,如:double Account::Rate = 2.25;
。static关键字只能用于类定义体内部的声明中,定义时不能标示为static
在C++中,const成员变量也不能在类定义处初始化,只能通过构造函数初始化列表进行,并且必须有构造函数。
const数据成员 只在某个对象生存期内是常量,而对于整个类而言却是可变的。因为类可以创建多个对象,不同的对象其const数据成员的值可以不同。所以不能在类的声明中初始化const数据成员,因为类的对象没被创建时,编译器不知道const数据成员的值是什么。
const定义的常量在超出其作用域之后其空间会被释放,而static定义的静态常量在函数执行后不会释放其存储空间。
11. 关键字static有什么作用
1)函数体内: static 修饰的局部变量作用范围为该函数体,不同于auto变量,其内存只被分配一次,因此其值在下次调用的时候维持了上次的值
2)模块内:static修饰全局变量或全局函数,可以被模块内的所有函数访问,但是不能被模块外的其他函数访问,使用范围限制在声明它的模块内
3)类中修饰成员变量:表示该变量属于整个类所有,对类的所有对象只有一份拷贝
4)类中修饰成员函数:表示该函数属于整个类所有,不接受this指针,只能访问类中的static成员变量
注意和const的区别!!!const强调值不能被修改,而static强调唯一的拷贝,对所有类的对象
12. 指针和引用的区别
- 引用是变量的一个别名,内部实现是只读指针
- 引用只能在初始化时被赋值,其他时候值不能被改变,指针的值可以在任何时候被改变
- 引用不能为NULL,指针可以为NULL
- 引用变量内存单元保存的是被引用变量的地址
- “sizeof 引用" = 指向变量的大小 , “sizeof 指针”= 指针本身的大小
- 引用可以取地址操作,返回的是被引用变量本身所在的内存单元地址
13. delete与 delete []区别
MemTest *mTest1=new MemTest[10];
MemTest *mTest2=new MemTest;
Int *pInt1=new int [10];
Int *pInt2=new int;
delete[]mTest1;//-1-
delete[]mTest2;//-2-
delete[]pInt1; //-3-
delete[]pInt2; //-4-
在-2-
处会报错。
这就说明:对于内建简单数据类型,delete和delete[]功能是相同的。对于自定义的复杂数据类型,delete和delete[]不能互用。delete[]删除一个数组,delete删除一个指针。简单来说,用new分配的内存用delete删除;用new[]分配的内存用delete[]删除。delete[]会调用数组元素的析构函数。内部数据类型没有析构函数,所以问题不大。如果你在用delete时没用方括号,delete就会认为指向的是单个对象,否则,它就会认为指向的是一个数组。
14. C++中new/delete 和malloc/free的区别
-
new、delete是C++中的操作符,而malloc和free是标准库函数。
-
对于非内部数据对象来说,只使用malloc是无法完成动态对象要求的,一般在创建对象时需要调用构造函数,对象消亡时,自动的调用析构函数。而malloc / free是库函数而不是运算符,不在编译器控制范围之内,不能够自动调用构造函数和析构函数。而new在为对象申请分配内存空间时,可以自动调用构造函数,同时也可以完成对对象的初始化。同理,delete也可以自动调用析构函数。而mallloc只是做一件事,只是为变量分配了内存,同理,free也只是释放变量的内存。
-
new返回的是指定类型的指针,并且可以自动计算所申请内存的大小。而 malloc需要我们计算申请内存的大小,并且在返回时强行转换为实际类型的指针。
15. 简单介绍vector内存分配方式
先申请一定的大小的数组, 当数组填满之后,另外申请一块原数组两倍大的新数组(保证装填因子大于50%), 然后把原数组的数据拷贝到新数组, 最后释放原数组的大小。
16. 简单介绍STL中的map和unordered_map(hash_map)
-
map
map的内部数据的组织基于红黑树实现(实际上是二叉排序树和非严格意义上的二叉平衡树),红黑树具有自动排序的功能,因此map内部所有的数据是有序的,map的查询、插入、删除操作的时间复杂度都是O(logN)。 -
unordered_map(hash_map)
C++ 11标准中加入了unordered系列的容器。unordered_map记录元素的hash值,根据hash值判断元素是否相同。unordered_map和map类似,都是存储key-value对,可以通过key快速索引到value,不同的是unordered_map不会根据key进行排序。哈希表中进行添加,删除,查找等操作,性能十分之高,不考虑哈希冲突的情况下,仅需一次定位即可完成,时间复杂度为O(1)。
unordered_map是新标准对hash_map的改进,无论是查找、插入的性能,unordered_map的效率都优于hash_map,更优于map;而空间复杂度方面,hash_map最低,unordered_map次之,map最大。
17. 红黑树的特性与其在C++ STL中的应用
map 、set、multiset、multimap的底层实现都是红黑树。
红黑树的特性:
(1)根节点是黑色
(2)不能有两个连续的红节点
(3)空指针是黑色
(4)从任意一个结点出发,到后代中空指针的路径上,均包含相同数量的黑色结点。
18. “常量指针”和“指针常量”有什么区别?
#include <iostream>
using namespace std;
int main() {
int a = 10;
int b = 20;
// 常量指针: 指针指向的值不可以改,指针的指向可以改
const int *p1 = &a;
// *p1 = 30; 错
// p1 = &b; 对
//指针常量:指针指向的值可以改,指针的指向不可以改
int *const p2 = &a;
// *p2 = 30; 对
// p2 = &b; 错
const int *const p3 = &a;
// *p3 = 30; 错
// p3 = &b; 错
return 0;
}
19. C++ vector和list的区别
-
vector数据结构
vector和数组类似,拥有一段连续的内存空间,并且起始地址不变。
因此能高效的进行随机存取,时间复杂度为O(1);
但因为内存空间是连续的,所以在进行插入和删除操作时,会造成内存块的拷贝,时间复杂度为O(n)。
另外,当数组中内存空间不够时,会重新申请一块内存空间并进行内存拷贝。 -
list数据结构
list是由双向链表实现的,因此内存空间是不连续的。
只能通过指针访问数据,所以list的随机存取非常没有效率,时间复杂度为O(n);
但由于链表的特点,能高效地进行插入和删除,时间复杂度为O(1)。
20. 解释静态绑定和动态绑定
静态绑定和动态绑定是实现C++多态性的方法
静态绑定:绑定的是对象的静态类型,函数依赖于对象的静态类型,在编译期确定
动态绑定:绑定的是对象的动态类型,函数依赖于对象的动态类型,在运行期确定
只有虚函数才使用的是动态绑定,其他的全部是静态绑定
21. 描述内存分配方式以及它们的区别
(1) 在栈上创建:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈是一种线性结构,进程的每个线程都有私有的“栈”。。
(2) 从堆上分配:亦称动态内存分配,堆是一种链式结构,程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多。程序通过堆栈的基地址和偏移量来访问本地变量。
(3) 从静态存储区域(也叫全局区)分配:内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。
(4)文字常量区:常量字符串就放在这里,程序结束自动释放;
(5)程序代码区:参访函数体的二进制代码。
22. extern “C”
有什么作用
首先,extern是C/C++语言中表明函数和全局变量作用范围的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。
通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。extern "C"是连接申明,被extern "C"
修饰的变量和函数是按照C语言方式编译和连接的。作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在符号库中的名字与C语言的不同。例如,假设某个函数的原型为:void foo( int x, int y);
该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。
所以,可以用一句话概括extern “C”这个声明的真实目的: 解决名字匹配问题,实现C++与C的混合编程,为了能够正确实现C++代码调用C语言代码。
23. 栈溢出是什么
Stack 是一块固定大小的连续内存,运行时由编译器管理,无需用户自行分配和回收;当函数调用嵌套层次非常深时会产生 Stack overflow(堆栈溢出)错误,如递归调用、循环调用、消息循环、大对象参数、大对象局部变量等都容易触发堆栈溢出。
24. sizeof() 用法总结
sizeof是C/C++中的一个操作符(operator),简单的说其作用就是返回一个对象或者类型所占的内存字节数(计算的是在栈中分配的内存大小)。
(1) sizeof不计算static变量(静态区变量)占的内存;
(2) 32位系统的指针的大小是4个字节,64位系统的指针是8字节,而不用管指针类型;
(3) char型占1个字节,int占4个字节,short int占2个字节,long int占4个字节,float占4字节,double占8字节,string占4字节,一个空类占1个字节,单一继承的空类占1个字节,虚继承涉及到虚指针所以占4个字节
(4) 数组的长度:若指定了数组长度,则不看元素个数,总字节数=数组长度*sizeof(元素类型);若没有指定长度,则按实际元素个数类确定(若是字符数组,则应考虑末尾的空字符)。
(5) 结构体对象的长度:在默认情况下,为方便对结构体内元素的访问和管理,当结构体内元素长度小于处理器位数的时候,便以结构体内最长的数据元素的长度为对齐单位,即为其整数倍。若结构体内元素长度大于处理器位数则以处理器位数为单位对齐。
(6) unsigned影响的只是最高位的意义,数据长度不会改变,所以sizeof(unsigned int)=4
(7) 自定义类型的sizeof取值等于它的类型原型取sizeof
(8) 对函数使用sizeof,在编译阶段会被函数的返回值的类型代替
(9) sizeof后如果是类型名则必须加括号,如果是变量名可以不加括号,这是因为sizeof是运算符
(10) 当使用结构类型或者变量时,sizeof返回实际的大小。当使用静态数组时返回数组的全部大小,sizeof不能返回动态数组或者外部数组的尺寸
25. C++空类默认有哪些成员函数?
默认构造函数、析构函数、复制构造函数、赋值函数
26. 哪一种成员变量可以在一个类的实例之间共享
static静态成员变量
27. 为什么构造函数不能为虚函数
-
构造一个对象的时候,必须知道对象的实际类型,而虚函数行为是在运行期间确定实际类型的。而在构造一个对象时,由于对象还未构造成功。编译器无法知道对象的实际类型,是该类本身,还是该类的一个派生类,或是更深层次的派生类。
-
虚函数的执行依赖于虚函数表。而虚函数表在构造函数中进行初始化工作,即初始化vptr,让他指向正确的虚函数表。而在构造对象期间,虚函数表还没有被初始化,将无法进行。
28. 描述进程和线程的区别
做个简单的比喻:进程=火车,线程=车厢
- 线程在进程下行进(单纯的车厢无法运行)
- 一个进程可以包含多个线程(一辆火车可以有多个车厢)
- 不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘)
- 同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)
- 进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)
- 进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢)
- 进程可以拓展到多机,线程适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上)
- 进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-“互斥锁”
29. static数据成员和static成员函数区别
(1)static数据成员:static数据成员独立于该类的任意对象而存在;每个static数据成员是与类关联的对象,并不与该类的对象相关联。static数据成员(const static数据成员除外)必须在类定义体的外部定义。不像普通数据成员,static成员不是通过类的构造函数进行初始化,而是应该在定义时进行初始化。
(2)static成员函数:static成员函数没有this形参,它可以直接访问所属类的static成员,不能直接使用非static成员。因为static成员函数不是任何对象的组成部分,所以static成员不能被声明为const。同时,static成员函数也不能被声明为虚函数。
30. 多态类中的虚函数表是编译时建立,还是运行时时建立的?
虚函数表是在编译期就建立了,各个虚函数被组织成了一个虚函数的入口地址数组。而虚函数表中的虚函数表指针是在运行期,也就是构造函数被调用时进行初始化的,这是实现多态的关键。
31. C++函数值的传递方式有哪几种
三种传递方式为:值传递、指针传递和引用传递。
32. 内联函数在编译时是否做参数类型检查?
内联函数要做参数类型检查, 这是内联函数跟宏相比的优势。
33. 全局变量和局部变量有什么区别?操作系统和编译器是怎么知道的?
(1)生命周期不同:
- 全局变量随主程序创建和创建,随主程序销毁而销毁
- 局部变量在局部函数内部,甚至局部循环体等内部存在,退出就不存在; 内存中分配在全局数据区
(2)使用方式不同:通过声明后全局变量程序的各个部分都可以用到;局部变量只能在局部使用,分配在栈区
操作系统和编译器通过内存分配位置来知道的,全局变量分配在全局数据段并且在程序开始运行的时候被加载,局部变量则分配在堆栈里面 。
34. static全局变量与普通的全局变量有什么区别?static局部变量和普通局部变量有什么区别?static函数与普通函数有什么区别?
(1)static全局变量与普通全局变量区别:static全局变量只初使化一次,防止在其他文件单元中被引用;
(2)static局部变量和普通局部变量区别:static局部变量只被初始化一次,下一次依据上一次结果值;
(3)static函数与普通函数区别:static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝。
35. 排序算法比较