1.c++内存四区
2.const和define的区别
(1)就起作用的阶段而言:#define是在编译的预处理阶段起作用(在预处理阶段进行替换),而const是在编译运行的时候起作用(const修饰的只读变量是在编译的时候确定其值)
(2)就起作用的方式而言:#define只是简单的字符串替换,没有类型检查。而const有对应的类型,是要进行判断的,可以避免一些低级的错误
(3)就存储方式而言:#define只是进行展开,有多少地方使用,就替换多少次。它定义的宏常量在内存中存若干个备份;const定义的只读变量在程序中只有一份备份
(4)从代码调试的方便程度而言:const常量可以进行调试的,define是不能进行调试的,因为在预编译阶段就已经进行替换了
(5)就内存分配而言:编译器通常不为普通的const只读变量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的值,没有了存储与读内存的操作,使得它的效率也很高
#define M 3
const int N = 5; //此时并未将N放入内存中
int i = N;此时为N分配内存,以后不再分配
int l = M;//预编译期间进行宏替换,分配内存
int j = N;//没有内存分配
int J = M;//再进行宏替换,又一次分配内存
const定义的只读变量从汇编的角度来看,只是给出了对应的内存地址,而不是像#define一样给出的是立即数,所以,const定义的只读变量在程序运行过程中只有一份拷贝(因为它是全局的只读变量,存放在静态区),而#define定义的宏常量在内存中有若干个拷贝。
const的优点
(1)const常量有数据类型,而宏常量没有数据类型,编译器可以对const进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误
(2)有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行调试
(3)const在内存中只存储了一份,节省了空间,避免不必要的内存分配,提高了效率
3.数组,std::array和STL的vector数组有什么区别
(1)数组
这些数的类型必须相同。
这些数在内存中必须是连续存储的。
(2)std::array
array 容器是 C++ 11 标准中新增的序列容器,简单地理解,它就是在 C++ 普通数组的基础上,添加了一些成员函数和全局函数。在使用上,它比普通数组更安全(原因后续会讲),且效率并没有因此变差。
和其它容器不同,array 容器的大小是固定的,无法动态的扩展或收缩,这也就意味着,在使用该容器的过程无法借由增加或移除元素而改变其大小,它只允许访问或者替换存储的元素。
(3)STL的vector
ector 容器是 STL 中最常用的容器之一,它和 array 容器非常类似,都可以看做是对 C++ 普通数组的“升级版”。不同之处在于,array 实现的是静态数组(容量固定的数组),而 vector 实现的是一个动态数组,即可以进行元素的插入和删除,在此过程中,vector 会动态调整所占用的内存空间,整个过程无需人工干预。
v ector 常被称为向量容器,因为该容器擅长在尾部插入或删除元素,在常量时间内就可以完成,时间复杂度为O(1);而对于在容器头部或者中部插入或删除元素,则花费时间要长一些(移动元素需要耗费时间),时间复杂度为线性阶O(n)。
对于一个 vector 对象来说,通过该模板类提供的 capacity() 成员函数,可以获得当前容器的容量;通过 size() 成员函数,可以获得容器当前的大小。
vector容器的底层实现机制:
STL 众多容器中,vector 是最常用的容器之一,其底层所采用的数据结构非常简单,就只是一段连续的线性内存空间。
通过分析 vector 容器的源代码不难发现,它就是使用 3 个迭代器(可以理解成指针)来表示的:
//_Alloc 表示内存分配器,此参数几乎不需要我们关心
template <class _Ty, class _Alloc = allocator<_Ty>>
class vector{
...
protected:
pointer _Myfirst;
pointer _Mylast;
pointer _Myend;
};
其中,_Myfirst 指向的是 vector 容器对象的起始字节位置;_Mylast 指向当前最后一个元素的末尾字节;_myend 指向整个 vector 容器所占用内存空间的末尾字节。
图 1 演示了以上这 3 个迭代器分别指向的位置。
在此基础上,将 3 个迭代器两两结合,还可以表达不同的含义,例如:
_Myfirst 和 _Mylast 可以用来表示 vector 容器中目前已被使用的内存空间;
_Mylast 和 _Myend 可以用来表示 vector 容器目前空闲的内存空间;
_Myfirst 和 _Myend 可以用表示 vector 容器的容量。
vector扩大容量的本质:
当 vector 的大小和容量相等(size==capacity)也就是满载时,如果再向其添加元素,那么 vector 就需要扩容。vector 容器扩容的过程需要经历以下 3 步:
完全弃用现有的内存空间,重新申请更大的内存空间;
将旧内存空间中的数据,按原有顺序移动到新的内存空间中;
最后将旧的内存空间释放。
emplace_back()和push_back()的区别
emplace_back() 和 push_back() 的区别,就在于底层实现的机制不同。push_back() 向容器尾部添加元素时,首先会创建这个元素,然后再将这个元素拷贝或者移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素);而 emplace_back() 在实现时,则是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。
相同点:
三者均可以使用下表运算符对元素进行操作,即vector和array都针对下标运算符[]进行了重载
三者在内存的方面都使用连续内存,即在vector和array的底层存储结构均使用数组
不同点:
vector属于变长容器,即可以根据数据的插入删除重新构建容器容量;但array和数组属于定长容量。
vector和array提供了更好的数据访问机制,即可以使用front和back以及at访问方式,使得访问更加安全。而数组只能通过下标访问,在程序的设计过程中,更容易引发访问 错误。
vector和array提供了更好的遍历机制,即有正向迭代器和反向迭代器两种
vector和array提供了size和判空的获取机制,而数组只能通过遍历或者通过额外的变量记录数组的size
vector和array提供了两个容器对象的内容交换,即swap的机制,而数组对于交换只能通过遍历的方式,逐个元素交换的方式使用
array提供了初始化所有成员的方法fill
vector提供了可以动态插入和删除元素的机制,而array和数组则无法做到,或者说array和数组需要完成该功能则需要自己实现完成
由于vector的动态内存变化的机制,在插入和删除时,需要考虑迭代的是否失效的问题。
总结:如果只是需要固定大小的数组,那么应该使用std::array,我们可以使用很多成员函数;
如果需要的是支持插入,删除,扩展的数组,那么建议用std::vector。
需要考虑到性能的时候,如果是进行一些数***算或者算法的时候,对性能要求比较高 。
4.数组和链表,什么时候使用数组结构?什么时候使用链表结构?
- 数组和链表的区别
数组:
数组的元素在内存中连续存储的;
它的优点:因为数据是连续存储的,所以内存地址连续,在查找数据的时候效率比较高;
它的缺点:在创建的时候,我们需要确定其大小,申请一块连续的内存空间,一经创建就无法改变。在运行的时候,空间的大小是无法随着需要增加和减少而改变的。
当数据量比较大的时候,有可能会出现越界的情况,数据比较小的时候,又有可能会浪费掉内存空间。在改变数据个数时,插入、删除数据效率比较低。
链表:
链表是动态申请内存空间,其不需要像数组需要在创建的时候就确定好内存的大小,链表的具体实现有ArrayList和LinkedList,前者默认大小为10,当存储空间不足的时候会自动扩容,而后者没有默认大小,只有你想,你可以一直扩容下去。
链表只需要在用的时候申请就可以了,根据需求来动态申请或者删除内存空间,对于数据插入和删除比数组灵活。还有就是链表中的数据可以放在内存中的任何地方。
- 链表和数组使用场景
数组应用场景:
数据比较少;经常做的运算,是按序号访问数据元素;构建的线性表较稳定。
链表应用场景:
对线性表的长度或者规模难以估计;频繁做插入删除操作;构建动态性比较强的线性表。