NO.14|C++STL|动态链接|map|unordered_map|vector|list|set|push_back|emplace_back(C++)

![[Pasted image 20250323154357.png]]

说说 STL 容器动态链接可能产生的问题

参考回答

  1. 可能产生 的问题
    容器是一种动态分配内存空间的一个变量集合类型变量。在一般的程序函数里,局部容器,参数传递容器,参数传递容器的引用,参数传递容器指针都是可以正常运行的,而在动态链接库函数内部使用容器也是没有问题的,但是给动态库函数传递容器的对象本身,则会出现内存堆栈破坏的问题。
  2. 产生问题的原因
    容器和动态链接库相互支持不够好,动态链接库函数中使用容器时,参数中只能传递容器的引用,并且要保证容器的大小不能超出初始大小,否则导致容器自动重新分配,就会出现内存堆栈破坏问题

说说 map 和 unordered_map 的区别?底层实现

参考回答

map和unordered_map的区别在于他们的实现基理不同。

  1. map实现机理
    map内部实现了一个红黑树(红黑树是非严格平衡的二叉搜索树,而AVL是严格平衡二叉搜索树),红黑树有自动排序的功能,因此map内部所有元素都是有序的,红黑树的每一个节点都代表着map的一个元素。因此,对于map进行的查找、删除、添加等一系列的操作都相当于是对红黑树进行的操作。map中的元素是按照二叉树(又名二叉查找树、二叉排序树)存储的,特点就是左子树上所有节点的键值都小于根节点的键值,右子树所有节点的键值都大于根节点的键值。使用中序遍历可将键值按照从小到大遍历出来。
  2. unordered_map实现机理
    unordered_map内部实现了一个哈希表(也叫散列表),通过把关键码值映射到Hash表中一个位置来访问记录,查找时间复杂度可达O(1),其中在海量数据处理中有着广泛应用。因此,元素的排列顺序是无序的

说说 vector 和 list 的区别,分别适用于什么场景

vector和list区别在于底层实现机理不同,因而特性和适用场景也有所不同。

vector:一维数组

特点:元素在内存连续存放,动态数组,在堆中分配内存,元素连续存放,有保留内存,如果减少大小后内存也不会释放。
优点:和数组类似开辟一段连续的空间,并且支持随机访问,所以它的查找效率高其时间复杂度
O(1)。
缺点:由于开辟一段连续的空间,所以插入删除会需要对数据进行移动比较麻烦,时间复杂度
O(n),另外当空间不足时还需要进行扩容。

list:双向链表

特点:元素在堆中存放,每个元素都是存放在一块内存中,它的内存空间可以是不连续的,通过指针来进行数据的访问。
优点:底层实现是循环双链表,当对大量数据进行插入删除时,其时间复杂度O(1)。
缺点:底层没有连续的空间,只能通过指针来访问,所以查找数据需要遍历其时间复杂度O(n),
没有提供[]操作符的重载。

应用场景

vector拥有一段连续的内存空间,因此支持随机访问,如果需要高效的随即访问,而不在乎插入和删除的效率,使用vector。
list拥有一段不连续的内存空间,如果需要高效的插入和删除,而不关心随机访问,则应使用list

简述 vector 的实现原理

参考回答

vector底层实现原理为一维数组(元素在空间连续存放)。

  1. 新增元素
    Vector通过一个连续的数组存放元素,如果集合已满,在新增数据的时候,就要分配一块更大
    的内存,将原来的数据复制过来,释放之前的内存,在插入新增的元素。插入新的数据分在最后插入push_back和通过迭代器在任何位置插入,这里说一下通过迭代器插入,通过迭代器与第一个元素的距离知道要插入的位置,即int index=iter-begin()。这个元素后面的所有元素都向后移动一个位置,在空出来的位置上存入新增的元素。
//新增元素  
void insert(const_iterator iter,const T& t )  
{  
	int index=iter-begin();  
	if (index<size_)  
	{  
		if (size_==capacity_)  
		{  
			int capa=calculateCapacity();  
			newCapacity(capa);  
		} 
		memmove(buf+index+1,buf+index,(size_-index)*sizeof(T));  
		buf[index]=t;  
		size_++;  
	}  
}
  1. 删除元素
    删除和新增差不多,也分两种,删除最后一个元素pop_back和通过迭代器删除任意一个元素erase(iter)。通过迭代器删除还是先找到要删除元素的位置,即int index=iter-begin();这个位置后面的每个元素都想前移动一个元素的位置。同时我们知道erase不释放内存只初始化成默认值。
    删除全部元素clear:只是循环调用了erase,所以删除全部元素的时候,不释放内存。内存是在析构函数中释放的。
//删除元素  
iterator erase(const_iterator iter)  
{  
	int index=iter-begin();  
	if (index<size_ && size_>0)  
	{  
		memmove(buf+index ,buf+index+1,(size_-index)*sizeof(T));  
		buf[--size_]=T();  
	} 
	return iterator(iter);  
}
  1. 迭代器iteraotr
    迭代器iteraotr是STL的一个重要组成部分,通过iterator可以很方便的存储集合中的元素.STL为每个集合都写了一个迭代器, 迭代器其实是对一个指针的包装,实现一些常用的方法,如++,--,!=,==,*,->等, 通过这些方法可以找到当前元素或是别的元素. vector是STL集合中比较特殊的一个,因为vector中的每个元素都是连续的,所以在自己实现vector的时候可以用指针代替。
//迭代器的实现  
template<class _Category,class _Ty,class _Diff = ptrdiff_t,class _Pointer = _Ty *,class _Reference = _Ty&>  
struct iterator  
{  
	// base type for all iterator classes  
	typedef _Category iterator_category;  
	typedef _Ty value_type;  
	typedef _Diff difference_type;  
	typedef _Diff distance_type;   // retained
	typedef _Pointer pointer;
	typedef _Reference reference;  
};

简述 STL 中的 map 的实现原理

参考回答

map是关联式容器,它们的底层容器都是红黑树。map 的所有元素都是 pair,同时拥有实值
(value)和键值(key)。pair 的第一元素被视为键值,第二元素被视为实值。所有元素都会根据元素的键值自动被排序。不允许键值重复。

  1. map的特性如下
    (1)map以RBTree作为底层容器;
    (2)所有元素都是键+值存在;
    (3)不允许键重复;
    (4)所有元素是通过键进行自动排序的;
    (5)map的键是不能修改的,但是其键对应的值是可以修改的

C++ 的 vector 和 list中,如果删除末尾的元素,其指针和迭代器如何变化?若删除的是中间的元素呢

参考回答
  1. 迭代器和指针之间的区别
    迭代器不是指针,是类模板,表现的像指针。他只是模拟了指针的一些功能,重载了指针的一
    些操作符,–>、++、–等。迭代器封装了指针,是一个”可遍历STL( Standard Template
    Library)容器内全部或部分元素”的对象,本质是封装了原生指针,是指针概念的一种提升,提供了比指针更高级的行为,相当于一种智能指针,他可以根据不同类型的数据结构来实现不同的++,–等操作。
    迭代器返回的是对象引用而不是对象的值,所以cout只能输出迭代器使用取值后的值而不能直接输出其自身。
  2. vector和list特性
    vector特性 动态数组。元素在内存连续存放。随机存取任何元素都在常数时间完成。在尾端增删元素具有较大的性能(大部分情况下是常数时间)。
    list特性 双向链表。元素在内存不连续存放。在任何位置增删元素都能在常数时间完成。不支持随机存取。
  3. vector增删元素
    对于vector而言,删除某个元素以后,该元素后边的每个元素的迭代器都会失效,后边每个元素都往前移动一位,erase返回下一个有效的迭代器。
  4. list增删元素
    对于list而言,删除某个元素,只有“指向被删除元素”的那个迭代器失效,其它迭代器不受任何影响。

请你来说一下 map 和 set 有什么区别,分别又是怎么实现的

参考回答

  1. set是一种关联式容器,其特性如下:
    (1)set以RBTree作为底层容器
    (2)所得元素的只有key没有value,value就是key
    (3)不允许出现键值重复
    (4)所有的元素都会被自动排序
    (5)不能通过迭代器来改变set的值,因为set的值就是键,set的迭代器是const的
  2. map和set一样是关联式容器,其特性如下:
    (1)map以RBTree作为底层容器
    (2)所有元素都是键+值存在
    (3)不允许键重复
    (4)所有元素是通过键进行自动排序的
    (5)map的键是不能修改的,但是其键对应的值是可以修改的
    综上所述,map和set底层实现都是红黑树;map和set的区别在于map的值不作为键,键和值是分开的

说说 push_back 和 emplace_back 的区别

参考回答

如果要将一个临时变量push到容器的末尾,push_back()需要先构造临时对象,再将这个对象拷贝到容器的末尾,而emplace_back()则直接在容器的末尾构造对象,这样就省去了拷贝的过程。

答案解析

参考代码

#include <iostream>  
#include <cstring>  
#include <vector>  
using namespace std;  
class A {  
public:  
	A(int i){  
		str = to_string(i);  
		cout << "构造函数" << endl;  
	} 
	~A()
	{
	}  
	A(const A& other): str(other.str){  
		cout << "拷贝构造" << endl;  
	}
public:  
	string str;  
};  
int main()  
{  
	vector<A> vec;  
	vec.reserve(10);  
	for(int i=0;i<10;i++){  
		vec.push_back(A(i)); //调用了10次构造函数和10次拷贝构造函数,  
		// vec.emplace_back(i); //调用了10次构造函数一次拷贝构造函数都没有调用过  
	}  
}

STL 中 vector 与 list 具体是怎么实现的?常见操作的时间复杂度是多少

参考回答
  1. vector 一维数组(元素在内存连续存放)
    是动态数组,在堆中分配内存,元素连续存放,有保留内存,如果减少大小后,内存也不会释放;如果新增大小当前大小时才会重新分配内存。
    扩容方式:
    a. 倍放开辟三倍的内存
    b. 旧的数据开辟到新的内存
    c. 释放旧的内存
    d. 指向新内存
  2. list 双向链表(元素存放在堆中)
    元素存放在堆中,每个元素都是放在一块内存中,它的内存空间可以是不连续的,通过指针来进行数据的访问,这个特点,使得它的随机存取变得非常没有效率,因此它没有提[]操作符的重载。但是由于链表的特点,它可以很有效的支持任意地方的删除和插入操作。
    特点:
    a. 随机访问不方便
    b. 删除插入操作方便
  3. 常见时间复杂度
    (1)vector插入、查找、删除时间复杂度分别为:O(n)、O(1)、O(n);
    (2)list插入、查找、删除时间复杂度分别为:O(1)、O(n)、O(1)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值