注:面试过程中整理的学习资料,如有侵权联系我即刻删除
目录
C++有哪些常见的内存错误?
C++中内存泄露的几种情况和解决方法。
默认的拷贝构造函数是怎么造成内存泄露的?
如何检测内存泄露?
如何避免内存泄露?
weak_ptr 弱指针 弱在哪里?
using namespace std的作用
如何发现死循环?
四核cpu如何跑满?
具体说明STL是如何实现vector的?
vector中erase和remove的区别?
vector通过下标访问,越界访问会有什么现象?
C++ STL中vector内存用尽后,为啥每次扩容是两倍增长,而不是3倍或其他倍数?
map使用下标去访问不存在的key值,会有什么现象?
map和vector什么时候使用map,什么时候使用vector?
vector、map/mutimap、unordered_map/unordered_multimap的底层数据结构,以及几种map容器如何选择?
迭代器iterator是什么?
如何使用迭代器iterator来对容器进行删除?
迭代器失效的情况
关于容器和迭代器的用法和相关知识点
内存泄漏是指由于疏忽或错误造成了程序未能释放掉不再使用的内存的情况,从而造成内存的浪费。
野指针也会造成内存泄露。指针没有初始化;指针在free或者delete之后没有置null;返回指向栈内存的引用或者指针等等都是野指针。
一个类里面如果含有指针成员变量,要么显式的写出拷贝构造函数和重载赋值运算符,要么禁用拷贝构造函数和重载赋值运算符。不然可能会造成两个对象的指针变量指向同一个内存空间,同一块内存被释放两次。
拷贝构造函数只是在用一个已存在的对象去初始化新建立的对象时调用,而对象进行赋值时,拷贝函数将不被调用。(用常量初始化新的对象调用的是构造函数);默认的拷贝构造函数实现的只能是浅拷贝,即直接将原对象的数据成员值依次复制给新对象中对应的数据成员,并没有为新对象另外分配内存资源。这样,如果对象的数据成员中有指针,两个指针实际上指向的是同一块内存空间。
在某些情况下,浅拷贝会带来数据安全方面的隐患。当类的数据成员中有指针类型时,我们就必须定义一个特定的拷贝构造函数,该拷贝构造函数不仅可以实现原对象和新对象之间数据成员的拷贝,而且可以为新的对象分配单独的内存资源,这就是深拷贝构造函数。(深拷贝和浅拷贝)
(1)在mfc中,每个cpp文件中都有以下语句,
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
DEBUG_NEW 这个宏定义在afx.h文件中,就是它帮助我们定位内存泄漏。如果我们代码有发生内存泄露的话,那么停止程序的时候,vs的output窗口就会显示内存泄露的信息,会定位到那一行代码。
(2)在普通的c++程序中,可以用 _CrtDumpMemoryLeaks();这个函数是在头文件crtdbg.h。在调试的时候使用,当它执行时,所有未销毁的对象均会报内存泄漏。因此尽量让这条语句在程序的最后执行。声明一个这样的宏,#define _CRTDBG_MAP_ALLOC,则可以输出内存泄露的位置。
如果一个程序有多个出口退出,在每一个可能的出口处调用 _CrtDumpMemoryLeaks 肯定是不可取的,那么这时可以在程序开始处包含下面的调用:_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );这条语句无论程序在什么地方退出都会自动调用 _CrtDumpMemoryLeaks。注意:这里必须同时设置两个位域标志:_CRTDBG_ALLOC_MEM_DF 和 _CRTDBG_LEAK_CHECK_DF。
(3)可以开任务管理器看一下程序的“内存使用”和”虚拟内存大小”两项,如果虚拟内存持续地增长,那么就是有内存泄露了。
使用智能指针(auto_ptr、unique_ptr、shared_ptr和weak_ptr)来替代new和delete的任务,头文件是
auto_ptr
使用shared_ptr时,遇到循环引用的情况,容易导致内存泄露。通过shared_ptr创建的两个指针,同时它们的内部均包含shared_ptr指向对方,如下图:
所以,如果shared_ptr所表征的引用关系中出现了一个环,那么环上对象的引用次数肯定不可能减为0,对象也就无法析构,发生内存泄露。
为了解决shared_ptr循环引用引起的内存泄露问题,引入了weak_ptr。用weak_ptr来指向shared_ptr所指向的对象,对象引用次数并不会增加。
把其中一个shared_ptr改为weak_ptr,就可以解决内存泄露的问题。
weak_ptr没有重载*、->操作符,所以不能通过*、->操作符操作智能指针的内部数据。
要操作内部数据,可通过weak_ptr::lock间接访问。
C++标准程序库的名称都被封装在一个名为std的命名空间中。这样就可以省略std,直接调用其中的cout、cin。
过去我们用的全局变量可以理解为全局命名空间,不需要namespace声明,比如带.h的头文件iostream.h。而不带.h的头文件iostream,就必须使用using namespace std,这样才能省略掉命名空间名和::。
命名空间主要是为了防止命名冲突,指定一些有名字的命名空间,把一些全局量放到各个命名空间中,与其他全局量分隔开来。
看任务管理器中程序占用的cpu是不是快百分之百了。
用四个线程写四个死循环。
vector的底层结构还是数组,vector的内部是使用动态数组的方式来实现的。只要没超过它的预留容量,vector就会自动扩充空间来容纳新的元素,如果超过了容量,vector就会把容量扩充为原来的两倍,动态的重新分配内存,把原数组的内容拷贝过去,再释放原来的空间。
vector中erase方法真正删除了元素,迭代器不能访问了。remove只是简单地将元素移到了容器的最后面,迭代器还是可以访问到。因为remove是algorithm中的函数,algorithm通过迭代器进行操作,不知道容器的内部结构,所以无法进行真正的删除。
程序不一定会崩溃,通过下标访问vector元素的时候是不会做边界检查的,而是返回这个地址中存储的值。如果想在访问vector中的元素时首先进行边界检查,可以使用vector中的at函数。通过使用at函数不但可以通过下标访问vector中的元素,而且在at函数内部会对下标进行边界检查。Sta.at(i)
VS2015是1.5倍的增长。
1, 2, 4, 8, 16, 32,……可以看到,每次需要申请的空间都无法用到前面释放的空间,成倍数增长是为了时间复杂度。不是三倍或者更大是为了防止堆内存的浪费。
程序不会抛出异常,会自动插入那个key值,返回一个默认值null。
(想着map的结构就明白了,map的key是个红黑树)
所以map的插入方法有两个,一个是下标访问,一个insert。
vector是顺序容器,map是关联容器。
如果想快速通过一个值找到其对应的另一个值的时候就应该用Map。比如,你希望输入一个学生的名字,就可以快速得到他的学号,那么你应该建立一个Map。如果不用map,而改用vector或者list之类的数据结构的话,就只能用遍历的方式查找,在数据量很大的时候,效率会低很多;哈希表(散列表)查找也是用map。
vector是动态数组,当我们需要随机存取的时候用vector就很合适。
底层数据结构:
vector基于数组,map/multimap/set基于红黑树,unordered_map/unordered_multimap基于哈希表。
根据应用场景进行选择:
map/unordered_map 不允许重复元素
multimap/unordered_multimap允许重复元素
map/multimap/set底层基于红黑树,元素自动有序,且插入、删除效率高
unordered_map/unordered_multimap底层基于哈希表,故元素无序,查找效率高。
迭代器iterator 提供了一种一般化的方法,可以对顺序或关联容器类型中的每个元素进行连续访问。
例如,假设iter为任意容器类型的一个iterator,则++iter 表示向前移动迭代器使其指向容器的下一个元素,而*iter 返回迭代器指向元素的值,每种容器类型都提供一个begin()和一个end()成员函数。
begin()返回一个iterator 它指向容器的第一个元素。
end()返回一个iterator 它指向容器的末元素的下一个元素也就是空。
通过迭代器,我们可以用相同的方式来访问、遍历容器。
typedef vector<int>::iterator VecIt;
typedef map
VecIt iter;
MapIt iter2;
可以。
(1)对于序列式容器:vector,deque,序列式容器就是数组式容器,删除当前的iterator会使得后边所有元素的iterator都失效。这是因为其使用了连续分配的内存,删除一个元素导致后面所有的元素都会向前移动一个位置,所以不能使用erase(iter++)的方式。但是erase方法可以返回下一个有效的iterator。(对于vector来说,erase删除之后自动指向下一个迭代器)
迭代器失效的情况是:
这样是先删除迭代器iter,iter已经失效了,然后iter再++,这样就会程序崩溃。
iter =cont.erase(iter);来解决连续内存结构的迭代器失效
erase(iter++)来解决不连续内存结构的迭代器失效。反正都不能在for中iter++。
在m.erase(it++);中,it++先执行,此时还没有erase,程序自然不会崩溃。当it++执行完后,已经指向了下一个元素了,但it++的返回值还是当前元素,此时再删除它,才是正确的。