本文为《C++ Primer》的读书笔记
顺序容器概述
所有顺序容器都提供了快速顺序访问元素的能力。但是, 这些容器在以下方面都有不同的性能折中:
- 向容器添加或从容器中删除元素的代价
- 非顺序访问容器中元素的代价
容器保存元素的策略对容器操作的效率有着重大的影响:
例如,string
和vector
将元素保存在连续的内存空间中,由元素的下标来计算其地址是非常快速的。但是, 在这两种容器的中间位置添加或删除元素就会非常耗时: 在一次插入或删除操作后, 需要移动插入/删除位置之后的所有元素,来保持连续存储。而且, 添加一个元素有时可能还需要分配额外的存储空间。在这种情况下, 每个元素都必须移动到新的存储空间中
list
和forward_list
两个容器的设计目的是令容器任何位置的添加和删除操作都很快速。作为代价, 这两个容器不支持元素的随机访问。而且, 与vector
、deque
和array
相比, 这两个容器的额外内存开销也很大
deque
是一个更为复杂的数据结构。与string
和vector
类似, deque
支持快速的随机访问。与string
和vector
一样,在deque
的中间位置添加或删除元素的代价(可能)很高。但是,在deque
的两端添加或删除元素都是很快的
forward_list
和array
是新C++标准增加的类型
- 与内置数组相比,
array
是一种更安全、更容易使用的数组类型。与内置数组类似,array
对象的大小是固定的 forward_list
的设计目标是达到与最好的手写的单向链表数据结构相当的性能。因此,forward_list
没有size
操作,因为保存或计算其大小就会比手写链表多出额外的开销。对其他容器而言,size
保证是一个快速的常量时间的操作
新标准库容器的性能几乎肯定与最精心优化过的同类数据结构一样好(通常会更好)。现代C++程序应该使用标准库容器, 而不是更原始的数据结构,如内置数组。
确定使用哪种顺序容器
通常,使用
vector
是最好的选择,除非你有很好的理由选择其他容器
以下是一些选择容器的基本原则:
- 除非你有很好的理由选择其他容器,否则应使用
vector
- 如果你的程序有很多小的元素,且空间的额外开销很重要,则不要使用
list
或forward_list
- 如果程序要求随机访问元素,应使用
vector
或deque
- 如果程序要求在容器的中间插入或删除元素,应使用
list
或forward_list
- 如果程序要求在头尾位置插入或删除元素,但不会在中间位置进行插入或删除操作,则使用
deque
- 如果程序只有在读取输入时才需要在容器中间位置插入元素,随后需要随机访问元素,则
首先,确定是否真的需要在容器中间位置添加元素。当处理输入数据时,通常可以很容易地向vector
追加数据,然后再调用标准库的sort
函数来重排容器中的元素,从而避免在中间位置添加元素
如果必须在中间位置插入元素,考虑在输入阶段使用list
, 一旦输入完成,将list
中的内容拷贝到一个vector
中
如果程序既需要随机访问元素,又需要在容器中间位置插入元素,那该怎么办? 答案取决于在list
或forward_list
中访问元素与vector
或deque
中插入/删除元素的相对性能
顺序容器操作
向顺序容器 (非array
) 添加元素
向一个
vector
、string
或deque
插入元素会使所有指向容器的迭代器、引用和指针失效
当我们使用这些操作时, 必须记得不同容器使用不同的策略来分配元素空间, 而这些策略直接影响性能:
- 在一个
vector
或string
的尾部之外的任何位置,或是一个deque
的首尾之外的任何位置添加元素, 都需要移动元素。而且, 向一个vector
或string
添加元素可能引起整个对象存储空间的重新分配
使用 push_back
除array
和forward_list
之外, 每个顺序容器(包括string
类型)都支持push_back
关键概念:容器元素是拷贝
当我们用一个对象来初始化容器时,或将一个对象插入到容器中时, 实际上放入到容器中的是对象值的一个拷贝, 而不是对象本身
使用 push_front
list
、forward_list
和deque
容器还支持名为push_front
的类似操作。此操作将元素插入到容器头部
在循环中以这种方式将元素添加到容器中, 最终会形成逆序
在容器中的特定位置添加元素
insert
允许我们在容器中任意位置插入0个或多个元素。vector
、deque
、list
和string
都支持insert
成员
forward_list
提供了特殊版本的insert
成员
每个insert
函数都接受一个迭代器作为其第一个参数,将元素插入到迭代器所指定的位置之前。迭代器指出了在容器中什么位置放置新元素。它可以指向容器中任何位置, 包括容器尾部之后的下一个位置
虽然某些容器不支持push_front
操作, 但它们对于insert
操作并无类似的限制(插入开始位置)(合法但是很耗时)
vector<string> svec;
list<string> slist;
// 等价于调用slist.push_front("Hello!");
slist.insert(slist.begin(), "Hello!");
// vector 不支持push_front, 但我们可以插入到begin()之前
// 警告: 插入到vector末尾之外的任何位置都可能很慢
svec.insert(svec.begin(), "Hello!");
插入范围内元素
除了第一个迭代器参数之外, insert
函数还可以接受更多的参数。其中一个版本接受一个元素数目和一个值, 它将指定数量的元素添加到指定位置之前, 这些元素都按给定值初始化:
// 将10个元素插入到svec 的末尾,并将所有元素都初始化为string "Anna"
svec.insert(svec.end(), 10, "Anna");
接受一对迭代器或一个初始化列表的insert
版本将给定范围中的元素插入到指定位置之前 (迭代器不能指向与目的位置相同的容器):
vector<string> v = {"quasi", "simba", "frollo", "scar"};
// 将v的最后两个元素添加到slist的开始位置
slist.insert(slist.begin(), v.end() - 2, v.end());
slist.insert(slist.end(), {"these", "words", "will",
"go", "at", "the", "end"});
在新标准下,接受元素个数或范围的insert
版本返回指向第一个新加入元素的迭代器(在旧版本的标准库中,这些操作返回void
)如果范围为空,不插入任何元素,insert
操作会将第一个参数返回
使用insert
的返回值
通过使用insert
的返回值,可以在容器中一个特定位置反复插入元素:
list<string> lst;
auto iter = lst.begin();
while (cin >> word)
iter = lst.insert(iter, word); //等价于调用push_front
练习9.22:
假定iv
是一个int
的vector
, 下面的程序存在什么错误?你将如何修改?
vector<int>::iterator iter = iv.begin(),
mid = iv.begin() + iv.size()/2;
while (iter != mid)
if (*iter == some_val)
iv.insert(iter, 2 * some val);
解答:
向容器插入元素会使现有指向容器的迭代器、引用和指针失效。因此应作如下修改:
首先将insert
返回的迭代器赋予iter
,该迭代器指向新插入的元素y
,因此,需要进行两次iter++
将iter
推进到x
之后的位置。
其次,insert
也会使mid
失效,因此还需要设置mid
使之指向iv
原来的中央元素。先用org_size
记录最初的容器大小,然后在循环过程中统计新插入的元素个数new_ele
,则iv.begin()+org_size/2+new_ele
指向iv
原来的中央元素
使用emplace
操作
新标准引入了三个新成员一一emplace_front
、emplace
和emplace_back
, 这些操作构造而不是拷贝元素。这些操作分别对应push_front
、insert
和push_back
,允许我们将元素放置在容器头部、一个指定位置之前或容器尾部
当我们调用一个emplace
成员函数时,是将参数传递给元素类型的构造函数。emplace
成员使用这些参数在容器管理的内存空间中直接构造元素。例如,假定c
保存Sales_data
元素:
// 在c的末尾构造一个Sales_data对象
// 使用三个参数的Sales—data构造函数
c.emplace_back("978-0590353403", 25, 15.99);
// 错误: 没有接受三个参数的push_back版本
c.push_back("978-0590353403", 25, 15.99);
// 正确: 创这一个临时的Sales_data对象传递给push_back
c.push_back(Sales_data("978-0590353403", 25, 15.99));
// iter指向c中一个元素, 其中保存了Sales_data 元素
c.emplace_back(); // 使用Sales_data的默认构造函数
c.emplace(iter, "999-999999999"); //使用Sales_ data(string)
// 使用Sales_data的接受一个ISBN、一个count和一个price的构造函数
c.emplace_front("978-0590353403", 25, 15.99);
访问元素
包括array
在内的每个顺序容器都有一个front
成员函数, 而除forward_list
之外的所有顺序容器都有一个back
成员函数。这两个操作分别返回首元素和尾元素的引用:
// 在解引用一个迭代器或调用front或back之前检查是否有元素
if (!c.empty()) {
// val和val2是c中第一个元素值的拷贝
auto val = *c.begin(), val2 = c.front();
// val3和val4是c中最后一个元素值的拷贝
auto last = c.end();
auto val3 = *(--last); //不能递减forward list 迭代器
auto val4 = c.back(); // forward_list 不支持
}
在调用
front
和back
(或解引用begin
和end
返回的迭代器) 之前, 要确保c
非空。如果容器为空,if
中操作的行为将是未定义的。
访问成员函数返回的是引用
在容器中访问元素的成员函数(即, front
、back
、下标和at
)返回的都是引用。如果容器是一个const
对象, 则返回值是const
的引用
if (!c.empty()) {
c.front() = 42; // 将42赋予c中的第一个元素
auto &v = c.back(); // 获得指向最后一个元素的引用
v = 1024; // 改变c中的元素
auto v2 = c.back(); // v2不是一个引用, 它是c.back()的一个拷贝
v2 = 0; // 未改变c中的元素
}
下标操作 和 at
成员函数
提供快速随机访问的容器(string
、vector
、deque
和array
)也都提供下标运算符
保证下标有效是程序员的责任, 下标运算符并不检查下标是否在合法范围内。使用越界的下标是一种严重的程序设计错误, 而且编译器并不检查这种错误
如果我们希望确保下标是合法的, 可以使用at
成员函数。at
成员函数类似下标运算符, 但如果下标越界, at
会抛出一个out_of_range
异常:
删除元素
删除
deque
中除首尾位置之外的任何元素都会使所有迭代器、引用和指针失效。指向vector
或string
中删除点之后位置的迭代器、引用和指针都会失效
删除元素的成员函数并不检查其参数。在删除元素之前, 程序员必须确保它(们)是存在的
不能对一个空容器执行弹出操作
pop_front
和pop_back
成员函数
与vector
和string
不支持push_front
—样,这些类型也不支待pop_front
。类似的,forward_list
不支持pop_back
如果你需要弹出的元素的值, 就必须在执行弹出操作之前保存它:
while (!ilist.empty()) {
process(ilist.front());
list.pop_front();
}
从容器内部删除一个元素
成员函数erase
从容器中指定位置删除元素。我们可以删除由一个迭代器指定的单个元素, 也可以删除由一对迭代器指定的范围内的所有元素。两种形式的erase
都返回指向删除的(最后一个)元素之后位置的迭代器
例如, 下面的循环删除一个list
中的所有奇数元素:
list<int> lst = {0,1,2,3,4,5,6,7,8,9);
auto it = lst.begin() ;
while (it != lst.end())
if (*it % 2) //若元素为奇数
it = lst.erase(it); //删除此元素
else
++it;
删除多个元素
接受一对迭代器的erase
版本允许我们删除一个范围内的元素:
// 删除两个迭代器表示的范围内的元素
// 返回指向最后一个被删元素之后位置的迭代器
elem1 = slist.erase(elem1, elem2); //调用后,elem1 == elem2
迭代器elern1
指向我们要删除的第一个元素, elem2
指向我们要删除的最后一个元素之后的位置
为了删除一个容器中的所有元素, 我们既可以调用clear
, 也可以用begin
和end
获得的迭代器作为参数调用erase
:
slist.clear();
slist.erase(slist.begin(), slist.end());
特殊的forward_list
操作
为了理解forward_list
为什么有特殊版本的添加和删除操作, 考虑当我们从一个单向链表中删除一个元素时会发生什么。如图9.1所示, 删除一个元素会改变序列中的链接
在此情况下, 删除elem3
会改变elem2
为了添加或删除一个元素, 我们需要访问其前驱, 以便改变前驱的链接。但是,单向链表中没有简单的方法来获取一个元素的前驱。因此, 在forward_list
中添加或删除元素的操作是通过改变给定元素之后的元素来完成的。
由于这些操作与其他容器上的操作的实现方式不同, forward_list
并未定义insert
、emplace
和erase
, 而是定义了名为insert_after
、emplace_after
和erase_after
forward_list
也定义了before_begin
, 它返回一个首前(off-the-beginning)迭代器。这个迭代器允许我们在链表首元素之前并不存在的元素” 之后“ 添加或删除元素(亦即在链表首元素之前添加删除元素)
当在forward_list
中添加或删除元素时, 我们必须关注两个迭代器:一个指向我们要处理的元素, 另一个指向其前驱
forward_list<int> flst = {0,1,2,3,4,5,6,7,8,9};
auto prev = flst.before_begin();
auto curr = flst.begin();
while (curr != flst.end()) {
if (*curr % 2) //若元素为奇数
curr = flst.erase_after(prev); //删除它并移动curr
else {
prev = curr;
++curr;
}
}
改变容器大小
如表9.9所描述, 我们可以用resize
来增大或缩小容器, 与往常一样, array
不支持resize
如果
resize
缩小容器,则指向被删除元素的迭代器、引用和指针都会失效;对vector
、string
或deque
进行resize
可能导致迭代器、指针和引用失效
如果当前大小大于所要求的大小, 容器后部的元素会被删除; 如果当前大小小于新大小, 会将新元素添加到容器后部
resize
操作接受一个可选的元素值参数, 用来初始化添加到容器中的元素。如果调用者未提供此参数, 新元素进行值初始化。如果容器保存的是类类型元素, 且resize
向容器添加新元素, 则我们必须提供初始值, 或者元素类型必须提供一个默认构造函数
容器操作可能使迭代器失效
向容器中添加元素和从容器中删除元素的操作可能会使指向容器元素的指针、引用或迭代器失效
在向容器添加元素后:
- 如果容器是
vector
或string
, 且存储空间被重新分配,则指向容器的迭代器、指针和引用都会失效。如果存储空间未重新分配,指向插入位置之前的元素的迭代器、指针和引用仍有效,但指向插入位置之后元素的迭代器、指针和引用将会失效 - 对于
deque
, 插入到除首尾位置之外的任何位置都会导致迭代器、指针和引用失效。如果在首尾位置添加元素,迭代器会失效,但指向存在的元素的引用和指针不会失效 - 对于
list
和forward_list
, 指向容器的迭代器(包括尾后迭代器和首前迭代器)、指针和引用仍有效
当我们从一个容器中删除元素后,指向被删除元素的迭代器、指针和引用会失效。当我们删除一个元素后:
- 对于
list
和forward_list
, 指向容器其他位置的迭代器(包括尾后迭代器和首前迭代器)、引用和指针仍有效 - 对于
deque
, 如果在首尾之外的任何位置删除元素,那么指向被删除元素外其他元素的迭代器、引用或指针也会失效。如果是删除deque
的尾元素,则尾后迭代器也会失效,但其他迭代器、引用和指针不受影响;如果是删除首元素,这些也不会受影响 - 对于
vector
和string
, 指向被删元素之前元素的迭代器、引用和指针仍有效。注意:当我们删除元素时,尾后迭代器总是会失效
建议:管理迭代器
当你使用迭代器(或指向容器元素的引用或指针)时,最小化要求迭代器必须保持有效的程序片段是一个好的方法。
由于向迭代器添加元素和从迭代器删除元素的代码可能会使迭代器失效,因此必须保证每次改变容器的操作之后都正确地重新定位迭代器。这个建议对vector
、string
和deque
尤为重要。
编写改变容器的循环程序
添加 / 删除vector
、string
或deque
元素的循环程序必须考虑迭代器、引用和指针可能失效的问题。程序必须保证每个循环步中都更新迭代器、引用或指针。如果循环中调用的是insert
或erase
, 那么更新迭代器很容易。这些操作都返回迭代器,我们可以用来更新:
// 傻瓜循环,删除偶数元素,复制每个奇数元素
vector<int> vi= {0,1,2,3,4,5,6,7,8,9);
auto iter = vi.begin(); //调用begin而不是cbegin,因为我们要改变vi
while (iter != vi.end()) {
if (*iter % 2) {
iter = vi.insert(iter, *iter); //复制当前元素
iter += 2; //向前移动迭代器, 跳过当前元素以及插入到它之前的元素
//注意:list 和 forward_list 不支持加减运算,只能使用++/--
} else
iter = vi.erase(iter); //删除偶数元素
// 不应向前移动迭代器,iter指向我们删除的元素之后的元素
}
不要保存end
返回的迭代器
当我们添加/删除vector
或string
的元素后,或在deque
中首元素之外任何位置添加/删除元素后,原来end
返回的迭代器总是会失效。因此,添加或删除元素的循环程序必须反复调用end
, 而不能在循环之前保存end
返回的迭代器,一直当作容器末尾使用。通常标准库的实现中end()
操作都很快,部分就是因为这个原因
例如,考虑这样一个循环,它处理容器中的每个元素,在其后添加一个新元素。我们希望循环能跳过新添加的元素,只处理原有元素。在每步循环之后,我们将定位迭代器,使其指向下一个原有元素。如果我们试图” 优化” 这个循环,在循环之前保存end()
返回的迭代器,一直用作容器末尾,就会导致一场灾难:
// 灾难:此循环的行为是未定义的
auto begin = v.begin(),
end = v.end(); //保存尾迭代器的值是一个坏主意
while (begin != end) {
// 做一些处理
// 插入新值,对begin重新赋值,否则的话它就会失效
++begin; //向前移动begin,因力我们想在此元素之后插入元素
begin = v.insert(begin, 42); //插入新值
++begin; //向前移动begin跳过我们刚刚加入的元素
}
此代码的行为是未定义的。在很多标准库实现上,此代码会导致无限循环
标准库 array
当定义一个array
时,除了指定元素类型,还要指定容器大小:
array<int, 42> //类型为:保存42个int的数组
array<string, 10> //类型为:保存10个string的数组
array<int, 10>::size_type i;
大小是
array
类型的一部分
array
大小固定的特性也影响了它所定义的构造函数的行为:
- 与其他容器不同,一个默认构造的
array
是非空的:它包含了与其大小一样多的元素。这些元素都被默认初始化,就像一个内置数组中的元素那样 - 如果我们对
array
进行列表初始化,初始值的数目必须 <=array
的大小。如果初始值数目小于array
的大小,则它们被用来初始化array
中靠前的元素,所有剩余元素都会进行值初始化
在这两种情况下,如果元素类型是一个类类型,那么该类必须有一个默认构造函数,以使值初始化能够进行:
值得注意的是,虽然我们不能对内置数组类型进行拷贝或对象赋值操作,但array
并无此限制:
intdigs[10] = {0,1,2,3,4,5,6,7,8,9};
int cpy[10] = digs; //错误: 内置数组不支持拷贝或赋值
array<int, 10> digits = {0,1,2,3,4,5,6,7,8,9};
array<int, 10> copy = digits; //正确: 只要数组类型匹配即合法
vector
对象是如何增长的
为了支持快速随机访间,vector
将元素连续存储
假定容器中元素是连续存储的,且容器的大小是可变的,考虑向vector
或string
中添加元素会发生什么:如果没有空间容纳新元素,则必须分配新的内存空间,将已有元素从旧位置移动到新空间中,然后添加新元素,释放旧存储空间。如果我们每添加一个新元素,vector
就执行一次这样的内存分配和释放操作,性能会慢到不可接受。为了避免这种代价,标准库实现者采用了可以减少容器空间重新分配次数的策略。当不得不获取新的内存空间时,vector
和string
的实现通常会分配比新的空间需求更大的内存空间,其实际性能也表现得足够好: 虽然vector
在每次重新分配内存空间时都要移动所有元素,但使用此策略后, 其扩张操作通常比list
和deque
还要快
管理容量的成员函数
如表9. 10所示,vector
和string
类型提供了一些成员函数,允许我们与它的实现中内存分配部分互动
capacity
操作告诉我们容器在不扩张内存空间的情况下可以容纳多少个元素reserve
操作允许我们通知容器它应该准备保存多少个元素
只有当需要的内存空间超过当前容量时,reserve
调用才会改变vector
的容量。如果需求大小大于当前容量,reserve
至少分配与需求一样大的内存空间(可能更大)。如果需求大小小于或等于当前容量,reserve
什么也不做。特别是,当需求大小小于当前容量时,容器不会退回内存空间- 在新标准库中,我们可以调用
shrink_to_fit
来要求deque
、vector
或string
退回不需要的内存空间。但是,具体的实现可以选择忽略此请求。也就是说,调用shrink_to_fit
也并不保证一定退回内存空间
为什么
list
或array
没有capacity
成员函数?
list
是链表,插入或删除元素时总是立刻分配或释放内存空间,因此capacity
和size
总是相等;而array
大小固定。因此它们都不需要capacity
成员函数
capacity
和size
容器的size是指它已经保存的元素的数目;而capacity
则是在不分配新的内存空间的前提下它最多可以保存多少元素
容器适配器
- 除了顺序容器外, 标准库还定义了三个顺序容器适配器:
stack
、queue
和priority_queue
- 适配器 (adaptor) 是标准库中的一个通用概念。容器、迭代器和函数都有适配器。本质上, 一个适配器是一种机制, 能使某种事物的行为看起来像另外一种事物。一个容器适配器接受一种已有的容器类型, 使其行为看起来像一种不同的类型
- 例如,
stack
适配器接受一个顺序容器(除array
或forward_list
外), 并使其操作起来像一个stack
一样
- 例如,
- 默认情况下,
stack
和queue
是基于deque
实现的,priority_queue
是在vector
之上实现的
- 适配器 (adaptor) 是标准库中的一个通用概念。容器、迭代器和函数都有适配器。本质上, 一个适配器是一种机制, 能使某种事物的行为看起来像另外一种事物。一个容器适配器接受一种已有的容器类型, 使其行为看起来像一种不同的类型
定义一个适配器
每个适配器都定义两个构造函数:
- 默认构造函数创建一个空对象
- 接受一个容器的构造函数拷贝该容器来初始化适配器
- 例如, 假定
deq
是一个deque<int>
, 我们可以用deq
来初始化一个新的stack
, 如下所示:
stack<int> stk(deq); // 从 deq 拷贝元素到 stk
- 我们可以在创建一个适配器时将一个命名的顺序容器作为第二个类型参数,来重载默认容器类型
// 在vector上实现的空栈
stack<string, vector<string>> str_stk;
// str_stk2在vector上实现, 初始化时保存svec的拷贝
stack<string, vector<string>> str_stk2(svec);
- 对于一个给定的适配器, 可以使用哪些容器是有限制的。所有适配器都要求容器具有添加和删除元素的能力。因此, 适配器不能构造在
array
之上。类似的, 我们也不能用forward_list
来构造适配器, 因为所有适配器都要求容器具有添加、删除以及访问尾元素的能力stack
只要求push_back
、pop_back
和back
操作,因此可以使用除array
和forward_list
之外的任何容器类型来构造stack
queue
适配器要求back
、push_back
、front
和push_front
, 因此它可以构造于list
或deque
之上, 但不能基于vector
构造priority_queue
除了front
、push_back
和pop_back
操作之外还要求随机访问能力, 因此它可以构造于vector
或deque
之上, 但不能基于list
构造
栈适配器 (stack
)
#include <stack>
- 每个容器适配器都基于底层容器类型的操作定义了自己的特殊操作。我们只可以使用适配器操作, 而不能使用底层容器类型的操作
- 例如,当想清空
stack
时,不能用clear
,而是需要用循环将元素一个一个pop
出来;当然更简单的方法是重新定义一个stack
- 例如,下面的语句试图在
intStack
的底层deque
对象上调用push_back
。虽然stack
是基于deque
实现的,但我们不能直接在一个stack
上调用push_back
,而必须使用push
- 例如,当想清空
intStack.push(ix);
队列适配器 (queue
, priority_queue
)
#include <queue>
上表中有错:
queue
不能用vector
实现;pop
删除元素且不返回该元素值
priority_queue
可以由容器进行初始化:
priority_queue<int> max_heap(v.begin(), v.end());
priority_queue
默认为最大堆;在元素类型上使用<
运算符来确定相对优先级;因此下面两种定义是等价的:
priority_queue<int> q;
priority_queue<int, vector<int>, less<int>> q;
- 因此,如果想使用最小堆,只需这么定义:
priority_queue<int, vector<int>, greater<int>> q;
- 如果想用
priority_queue
存储一个类,则可以重载operator <
struct fruit {
string name;
int price;
friend bool operator < (fruit f1, fruit f2) {
return f1.price > f2.price; // 实际想要实现的是最小堆的效果
}
}f1, f2, f3;
int main() {
priority_queue<fruit> q;
f1.name = "桃子";
f1.price = 3;
f2.name = "梨子";
f2.price = 4;
f3.name = "苹果";
f3.price = 1;
q.push(f1);
q.push(f2);
q.push(f3);
// output: 苹果 1
cout << q.top().name << " " << q.top().price << endl;
return 0;
}
- 也可以这么实现:
struct cmp {
bool operator() (fruit f1, fruit f2) {
return f1.price > f2.price;
}
};
priority_queue<fruit, vector<fruit>, cmp> q;
- 或者这样:
struct fruit {
string name;
int price;
friend bool operator > (fruit f1, fruit f2) {
return f1.price > f2.price;
}
}f1, f2, f3;
int main() {
priority_queue<fruit, vector<fruit>, greater<fruit>> q;
...
return 0;
}