10.1 标准模板库的定义
- STL 是为本地C++编译器提供的一个类与函数模板的大集合。
- STL 包含6种组件:容器、容器适配器、迭代器、算法、函数对象和函数适配器。
- 因为STL组件时标准库的一部分,所以他们的名称都在std名称空间内定义。
10.1.1 容器
- 容器:是用来存储和组织其他对象的对象。
- 通过提供要存储的对象的类型来从STL 模板中创建容器类。
例:
vector< T > 是一个容器的模板,它是一个线性数组在必要时会自动增加大小,T是类型形参,指定要存储的对象类型。
vector<string> strings; // 创建存储string类型对象的容器类strings
vector<double> data; // 创建存储double类型对象的容器类data
- 通常,容器存储我们存储在其中的对象的副本,它们自动分配和管理对象占用的内存。当销毁魔鬼容器对象时,容器会负责销毁它们包含的对象并释放它们占用的内存。
- 使用STL容器存储对象的一个优点是它们使我们不用费心管理它们的内存。
STL容器类的模板在标准头文件中定义:
- K 是键的类型。
10.1.2 容器适配器
- 容器适配器是包装了现有STL容器类的模板类,提供了一个不同的、通常更有限制性的功能。
容器适配器在头文件中定义:
10.1.3 迭代器
- 迭代器对象的行为与指针类似,它对于访问除容器适配器定义的内容之外的所有STL容器的内容非常重要。容器适配器不支持迭代器。
迭代器有4个类别,每个类别支持不同范围的运算。
- 注意,当获得一个访问容器内容的迭代器时,得到的迭代器类型取决于使用的容器的种类。如果在创建迭代器时对它进行初始化,则auto 关键字可以为我们推断出其类型。
10.1.4 算法
-
算法是操作迭代器提供的一组对象的STL函数模板。
-
当向容器的内容应用算法时,我们提供指向容器内的对象的迭代器。算法用这些迭代器来访问容器内的对象,并在适当的时候将它们写回容器。
-
当向一个矢量的内容应用sort()算法时,就向sort()函数传递2个迭代器。一个指向第一个对象,另一个指向矢量中最后一个元素的下一个位置。
-
算法在2个标准头文件中定义:algorithm 头文件和numeric 头文件。
10.1.5 STL中的函数对象
- 函数对象时重载()运算符的类类型的对象,即该类实现operator()()函数。
- 函数对象模板在functional头文件中定义,必要时也可以定义自己的函数对象。
10.1.6 函数适配器
- 函数适配器是允许合并函数对象以产生一个更复杂的函数对象的函数模板。
10.2 STL 容器范围
- 序列容器:以线性风格存储给定类型对象的容器,可以作为动态数组或者列表。
- 关联容器:基于为每个要存储的对象提供的键存储对象,这些键用来定位容器内的对象。
10.3 序列容器
3个基本序列容器的类模板
10.3.1 创建矢量容器
// 创建矢量容器
vector<int> mydata;
mydata.push_back(99); // 向矢量的末尾添加一个新元素
创建一个将存储int类型的值的容器。存储元素的初始容量是0,因此在插入第1个值时开始分配更多的内存。
// 创建存储整数的矢量
vector<int> mydata(100);// 创建一个含有100个元素的矢量,全部初始化为0
mydata[2] = 999; // 在第3个元素中存储一个值
//在创建时不创建元素,通过调用reserve()函数在创建后增加其容量
vector<int> mydata;
mydata.reserve(100); //reserve()函数的实参是要容纳的最小元素个数
// 可以用外部数组中的元素作为初始值来创建矢量
double data[] = {1.5,2.5,3.5,4.5,5.5,6.5,7.5,8.5,9.5,10.5};
vector<double> mydata(data,data+8); //对应data[0]~data[7]
// 第1个实参指针指向数组中的第1个初始化元素;
// 第2个实参指针指向最后一个初始化元素的下一个位置;
// 在创建矢量时可以用含有相同类型元素的另一个矢量中的值对它进行初始化
vector<double> values(mydata.begin(),mydata.end());
// begin()函数返回一个随机访问迭代器,它指向调用该函数的矢量中的第一个元素
// end()函数返回的随机访问迭代器指向最后一个元素的下一个位置
- 元素序列通常在STL中由2个迭代器指定,一个指向序列中的第1个元素,另一个指向序列中最后一个元素的下一个位置。
调用矢量的rbegin()函数将返回指向最后一个元素的迭代器,rend()函数指向第一个元素的下一个位置(即第一个元素前面的位置)
// 创建一个以逆序包含另一个矢量中的内容的矢量:
double data[] = {1.5,2.5,3.5,4.5,5.5,6.5,7.5,8.5,9.5,10.5};
vector<double> mydata(data,data+8);
vector<double> values(mydata.rbegin(),mydata.rend());
// values矢量将以逆序包含mydata中的元素
10.3.2 矢量容器的容量和大小
- 容量(capacity):在不分配更多内存的情况下容器当前可以容纳的最大对象数目。
- 大小(size):实际存储在容器中的对象数目,因此大小不能大于容量。
// 通过调用size() 和 capacity()成员函数来得到一个容器data的大小与容量。
cout << endl << "The current capacity of the container is : "<< data.capacity()
<< endl << "The current size of the container is : "<< data.size()<< endl;
// 调用矢量的capacity()函数 将返回当前容量
// 调用矢量的size()函数 将返回当前大小
// 这两个返回值的类型都是vector<T>::size_type
为了创建一个变量来存储size() 或 capacity()函数的返回值,将它的类型指定为vector< T >::size_type.
vector<double> values;
vector<double>::size_type cap = value.capacity();
-
empty()函数返回bool类型的值,当矢量为空时返回true,否则返回false。
-
调用矢量的max_size()函数可以发现元素的最大可能数目。
-
调用矢量的resize()函数可以修改矢量大小,这个函数可以增加或减小矢量的大小。
- 如果指定一个小于当前大小的新大小,那么会从矢量的末尾删除足够的元素,使它减小到新的大小。
- 如果新大小大于旧大小,那么会向矢量的末尾添加新元素将长度增加到新的大小。
vector<int> values(5,66); // Contains 66 66 66 66 66
values.resize(7,88); // Contains 66 66 66 66 66 88 88
values.resize(10); // Contains 66 66 66 66 66 88 88 0 0 0
values.resize(4); // Contains 66 66 66 66
resize() 第1个实参是矢量的新大小
如果第2个实参存在的话 是组成新大小需要添加的新元素的值
如果正在增加大小,而且没有指定新元素要使用的值,就会使用这个默认的值
在矢量存储类类型对象的情况中,默认值将为类的无实参构造函数产生的对象
#include <iostream>
#include <vector> // 包含vector<T> 模板的定义
using std::cout;
using std::endl;
using std::vector; // 包含listInfo()函数模板的一个定义
// 输出任意矢量容器的当前容量与大小
template<class T>
void listInfo(vector<T> &v)
{
cout << "Container capacity: " << v.capacity()
<< " size: "<< v.size() << endl;
}
//形参指定为引用类型vector<T>& 使得函数体中的代码可以直接访问作为实参传递给函数的容器
//如果将形参指定为vector<T>类型,那么每次调用函数时就会复制实参。
int main()
{
vector<double> data; //创建矢量
listInfo(data);
cout << endl << "After calling reserve(100):"<<endl;
data.reserve(100);
listInfo(data);
vector<int> numbers(10,-1); // 初始化元素为-1
cout << endl << "The initial value are:";
//输出容器中的元素
for(vector<int>::size_type i = 0;i < numbers.size(); i++)
cout << " "<< numbers[i];
// 使用auto关键字:for(auto i = 0;i < numbers.size(); i++)
// 使用迭代器访问元素:
// for(auto iter = number.begin();iter < numbers.end();iter++)
// cout << " "<< *iter;
auto oldC = numbers.capacity();
auto newC = oldC;
cout << endl << endl;
listInfo(numbers);
for(int i = 0; i < 1000; i++)
{
numbers.push_back(2*i);
newC = numbers.capacity();
if(oldC < newC)
{
oldC = newC;
listInfo(numbers);
}
}
return 0;
}
10.3.3 访问矢量中的元素
// 使用at()函数,它的实参是要访问的元素的索引位置
for(auto i = 0; i < numbers.size(); i++)
cout << " " << numbers.at(i);
-
at()函数与下标操作符[]区别:
如果用超出合法范围的下标操作符进行下标,结果就不确定,如果用at()函数进行下标,那么会抛出out_of_range 类型的异常。
// 调用front()或back()函数,访问矢量容器中的第一个或最后一个元素
cout << "The value of the first element is:"<< numbers.front()<<endl;
cout << "The value of the first element is:"<< numbers.back()<<endl;
// 这2个函数有2个版本:
//1.返回对存储的对象的引用
//2.返回对存储的对象的const引用
//显式的防止修改对象:
const int& firstvalue = numbers.front();
int& lastvalue = numbers.back();
// 存储const 变量中返回的引用会自动返回const引用的函数版本。
10.3.4 在矢量中插入和删除元素
vec.pop_back();//删除矢量vec中的最后一个元素,并将大小减1
vec.clear(); //删除vec中的所有元素,因此大小将变成0,容量仍保持不变
// insert()函数在矢量中的特定位置插入一个新元素
// 第1个实参是指定要插入元素的位置的迭代器
// 第2个实参是要插入的元素
vector<int> vec(5,99);
vec.insert(vec.begin()+1,88);
// 执行后矢量中包含:99 88 99 99 99 99
// 从给定位置开始插入几个相同的元素:
vec.insert(vec.begin()+2,,3,77);
// 第2个实参是要插入的元素个数
// insert()函数在给定位置插入一个元素序列的版本:
// 第1个实参:指向要插入第1个元素的位置的迭代器
// 第2个实参、第3个实参 是输入迭代器,指定要插入的某个来源中的元素的范围
//erase()函数可以删除矢量中任何位置的一个或多个元素
newvc.erase(newvc.end()-2); //删除newvc中的倒数第2个元素
// 删除多个元素,使用2个迭代器实参来指定间隔
newvc.erase(newvc.begin()+1,newvc.begin()+4);
// 删除newvc中的第2、3、4 个元素
// swap()函数用来交换2个矢量的内容
vector<int> first(5,77);
vector<int> second(8,-1);
first.swap(second);
// assign()函数可以用另一个序列替换一矢量中的全部内容,或者用给定数量的对象实例
// 替换矢量内容。
vector<double> values;
for(int i = 1; i <= 50; i++)
values.push_back(2.5*i);
vector<double> newdata(5,3.5);
newdata.assign(values.begin()+1,values.end()-1);
// 删除newdata中的所有元素然后插入values中除第1个和最后一个元素外的所有元素的副本。
// 用统一个元素的实例序列替换一个矢量的内容
newdata.assign(30,99.5);
// 第1个实参:替换序列中的元素个数
// 第2个实参:要使用的元素
10.3.5 在矢量中存储类对象
可以在矢量中存储任何类类型的对象,不过类必须满足某个最低标准。 下面是一个要与矢量兼容的给定类T的最小规范:
class T
{
public:
T();
T(const T& t);
~T();
T& operator= (const T& t);
};
在矢量中存储对象:
//Person.h
#pragma once
#include <cstring>
#include <iostream>
using std::cout;
using std::endl;
class Person
{
public:
Person(const char* first = "John",const char* second = "Doe")
{
initName(first,second);
}
Person(const Person& p)
{
initName(p.firstname,p.secondname);
}
~Person()
{
delete[] firstname;
delete[] secondname;
}
Person& operator=(const Person& p)
{
if(&p == this)
return *this;
delete[] firstname;
delete[] secondname;
initName(p.firstname,p.secondname);
return *this;
}
bool operator < (const Person& p)
{
int result(strcmp(secondname,p.secondname));
if(result < 0|| result == 0 && strcmp(firstname,p.firstname)<0)
return true;
return false;
}
void showPerson() const
{
cout << firstname << " " << secondname << endl;
}
private:
char* firstname;
char* secondname;
void initName(const char* first,const char* second)
{
size_t length(strlen(first)+1);
firstname = new char[length];
strcpy_s(firstname,length,first);
length = strlen(second)+1;
secondname = new char[length];
strcpy_s(secondname,length,second);
}
};
// 在矢量中存储Person对象
#include <iostream>
#include <vector>
#include "Person.h"
using std::cin;
using std::cout;
using std::endl;
using std::vector;
int main()
{
vector<Person> people;
const size_t maxlength(50);
char firstname[maxlength];
char secondname[maxlength];
while(true)
{
cout << "Enter a first name or press Enter to end: ";
cin.getline(firstname,maxlength,'\n');
if(strlen(firstname) == 0)
break;
cout << "Enter the sencond name:";
cin.getline(secondname,maxlength,'\n');
people.push_back(Person(firstname,secondname));
}
cout << endl;
auto iter(people.begin()); // vector<Person> ::iterator类型的迭代器输出矢量元素
while(iter != people.end())
iter++->showPerson();
return 0;
}
10.3.6 排序矢量元素
- 在algorithm 头文件中定义的sort()函数模板会排序2个随机访问迭代器所指向的对象序列,这2个迭代器分别指向序列中的第一个对象,以及最后一个对象的下一个位置。
- 注意,随机访问迭代器至关重要,容量较小的迭代器会不够用。
- sort()函数模板用 < 运算符来排列元素的顺序,可以用sort()模板来排序提供随机访问迭代器的任何容器的内容,只要它包含的对象可以用 < 运算符进行比较。
在上示例中 Person类中实现了 operator<() ,因此可以排序Person对象的一个序列。
排序vector< Person > 容器的内容:
sort(people.begin(),people.end());
// 以升序方式来排序矢量中的内容。
// 需要添加 #include algorithm 和 using std::sort
对sort()数组用sort()模板函数,唯一的要求是 < 运算符应当使用存储在数组中的元素的类型。
const size_t max(100);
int data[max];
cout << "Enter up to " << max << "non-zero integers. Enter 0 to end." << endl;
int value(0);
size_t count(0);
for(size_t i = 0 ; i < max; i++)
{
cin >> value;
if(value == 0)
break;
data[count++] = value;
}
sort(data,data+count);
当需要以降序方式排序一个序列时,使用sort()算法的一个版本:它接受二元谓词函数对象作为函数的第三个实参。
functional 头文件定义比较谓词的完整类型集合:
less<T> less_equal<T> equal<T> greater_equal<T> greater<T>
这些模板都可以创建可用于sort() 和 其他算法的函数对象的类类型。上例中sort()函数默认情况下使用以个less< int > 函数对象。
sort(data,data+count,greater<int>());
// 以降序方式排序data数组中的元素
// 需添加: #include <functional> 和 using std::greater
10.3.7 排序矢量中的指针
// 在容器中存储Person 对象的指针
#include <iostream>
#include <vector>
#include "Person.h"
using std::cin;
using std::cout;
using std::endl;
using std::vector;
int main()
{
vector<Person*> people; // vector<T>模板类型形参为 Person* ,指向Person对象的指针
const size_t maxlength(50);
char firstname[maxlength];
char secondname[maxlength];
while(true)
{
cout << "Enter a first name or press Enter to end:";
cin.getline(firstname,maxlength,'\n');
if(strlen(firstname) == 0)
break;
cout << "Enter the second name :";
cin.getline(secondname,maxlength,'\n');
people.push_back(new Person(firstname,secondname));
// 在堆上创建每个Person对象,地址传递给矢量的push_back()函数
}
cout << endl;
auto iter(people.begin()); // 输出Person对象的迭代器类型 vector<Person*>::iterator
while(iter != people.end())
(*(iter++))->showPerson();
iter = people.begin(); // 在堆上删除创建的对象
while(iter != people.end())
delete *(iter++); // 解除迭代器的引用来获得要销毁的对象的地址
people.clear(); // 所有对象已销毁,矢量中的指针现在已经无效,调用矢量的clear()函数清空指针
return 0;
}
10.3.8 双端队列容器
双端队列容器模板 deque< T > 是在deque头文件中定义的。双端队列容器非常类似于矢量,其功能与矢量容器相同,并且包括同样的函数成员,但是也可以在序列的开头和末尾有效的添加和删除元素。
用双端队列替换 Ex10_2中使用的矢量:
deque<Person> people;
// 需要修改: #include <deque>
向容器的前端添加元素的函数:push_front()
删除第一个元素的函数: pop_front()
people.push_front(Person(firstname,secondname));
// 使用这条语句向容器中添加元素的唯一区别:双端队列中的元素
// 顺序将为矢量中元素顺序的逆序。
- 双端队列的缺点:内存管理比矢量复杂,因此它会稍慢一些,除非确实需要向容器的前端添加元素,否则矢量是更好的选择。
//将任意数目的整数存储在双端队列中,然后对它们进行运算
#include <iostream>
#include <deque>
#include <algorithm>
#include <numeric> // 包含accumulate()函数
using std::cin;
using std::cout;
using std::endl;
using std::deque;
using std::sort;
using std::accumulate;
int main()
{
deque<int> data;
deque<int> ::iterator iter; // 定义迭代器:存储以前向方式访问队列元素的迭代器
deque<int> ::reverse_iterator riter; // 逆向迭代器
cout << "Enter a series of non-zero integers separated by spaces."
<< "Enter 0 to end." << endl;
int value(0);
// 一系列用,分隔的表达式的值时最右边的表达式的值,即value!=0 为真,且读取的值为非零,则继续while循环
while(cin >> value,value != 0)
data.push_front(value);
cout << endl << "The values you entered are : " << endl;
for(iter = data.begin();iter != data.end(); iter++)
cout << *iter << " ";
cout << endl;
cout << endl << "In reverse order the values you entered are: " << endl;
for(riter = data.rbegin(); riter != data.rend(); riter++)
cout << *riter << " "; // 逆序迭代器 从最后一个元素开始
cout << endl;
cout << endl << "In descending sequence the values you entered are: "<< endl;
sort(data.rbegin(),data.rend()); // 降序排序元素
for(iter = data.begin(); iter != data.end() ; iter++)
cout << *iter << " ";
cout << endl;
cout << endl << "The sum of the elements in the queue is : "
<< accumulate(data.begin(),data.end(),0) << endl;
// accumulate()函数:累计前两个迭代器实参标识的元素序列的和。
// 第3个实参指定和的已个初始值。 返回运算结果
return 0;
}
10.3.9 使用列表容器
- list 头文件中定义的 List< T > 容器模板实现一个双向链表。
- 与矢量或双端队列相比,列表容器的优点: 可以在固定时间从序列的任意位置插入或删除元素。
(1)创建空列表:
lsit<string> names;
(2)用给定数目的默认元素创建一个列表
list<string> sayings(20);
(3) 创建包含给定数目的相同元素的列表
list<double> values(50,2.71828);
// 创建一个包含50个double类型元素的列表
(4) 构造一个用2个迭代器指定的序列中的值初始化的列表
list<double> samples(++values.begin(),--values.end());
// 用value 列表中的内容创建一个列表,省略了values中的第一个和最后一个元素
1. 向列表中添加元素
- 通过调用push_front() 或 push_back() 向列表的开头或末尾添加元素。
- 使用3个版本的insert()函数:
(1) 在迭代器指定的位置插入一个新元素:
list<int> data(20,1);
data.insert(++data.begin(),77);
// 第1个实参: 在插入位置指定的迭代器
// 第2个实参:要插入的元素
// 应用到begin() 返回的双向迭代器的递增运算符使它指向列表中的第2个元素。
(2)在给定位置插入相同元素的一些副本
list<int> ::iterator iter = data.begin();
for (int i = 0; i < 9; i++)
++iter;
data.insert(iter,3,88);
// 第1个实参: 指定位置的迭代器
// 第2个实参:要插入的元素数目
// 第3个实参:要重复插入的元素
(3) 向列表中插入一个元素序列
vector<int> numbers(10,5);
data.insert (--(--data.end()),numbers.begin(),numbers.end());
// 第1个实参:指向倒数第2个元素位置的迭代器
// 第2个实参、第3个实参 :指定要插入的序列
2. 访问列表中的元素
- 通过调用列表的front() 或 back()函数来获得对列表中第1个或最后一个元素的引用。
- begin() 和 end() 函数返回一个双向迭代器,分别指向第1个元素,或最后一个元素的下一个位置。
- rebegin() 和 rend() 函数返回一个双向迭代器,可以以逆向序列迭代所有元素。
// 通过键盘输入一些语句,并将它们存储在一个列表中
#include <iostream>
#include <list>
#include <string>
using std::cin;
using std::cout;
using std::endl;
using std::list;
using std::string;
int main()
{
list<string> text;// 创建列表容器
list<string>::iterator iter; //用来输出列表内容的迭代器变量
cout << "Enter a few lines of text.Just press Enter to end:"
<< endl;
string sentence;
while(getline(cin,sentence,'\n'),!sentence.empty())
text.push_front(sentence);
cout << endl << "Here is the text you entered:" << endl;
for(iter = text.begin();iter != text.end();iter++)
cout << *iter << endl;
cout << endl << "Inascending sequence the sentences you entered are:" << endl;
text.sort();
for(iter = text.begin();iter != text.end();iter++)
cout << *iter << endl;
return 0;
}
3. 列表上的其他操作
- clear()函数:删除列表中的所有元素。
- erase()函数:
删除由一个迭代器指定的单个元素,或者由一对迭代器以惯用的方式(序列中的第1个元素,以及最后一个元素的下一个位置)指定的一个元素序列。
int data[] = {10,22,4,56,89,77,13,9};
list<int> numbers(data,data+8);
number.erase(++numbers.begin()); // 删除第2个元素
number.erase(++numbers.begin(),--(--numbers.end())); //输出:10,13,19
- remove() 函数从列表中删除匹配特定值的元素。
// 删除所有等于22 的元素
numbers.remove(22);
- assign()函数删除列表中是所有元素,并将单个对象多次复制到列表中,或者复制由2个迭代器指定的一个对象序列。
int data[] = {10,22,4,56,89,77,13,9};
list<int> numbers(data,data+8);
numbers.assign(10,99); // 替换元素的个数 ,替换元素的值
numbers.assing(data+1,data+4); //替换22,4,56
- unique()函数 消除列表中相邻的重复元素。
- splice() 函数 删除一个列表的全部或一部分,并将它插到另一个列表中。
int data[] = {1,2,3,4,5,6,7,8};
list<int> numbers(data,data+3); // 1 2 3
list<int> values(data+4,data+8);// 5 6 7 8
numbers.splice(++numbers.begin(),values); // 1 5 6 7 8 2 3
// 第1个实参:迭代器 指定应在何处插入元素
// 第2个实参:要插入的元素来自的列表
- splice() 另一个版本:删除源列表中给定位置的元素,并将它们插入在目标列表的给定位置。
int data[] = {1,2,3,4,5,6,7,8};
list<int> numbers(data,data+3); // 1 2 3
list<int> values(data+4,data+8);// 5 6 7 8
numbers.splice(numbers.begin(),values,--values.end()); // 8 1 2 3
// 第1个实参:迭代器 指定应在何处插入元素
// 第2个实参:要插入的元素来自的列表
// 第3个实参:指定要从源列表中选择的第一个元素的位置
- splice() 的第3个版本:从列表中选择一个元素范围
int data[] = {1,2,3,4,5,6,7,8};
list<int> numbers(data,data+3); // 1 2 3
list<int> values(data+4,data+8);// 5 6 7 8
numbers.splice(++numbers.begin(),values,++values.begin(),--values.end()); // 1 6 7 2 3
// 第1个实参:迭代器 指定应在何处插入元素
// 第2个实参:要插入的元素来自的列表
// 第3、4个实参:指定要从源列表中选择哪一段
- merge()函数
删除作为一个实参提供的列表中的元素,并将它们插入调用该函数的列表中。然后函数默认会按升序方式排序扩展列表的内容,或者按我们作为merge()函数的第2个实参提供的函数对象确定的其他顺序排序。
int data[] = {1,2,3,4,5,6,7,8};
list<int> numbers(data,data+3); // 1 2 3
list<int> values(data+1,data+8);// 2 3 4 5 6 7 8
numbers.merge(values); //1 2 2 3 3 4 5 6 7 8
// 将vlaues的内容合并到numbers中,执行后values将变空。
numbers.sort(greater<int>()); // 3 2 1
values.sort(greater<int>()); // 8 7 6 5 4 3 2
numbers.merge(values,greater<int>()); // 8 7 6 5 4 3 3 2 2 1
// 在functional 头文件中定义的greater<int>() 函数对象来指定应以降序方式排序的列表
- remove_if()函数 基于应用一元谓词的结果来删除列表中的元素。
一元谓词是一个应用到单个实参的函数对象,它返回一个bool值。如果向一个元素应用谓词结果是ture,就会从列表中删除该元素。
STL 定义用在这种上下文中的unary_function<T,R> 基础模板,这个模板只定义将由指定函数对象类型的派生类继承的类型。
template<class _Arg,class _Result>
struct unary_function
{
typedef _Arg argument_type;
typedef _Result result_type;
};
//基于STL中的帮助模板定义一个函数对象模板,然后用它来删除列表中的负值
#pragma once
#include <functional>
template <class T> class is_negative: public std::unary_function<T,bool>
{
public:
result_type operator() (argument_type & value)
{
return value < 0; // 值<0 返回true,remove_if ,结果返回ture 从列表中删除该元素
}
};
#include <iostream>
#include <list>
#include <functional>
#include "function_object.h"
using std::cin;
using std::cout;
using std::endl;
using std::list;
using std::greater;
template <class T>
void listlist(list<T>& data)
{
for(auto iter = data.begin(); iter != data.end(); iter++)
cout << *iter << " ";
cout << endl;
}
template <class T>
void loadlist(list<T>& data)
{
T value = T();
while(cin >> value, value != T())
data.push_back(value);
}
int main()
{
list<int> numbers;
cout << "Enter non-zero integers separated by spaces. Enter 0 to end."
<< endl;
loadlist(numbers);
cout << "The list contains: "<< endl;
listlist(numbers);
numbers.remove_if(is_negative<int>()); //依次向列表中的各个元素应用谓词,并删除谓词返回的true元素
cout << "After applying the remove_if() function the list contains:"
<< endl;
listlist(numbers);
list<double> values;
cout << endl
<< "Enter non-zero integers separated by spaces. Enter 0 to end."
<< endl;
loadlist(values);
cout << "The list contains: "<< endl;
listlist(values);
values.remove_if(is_negative<double>());
cout << "After applying the remove_if() function the list contains:"
<< endl;
listlist(values);
return 0;
}
10.3.10 使用其他序列容器
1. 队列容器
- queue< T > 容器通过适配器实现先进先出存储机制。只能向队列的末尾添加或从开头删除元素。
创建队列:
queue<string> names;
// 创建一个可以存储string类型元素的队列。
基于列表创建队列:
queue<string,list<string>> names;
// 适配器模板的第2个类型形参指定要使用的底层序列容器
队列适配器类扮演底层容器类的包装器的角色,可以执行的操作范围基本上限制在下表中:
- 注意,访问队列内容的唯一方式是通过back() 和 front() 函数。
// 连续读取一条或多条格言,并将它们存储在队列中,然后检索这些格言并输出它们。
#include <iostream>
#include <queue>
#include <string>
using std::cin;
using std::cout;
using std::endl;
using std::queue;
using std::string;
int main()
{
queue<string> sayings; // 创建队列容器
string saying;
cout << "Enter one or more sayings.Press Enter to end." << endl;
while(true)
{
getline(cin,saying); // 换行符是默认输入终止符
if(saying.empty())
break;
sayings.push(saying);
}
cout << "There are " << sayings.size()
<< " sayings in the queue."
<< endl << endl;
cout << "The sayings that you entered are:" << endl;
while(!sayings.empty())
{
cout << sayings.front() << endl; // front()函数返回对队列前端对象的引用
sayings.pop(); // pop()函数将各个元素从队列中删除
}
return 0;
}
2. 优先级队列容器
- priority_queue< T > 容器是一个队列,它的顶部总是具有最大或最高优先级的元素。
定义优先级队列容器:
priority_queue<int> numbers
当向队列中添加元素时确定元素的相对优先级的默认条件是标准less< T >函数对象模板。
用push()函数向优先级队列中添加一个元素:
numbers.push(99);
优先级队列的完整操作集合
- 注意,使用优先级队列时不能访问队列后端的元素,只能访问前端的元素。
默认情况下,优先级队列适配器类使用的基础容器为vector< T >.可以选择指定不同的序列容器做为基础,并选择一个备用函数对象来确定元素的优先级。
priority_queue<int,deque<int>,greater<int>> numbers;
//基于deque<int> 容器定义优先级队列,用greater<int>类型的函数对象插入元素。
// 这个优先级队列中的元素将以降序排列,顶部是最小的元素
// 3个模板形参是元素类型、要用做基础的容器、要用来排列元素顺序的谓词类型。
//Person类保存string类型的姓名,将Person对象存储在容器中
//Person.h
#pragma once
#include <iostream>
#include <string>
using std::cout;
using std::endl;
using std::string;
class Person
{
public:
Person(const string first,const string second)
{
firstname = first;
secondname = second;
}
Person(){}
Person(const Person& p)
{
firstname = p.firstname;
secondname =p.secondname;
}
bool operator > (const Person& p) const
{
return p > *this;
}
void showPerson() const
{
cout << firstname << " " << secondname << endl;
}
private:
string firstname;
string secondname;
};
// 在优先级队列中存储Person对象
#include <iostream>
#include <vector>
#include <queue>
#include <functional>
#include "Person.h"
using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::priority_queue;
using std::greater;
int main()
{
priority_queue < Person,vector<Person>,greater<Person>> people;
string first,second;
while(true)
{
cout << "Enter a first name or press Enter to end:";
getline(cin,first);
if(first.empty())
break;
cout << "Enter a second name:";
getline(cin,second);
people.push(Person(first,second));
}
cout << endl << "There are " << people.size()
<< " people in the queue."
<< endl << endl;
cout << "The names that you entered are: " << endl;
while(!people.empty())
{
people.top().showPerson();
people.pop();
}
return 0;
}
3. 栈容器
- stack< T > 容器适配器模板在stack头文件中定义,默认情况下基于deque< T >容器实现向下推栈。向下推栈是一种后进先出存储机制,只能访问最近在栈中添加的对象。
定义栈:
stack<Person> people;
// 基于一个列表定义栈:
stack< string,list<string> > names;
对于stack< T >容器仅可以应用5种操作
4. 数组容器
- 定义在array头文件中的array<T,N> 容器存储着一个数组,此数组包含N个类型为T的元素。
初始化数组容器:
std::array<int,10> samples = {1,2,3,4,5};
// 使用迭代器遍历array<> 中的所有元素:
for(auto iter = samples.begin(); iter != samples.end() ; ++iter)
cout << *iter << " ";
// 使用索引值访问array <>容器中的元素:
for(size_t i = 0; i < samples.size(); ++i)
cout << samples[i] << " ";
10.4 关联容器
- 关联容器(map< K,T>)最重要的特性是无需搜索就可以检索特定对象。
- 关联容器内T类型对象的位置由与对象一起提供的类型为K的键确定,因此只需提供适当的键就可以快速的检索任何对象。该键实际上是一个确定映射中的条目顺序的排序键。
10.4.1 使用映射容器
map<Person,string> phonebook;
// 定义了一个空映射容器,用来存储 键/对象对 条目,
// 键的类型是Person,对象的类型是string
创建一个映射容器,用另一个映射容器中的键/对象对 序列来初始化:
map<Person,string> phonebook(iter1,iter2);
// iter1 和 iter2 是用常规方式定义另一个容器中的一系列键/对象对的一对迭代器,
// iter2 指定将包括在序列中的最后一个键/对象对 后面的位置。
默认情况下,映射中的条目基于less< key > 类型的函数对象排序,即升序键序列存储。
通过提供第3个模板类形参来修改用来排列映射中条目顺序的类型函数对象。
map<Person,string,greater<Person> phonebook;
// greater <Person> :条目将以降序键序列排序
1. 存储对象
pair< K,T > 类型是在utility头文件中定义的,它包括在map头文件中,因此如果正在使用映射,类型就会自动可用。
// 定义一对对象
auto entry = pair<Person,string>(Person("Mel","Gibson"),"213 345 5678");
// 创建pair<Person,string> 类型的变量entry,并将它初始化为从Person对象和string对象中创建的对象。
pair< K,T > 类模板的实例定义两个构造函数,一个是前面的代码片段中用来定义键中的对象和它的关联对象的构造函数。另一个是允许从现有对中构造一个新对的复制构造函数。可以通过成员first和second访问一个对中的元素。
entry.first 引用Person对象;
entry.second 引用string对象;
- 使用在utility头文件中定义的帮助函数make_pair() 来创建一对对象:
auto entry = make_pair(Person(Person("Mel","Gibson"),"213 345 5678");
- 用于对对象的所有比较运算符都被重载了,因此可以用下列运算符中的任何一个来比较它们:<、 <=、==、!=、>= 、>。
- 用typedef语句来简写在特定情况下使用的pair< K,T > 类型:
map<Person,string> phonebook;
typedef pair<Person,string> Entry;
// 定义Entry 作为键/对象 对的类型
定义了Entry类型后,就可以创建该类型的对象:
Entry entry1 = Entry(Person("Jack","Jones"),"213 567 1234");
- 用Insert() 函数在映射中插入一个或多个对:
// 插入一个对象:
phonebook.insert(entry1);
// insert() 函数的这个版本返回的值也是一个对,这个对中的第一个对象是迭代器,第二个对象是bool类型的值。
// 如果进行了插入操作,那么该对中的bool值为true,否则为false。
// 如果元素存储在映射中,对中的迭代器将指向这个元素,或者如果插入失败,则指向已经在映射中的元素。
- 检查有没有存储对象:
auto checkpair = phonebook.insert(entry1);
if(checkpair.sencond)
cout << "Insert succeeded." << endl;
else
cout << "Insertion failed." << endl;
解除insert() 函数返回的对中的迭代器的引用将赋予我们对存储在映射中的对的访问权限。
cout << "The key for the entry is :" << endl;
checkpair.first->first.showPerson();
// 表达式checkpair.first 引用checkpair对的第一个成员,是一个迭代器,因此使用这个表达式访问指向该映射中的对象的指针。
// 映射中的对象是另一个对,因此表达式 checkpair.first->first 访问那个对的第一个成员。即Person对象。
- map< K,T > 模板定义operator[]{}函数,可以用这个下标操作符来插入对象。
// 在电话簿映射中插入entry1对象:
phonebook[Person("Jack","Jones")] = "213 567 1234";
2. 访问对象
- 可以用下标操作符从映射中检索对应于给定键的对象
string number = phonebook[Person("Jack","Jones")] ;
// 在number中存储对应Person("Jack","Jones")键的对象:
用find() 函数来检查是否存在给定键的条目:
string number;
Person key = Person("Jack","Jones");
auto iter = phonebook.find(key);
if(iter != phonebook.end())
{
number = iter->second;
cout << " The number is "<< number << endl;
}
else
{
cout << "No number for the key ";
key.showPerson();
}
// find()函数返回一个类型为map<Person,string>::iterator的迭代器,此迭代器指向对应于该
// 键的对象,或者指向映射中最后一个条目的下一个位置,它对应于end()函数返回的迭代器。
// 如果iter != end()函数返回的迭代器,那么存在该条目,可以通过那个对的第2个成员来访问对象
3. 其他映射操作
- erase() 函数可以从映射中删除单个条目或者某个范围内的条目。
Person key = Person("Jack","Jones");
auto count = phonebook.erase(key);
if(count == 0)
cout << "Entry was not found." << endl;
// 当向erase() 函数提供一个键时,它返回已经删除的条目个数。
提供一个迭代器作为erase()的实参:
Person key = Person("Jack","Jones");
auto iter = phonebook.find(key);
iter = phonebook.erase(iter);
// erase() 返回一个迭代器,指向映射中被删除的条目的下一条目
// 或者如果不存在这样的元素,那么返回一个指向映射末尾的指针
if(iter == phonebook.end())
cout << "End of the map reached." << endl;
- 可以应用于映射容器的其他操作内容:
10.4.2 使用多重映射容器
-
多重映射不能使用下标操作符
-
映射与多重映射的基本区别:
在多重映射中相同的键可以有多个条目,这样会影响部分函数的行为方式。 -
多重映射的insert() 函数:
insert() 的最简版本接受一个pair< K,T > 对象作为实参,返回一个指向插入到多重映射中的条目的迭代器。
insert() 带2个实参的版本,第2个实参 是要插入的对,第1个实参是一个迭代器,指向多重映射中要开始搜索插入点的位置。返回一个指向被插入的元素的迭代器。 -
当向多重映射的erase() 函数传递一个键时,它删除带相同键的所有条目,返回的值表明删除了多少条目。
-
find() 函数只能在多重映射中找到有给定键的第一个元素。
// 给定一个phonebook对象类型为 multimap<Person,string>
// 列出对应于给定键的电话号码:
Person person = Person("Jack","Jones");
auto iter = phonebook.lower_bound(person);
if(iter == phonebook.end())
cout << "There are no entries for "<< person.getName() << endl;
else
{
cout << "The following numbers are listed for " << person.getName() << ":" << endl;
for(; iter != phonebook.upper_bound(person);iter++)
cout << iter-> second << endl;
}
10.5 关于迭代器的更多内容
10.5.1 使用输入流迭代器
// 创建输入流迭代器
istream_iterator<int> numbersInput(cin);
// 创建istream_iterator 类型的迭代器numbersInput,可以指向流中int类型的对象。
// 这个构造函数的实参指定与迭代器相关的实际流,它是一个可以从标准输入流cin中读取整数的迭代器。
默认的istream_iterator< T > 构造函数创建一个end-of-stream 迭代器,它等价于通过调用end() 函数获得的容器end迭代器:
istream_iterator<int> numbersEnd;
将cin中的值加载到vector< int >容器中。
vector<int> numbers; // 定义矢量容器
cout << "Enter integers separated by spaces then a letter to end:" << endl;
istream_iterator<int> numbersInput(cin),numbersEnd;// 创建2个输入流迭代器
// numbersInput 从cin中读取int类型值的输入流迭代器
// numbersEnd 是读取同样的输入流的end-of-stream 迭代器
while(numbersInput != numbersEnd)
numbers.push_back(*numbersInput++);
// 如果输入Ctrl+Z 来结束输入流 或 输入无效字符,就会产生end-of-stream 条件
- sstream 头文件定义basic_istringstream< T > 类型,这个类型定义可以访问流缓冲区中的数据的对象类型。
- 还将类型istringstream 定义为basic_istringstream< char >,它将是char类型的字符流。
- 可以从string对象中构造一个istringstream 对象,这意味着从string对象中读取数据,就像从cin中读取数据那样。因为istringstream对象是一个流,所以可以将它传递给一个输入迭代器构造函数,并用该迭代器访问底层流缓冲区中的数据。
string data("2.4,2.5,3.6,2.1,6.7,6.8,94,95,1.1,1.4,32");
istringstream input(data);
istream_iteraotr<double> begin(input),end;
// 创建2个可以访问input流中的double值的流迭代器。
// 用它们将data的内容传递给accumulate() 算法
cout << "The sum of the values from the data string is "
<< accumulate(begin,end,0.0) << endl;
从string对象data中创建istringstream对象,因此可以像流一样从data中读取数据。
注意,accumulate()函数的第3个实参的类型确定结果的类型,因此必须将它指定为double类型的值,以正确的得到产生的和。
//使用流迭代器从标准输入流中读取文本,并将它传输给一个映射容器,以产生该文本的单词数量
#include <iostream>
#include <iomanip>
#include <string>
#include <map>
#include <iterator>
using std::cout;
using std::cin;
using std::endl;
using std::string;
int main()
{
std::map<string,int> words; //定义映射容器来存储单词和单词个数
//这个容器用string类型的单词作为键 来存储int类型的每个单词的个数
cout << "Enter some text and press Enter followed by Ctrl+Z then Enter to end:"
<< endl << endl;
std::istream_iterator<string> begin(cin); // 标准输入流的流迭代器
std::istream_iterator<string> end; // end-of-stream迭代器,用来检测何时到达输入的末尾
while(begin != end) // 通过标准输入流在输入的所有单词上迭代,直到达到end-of-stream状态
words[*begin++]++; // 用映射容器的下标操作符将单词的个数存储为键
// *begin 访问一个单词,*begin++ 在访问单词后递增迭代器
cout << endl << "Here are the word counts for the text you entered:"<< endl;
for(auto iter = words.begin(); iter != words.end();++iter)
cout << std::setw(5) << iter->second <<" "<< iter->first << endl;
return 0;
}
//关联容器通过 键 存储和读取元素
// map的下标也是用索引(即键) 来获取该键所关联的值。
// 如果该键已在容器中,则map的下标运算与vector的下标运算行为相同,返回该键所关联的值
// 如果该键不存在,map容器将为该键创建一个新的元素,并将它插入到此map对象中,此时所关联的值采用值初始化
// 类类型的元素用默认构造函数初始化,而内置类型的元素则初始化为0
10.5.2 使用插入迭代器
3个创建插入迭代器的模板:
- back_insert_iterator< T > 在类型T的容器末尾插入元素。容器必须为此提供push_back() 函数才能工作。
- front_insert_iterator < T > 在类型T的容器开头插入元素。这依赖于push_front() 对容器可用。
- insert_iterator< T > 在类型T 的容器内从指定位置开始插入元素。这要求容器有一个insert()函数,此函数接收2个参数,迭代器作为第1个实参,要插入的项作为第2个实参。
list<int> numbers;
front_insert_iterator< list<int> > iter(numbers);
// 创建一个可以在list<int> 容器numbers的开头插入数据的插入迭代器。
// 向容器中插入值:
*iter = 99;
// 将front_inserter 函数用于numbers容器:
front_inserter(numbers) = 99;
// 为numbers列表创建了一个前端插入器,并用它在列表开头插入99
// front_inserter()函数的实参是运用迭代器的容器
//insert_iterator<T>迭代器的构造函数需要2个实参:
insert_iterator<vector<int>> iter_anywhere(numbers,numbers.begin());
// 第2个实参:指定在何处插入数据的迭代器
// 用这个迭代器向一个矢量容器中插入一系列值
for(int i = 0; i < 100; i++)
*iter_anywhere = i + 1;
// 在numbers 容器中插入1~100 的值
//inserter()的第1个实参是容器,第2个实参是一个标识在何处插入数据的迭代器。
//从cin中读取值,并将这些值传递给list<T>容器:
list<double> values;
cout << "Enter a series of values separated by spaces"
<< " followed by Crtl+Z or a letter to end: "<< endl;
istream_iterator<double> input(cin),input_end;
copy(input,input_end,back_inserter<list<double>(values));
// 复制操作的目的地是在copy()函数的第3个实参中创建的后端插入迭代器
// 后端插入迭代器在列表容器values中添加复制操作传输的数据。
10.5.3 使用输出流迭代器
2个构造函数可以创建输出流迭代器模板的一个实例:
1.创建仅向目的流传输数据的迭代器
ostream_iterator<int> out(cout);
// 类型实参int指定要处理的数据类型,构造函数实参cout指定将作为数据的目的地的流
// 使用该迭代器
int data[] = {1,2,3,4,5,6,7,8,9};
vector<int> numbers(data,data+9);
copy(numbers.begin(),numbers.end(),out);
// 在algorithm 头文件中定义的copy()算法将由前2个迭代器实参
// 指定的对象序列复制到由第3个实参指定的输出迭代器。
// 该函数将numbers矢量中的元素赋值到out迭代器中,它将元素写到cout中。
2.第2个输出流迭代器构造函数:
ostream_iterator<int> out(cout,", ");
// 第2个实参,作为输出值的分隔符
// 从cin中读取一系列整数值,并存储在一个矢量中,输出这些值及它们的和。
#include <iostream>
#include <numeric>
#include <vector>
#include <iterator>
using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::istream_iterator;
using std::ostream_iterator;
using std::back_inserter;
using std::accumulate;
int main()
{
vector<int> numbers;
cout << "Enter a series of integers seqarated by spaces"
<< " followed by Ctrl+Z or a letter:" << endl;
istream_iterator<int> input(cin),input_end;
ostream_iterator<int> out(cout," ");
copy(input,input_end,back_inserter<vector<int>>(numbers));
// 数据从cin中读入,用copy算法传输给矢量容器
cout << "You entered the following values:" << endl;
copy(numbers.begin(),numbers.end(),out);
cout << endl << "The sum of these values is "
<< accumulate(numbers.begin(),numbers.end(),0) << endl;
return 0;
}
10.6 关于函数对象的更多内容
fuctional头文件定义了一个可扩展的模板集合,用来创建可用于算法和容器的函数对象。常用模板如下:
functional头文件还定义对元素进行算术运算的函数对象。通常使用algorithm头文件中定义的transform()算法,通过这些函数对象对数字值序列进行运算。
函数对象如下表,其中形参T指操作数的类型。
10.7 关于算法的更多内容
algorithm 和 numeric 头文件定义了大量算法:
numeric头文件:算法主要用来处理数组中的值。
algorithm 头文件:算法大多用来搜索、排序、复制和合并迭代器指定的对象序列。
10.7.1 fill()
fill() 函数的格式:
fill(ForwardIterator begin,ForwardIterator end, const Type& value)
// 用value填充有迭代器begin 和 end 指定的元素。
// 给定一个矢量v,存储含有10个以上元素的string类型值:
fill(v.begin(),v.begin()+10,"invalid");
// 将v中的前10个元素设置为fill()的最后一个实参指定的值"invalid"
10.7.2 replace()
//replace() 算法格式:
replace(ForwardIterator begin,ForwardIterator end,const Type& oldValue,const Type& newValue)
//分析begin和end指定范围内的每个元素,并用newValue替换出现的每个oldValue.
//给定一个存储string对象的矢量v,用no替换出现的yes:
replace(v.begin(),v.end(),string("yes"),string("no"));
// replace 使用指针:
char str[] = "A nod is as good as a wink to a blind horse.";
repalce(str,str+strlen(str),'o','*');
cout << str << endl;
// 输出: A n*d is as g**d as a wink t* a blind h*rse.
10.7.3 find()
// find() 函数格式:
find(ForwardIterator begin,ForwardIterator end, const Type& value)
//搜索首次出现的value的前2个实参指定的序列。
//给定一个含有int类型值的矢量v:
vector<int>::iterator iter = find(v.begin(),v.end(),21);
//使用iter作为新搜索的开始,用find()算法反复查找出现的所有给定值:
vector<int>::iterator iter = v.begin();
int value = 21,count = 0;
while((iter = find(iter,v.end(),value)) != v.end())
{
iter++;
count++;
}
cout << "The vector contains " << count << " occurrences of " << value << endl;
// 在矢量v中搜索出现的所有value。
10.7.4 transform()
transform() 函数有2个版本:
1.第1个版本将一个一元函数对象指定的操作应用到由一对迭代器指定的一个元素集合上:
transform(InputIterator begin,InputIterator end,OutputIterator result,UnaryFunction f)
// 将一元函数f应用到迭代器begin 和 end 指定的范围中的所有元素,
// 并从迭代器 result指定的位置开始存储结果。
// 这些函数返回一个迭代器,指向存储的最后一个结果的下一个位置。
// 举例:
double values[] = {2.5,-3.5,4.5,-5.5,6.5,-7.5};
vector<double> data(values,values+ sizeof values / sizeof values[0]);
transform(data.begin(),data.end(),data.begin(),negate<double>());
// 输出:-2.5,3.5,-4.5,5.5,-6.5,7.5;
2.第2个版本通过来自迭代器指定的2个范围内的操作数应用一个二元函数。
// 函数格式:
transform(InputIterator1 begin1,InputIterator1 end1,InputIterator2 begin2,OutputIterator result,BinaryFunction f)
// 由begin1 和 end1 指定的范围表示最后一个实参指定的二元函数f的左操作数集合。
// 表示右操作数的范围从begin2迭代器指定的位置开始,这个范围不需
// 要提供end迭代器,因为这个范围的元素数量必须与begin1 和 end1 指
// 定的范围中的元素个数相同。结果将从result迭代器位置开始存储在这个范围内。
// 用法:
double values[] = {2.5,-3.5,4.5,-5.5,6.5,-7.5};
vector<double> data(values,values+ sizeof values / sizeof values[0]);
vector<double> squares(data.size());
transform(data.begin(),data.end(),data.begin(),squares.begin,multiplies<double>());
ostream_iterator<double> out(cout," ");
copy(squares.begin(),squares.end(),out);
//transform() 函数通过multiplies<double>() 函数对象使data的每个元素与自身相乘。结果存储在squares矢量中。
10.8 lambda 表达式
- lambda表达式定义一个没有名称、也不需要显式类定义的函数对象。
- lambda表达式一般作为一种手段,用来将函数作为实参传递到另一个函数。
/计算包含数值矢量的矢量元素的立方
double values[] = {2.5,-3.5,4.5,-5.5,6.5,-7.5};
vector<double> data(values,value+6);
vector<double> cubes(data.size());
transform(data.begin(),data.end(),cubes.begin(),[](double x){return x*x*x;});
// lambda表达式时transform()操作的最后一个实参
// []称为lambda引导,因为它们标记着lambda表达式的开始
// (double x) 是lambda形参列表
// { }内是lambda表达式的主体
- lambda形参列表限制:
- 不能指定lambda表达式形参的默认值。
- 形参列表的长度不能是可变的。
- 当lambda表达式的主体是单一的返回语句,而该语句在lambda表达式主主体中返回一个值时,返回类型默认是返回值的类型。否则默认返回类型是void。
- 指定返回类型:
[ ](double x)->double {return x*x*x;}
//箭头后面的关键字double指定返回类型为double
//输出计算的值
[ ](double x)->double {
double result(x*x*x);
cout << result << " ";
return result;
}
10.8.1 capture 子句
- lambda表达式引导可以包含一个捕获子句,用来确定lambda主体如何访问封闭作用域中的变量。
- 如果[ ]之间是 =,则lambda主体可以按值访问封闭作用域中的所有自动变量——即变量值可用在lambda表达式中,但不会修改原始的变量。
- 如果[ ] 之间是 & ,则封闭作用域中的所有变量都是按引用访问,因此可以被lambda主体中的代码修改。
double factor = 5.0;
double values[] = {2.5,-3.5,4.5,-5.5,6.5,-7.5};
vector<double> data(values,value+6);
vector<double> cubes(data.size());
transform(data.begin(),data.end(),cubes.begin(),[=](double x){return x*x*x;});
//注意,这与按值传递实参不同,变量factor的值可用在lambda中,但不能更新factor的副本,因为它实际上是常量
从lambda中修改作用域中变量的临时副本,则通过mutable关键字实现:
transform(data.begin(),data.end(),cubes.begin(),
[=](double x)mutable->double {
factor += 10.0;
return factor*x*x*x;});
//现在可以修改封闭作用域中任意变量的副本,而不会改变原始变量。
//执行语句后,factor的值仍然是5.0,lambda会记住factor从一个调用到下一个调用的本地值,
//因此,对于第1个元素factor将是5+10=15;对于第2个元素,factor将是15+10=25,以此类推。
要从lambda 中改变factor的原始值,只需要将& 用作捕获子句。
10.8.2 捕获特定的变量
显式的标识要访问的变量:
transform(data.begin(),data.end(),cubes.begin(),
[&factor](double x)->double {
factor += 10.0;
return factor*x*x*x;});
// 现在factor是封闭作用域中唯一可访问的变量,且它可按引用访问。
// 如果省略&,则factor按值访问,并且不可更新。
如果要在捕获子句中标识多个变量,只需用 , 分隔它们即可。可以在捕获子句列表中包含 = ,也可以包含显式的变量名。
[=,&factor] // lambda将按引用访问factor,并按值访问封闭作用域中的所有其他变量。
[&,factor] // 按值捕获factor,按引用捕获所有其他变量。
lambda表达式也可以包含throw() 异常说明,表明lambda不抛出异常。
transform(data.begin(),data.end(),cubes.begin(),
[&factor](double x)throw()->double {
factor += 10.0;
return factor*x*x*x;});
10.8.3 模板和lambda表达式
// 在模板中使用lambda表达式:
template <class T>
T average(const vector<T>& vec)
{
T sum(0);
for_each(vec,begin(),vec.end(),[&sum](const T& value){sum += value;});
return sum/vec.size();
}
//此模板生成的函数用来计算一个矢量中存储的一组数值的平均值。
// 使用各种lambda表达式:
#include <algorithm>
#include <iostream>
#include <iomanip>
#include <vector>
#include <ctime>
#include <cstdlib>
using namespace std;
template <class T> T average(const vector<T>& vec)
{
T sum(0);
for_each(vec.begin(),vec.end(),[&sum](const T& value){sum += value;});
return sum/vec.size();
}
template <class T> void setValues(vector<T>& vec,T start,T increment)
{
T current(start);
generate(vec.begin(),vec.end(),
[increment,¤t]()->T{T result(current);
current += increment;
return result;});
}
template <class T> void randomValues(vector<T>& vec,T min,T max)
{
srand(static_cast<unsigned int>(time(0)));
generate(vec.begin(),vec.end(),
[=](){return static_cast<T>(static_cast<double>(rand()) / RAND_MAX*(max-min)+min);});
}
template <class T> void listVector(const vector<T>& vec)
{
int count = 0;
for_each(vec.begin(),vec.end(),[&count](const T& n)->void{ cout << setw(10) << n;
if(++count % 5)
cout << " ";
else
cout << endl;});
}
int main()
{
vector<int> integerData(50);
randomValues(integerData,1,10);
cout << "Vector contains:" << endl;
listVector(integerData);
cout << "Average value is " << average(integerData) << endl;
vector<double> realData(20);
setValues(realData,5.0,2.5);
cout << "Vector contains:" << endl;
cout << "Average value is " << average(realData) << endl;
return 0;
}
10.8.4 包装lambda 表达式
- 对STL functional 头文件的扩展定义了function< > 类模板,使用此类模板可以为函数对象定义一个包装。
- function< > 模板称为多态函数包装,因为模板实例可以包装多种具有指定形参列表和返回类型的函数对象。
- 使用function< >模板包装lambda表达式实际上赋予lambda表达式一个名称。
// 创建一个能够包装一个函数对象的类型为function的对象
// 函数对象具有一个double类型的形参,并返回一个类型为int的值
function<int(double)> f = [](double x)->int{return static_cast<int> (x*x);};
// 查找一对整数值的最大公因数(HCF,highest common factor)。
// HCF是能够整除这2个整数的最大数
#include <iostream>
#include <functional>
using std::function;
using std::cout;
using std::endl;
int main()
{
function<int(int,int)> hcf = [&](int m,int n) mutable->int
{
if(m < n) return hcf(n,m);
int remainder(m%n);
if(0 == remainder) return n;
return hcf(n,remainder);
};
int a(17719),b(18879);
cout << "For numbers " << a << " and " << b
<< " the HCF is " << hcf(a,b) << endl;
return 0;
}
// lambda表达式使用欧几里得的方法查找2个整数值的最大公因数。
// 用较大的数除以较小的数,如果余数是0,则HCF就是较小的数,如果余数不是0,
// 则需继续处理,用前面较小的数除以余数,重复这一过程,直到余数为0.
10.9 C++/CLI 程序的STL
- STL/CLR库包含在cliext名称空间内,因此所有STL名称都用cliext而不是std限定。
- cliext 包括等价于标准C++的STL 每个头文件的子目录,以及容器适配器、算法和函数对象的子目录,STL/CLR子目录名在每种情况下都相同。
- 要在C++/CLI程序中使用矢量容器,需要添加#include cliext/vector 。
10.9.1 STL/CLR 容器
- STL/CLR 容器可以存储引用类型、引用类型的句柄以及无包装的值类型,但不能存储包装过的值类型。
10.9.2 使用序列容器
//Person.h
#pragma once
using namespace System;
ref class Person
{
public:
Person():firstname(L""),secondname(L""){}
Person(String^ first,String^ second):firstname(first),secondname(second){}
~Person(){}
virtual String^ ToString() override
{
return firstname + L" " + secondname;
}
private:
String^ firstname;
String^ secondname;
};
// Ex10_16.cpp: 主项目文件。
#include "stdafx.h"
#include "Person.h"
#include <cliext/vector>
using namespace System;
using namespace cliext;
int main(array<System::String ^> ^args)
{
vector<Person^>^ people = gcnew vector<Person^>(); //在CLR堆上创建矢量容器
// 模板类型实参Person^ :Person对象的句柄 。people是 vector<Person^>^ 类型的句柄
String^ first; //定义3个用作工作存储器的句柄
String^ second;
Person^ person;
while(true)
{
Console::Write(L"Enter a first name or press Enter to end:");
first = Console::ReadLine();
if(0 == first->Length) //只按下Enter键,字符串的长度将为0,因此可以测试它来决定何时退出循环
break;
Console::Write(L"Enter a second name:");
second = Console::ReadLine();
person = gcnew Person(first->Trim(),second->Trim());
// 创建一个Person对象,将句柄存储在person中
// Trim()函数删除所有打头或拖尾的空格。
people->push_back(person);
// 调用people容器的push_back()函数将person句柄存储在矢量中
}
Console::WriteLine(L"\nThe persons in the vector are:");
for each(Person^ person in people)
Console::WriteLine(L"{0}",person);
Console::ReadLine();
return 0;
}
在双端队列中存储引用类对象
//Person.h
#pragma once
using namespace System;
ref class Person
{
public:
Person():firstname(""),secondname(""){}
Person(String^ first,String^ second):firstname(first),secondname(second){}
//复制构造函数
Person(const Person% p):firstname(p.firstname),secondname(p.secondname){}
Person(Person^ p):firstname(p->firstname),secondname(p->secondname){}
~Person(){}
Person% operator= (const Person% p)
{
if(this != %p)
{
firstname = p.firstname;
secondname = p.secondname;
}
return *this;
}
bool operator < (Person^ p)
{
if(String::Compare(secondname,p->secondname) < 0 ||
(String::Compare(secondname,p->secondname)== 0 &&
String::Compare(firstname,p->firstname) < 0))
return true;
return false;
}
virtual String^ ToString() override
{
return firstname + L" " + secondname;
}
private:
String^ firstname;
String^ secondname;
};
// 在双端队列中存储引用类对象,生成输出之前对容器内容进行排序
#include "stdafx.h"
#include "Person.h"
#include <cliext/deque>
#include <cliext/algorithm>
using namespace System;
using namespace cliext;
int main(array<System::String ^> ^args)
{
deque<Person>^ people = gcnew deque<Person>();//创建双端队列容器
String^ first;
String^ second;
Person person;
while(true)
{
Console::Write(L"Enter a first name or press Enter to end:");
first = Console::ReadLine();
if(0 == first->Length) //只按下Enter键,字符串的长度将为0,因此可以测试它来决定何时退出循环
break;
Console::Write(L"Enter a second name:");
second = Console::ReadLine();
person = Person(first->Trim(),second->Trim());
people->push_back(person);
}
sort(people->begin(),people->end()); // 排序容器中的元素
Console::WriteLine(L"\nThe persons in the vector are:");
for each(Person^ p in people)
Console::WriteLine(L"{0}",p);
Console::ReadLine();
return 0;
}
在列表中存储值类型
// 使用一个座位容器的列表来存储double类型的值。
#include "stdafx.h"
#include <cliext/list>
using namespace System;
using namespace cliext;
int main(array<System::String ^> ^args)
{
array<double>^ values = {2.5,-4.5,6.5,-2.5,2.5,7.5,1.5,3.5};
list<double>^ data = gcnew list<double>(); //创建list<T>对象的句柄
for(int i = 0; i < values->Length; i++)
{
data->push_back(values[i]);
}
Console::WriteLine(L"The list contains:");
for each(double value in data)
Console::WriteLine(L"{0}",value);
Console::WriteLine();
data->sort(greater<double>()); //以升序排列
Console::WriteLine(L"\nAfter sorting the list contains:");
for each(double value in data)
Console::WriteLine(L"{0}",value);
Console::WriteLine();
Console::ReadLine();
return 0;
}
10.9.3 使用关联容器
- 存储在map<K,T>类型的映射中的元素的类型在STL/CLR映射容器中它的类型是map<K,T>::value_type,这是一个值类型。使用make_value()函数创建映射条目。
- 在map<K,T>中插入一个元素的insert()函数对于本地STL容器返回一个类型为 pair<map<K,T>::iterator,bool>的值,而对于STL/CLR map<K,T> 容器insert() 函数返回下面这个类型的对象:
map<K,T>::pair_iter_bool
//它是一个引用类型。
//map<K,T>::pair_iter_bool 对象有2个公有字段:first 和 second
//first 是下面类型迭代器的已个句柄:
map<K,T>::iterator^
// second的类型为bool。如果second的值为true,那么first指向新插入的元素,
//否则,它指向映射中已经存在的具有相同键的元素
- 用映射实现电话簿
//Person.h
#pragma once
using namespace System;
ref class Person
{
public:
Person():firstname(""),secondname(""){}
Person(String^ first,String^ second):firstname(first),secondname(second){}
~Person(){}
// operator<()成员至关重要,因为将使用Person对象作为映射中的键,并存储键/对象对,
// 映射必须能比较键。
bool operator < (Person^ p)
{
if(String::Compare(secondname,p->secondname) < 0 ||
(String::Compare(secondname,p->secondname)== 0 &&
String::Compare(firstname,p->firstname) < 0))
return true;
return false;
}
virtual String^ ToString() override
{
return firstname + L" " + secondname;
}
private:
String^ firstname;
String^ secondname;
};
// Ex10_19.cpp: 主项目文件。
#include "stdafx.h"
#include "Person.h"
#include <cliext/map>
using namespace System;
using namespace cliext;
Person^ getPerson() // 获得新Person对象的句柄
{
String^ first;
String^ second;
Console::Write(L"Enter a first name: ");
first = Console::ReadLine();
Console::Write(L"Enter a second name: ");
second = Console::ReadLine();
return gcnew Person(first->Trim(),second->Trim());
}
void addEntry(map<Person^,String^>^book) //向映射中添加新条目
{
map<Person^,String^>::value_type entry; //定义存储新映射条目的变量
String^ number;
Person^ person = getPerson(); //获得新Person对象的句柄
Console::Write(L"Enter the phone number for {0}: ",person);
number = Console::ReadLine()->Trim();
entry = book->make_value(person,number);// 用person和number作为实参创建新的映射条目
map<Person^,String^>::pair_iter_bool pr = book->insert(entry); // 插入新的条目
if(pr.second) //pr的second字段是一个指示插入操作是否成功的bool值
Console::WriteLine(L"Entry successful.");
else
Console::WriteLine(L"Entry exists for {0}.the number is {1}",person,pr.first->second);
}
void listEntries(map<Person^,String^>^ book)
{
if(book->empty())
{
Console::WriteLine(L"The phone book is empty.");
return;
}
map<Person^,String^>::iterator iter;
for(iter = book->begin(); iter != book->end();iter++)
Console::WriteLine(L"{0,-30}{1,-12}",iter->first,iter->second);
}
void getEntry(map<Person^,String^>^ book)
{
Person^ person = getPerson();
map<Person^,String^>::const_iterator iter = book->find(person);
if(book->end() == iter)
Console::WriteLine(L"No entry found for {0}",person);
else
Console::WriteLine(L"The number for {0} is {1}",person,iter->second);
}
void deleteEntry(map<Person^,String^>^ book)
{
Person^ person = getPerson();
map<Person^,String^>::iterator iter = book->find(person);
if(book->end() == iter)
Console::WriteLine(L"No entry found for {0}",person);
else
{
book->erase(iter);
Console::WriteLine(L"{0} erased.",person);
}
}
int main(array<System::String ^> ^args)
{
map<Person^,String^>^ phonebook = gcnew map<Person^,String^>();
//定义映射对象,映射存储一个对象,将Person对象的句柄合并为键,将String对象的句柄合并为关联对象。
String^ answer; // 定义在何处存储输入响应
while(true)
{
Console::Write(L"Do you want to enter a phonebook entry(Y or N): ");
answer = Console::ReadLine()->Trim(); //获得对第1个提示符的响应
if(Char::ToUpper(answer[0])== L'N') // 检查作为输入响应的L‘n’或 L‘N’,ToUpper()将answer中的第1个字符转换为大写。
break;
addEntry(phonebook);
}
while(true)
{
Console::WriteLine(L"\nChoose from the following options: ");
Console::WriteLine(L"A Add an entry D Delete an entry G get an entry");
Console::WriteLine(L"L list entries Q Quit");
while(true)
{
answer = Console::ReadLine()->Trim();
if(answer->Length)
break;
Console::WriteLine(L"\nYou must enter something-try again.");
}
switch(Char::ToUpper(answer[0]))
{
case L'A':
addEntry(phonebook);
case L'G':
getEntry(phonebook);
case L'D':
deleteEntry(phonebook);
case L'L':
listEntries(phonebook);
case L'Q':
return 0;
default:
Console::WriteLine(L"Invalid selection.Try again.");
break;
}
}
//Console::ReadLine();
return 0;
}
本章主要内容
To be continue…