C++ day41 STL算法库(处理容器的非成员函数)

前面零碎介绍到的

  • sort()

但是sort()非成员函数要求参数是随机访问迭代器,list容器哭了,,,他做不到啊,于是list类就自力更生自立自强,自己开发了一个sort()成员函数,使用双向迭代器作为参数。

  • copy()
  • random_shuffle()
  • set_union()
  • set_intersection()
  • set_difference()
  • transform()
  • find()

STL非成员方法的通用设计:用模板提供泛型,用迭代器提供容器中数据的通用表示

所有的STL非成员函数实现算法,都是基于这两个总体的设计思路来的。这两点也已经是我们强调的不能再强调,发现的不能再发现的了。他们就是STL重用代码的终极套路。

模板的作用就不说了。迭代器的作用可以再举例子:
copy函数可以把常规数组(因为指针是一种特殊的迭代器)复制到list容器中,到vector容器中,可以把vector容器的数据复制到树实现的set集合容器中。。。。。可以用==来比较vector对象和list对象的数据,因为容器重载的==使用的是迭代器来比较不同容器的内容。如果deque对象和vector对象的内容一样,顺序也一样,则他们就是相等的,==会返回true。

STL的容器的设计也是统一的。
可以看到模板和迭代器在STL中的重要性了。完全就离不开,到处都是。很多函数都要用迭代器指明区间。

且STL算法会用模板参数的名称告诉一些信息,比如:
在这里插入图片描述
没有随意地把模板参数命名为T, U, 而是专门命名为InputIterator和OutputIterator,这样用户一看原型就知道要用什么迭代器作为参数。

STL算法库分类

根据完成的操作来分类(官方分类法)

基于算法完成的操作来分类,算法库可分为四组(前三种在头文件algorithm,最后一种在头文件numeric中。):

  • 非修改序列操作

“序列”指的是依次对区间的每个元素进行操作。“非修改”指的是这些操作不修改元素本身。

比如:
for_each()
find()

  • 修改序列操作

还是依次操作每一个元素,但是要修改元素本身了。

比如:
transform()
random_shuffle()
copy()

  • 排序即排序相关的操作

包括很多排序函数,比如sort(),还有集合操作

  • 通用数学运算

包括区间的内容进行累加,两个容器的内积,俩容器的差等,都是数学运算,一般vector容器用这些方法最多

根据结果放置的位置分类:就地算法 VS 复制算法

  • 就地算法:in-place algorithm, 就地完成工作, 即结果存放在原始数据的位置上,比如sort()
  • 复制算法:copying algorithm,把结果存在另一个位置,而非原数据位置,比如copy()

很多算法都有就地版本和复制版本两个版本的实现。比如transform()函数,如果他的输出位置指向输入区间,则为就地版本,否则为复制版本。(不是所有方法都允许输出位置指向输入区间的,必须原型中的前两个迭代器参数用可以支持读写的迭代器的方法才可以)

STL约定,让有两种版本实现的方法的复制版本以后缀_copy结尾,并接受一个额外的输出迭代器参数,指向结果存放位置。而就地版本只需要两个迭代器参数(参数可以不只两个,但是迭代器只需要2个),反正输出数据就地存放。

在这里插入图片描述

注意复制版本和就地版本在返回值类型,迭代器参数类型上差别很大,一般复制版本的对指定区间的两个迭代器要求很低,输入迭代器即可,而输出位置也只需要输出迭代器。而就地版本由于原数据位置又要读又要写,所以必须至少是正向迭代器

_if后缀版本的函数

有一类函数,先把函数应用于容器元素,如果得到的值是true,才把旧值替换为新值啥的。
在这里插入图片描述

string类和STL的暧昧关系

  • string类并不是STL的组成部分
  • 但是string的设计考虑到了STL:它也有begin(), end(), rbegin(), rend()等成员方法
  • string类可以使用STL的接口(方法),比如sort()

示例:给string类用STL方法:一个单词的所有排列组合,next_permutation函数暴力穷举

跑起来有一点慢。
真的给出了所有的排列组合啊,枚举

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

int main()
{
	using namespace std;
	string letters;
	cout << "Enter the letter grouping (quit to quit):";
	while (cin >> letters && letters != "quit"){
		cout << "Permutations of " << letters << endl;
		sort(letters.begin(), letters.end());
		cout << letters <<endl;
		while (next_permutation(letters.begin(), letters.end()))
			cout << letters << ' ';
		cout << "enter next sequence(quit to quit): ";
	}
	cout << "Done!\n";
	return 0;
}

妈呀。。。

Enter the letter grouping (quit to quit):animal
Permutations of animal
aailmn
aailnm aaimln aaimnl aainlm aainml aalimn aalinm aalmin aalmni aalnim aalnmi aamiln aaminl aamlin aamlni aamnil aamnli aanilm aaniml aanlim aanlmi aanmil aanmli aialmn aialnm aiamln aiamnl aianlm aianml ailamn ailanm ailman ailmna ailnam ailnma aimaln aimanl aimlan aimlna aimnal aimnla ainalm ainaml ainlam ainlma ainmal ainmla alaimn alainm alamin alamni alanim alanmi aliamn alianm aliman alimna alinam alinma almain almani almian almina almnai almnia alnaim alnami alniam alnima alnmai alnmia amailn amainl amalin amalni amanil amanli amialn amianl amilan amilna aminal aminla amlain amlani amlian amlina amlnai amlnia amnail amnali amnial amnila amnlai amnlia anailm anaiml analim analmi anamil anamli anialm aniaml anilam anilma animal animla anlaim anlami anliam anlima anlmai anlmia anmail anmali anmial anmila anmlai anmlia iaalmn iaalnm iaamln iaamnl iaanlm iaanml ialamn ialanm ialman ialmna ialnam ialnma iamaln iamanl iamlan iamlna iamnal iamnla ianalm ianaml ianlam ianlma ianmal ianmla ilaamn ilaanm ilaman ilamna ilanam ilanma ilmaan ilmana ilmnaa ilnaam ilnama ilnmaa imaaln imaanl imalan imalna imanal imanla imlaan imlana imlnaa imnaal imnala imnlaa inaalm inaaml inalam inalma inamal inamla inlaam inlama inlmaa inmaal inmala inmlaa laaimn laainm laamin laamni laanim laanmi laiamn laianm laiman laimna lainam lainma lamain lamani lamian lamina lamnai lamnia lanaim lanami laniam lanima lanmai lanmia liaamn liaanm liaman liamna lianam lianma limaan limana limnaa linaam linama linmaa lmaain lmaani lmaian lmaina lmanai lmania lmiaan lmiana lminaa lmnaai lmnaia lmniaa lnaaim lnaami lnaiam lnaima lnamai lnamia lniaam lniama lnimaa lnmaai lnmaia lnmiaa maailn maainl maalin maalni maanil maanli maialn maianl mailan mailna mainal mainla malain malani malian malina malnai malnia manail manali manial manila manlai manlia miaaln miaanl mialan mialna mianal mianla milaan milana milnaa minaal minala minlaa mlaain mlaani mlaian mlaina mlanai mlania mliaan mliana mlinaa mlnaai mlnaia mlniaa mnaail mnaali mnaial mnaila mnalai mnalia mniaal mniala mnilaa mnlaai mnlaia mnliaa naailm naaiml naalim naalmi naamil naamli naialm naiaml nailam nailma naimal naimla nalaim nalami naliam nalima nalmai nalmia namail namali namial namila namlai namlia niaalm niaaml nialam nialma niamal niamla nilaam nilama nilmaa nimaal nimala nimlaa nlaaim nlaami nlaiam nlaima nlamai nlamia nliaam nliama nlimaa nlmaai nlmaia nlmiaa nmaail nmaali nmaial nmaila nmalai nmalia nmiaal nmiala nmilaa nmlaai nmlaia nmliaa enter next sequence(quit to quit): apple
Permutations of apple
aelpp
aeplp aeppl alepp alpep alppe apelp apepl aplep aplpe appel apple ealpp eaplp eappl elapp elpap elppa epalp epapl eplap eplpa eppal eppla laepp lapep lappe leapp lepap leppa lpaep lpape lpeap lpepa lppae lppea paelp paepl palep palpe papel paple pealp peapl pelap pelpa pepal pepla plaep plape pleap plepa plpae plpea ppael ppale ppeal ppela pplae pplea enter next sequence(quit to quit): quit
Done!

在这里插入图片描述它一次只给出一种排列组合,但是用while可以给出所有的。

STL非成员函数 VS 容器成员方法:成员方法胜!

有很多STL非成员函数提供的算法或者说功能,实际上很多容器类自己也有成员方法做一样的事情,比如list链表模板类有自己的remove()成员方法,但是STL也有remove()非成员算法,那这个时候要怎么选择呢?

优先选择成员方法!!!

因为:

  • 为特定容器量身打造,速度肯定有差别。
  • 作为成员函数,可以使用模板类的内存管理工具,必要时可以改变容器的长度。而STL非成员方法不是成员,就不可以调整长度,比如remove方法,它把没被删的元素放到链表的开始位置,然后返回一个指向新的超尾的迭代器,即后面一段位置空了。

示例:remove()的成员版和非成员版

#include <iostream>
#include <list>
#include <algorithm>
const int N = 10;
void show(int n)
{
	std::cout << n << ' ';
}

int main()
{
	using namespace std;
	int ar[N] = {4, 5, 4, 2, 2, 3, 4, 8, 1, 4};
	list<int> la(ar, ar+N);//范围构造函数
	list<int> lb(la);//复制构造函数

	cout << "Original list elements:\n";
	for_each(la.begin(), la.end(), show);
	cout << endl;

	la.remove(4);//成员方法
	cout << "After using the remove method(member):\n";
	for_each(la.begin(), la.end(), show);
	cout << endl;

	list<int>::iterator last;
	last = remove(lb.begin(), lb.end(), 4);//非成员方法
	cout << "After using the remove() function(non-member):\n";
	for_each(lb.begin(), lb.end(), show);
	cout << endl;

	cout << "After using the erase() method(member):\n";
	lb.erase(last, lb.end());
	for_each(lb.begin(), lb.end(), show);
	cout << endl;
	return 0;

}

可以非常清楚的看到:非成员方法的remove,也删掉了4,结果很正确,但是它没有把4个4的长度删去,即正确结果522381后面的4个位置还存着原来的值,整个链表的长度也没变,但是last指向的是1后面的位置,即新超尾。

最后用erase()成员函数删除后面的那个区间

成员方法remove有减小链表长度。

Original list elements:
4 5 4 2 2 3 4 8 1 4
After using the remove method(member):
5 2 2 3 8 1
After using the remove() function(non-member):
5 2 2 3 8 1 4 8 1 4
After using the erase() method(member):
5 2 2 3 8 1

STL使用大示例(展示STL组间的协作):把输入的多个单词按字母顺序输出,并输出每个单词的数目

这个示例同时使用了很多STL容器和方法,这个示例本身的目的是想要展示出STL组件之间的相互协作,各个组成部分协同工作,每个组件都是工具。

示例任务是,写一个程序让用户输入单词,假设输入的单词中只有大小写字母。最后的输出是:

  • 按输入顺序排列的列表
  • 按字母顺序排列的列表(排序时忽略大小写)
  • 每个单词出现的次数

分析:

  • 第一个要求很简单,直接用一个vector<string>就好了。
  • 第二个要求。有两种解决法:一是先sort()再unique();而是直接把单词复制到一个set中。原因不想解释了。
  • 第三个要求:把单词们放到map里,用STL的count()非成员函数
#include <iostream>
#include <string>
#include <vector>
#include <set>
#include <map>
#include <iterator>
#include <algorithm>
#include <cctype>
using namespace std;
char toLower(char ch){return tolower(ch);}
string & ToLower(string & st);
void show(const string & s);

int main()
{
	vector<string> words;
	cout << "Enter words (enter quit to quit):\n";
	string input;
	while (cin >> input && input != "quit")
		words.push_back(input);

	cout << "by input order:\n";
	for_each(words.begin(), words.end(), show);
	cout << endl;

	cout << "by alphabetic order:\n";
	set<string> words_set;
	transform(words.begin(), words.end(), insert_iterator<set<string>>(words_set, words_set.begin()), ToLower);
	for_each(words_set.begin(), words_set.end(), show);
	cout << endl;

	map<string, int> words_map;
	//创建map
	for (auto si = words_set.begin();si!=words_set.end();++si)
		words_map[*si] = count(words.begin(), words.end(), *si);
	//打印map
	cout << "word\tcount\n";
	for (auto si = words_set.begin();si!=words_set.end();++si)
		cout << *si << "\t" << words_map[*si] << endl;

	return 0;
}
string & ToLower(string & st)
{
	transform(st.begin(), st.end(), st.begin(), toLower);//挨个处理每个字符
	return st;
}

void show(const string & s)
{
	cout << s << ' ';
}
Enter words (enter quit to quit):
car
cat
AppLe
WINDOW
PEN
CAR
car
oops
cute
oops
apple
quit
by input order:
car cat AppLe WINDOW PEN CAR car oops cute oops apple
by alphabetic order:
apple car cat cute oops pen window
word    count
apple   2
car     3
cat     1
cute    1
oops    2
pen     1
window  1

总结

  • 通过近期对STL的接触,我发现他很多函数用俩迭代器提供容器区间,然后用一个函数对象参数的行为,挨个处理区间里的每一个元素,替代了for循环,不用自己写for循环了,特别爽。

  • map类,可以把键当做下标一样,用数组表示法去访问值,很有意思,和Python的字典数据结构一样,也许Python的字典也是树实现的吧。

  • STL其实故意把大多数的算法都实现为非成员函数,而非类的成员函数,因为:

一是可以节省函数数量,每一个类都写一个自己的排序,对比所有类共用一个非成员函数来排序,需要定义的函数少多了,重用了代码,减少了工作量。只是某些算法设计到特殊的,比如自己写一个效率更高,那就自己写成员算法再实现一遍。

比如:sort()非成员函数要求参数是随机访问迭代器,list容器哭了,,,他做不到啊,于是list类就自力更生自立自强,自己开发了一个sort()成员函数,使用双向迭代器作为参数。

二是可以把这些STL非成员函数定义的算法用于非STL容器中,比如用到常规数组,string类,array类对象,以及你自己设计的任何秉承了STL迭代器和容器以及泛型规则的类。

string类,array类都是很聪明高情商的类,他们都不是STL的一部分,但是瞅准了STL是个好靠山,就在自己的设计上和STL靠拢兼容,使得很多STL很多方法也可以用在他们的对象身上,揩STL的油,沾STL的光。
当然STL很欢迎类们去揩油沾光,他又不累。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值