STL学习(4)- STL算法、迭代器以及泛型与容器的结合使用

有点长,各位可以根据目录选择性观看。

写在前面

STL这个专题是博主个人的总结,私下里整理的时候感觉有些东西像迭代器、find()等一些东西并不是某个容器独有的,没有必要没介绍一个容器都详细写一下这些东西。像泛型等知识,每个容器都要用到,也没必要每个容器都解释一下什么是泛型,所以博主总结了一下这些东西,整体包括一些算法和C++的相关知识,以及在STL中的应用。像insert、erase等方法,虽然大部分容器都有,但是还是有一些差别和不同的使用方式,这里就不多谈了。

泛型简单介绍与使用

泛型,理解以下字面是广泛类型编程,我们平常在写代码的时候对于一个变量,一定有一个确定的数据类型,int、double或者是自己写的类、结构体,但是在工程上我们对于数据的处理却不能仅仅指定一个单独的类型。就像函数的参数,如果对每一种数据类型都写一个单独的函数,就非常浪费时间。但是如果我们使用泛型,在参数上接受“广泛的数据类型”,那么只用写一个函数就能处理多种不同数据类型参数的情况。

在STL中,每个容器仅仅提供了底层的数据结构以及封装好的函数等,例如vector,我们也不知道存放在vector中的数据到底是int还是double,所以,在STL中,利用泛型就能解决多种参数的问题,同时实现代码的复用。

#include<iostream>
#include<string>

using namespace std;

template<typename T>

T add(T a, T b){
	return a + b;
}

int main(){
	int a = 2, b = 3;
	double c = 2.333, d = 1.2222;
	cout << add(a, b) << endl;
	cout << add(c, d) << endl;

	system("pause");
	return 0;
}

上面代码就是一个泛型的例子。template 是关键字,T表示一个待实例化的类型。我们可以理解为“代号T”,任何一个数据都能看成这个“代号T”。add函数的2个形参都是T数据类型,返回值也是T数据类型。我们第一次调用add函数,2个参数是int类型,那么T就代表了int,所以返回值也是int;第二次调用,2个参数是double,那么返回值也是double。

对于STL的容器而言,在接收参数的时候也有这样的需求,接受的参数是T,那么具体的函数实现都是以“代号T”来进行,无论是int还是double,因为都是T,所以都能接收。

#include<iostream>
#include<vector>

using namespace std;

int main(){
	vector<int> v;
	vector<double> v2;
	v.push_back(12);
	v2.push_back(2.33);
	cout << v[0] << endl;
	cout << v2[0] << endl;

	system("pause");
	return 0;
}

以上代码就是STL中使用最多的泛型了,尖括号“<>”中就是指出泛型T的具体数据类型,那么我们在使用容器的时候就只能放这个一个数据类型。等于说我们有vector<T>,T当然可以用任何一个数据类型来代替,但是代替后我们就必须用这个数据类型。v与v2,一个是int一个是double,但是全部都能存进去。实际上,STL中vector的代码只有只用,因为用了泛型,所以可以接收多种不同的数据类型。

同样的,我们在使用容器的时候都要利用泛型,这样才能确定具体要存放什么数据。例如:

#include<iostream>
#include<vector>
#include<map>
#include<set>

using namespace std;

int main(){
	vector<int> v;
	map<int, string> m;
	set<int> s;

	system("pause");
	return 0;
}

使用容器一定要确定泛型。

iterator

迭代器,因为有迭代,我们首先想到的就是for循环,迭代器也一样,就是遍历容器里的数据对象。对储存在容器中的数据进行处理的时候,迭代器能够按照预先的顺序(一般都是正向迭代,但是我们也可以逆向迭代,这个下面会说)从一个成员移动到下一个成员。换而言之,迭代器就是一个指针,不断地在容器中按顺序移动,指向不同的成员。

实际上迭代器是个泛化指针,也是利用了泛型。当生成一个容器的迭代器的时候,迭代器就指向了一个成员,然后可以通过自加一或者自减一来进行移动,就可以对数据进行操作。

上面不止一次谈到了“容器”,每个容器都能生成属于自己的迭代器,这样迭代器就能按照一定的顺序进行遍历,因为不同的容器底层的实现不同,迭代器的遍历方式也不同。由于容器利用了泛型,所以数据类型也不同,那么迭代器的数据类型也不同。所以说每种容器都有自己的迭代器。

vector<int> v;
vector<int>::iterator it;

这就是标准声明,声明一个iterator类型,属于一个vector容器,且数据类型为int的迭代器。这里注意,此时只是声明了it,并不代表it就指向了具体的位置。就好比int *a,只是声明了指针,并不代表已经指向了一个位置。例如:

vector<int> v1;
vector<int> v2;
vector<int>::iterator it;

仅仅是声明。但是注意,毕竟it是指针,指向的一定是vector<int>的数据,所以赋值的时候也不能有错误。下面给一段代码:

#include<iostream>
#include<vector>

using namespace std;

int main(){
	int arr[5] = { 1,3,5,8,9 };
	vector<int> v(arr,arr+5);
	vector<int>::iterator it;
	it = v.begin();
	for (; it != v.end(); it++) {
		cout << *it << endl;
	}

	system("pause");
	return 0;
}

这就是迭代器的简单使用。begin()函数,返回的是容器的首指针,也就是说,此时it指向的是容器中“1”的内存。for循环中判断退出的控制条件是 it != v.end(),end()函数,返回的是容器的尾指针的后一位,等于说已经“越界”了。*it,不再赘述(对指针不理解的话要先学一学了- -)。

我们上面说过,迭代器是按照顺序移动的,但是在for循环中我们明明是自己令it++来移动的。实际上按照顺序移动或者说正向迭代,指的是一个方向。有正向迭代一定有逆向迭代,逆向迭代的时候仍然使用it++,我们会发现指针在反向移动。所以说,it++指的是一个方向,指针正向移动的时候是正向还是反向,决定了迭代器的迭代顺序。

虽然话是这样说,但是正向迭代的时候强行逆向还是有些不方便,有代码:

#include<iostream>
#include<vector>

using namespace std;

int main(){
	int arr[5] = { 1,3,5,8,9 };
	vector<int> v(arr,arr+5);
	vector<int>::iterator it;
	it = v.end()-1;
	for (; it != v.begin(); it--) {
		cout << *it << endl;
	}
	cout << *(it) << endl;

	system("pause");
	return 0;
}

如果用begin()-1来判定是完全错误的,所以还要单独在输出一次*it。

上面都是以vector的迭代器为例,因为线性结构相对是简单的,理解起来也比较方便。同样对于list、deque,迭代器用起来也是类似的。但是对于set、map等,用起来差不多,但是底层的实现就完全不一样了,这里还是要注意的。

因为map是key-value形式的数据结构,那么迭代器也是有2个成员对象来分别储存key与value,可以看代码:

#include<iostream>
#include<map>
#include<string>

using namespace std;

int main(){
	map<int, string> m;
	pair<map<int, string>::iterator, bool> pair_it;
	pair_it = m.insert(pair<int, string>(1, "ss"));
	pair_it = m.insert(pair<int, string>(2, "aa"));
	pair_it = m.insert(pair<int, string>(3, "dd"));
	map<int, string>::iterator it = m.begin();
	for (; it != m.end(); it++) {
		cout << it->first << " " << it->second << endl;
	}

	system("pause");
	return 0;
}

由于map的特殊性,一定要用insert来插入数据,这里不细谈,不会的可以先学习一下map容器,这里重点在于遍历。可以看到it有2个成员变量:first、second,分别表示map中key与value的存放值。

iterator的一个重要的坑

这个问题是在刷题的时候碰到一次,该题的大概意思是判断字符串是否是回文串,同时要求将字符串中的括号去掉,大小写等价,只考虑其中的字母与数字。由于给的字符串是存在逗号等符号的,不能考虑这些符号。我的思路是先用stringstream将空格去掉,然后对字符串进行迭代,碰到非字母、数字的字符就利用erase()删除。这里给出第一次写的代码:

void work() {
    string::iterator it = poty.begin();
    for (; it != poty.end(); it++) {
        if (*it <= 'z' && *it >= 'a') continue;
        if (*it <= 'Z' && *it >= 'A') {
            *it = *it + 32;
            continue;
        }
        if (*it <= '9' && *it >= '0') continue;
        poty.erase(it);  // 看我看我!
    }
}

可以看出大概意思。但是实际上运行的时候直接异常了。这里注意一下有注释标记的那一行代码:

这个erase函数就是删除,也是常用的STL算法,几乎每一个容器都自带一个erase函数。并且包括find()在内的大部分函数,如果使用迭代器进行操作,注意操作后迭代器默认是自己移动了一位的!所以说这里删除后实际上跳过了一个字符!假设有字符串“abs/as”,那么it指向'/'时进行删除,然后it就指向a了,那么接下来直接for循环it++,it没有处理a就直接指向s。同样,如果a就是最后一个字符,那么it已经指向s.end()了,再it++就直接越界引发异常。所以正确的写法如下:

void work() {
    string::iterator it = poty.begin();
    for (; it != poty.end(); it++) {
        if (*it <= 'z' && *it >= 'a') continue;
        if (*it <= 'Z' && *it >= 'A') {
            *it = *it + 32;
            continue;
        }
        if (*it <= '9' && *it >= '0') continue;
        poty.erase(it);
        if (it == poty.end()) break;
        if (*it <= 'z' && *it >= 'a') continue;
        if (*it <= 'Z' && *it >= 'A') {
            *it = *it + 32;
            continue;
        }
        if (*it <= '9' && *it >= '0') continue;
    }
}
其实就是将上面的给再运行一次,同时判断下是否是字符串尾即可。

逆向迭代

上面谈过了迭代器,也提了一下逆向迭代,实际上逆向迭代也跟反转函数reverse()有一定关系,这里就先只谈逆向迭代。

#include<iostream>
#include<vector>

using namespace std;

int main(){
	int a[5] = { 1,2,3,4,5 };
	vector<int> v(a,a+5);
	vector<int>::reverse_iterator rt;
	rt = v.rbegin();
	for (; rt != v.rend(); rt++) {
		cout << *rt << endl;
	}

	system("pause");
	return 0;
}

我们可以跟之前写的迭代器代码进行比较,我们在逆向迭代的时候就不能创建一般的迭代器,而是要使用reverse_iterator逆向迭代器。同时,begin、end等也不能反着用,而是要使用rbegin与rend。这2个函数就是配合逆向迭代器使用的,作用就是反向的begin与end,rbegin实际是end,rend实际是begin,但是在逆向迭代的时候从rbegin开始到rend结束。

algorithm

STL中一个重要的库,算法库。包含了很多相关算法,用于处理容器中的数据。所以说这些函数本身跟是什么容器以及容器中是什么数据是无关的,在调用了algorithm库后即可使用这些函数:

#include<algorithm>

直接调用即可。下面将介绍常用的几个函数。

find与find_if

find函数其实在string以及vector中用的很多,但是那些是封装在容器中的函数,并不是algorithm库中的函数,或者说std::find()。下面介绍的就是关于find函数的使用。

find函数主要实现的是在容器内查找指定的元素,并且这个元素必须是基本数据类型的。查找成功返回一个指向指定元素的迭代器,查找失败返回end迭代器。也就是说,我们要用迭代器来调用查找结果。

下面的代码是非常基础的find(),分别对于数组和vector容器进行查找。

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

using namespace std;

int main(){
	int a[5] = { 1,2,3,4,5 };
	int *ans = find(a, a + 5, 3);
	cout << *ans << endl;

	vector<int> v(a,a+5);
	vector<int>::iterator it;
	it = find(v.begin(), v.end(), 3);
	if (it == v.end()) {
		cout << "not found!" << endl;
	}else {
		cout << *it << endl;
	}

	system("pause");
	return 0;
}

可以看出,find()函数的3个参数:find(begin,end,val);begin与end表示了开始与结束2个内存地址,val表示要查询的元素。返回值是迭代器(数组返回的是一个指针)。其实数组那里应该判断一下是否查找成功,而如果对容器查找,只用将迭代器与end()函数比较一下,如果没有找到,就是迭代器迭代了整个容器到达末尾;如果找到,就是迭代器停留在查找到的位置不再移动。

find_if函数,就是在普通的查找之上添加了一个查找条件,例如查找一个大于20的数,那么我们写一个返回值为bool的函数,在第3个参数将这个函数传入,那么find_if函数就会在内存范围内迭代所有的元素,同时将元素作为参数代入自定义的函数,如果返回值是true,视为找到这个元素,返回指向该位置的迭代器;否则返回end()。

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

using namespace std;

bool func(int a) {
	if (a > 20) return true;
	return false;
}

int main(){
	int a[5] = { 1,2,3,4,22 };
	vector<int> v(a,a+5);
	vector<int>::iterator it;
	it = find_if(v.begin(), v.end(), func);
	if (it == v.end()) {
		cout << "not found" << endl;
	}else {
		cout << *it << endl;
	}

	system("pause");
	return 0;
}

前2个参数也是确定查找范围,第三个参数需要提前写函数,将函数作为参数传入。确定找到元素的条件是将元素作为参数(所以写函数的时候要注意,形参一定要跟比较的数据类型相同,当然是用泛型也可以)看函数的返回值。

由于唯一判断找到的条件是形参函数的返回值,所以我们可以利用这个来干很多事情,上面提到过,find的查找只能找基本数据类型,那么我们就能够用find_if来查找结构体、类等。

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

using namespace std;

struct test {
	int a, b;
};

bool func(test temp) {
	if (temp.a == 1 && temp.b == 1) return true;
	return false;
}

int main() {
	vector<test> v;
	test temp;
	temp.a = 1;
	temp.b = 1;
	v.push_back(temp);
	vector<test>::iterator it;
	it = find_if(v.begin(), v.end(), func);
	if (it == v.end()) {
		cout << "not found!" << endl;
	}
	else {
		cout << it->a << " " << it->b << endl;
	}

	system("pause");
	return 0;
}

利用find_if可以进行很多特殊的查找。

以上,都是在线性结构上进行查找。如果是map、set等容器,还是推荐使用容器内封装的find进行操作。

reverse、reverse_copy与copy

reverse,字面意思就是反转。线性结构的话,确定一段内存,可以将该内存中所有元素进行反转。经典的例子就是判断回文串。如果不想使用manachar算法也不想利用栈的话,枚举回文串的中点以及串长,利用reverse函数,将反转的子串与中点另一侧子串进行比较,相等则说明发现回文串。下面给出一些例子:

#include<iostream>
#include<string>
#include<vector>
#include<map>
#include<algorithm>

using namespace std;

int main() {
	int a[5] = { 1,2,3,4,5 };
	reverse(a, a + 5);
	for (int i = 0; i < 5; i++) {
		cout << a[i] << " ";
	}
	cout << endl;

	int b[5] = { 1,2,3,4,5 };
	vector<int> v(b,b+5);
	reverse(v.begin(), v.end());
	for (int i = 0; i < 5; i++) {
		cout << v[i] << " ";
	}
	cout << endl;

	system("pause");
	return 0;
}

可以看到,reverse可以将返回内存内的数据直接反转。注意反转是建立在原内存上的,也就是说数组a和vector v都已经被反转了,跟之前的不一样。如果不想在原内存上进行反转,那么就可以利用reverse_cpoy函数。

#include<iostream>
#include<string>
#include<vector>
#include<map>
#include<algorithm>

using namespace std;

int main() {
	int b[5] = { 1,2,3,4,5 };
	vector<int> v(b,b+5);
	int a[5];
	reverse_copy(v.begin(), v.end(),a);
	cout << "a's value: ";
	for (int i = 0; i < 5; i++) {
		cout << a[i] << " ";
	}
	cout << endl;
	cout << "vector's value: ";
	for (int i = 0; i < 5; i++) {
		cout << v[i] << " ";
	}
	cout << endl;

	system("pause");
	return 0;
}

可以看到,相比与reverse()函数,reverse_copy()仅仅多了第三个参数,前2个参数仍然是2个界限,将这段内存中的数据反转储存在第三个参数作为开始的内存中。第三个参数就是一个指针,所以一个很重要的点就是如果储存在数组中,要防止越界。

如果理解了reverse_copy()函数,就非常容易理解copy()函数了。copy函数是reverse_copy()函数的简单版,也是3个参数,将一段内存中的数据复制到新的内存中,第三个参数指向新内存的开始。下面只提供代码段:

int b[5] = { 1,2,3,4,5 };
vector<int> v(b,b+5);
int a[5];
copy(v.begin(), v.end(),a);
cout << "a's value: ";
for (int i = 0; i < 5; i++) {
	cout << a[i] << " ";
}
cout << endl;
cout << "vector's value: ";
for (int i = 0; i < 5; i++) {
	cout << v[i] << " ";
}
cout << endl;

就是上面reverse_copy()函数示例代码中,将reverse_copy改成copy。

swap

swap()函数说起来很简单,就是交换2个相同的容器的内容,但是其本质却并不简单,博主单独写了一篇博文讨论了swap转换与内存的关系,详细使用与理解可以参考博主另一篇博客:

C++STL中swap函数操作与内存地址改变的简析

这里单独提一下,swap也可以交换结构体的,就是很简单的直接交换,例:

#include<iostream>
#include<string>
#include<vector>
#include<map>
#include<algorithm>

using namespace std;

struct str{
	int a, b;
};

int main() {
	str s1;
	s1.a = 1;
	s1.b = 1;
	str s2;
	s2.a = 2;
	s2.b = 2;
	swap(s1, s2);
	cout << s1.a << " " << s1.b << endl;
	cout << s2.a << " " << s2.b << endl;

	system("pause");
	return 0;
}

这里就不贴运行结果啦。

count与count_if

计数函数,很简单,跟find、find_if有异曲同工之妙。在线性的时间复杂度上统计数目。count就是简单的计数,3个参数:count(begin,end,value),begin与end是2个内存地址,表示在一段内存中,统计value的数量。所以count的对象都是基本数据类型。

如果想要一些特殊的计数,例如统计所有>5的数,或者对于结构体进行统计等等比较高级的操作,都用count_if函数。跟find_if一样,第三个参数是以个函数,实际在比较的时候将当前迭代的数据作为参数输入,函数返回true则进行一次计数,否则不计数。

下面的代码将展示这2个函数:

#include<iostream>
#include<string>
#include<vector>
#include<map>
#include<algorithm>

using namespace std;

struct str {
	int a, b;
};

bool cmp(str s) {
	if (s.a == 2 && s.b == 2) return true;
	return false;
}

int main() {
	int a[5] = { 1,2,3,4,3 };
	int ans = count(a, a + 5, 3);
	cout << ans << endl;
	
	str v[2];
	str temp;
	temp.a = 2;
	temp.b = 2;
	v[0] = temp;
	str temp2;
	temp2.a = 2;
	temp2.b = 2;
	v[1] = temp2;
	ans = count_if(v, v + 2, cmp);
	cout << ans << endl;

	system("pause");
	return 0;
}

结果当然都是2了。

sort

毕竟是C++,当然要展示一下大哥风范,所以sort函数的底层实现并不是原来我认为的“简单快排”。sort的底层非常庞大,并且根据数据量的不同会选择不同的算法来实现。

之前也提了很多次,博主个人对C++也不太感冒,更详细、底层的东西也不敢口胡,所以下面主要还是讲如何用这个函数了。这里推荐一篇文章,主要分析了sort的底层原理,写的很好,推荐大家看一下:

知无涯之std::sort源码剖析

sort的使用就非常简单了,给出一段内存范围,自动对内存范围内进行排序。关于这个内存范围,数组的话要单独写了,其他的容器类,list等使用begin与end函数是非常方便快捷的。例:

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

using namespace std;

int main() {
	int a[5] = { 2,1,6,4,9 };
	vector<int> v(a,a+5);
 	sort(a, a + 5);
	sort(v.begin(), v.end());
	for (int i = 0; i < 5; i++) {
		cout << a[i] << " ";
	}
	cout << endl;
	for (int i = 0; i < 5; i++) {
		cout << v[i] << " ";
	}
	cout << endl;

	system("pause");
	return 0;
}

这里注意,因为sort是直接在内存上操作的,并且也不提供返回值。也就是说排序后原来的数据都已经保存了。所以我们直接调用即可。

默认的sort函数是升序排序的,当然sort也提供了可选的第三参数,允许我们修改排序方式。第三参数允许我们传进去一个函数,sort函数在排序的时候不管底层的算法是什么,一定会进行2个数之间的比较。此时,就会把2个数传递进这个函数中,然后根据函数的返回值来确定排序方式。也就是说,这个函数的返回值一定是bool类型。例如我们要进行降序排序:

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

using namespace std;

bool cmp(int a, int b) {
	return a > b;
}

int main() {
	int a[5] = { 2,1,6,4,9 };
	vector<int> v(a,a+5);
 	sort(a, a + 5,cmp);
	sort(v.begin(), v.end(),cmp);
	for (int i = 0; i < 5; i++) {
		cout << a[i] << " ";
	}
	cout << endl;
	for (int i = 0; i < 5; i++) {
		cout << v[i] << " ";
	}
	cout << endl;

	system("pause");
	return 0;
}

上面的代码基本是第一个代码的变形,就加了cmp函数。我在cmp函数中定义:当a>b时,返回true,那么sort就认为:当a>b的时候是“正确的”,那么就会让整个数列任意的a与b(或者说前面的元素和后面的元素)都满足这个条件。从而完成了降序操作。

本质上,传进的函数是起到了比较的作用,具体的比较方式是任意的。所以sort函数的排序也有很大的扩展性,我们可以根据不同的情况灵活定义“排序方式”,只用修改cmp函数中的表达式即可。

如果想要拓展一下,我们可以根据数据结构的不同灵活改变cmp函数,从而令任意的数据都能按照我们的要求进行排序。下面的代码是对一个自定义的数据结构进行排序操作:

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

using namespace std;
struct node {
	int x, y;
};

bool cmp(node a, node b) {
	return a.x > b.x;
}

int main() {
	node a[5];
	for (int i = 0; i < 5; i++) {
		node newnode;
		newnode.x = i + 1;
		newnode.y = i + 2;
		a[i] = newnode;
	}
	sort(a, a + 5, cmp);
	for (int i = 0; i < 5; i++) {
		cout << a[i].x << " ";
	}
	cout << endl;

	system("pause");
	return 0;
}

这里可以看到跟之前的代码不同之处。当然a数组的变化以及赋值这里就不提了,这个看不懂还是好好学语言基础吧(可不是C++才有的)。主要谈一下cmp函数。可以看到,cmp函数的形参列表由原来的int变成了node,因为整个数组中存放的数据全部是node类型的,当然进行比较的时候也是node类型进行比较。这里可以测试一下,假设cmp函数的形参仍然是int,在我的VS2017版本下会出现这样的错误:

error C2664: “bool (int,int)”: 无法将参数 1 从“node”转换为“int”
note: 没有可用于执行该转换的用户定义的转换运算符,或者无法调用该运算符
error C2664: “bool (int,int)”: 无法将参数 2 从“node”转换为“int”

这样就很明了了,sort在运行的时候碰到需要匹配的就将2个数据直接作为参数传进cmp函数中。这样我们就能在函数体中具体定义比较的情况。

unique

unique(),顾名思义,是对数据去重的函数。但是这个函数不是我们常规意义上的去重。有2个重要特点:

1.去重之前先排序。

2.本质是数据交换,不会删除数据。

解释上面的特点就要先理解这个函数的具体作用了。unique()函数的使用跟sort等函数一样,也是对一段内存进行操作。所以标准使用如下:

int a[len] = {...};
unique(a,a+len);

但是看下面的代码:

#include<iostream>
#include<algorithm>

using namespace std;

int main() {
	int a[8] = { 1,4,5,2,1,4,6,2 };
	unique(a, a + 8);
	for (int i = 0; i < 8; i++) {
		cout << a[i] << " ";
	}

	system("pause");
	return 0;
}

输出的结果跟原来数组一样,并没有发生变化,这里先解释unique:

unique是针对相邻的2个数据进行判断是否重复,并且发现重复后,将重复的数据放在数组末尾。所以说我们要先对数据进行排序,否则函数无法在审视全局的情况下进行排序。由于重复的数据放在数据的末尾,所以可以说unique并没有进行删除操作。如果对上面的代码加上一句sort(a,a+8),输出内容则是重复的全部在末尾。

为了解决上面的问题,我们有2种方式:

1.利用unique的返回值,获得真正的长度,代码:

#include<iostream>
#include<algorithm>

using namespace std;

int main() {
	int a[8] = { 1,4,5,2,1,4,6,2 };
	sort(a, a + 8);
	int len = unique(a, a + 8) - a;
	for (int i = 0; i < len; i++) {
		cout << a[i] << " ";
	}

	system("pause");
	return 0;
}

2.如果使用迭代器等,由于unique返回的是迭代器,所以可以直接使用迭代器+erase函数将重复的元素删除,代码:

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

using namespace std;

int main() {
	int a[8] = { 1,4,5,2,1,4,6,2 };
	sort(a, a + 8);
	vector<int> v(a, a + 8);
	vector<int>::iterator it = unique(v.begin(),v.end());
	v.erase(it,v.end());
	for (int i = 0; i < v.size(); i++) {
		cout << v[i] << " ";
	}

	system("pause");
	return 0;
}

heap

其实博主是写了一点heap的相关内容的,但是heap的机制不能简单描述清楚,所以还是准备另开一篇博文介绍一下heap在STL中的使用,下面是挖坑传送门:

---挖坑待填---

后记

拖了很长时间才写完。。。主要还是太懒,咸鱼的时候想起来了写两下。博文也参考了很多大佬的文章,当然错误是难免的,如果能发现错误希望能留言告知,万分感谢!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值