C++标准库(二):顺序容器

本文为《C++ Primer》的读书笔记

顺序容器概述

所有顺序容器都提供了快速顺序访问元素的能力。但是, 这些容器在以下方面都有不同的性能折中:

  • 向容器添加或从容器中删除元素的代价
  • 非顺序访问容器中元素的代价

在这里插入图片描述


容器保存元素的策略对容器操作的效率有着重大的影响:

例如,stringvector将元素保存在连续的内存空间中,由元素的下标来计算其地址是非常快速的。但是, 在这两种容器的中间位置添加或删除元素就会非常耗时: 在一次插入或删除操作后, 需要移动插入/删除位置之后的所有元素,来保持连续存储。而且, 添加一个元素有时可能还需要分配额外的存储空间。在这种情况下, 每个元素都必须移动到新的存储空间中

listforward_list两个容器的设计目的是令容器任何位置的添加和删除操作都很快速。作为代价, 这两个容器不支持元素的随机访问。而且, 与vectordequearray相比, 这两个容器的额外内存开销也很大

deque是一个更为复杂的数据结构。与stringvector 类似, deque支持快速的随机访问。与stringvector 一样,在deque的中间位置添加或删除元素的代价(可能)很高。但是,在deque 的两端添加或删除元素都是很快的


forward_listarray是新C++标准增加的类型

  • 与内置数组相比,array是一种更安全、更容易使用的数组类型。与内置数组类似,array对象的大小是固定的
  • forward_list的设计目标是达到与最好的手写的单向链表数据结构相当的性能。因此,forward_list没有size操作,因为保存或计算其大小就会比手写链表多出额外的开销。对其他容器而言,size保证是一个快速的常量时间的操作

新标准库容器的性能几乎肯定与最精心优化过的同类数据结构一样好(通常会更好)。现代C++程序应该使用标准库容器, 而不是更原始的数据结构,如内置数组。

确定使用哪种顺序容器

通常,使用vector是最好的选择,除非你有很好的理由选择其他容器

以下是一些选择容器的基本原则:

  • 除非你有很好的理由选择其他容器,否则应使用vector
  • 如果你的程序有很多小的元素,且空间的额外开销很重要,则不要使用listforward_list
  • 如果程序要求随机访问元素,应使用vectordeque
  • 如果程序要求在容器的中间插入或删除元素,应使用listforward_list
  • 如果程序要求在头尾位置插入或删除元素,但不会在中间位置进行插入或删除操作,则使用deque
  • 如果程序只有在读取输入时才需要在容器中间位置插入元素,随后需要随机访问元素,则
    首先,确定是否真的需要在容器中间位置添加元素。当处理输入数据时,通常可以很容易地向vector追加数据,然后再调用标准库的sort函数来重排容器中的元素,从而避免在中间位置添加元素
    如果必须在中间位置插入元素,考虑在输入阶段使用list, 一旦输入完成,将list中的内容拷贝到一个vector

如果程序既需要随机访问元素,又需要在容器中间位置插入元素,那该怎么办? 答案取决于在listforward_list中访问元素与vectordeque中插入/删除元素的相对性能

顺序容器操作

向顺序容器 (非array) 添加元素

在这里插入图片描述

向一个vectorstringdeque插入元素会使所有指向容器的迭代器、引用和指针失效

当我们使用这些操作时, 必须记得不同容器使用不同的策略来分配元素空间, 而这些策略直接影响性能

  • 在一个vectorstring的尾部之外的任何位置,或是一个deque的首尾之外的任何位置添加元素, 都需要移动元素。而且, 向一个vectorstring添加元素可能引起整个对象存储空间的重新分配

使用 push_back

arrayforward_list之外, 每个顺序容器(包括string类型)都支持push_back

关键概念:容器元素是拷贝
当我们用一个对象来初始化容器时,或将一个对象插入到容器中时, 实际上放入到容器中的是对象值的一个拷贝, 而不是对象本身

使用 push_front

listforward_listdeque容器还支持名为push_front的类似操作。此操作将元素插入到容器头部

在循环中以这种方式将元素添加到容器中, 最终会形成逆序

在容器中的特定位置添加元素

insert允许我们在容器中任意位置插入0个或多个元素。vectordequeliststring都支持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是一个intvector, 下面的程序存在什么错误?你将如何修改?

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_frontemplaceemplace_back, 这些操作构造而不是拷贝元素。这些操作分别对应push_frontinsertpush_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 不支持
}

在调用frontback (或解引用beginend返回的迭代器) 之前, 要确保c非空。如果容器为空, if中操作的行为将是未定义的。

访问成员函数返回的是引用

在容器中访问元素的成员函数(即, frontback、下标和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成员函数

提供快速随机访问的容器(stringvectordequearray)也都提供下标运算符

保证下标有效是程序员的责任, 下标运算符并不检查下标是否在合法范围内。使用越界的下标是一种严重的程序设计错误, 而且编译器并不检查这种错误

如果我们希望确保下标是合法的, 可以使用at成员函数。at成员函数类似下标运算符, 但如果下标越界, at会抛出一个out_of_range异常:

删除元素

在这里插入图片描述

删除deque中除首尾位置之外的任何元素都会使所有迭代器、引用和指针失效。指向vectorstring中删除点之后位置的迭代器、引用和指针都会失效

删除元素的成员函数并不检查其参数。在删除元素之前, 程序员必须确保它(们)是存在的
不能对一个空容器执行弹出操作

pop_frontpop_back 成员函数

vectorstring不支持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, 也可以用beginend获得的迭代器作为参数调用erase:

slist.clear(); 
slist.erase(slist.begin(), slist.end()); 

特殊的forward_list操作

为了理解forward_list为什么有特殊版本的添加和删除操作, 考虑当我们从一个单向链表中删除一个元素时会发生什么。如图9.1所示, 删除一个元素会改变序列中的链接

在这里插入图片描述
在此情况下, 删除elem3 会改变elem2

为了添加或删除一个元素, 我们需要访问其前驱, 以便改变前驱的链接。但是,单向链表中没有简单的方法来获取一个元素的前驱。因此, 在forward_list中添加或删除元素的操作是通过改变给定元素之后的元素来完成的。

由于这些操作与其他容器上的操作的实现方式不同, forward_list并未定义insertemplaceerase, 而是定义了名为insert_afteremplace_aftererase_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缩小容器,则指向被删除元素的迭代器、引用和指针都会失效;对vectorstringdeque进行resize可能导致迭代器、指针和引用失效

如果当前大小大于所要求的大小, 容器后部的元素会被删除; 如果当前大小小于新大小, 会将新元素添加到容器后部

resize操作接受一个可选的元素值参数, 用来初始化添加到容器中的元素。如果调用者未提供此参数, 新元素进行值初始化。如果容器保存的是类类型元素, 且resize向容器添加新元素, 则我们必须提供初始值, 或者元素类型必须提供一个默认构造函数

容器操作可能使迭代器失效

向容器中添加元素和从容器中删除元素的操作可能会使指向容器元素的指针、引用或迭代器失效

在向容器添加元素后:

  • 如果容器是vectorstring, 且存储空间被重新分配,则指向容器的迭代器、指针和引用都会失效。如果存储空间未重新分配,指向插入位置之前的元素的迭代器、指针和引用仍有效,但指向插入位置之后元素的迭代器、指针和引用将会失效
  • 对于deque, 插入到除首尾位置之外的任何位置都会导致迭代器、指针和引用失效。如果在首尾位置添加元素,迭代器会失效,但指向存在的元素的引用和指针不会失效
  • 对于listforward_list , 指向容器的迭代器(包括尾后迭代器和首前迭代器)、指针和引用仍有效

当我们从一个容器中删除元素后,指向被删除元素的迭代器、指针和引用会失效。当我们删除一个元素后:

  • 对于listforward_list, 指向容器其他位置的迭代器(包括尾后迭代器和首前迭代器)、引用和指针仍有效
  • 对于deque, 如果在首尾之外的任何位置删除元素,那么指向被删除元素外其他元素的迭代器、引用或指针也会失效。如果是删除deque的尾元素,则尾后迭代器也会失效,但其他迭代器、引用和指针不受影响;如果是删除首元素,这些也不会受影响
  • 对于vectorstring, 指向被删元素之前元素的迭代器、引用和指针仍有效。注意:当我们删除元素时,尾后迭代器总是会失效

建议:管理迭代器
当你使用迭代器(或指向容器元素的引用或指针)时,最小化要求迭代器必须保持有效的程序片段是一个好的方法。
由于向迭代器添加元素和从迭代器删除元素的代码可能会使迭代器失效,因此必须保证每次改变容器的操作之后都正确地重新定位迭代器。这个建议对vectorstringdeque尤为重要。

编写改变容器的循环程序

添加 / 删除vectorstringdeque元素的循环程序必须考虑迭代器、引用和指针可能失效的问题。程序必须保证每个循环步中都更新迭代器、引用或指针。如果循环中调用的是inserterase, 那么更新迭代器很容易。这些操作都返回迭代器,我们可以用来更新:

// 傻瓜循环,删除偶数元素,复制每个奇数元素
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 返回的迭代器

当我们添加/删除vectorstring的元素后,或在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 将元素连续存储

假定容器中元素是连续存储的,且容器的大小是可变的,考虑向vectorstring中添加元素会发生什么:如果没有空间容纳新元素,则必须分配新的内存空间,将已有元素从旧位置移动到新空间中,然后添加新元素,释放旧存储空间。如果我们每添加一个新元素,vector就执行一次这样的内存分配和释放操作,性能会慢到不可接受。为了避免这种代价,标准库实现者采用了可以减少容器空间重新分配次数的策略。当不得不获取新的内存空间时,vectorstring的实现通常会分配比新的空间需求更大的内存空间,其实际性能也表现得足够好: 虽然vector在每次重新分配内存空间时都要移动所有元素,但使用此策略后, 其扩张操作通常比listdeque还要快

管理容量的成员函数

如表9. 10所示,vectorstring类型提供了一些成员函数,允许我们与它的实现中内存分配部分互动

在这里插入图片描述

  • capacity操作告诉我们容器在不扩张内存空间的情况下可以容纳多少个元素
  • reserve操作允许我们通知容器它应该准备保存多少个元素
    只有当需要的内存空间超过当前容量时,reserve调用才会改变vector的容量。如果需求大小大于当前容量,reserve至少分配与需求一样大的内存空间(可能更大)。如果需求大小小于或等于当前容量,reserve什么也不做。特别是,当需求大小小于当前容量时,容器不会退回内存空间
  • 在新标准库中,我们可以调用shrink_to_fit 来要求dequevectorstring退回不需要的内存空间。但是,具体的实现可以选择忽略此请求。也就是说,调用shrink_to_fit 也并不保证一定退回内存空间

为什么listarray没有capacity成员函数?
list是链表,插入或删除元素时总是立刻分配或释放内存空间,因此capacitysize总是相等;而array大小固定。因此它们都不需要capacity成员函数

capacitysize

容器的size是指它已经保存的元素的数目;而capacity则是在不分配新的内存空间的前提下它最多可以保存多少元素

容器适配器

  • 除了顺序容器外, 标准库还定义了三个顺序容器适配器stackqueuepriority_queue
    • 适配器 (adaptor) 是标准库中的一个通用概念。容器、迭代器和函数都有适配器。本质上, 一个适配器是一种机制, 能使某种事物的行为看起来像另外一种事物。一个容器适配器接受一种已有的容器类型, 使其行为看起来像一种不同的类型
      • 例如, stack 适配器接受一个顺序容器(除arrayforward_list外), 并使其操作起来像一个stack一样
    • 默认情况下,stackqueue 是基于 deque 实现的,priority_queue 是在 vector 之上实现的

在这里插入图片描述

定义一个适配器

每个适配器都定义两个构造函数

  • 默认构造函数创建一个空对象
  • 接受一个容器的构造函数拷贝该容器来初始化适配器

  • 例如, 假定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_backpop_backback操作,因此可以使用除arrayforward_list之外的任何容器类型来构造stack
    • queue 适配器要求backpush_backfrontpush_front, 因此它可以构造于listdeque之上, 但不能基于vector构造
    • priority_queue除了frontpush_backpop_back 操作之外还要求随机访问能力, 因此它可以构造于vectordeque之上, 但不能基于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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值