最全c++ STL_谁的孙子最多 c++(1),2024年字节跳动74道高级程序员面试

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

//2 判断用户输入的用户名中是否包含除了小写字母之外字符(ASCII范围97~122)

bool Check\_Username(string& email){
	
	int pos = email.find("@");
	string username = email.substr(0,pos-1);
	for (string::iterator it = username.begin(); it != username.end(); it++){
		if (\*it < 97 || \*it > 122){
			return false;
		}
	}

	return true;

}

// 3 判断用户输入的邮箱地址是否正确(alne@mavenir.cn)

bool Check\_EqualtTo(string& email){
	
	string rightEmail = "zhaosi@itcast.cn";
	if (email.compare(rightEmail) != 0){
		return false;
	}
	return true;
}

void testEmail(){

	//用户邮箱地址验证
	// 1 判断邮箱有效性 是否包含@和. 并且.在@之后
	// 2 判断用户输入的用户名中是否包含除了小写字母之外字符(ASCII范围97~122)
	// 3 判断用户输入的邮箱地址是否正确(alne@mavenir.cn)

	string email;
	cout << "请输入您的邮箱:" << endl;
	cin >> email;

	bool flag = Check\_Valid(email);
	if (!flag){
		cout << "emain格式不合法!" << endl;
		return;
	}

	flag = Check\_Username(email);
	if (!flag){
		cout << "用户名中包含除小写字母之外的字母!" << endl;
		return;
	}

	flag = Check\_EqualtTo(email);
	if (!flag){
		cout << "邮箱地址不正确!" << endl;
		return;
	}

	cout << "邮箱输入正确!" << endl;
}

int main(){

	testEmail();

	system("pause");
	return EXIT_SUCCESS;
}

2.1.4 string使用注意事项

1、不要忽略string::size_type 类型,string::size_type 的类型是unsigned类型,代码中通过宏判断程序编译的位数,从而决定是32还是64。另外string中使用find频繁,所以建议单独去看看find的使用,熟能生巧。

2、关于char数组和string
char s[]="abcd"; 这个是字符串,以'\0'结束的,用strlen(s)是4,'\0'不计,但是它是有五个字符的
char s1[]={'a','b','c','d'}; 这个是字符数组,没有'\0',用strlen(s1)是不确定的,它有四个字符
char数组转换string

char ch[]="hello world!";
//1.向构造函数传入c字符串创建string对象:
string str(ch);
//2.使用拷贝构造函数创建string对象:
string str = ch;
//3.对已有的string对象调用string类内部定义的赋值运算符:
string str;
str = ch; 

3、当函数使用string临时对象并返回时出现乱码
返回的是临时对象,外面要使用变量接住,否则马上就析构了。

#include<iostream>
#include <string>

using namespace std;

char\* Fun(char\* Char1, const char \* Char2)
{
	string str1 = Char1;
	const string str2 = Char2;
	str1 = str1 + str2;
	return (const\_cast<char\*>(str1.c\_str()));
}

int main(void)`在这里插入代码片`
{
	char\* CharStr1 = "ab";
	const char\* CharStr2 = "cd";

	cout << Fun(CharStr1,CharStr2) << endl;
	return 0 ;

}

4、string转char * 乱码问题

C++提供了两个函数:c_str()data()
当处理较短的string时,直接调用这两个函数没有出现问题,调用格式如下:

std::string str = "Hello Word";
char\* p1 = str.c\_str();
char\* p2 = str.data();

当字符串比较长时,采用这两个函数,转换出来的char* 出现乱码,采用下边这种方式是完全没问题的:

char \* strc = new char[strlen(s.c\_str()) + 1];
strcpy\_s(strc, s.length() + 1,s.c\_str());

string转const char* ,可以尝试以下操作

const char \*str;
str = \_strdup(s.c\_str());

示例代码:

#define \_CRT\_SECURE\_NO\_WARNINGS

#define STR "世界上第一种计算机高级语言是诞生于1954年的FORTRAN语言。之后出现了多种计算机高级语言。1970年,AT&T的Bell实验室的D.Ritchie和K.Thompson共同发明了C语言。研制C语言的初衷是用它编写UNIX系统程序,因此,它实际上是UNIX的副产品。它充分结合了汇编语言和高级语言的优点,高效而灵活,又容易移植。 1971年,瑞士联邦技术学院N.Wirth教授发明了Pascal语言。Pascal语言语法严谨,层次分明,程序易写,具有很强的可读性,是第一个结构化的编程语言。20世纪70年代中期,Bjarne Stroustrup在剑桥大学计算机中心工作。他使用过Simula和ALGOL,接触过C。他对Simula的类体系感受颇深,对ALGOL的结构也很有研究,深知运行效率的意义。既要编程简单、正确可靠,又要运行高效、可移植,是Bjarne Stroustrup的初衷。以C为背景,以Simula思想为基础,正好符合他的设想。1979年,Bjame Sgoustrup到了Bell实验室,开始从事将C改良为带类的C(C with classes)的工作。1983年该语言被正式命名为C++。自从C++被发明以来,它经历了3次主要的修订,每一次修订都为C++增加了新的特征并作了一些修改。第一次修订是在1985年,第二次修订是在1990年,而第三次修订发生在c++的标准化过程中。在20世纪90年代早期,人们开始为C++建立一个标准,并成立了一个ANSI和ISO(Intemational Standards Organization)国际标准化组织的联合标准化委员会。该委员会在1994年1月25曰提出了第一个标准化草案。在这个草案中,委员会在保持Stroustrup最初定义的所有特征的同时,还增加了一些新的特征。在完成C++标准化的第一个草案后不久,发生了一件事情使得C++标准被极大地扩展了:Alexander stepanov创建了标准模板库(Standard Template Library,STL)。STL不仅功能强大,同时非常优雅,然而,它也是非常庞大的。在通过了第一个草案之后,委员会投票并通过了将STL包含到C++标准中的提议。STL对C++的扩展超出了C++的最初定义范围。虽然在标准中增加STL是个很重要的决定,但也因此延缓了C++标准化的进程。委员会于1997年11月14日通过了该标准的最终草案,1998年,C++的ANSI / IS0标准被投入使用。通常,这个版本的C++被认为是标准C++。所有的主流C++编译器都支持这个版本的C++,包括微软的Visual C++和Borland公司的C++Builder。"

#include <iostream>
#include <vector>
#include <string>
#include <cstring>

using namespace std;


int main()
{
	const string s = STR;

	const char\* p1 = s.c\_str();
	const char\* p2 = s.data();

	cout << p1 << endl << endl;
	cout << p2 << endl << endl;

	const char \*str;
	str = \_strdup(s.c\_str());
	cout << str <<endl << endl << endl;

	char \* strc = new char[strlen(s.c\_str()) + 1];
	strcpy\_s(strc, s.length() + 1,s.c\_str());
	cout << strc << endl << endl;

	return 0;
}

5、cctype中一些常用的字符函数
在这里插入图片描述

2.2 vector容器

2.2.1 vector特性

vector容器是一个长度动态改变的动态数组,既然也是数组,那么其内存是一段连续的内存,具有数组的随机存取的优点。
vector是动态数组,连续内存空间,具有随机存取效率高的优点。
vector是单口容器,在队尾插入和删除元素效率高,在指定位置插入会导致数据元素移动,效率低。

vector如何实现动态增长?
答: 当vector空间满的时候,再当插入新元素的时候,vector会重新申请一块更大的内存空间,将原空间数据拷贝到新的内存空间,然后释放旧的内存空间,再将新元素插入到新空间中,以此可以看出vector的空间动态增长效率较低。
动态增长是怎样的?增长多大?
答: 当添加元素时,如果vector空间大小不足,则会以原大小的两倍另外配置一块较大的新空间,然后将原空间内容拷贝过来,在新空间的内容末尾添加元素,并释放原空间。vector的空间动态增加大小,并不是在原空间之后的相邻地址增加新空间,因为vector的空间是线性连续分配的,不能保证原空间之后有可供配置的空间。因此,对vector的任何操作,一旦引起空间的重新配置,指向原vector的所有迭代器就会失效。

2.2.2 vector常用API
2.2.2.1 vector构造函数及其初始化
vector<T> v; //采用模板实现类实现,默认构造函数
vector(v.begin(), v.end());//将v[begin(), end())区间中的元素拷贝给本身。
vector(n, elem);//构造函数将n个elem拷贝给本身。
vector(const vector &vec);//拷贝构造函数。

vector<int> a(10); //定义了10个整型元素的向量(尖括号中为元素类型名,它可以是任何合法的数据类型),但没有给出初值,其值是不确定的。
vector<int> a(10,1); //定义了10个整型元素的向量,且给出每个元素的初值为1
vector<int> a(b); //用b向量来创建a向量,整体复制性赋值
vector<int> a(b.begin(),b.begin+3); //定义了a值为b中第0个到第2个(共3个)元素
int b[7]={1,2,3,4,5,9,8};
vector<int> a(b,b+7); //从数组中获得初值

2.2.2.1 vector赋值操作
assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);//将n个elem拷贝赋值给本身。
vector& operator=(const vector  &vec);//重载等号操作符
swap(vec);// 将vec与本身的元素互换。

//第一个赋值函数,可以这么写:
int arr[] = { 0, 1, 2, 3, 4 };
assign(arr, arr + 5);//使用数组初始化vector


2.2.2.1 vector大小操作
size();//返回容器中元素的个数
empty();//判断容器是否为空
resize(int num);//重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
resize(int num, elem);//重新指定容器的长度为num,若容器变长,则以elem值填充新位置。如果容器变短,则末尾超出容器长>度的元素被删除。
capacity();//容器的容量
reserve(int len);//容器预留len个元素长度,预留位置不初始化,元素不可访问。

2.2.2.1 vector数据存取操作
at(int idx); //返回索引idx所指的数据,如果idx越界,抛出out\_of\_range异常。
operator[];//返回索引idx所指的数据,越界时,运行直接报错
front();//返回容器中第一个数据元素
back();//返回容器中最后一个数据元素

2.2.2.1 vector插入和删除
insert(const_iterator pos, int count,ele);//迭代器指向位置pos插入count个元素ele.
push\_back(ele); //尾部插入元素ele
pop\_back();//删除最后一个元素
erase(const_iterator start, const_iterator end);//删除迭代器从start到end之间的元素
erase(const_iterator pos);//删除迭代器指向的元素
clear();//删除容器中所有元素

例子

vec.insert(vec.begin()+i,a);//在第i+1个元素前面插入a
vec.erase(vec.begin()+2);//删除第3个元素
vec.erase(vec.begin()+i,vec.end()+j);//删除区间[i,j-1];区间从0开始

注意:
end()指向的是最后一个元素的下一个位置,所以访问最后一个元素的正确操作为:v.end() - 1

2.2.3 vector练习

删除数组中的奇数

#include <iostream>
#include <vector>

using namespace std;
int main()
{
	vector<int> a = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	for (vector<int>::iterator it = a.begin(); it != a.end();)
	{
		if ((\*it) % 2 == 1)
			it = a.erase(it);
		else
			it++;
	}
	for (vector<int>::iterator it = a.begin(); it != a.end(); it++)
		cout << \*it;
	return 0;
}

2.2.4 vector使用注意事项

1.reserve和resize的区别
答:reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。
而resize在开空间的同时还会进行初始化,会影响size。

2.vector迭代器失效
insert(可能会增容)或erase之后,会引发迭代器失效 解决办法:重置迭代器,让迭代器指向新的有效的空间
解决erase迭代器失效的办法:迭代器在设置的时候,有一个返回值,它会返回最近删除位置下一个位置的有效迭代器

2.1当插入(push_back)一个元素后,end操作返回的迭代器肯定失效。
2.2当插入(push_back)一个元素后,capacity返回值与没有插入元素之前相比有改变,则需要重新加载整个容器,此时first和end操作返回的迭代器都会失效。不增容不会导致迭代器失效。
2.3当进行删除操作(erase,pop_back)后,指向删除点的迭代器全部失效;指向删除点后面的元素的迭代器也将全部失效

2.2.5 vector总结

总结: vector是个动态数组,当空间不足的时候插入新元素,vector会重新申请一块更大的内存空间,将旧空间数据拷贝到新空间,然后释放旧空间。vector是单口容器,所以在尾端插入和删除元素效率较高,在指定位置插入,势必会引起数据元素移动,效率较低。

2.3 deque容器

2.3.1 deque特性

deque是“double-ended queue”的缩写,和vector一样,deque也支持随机存取。vector是单向开口的连续性空间,deque则是一种双向开口的连续性空间,所谓双向开口,意思是可以在头尾两端分别做元素的插入和删除操作,vector当然也可以在头尾两端进行插入和删除操作,但是头部插入和删除操作效率奇差,无法被接受。

deque和vector的最大差异是
一在于deque允许常数时间内对头端进行元素插入和删除操作。
二在于deque没有容量的概念,因为它是动态的以分段的连续空间组合而成,随时可以增加一段新的空间并链接起来,换句话说,像vector那样“因旧空间不足而重新分配一块更大的空间,然后再复制元素,释放空间”这样的操作不会发生在deque身上,也因此deque没有必要提供所谓的空间保留功能。

特点:
1、双端插入和删除元素效率较高.
2、指定位置插入也会导致数据元素移动,降低效率.
3、可随机存取,效率高.

2.3.2 deque常用API
2.3.2.1 deque构造函数
deque<T> deqT;//默认构造形式
deque(beg, end);//构造函数将[beg, end)区间中的元素拷贝给本身。
deque(n, elem);//构造函数将n个elem拷贝给本身。
deque(const deque &deq);//拷贝构造函数。

2.3.2.2 deque赋值操作
assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);//将n个elem拷贝赋值给本身。
deque& operator=(const deque &deq); //重载等号操作符 
swap(deq);// 将deq与本身的元素互换


#include <iostream>
#include <deque>

int main()
{
	std::deque<int> first;
	std::deque<int> second;
	std::deque<int> third;

	first.assign(7, 100);             // 7 ints with a value of 100

	std::deque<int>::iterator it;
	it = first.begin() + 1;

	second.assign(it, first.end() - 1); // the 5 central values of first

	int myints[] = { 1776,7,4 };
	third.assign(myints, myints + 3);   // assigning from array.

	std::cout << "Size of first: " << int(first.size()) << '\n';
	std::cout << "Size of second: " << int(second.size()) << '\n';
	std::cout << "Size of third: " << int(third.size()) << '\n';
	return 0;
}


2.3.2.3 deque大小操作
deque.size();//返回容器中元素的个数
deque.empty();//判断容器是否为空
deque.resize(num);//重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
deque.resize(num, elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置,如果容器变短,则末尾超出容器长度的元素被删除。

2.3.2.4 deque双端插入和删除操作
push\_back(elem);//在容器尾部添加一个数据
push\_front(elem);//在容器头部插入一个数据
pop\_back();//删除容器最后一个数据
pop\_front();//删除容器第一个数据

2.3.2.5 deque数据存取
at(idx);//返回索引idx所指的数据,如果idx越界,抛出out\_of\_range。
operator[];//返回索引idx所指的数据,如果idx越界,不抛出异常,直接出错。
front();//返回第一个数据。
back();//返回最后一个数据

2.3.2.6 deque插入操作
insert(pos,elem);//在pos位置插入一个elem元素的拷贝,返回新数据的位置。
insert(pos,n,elem);//在pos位置插入n个elem数据,无返回值。
insert(pos,beg,end);//在pos位置插入[beg,end)区间的数据,无返回值。

经验之谈 : deque是分段连续的内存空间,通过中控器维持一种连续内存空间的状态,其实现复杂性要大于vector queue stack等容器,其迭代器的实现也更加复杂,在需要对deque容器元素进行排序的时候,建议先将deque容器中数据数据元素拷贝到vector容器中,对vector进行排序,然后再将排序完成的数据拷贝回deque容器。

2.3.2.7 deque删除操作
clear();//移除容器的所有数据
erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置。
erase(pos);//删除pos位置的数据,返回下一个数据的位置。

2.3.5 deque使用注意事项

1、不遍历deque
deque 是由一段一段的定量的连续空间构成。一旦有必要在 deque 前端或者尾端增加新的空间,便配置一段连续定量的空间,串接在 deque 的头端或者尾端。deque 最大的工作就是维护这些分段连续的内存空间的整体性的假象,并提供随机存取的接口,避开了重新配置空间,复制,释放的轮回,代价就是复杂的迭代器架构。
因此在需要遍历时,不建议使用它。

2、deque迭代器失效

成员函数push_back():会直接在容器末尾添加一个元素。原迭代器中end()会失效,其他的都不会失效。

成员函数push_front():会直接在容器头部添加一个元素。原迭代器中begin()会失效,其他的都不会失效。

成员函数pop_back():会直接删除最后一个元素。原迭代器中end()会失效,其他的都不会失效

成员函数pop_front():会直接在容器头部删除一个元素。原迭代器中begin()会失效,其他的都不会失效。

成员函数insert(iterator, n):如果插入点之前的元素较少,会在容器头部插入一个元素,然后将插入点及其之前的所有元素向前移动一位,再在插入点创建新元素。否则,将插入点及其之后的元素向后移动一位,再在插入点创建新元素。因此,向前移动则导致原迭代器中插入点及插入点之前的迭代器都失效;向后移动则导致迭代器中插入点及插入点之后的迭代器都失效。

成员函数erase(iterator):如果删除点之前的元素较少,将删除点之前的所有元素向后移动一位,再删除第一个元素。否则,将删除点之后的所有元素向前移动一位,再删除最后一个元素。因此,向前移动将导致原迭代器中删除点及删除点之后的迭代器失效;向后移动将导致原迭代器中删除点及删除点之前的迭代器都失效

原文链接:https://blog.csdn.net/xp178171640/article/details/104905338

2.3.6 deque总结

deque它支持高效的在其首部插入和删除元素。

最后关于vector、list、deque三个容器使用选择的一些准则

如果我们需要随机访问一个容器,则vector要比List好得多。
如果我们一直要存储元素的个数,则vector又是一个比list好的选择。
如果我们需要的不只是在容器两端插入和删除元素,则list显然比vector好。
除非我们需要在容其首部插入和删除元素,否则vector要比deque好。
如果只需要在读取输入时在容器的中间位置插入元素,然后需要随机访问元素,则可考虑输入时将元素读入到一个List容器,然后排序,然后将排序后的list容器复制到一个vector容器中

2.4 stack容器

2.4.1 stack特性

stack是一种先进后出(first in last out,FILO)的数据结构,它只有一个出口,stack只允许在栈顶新增元素,移除元素,获得顶端元素,但是除了顶端之外,其他地方不允许存取元素,只有栈顶元素可以被外界使用,也就是说stack不具有遍历行为,没有迭代器。
因此,栈不能遍历,不支持随机存取,只能通过top从栈顶获取和删除元素。

2.4.2 stack常用API
2.4.2.1 stack构造函数
stack<T> stkT;//stack采用模板类实现, stack对象的默认构造形式: 
stack(const stack &stk);//拷贝构造函数

2.4.2.2 stack赋值操作
stack& operator=(const stack &stk);//重载等号操作符

2.4.2.3 stack数据存取操作
push(elem);//向栈顶添加元素
pop();//从栈顶移除第一个元素
top();//返回栈顶元素

2.4.2.3 stack大小操作
empty();//判断堆栈是否为空
size();//返回堆栈的大小

2.4.3 stack注意事项

在使用pop()和top()函数之前必须先使用empty()函数判断栈是否为空。

2.4.4 stack总结

算法笔记中这样提到:stack用来模拟实现一些递归,防止程序对栈内存的限制而导致程序运行出错。一般来说,程序的栈内存空间很小,对有些题目来说,如果用普通的函数来进行递归,一旦递归层数过深(不同机器不同,约几千至几万层),则会导致程序运行崩溃。如果用栈来模拟递归算法的实现,则可以避免这一方面的问题(不过这种应用出现较少)。

2.5 queue

2.5.1 queue特性

queue是一种先进先出(first in first out, FIFO)的数据类型,他有两个口,数据元素只能从一个口进,从另一个口出.队列只允许从队尾加入元素,队头删除元素,必须符合先进先出的原则,queue和stack一样不具有遍历行为。
特点:
1、必须从一个口数据元素入队,另一个口数据元素出队。
2、不能随机存取,不支持遍历

2.5.2 queue常用API
2.5.2.1 queue构造函数
queue<T> queT;//queue采用模板类实现,queue对象的默认构造形式:
queue(const queue &que);//拷贝构造函数

2.5.2.2 queue存取、插入和删除操作
push(elem);//往队尾添加元素
pop();//从队头移除第一个元素
back();//返回最后一个元素
front();//返回第一个元素

2.5.2.3 queue赋值操作
queue& operator=(const queue &que);//重载等号操作符

2.5.2.4 queue大小操作

empty();//判断队列是否为空

size();//返回队列的大小

2.5.3 queue使用注意事项

1、优先队列
priority_queue 对于基本类型的使用方法相对简单。他的模板声明带有三个参数:
priority_queue<Type, Container, Functional> 其中Type 为数据类型, Container 为保存数据的容器,Functional 为元素比较方式。
Container 必须是用数组实现的容器,比如 vector, deque 但不能用 list.STL里面默认用的是 vector. 比较方式默认用 operator< , 所以如果你把后面俩个参数缺省的话,优先队列就是大顶堆,队头元素最大。

#include <iostream>
#include <queue> 
using namespace std; 
int main(){
    priority_queue<int> q;     
    for( int i= 0; i< 10; ++i ) q.push( rand() );
    while( !q.empty() ){
        cout << q.top() << endl;
        q.pop();
    }     
    getchar();
    return 0;
}

如果要用到小顶堆,则一般要把模板的三个参数都带进去。
STL里面定义了一个仿函数 greater<>,对于基本类型可以用这个仿函数声明小顶堆

#include <iostream>
#include <queue> 
using namespace std; 
int main(){
    priority_queue<int, vector<int>, greater<int> > q;
    for( int i= 0; i< 10; ++i ) q.push( rand() );
    while( !q.empty() ){
        cout << q.top() << endl;
        q.pop();
    }     
    getchar();
    return 0;
}

对于自定义类型,则必须自己重载 operator< 或者自己写仿函数

#include <iostream>
#include <queue> 
using namespace std; 
struct Node{
    int x, y;
    Node( int a= 0, int b= 0 ):
        x(a), y(b) {}
};
 
bool operator<( Node a, Node b ){
    if( a.x== b.x ) return a.y> b.y;
    return a.x> b.x; 
}
 
int main(){
    priority_queue<Node> q;     
    for( int i= 0; i< 10; ++i )
    q.push( Node( rand(), rand() ) );     
    while( !q.empty() ){
        cout << q.top().x << ' ' << q.top().y << endl;
        q.pop();
    }     
    getchar();
    return 0;
}

2.5.4 queue总结

共同点:都是只允许在端点处插入和删除元素的数据结构;
不同点:栈是仅在栈顶进行访问,遵循后进先出的原则(LIFO);队列是在队尾插入数据,在队头删除数据(FIFO)

2.6 list

2.6.1 list特性

对于链表,我们应该再熟悉不过了。
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
特点:
采用动态存储分配,不会造成内存浪费和溢出
链表执行插入和删除操作十分方便,修改指针即可,不需要移动大量元素
链表灵活,但是空间和时间额外耗费较大

2.6.2 list常用API
2.6.2.1 list构造函数
list<T> lstT;//list采用采用模板类实现,对象的默认构造形式:
list(beg,end);//构造函数将[beg, end)区间中的元素拷贝给本身。
list(n,elem);//构造函数将n个elem拷贝给本身。
list(const list &lst);//拷贝构造函数。

2.6.2.2 list数据元素插入和删除操作
push\_back(elem);//在容器尾部加入一个元素
pop\_back();//删除容器中最后一个元素
push\_front(elem);//在容器开头插入一个元素
pop\_front();//从容器开头移除第一个元素
insert(pos,elem);//在pos位置插elem元素的拷贝,返回新数据的位置。
insert(pos,n,elem);//在pos位置插入n个elem数据,无返回值。
insert(pos,beg,end);//在pos位置插入[beg,end)区间的数据,无返回值。
clear();//移除容器的所有数据
erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置。
erase(pos);//删除pos位置的数据,返回下一个数据的位置。
remove(elem);//删除容器中所有与elem值匹配的元素。

2.6.2.3 list大小操作
size();//返回容器中元素的个数
empty();//判断容器是否为空
resize(num);//重新指定容器的长度为num,

若容器变长,则以默认值填充新位置。
如果容器变短,则末尾超出容器长度的元素被删除。

resize(num, elem);//重新指定容器的长度为num,

若容器变长,则以elem值填充新位置。
如果容器变短,则末尾超出容器长度的元素被删除。

2.6.2.4 list赋值操作
assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);//将n个elem拷贝赋值给本身。
list& operator=(const list &lst);//重载等号操作符
swap(lst);//将lst与本身的元素互换。

2.6.2.5 list数据的存取
front();//返回第一个元素。
back();//返回最后一个元素。

2.6.2.6 list反转排列排序
reverse();//反转链表,比如lst包含1,3,5元素,运行此方法后,lst就包含5,3,1元素。
sort(); //list排序

2.6.3 list使用注意事项

1、list使用迭代器删除元素

#include <iostream>
#include <list>


using namespace std;


int main(int argc, char\* argv[])
{
	std::list<int> mList;
	mList.push\_back(1);
	mList.push\_back(2);
	mList.push\_back(0);
	mList.push\_back(3);
	mList.push\_back(4);
	mList.push\_back(0);
	mList.push\_back(0);
	mList.push\_back(6);
	mList.push\_back(0);
	mList.push\_back(0);


	std::list<int>::iterator iter = mList.begin();
	for (; iter != mList.end(); )
	{
		if (0 != \*iter)
		{
			++iter;
		}
		else
		{
			mList.erase(iter++);
		}
	}


	iter = mList.begin();
	for (; iter != mList.end(); ++iter)
	{
		std::cout << " " << \*iter << " ";


	}


	return 0;
}

  • 对于list中只有元素的删除操作会导致指向该元素的迭代器失效,其他元素迭代器不受影响。
  • 对于vector元素的删除、插入操作会导致指向该元素以及后面的元素的迭代器失效。

2、链表和数组的区别:

  1. 数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费。
  2. 链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据元素。(数组中插入、删除数据项时,需要移动其它数据项)
2.6.4 list总结

1、list是可以在常数范围类任意位置进行插入和删除的序列式容器,可以前后双向迭代
2、list的底层是双向带头循环链表
3、list个forword_list类似,最大的不同是forword只能向前迭代。
4、list在任意位置插入删除的效率较高
5、list不支持随机访问

2.7 set/multiset容器

2.7.1 set/multiset特性

set/multiset的特性是所有元素会根据元素的值自动进行排序。set是以RB-tree(红黑树,平衡二叉树的一种)为底层机制,其查找效率非常好。set容器中不允许重复元素,multiset允许重复元素。

通过红黑树实现的set能够在log2n 的时间内完成插入、查找、删除的数据结构。

补充概念:
1、二叉树就是任何节点最多只允许有两个字节点。分别是左子结点和右子节点。
2、二叉搜索树,是指二叉树中的节点按照一定的规则进行排序,使得对二叉树中元素访问更加高效。二叉搜索树的放置规则是:任何节点的元素值一定大于其左子树中的每一个节点的元素值,并且小于其右子树的值。因此从根节点一直向左走,一直到无路可走,即得到最小值,一直向右走,直至无路可走,可得到最大值。那么在儿茶搜索树中找到最大元素和最小元素是非常简单的事情。
3、平衡二叉树

  • 是「二叉排序树」
    二叉排序树又称二叉排序树。它或者是一个空树,或者是一个具有下列性质的二叉树:
    若它的左子树不空,则左子树上所有节点的值均小于它的根结构的值
    若它的右子树不空,则右子树上所有结点的值均大于它的根节点的值
    它的左、右子树也分别是二叉排序树
  • 任何一个节点的左子树或者右子树都是「平衡二叉树」(左右高度差小于等于 1)
2.7.2 set 常用API
2.7.2.1 set构造函数
set<T> st;//set默认构造函数:
mulitset<T> mst; //multiset默认构造函数: 
set(const set &st);//拷贝构造函数

2.7.2.2 set赋值操作
set& operator=(const set &st);//重载等号操作符
swap(st);//交换两个集合容器

2.7.2.3 set大小操作
size();//返回容器中元素的数目
empty();//判断容器是否为空

2.7.2.4 set插入和删除操作
insert(elem);//在容器中插入元素。
clear();//清除所有元素
erase(pos);//删除pos迭代器所指的元素,返回下一个元素的迭代器。
erase(beg, end);//删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
erase(elem);//删除容器中值为elem的元素。

2.7.2.5 set查找操作
find(key);//查找键key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回map.end();
lower\_bound(keyElem);//返回第一个key>=keyElem元素的迭代器。
upper\_bound(keyElem);//返回第一个key>keyElem元素的迭代器。
equal\_range(keyElem);//返回容器中key与keyElem相等的上下限的两个迭代器。

2.7.3 set使用注意事项

1、set升序与降序

#include <iostream>
#include <set>
using namespace std;
 
struct myfunc 
{
    bool operator()(int v1,int v2) const
    {
        return v1 > v2;
    }
};
 
int main()
{
    //set升序排列
    set<int> Tmp;
    //初始化
    Tmp.insert(8);
    Tmp.insert(10);
    Tmp.insert(100);
    Tmp.insert(9);
    Tmp.insert(20);
    Tmp.insert(3);
 
    for (set<int>::iterator it = Tmp.begin(); it != Tmp.end(); ++it)
    {
        cout << "升序排列:" << \*it << endl;
    }
 
 
    //set降序排列
    set<int, myfunc> setTmp;
    //初始化
    setTmp.insert(8);
    setTmp.insert(10);
    setTmp.insert(100);
    setTmp.insert(9);
    setTmp.insert(20);
    setTmp.insert(3);
 
    for (set<int,myfunc>::iterator it = setTmp.begin();  it != setTmp.end(); ++it)
    {
        cout << "降序排列:" << \*it << endl;
    }
 
}

2、我们不可以通过set的迭代器改变元素的值,因为set集合是根据元素值进行排序,关系到set的排序规则,如果任意改变set的元素值,会严重破坏set组织。
3、set删除操作

S.erase(key);        //删除一个值,如果没有就无操作
S.erase(S.find(key));//删除迭代器代表的值,若果没有,就会删除S.end(),会导致在遍历set的时候无法终止。
S.erase(S.lower\_bound(key1), S.lower\_bound(key2));//删除[key1,key2)之内的所有值。

2.8 map/multimap容器

2.8.1 map/multimap特性

map相对于set区别,map具有键值和实值,所有元素根据键值自动排序。pair的第一元素被称为键值,第二元素被称为实值。map也是以红黑树为底层实现机制。

2.8.2 对组

对组(pair)将一对值组合成一个值,这一对值可以具有不同的数据类型,两个值可以分别用pair的两个公有函数first和second访问。
类模板:template <class T1, class T2> struct pair.
创建对组方式

//第一种方法创建一个对组
pair<string, int> pair1(string("name"), 20);
cout << pair1.first << endl; //访问pair第一个值
cout << pair1.second << endl;//访问pair第二个值
//第二种
pair<string, int> pair2 = make\_pair("name", 30);
cout << pair2.first << endl;
cout << pair2.second << endl;
//pair=赋值
pair<string, int> pair3 = pair2;
cout << pair3.first << endl;
cout << pair3.second << endl;

2.8.3 map常用API
2.8.3.1 map构造函数
map<T1, T2> mapTT;//map默认构造函数: 
map(const map &mp);//拷贝构造函数

初始化

typedef std::map<std::string,std::string> stringmap;
stringmap first;                              // empty
stringmap second ( {{"apple","red"},{"lemon","yellow"}} );       // init list
stringmap third ( {{"orange","orange"},{"strawberry","red"}} );  // init list
stringmap fourth (second);                    // copy
stringmap fifth (merge(third,fourth));        // move
stringmap sixth (fifth.begin(),fifth.end());  // range

2.8.3.2 map赋值操作
map& operator=(const map &mp);//重载等号操作符
swap(mp);//交换两个集合容器

2.8.3.3 map大小操作
size();//返回容器中元素的数目
empty();//判断容器是否为空

2.8.3.4 map插入数据元素操作
map.insert(...); //往容器插入元素,返回pair<iterator,bool>
map<int, string> mapStu;
// 第一种 通过pair的方式插入对象
mapStu.insert(pair<int, string>(3, "james"));
// 第二种 通过pair的方式插入对象
mapStu.inset(make\_pair(-1, "wade"));
// 第三种 通过value\_type的方式插入对象
mapStu.insert(map<int, string>::value\_type(1, "paul"));
// 第四种 通过数组的方式插入值
mapStu[3] = "kobe";
mapStu[5] = "jordan";

注意:
前三种方法,采用的是insert()方法,该方法返回值为pair<iterator,bool>
第四种方法非常直观,但存在一个性能的问题。插入3时,先在mapStu中查找主键为3的项,若没发现,则将一个键为3,值为初始化值的对组插入到mapStu中,然后再将值修改成“小刘”。若发现已存在3这个键,则修改这个键对应的value。
string strName = mapStu[2]; //取操作或插入操作
只有当mapStu存在2这个键时才是正确的取操作,否则会自动插入一个实例, 键为2,值为初始化值。

2.8.3.5 map删除操作
clear();//删除所有元素
erase(pos);//删除pos迭代器所指的元素,返回下一个元素的迭代器。
erase(beg,end);//删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
erase(keyElem);//删除容器中key为keyElem的对组。

2.8.3.6 map查找操作
find(key);//查找键key是否存在,若存在,返回该键的元素的迭代器;/若不存在,返回map.end();
count(keyElem);//返回容器中key为keyElem的对组个数。对map来说,要么是0,要么是1。对multimap来说,值可能大于1。
lower\_bound(keyElem);//返回第一个key<=keyElem元素的迭代器。
upper\_bound(keyElem);//返回第一个key>keyElem元素的迭代器。
equal\_range(keyElem);//返回容器中key与keyElem相等的上下限的两个迭代器。

2.8.4 map使用注意事项

1、operator[]的语义导致的问题

例如定义 map<string, mystruct*> map_sample
mystruct为自定义类型。
通过操作符[], 我们可以像数组一样访问map中的成员, 非常的方便, 如:

mystruct \*ptr = map_sample["alen"];

当键alen存在不会导致问题,但是当键 ‘alen’在当前map_sample中不存在时,操作符[]会自动在map_sample中添加键alen,并且将对应的值初始化缺省值(这里将导致值被初始化为NULL)。
这个特性在某些应用场景中可能会带来问题,如果值mystruct*每次都是malloc,这样在另外任务中通过[]访问后直接使用,碰到上面这种不存在情况时将导致访问空指针或者在调用free时导致程序core dump。
看到这里你会说,那是因为你使用[]后没有判断指针是否为NULL导致的,不是[]的问题,但是成员’alen’是实实在在的被添加了进去了,在你发现返回指针为NULL时,是不是要再去erase掉这个值为空的键值对呢?所以如果你的程序不能确定某个key是不是存在,建议不要使用操作符’[]', 而是通过使用接口find判断后再访问,可以避免出现上述问题。

2、通过map的迭代器不可以修改map的键值,键值关系到容器内元素的排列规则,任意改变键值会破坏容器的排列规则,但是你可以改变实值。

2.8.4 map总结

1、map与unordered_map相比:

map底层实现为红黑数,undered_map底层实现为哈希表,两者均不能有重复的建,均支持[]运算符

2、map与multimap相比:

两者底层实现均为红黑树,map不允许相同key值存在,但是multimap支持重复的键,不支持[]运算符。

2.9 STL容器共性机制

STL容器所提供的都是值(value)寓意,而非引用(reference)寓意,也就是说当我们给容器中插入元素的时候,容器内部实施了拷贝动作,将我们要插入的元素再另行拷贝一份放入到容器中,而不是将原数据元素直接放进容器中,也就是说我们提供的元素必须能够被拷贝。
除了queue和stack之外,每个容器都提供可返回迭代器的函数,运用返回的迭代器就可以访问元素。
通常STL不会抛出异常,需要使用者传入正确参数。
每个容器都提供了一个默认的构造函数和默认的拷贝构造函数。
大小相关的构造方法: 1 size()返回容器中元素的个数 2 empty()判断容器是否为空

2.10 STL容器使用时机

vector的使用场景:比如软件历史操作记录的存储,我们经常要查看历史记录,比如上一次的记录,上上次的记录,但却不会去删除记录,因为记录是事实的描述。
deque的使用场景:比如排队购票系统,对排队者的存储可以采用deque,支持头端的快速移除,尾端的快速添加。如果采用vector,则头端移除时,会移动大量的数据,速度慢。
vector与deque的比较:
一:vector.at()比deque.at()效率高,比如vector.at(0)是固定的,deque的开始位置却是不固定的。
二:如果有大量释放操作的话,vector花的时间更少,这跟二者的内部实现有关。
三:deque支持头部的快速插入与快速移除,这是deque的优点。
list的使用场景:比如公交车乘客的存储,随时可能有乘客下车,支持频繁的不确实位置元素的移除插入。
set的使用场景:比如对手机游戏的个人得分记录的存储,存储要求从高分到低分的顺序排列。

map的使用场景:比如按ID号存储十万个用户,想要快速要通过ID查找对应的用户。二叉树的查找效率,这时就体现出来了。如果是vector容器,最坏的情况下可能要遍历完整个容器才能找到该用户。

更多容器的特点及时间复杂度对比参考
https://blog.csdn.net/qq_39382769/article/details/102441699

第三章 常用算法

3.1 内建函数对象

STL内建了一些函数对象。分为:算数类函数对象,关系运算类函数对象,逻辑运算类仿函数。这些仿函数所产生的对象,用法和一般函数完全相同,当然我们还可以产生无名的临时对象来履行函数功能。使用内建函数对象,需要引入头文件 #include。

6个算数类函数对象,除了negate是一元运算,其他都是二元运算。

template<class T> T plus<T>//加法仿函数
template<class T> T minute<T>//减法仿函数
template<class T> T multiplies<T>//乘法仿函数
template<class T> T divides<T>//除法仿函数
template<class T> T modulus<T>//取模仿函数
template<class T> T negate<T>//取反仿函数

6个关系运算类函数对象,每一种都是二元运算。

template<class T> bool equal_to<T>//等于
template<class T> bool not_equal_to<T>//不等于
template<class T> bool greater<T>//大于
template<class T> bool greater_equal<T>//大于等于
template<class T> bool less<T>//小于
template<class T> bool less_equal<T>//小于等于

逻辑运算类运算函数,not为一元运算,其余为二元运算。

template<class T> bool logical_and<T>//逻辑与
template<class T> bool logical_or<T>//逻辑或
template<class T> bool logical_not<T>//逻辑非

使用例子:

//使用内建函数对象声明一个对象
plus<int> myPlus;
cout << myPlus(5, 3) << endl;
//使用匿名临时对象
cout << plus<int>()(5, 6) << endl;

sort排序使用预定义函数对象进行排序。
count_if equal_to 参数绑定

3.2 算法概述

算法主要是由头文件<algorithm> <functional> <numeric>组成。
<algorithm>是所有STL头文件中最大的一个,其中常用的功能涉及到比较,交换,查找,遍历,复制,修改,反转,排序,合并等…
<numeric>体积很小,只包括在几个序列容器上进行的简单运算的模板函数.
<functional> 定义了一些模板类,用以声明函数对象。
STL算法分为:质变算法和非质变算法。
所有的STL算法都作用在由迭代器[first,end)所标示出来的区间上,所谓质变算法,是指运算过程中会改变区间内的(迭代器所指)的元素内容。比如,拷贝(copy)、互换(swap)、替换(replace)、填写(fill)、删除(remove)、排序(sort)等算法都属于此类。
非质变算法是指是指在运算过程中不会区间内(迭代器所指)的元素内容,比如查找(find)、计数(count)、遍历(for_each)、寻找极值(max,min)等,都属于此类。但是如果你在for_each遍历每个元素的时候试图应用一个会改变元素内容的仿函数,那么元素当然也会改变。

3.3 常用遍历算法

/\*
 遍历算法 遍历容器元素
 @param beg 开始迭代器
 @param end 结束迭代器
 @param \_callback 函数回调或者函数对象
 @return 函数对象
\*/
for\_each(iterator beg, iterator end, _callback);
/\*
 transform算法 将指定容器区间元素搬运到另一容器中
 注意 : transform 不会给目标容器分配内存,所以需要我们提前分配好内存
 @param beg1 源容器开始迭代器
 @param end1 源容器结束迭代器
 @param beg2 目标容器开始迭代器
 @param \_cakkback 回调函数或者函数对象
 @return 返回目标容器迭代器
\*/
transform(iterator beg1, iterator end1, iterator beg2, _callbakc)

3.3.1 for_each:

基本正向遍历和逆向遍历

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

t << plus()(5, 6) << endl;


sort排序使用预定义函数对象进行排序。  
 count\_if equal\_to 参数绑定


### 3.2 算法概述


算法主要是由头文件`<algorithm> <functional> <numeric>`组成。  
 `<algorithm>`是所有STL头文件中最大的一个,其中常用的功能涉及到比较,交换,查找,遍历,复制,修改,反转,排序,合并等…  
 `<numeric>`体积很小,只包括在几个序列容器上进行的简单运算的模板函数.  
 `<functional>` 定义了一些模板类,用以声明函数对象。  
 STL算法分为:质变算法和非质变算法。  
 所有的STL算法都作用在由迭代器[first,end)所标示出来的区间上,所谓质变算法,是指运算过程中会改变区间内的(迭代器所指)的元素内容。比如,拷贝(copy)、互换(swap)、替换(replace)、填写(fill)、删除(remove)、排序(sort)等算法都属于此类。  
 非质变算法是指是指在运算过程中不会区间内(迭代器所指)的元素内容,比如查找(find)、计数(count)、遍历(for\_each)、寻找极值(max,min)等,都属于此类。但是如果你在for\_each遍历每个元素的时候试图应用一个会改变元素内容的仿函数,那么元素当然也会改变。


### 3.3 常用遍历算法



/*
遍历算法 遍历容器元素
@param beg 开始迭代器
@param end 结束迭代器
@param _callback 函数回调或者函数对象
@return 函数对象
*/
for_each(iterator beg, iterator end, _callback);
/*
transform算法 将指定容器区间元素搬运到另一容器中
注意 : transform 不会给目标容器分配内存,所以需要我们提前分配好内存
@param beg1 源容器开始迭代器
@param end1 源容器结束迭代器
@param beg2 目标容器开始迭代器
@param _cakkback 回调函数或者函数对象
@return 返回目标容器迭代器
*/
transform(iterator beg1, iterator end1, iterator beg2, _callbakc)


#### 3.3.1 for\_each:


基本正向遍历和逆向遍历  


[外链图片转存中...(img-zZX6L9V8-1715811136765)]
[外链图片转存中...(img-StdxA49n-1715811136766)]

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值