C++基础概念
C++基础概念 1
1.linux中静态库和动态库的异同 3
2.是否了解动态规划 11
3.STL源码中的map实现(红黑树的五点要求) 12
B+树 13
4.map和unorder_map(hash_map)的区别 14
5.new一个对象涉及几个步骤?其中哪个步骤可以通过重载new操作符来修改 15
6.malloc一次性最大能申请多大内存空间 16
7.new和malloc的区别 17
8.delete和free的区别 19
9.什么时候必须用delete[] 20
10.静态多态 动态多态 21
11.动态绑定(后期绑定,运行时绑定),静态绑定(前期绑定,编译时绑定) 21
12.学过的设计模式? 状态机模式,简单工厂,策率模式,享元模式,模板模式 1次 24
13.指针和引用的区别: 24
14.C语言的代码内存布局详解 26
15.内存中堆和栈的区别 29
16.用户进程内存空间 30
17.类型转换有哪些?(四种类型转换,分别举例说明) 30
18.内存对齐原则 33
19.内联函数(讲了一下内联函数的优点以及和宏定义的区别) 35
20.typedef和#define的用法与区别 37
21.const 和宏定义#define的区别 39
22.链接指示:extern “C” 40
23.extern与include 44
24.继承机制中对象之间,引用和指针之间如何转换 45
25.如何实现只能动态分配类对象,不能定义类对象 46
26模板的优缺点 47
27.模板特化 48
28.explicit作用: Primer P264 50
29.strcpy函数 53
30.memcpy的缺陷 55
31.内存溢出,内存泄漏 56
32.查看内存泄漏的软件都有什么 58
33.初始化列表 59
34.public、protected、private访问属性及继承 60
35.智能指针 61
36.虚函数和纯虚函数 64
37.构造函数为什么不能是虚函数,虚析构函数是用来做什么的 65
38.pthread_create()的函数(Linux环境中的一个创建线程函数)原型写出来 66
39.C中volatile关键字 67
40.为什么父类指针可以指向子类反之则不行 68
41.stl中的list中的size函数是如何实现的?是遍历还是设置一个变量来保存?让你实现,你倾向哪一种,为什么? 69
42.子类a多重继承于b和c,b和c都有虚函数,此时a中有几张虚函数表?有几个虚函数指针?当依次调用分属b和c的虚函数时,虚函数指针怎么变化? 70
43.浅拷贝还是深拷贝 72
44.拷贝构造函数和赋值运算符的区别 76
45.系统调用里有sleep(),usleep()函数,usleep()函数号称自己是微秒级,你相信它真的能达到这么快吗?或者说系统调用可以达到微秒速度吗? 77
46.重载、覆盖、隐藏 79
47.this, const成员函数、mutable类型 80
48.STL内存管理 81
49.static 全局变量 和 非static全局变量的区别 83
50.编写makefile文件 84
51.coredump产生的几种可能情况 85
52.网络连接中的长链接和短链接。 86
53.同步IO和异步IO,阻塞和非阻塞 87
54.实现栈和队列 89
55.大端小端 90
56.编译器都会做哪些优化 92
57.回调一个对象的成员函数,但是这个对象可能已经不存在了,怎么办? 93
58.介绍一下C++11的特性 94
59.硬盘拷贝速度,内存拷贝速度 95
60.Gdb调试,调试core dump文件 96
61.构造函数可以调用虚函数吗 98
62.dynamic_cast转换失败时,会出现什么情况? 100
63.为什么析构函数不能够抛出异常 101
1.linux中静态库和动态库的异同
本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。库有两种:静态库(.a、.lib)和动态库(.so、.dll)。
所谓静态、动态是指链接。
回顾一下,将一个程序编译成可执行程序的步骤:
图:编译过程
之所以成为【静态库】,是因为在链接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中。因此对应的链接方式称为静态链接。
图:创建静态库过程
Linux静态库命名规范,必须是"lib[your_library_name].a":lib为前缀,中间是静态库名,扩展名为.a。
通过上面的介绍发现静态库,容易使用和理解,也达到了代码复用的目的,那为什么还需要动态库呢?
为什么还需要动态库?
为什么需要动态库,其实也是静态库的特点导致。
空间浪费是静态库的一个问题
另一个问题是静态库对程序的更新、部署和发布页会带来麻烦。如果静态库liba.lib更新了,所以使用它的应用程序都需要重新编译、发布给用户(对于玩家来说,可能是一个很小的改动,却导致整个程序重新下载,全量更新)。
动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。动态库在程序运行是才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新。
动态库特点总结:
动态库把对一些库函数的链接载入推迟到程序运行的时期。
可以实现进程之间的资源共享。(因此动态库也称为共享库)
将一些程序升级变得简单。
甚至可以真正做到链接载入完全由程序员在程序代码中控制(显示调用)。
与创建静态库不同的是,不需要打包工具(ar、lib.exe),直接使用编译器即可创建动态库。
linux动态库的命名规则
动态链接库的名字形式为 libxxx.so,前缀是lib,后缀名为“.so”。
Linux下库相关命令
g++(gcc)编译选项
-shared:指定生成动态链接库。
-static:指定生成静态链接库。
-fPIC :表示编译为位置独立的代码,用于编译共享库。目标文件需要创建成位置无关码, 念上就是在可执行程序装载它们的时候,它们可以放在可执行程序的内存里的任何地方。
-L. :表示要连接的库所在的目录。
-l:指定链接时需要的动态库。编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.a/.so来确定库的名称。
nm命令
有时候可能需要查看一个库中到底有哪些函数,nm命令可以打印出库中的涉及到的所有符号。库既可以是静态的也可以是动态的。nm列出的符号有很多,常见的有三种:
一种是在库中被调用,但并没有在库中定义(表明需要其他库支持),用U表示;
一种是库中定义的函数,用T表示,这是最常见的;
一种是所谓的弱态”符号,它们虽然在库中被定义,但是可能被其他库中的同名符号覆盖,用W表示。
例如:nm libhello.a
ldd命令
ldd命令可以查看一个可执行程序依赖的共享库,例如我们编写的四则运算动态库依赖下面这些库:
总结:
一.不同
1.名字: libxxx.a libxxx.so
2.链接时间
静态库在链接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中 比较大 更新时(即使修改了一点)也要全部更新。
动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入 比较小 有修改时,只需要更新对应的动态库即可。
3.生成的过程不同,.a用ar工具生成,而.so用gcc即可。
生成静态库
将DynamicMath.cpp生成动态库
以下不是区别:
使用静态库,动态库都想下面这样:
但动态库在上述命令后可能会报错所找不到动态库文件libname.so。因为程序在运行时,会在/usr/lib和/lib等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提示类似上述错误而终止程序运行。我们将文件 libname.so复制到目录/usr/lib中即可。
二、相同
都是由*.o目标文件生成
都是为了代码重用
这篇写的特别好
这个链接中对整个链接动态库的整个过程做了实验
.a 静态库 archive(用ar工具生成) 档案馆的意思,就像档案馆这个名字一样,其实他就是一组目标文件(.o)的集合而已。即许多目标文件经过压缩打包后形成的一个文件。
2.是否了解动态规划
动归,本质上是一种划分子问题的算法,站在任何一个子问题的处理上看,当前子问题的提出都要依据现有的类似结论,而当前问题的结论是后 面问题求解的铺垫。任何DP都是基于存储的算法,核心是状态转移方程。
3.STL源码中的map实现(红黑树的五点要求)
我们什么时候用到了红黑树?
C++STL中map,set的底层实现全是用的红黑树,java,C#等语言同样如此。
为什么需要红黑树?
map,set底层都提供了排序功能,且查找速度快。红黑树实际上是AVL的一种变形,但是其比AVL(平衡二叉搜索树)具有更高的插入效率,当然查找效率会平衡二叉树稍微低一点点,毕竟平衡二叉树太完美了。但是这种查找效率的损失是非常值得的。它的操作有着良好的最坏情况运行时间,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除,这里的n是树中元素的数目。
我说用散列表也可以,unordered_map底层就是用的散列表。红黑树可以保证有序(O(logn)),散列表不保证有序,但由于是通过hash到桶实现的,自然复杂度是O(n). 红黑树需要两个指针,散列表冲突的情况下也只需要一个指针,所以需要的空间少一些。
若考虑有序,查询速度稳定,容器元素量少于1000,非频繁查询那么考虑使用map。
若非常高频查询(100个元素以上,unordered_map都会比map快),内部元素可非有序,数据大超过1k甚至几十万上百万时候就要考虑使用unordered_map
何为红黑树?
这里二叉平衡树的概念我就不提了。红黑树是每个节点都带有颜色属性的二叉查找树,颜色或红色或黑色。
性质1 节点是红色或黑色。
性质2 根节点是黑色。
性质3 每个叶节点(NIL节点,空节点)是黑色的。
性质4 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
性质5 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点(部分平衡)。
这些约束的好处是:保持了树的相对平衡,同时又比AVL的插入删除操作的复杂性要低许多。
红黑树和AVL树的区别:
平衡二叉树的追求的是全局均衡,如在做插入,删除操作时,需要调整整棵树,显然这是费时的,因此希望在做调整时,是局部调整,因此提出了红黑树。
红黑树并不追求“完全平衡”——它只要求部分地达到平衡要求,降低了对旋转的要求,从而提高了性能。
红黑树能够以O(log2 n) 的时间复杂度进行搜索、插入、删除操作。此外,由于它的设计,任何不平衡都会在三次旋转之内解决。当然,还有一些更好的,但实现起来更复杂的数据结构,能够做到一步旋转之内达到平衡,但红黑树能够给我们一个比较“便宜”的解决方案。红黑树的算法时间复杂度和AVL相同,但统计性能比AVL树更高。
B+树
待整理
见数据库索引
4.map和unorder_map(hash_map)的区别
1.实现原理,红黑树,散列表
2.速度,O(logn),O(1)。自然红黑树占用CPU高。
map具有稳定性,底层存储为树,这种算法差不多相当与list线性容器的折半查找的效率一样,都是O (log2N)。而hash_map使用hash表来排列配对,hash表是使用关键字来计算表位置。当这个表的大小合适,并且计算算法合适的情况下,hash表的算法复杂度为O(1)的,但是这是理想的情况下的,如果hash表的关键字计算与表位置存在冲突,那么最坏的复杂度为O(n)。 map在一次查找中,你可以断定它最坏的情况下其复杂度不会超过O(log2N)。而hash表就不一样,是O(1),还是O(N),或者在其之间,你并不能把握。
3.内存,红黑树至少两指针,散列表用冲突的话用链表解决的话也就一个指针,内存占用小。
4.map保证有序,需要保证有序的话必须使用map。若是问统计关键字出现次数map用什么实现:hash map,它效率高内存小,不需要保存有序。
5.数据小的时候大家差不多,数据量大用hash_map
2,3,4,5都是由于实现不同导致的差异。
unorder_map和hash_map都是用散列表实现的,但前者是c++标准之一,后者是一些库对STL的扩展,推荐用前者,支持的编译器多,好移植。
5.new一个对象涉及几个步骤?其中哪个步骤可以通过重载new操作符来修改
当用户用new构造一个对象的时候,其实内含两种操作:1)调用::operator new申请内存;2)调用该对象的构造函数构造此对象的内容
当用户用delete销毁一个对象时,其实内含两种操作:1)调用该对象的析构函数析构该对象的内容;2)调用::operator delete释放内存
分配足够的内存以容纳对象,然后调用构造函数初始化上一步所分配的内存。
你所能改变的只是第一步的行为,如何为对象分配RAW内存。
new操作符会调用operator new函数,new操作符进行两步:分配内存,调用构造函数。第一步就是通过调用operator new函数来完成的,可以通过重载operator new函数来改变第一步。
第二步是编译器调用的,程序员可以手动调用吗?可以通过placement new来做到:
详细请见
6.malloc一次性最大能申请多大内存空间
32位最大是4G。有一部分要给内核。比如32位下windows除内核外剩余大概2g,而Linux3G,其余的都是给了内核。Malloc是在堆上,栈还要占用一部分。
详细请见
7.new和malloc的区别
1、new是c++中的操作符,malloc是c 中的一个函数
2、new不止是分配内存,而且会调用类的构造函数,同理delete会调用类的析构函数,而malloc则只分配内存,不会进行初始化类成员的工作,同样free也不会调用析构函数
3、new自动分配大小,malloc要用户指定。
new出来的指针是直接带类型信息的。而malloc返回的都是void指针
4、new和malloc效率比较
new可以认为是malloc加构造函数的执行。
malloc原理:
malloc函数的实质体现在,它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表。调用malloc函数时,它沿连接表寻找一个大到足以满足用户请求所需要的内存块。然后,将该内存块一分为二(一块的大小与用户请求的大小相等,另一块的大小就是剩下的字节)。接下来,将分配给用户的那块内存传给用户,并将剩下的那块(如果有的话)返回到连接表上。调用free函数时,它将用户释放的内存块连接到空闲链上。到最后,空闲链会被切成很多的小内存片段,如果这时用户申请一个大的内存片段,那么空闲链上可能没有可以满足用户要求的片段了。于是,malloc函数请求延时,并开始在空闲链上翻箱倒柜地检查各内存片段,对它们进行整理,将相邻的小空闲块合并成较大的内存块。如果无法获得符合要求的内存块,malloc函数会返回NULL指针,因此在调用malloc动态申请内存块时,一定要进行返回值的判断。
malloc()到底从哪里得来了内存空间:
1、malloc()到底从哪里得到了内存空间?答案是从堆里面获得空间。也就是说函数返回的指针是指向堆里面的一块内存。操作系统中有一个记录空闲内存地址的链表。当操作系统收到程序的申请时,就会遍历该链表,然后就寻找第一个空间大于所申请空间的堆结点,然后就将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。就是这样!
2、什么是堆:堆是大家共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程 初始化的时候分配,运行过程中也可以向系统要额外的堆,但是记得用完了要还给操作系统,要不然就是内存泄漏。
什么是栈:栈是线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立。每个函数都有自己的栈,栈被用来在函数之间传递参数。操作系统在切换线程的时候会自动的切换栈,就是切换SS/ESP寄存器。栈空间不需要在高级语言里面显式的分配和释放。
通过上面对概念的描述,可以知道:
栈是由编译器自动分配释放,存放函数的参数值、局部变量的值等。操作方式类似于数据结构中的栈。
堆一般由程序员分配释放,若不释放,程序结束时可能由OS回收。注意这里说是可能,并非一定。所以我想再强调一次,记得要释放!
注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
8.delete和free的区别
delete 用于释放 new 分配的空间,free 有用释放 malloc 分配的空间
delete [] 用于释放 new [] 分配的空间
delete 释放空间的时候会调用 相应对象的析构函数
顺便说一下new在分配空间的时候同时会调用对象的构造函数,对对象进行初始化,使用malloc则只是分配内存
调用free 之前需要检查 需要释放的指针是否为空,使用delete 释放内存则不需要检查指针是否为NULL
free 和 delete 不能混用,也就是说new 分配的内存空间最好不要使用使用free 来释放,malloc 分配的空间也不要使用 delete来释放
补充一个问题,free和delete 是如何知道需要释放的内存块的大小的?
在调用malloc或new 分配内存空间的时候,实际分配的空间会比程序员申请的空间要大。实际分配的内存空间前面有一部分空间用于保存所分配内存的大小,校验和等信息。当分配函数返回时,将会返回实际可操作的地址(也就是实际分配空间加上前面用于记录分配信息的空间之后的地址)。下面举个例子,例子通过破坏 new 返回地址的前面四个字节的数据导致内存空间释放出问题。如果不破坏前面的数据则不会出现内存不能释放的情况。
9.什么时候必须用delete[]
int a = new int[10] delete a; 上述代码是否有问题,会内存泄漏吗?(不会)
class A {public: int a[100]; }; A pa = new A[100]; delete pa;上述代码是否有问题?会内存泄漏吗?(不会)
delete 跟 delete []到底内在有何区别?什么情况下必须用delete [],否则内存泄漏。
对于像int/char/long/int*/struct等等简单数据类型,由于对象没有destructor,所以用delete 和delete [] 是一样的
delete跟delete[]都会释放指针所指内存区域(地址会记录区域的大小、数目等,所以无论单个对象还是数组,都能正确释放内存),只不过delete只调用第一个对象的析构函数,而delete[]会调用后续所有对象的析构函数。对于对象数组,只要内存是连续的,用delete来释放是完全没有问题的。delete[]只有在对象中有分配在堆上的数据时才必须使用,因为堆上的数据只有各自的析构函数知道如何释放。
你一定会问,反正不管怎样都是把存储空间释放了,有什么区别。
答:关键在于调用析构函数上。此程序的类没有使用操作系统的系统资源(比如:Socket、File、Thread等),所以不会造成明显恶果。如果你的类使用了操作系统资源,单纯把类的对象从内存中删除是不妥当的,因为没有调用对象的析构函数会导致系统资源不被释放,如果是Socket则会造成Socket资源不被释放,最明显的就是端口号不被释放,系统最大的端口号是65535(216 _ 1,因为还有0),如果端口号被占用了,你就不能上网了,呵呵。如果File资源不被释放,你就永远不能修改这个文件,甚至不能读这个文件(除非注销或重器系统)。如果线程不被释放,这它总在后台运行,浪费内存和CPU资源。这些资源的释放必须依靠这些类的析构函数。
详细请见
10.静态多态 动态多态
编译期多态(静态多态,早绑定)----------重载和模板
运行期多态(动态多态,晚绑定)----------虚函数
11.动态绑定(后期绑定,运行时绑定),静态绑定(前期绑定,编译时绑定)
C++中,非虚函数都是静态绑定,而虚函数却是动态绑定。
2.什么是早绑定?
早绑定是指在程序运行之前,即编译和链接是执行的绑定就是早绑定!
3.什么事迟绑定?
迟绑定就是指发生在运行时,基于不同类型的对象。对函数调用不同的函数体是发生的绑定。当一种语言要实现迟绑定是,必须有某种机制去确定对象的具体类型,然后调用适当的成员函数!
其次我们来看一个小例子:
程序运行结果:
Animall breath;
Fish sleep;
我们可以看到虽然a为指向Animal的指针,但是当调用sleep是却是Fish sleep:
这其实就是多态的性质,当父类指针指向子类对象时,这是会调用子类虚表中的虚函数,
当然如果是非虚函数,则没有该类没有虚表,则只会按指针去找代码区中的函数实现。
当然父类指针就会找到父类里的函数,所有输出会是animal breath!
4.这种特性是怎么实现的呢?(虚函数机制)
首先,在C++中,虚函数用virtual关键字标示。当子类重写父类的虚函数时,在子类中自动的该函数也是虚函数!
对于带有虚函数的类,在数据区里会有其对应的一个虚函数表。而且每一个虚函数表都会有一个虚指针VTPR。如果是同一类型的对象,则虚函数指针指向同一地址。
那让我们再来看看多态的实现机制
首先父类会有一个虚函数表,子类对象也对应一个虚函数表,虚函数指针是在对象的构造函数调用时生成的。
当把一个子类对象赋值给父类指针时,由于虚函数指针只是属于对象的,所以父类指针中的虚函数指针则是子类的!
但调用虚函数时,会根据这个虚函数指针去虚函数表里寻找。所以调用的代码和对象的类型相关。
12.学过的设计模式? 状态机模式,简单工厂,策率模式,享元模式,模板模式 1次
13.指针和引用的区别:
(1)指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;
而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已,可以理解为操作受限了的指针(仅容许取内容操作)。如:
int a=1;int *p=&a;
int a=1;int &b=a;
上面定义了一个整形变量和一个指针变量p,该指针变量指向a的存储单元,即p的值是a存储单元的地址。
而下面2句定义了一个整形变量a和这个整形a的引用b,事实上a和b是同一个东西,在内存占有同一个存储单元。
(2)可以有const指针,但是没有const引用;
(3)指针可以有多级,但是引用只能是一级(int **p;合法 而 int &&a是不合法的)
(4)指针的值可以为空,但是引用的值不能为NULL,并且引用在定义的时候必须初始化;
(5)指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了。
(6)"sizeof引用"得到的是所指向的变量(对象)的大小,而"sizeof指针"得到的是指针本身的大小;
(7)指针和引用的自增(++)运算意义不一样;