コンテナーから要素を削除したり、連想コンテナーに特定のキーがあるかどうかを尋ねたりするのは、複雑すぎます。 C++20 では話が変わったからです。
簡単に始めましょう。コンテナから要素を消去したい。
消去削除イディオム
わかった。コンテナから要素を削除するのは非常に簡単です。 std::vecto
の場合 関数 std::remove.
を使用できます
// removeElements.cpp #include <algorithm> #include <iostream> #include <vector> int main() { std::cout << std::endl; std::vector myVec{-2, 3, -5, 10, 3, 0, -5 }; for (auto ele: myVec) std::cout << ele << " "; std::cout << "\n\n"; std::remove_if(myVec.begin(), myVec.end(), [](int ele){ return ele < 0; }); // (1) for (auto ele: myVec) std::cout << ele << " "; std::cout << "\n\n"; }
プログラム removeElemtens.cpp
std::vector
のすべての要素を削除します それはゼロより小さいです。簡単ですか?ここで、プロの C++ プログラマーなら誰でも知っている罠に陥ります。
std::remove
または std::remove_if
inline (1) は何も削除しません。 std::vector
まだ同じ数の引数があります。どちらのアルゴリズムも、変更されたコンテナーの新しい論理エンドを返します。
コンテナーを変更するには、新しい論理エンドをコンテナーに適用する必要があります。
// eraseRemoveElements.cpp #include <algorithm> #include <iostream> #include <vector> int main() { std::cout << std::endl; std::vector myVec{-2, 3, -5, 10, 3, 0, -5 }; for (auto ele: myVec) std::cout << ele << " "; std::cout << "\n\n"; auto newEnd = std::remove_if(myVec.begin(), myVec.end(), // (1)
[](int ele){ return ele < 0; }); myVec.erase(newEnd, myVec.end()); // (2) // myVec.erase(std::remove_if(myVec.begin(), myVec.end(), // (3)
[](int ele){ return ele < 0; }), myVec.end()); for (auto ele: myVec) std::cout << ele << " "; std::cout << "\n\n"; }
行 (1) は、新しい論理終了 newEnd
を返します。 コンテナ myVec
の . myVec
からすべての要素を削除するために、この新しい論理終了が行 (2) に適用されます。 newEnd
から .行 (3) のように関数 remove と erase を 1 つの式に適用すると、この構造が「erase-remove-idiom」と呼ばれる理由が正確にわかります。
新しい関数 erase
のおかげで および erase_if
C++20 では、コンテナーから要素を消去する方がはるかに便利です。
erase
そして erase_if
C++20
erase
で と erase_if
、コンテナを直接操作できます。対照的に、前に提示された消去削除イディオムは非常に冗長です ( eraseRemoveElements.cpp
の 3 行目)。 ):erase
アルゴリズム std::remove_if
によって提供された 2 つの反復子が必要です .
新しい関数 erase
を見てみましょう と erase_if
実際には意味します。次のプログラムは、いくつかのコンテナーの要素を消去します。
// eraseCpp20.cpp #include <iostream> #include <numeric> #include <deque> #include <list> #include <string> #include <vector> template <typename Cont> // (7) void eraseVal(Cont& cont, int val) { std::erase(cont, val); } template <typename Cont, typename Pred> // (8) void erasePredicate(Cont& cont, Pred pred) { std::erase_if(cont, pred); } template <typename Cont> void printContainer(Cont& cont) { for (auto c: cont) std::cout << c << " "; std::cout << std::endl; } template <typename Cont> // (6) void doAll(Cont& cont) { printContainer(cont); eraseVal(cont, 5); printContainer(cont); erasePredicate(cont, [](auto i) { return i >= 3; } ); printContainer(cont); } int main() { std::cout << std::endl; std::string str{"A Sentence with an E."}; std::cout << "str: " << str << std::endl; std::erase(str, 'e'); // (1) std::cout << "str: " << str << std::endl; std::erase_if( str, [](char c){ return std::isupper(c); }); // (2) std::cout << "str: " << str << std::endl; std::cout << "\nstd::vector " << std::endl; std::vector vec{1, 2, 3, 4, 5, 6, 7, 8, 9}; // (3) doAll(vec); std::cout << "\nstd::deque " << std::endl; std::deque deq{1, 2, 3, 4, 5, 6, 7, 8, 9}; // (4) doAll(deq); std::cout << "\nstd::list" << std::endl; std::list lst{1, 2, 3, 4, 5, 6, 7, 8, 9}; // (5) doAll(lst); }
行 (1) はすべての文字 e
を消去します 指定された文字列から str.
行 (2) はラムダ式を同じ文字列に適用し、すべての大文字を消去します。
残りのプログラムでは、シーケンス コンテナーの要素 std::vecto
r (3 行目)、 std::deque
(4 行目)、 std::list
(5行目)が消去されます。各コンテナで、関数テンプレート doAll
(6 行目) が適用されます。 doAll
要素 5 と 3 より大きいすべての要素を消去します。関数テンプレート erase
(7 行目) 新しい関数 erase
を使用します 関数テンプレート erasePredicate
(8 行目) 新しい関数 erase_if
を使用します .
Microsoft Compiler のおかげで、これがプログラムの出力です。
新しい関数 erase
と erase_if
標準テンプレート ライブラリのすべてのコンテナに適用できます。これは、次の便利な関数 contains
には当てはまりません .
連想コンテナー内の要素の存在を確認する
関数 contains
のおかげで 、連想コンテナに要素が存在するかどうかを簡単に確認できます。
ストップ、あなたは言うかもしれませんが、これは既に find または count で実行できます。
いいえ、どちらの機能も初心者向けではなく、それぞれに欠点があります。
// checkExistens.cpp #include <set> #include <iostream> int main() { std::cout << std::endl; std::set mySet{3, 2, 1}; if (mySet.find(2) != mySet.end()) { // (1) std::cout << "2 inside" << std::endl; } std::multiset myMultiSet{3, 2, 1, 2}; if (myMultiSet.count(2)) { // (2) std::cout << "2 inside" << std::endl; } std::cout << std::endl; }
関数は期待される結果を生成します。
両方の呼び出しの問題を次に示します。 find
call inline (1) は冗長すぎます。 count
についても同じ議論が成り立ちます ラインで呼び出します (2)。 count
call にもパフォーマンスの問題があります。要素がコンテナー内にあるかどうかを知りたい場合は、要素が見つかった時点で停止し、最後までカウントしないようにする必要があります。具体的なケースでは myMultiSet.count(2)
2 を返しました。
逆に、C++20 の contains メンバー関数は非常に便利に使用できます。
// containsElement.cpp #include <iostream> #include <set> #include <map> #include <unordered_set> #include <unordered_map> template <typename AssozCont> bool containsElement5(const AssozCont& assozCont) { // (1) return assozCont.contains(5); } int main() { std::cout << std::boolalpha; std::cout << std::endl; std::set<int> mySet{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; std::cout << "containsElement5(mySet): " << containsElement5(mySet); std::cout << std::endl; std::unordered_set<int> myUnordSet{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; std::cout << "containsElement5(myUnordSet): " << containsElement5(myUnordSet); std::cout << std::endl; std::map<int, std::string> myMap{ {1, "red"}, {2, "blue"}, {3, "green"} }; std::cout << "containsElement5(myMap): " << containsElement5(myMap); std::cout << std::endl; std::unordered_map<int, std::string> myUnordMap{ {1, "red"}, {2, "blue"}, {3, "green"} }; std::cout << "containsElement5(myUnordMap): " << containsElement5(myUnordMap); std::cout << std::endl; }
この例に追加することはあまりありません。関数テンプレート containsElement5
true
を返します 連想コンテナーにキー 5 が含まれている場合。私の例では、連想コンテナーのみを使用しました std::set
、 std::unordered_set
、 std::map
、および std::unordered_set
キーを複数回持つことはできません。
次は?
便利な機能については、次の投稿に続きます。 C++20 では、2 つの値の中点を計算できます。std::string
部分文字列で開始または終了し、 std::bind_front
で callable を作成します .