对STL部分接口的理解

1.string部分接口介绍

1.1 reserve && resize

#include <iostream>
#include <string>
using namespace std;
void test_string7()
{

	string s;
	// reverse 逆置
	s.reserve(100); //保留 开空间
	//s.resize(1000, 'x');    //     开空间 + 初始化
	size_t sz = s.capacity();
	cout << "capacity changed: " << sz << '\n';
	cout << "making s grow:\n";
	for (int i = 0; i < 1000; ++i)
	{
		s.push_back('c');
		if (sz != s.capacity())
		{
			sz = s.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
}

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

 

  •  reserve(100)开闭100个字节的空间,编译器会比我们想要开闭的空间更多一些
  •  不同的平台,扩容机制也不同,有的是扩2倍,有的是扩1.5倍,

1.2 c_str

#include <iostream>
#include <assert.h>
using namespace std;
void test_string10()
{
	string filename("test.cpp");
	cout << filename << endl;
	cout << filename.c_str() << endl;
	filename += '\0';

	filename += "string.cpp";
	cout << filename << endl; // string 对象size为准
	cout << filename.c_str() << endl; // 常量字符串对象\0

	cout << filename.size() << endl;// 如果有\0,会算\0
	cout << filename.length() << endl;// 不算f\0
	string copy = filename;
	cout << copy << endl << endl;
}

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

 

  • string容器默认不存储‘\0’
  • string中的c_str会把string变成以‘\0’结尾的字符串

1.3 find && substr

#include <iostream>
#include <assert.h>
using namespace std;
void DealUrl(const string& url)
{
	size_t pos1 = url.find("://");
	if (pos1 == string::npos)
	{
		cout << "非法url" << endl;
		return;
	}
	string protocol = url.substr(0, pos1);
	cout << protocol << endl;

	size_t pos2 = url.find('/', pos1 + 3);
	if (pos2 == string::npos)
	{
		cout << "非法url" << endl;
		return;
	}
	string domain = url.substr(pos1 + 3, pos2 - (pos1 + 3));
	cout << domain << endl;

	string uri = url.substr(pos2 + 1);
	cout << uri << endl << endl;
}

void test_string11()
{
	string url1 = "https://cplusplus.com/reference/string/string/";
	DealUrl(url1);
}

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

  •  find 和 substr都是左闭右开
  • find 和 rfind都是从左向右看下标

1.4 compare

#include <iostream>
#include <string>
using namespace std;
int main()
{
    string s1("abcabc");
    string s2("cab");
    const char* s3 = "abc";
    const char* s4 = "aaaa";
    if (s1.compare(s2) == 0) {
        cout << s1 << "=" << s2 << endl;
    }
    if (s1.compare(3, 3, s3) == 0) {
        cout << s1 << "=" << s3 << endl;
        //abcabc 
        //   abc
    }
    return 0;
}

  

1.5 append拼接

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s1("I");
	string s2(" like");

	//append(string)完成两个string对象的拼接
	s1.append(s2); //I like

	//append(str)完成string对象和字符串str的拼接
	s1.append(" C++"); //I like C++

	//append(n, char)将n个字符char拼接到string对象后面
	s1.append(3, '!'); //I like C++!!!
	
	cout << s1 << endl; //I like C++!!!
	return 0;
}
  • 相当于尾插字符串 

 1.6 to_string && stoi && stod

#include <iostream>
#include <string>
using namespace std;
void test_string12()
{
	int ival;
	double dval;
	cin >> ival >> dval;
	string istr = to_string(ival);
	string dstr = to_string(dval);
	cout << istr << endl;
	cout << dstr << endl;

	istr = "9999";
	dstr = "9999.99";
	ival = stoi(istr);
	dval = stod(dstr);
	cout << typeid(ival).name() << endl;
	cout << typeid(dval).name() << endl;
}

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

  • to_string:其他类型转换成字符
  • stoi:字符转化成整型
  • stod:字符转化成浮点型 

1.7 clear

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s("CSDN");

	//clear()删除对象的内容,该对象将变为空字符串
	s.clear();
	cout << s << endl; //空字符串
	return 0;
}
  •  使string变成空字符串

 1.8 模拟实现string

namespace test
{
	//模拟实现string类
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;

		//默认成员函数
		string(const char* str = "");         //构造函数
		string(const string& s);              //拷贝构造函数
		string& operator=(const string& s);   //赋值运算符重载函数
		~string();                            //析构函数

		//迭代器相关函数
		iterator begin();
		iterator end();
		const_iterator begin()const;
		const_iterator end()const;

		//容量和大小相关函数
		size_t size();
		size_t capacity();
		void reserve(size_t n);
		void resize(size_t n, char ch = '\0');
		bool empty()const;

		//修改字符串相关函数
		void push_back(char ch);
		void append(const char* str);
		string& operator+=(char ch);
		string& operator+=(const char* str);
		string& insert(size_t pos, char ch);
		string& insert(size_t pos, const char* str);
		string& erase(size_t pos, size_t len = npos);
		void clear();
		void swap(string& s);
		const char* c_str()const;

		//访问字符串相关函数
		char& operator[](size_t i);
		const char& operator[](size_t i)const;
		size_t find(char ch, size_t pos = 0)const;
		size_t find(const char* str, size_t pos = 0)const;
		size_t rfind(char ch, size_t pos = npos)const;
		size_t rfind(const char* str, size_t pos = 0)const;

		//关系运算符重载函数
		bool operator>(const string& s)const;
		bool operator>=(const string& s)const;
		bool operator<(const string& s)const;
		bool operator<=(const string& s)const;
		bool operator==(const string& s)const;
		bool operator!=(const string& s)const;

	private:
		char* _str;       //存储字符串
		size_t _size;     //记录字符串当前的有效长度
		size_t _capacity; //记录字符串当前的容量
		static const size_t npos; //静态成员变量(整型最大值)
	};
	const size_t string::npos = -1;

	// <<和>>运算符重载函数
	istream& operator>>(istream& in, string& s);
	ostream& operator<<(ostream& out, const string& s);
	istream& getline(istream& in, string& s);
}

2. vector部分接口介绍

2.1 reserve && resize

#include <iostream>
#include <vector>

namespace std
{
	void TestVectorExpand()
	{
		size_t sz;
		vector<int> v;
		//v.resize(100);
		v.reserve(100);

		sz = v.capacity();
		cout << "making v grow:\n";
		for (int i = 0; i < 1000; ++i)
		{
			v.push_back(i);
			if (sz != v.capacity())
			{
				sz = v.capacity();
				cout << "capacity changed: " << sz << '\n';
			}
		}
	}

	void test_vector3()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		cout << v1.max_size() << endl;

		TestVectorExpand();
	}
}

int main()
{
	std::test_vector3();
	return 0;
}

  • 空间只能变大或者不变
  • reserve开空间,它是期待预留空间
    • 如果原来空间大,期待预留空间小,则空间不变
    • 如果原来空间小,期待预留空间大,则空间变大
  • resize开空间和初始化,它是调整元素个数,
    • 如果调整之后元素个数比空间小,则空间不变,size会变小
    • 如果调整之后元素个数比空间大,则空间变大,size会变大
  • max_size返回的是容纳最大元素的数

 2.2 insert && erase

#include <iostream>
#include <vector>
#include <algorithm>

namespace std
{
	void test_vector4()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);

		vector<int>::iterator pos = find(v1.begin(), v1.end(), 3);
		// 如果find没找到,就会返回vector最后一个元素的下一个,即end(),
		if (pos != v1.end())
		{
			v1.insert(pos, 30);
		}

		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;

		pos = find(v1.begin(), v1.end(), 30);
		if (pos != v1.end())
		{
			v1.erase(pos);
		}

		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;
	}
}

int main()
{
	std::test_vector4();
	return 0;
}

  •  vector中没有实现find接口,但algorithm中定义了find函数,还有sort函数
  • find的返回值的类型是迭代器,不是size_t,且如果find没找到,就会返回vector最后一个元素的下一个,即end(),

3. vector中迭代器失效问题 

3.1 问题一:pos指向位置已经不再是原来的值了(因为数据挪动) 

#include <iostream>
#include <vector>
using namespace std;
int main()
{
	vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	// 在所有的偶数前面插入这个偶数的2倍
	auto it = v1.begin();
	while (it != v1.end())
	{
		if (*it % 2 == 0)
		{
			it = v1.insert(it, *it * 2);
		}
		it++;
	}

	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
	return 0;
}

  • insert返回的是原来的位置,新的值
  •  这段代码会直接崩溃,这不仅insert会产生上面的问题,erase也可能会有这样的问题

 解决方案 

  •  只要让它插入值之后,多走一步就行了

问题原因 

  •  这是在vs2022中调式的结果

3.2 问题二:扩容导致野指针问题

  •  我这里用vs2022不好演示,只要涉及扩容的操作,都有可能产生野指针问题,比如insert

3.3 对迭代器失效的补充说明

  •  STL只是一个规范,不同的平台下面实现也是不同的,
    所以相同的代码,产生的结果不一样,也是正常的
  • vs的编译器采用的是PJ版
  • linux的g++编译器采用的是SG版
  • 数据排列偶然性:a.最后一个不是偶数,b.没有连续的偶数,

3.4 小结

  •  insert/erase pos位置,不要再次直接访问pos,可能出现各种出乎意料的结果,这就是所谓的迭代器失效

3.5 模拟实现vector

namespace test
{
	//模拟实现vector
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

		//默认成员函数
		vector();                                           //构造函数
		vector(size_t n, const T& val);                     //构造函数
		template<class InputIterator>
		vector(InputIterator first, InputIterator last);    //构造函数
		vector(const vector<T>& v);                         //拷贝构造函数
		vector<T>& operator=(const vector<T>& v);           //赋值运算符重载函数
		~vector();                                          //析构函数

		//迭代器相关函数
		iterator begin();
		iterator end();
		const_iterator begin()const;
		const_iterator end()const;

		//容量和大小相关函数
		size_t size()const;
		size_t capacity()const;
		void reserve(size_t n);
		void resize(size_t n, const T& val = T());
		bool empty()const;

		//修改容器内容相关函数
		void push_back(const T& x);
		void pop_back();
		void insert(iterator pos, const T& x);
		iterator erase(iterator pos);
		void swap(vector<T>& v);

		//访问容器相关函数
		T& operator[](size_t i);
		const T& operator[](size_t i)const;

	private:
		iterator _start;        //指向容器的头
		iterator _finish;       //指向有效数据的尾
		iterator _endofstorage; //指向容器的尾
	};
}

4. list部分接口介绍 

4.1 sort

#include <iostream>
#include <list>
#include <vector>
#include <algorithm>
#include <time.h>
using namespace std;

void test_op()
{
	srand(time(0));
	const int N = 10000000;
	vector<int> v;
	v.reserve(N);

	list<int> lt1;
	list<int> lt2;

	for (int i = 0; i < N; ++i)
	{
		auto e = rand();
		lt1.push_back(e);
		lt2.push_back(e);
	}

	// 拷贝到vector排序,排完以后再拷贝回来
	int begin1 = clock();
	for (auto e : lt1)
	{
		v.push_back(e);
	}
	sort(v.begin(), v.end());// 调用库里面的sort
	size_t i = 0;
	for (auto& e : lt1)
	{
		e = v[i++];// 拷贝回去
	}
	int end1 = clock();

	int begin2 = clock();
	// sort(lt.begin(), lt.end()); error
	lt2.sort();// 调用list自己的sort
	int end2 = clock();

	printf("copy vector sort:%d\n", end1 - begin1);
	printf("list sort:%d\n", end2 - begin2);
}

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

  •  这里把list数据拷贝给vector,vector排序之后又拷贝给list
  • 这里是调用list自己的sort排序(不能调用库里面的sort)

4.2 list的模拟实现

namespace cl
{
	//模拟实现list当中的结点类
	template<class T>
	struct _list_node
	{
		//成员函数
		_list_node(const T& val = T()); //构造函数

		//成员变量
		T _val;                 //数据域
		_list_node<T>* _next;   //后继指针
		_list_node<T>* _prev;   //前驱指针
	};

	//模拟实现list迭代器
	template<class T, class Ref, class Ptr>
	struct _list_iterator
	{
		typedef _list_node<T> node;
		typedef _list_iterator<T, Ref, Ptr> self;

		_list_iterator(node* pnode);  //构造函数

		//各种运算符重载函数
		self operator++();
		self operator--();
		self operator++(int);
		self operator--(int);
		bool operator==(const self& s) const;
		bool operator!=(const self& s) const;
		Ref operator*();
		Ptr operator->();

		//成员变量
		node* _pnode; //一个指向结点的指针
	};

	//模拟实现list
	template<class T>
	class list
	{
	public:
		typedef _list_node<T> node;
		typedef _list_iterator<T, T&, T*> iterator;
		typedef _list_iterator<T, const T&, const T*> const_iterator;

		//默认成员函数
		list();
		list(const list<T>& lt);
		list<T>& operator=(const list<T>& lt);
		~list();

		//迭代器相关函数
		iterator begin();
		iterator end();
		const_iterator begin() const;
		const_iterator end() const;

		//访问容器相关函数
		T& front();
		T& back();
		const T& front() const;
		const T& back() const;

		//插入、删除函数
		void insert(iterator pos, const T& x);
		iterator erase(iterator pos);
		void push_back(const T& x);
		void pop_back();
		void push_front(const T& x);
		void pop_front();

		//其他函数
		size_t size() const;
		void resize(size_t n, const T& val = T());
		void clear();
		bool empty() const;
		void swap(list<T>& lt);

	private:
		node* _head; //指向链表头结点的指针
	};
}

5. 理解 stack && queue 

  • 栈和队列的模拟实现都是通过复用来实现的
  • 栈的适配器Container可以是vector或者是list,队列的适配器Container可以是list
  • 库里面提供了适配器,使得栈和队列的底层有了更大的发挥空间,更像泛型编程

 5.1 stack的模拟实现

namespace lyc //防止命名冲突
{
	template<class T, class Container = std::deque<T>>
	class stack
	{
	public:
		//元素入栈
		void push(const T& x)
		{
			_con.push_back(x);
		}
		//元素出栈
		void pop()
		{
			_con.pop_back();
		}
		//获取栈顶元素
		T& top()
		{
			return _con.back();
		}
		const T& top() const
		{
			return _con.back();
		}
		//获取栈中有效元素个数
		size_t size() const
		{
			return _con.size();
		}
		//判断栈是否为空
		bool empty() const
		{
			return _con.empty();
		}
		//交换两个栈中的数据
		void swap(stack<T, Container>& st)
		{
			_con.swap(st._con);
		}
	private:
		Container _con;
	};
}

 5.2 queue的模拟实现

namespace lyc //防止命名冲突
{
	template<class T, class Container = std::deque<T>>
	class queue
	{
	public:
		//队尾入队列
		void push(const T& x)
		{
			_con.push_back(x);
		}
		//队头出队列
		void pop()
		{
			_con.pop_front();
		}
		//获取队头元素
		T& front()
		{
			return _con.front();
		}
		const T& front() const
		{
			return _con.front();
		}
		//获取队尾元素
		T& back()
		{
			return _con.back();
		}
		const T& back() const
		{
			return _con.back();
		}
		//获取队列中有效元素个数
		size_t size() const
		{
			return _con.size();
		}
		//判断队列是否为空
		bool empty() const
		{
			return _con.empty();
		}
		//交换两个队列中的数据
		void swap(queue<T, Container>& q)
		{
			_con.swap(q._con);
		}
	private:
		Container _con;
	};
}

6. 关于适配器Container的缺省参数deque

  •  deque就像是listvector合体,不仅支持头删头插,尾删尾删,还支持随机访问
  • 在deque的底层中,是先开一段空间,空间不够再开一段空间(这里没有扩容),

6.1 deque的优势和缺陷

  • 优势: a.头尾插入删除, b.随机访问
  • 缺陷: 
    • operator[]计算稍显复杂,大量使用,将会导致性能下降(与vector的[]相比)
    • 中间插入删除效率不高
    • 迭代器会很复杂

头尾的插入和删除都非常适合,相比vector和list而言,所以很适合去做stack和queue的默认适配器

7. priority_queue(优先级队列)

priority_queue<int, vector<int>, less<int>> q1;

​7.1 堆的向上调整算法

#include <iostream>
#include <functional>
#include <queue>
using namespace std;
int main()
{
	priority_queue<int> q;
	q.push(3);
	q.push(6);
	q.push(0);
	q.push(2);
	q.push(9);
	q.push(8);
	q.push(1);
	while (!q.empty())
	{
		cout << q.top() << " ";
		q.pop();
	}
	cout << endl; //9 8 6 3 2 1 0
	return 0;
}

 ​7.2 堆的向下调整算法

 以大堆为例,使用堆的向下调整算法有一个前提,就是待向下调整的结点的左子树右子树必须都为大堆

//堆的向下调整(大堆)
void AdjustDown(vector<int>& v, int n, int parent)
{
	//child记录左右孩子中值较大的孩子的下标
	int child = 2 * parent + 1;//先默认其左孩子的值较大
	while (child < n)
	{
		if (child + 1 < n&&v[child] < v[child + 1])//右孩子存在并且右孩子比左孩子还大
		{
			child++;//较大的孩子改为右孩子
		}
		if (v[parent] < v[child])//左右孩子中较大孩子的值比父结点还大
		{
			//将父结点与较小的子结点交换
			swap(v[child], v[parent]);
			//继续向下进行调整
			parent = child;
			child = 2 * parent + 1;
		}
		else//已成堆
		{
			break;
		}
	}
}

 ​7.3 priorty_queue的模拟实现

namespace lyc //防止命名冲突
{
	//比较方式(使内部结构为大堆)
	template<class T>
	struct less
	{
		
	};
	//比较方式(使内部结构为小堆)
	template<class T>
	struct greater
	{
		
	};
	//优先级队列的模拟实现
	template<class T, class Container = vector<T>, class Compare = less<T>>
	class priority_queue
	{
	public:
		//堆的向上调整
		void AdjustUp(int child)
		{
			
		}
		//插入元素到队尾(并排序)
		void push(const T& x)
		{
			
		}
		//堆的向下调整
		void AdjustDown(int n, int parent)
		{
			
		}
		//弹出队头元素(堆顶元素)
		void pop()
		{
			
		}
		//访问队头元素(堆顶元素)
		T& top()
		{

		}
		const T& top() const
		{

		}
		//获取队列中有效元素个数
		size_t size() const
		{
			
		}
		//判断队列是否为空
		bool empty() const
		{
			
		}
	private:
		Container _con; //底层容器
		Compare _comp; //比较方式
	};
}
  • 相关代码: priority_queue模拟实现 
  • priority_queue底层是用数据结构中的堆实现,
  • 不传第三个模板参数(仿函数)默认是大堆

8. 仿函数

#include <iostream>
#include <algorithm>
#include <queue>
#include <functional>
using namespace std;

 /*仿函数/函数对象  -- 类,重载operator()
 类对象可以像函数一样去使用*/
namespace bit
{
	template<class T>
	class less
	{
	public:
		bool operator()(const T& l, const T& r) const
		{
			return l < r;
		}
	};

	template<class T>
	class greater
	{
	public:
		bool operator()(const T& l, const T& r) const
		{
			return l > r;
		}
	};
}

int main()
{
	bit::less<int> lsFunc;
	cout << lsFunc(1, 2) << endl;
	// 等价于下面
	//cout << lsFunc.operator()(1, 2) << endl;

	bit::greater<int> gtFunc;
	cout << gtFunc(1, 2) << endl;
	// 等价于下面
	//cout << lsFunc.operator()(1, 2) << endl;
	return 0;
}
  • 仿函数就是一个类重载了()运算符,

8.1 比较sort的仿函数和priority_queue的仿函数

#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;

int main()
{
	int arr[] = { 0,1,2,3,4,5,6 };
	vector<int> v(arr,arr+sizeof(arr)/sizeof(arr[0]));
	sort(v.begin(), v.end(), greater<int>());// 不传第三个参数默认排升序
	// sort(v.begin(), v.end());// 不传第三个参数默认排升序
	for (int i = 0; i < v.size(); i++)
	{
		cout << v[i] << " ";
	}
	cout << endl;
	priority_queue<int,vector<int>,greater<int>> pq;// 不传第三个参数模板参数默认排大堆
	pq.push(4);
	pq.push(3);
	pq.push(2);
	pq.push(1);
	while (!pq.empty())
	{
		cout << pq.top() << " ";
		pq.pop();
	}
	return 0;
}

 

  •  不同点:sort是传的仿函数对象,priority是传的仿函数类型

9. 容器的分类

 

 

  • forward_iterator 单向迭代器
  • bidirectional_iterator 双向迭代器
  • random_access_iterator 随机迭代器
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
STL list是C++标准模板库中的一个双向链表容器,它提供了一系列接口用于操作和管理链表中的元素。下面是一些常见的STL list接口: 1. 构造函数: - list(size_type n, const value_type& val = value_type()):构造包含n个值为val的元素的list。 - list():构造一个空的list。 - list(const list& x):拷贝构造函数,用于复制另一个list的内容构造新的list。 - list(InputIterator first, InputIterator last):用[first, last)区间中的元素构造list。 2. 容器大小: - size():返回list中元素的个数。 - empty():检查list是否为空。 3. 元素访问: - front():返回list中第一个元素的引用。 - back():返回list中最后一个元素的引用。 4. 插入和删除元素: - push_front(const value_type& val):在list的开头插入一个元素。 - pop_front():移除list的第一个元素。 - push_back(const value_type& val):在list的末尾插入一个元素。 - pop_back():移除list的最后一个元素。 - insert(iterator position, const value_type& val):在指定位置之前插入一个元素。 - erase(iterator position):移除指定位置的元素。 - erase(iterator first, iterator last):移除[first, last)区间中的元素。 5. 元素操作: - clear():移除list中的所有元素。 - remove(const value_type& val):移除所有等于val的元素。 - unique():移除所有相邻重复的元素。 - sort():对list中的元素进行排序。 - reverse():反转list中元素的顺序。 你可以参考C++官方文档(https://cplusplus.com/reference/list/list/)了解更多关于STL list的接口和用法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值