c语言中std::map_在现代C ++中明智地使用std :: map

c语言中std::map

std::map and its siblings(std::multimap, std::unordered_map/multimap) used to be my favourite containers when I was doing competitive programming. In fact, I still like them(though using less frequently nowadays). And with Modern C++, we now have more reasons to use std::map. That’s why I have decided to address this topic by writing an article summarizing these new features. So, without much gibberish, let’s dive-in directly.

在进行竞争性编程时, std :: map及其兄弟姐妹( std :: multimapstd :: unordered_map / multimap )曾经是我最喜欢的容器。 实际上,我仍然喜欢它们(尽管如今使用频率降低了)。 借助Modern C ++ ,我们现在有更多使用std :: map的理由。 这就是为什么我决定写一篇总结这些新功能的文章来解决这个问题的原因。 因此,在没有太多胡言乱语的情况下,让我们直接进行深入研究。

/!\: This article has been originally published on my blog. If you are interested in receiving my latest articles, please sign up to my newsletter.

/!\:本文最初发布在我的 博客上 如果您有兴趣接收我的最新文章, 请注册我的新闻通讯

std :: map ::包含 (C ++ 20) (std::map::contains(C++20))

  • std::map::contains member function is a good step towards code expressiveness. And I am also tire of writing :

    std::map::contains成员函数是朝着代码表达性迈出的重要一步。 而且我对写作也感到厌倦:

if (auto search = freq_of.find(2); search != freq_of.end()) {
cout << "Found" << endl;
}
// Where assume, freq_of = map<uint32_t, uint32_t>{{3, 1}, {1, 1}, {2, 1}};
  • Rather, from C++20, you can write:

    相反,从C ++ 20,您可以编写:
if (freq_of.contains(2)) {
cout << "Found" << endl;
}

The code we write is written first for human consumption & only secondarily for the computer to understand.

我们编写的代码首先被编写供人类使用,其次才被计算机理解。

- John Sonmez

-约翰·桑梅兹

std :: map :: try_emplace (C ++ 17) (std::map::try_emplace(C++17))

  • While inserting into the map, we have 2 different possibilities:

    在插入地图时,我们有2种不同的可能性:
  1. The key doesn’t exist yet. Create a fresh key-value pair.

    密钥尚不存在。 创建一个新的键值对。
  2. The key does exist already. Take the existing item and modify it.

    密钥确实已经存在。 取现有项目并进行修改。
  • A typical approach to insert an element in std::map is by using operator[ ], std::map::insert or std::map::emplace . But, in all of these cases, we have to bear the cost of default/specialized constructor or assignment call. And the worst part is if an item already exists, we have to drop the freshly created item.

    std::map插入元素的典型方法是使用operator[ ]std::map::insertstd::map::emplace 。 但是,在所有这些情况下,我们必须承担默认/专用构造函数或赋值调用的费用。 最糟糕的是,如果某个项目已经存在,我们就必须删除新创建的项目。

int main() {
vector v{3, 4, 5, 8, 7, 3, 5, 2, 4};
map<uint32_t, uint32_t> freq_of; for (const auto &n : v) {
if (const auto &[it, inserted] = freq_of.emplace(n, 1); !inserted) {
it->second++; // Exists already
}
} assert(freq_of[3] == 2); return EXIT_SUCCESS;
}
  • Instead:

    代替:
if (const auto &[it, inserted] = freq_of.try_emplace(n, 1); !inserted) {
it->second++;
}
  • But, since C++17, there is this std::map::try_emplace method that creates items only if the key doesn’t exist yet. This boosts the performance in case objects of that type are expensive to create.

    但是,从C ++ 17开始,有一个std :: map :: try_emplace方法仅在键不存在时才创建项目 。 如果创建这种类型的对象的成本很高,则可以提高性能。

  • Although the above example hasn’t showcased the expensive to create items. But, yes! whenever you encounter such a situation, must be known how to handle it with std::map::try_emplace.

    尽管以上示例并未展示创建项目的昂贵成本。 但是,是的! 每当遇到这种情况时,必须知道如何使用std::map::try_emplace处理它。

std :: map :: insert_or_assign (C ++ 17) (std::map::insert_or_assign(C++17))

  • When you have to insert element anyhow. For the sake of convenience, you use std::map::operator[ ]. Which is OK( and dangerous)! Unless you have any constraint on insertion or assignment.

    无论如何,当您必须插入元素时。 为了方便起见,使用std :: map :: operator [] 。 可以( 危险 )! 除非您对插入或分配有任何限制。

  • For example, while counting the frequency of elements with the added constraint that when an element is repeated(i.e. assigned) you have to remove all the element lesser than the current one.

    例如,在计算带有附加约束的元素的频率时,当重复(即分配)一个元素时,您必须删除所有小于当前元素的元素。
  • In such a situation, std::map::operator[ ] isn't feasible. Rather, std::map::insert_or_assign is more appropriate and returns more information than std::map::operator[ ]. It also does not require default-constructibility of the mapped type. Consider the following example for the same.

    在这种情况下, std::map::operator[ ]不可行。 相反, std::map::operator[ ] 相比std::map::insert_or_assign 更合适并且返回更多信息 。 它还不需要映射类型的默认可构造性。 同样考虑以下示例。

int main() {
vector v{8, 3, 9, 5, 8};
map<uint32_t, uint32_t> freq_of; for (auto &&n : v) {
const auto &[it, is_inserted] = freq_of.insert_or_assign(n, 1); if (!is_inserted) { // remove all lesser element then current one if repeated
freq_of.erase(begin(freq_of), it);
}
} assert((freq_of == decltype(freq_of){
{8, 1},
{9, 1},
})); return EXIT_SUCCESS;
}

带提示的std :: map :: insert (C ++ 11/17) (std::map::insert With Hint(C++11/17))

  • Looking up items in an std::map takes O(log(n)) time. This is the same for inserting new items. Because the position where to insert them must looked up. Naive insertion of M new items would thus take O(M * log(n)) time.

    std::map查找项目需要O(log(n))时间。 插入新项目的方法相同。 因为必须将它们插入的位置向上看。 因此,天真的插入M新项将花费O(M * log(n))时间。

  • In order to make this more efficient, std::map insertion functions accept an optional insertion hint parameter. The insertion hint is basically an iterator, which points near the future position of the item that is to be inserted. If the hint is correct, then we get amortized O(1) insertion time.

    为了提高效率, std::map插入函数接受可选的插入提示参数。 插入提示基本上是一个迭代器,它指向要插入的项目的将来位置。 如果提示正确,那么我们将摊销O(1)插入时间。

  • This is quite useful from a performance point of view when the insertion sequence of items is somewhat predictable. For example:

    从性能的角度来看,当项目的插入顺序是可预测的时,这是非常有用的。 例如:
int main() {
map<uint32_t, string> m{{2, ""}, {3, ""}};
auto where(end(m)); for (const auto &n : {8, 7, 6, 5, 4, 3, 2, 1}) { // Items in non-incremental order
where = m.insert(where, {n, ""});
} // How it is not done!
// m.insert(end(m), {0, ""}); for (const auto &[key, value] : m) {
cout << key << " : " << value << endl;
} return EXIT_SUCCESS;
}
  • A correct hint will point to an existing element, which is greater than the element to be inserted so that the newly inserted key will be just before the hint. If this does not apply for the hint the user provided during insertion, the insert function will fall back to a nonoptimized insertion, yielding O(log(n)) performance again.

    正确的提示将指向一个现有元素,该元素大于要插入的元素,因此新插入的键将刚好在提示之前。 如果这不适用于用户在插入过程中提供的提示,则插入功能将退回到未优化的插入状态,从而再次产生O(log(n))性能。

  • For the above example, the first insertion, we got the end iterator of the map, because we had no better hint to start with. After installing an 8 in the tree, we knew that installing 7 will insert a new item just in front of the 8, which qualified it to be a correct hint. This applies to 6 as well, if put into the tree after inserting the 7, and so on. This is why it is possible to use the iterator, which was returned in the last insertion for the next insertion.

    对于上面的示例,第一次插入,我们获得了地图的结束迭代器,因为我们没有更好的提示开始。 在树中安装8后,我们知道安装7将在8的前面插入一个新项,这将其限定为正确的提示。 如果在插入7之后将其放入树中,则这也适用于6。 这就是为什么可以使用在上一次插入中返回的迭代器进行下一次插入的原因。
  • You can play around the above example to justify the performance gain with quick-benchmark.

    您可以绕过上面的示例,以使用quick-benchmark证明性能提高是合理的。

*Note:* It is important to know that before C++11, insertion hints were considered correct when they pointed before the position of the newly inserted item.

*注意: *重要的是要知道,在C ++ 11之前,插入提示指向新插入项的位置之前被认为是正确的。

std :: map :: merge (C ++ 17) (std::map::merge(C++17))

  • Same as std::list:splice, which transfers the elements from one list to another. we have std::map::merge which can merge the two same type of std::map.

    std :: list:splice相同 ,后者将元素从一个列表传输到另一个列表。 我们有std::map::merge可以合并两种相同类型的std::map

int main() {
map<uint32_t, string> fruits{{5, "grapes"}, {2, "tomoto"}};
map<uint32_t, string> person{{2, "mickel"}, {10, "shree"}};
map<uint32_t, string> fruits_and_persons; fruits_and_persons.merge(fruits);
assert(fruits.size() == 0); fruits_and_persons.merge(person);
assert(person.size() == 1);
assert(person.at(2) == "mickel"); // Won't overwrite value at 2 i.e.`mickel` assert((fruits_and_persons == decltype(fruits){
{2, "tomoto"},
{5, "grapes"},
{10, "shree"},
})); return EXIT_SUCCESS;
}
  • The thing here to note is what happens when there are duplicates! The duplicated elements are not transferred. They’re left behind in the right-hand-side map.

    这里要注意的是,当重复时会发生什么! 重复的元素不会传输。 他们被遗留在右侧地图中

std :: map :: extract (C ++ 17 (std::map::extract(C++17)

  • Unlike std::map::merge that transfers the elements in bulk, std::map::extract along with std::map::insert transfers element piecewise. But what is the more compelling application of std::map::extract is modifying keys.

    std::map::merge大量传输元素不同, std::map::extract std::map::insert 逐段传输元素 。 但是std::map::extract更引人注目的应用是修改密钥。

  • As we know, for std::map keys are always unique and sorted. Hence, It is crucial that users cannot modify the keys of map nodes that are already inserted. In order to prevent the user from modifying the key items of perfectly sorted map nodes, the const qualifier is added to the key type.

    众所周知,对于std::map键始终是唯一的且已排序。 因此,至关重要的是用户不能修改已插入的地图节点的键。 为了防止用户修改完全排序的地图节点的关键项,将const限定符添加到关键类型中。

  • This kind of restriction is perfectly valid because it makes harder for the user to use std::map the wrong way. But what if we really need to change the keys of some map items?

    这种限制是完全有效的,因为它使用户更难以错误的方式使用std::map 。 但是,如果我们真的需要更改某些地图项的键怎么办?

  • Prior to C++17, we had to remove & reinsert the items in order to change the key. The downside of this approach is memory allocation & deallocation, which sounds bad in terms of performance. But, from C++17, we can remove & reinsert std::map nodes without any reallocation of memory.

    在C ++ 17之前,我们必须删除并重新插入项目才能更改密钥。 这种方法的缺点是内存分配和释放,这在性能方面听起来很糟糕。 但是,从C ++ 17开始,我们可以删除并重新插入std :: map节点,而无需重新分配内存。
int main() {
map<int, string> race_scoreboard{{1, "Mickel"}, {2, "Shree"}, {3, "Jenti"}};
using Pair = map<int, string>::value_type; {
auto Jenti(race_scoreboard.extract(3));
auto Mickel(race_scoreboard.extract(1)); swap(Jenti.key(), Mickel.key()); auto [it, is_inserted, nh] = race_scoreboard.insert(move(Jenti)); // nh = node handle
assert(*it == Pair(1, "Jenti") && is_inserted == true && nh.empty()); race_scoreboard.insert(move(Mickel));
} assert((race_scoreboard == decltype(race_scoreboard){
{1, "Jenti"},
{2, "Shree"},
{3, "Mickel"},
})); return EXIT_SUCCESS;
}
  • Consider the above example of the racing scoreboard where you have employed std::map to imitate the racing position. And after a while, Jenti took the lead & Mickel left behind. In this case, how we have switched the keys(position on a race track) of those players.

    考虑上面的竞赛记分牌示例,其中您使用了std::map来模仿竞赛位置。 过了一会儿,简蒂(Jenti)带头,米克尔(Mickel)落伍了。 在这种情况下,我们如何切换这些玩家的按键(在赛道上的位置)。

  • std::map::extract comes in two flavours:

    std::map::extract有两种口味:

node_type extract(const_iterator position);
node_type extract(const key_type& x);
  • In the above example, we used the second one, which accepts a key and then finds & extracts the map node that matches the key parameter. The first one accepts an iterator, which implies that it is faster because it doesn’t need to search for the item.

    在上面的示例中,我们使用了第二个示例,该示例接受一个键,然后查找并提取与键参数匹配的地图节点。 第一个接受迭代器,这意味着它更快,因为它不需要搜索项目。

如果不存在具有特定密钥的节点怎么办? (What If the Node With a Particular Key Does Not Exist?)

  • If we try to extract an item that doesn’t exist with the second method (the one that searches using a key), it returns an empty node_type instance i.e. node handle. The empty() member method or overloaded bool operator tells us that whether a node_type instance is empty or not.

    如果我们尝试提取第二种方法(使用键进行搜索的方法)不存在的项目,则它将返回一个空的 node_type 实例,即node handleempty()成员方法或重载的布尔运算符告诉我们node_type实例是否为空。

好! 然后,如何修改std :: map键? (OK! Then How Do I Modify std::map Keys?)

  • After extracting nodes, we were able to modify their keys using the key() method, which gives us non-const access to the key, although keys are usually const.

    提取节点后,我们可以使用 key() 方法修改其键,尽管键通常是const ,但这使我们可以非常量访问键。

  • Note that in order to reinsert the nodes into the map again, we had to move them into the insert function. This makes sense because the extract is all about avoiding unnecessary copies and allocations. Moreover, while we move a node_type instance, this does not result in actual moves of any of the container values.

    请注意,为了再次将节点重新插入到映射中,我们必须将其移动到insert函数中。 这是有道理的,因为摘录只是为了避免不必要的复制和分配。 而且,虽然我们移动一个node_type实例,但这不会导致任何容器值的实际移动。

我还可以在std :: map中修改关联值吗? (Can I Modify Associated Values in std::map Also?)

  • Yes! You can use the accessor methods nh.mapped()(instead of nh.key()) to manipulate the pieces of the entry in a std::map (or nh.value() for the single piece of data in an element of a std::set). Thus you can extract, manipulate, and reinsert a key without ever copying or moving its actual data.

    是! 您可以使用访问器方法 nh.mapped() (而不是nh.key() )来处理std::map中的条目项(或nh.value()来处理元素中的单个数据项)。一个std::set )。 因此,您可以提取,操作和重新插入密钥,而无需复制或移动其实际数据。

但是安全呢? (But What About Safety?)

  • If you extract a node from a map and then throw an exception before you’ve managed to re-insert it into the destination map.

    如果您从地图提取节点,然后在设法将其重新插入到目标地图之前抛出异常

  • A node handle’s destructor is called and will correctly clean up the memory associated with the node. So, technically std::map::extract by-default(without insert) will act as std::map::erase!

    节点句柄的析构函数将被调用,并将正确清理与该节点关联的内存。 因此,从技术上讲,默认情况下 std::map::extract (不带插入)将充当 std :: map :: erase

还有更多! 互通性 (There Is More! Interoperability)

  • Map nodes that have been extracted using the std::map::extract are actually very versatile. We can extract nodes from a map instance and insert it into any other map or even multimap instance.

    使用std::map::extract地图节点实际上非常通用。 我们可以从地图实例中提取节点,然后将其插入任何其他地图甚至多地图实例中

  • It does also work between unordered_map and unordered_multimap instances, as well as with set/multiset and respective unordered_set/unordered_multiset.

    它也可以在unordered_mapunordered_multimap实例之间以及set / multiset和相应的unordered_set / unordered_multiset之间工作

  • In order to move items between different map/set structures, the types of key, value and allocator need to be identical.

    为了在不同的映射/集合结构之间移动项目,键,值和分配器的类型必须相同。

运算符[]与insert()与at()之间的区别 (Difference Between operator[ ] vs insert() vs at())

This is trivial for experienced devs but, still I want to go over it quickly.

对于经验丰富的开发人员而言,这是微不足道的,但是,我仍然想快速了解一下。

std :: map :: operator [] (std::map::operator[ ])

  • Operation: find-or-add; try to find an element with the given key inside the map, and if it exists it will return a reference to the stored value. If it does not, it will create a new element inserted in place with default initialization and return a reference to it.

    操作 :查找或添加; 尝试在地图中查找具有给定键的元素,如果该元素存在,它将返回对存储值的引用。 如果没有,它将创建一个默认插入的新元素,并返回对其的引用。

  • Applicability:

    适用范围

  • Not usable for const std::map, as it will create the element if it doesn't exist.

    不适用于const std::map ,因为如果不存在,它将创建元素。

  • Not suitable for value type that does not default constructible and assignable(in layman term, doesn’t have default constructor & copy/move constructor).

    不适合不能默认构造和分配的值类型(用外行术语来说,没有默认构造函数和复制/移动构造函数)。
  • When key exists: Overwrites it.

    当密钥存在时 :覆盖它。

std :: map :: insert (std::map::insert)

  • Operation: insert-or-nop; accepts a value_type (std::pair) and uses the key(first member) and to insert it. Asstd::map does not allow for duplicates, if there is an existing element it will not insert anything.

    操作 :插入或插入; 接受一个value_type( std::pair )并使用key(first成员)并将其插入。 由于std::map不允许重复,因此如果存在现有元素,则不会插入任何内容。

  • Applicability:

    适用范围

  • Liberty in calling insert different ways that require the creation of the value_type externally and the copy of that object into the container.

    调用时的自由以不同的方式插入,这需要在外部创建value_type并将该对象的副本复制到容器中。
  • Highly applicable when item insertion sequence is somewhat predictable to gain the performance.

    当物品插入顺序在某种程度上可以预测以提高性能时,则非常适用。
  • When key exists: Not modify the state of the map, but instead return an iterator to the element that prevented the insertion.

    当key存在时 :不修改映射的状态,而是将迭代器返回到阻止插入的元素。

std :: map :: at (std::map::at)

  • Operation: find-or-throw; returns a reference to the mapped value of the element with key equivalent to input key. If no such element exists, an exception of type std::out_of_range is thrown.

    操作 :查找或抛出; 使用与输入键等效的键返回对元素映射值的引用。 如果没有这样的元素存在,一个异常类型的std :: out_of_range异常。

  • Applicability:

    适用范围

  • Not recommended using at() when accessing const maps and when element absence is a logic error.

    当访问常量映射并且元素缺失是逻辑错误时,不建议使用at()

  • Yes, it’s better to use std::map::find() when you're not sure element is there. Because, throwing and catching std::logic_error exception will not be a very elegant way of programming, even if we don't think about performance.

    是的,当您不确定元素在那里时,最好使用std::map::find() 。 因为,即使我们不考虑性能,抛出并捕获std :: logic_error异常也不是一种非常优雅的编程方式。

  • When key exists: returns a reference to mapped value.

    当key存在时 :返回对映射值的引用。

分词 (Parting Words)

If you see the table of content for this article above, more than half of the member functions are around inserting the elements into the map. To the newbie, this is the reason for anxiety(or standard committee would say modernness). But if you account for the new features & complexity of language those are pretty much justified. BTW, this modernness doesn’t stop here, we do have other specialization also available for map like std::swap(C++17), std::erase_if(C++20) & bunch of comparison operators.

如果您在上面看到了本文的目录,那么一半以上的成员函数就是将元素插入到地图中。 对于新手来说,这就是焦虑的原因(或者标准委员会会说现代性)。 但是,如果您考虑到语言的新功能和复杂性,那么这是很合理的。 顺便说一句,这种现代性不止于此,我们还为地图提供了其他特殊功能,例如std :: swap (C ++ 17), std :: erase_if (C ++ 20)和一堆比较运算符。

翻译自: https://medium.com/dev-genius/using-std-map-wisely-with-modern-c-cdc7c2c81b52

c语言中std::map

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
library IEEE; Library UNISIM; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; use IEEE.STD_LOGIC_ARITH.ALL; use UNISIM.vcomponents.all; entity pin_test is port ( rst_manu_h :in std_logic; clk_in :in std_logic; FPGA_CR2 :out std_logic; FPGA_CR1 :out std_logic; FPGA_PR :out std_logic; FPGA_TCK :out std_logic; test_out :out std_logic; FPGA_RST :out std_logic --LED : out std_logic_vector(3 downto 0) ); end pin_test; architecture rtl of pin_test is signal clk_div1 : integer range 0 to 2086; signal clk_div : std_logic_vector(27 downto 0); signal clk_div2 : std_logic_vector(27 downto 0); signal clk0 : std_logic; signal clk180 : std_logic; signal clk_180 : std_logic; signal clk2x : std_logic; signal CLKFX : std_logic; signal clk : std_logic; signal clkdv : std_logic; signal clkin_buf : std_logic; signal clk_sys : std_logic; signal reset : std_logic; signal TX_CLK : std_logic; signal tem1: std_logic; signal tem2 : std_logic; begin clk <= clkin_buf ; reset <= not rst_manu_h; CLK_DIVIDOR1:process(clk) begin if(clk'event and clk = '1')then if clk_div1=2086 then --clk_div1 '0'); clk_div1 <=0; else clk_div1<= clk_div1 + 1; end if; end if; end process CLK_DIVIDOR1; CLK_DIVIDOR:process(CLKFX) begin if(CLKFX'event and CLKFX= '1') then clk_div<= clk_div + 1; end if; end process CLK_DIVIDOR; CLK_DIVIDOR2:process(CLKFX) begin if(CLKFX'event and CLKFX = '0') then clk_div2<= clk_div2 + 1; end if; end process CLK_DIVIDOR2; tem1 2068 else '0'; tem2 <= clk or tem1; test_out <= CLKFX ; FPGA_CR2 <= not tem2; FPGA_CR1 <= clk or tem1; FPGA_PR <= clk_div(15); FPGA_TCK 2068 else '0'; FPGA_RST <= clk_div(0)and clk_div2(0); --------------------------------------------------------------------

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值