常用STL(Standard Template Library 标准模板库)入门(C++)

常用STL(Standard Template Library 标准模板库)入门(C++)


  1. 不定长数组:vector
  2. 集合:set
  3. set和结构体
  4. 映射:map
  5. 二维map


动态数组 vector
  1. 引用库
    #include <vector>

  2. 构造动态数组

    • C++中直接构造一个vector的语句为: vector<T> vec 。这样我们定义了一个名为vec的储存 T 类型数据的动态数组;其中 T 是数组要储存的数据类型,可以使int、floot、double、或者其他自定义的数据类型等。
    • 初始的时候vec是空的。如vector<int> a 定义了一个储存整数的动态数组a
  3. 插入数组

    • C++ 中通过 push_back() 方法在数组最后面插入一个新的元素
  4. 获取长度并访问元素

    • C++ 中通过 size() 方法获取 vector 的长度,通过 [] 操作直接访问vector中的元素,这一点与数组一样
  5. 修改元素

    • C++ 中修改vector中某个元素只需用=赋值
  6. 删除元素

    • 和插入一样,删除元素也只能在动态数组的末端进行操作
    • C++ 中通过 pop_back() 方法删动态数组的最后一个元素
  7. 清空

    • C++ 中都只需要调用 clear() 方法就可清空vector
    • C++ 中vectorclear()只是清空vector ,并不会清空开的内存。用一种方法可以清空vector的内存:
// 清空vector的内存
// vector<int> v;
vector<int>().swap(v);

C++ vector方法总结

方法 功能
push_back 在末尾加入一个元素
pop_back 在末尾弹出一个元素
size 获取长度
clear 清空
#include<cstdio>
#include<vector>
#include<iostream>
using namespace std;
int main() {
	vector<int> vec;  // []
	vec.push_back(1); // [1]
	vec.push_back(2); // [1,2]
	vec.push_back(3); // [1,2,3]
	vec[1] = 3;  // [1,3,3]
	vec[2] = 2;  // [1,3,2]
	vec.pop_back();  // [1,3]
	vec.pop_back();  // [1]
	for (int i = 0; i < vec.size(); i++) {
		cout << vec[i] << endl;  
	}
    vec.clear();
	return 0;
} 

vector的一些用法
  1. 用动态数组存储自定义数据
    • 动态数组不仅仅可以存储基本的数据类型,还能存储自定义数据类型,如结构体
struct Student {
	string name;  // 名字 
	int age;	  // 年龄 
};

int main() {
	vector<Student> class1;  // 班级
	Student stu1, stu2;		 // 学生1 学生2 
	stu1.name = "xiaoming";
	stu1.age = 12;
	stu2.name = "daxiong";
	stu2.age = 18;
	class1.push_back(stu1);
	class1.push_back(stu2);	
	return 0; 
} 
  1. 构造函数
    如果我们需要一个长度为n,全是1的动态数组。我们可以像下面这样写:
int n = 10;
vector<int> vec;
for (int i = 0; i < n; i++) {
    vec.push_back(1);
}

其实,我们可以用构造函数的方式快速构建这样一个动态数组。所谓构造函数,就是我们在定义一个对象的时候可以给他赋予初始值。

int n = 10;
vector<int> vec(n, 1);
  • 我们在定义一个vector的时候,调用构造函数,第一个参数表示初始的动态数组的长度第二个参数表示初始的数组里面每个元素的值。如果不传入第二个参数,那么初始值都是0.
  1. 二维动态数组
    • vector<vector<int> > vec2 这样就定义了一个二维的动态数组。注意,<int> > 中间有一个空格,这个空格一定要加上,否则在一些老版本的编译器上将不能通过编译。
    • 通过上面的方法定义的二维数组,每一维都是空的,我们必须要一维一维的赋值。比如我们规定第一维的大小为n
#include<vector>
#include<cstdio>
#include<iostream>
using namespace std;
int main() {
	int n = 5;
	vector<vector<int> > vec2;
	for (int i = 0; i < n; i++) {
		vector<int> x(i+1, 1);
		vec2.push_back(x);
	}
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < vec2[i].size(); j++) {
			cout << vec2[i][j] << " ";
		} 
		cout << endl;
	}
	return 0;
} 
// 输出结果
1
1 1
1 1 1
1 1 1 1
1 1 1 1 1
  • 二维动态数组的每一维的长度都可以不一样,可以是任意形状的
    借助构造函数,我们可以快速构造一个n行m的动态数组,每个元素的初始值是0:vector<vector<int> > vec2(n, vector<int>(m, 0));

动态数组非常容易写错的写法:

vector<int> vec;
for (int i = 0; i < 10; i++) {
    cout << vec[i] << endl;
}
// 没有 push_back 任何元素,会访问非法内存
vector<vector<int> > vec2;
for (int i = 0; i < 10; i++) {
    vec2[i].push_back(i);
}
// 该代码当我们尝试访问vec2[i] 时,会访问非法内存,因为第一维的大小为0
// 下面有三种已修复正确的代码
vector<vector<int> > vec2(10, vector<int>(5)); // 第一维长度为5 全部为0
for (int i = 0; i < 10; ++i) {
    vec2[i].push_back(i);
}

vector<vector<int> > vec2(10, vector<int>()); 
for (int i = 0; i < 10; ++i) {
    vec2[i].push_back(i);
}

vector<vector<int> > vec2(10); 
for (int i = 0; i < 10; ++i) {
    vec2[i].push_back(i);
}
  • vector的维度可以像数组一样更多,但超过两维以后操作起来麻烦,所以一般用vector都只用到两维
//定义一个长度为n,全是0的动态数组x的三种方法:
vector<int> x(n);
vector<int> x(n, 0);
vector<int> x;
for (int i = 0; i < n; i++) {
    x.push_back(0);
}
//动态输入 输出乘法口诀表 
#include<cstdio>
#include<vector>
#include<iostream>
using namespace std;

int main() {
	int n;
	cin >> n;
	vector<vector<int> > v2d;
	for (int i = 0; i < n; i++) {
		v2d.push_back(vector<int>()); // 给第一维赋值 大小为n 每一个元素是一个一维的vector 
	}
	
	for (int i = 0; i < v2d.size(); i++) {
		for (int j = 0; j <= i; j++) {
			v2d[i].push_back((i+1)*(j+1));  // 用二维的vector来记录一个1到n的乘法表 
		}
	}
	
	for (int i = 0; i < v2d.size(); i++) {
		for (int j = 0; j < v2d[i].size(); j++) {
			cout << j + 1 << " * " << i + 1 << " = " << v2d[i][j] << "\t";
		}
		cout << endl;
	} 
	return 0;
}


集合 set
  1. 引用库
    #include <set>

  2. 构造一个集合

    • C++ 中直接构造一个set语句为:set<T> s 这样我们定义了一个名为 s 、存储 T 类型数据的集合,其中 T 是集合要存储的数据类型。初始的时候 s 是空集合。如set<int> aa, set<string> bbb等等
  3. 插入元素

    • C++中用 insert() 函数向集合中插入一个新的元素
    • 如果集合中已经存在了某个元素,再次插入不会产生任何效果,集合中不会出现重复元素
  4. 删除元素

    • C++ 中通过 erase() 函数删除集合中的一个元素,如果集合中不存在这个元素,不进行任何操作
  5. 判断元素是否存在

    • C++ 中如果你想知道某个元素是否在集合中出现,你可以直接用 count() 函数。
    • 如果集合中存在我们要找的元素,返回1,否则会返回0.
  6. 遍历元素

    • C++ 通过迭代器可以访问集合中的每个元素,迭代器就好像一根手指指向 set 中的某个元素。通过操作这个手指,我们可以改变它指的元素。 通过 * (解引用运算符,不是乘号的意思) 操作可以获取迭代器指向的元素。通过 ++ 操作让迭代器指向下一个元素,同理 -- 操作让迭代器指向上一个元素。
    • 迭代器的写法比较固定:set<T>::iterator it 就定义了一个指向 set<T> 这种集合的迭代器 itT 是任意的数据类型。其中 ::iterator 是固定的写法
    • begin 函数返回容器中起始元素的迭代器,end 函数返回容器的尾后迭代器

注意,在C++中遍历set是从小到大遍历的, 也就是说set 会帮我们排好序。

  1. 清空
    • C++ 中调用 clear() 函数就可清空 set,同时会清空set占用的内存

C++ set函数总结
set中插入、删除、查找某个元素的时间复杂度都是O(log n),并且内部元素是有序的

函数 功能 时间复杂度
insert 插入一个元素 O(log n)
erase 删除一个元素 O(log n)
count 统计集合中某个元素的个数 O(log n)
size 获取元素个数 O(1)
clear 清空 O(n)
#include<cstring>
#include<set>
#include<iostream>
using namespace std;
int main() {
	set<string> country;  // {}
	country.insert("China"); // {"China"}
	country.insert("America");  // {"China", "America"}
	country.insert("France");  // {"China", "America", "France"}
	country.erase("America");  // {"China", "France"}
	country.erase("England");  // {"China", "France"}
	if (country.count("China")) {
		cout << "China belong to country" << endl;
	}
	for (set<string>::iterator it = country.begin(); it != country.end(); it++) {
		cout << *it << endl;  // {"China", "France"}
	}
	country.clear();
	return 0;
} 

set和结构体
  • set 经常会配合结构体来使用,用set来存储结构体和vector有些区别。set是需要经过排序的;系统自带的数据类型有默认的比较大小的规则,而我们自定义的结构体,系统不可能知道这个结构体比较大小的方式,所以我们需要用一种方式来告诉系统怎么比较这个结构体的大小。其中一种方法叫做运算符重载,我们需要 重新定义小于符号

下面的代码定义了一个重载了小于符号的结构体:

struct Node {
	int x, y;
	bool operator<(const Node &rhs) const {
		if(x ==rhs.x) {
			return y < rhs.y;
		} else {
			return x < rhs.x;
		}
	}
};
  • operator< 表示我们要重载运算符 < ,可以看成是一个函数名。rhs 是“right hand side”的简称,有右操作数的意思,这里我们定义一个 const 引用。因为该运算符重载定义在结构体内部,左操作数就当前调用 operator< 的对象。
  • 特别要注意,不要漏掉最后的 constconst 函数表示不能对其数据成员进行修改操作,并且const 对象不能调用非 const 成员函数,只允许调用 const 成员函数。
  • 上面重载规定了排序方式为,优先按照 x 从小到大排序,如果 x 相同,那么再按照 y 从小到大排序。经过了 < 运算符重载的结构体,我们就可以比较两个Node对象的大小了,因此可以直接存储在set中了。

-> 运算符 it->x(*it).x 的效果是一样的,就是获取迭代器 it 指向结构体里 x 变量的值

/* 用set来存储一个二维坐标系上点的集合
输入
6
5 6
1 2
2 1
3 4
1 2
1 1

输出
1 1
1 2
2 1
3 4
5 6 
*/
#include<set>
#include<cstdio>
#include<iostream>
using namespace std;

struct Point {
	int x, y;
	bool operator<(const Point &rhs) const {  // 给结构体添加 < 的运算符重置 
		if(x == rhs.x) {
			return y < rhs.y;
		} else {
			return x < rhs.x;
		}
	}
};
	
int main() {
	int n;
	set<Point> v;
	cin >> n;
	for (int i = 0; i < n; i++) {
		Point temp;  // 定义一个储存点的容器 
		cin >> temp.x >> temp.y;
		v.insert(temp);
	}
	for (set<Point>::iterator it = v.begin(); it != v.end(); it++) {
		cout << it->x << " " << it->y << endl;
	}
	return 0; 
} 


映射 map

关键字集合(key)–> 值集合(value)

  1. 引用库
    #include<map>

  2. 构造一个映射

    • 在C++中,构造一个map的语句为: map<T1, T2> m . 这样我们定义了一个名为 m 的从 T1 类型到 T2 类型的映射。初始的时候 m 是空映射.
      • map<string, int> m 构建了一个字符串到整数的映射,这样我们可以把一个字符串和一个整数关联起来
  3. 插入一对映射

    • 在C++ 中通过 insert() 函数向集合中插入一个新的映射,参数是一个 pair
      • pair 是一个标准库类型,定义在头文件 utility 中。可以看成是有两个成员变量firstsecond的结构体,并且重载了 < 运算符(先比较first大小,如果一样再比较second)。当我们创建一个 pair 时,必须提供两个类型。
      • 我们可以像这样定义一个保存stringintpair
        pair<string, int> p;
    • make_pair(v1, v2) 函数返回由 v1v2 初始化的pair,类型可以从v1v2的类型判断出来
    • 我们向映射中加入新映射对的时候就是通过插入 pair 来实现的。如果插入的key之前已经存在了,将不会用插入的新的value替代原来的value,也就是这次插入是无效的
  4. 访问映射

    • 在C++中访问映射和数组一样,直接用[]就能访问。比如dict["Tom"] 就可以获取"Tom" 的班级。而如果没有对"Tom"做过映射的话,此时你访问dict["Tom"],系统将会自动为"Tom"生成一个映射,其value为对应类型的默认值(比如int的默认值是0, string的默认值是空字符串)
    • 并且我们可以之后再给映射赋予新的值,比如dict["Tom"] = 3,这样为我们提供了另一种方便的插入手段。实际上常用通过下标访问的方式来插入映射,而不是通过用insert插入一个pair来实现。
  5. 判断关键字是否存在

    • 如果你想知道某个关键字是否被映射过,可以使用 count() 函数。如果关键字存在,返回1,否则返回0.
  6. 遍历映射

    • map的迭代器的定义和set差不多,map<T1, T2>::iterator it 就定义了一个迭代器,其中T1T2 分别是 keyvalue 的类型
    • C++通过迭代器可以访问集合中的每个元素。这里迭代器指向的元素是一个pair,有firstsecond两个成员变量,分别代表一个映射的keyvalue

我们用 -> 运算符来获取值,it->first(*it).first 的效果是一样的,就是获取迭代器it指向的pairfirst成员的值
注意,在C++中遍历map是按照关键字从小到大遍历的, 这一点与set有些共性

  1. 清空
    • 调用 clear() 函数就可清空map和其占用的内存

C++中map常用函数总结

函数 功能 时间复杂度
insert 插入一对映射 O(log n)
count 判断关键字是否存在 O(log n)
size 获取映射对个数 O(1)
clear 清空 O(n)
#include<map>
#include<string>
#include<utility>
using namespace std;
int main() {
	map<string, int> dict;				// dict 是一个 string 到 int的映射,存放每个名字对应的班级号,初始值为空 
	dict.insert(make_pair("Tom", 1));	// {"Tom"->1} 
	dict.insert(make_pair("Jone", 2));	// {"Tom"->1, "Jone"->2}
	dict.insert(make_pair("Mary", 1));	// {"Tom"->1, "Jone"->2, "Mary"->1}
	dict.insert(make_pair("Tom", 2));	// {"Tom"->1, "Jone"->2, "Mary"->1}
	if(dict.count("Mary")) {
		cout << "Mary is in class "<< dict["Mary"] << endl;
	} else {
		cout << "Mary has no class" << endl;
	}
	for (map<string, int>::iterator it = dict.begin(); it != dict.end(); it++) {
		cout << it->first << " -> " << it->second << endl;  // first 是关键字,second 是对应值 
	}
	return 0;
}

二维map
  1. map套用set
    map<int, set<string> > s 就定义上面描述的数据结构,和二维vector一样,两个> > 中间的空格不能少了

    • 可理解为给全校的班级进行编号,对每一个班级建立一个set,但这样只能分辨不同班级的同名同学,不能分辨同班同名同学。
    • 如果对2班的小明进行插入查询删除操作,插入 s[2].insert("xiaoming") ,查询 s[2].count("xiaoming") ,删除 s[2].erase("xiaoming")
  2. map套用map
    map<int, map<string, int> > s. 可统计同班同名的同学,2班有一个小明,s[2]["xiaoming"]++, 2班共有个 s[2]["xiaoming"] 小明。

/* 统计各班级各名字个数
输入
6
1 zgh
2 yuhaoran
2 yuhaoran
1 party
100 xxx
50 xxx
 
输出
There are 1 people named party in class 1
There are 1 people named zgh in class 1
There are 2 people named yuhaoran in class 2
There are 1 people named xxx in class 50
There are 1 people named xxx in class 100
*/ 

#include<map>
#include<iostream>
using namespace std;
int main() {
	map<int, map<string, int> > info;
	int n;
	cin >> n;
	for (int i = 0; i < n; i++) {
		int class_id;
		string name;
		cin >> class_id >> name;
		info[class_id][name]++;
	} 
	
	for (map<int, map<string, int> >::iterator it1 = info.begin(); it1 != info.end(); it1++) {
		//it1->first 是一个 int,表示班级编号,it1->second 是一个 map<string, int> 容器 
		//遍历内层迭代器,即遍历某个班级里每个名字个数的 map 
		for (map<string, int>::iterator it2 = it1->second.begin(); it2 != it1->second.end(); it2++) {
		 	cout << "There are " << it2->second << " people named " << it2->first << " in class " << it1->first << endl;
		} 
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/KennGum/article/details/107827117