| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> C++知识库 -> C++ Primer Plus 笔记(16章:string类和标准模板库) -> 正文阅读 |
|
[C++知识库]C++ Primer Plus 笔记(16章:string类和标准模板库) |
16 string类和标准模板库16.1 string类16.1.1 构造字符串常见的字符串书中给了7个,另外还有两个在C++11里新增的(NTBS)表示以空字符结束的传统字符串
下面进行一下测试: int main() { string date("2021.9.18"); cout << date[0] << endl;//重载[],使其可以使用数组表示法来访问string对象 string divide(15, '#');//由15个#组成的对象 cout << divide << endl; string day(date,5);//从位置5开始复制字符串 day += " is a meaningful day";//重载+= string empty;//空 empty = date + " , " + day; cout << empty << endl; char alls[] = "fuck you,Japanese"; string words(alls, 14);//只取到位置14 cout << words << endl; string word(alls + 9, alls + 14);//数组相当于指针,分别指向J和n后面的e,前闭后开 cout << word << endl; } ? ? 输出: 2 ############### 2021.9.18 , 9.18 is a meaningful day fuck you,Japan Japan 关于新增特性的用法:
string master = {'A','L','P','H','A'} 16.1.2 string类输入C风格字符串有三种方法: char info[100]; cin >> info; cin.getline(info,100,'#');//读一行,抛掉‘#’ cin.get(info,100);//保留'/n' string对象则有两种: string stuff; cin >> stuff; getline(cin,stuff);//读一行,抛掉‘/n’ getline(stuff,'#'); 两者的getline都可以加上控制分界字符的参数,但string版的可以自动调节大小 string版本的getline()停止读取字符的条件有三种:
下面测试一下第一种情况: int main() { ifstream dog_list; dog_list.open("D:\\Desktop\\dog.txt");//windows中双斜杠代表'\' if (dog_list.is_open() == false) { cerr << "oh,shit!" << endl; exit(EXIT_FAILURE); } string name; int count = 0; while (dog_list) { getline(dog_list, name, '#'); count++; cout << count << ":" << name << endl; } cout << "Done!"; dog_list.close(); return 0; } 16.1.3 使用字符串书上给了一个猜字母的程序,我稍微改了一下: #include<iostream> #include<string> #include<fstream> #include<cstdlib> #include<ctime> #include<cctype> using namespace std; const int NUM = 10; const string wordlist[NUM] = { "bed","bag","bit","bee","beef","beat","boss","byd","born","burn" }; ? int main() { std::srand(std::time(0)); char play; cout << "玩不?<y/n>"; cin >> play; play = tolower(play); while (play == 'y') { string target = wordlist[std::rand() % NUM]; int length = target.length(); string attempt(length, '-'); attempt[0] = 'b'; string wrongchars; int guesses = 6; cout << "首字母为b,一共有" << length << "个字母,可以猜6次,一次猜一个字母" << "你现在有" << guesses << "次机会" << endl; cout << "你目前的答案是:" << attempt << endl; while (guesses > 0 && attempt != target) { char letter; cout << "猜一个:"; cin >> letter; if (wrongchars.find(letter) != string::npos || attempt.find(letter) != string::npos)//判断是否包含 { cout << "猜过了,换一个\n"; continue; } int loc = target.find(letter); if (loc == string::npos) { cout << "猜错了!\n"; guesses--; wrongchars += letter; } else { cout << "猜对了\n"; attempt[loc] = letter; //接下来判断是否有重复 loc = target.find(letter, loc + 1); while (loc != string::npos) { attempt[loc] = letter; loc = target.find(letter, loc + 1); } } cout << "你现在的答案:" << attempt << endl; if (attempt != target) { if (wrongchars.length() > 0) cout << "错误选项:" << wrongchars << endl; cout << "剩余" << guesses << "次机会" << endl; } } if (guesses > 0) cout << "全对!" << endl; else cout << "抱歉,答案是:" << target << endl; cout << "还来吗?<y/n>"; cin >> play; play = tolower(play); } cout << "滚吧!"; return 0; } npos变量是一个常数,对应string所能存储的最大字符数,可以用来查找字符或字符串,并将相应位置存在loc中 不过,因为有可能有重复,所以还要再进行一次循环,继续查找 除此之外,string还提供了一些其他功能,之后遇到再说 16.2 智能指针模板类智能指针是行为类似于指针的类对象,常规指针删除后不会释放该指针所指向的内存,智能指针可以在对象过期时,让它的析构函数删除指向的内存 16.2.1 使用智能指针基本形式如下: auto_ptr<double> pd(new double) 其中,new double是new返回的指针,作为构造函数auto_ptr<double>的实参 下面使用另两个指针,注意要支持C++11。每一个智能指针都放在一个代码块里,这样离开代码块时,指针将过期Report负责报告对象的创建与销毁 #include<iostream> #include<string> #include<memory>//必须包含该头文件 using namespace std; ? class Report { ? ?private: ? string str; ? ? ? ?public: ? Report(const string s) : str(s) ? ? ? {cout << "对象创建" << endl;} ? ~Report(){cout << "对象销毁" << endl;} ? void comment() const{cout << str << endl;} }; int main() { ? { ? ? ? ? ? ?unique_ptr<Report> ps (new Report("使用unique")); ? ps -> comment();//调用成员函数 ? } ? { ? ? ? ?shared_ptr<Report> ps (new Report("使用shared")); ? ps -> comment(); ? } ? ?return 0; } 16.2.2 有关智能指针的注意事项对于常规指针,如果两个指针指向同一个对象,删除时有可能删两次。为了避免,方法有多种:
下面举一个例子: int main() { ? ?using namespace std; ? ?auto_ptr<string> names[5] = ? { ? ? ? ?auto_ptr<string> (new string("约翰塞纳")), ? ? ? ?auto_ptr<string> (new string("兰迪奥顿")), ? ? ? ?auto_ptr<string> (new string("艾吉")), ? ? ? ?auto_ptr<string> (new string("罗曼雷恩斯")), ? ? ? ?auto_ptr<string> (new string("德鲁")) ? }; ? ?auto_ptr<string> pwin; ? ?pwin = names[2]; ? ? ? ?cout << "WWE年度超级巨星提名有:"; ? ?for(int i = 0;i<5;i++) ? ? ? ?cout << *film[i] << endl; ? ?cout << "获得WWE年度巨星称号的是:" << *pwin << '!' << endl; ? ?cin.get(); ? ?return 0; } 上述程序会出问题,因为pwin夺走了names[2]的对象所有权,使其不能再调用原本存储的数据 所以,将中间部分改为下列代码: shared_ptr<string> pwin; pwin = names[2]; 这样,引用计数为2,pwin调用析构函数后,计数为1,names[2]仍可以继续使用,直到它也调用析构函数,计数降为0,才会释放以前分配的空间 16.2.3 unique_ptr为何优于auto_ptr两者都采用所有权模型,但如果所有权被剥夺,unique_ptr会主动判断为非法,而auto_ptr不会,比如: unique_ptr<string> p3(new string("auto")); unique_ptr<string> p4; p4 = p3; 此时,编译器会认为语句非法 16.2.4 选择智能指针如果程序要使用多个指向同一个对象的指针,应选择shared_ptr,例如:
如果程序不需要多个指向同一个对象的指针,则可使用unique_ptr。如果函数使用new分配内存,并返回指向该内存的指针,则可将其声明为unique_ptr,这样,所有权将转让给接收返回值的unique_ptr,而智能指针将负责调用delete。 最后,尽量不要使用auto_ptr(行为不确定) 16.3 标准模板库STL提供了一组表示容器,迭代器,函数对象和算法的模板。 其中,容器可以存储类型相同的值,类似于数组;迭代器用于遍历对象,是广义指针;函数对象是类似于函数的对象,可以是类对象或函数指针 STL不是面向对象编程,而是一种不同的编程模式——泛型编程 16.3.1 模板类vector要创建模板对象,可使用通常的<type>表示法来指出类型,同时,vector使用动态内存分配 #include vector using namespace std; vector<int> rating(5); int n; cin >> n; vector<double> scores(n); 16.3.2 可对vector执行的操作STL容器都提供了一些基本的方法:
要为vector的double类型声明一个迭代器,可以这样做: vector<double>::iterator pd; vector<double> scores; pd = scores.begin(); *pd = 22.3; pd++; C++11还有一种新方法: auto pd = scores.begin(); 另外,还有一些方法是某些容器所特有的:
下面同时使用这些方法: #include<iostream> #include<string> #include<vector> using namespace std; ? struct Review { string title; int rating; }; bool FillReview(Review& rr); void ShowReview(const Review& rr); ? int main() { vector<Review> books; Review temp; while (FillReview(temp)) books.push_back(temp); int num = books.size(); if (num > 0) { cout << "您将进入下面几本书:" << endl; for (int i = 0; i < num; i++) ShowReview(books[i]); cout << "重复:" << endl; vector<Review>::iterator pr; for (pr = books.begin(); pr != books.end(); pr++) ShowReview(*pr); vector<Review> oldlist(books);//复制使用的结构体 if (num > 3) { //删去两个 books.erase(books.begin() + 1, books.begin() + 3); cout << "删除后:" << endl; for (pr = books.begin(); pr != books.end(); pr++) ShowReview(*pr); //插入一个 books.insert(books.begin(), oldlist.begin() + 1, oldlist.begin() + 2); cout << "插入后:" << endl; for (pr = books.begin(); pr != books.end(); pr++)\ ShowReview(*pr); } books.swap(oldlist); cout << "交换后:" << endl; for (pr = books.begin(); pr != books.end(); pr++) ShowReview(*pr); } else cout << "Nothing entered,nothing gained.\n"; return 0; } ? bool FillReview(Review& rr) { cout << "输入书名:(输入quit退出)"; getline(cin, rr.title); if (rr.title == "quit") return false; cout << "输入编号:"; cin >> rr.rating; if (!cin) return false;//避免多余的输入行 while (cin.get() != '\n') continue; return true; } ? void ShowReview(const Review& rr) { cout << rr.rating << '\t' << rr.title << endl; } ? ? 输出结果: 您将进入下面几本书: 1 ? ? ? www 4 ? ? ? ddd 3 ? ? ? hhh 5 ? ? ? iii 重复: 1 ? ? ? www 4 ? ? ? ddd 3 ? ? ? hhh 5 ? ? ? iii 删除后: 1 ? ? ? www 5 ? ? ? iii 插入后: 4 ? ? ? ddd 1 ? ? ? www 5 ? ? ? iii 交换后: 1 ? ? ? www 4 ? ? ? ddd 3 ? ? ? hhh 5 ? ? ? iii 16.3.3 对vector的其他可执行操作下面介绍三个有代表性的STL函数
for_each()函数可以将被指向的函数应用于容器区间中的各个元素,因此可以代替for循环 但是,它不可以改变元素的值 for_each(books.begin(),books.end(),ShowReview)//接上一个例子
random_shuffle()可以接受两个指定区间的迭代器参数,并随机排列该区间中的元素 random_shuffle(books.begin(),books.end()); 该函数要求容器必须允许随机访问(如vector)
它的要求同上,且有两个版本 第一,如果容器内不是用户定义的元素,可直接使用,使其按升序排序 vector<int> coolstuff; ... sort(coolstuff.begin(),coolstuff.end()); 第二,如果是用户定义的元素,必须先用operator<()函数进行处理,再进行排序 bool operator<(const Review& r1, const Review& r2) { ? ?if(r1.title < r2.title) ? ? ? ?return true; ? ?else if(r1.title == r2.title && r1.rating < r2.rating) ? ? ? ?return true; ? ?else ? ? ? ?return false; } //接上上例,由于结构体为公有,可以直接使用 sort(books.begin(),books.end()); 16.3.4 基于范围的for循环前面提到,for_each()不能改变元素的值,但是基于范围的for循环可以 void InflateReview(Review& r){r.rating++;} for(auto& x : books)InflateReview(x); 16.4 泛型编程面向对象编程关注的是编程的数据方面,而泛型编程聚焦于算法层面,旨在编写独立于数据类型的代码。 16.4.1 为何使用迭代器我们以链表为例,首先用面向对象的思维编写 struct Node { ? ?double item; ? ?Node* p_next; }; ? Node* find_ll(Node* head, const double& val) { Node* Start = head; ? ?for(start; start!=0; start = start->p_next) ? ? ? ?if(start->item == val) ? ? ? ? ? ?return start; ? ?return 0; } 可以看出,这种算法并没有摆脱特定的数据结构,而要实现find函数,迭代器应具备下面四个特征:
于是,我们可以定义一个iterator类,来实现这些需求: class iterator { ? ?Node* pt; ? ? ? ?public: ? ?iterator():pt(0){} ? ?iterator(Node* pn):pt(pn){} ? ?double iterator*(){return pt->item;} ? ?iterator& operator++()//定义++p ? { ? ? ? ?pt = pt->next; ? ? ? ?return *this; ? } ? ?iterator operator++(int)//定义p++ ? { ? ? ? ?iterator tmp = *this; ? ? ? ?pt = pt->next; ? ? ? ?return tmp; ? } } 接下来,就可以正式编写find函数了 iterator find_ll(iterator head, const double& val) { ? ?iterator start; ? ?for(start = head; start!=0; start++) ? if(*start == val) ? ? ? ? ? ?return start; ? ?return 0; } 所以,在设计迭代器和容器时,要基于算法的需求。而算法要尽可能的用通用的术语来表达,使之独立于数据类型和容器类型之上 16.4.2 迭代器类型STL定义了5种迭代器类型,分别是:
这5种迭代器都有*,==,!=操作,另外两相同迭代器解除引用后仍相同,即: iter1 == iter2; *iter1 == *iter2;
输入迭代器必须能够访问容器中的所有值(通过++来实现) 若将其指向第一个元素,可递增至超尾 基于输入迭代器的算法必须是单同行的,因为它既不能保证第二次遍历容器时顺序不变(不依赖与前一次遍历是的迭代器值),也不能保证先前的值可以解除引用(不依赖与本次遍历中前面迭代器的值)
与前者类似,但只能解除引用来修改元素的值,不能读取 单通行,只读算法,可以使用输入迭代器;单通行,只写算法,可以使用输出迭代器
与前两者不同的是,它可以按相同的顺序遍历一系列的值,并对前面的迭代器的值解除引用(如果保存了它),获得相同的值。因此,它可以实现多次通行算法 同时,正向迭代器读写皆可: int* pirw; //读写迭代器 const int* pir; //只读迭代器
它拥有正向迭代器的所有特性,并且支持两种(前后缀)递减符
随机访问,指直接跳到容器中的任一元素,它在拥有双向迭代器所有特性的同时,还支持随机访问操作和用于对元素进行排序的关系运算符
16.4.3 迭代器层次结构编写算法是尽可能使用要求低的迭代器,比如find()函数使用的是级别最低的输入迭代器,可用于任何包含可读值的函数,而sort()则由于需要随机访问迭代器,因此只能用于支持这种迭代器的容器 下面是五种迭代器的性能总结:
16.4.4 概念,改进和模型
迭代器即是广义指针,又是STL算法的接口,因此STL算法可使用迭代器来对基于常规指针的非STL容器进行操作 例如,将STL算法用于数组: const int SIZE; double receipts[SIZE]; ? sort(receipts,receipts+SIZE);//将其进行排序 copy()算法用于复制,它有三个迭代器参数,分别表示复制的范围和要粘贴到的位置,如: int casts[10] = {1,2,3,4,5,6,7,8,9,0}; vector<int> dice(10); copy(casts,casts+10,dice.begin()); ostream_iterator是一个输出迭代器的模板,也是一个适配器(类或函数) #include<iterator> ... ostream_iterator<int,char>out_iter(cout," "); 第一个模板参数为发送给输出流的类型,第二个模板参数为输出流使用的类型,构造函数的第一个指明了要使用的输出流,第二个是发送给每个数据项后显示的分隔符,因此,下面两个等效: cout << 15 << " "; *out_iter++ = 15;//将15发送给输出流,并为下一个输出流做好准备 所以,如果要将dice容器的内容复制到输出流中,在显示器上显示,可以这样: copy(dice.begin(),dice.end(),out_iter); 当然,也可以采用匿名迭代器: copy(dice.begin(),dice.end(),ostream_iterator<int,char>(cout," ")); 同理,istream_iterator也可以这么玩: copy(istream_iterator<int,char>(cin), istream_iterator<int,char>(),dice.begin()); 其中,第一个参数为要读取的类型,第二个为输入流使用的类型,cin意味着通过cin管理输入流,第二行省略构造函数表示输入失败,使其停止读取
先来介绍一下reverse_iterator。对reverse_iterator执行递增会导致它递减,这样做当然不是因为闲的蛋疼,而是为了减少对已有函数的使用 vector类有一个名为rbegin()的成员函数和一个名为rend()的成员函数。前者返回一个指向超尾的反向迭代器,后者返回一个指向第一个元素的反向迭代器 int cast[4] = {1,2,3,4}; vector<int> dice(10); copy(cast,cast+4,dice.begin()); ostream_iterator<int,char>out_iterator(cout," "); copy(dice.rbegin(),dice.rend(),out_iterator);//隐式地使用 //下面为显式地使用 vector<int>::reverse_iterator ri; for(ri = dice.rbegin();ri!=dice.rend();++ri) ? ?cout << *ri << ' '; 另外三种迭代器,包括back_insert_iterator,front_insert_iterator和insert_iterator, #include <iostream> #include <string> #include <iterator> #include <vector> #include <algorithm> ? void output(const std::string& s) { std::cout << s << " "; } ? int main() { using namespace std; string s1[4] = { "cena","roman","uzi","faker" }; string s2[2] = { "yeah","noo" }; string s3[2] = { "silly","singer2" }; vector<string> words(4); copy(s1, s1 + 4, words.begin()); for_each(words.begin(), words.end(), output); cout << endl; copy(s2, s2 + 2, back_insert_iterator<vector<string>>(words)); for_each(words.begin(), words.end(), output); cout << endl; copy(s3, s3 + 2, insert_iterator<vector<string>>(words, words.begin())); for_each(words.begin(), words.end(), output); cout << endl; return 0; } 16.4.5 容器种类7种STL容器类型都是序列,下面是序列的一些创建方式和基本函数:
一些容器还有自己的特殊函数:
vector是数组的一种类表示,它提供了自动内存管理的功能,可以动态地改变vector对象的长度,并随着元素的增加与删除而增大或缩小,它还提供了随机访问,结尾增删时间固定,开头及中间的复杂度为线性时间 同时,vector还可以是可反转容器,并且有两个类方法:rbegin()和rend(),分别返回反转序列的第一个元素的迭代器和超尾迭代器 for_each(dice.begin(),dice.end(),Show); cout << endl; for_each(dice.rbegin(),dice.rend(),Show);
双端队列,与vector的区别:开头与结尾的增删都为固定时间
双向链表,任一位置增删为固定时间,但不支持数组表示法和随机访问。下面是list的成员函数:
#include <iostream> #include <list> #include <iterator> #include <algorithm> using namespace std; ? void outint(int n) { cout << n << " "; } ? int main() { list<int> one(5, 2); int stuff[5] = { 1,2,4,8,6 }; list<int> two; two.insert(two.begin(), stuff, stuff + 5); int more[6] = { 6,4,2,4,6,5 }; list<int> three(two); three.insert(three.end(), more, more + 6); ? cout << "list one: "; for_each(one.begin(), one.end(), outint); cout << endl << "list two: "; for_each(two.begin(), two.end(), outint); cout << endl << "list three: "; for_each(three.begin(), three.end(), outint); three.remove(2);//移除所有2 cout << endl << "list three minus 2s: "; for_each(three.begin(), three.end(), outint); three.splice(three.begin(), one); cout << endl << "list three after splice: "; for_each(three.begin(), three.end(), outint); three.unique();//删去重复项 cout << endl << "list three after unique: "; for_each(three.begin(), three.end(), outint); three.sort(); cout << endl << "list three after sort: "; for_each(three.begin(), three.end(), outint); three.merge(two); //cout << endl << "Sorted two merged into three: "; //for_each(three.begin(), three.end(), outint); cout << endl; ? return 0; }
相当于单链表,只需要正向迭代器,不需要双向,因此不能反转。
相当于队列,相较于deque,不允许随机访问和遍历,只允许队尾添加和队首删除
相当于栈,相较于stack,不允许随机访问和遍历,只允许在栈顶压入和弹出 16.4.6 关联容器STL提供了4种关联容器:set,multiset,map,mutimap
set是关联集合,可反转,可排序,且键是唯一的(不能存储多个相同值) 既然是集合,那必然少不了相关的算法,比如求a,b的并集,打印出来: set_union(a.begin(),a.end(),b.begin(),b.end(), ostream_iterator<string,char>out(cout," ")); 如果要将并集放到c中,最后一个参数不能用c.begin( ),原因有二:
所以正确答案是创建一个insert_iterator(可以将复制转换为插入),将信息复制给C: set_union(a.begin(),a.end(),b.begin(),b.end(), ? ?insert_iterator<set<string>>(c,c.begin())); set_intersection()和set_difference()分别为交集和差集 此外,set还有两个是用的方法:lower_bound和upper_bound。前者返回一个迭代器,该迭代器指向第一个不小于键参数的成员,后者则是返回指向第一个不大于键参数的成员的迭代器 示例: #include <iostream> #include <string> #include <set> #include <algorithm> #include <iterator> using namespace std; const int N = 6; ? ? int main() { string s1[N] = { "dd","shiiit","bb","asxas","xs","ee" }; string s2[N] = { "aa","bb","cc","dd","ee","ff" }; ? set<string>a(s1, s1+ N); set<string>b(s2, s2+ N); ? ostream_iterator<string, char>out(cout, " "); cout << "Set a: "; copy(a.begin(), a.end(), out); cout << endl; cout << "a和b的差集:"; set_difference(a.begin(), a.end(), b.begin(), b.end(), out); set<string>c; set_intersection(a.begin(), a.end(), b.begin(), b.end(), insert_iterator<set<string>>(c, c.begin())); cout << endl << "a和b的交集:"; copy(c.begin(), c.end(), out); ? cout << endl << "随机:"; copy(c.lower_bound("cc"), c.upper_bound("ee"), out); }
与set相似,mutimap也可以是反转的,经过排序的关联容器,但键和值的类型不同,且同一个键可能与多个值相关联 假如要用区号作为键来储存城市名,一种方法是创建一个pair再将它插入: multimap<int,string>codes; pair<const int, string> item(213,"Los Angeles"); codes.insert(item); 也可以创建匿名pair对象并将它插入: codes.insert(pair<const int,string>(213,"Los Angeles")); 对于pair对象,可以使用first和second访问它的两部分: pair<const int,string>item(213,"Los Angeles"); cout << item.first << item.second << endl; 下面我们进行演示: #include <iostream> #include <string> #include <map> #include <algorithm> using namespace std; typedef int KeyType; typedef pair<const KeyType, string>Pair; typedef multimap<KeyType, string>MapCode; ? int main() { MapCode codes; ? codes.insert(Pair(415, "San Francisco")); codes.insert(Pair(510, "Oakland")); codes.insert(Pair(718, "Brooklyn")); codes.insert(Pair(718, "Staten Island")); codes.insert(Pair(415, "San Rafael")); codes.insert(Pair(510, "Berkeley")); ? cout << "带415的城市有" << codes.count(415) << "个" << endl; cout << "带718的城市有" << codes.count(718) << "个" << endl; cout << "带510的城市有" << codes.count(510) << "个" << endl; ? MapCode::iterator it; for (it = codes.begin(); it != codes.end(); ++it) cout << " " << (*it).first << " " << (*it).second << endl; pair<MapCode::iterator, MapCode::iterator> range = codes.equal_range(718); for (it = range.first; it != range.second; it++) cout << (*it).second << endl; return 0; } 16.5 函数对象函数对象,也叫函数符,其实就是重载了()的类对象,使用时与函数功能类似 16.5.1 函数符概念
当然,这些概念还有改进版:
举个例子,假设要删除另一个链表中所有大于200的值,可以设计一个TooBig类,使用类成员而非函数参数来传递“200”这个信息: template<class T> ? ?class TooBig ? { ? ? ? ?private: ? ? ? T cutoff; ? ? ? ?public: ? ? ? TooBig(const T & t) : cutoff(t){} ? ? ? bool operator()(const T & v){return v > cutoff;} ? }; 这里,v作为函数参数传递,cutoff是由类构造函数设置的。这样,在调用list的成员函数remove_if(将谓词作为参数,返回true则删除该元素)时,就可以像下面这样做: TooBig<int> f100(100); list<int> fff = {1,2,3,4,31,2}; list<int> ggg = {5,3,2,123,6,9}; fff.remove_if(f100); ggg.remove_if(TooBig<int>(200));//匿名使用 16.5.2 预定义的函数符STL定义了多个基本函数符,来支持将函数作为参数的STL函数,例如,transform()函数有两个版本。第一个版本可传递4个参数,前两个参数为容器区间,第三个为复制到哪里,第四个为函数符,对每个元素进行操作: const int LIM = 5; double arr[LIM] = {16,25,36,49,64,81}; vector<double> gr(arr,arr+6); ostream_iterator<double,string>out(cout," "); transform(gr.begin(),gr.end(),out,sqrt); 第二个版本则在第二个参数后再加一个参数,用于表示第二个区间的起始位置 假设mean(double,double)函数会返回两个参数的平均值,mr也是一个vector,则有: transform(gr.begin(),gr.end(),mr.begin(),out,mean); 如果要执行相加操作,可以使用plus<>类来完成常规类型的相加运算 plus<double> add; transform(gr.begin(),gr.end(),mr.begin(),out,add); transform(gr.begin(),gr.end(),mr.begin(),out,plus<double>()); 运算符和相应的函数符:
16.6 算法对于算法函数设计,有两个主要的通用部分。首先,它们都使用模板来提供泛型;其次,它们都使用迭代器来提供访问容器中数据的通用表示 16.6.1 算法组STL将算法库分成4组:
16.6.2 使用STL假设要编写一个程序,让用户输入单词,并记录每个单词背输入的次数。 输入和保存单词列表很简单: vector<string> words; string input; while(cin << input && input != "quit") ? ?word.push_back(input); 接下来,可以使用sort()和unique()来得到按字母顺序排列的单词表,但会覆盖原数据,故还可以创建一个set<string>对象,将内容复制进集合,再进行排序。另外,集合的键是惟一的,省去了unique set<string> wordset; transform(words.begin(),words.end(),insert_iterator<set<string>>(wordset,wordset.begin()),ToLower); 其中,ToLower函数用于转小写,代码如下: string & ToLower(string * st) { ? ?transform(st.begin(),st.end(),st.begin(),tolower); ? ?return st; } 然后,利用count函数,将一个区间和一个值作为参数,返回这个值在区间出现的次数。为了关联单词和计数,可将pair<const string,int>对象存储在map中: map<string,int>wordmap; set<string>::itreator si; for(si = wordset.begin();si!=wordset.end();si++) ? ?wordmap.insert(pair<string,int>(*si,count(words.begin(),words.end(),*si))); map可以用数组表示法来表示与键关联的值,所以输出结果也可以这样写: for(si = wordset.begin();si!=wordset.end();si++) ? ?cout << *si << ": " << wordmap[*si] << endl; 16.7 其他库16.7.1 模板initializeer_list它是C++11新增的,可以使用初始化列表语法将STL容器初始化为一系列值: std::vector<double> payments{45.11,43.22,41.33,51.22}; 这将创建一个包含4个元素的容器,并使用列表中的4个值来初始化这些元素 另外,还有一个问题: std::vector<int> vi{10} 上面标示的是下面哪个构造函数呢? std::vector<int> vi(10);//包含10个未初始化的元素 std::vector<int> vi({10});//包含1个初始化值为10的元素 答案应该是第二个 最后,简单地使用一下它: #include <iostream> #include <initializer_list> using namespace std; ? double sum(initializer_list<double> il); double average(const initializer_list<double>& ril); ? int main() { cout << "sum = " << sum({ 2,3,4 }) << ",ave = " << average({ 2,3,4 }) << endl; return 0; } ? double sum(initializer_list<double> il) { double tot = 0; for (auto p = il.begin(); p != il.end(); p++) tot += *p; return tot; } ? double average(const initializer_list<double>& ril) { double tot = 0; int n = ril.size(); double ave = 0.0; if (n > 0) { for (auto p = ril.begin(); p != ril.end(); p++) tot += *p; ave = tot / n; } return ave; } |
|
C++知识库 最新文章 |
【C++】友元、嵌套类、异常、RTTI、类型转换 |
通讯录的思路与实现(C语言) |
C++PrimerPlus 第七章 函数-C++的编程模块( |
Problem C: 算法9-9~9-12:平衡二叉树的基本 |
MSVC C++ UTF-8编程 |
C++进阶 多态原理 |
简单string类c++实现 |
我的年度总结 |
【C语言】以深厚地基筑伟岸高楼-基础篇(六 |
c语言常见错误合集 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/23 23:05:24- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |