string和vector常见面试题

string和vector常见面试题

手撕string类
  • 主要就是实现构造函数,析构函数,拷贝构造函数,以及赋值运算符重载的函数,这四个函数
#include
#include
using namespace std;

namespace bite
{
	class string
	{
	public:
		string(const char* str="")
		{
			if (str == nullptr)
			{
				_str = new char[1];
				*_str = '\0';
			}
			else
			{
				_str = new char[strlen(str) + 1];
				strcpy(_str, str);
			}
		}

		//拷贝构造函数
		string(const string& s)
			:_str(new char[strlen(s._str)+1])
		{
			strcpy(_str, s._str);
		}

	
		//赋值运算符的重载
		string& operator=(const string& s)
		{
			if (this != &s)
			{
				//申请空间,拷贝元素,释放旧空间
				char* temp = new char[strlen(s._str) + 1];
				strcpy(temp, s._str);
				delete[] _str;
				_str = temp;
			}
			return *this;
		}

		~string()
		{
			delete[] _str;
			_str = nullptr;
		}
	private:
		char* _str;
	};
}

int main()
{
	bite::string s1("hello");

	bite::string s2(s1);

	bite::string s3;
	
	s3 = s2;

	return 0;
}

vector和数组的区别

相同点
  • 可以使用下表运算符对元素进行操作,即vector和array都针对下标运算符[]进行了重载
  • 在内存的方面都使用连续内存,即在vector和array的底层存储结构均使用数组
不同点
  • vector属于变长容器,即可以根据数据的插入删除重新构建容器容量,但数组属于定长容量
  • vector和array提供了更好的数据访问机制,即可以使用front和back以及at访问方式,使得访问更加安全。而数组只能通过下标访问,在程序的设计过程中,更容易引发访问 错误
  • vector有一系列的函数操作,非常方便使用.和vector不同,数组不提供 push_back或者其他的操作在数组中添加新元素,数组一经定义就不允许添加新元素
  • 数组为内置的数据类型,存放在栈中,其内存的分配和释放完全由系统自动完成;vector,存放在堆中,由STL库中程序负责内存的分配和释放,使用方便

vector和list的区别

底层结构
  • vector的底层结构是动态顺序表,在内存中是一段连续的空间
  • list的底层结构是带头节点的双向循环链表,在内存中不是一段连续的空间
随机访问
  • vector支持随机访问,可以利用下标精准定位到一个元素上,访问某个元素的时间复杂度是O(1)
  • list不支持随机访问,要想访问list中的某个元素只能是从前向后或从后向前依次遍历,时间复杂度是O(N)
插入和删除
  • vector任意位置插入和删除的效率低,因为它每插入一个元素(尾插除外),都需要搬移数据,时间复杂度是O(N),而且插入还有可能要增容,这样一来还要开辟新空间,拷贝元素,是旧空间,效率会更低
  • list任意位置插入和删除的效率高,他不需要搬移元素,只需要改变插入或删除位置的前后两个节点的指向即可,时间复杂度为O(1)
空间利用率
  • vector由于底层是动态顺序表,在内存中是一段连续的空间,所以不容易造成内存碎片,空间利用率高,缓存利用率高
  • list的底层节点动态开辟空间,容易造成内存碎片,空间利用率低,缓存利用率低
迭代器
  • vector的迭代器是原生态指针。
  • list对原生态指针(节点的指针)进行了封装。
使用场景
  • vector适合需要高效率存储,需要随机访问,并且不管行插入和删除效率的场景。
  • list适合有大量的插入和删除操作,并且不关心随机访问的场景

vector中删除一个元素的代码

#include
#include
using namespace std;

namespace bite
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		//typedef const T* const iterator;

		//删除一个元素的代码
		iterator erase(iterator pos)
		{
			if (pos< first || pos>finish)
				return finish;
			auto prev = pos;
			auto cur = prev + 1;
			while (cur < finish)
			{
				*prev++ = *cur++;
			}
			--finish;
			return pos;
		}

	private:
		iterator first;
		iterator finish;
		iterator end_of_sortage;

	};
}
int main()
{
	return 0;
}

vector底层原理,reserve和resize区别,erase中间元素会发生什么

  • vector底层的原理其实就是通过三个指针维护起来了一块动态变化的内存空间,三个指针分别未first,finish,end_of_sortage,分别代表空间的起始位置,空间最后一个元素的的下一个位置,以及空间的容量,通过这三个指针就维护了一段底层的空间
  • 再vector下erase元素的时候,需要注意迭代器失效的问题

vector释放内存的方式

int a[10] = {12,3,45,6,7,2,45,5,2,10};
vector<int> S(a,a+10);
S.swap(vector<int>());
  • 上面的这种方式其实是利用swap函数,和临时对象交换,使S对象的内存为临时对象的内存一样,而临时对象的内存为S对象的内存。交换以后,临时对象消失,释放内存

vector的内存分配方式

  • vector的存在可以使开发者不必关心内存的申请和释放。但是,vector的一个缺点就是它的内存分配是按照2的倍数/1.5倍分配内存的。当当前容量对插入元素不够时,分配一块新的内存,这块内存的容量是原vector容量的2倍大小,然后复制旧内存,释放旧内存,可能多次涉及拷贝构造函数和析构函数,如果一个程序需要的内存超过2G的话,那么就会出现bad_alloc错误,从而引发程序的崩溃,而这也正是vector的劣势所在
  • vector其中一个特点:内存空间只会增长,不会减小,援引C++ Primer:为了支持快速的随机访问,vector容器的元素以连续方式存放,每一个元素都紧挨着前一个元素存储。设想一下,当vector添加一个元素时,为了满足连续存放这个特性,都需要重新分配空间、拷贝元素,撤销旧空间,这样性能难以接受。因此STL实现者在对vector进行内存分配时,其实际分配的容量要比当前所需的空间多一些。就是说,vector容器预留了一些额外的存储区,用于存放新添加的元素,这样就不必为每个新元素重新分配整个容器的内存空间

push_back和emplace_back

  • push_bach()—首先需要调用构造函数构造一个临时对象,然后调用拷贝构造函数将这个临时对象放入容器中,然后释放临时变量。在执行push_back的时候,调用了构造和拷贝构造函数,因为在使用push_back()向容器中加入一个右值元素(临时对象)时,首先会调用构造函数构造这个临时对象,然后需要调用拷贝构造函数将这个临时对象放入容器中。原来的临时变量释放。这样造成的问题就是临时变量申请资源的浪费
  • emplace_back()—这个元素原地构造,不需要触发拷贝构造和转移构造,在执行emplace_back的时候,只调用了转移构造函数,在插入的时候直接构造,效率更高,减少额外空间的开辟

你可能感兴趣的:(C/C++)