C++ std::min 用法與範例

本篇介紹 C++ 的 std::min 用法與範例,C++ std::min() 用來比較傳入的兩個數值並且回傳比較小的數值,

以下的 C++ std::min 用法與範例將分為這幾部分,

  • C++ std::min 兩數取最小值
  • std::min 多個數值中取最小值 (C++11)

那我們開始吧!

C++ std::min 兩數取最小值

C++ std::min 可以取各種變數類型的兩數最小值,包含 int, short, long, float, double 甚至是 char,
範例如下,比較 50 與 100 兩整數的話會回傳 50,比較 XY 兩字元的話會回傳 X,比較 3.6 與 5.4 兩浮點數數的話會回傳 3.6,

std-min.cpp
1
2
3
4
5
6
7
8
9
10
11
12
// g++ std-min.cpp -o a.out
#include <iostream>
#include <algorithm>

using namespace std;

int main() {
cout << std::min(50, 100) << "\n";
cout << std::min('X', 'Y') << "\n";
cout << std::min(3.6f, 5.4f) << "\n";
return 0;
}

輸出:

1
2
3
50
X
3.6

std::min 多個數值中取最小值 (C++11)

這個是 C++11 才加入的功能,讓 std::min 可以接受多個數值作為輸入,然後回傳這當中的最小值,
寫法如下,第一種直接用 { } 大括號的方式將數值帶入,然後它會呼叫到 std::initializer_list 建構子,
第二種寫法是直接先宣告好 std::initializer_list, 再將其變數帶入 std::min,
第三種就是懶人寫法 auto ~~~

std-min2.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// g++ std-min2.cpp -o a.out -std=c++11
#include <iostream>
#include <algorithm>
#include <typeinfo>

using namespace std;

int main() {
cout << std::min({2, 4, 6, 8}) << "\n";

std::initializer_list<int> array = {1, 3, 5, 7};
cout << std::min(array) << "\n";

auto array2 = {1, 3, 5, 7};
cout << std::min(array2) << "\n";
cout << typeid(array2).name() << "\n";

return 0;
}

輸出如下,這邊我們順便使用 typeid 來看看 auto 建立 array2 出來的變數類型是什麼,
關於 typeid 的用法可以參考這篇介紹,這邊只要知道它可以用來印出變數類型是什麼類型就好,
結果就是 St16initializer_listIiE,簡單說它就是 std::initializer_list,
所以編譯器會在編譯時期幫我們將 auto 自動轉換成 std::initializer_list,

1
2
3
4
2
1
1
St16initializer_listIiE

如果想要在 vector 這種容器裡面取出最小值的方法請參考std::min_element 這篇

以上就是 C++ std::min 用法與範例介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其他參考
std::min - cppreference.com
https://en.cppreference.com/w/cpp/algorithm/min

其它相關文章推薦
C/C++ 新手入門教學懶人包
std::min_element 用法與範例
std::max 用法與範例
C++ virtual 的兩種用法
C/C++ 字串反轉 reverse
C/C++ call by value傳值, call by pointer傳址, call by reference傳參考 的差別
C++ 類別樣板 class template
std::sort 用法與範例
std::find 用法與範例
std::queue 用法與範例
std::map 用法與範例

C++ std::multimap 用法與範例

本篇將介紹如何使用 C++ std multimap 以及用法,C++ std::multimap 是一個關聯式容器,關聯式容器把鍵值和一個元素連繫起來,並使用該鍵值來尋找元素、插入元素和刪除元素等操作。

multimap 是有排序關聯式容器,即 multimap 容器中所有的元素都會根據鍵值來排序,跟 map 不同的是 multimap 允許鍵值重複,也就是說 multimap 裡同一個鍵值 key 可以對應多個 value,也因為這樣的差異,multimap 跟 map 容器相比下没有提供 [] 運算子與 at() 成員函式。

multimap 的實作方式通常是用紅黑樹(red-black tree)實作的,這樣它可以保證可以在O(log n)時間內完成搜尋、插入、刪除,n為元素的數目。

以下內容將分為這幾部分,

  • multimap 常用功能
  • multimap 初始化
  • multimap 容器插入元素與存取元素
  • multimap 容器的迴圈遍歷
  • 刪除 multimap 指定的元素
  • 清空 multimap 容器
  • 判斷 multimap 容器是否為空
  • 計數 multimap 容器

要使用 multimap 容器的話,需要引入的標頭檔<map>

multimap 常用功能

C++ multimap 是一種關聯式容器,包含「key鍵值/value資料」成對關係
迭代器
begin():回傳指向multimap頭部元素的迭代器
end():回傳指向multimap末尾的迭代器
rbegin():回傳一個指向multimap尾部的反向迭代器
rend():回傳一個指向multimap頭部的反向迭代器
容量
empty():檢查容器是否為空,空則回傳true
size():回傳元素數量
max_size():回傳可以容納的最大元素個數
修改器
clear():刪除所有元素
insert():插入元素
erase():刪除一個元素
swap():交換兩個multimap
查找
count():回傳指定元素出現的次數
find():查找一個元素

multimap 初始化

接下來說說怎麼初始化 c++ multimap 容器吧!
先以 int 當 key, int 當 value 的 multimap 為範例,
std::multimap 宣告時要宣告兩個變數類型,
multimap.first:第一個稱為(key)鍵值,在 multimap 裡面,(key)鍵值可以重複
multimap.second:第二個稱為(key)鍵值對應的數值(value)
宣告一個空的 multimap 就這樣寫,

1
std::multimap<int, int> mmap;

multimap 容器初始化的寫法有幾種,如果是先宣告一個 multimap 容器,之後再用 insert() 方式將元素插入的話,寫法如下,

1
2
3
4
std::multimap<int, int> mmap;
mmap.insert(std::pair<int, int>(1, 10));
mmap.insert(std::pair<int, int>(2, 20));
mmap.insert(std::pair<int, int>(3, 30));

或者 std::pair 換成 std::make_pair 就不用寫變數類型,

1
2
3
4
std::multimap<int, int> mmap;
mmap.insert(std::make_pair(1, 10));
mmap.insert(std::make_pair(2, 20));
mmap.insert(std::make_pair(3, 30));

如果更懶都不想寫的話,可以這樣寫,注意使用大括號,

1
2
3
4
std::multimap<int, int> mmap;
mmap.insert({1, 10});
mmap.insert({2, 20});
mmap.insert({3, 30});

如果要宣告 multimap 時順便一起初始化,寫成一行的話可以像這樣寫,

1
2
3
4
5
std::multimap<int, int> mmap = {
std::pair<int, int>(1, 10),
std::pair<int, int>(2, 20),
std::pair<int, int>(3, 30)
};

或者改用 std::make_pair 就不用寫變數類型,

1
2
3
4
5
std::multimap<int, int> mmap = {
std::make_pair(1, 10),
std::make_pair(2, 20),
std::make_pair(3, 30)
};

或者更懶一點的寫法,

1
2
3
4
5
std::multimap<int, int> mmap = {
{1, 10},
{2, 20},
{3, 30}
};

multimap 容器插入元素與存取元素

剛剛在初始化部分已經有示範過了,multimap 容器插入元素是使用 insert() 成員函式來插入元素,

std-map.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// g++ std-multimap.cpp -o a.out -std=c++11
#include <iostream>
#include <map>

int main() {
std::multimap<int, int> mmap;
mmap.insert(std::pair<int, int>(1, 10));
mmap.insert(std::pair<int, int>(2, 20));
mmap.insert(std::pair<int, int>(3, 30));

for (const auto& m : mmap) {
std::cout << m.first << ", " << m.second << "\n";
}
return 0;
}

插入完印出來的結果如下,

1
2
3
1, 10
2, 20
3, 30

multimap 容器的迴圈遍歷

迴圈遍歷 multimap 容器的方式有幾種,以下先介紹使用 range-based for loop 來遍歷 multimap 容器,
這邊故意將鍵值不按順序初始化或者插入,先插入 13242 key 鍵值的元素,
然後我們再來觀察看看是不是 multimap 會將其排序,

std-map2.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// g++ std-multimap2.cpp -o a.out -std=c++11
#include <iostream>
#include <map>

int main() {
std::multimap<int, int> mmap = {
std::make_pair(1, 10),
std::make_pair(3, 30),
std::make_pair(2, 23),
std::make_pair(4, 40),
std::make_pair(2, 20)
};

for (const auto& m : mmap) {
std::cout << m.first << ", " << m.second << "\n";
}
return 0;
}

輸出內容如下,從這個輸出結果發現是 key 鍵值由小到大排列,所以 multimap 跟 map 容器一樣是會幫你排序的,
在插入元素的同時會根據鍵值來進行排序,

1
2
3
4
5
1, 10
2, 23
2, 20
3, 30
4, 40

使用前向迭代器,輸出結果跟上列相同,

1
2
3
4
5
for (std::multimap<int, int>::iterator it = mmap.begin(); it != mmap.end(); it++) {
// or
// for (auto it = mmap.begin(); it != mmap.end(); it++) {
std::cout << (*it).first << ", " << (*it).second << "\n";
}

使用反向迭代器的例子,如果嫌 iterator 迭代器名稱太長的話可以善用 auto 關鍵字讓編譯器去推導該變數類型,

1
2
3
4
5
// for (std::multimap<int, int>::reverse_iterator it = mmap.rbegin(); it != mmap.rend(); it++) {
// or
for (auto it = mmap.rbegin(); it != mmap.rend(); it++) {
std::cout << (*it).first << ", " << (*it).second << "\n";
}

反向迭代器的輸出結果如下,反著印出來,

1
2
3
4
5
4, 40
3, 30
2, 20
2, 23
1, 10

刪除 multimap 指定的元素

刪除 multimap 指定的元素要使用 erase()

std-multimap5.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// g++ std-multimap5.cpp -o a.out -std=c++11
#include <iostream>
#include <map>

int main() {
std::multimap<int, int> mmap;
mmap.insert(std::pair<int, int>(1, 10));
mmap.insert(std::pair<int, int>(2, 20));
mmap.insert(std::pair<int, int>(2, 23));
mmap.insert(std::pair<int, int>(3, 30));

mmap.erase(1);
for (const auto& m : mmap) {
std::cout << m.first << " " << m.second << "\n";
}

std::cout << "---\n";

mmap.erase(2);
for (const auto& m : mmap) {
std::cout << m.first << " " << m.second << "\n";
}

return 0;
}

結果如下,

1
2
3
4
5
2 20
2 23
3 30
---
3 30

那如果 multimap 刪除不存在的元素會發生什麼事呢?

std-multimap6.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// g++ std-multimap6.cpp -o a.out -std=c++11
#include <iostream>
#include <string>
#include <map>

int main() {
std::multimap<int, int> mmap;
mmap.insert(std::pair<int, int>(1, 10));
mmap.insert(std::pair<int, int>(2, 20));
mmap.insert(std::pair<int, int>(2, 23));
mmap.insert(std::pair<int, int>(3, 30));

auto ret = mmap.erase(2);
std::cout << ret << "\n";
for (const auto& m : mmap) {
std::cout << m.first << " " << m.second << "\n";
}

std::cout << "---\n";

ret = mmap.erase(4);
std::cout << ret << "\n";
for (const auto& m : mmap) {
std::cout << m.first << " " << m.second << "\n";
}

return 0;
}

multimap 刪除不存在的元素並不會造成什麼 crash 這種嚴重問題,他反而會回傳一個數量告訴你它刪除了多少個元素,以這個例子來說 erase(2) 是刪除了 2 個元素,erase(4) 是刪除了 0 個元素,結果如下,

1
2
3
4
5
6
7
2
1 10
3 30
---
0
1 10
3 30

清空 multimap 容器

要清空 multimap 容器的的話,要使用 clear()

1
2
3
4
5
6
std::multimap<int, int> mmap;
mmap.insert(std::pair<int, int>(1, 10));
mmap.insert(std::pair<int, int>(2, 20));
mmap.insert(std::pair<int, int>(3, 30));

mmap.clear();

判斷 multimap 容器是否為空

要判斷 multimap 是否為空或是裡面有沒有元素的話,可以用 empty(),寫法如下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
std::multimap<int, int> mmap;

if (mmap.empty()) {
std::cout << "mmap is empty.\n";
} else {
std::cout << "mmap is not empty, size is " << mmap.size() << ".\n";
}

mmap.insert(std::pair<int, int>(1, 1));

if (mmap.empty()) {
std::cout << "mmap is empty.\n";
} else {
std::cout << "mmap is not empty, size is " << mmap.size() << ".\n";
}

輸出結果如下,insert() 一個元素後 size 變成 1,

1
2
mmap is empty.
mmap is not empty, size is 1.

計數 multimap 容器

要計算 multimap 裡某鍵值的數量有幾個的話,可以用 count(),寫法如下,

1
2
3
4
5
std::multimap<int, int> mmap{ {1, 10}, {2, 20}, {2, 23}, {3, 30} };

std::cout << mmap.count(1) << "\n";
std::cout << mmap.count(2) << "\n";
std::cout << mmap.count(3) << "\n";

輸出結果如下,鍵值為 2 的有兩個,

1
2
3
1
2
1

其它參考
std::multimap - cppreference.com
https://en.cppreference.com/w/cpp/container/multimap
multimap - C++ Reference
https://www.cplusplus.com/reference/map/multimap/
multimap 類別 | Microsoft Docs
https://docs.microsoft.com/zh-tw/cpp/standard-library/multimap-class?view=msvc-160

其它相關文章推薦
如果你想學習 C++ 相關技術,可以參考看看下面的文章,
C/C++ 新手入門教學懶人包
std::map 用法與範例
std::unordered_map 用法與範例
std::vector 用法與範例
std::deque 介紹與用法
std::queue 用法與範例

C++ std::weak_ptr 用法與範例

本篇 ShengYu 將介紹 C++ 的 std::weak_ptr 用法,

需要引入的標頭檔<memory>,編譯需要支援 C++11

weak_ptr 檢查參考的物件是否過期

用 weak_ptr 檢查參考的物件是否過期,如果該物件被釋放 (delete) 的話那麼表示已過期,

如下列範例所示,將 Size 物件從 shared_ptr 建立 weak_ptr wp 後,可以發現 wp.use_count() 是 1 不是 2,這時用 wp.expired() 來檢查是否過期,結果顯示還沒過期,接著將原本的 shared_ptr sp 指向 nullptr,那麼 reference count 降為 0 後 Size 物件會被 delete,接著再次使用 wp.expired() 來檢查是否過期,結果顯示已過期了,

std-std-weak_ptr.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// g++ std-std-weak_ptr.cpp -o a.out -std=c++11
#include <iostream>
#include <memory>

using namespace std;

class Size {
public:
Size() {}
Size(int w, int h) : width(w), height(h) {}

int width = 0;
int height = 0;
};

int main() {
shared_ptr<Size> sp = make_shared<Size>(10, 20);
weak_ptr<Size> wp(sp);

cout << wp.use_count() << "\n";

if (wp.expired()) { // 如果 wp 沒有指向任何物件的話
cout << "wp was expired\n";
} else {
cout << "wp does not expire\n";
}

sp = nullptr; // reference count 降為 0,Size 物件被 delete

if (wp.expired()) { // 如果 wp 沒有指向任何物件的話
cout << "wp was expired\n";
} else {
cout << "wp does not expire\n";
}

return 0;
}

輸出如下,

1
2
3
1
wp does not expire
wp was expired

從 weak_ptr 轉 shared_ptr

weak_ptr 轉 shared_ptr 有兩種方式,一種是使用 weak_ptr.lock,如果該物件還沒被釋放的話可以成功的轉換成 shared_ptr,如果該物件被釋放的話則會回傳 nullptr,

1
shared_ptr<Size> sp = wp.lock();

另一種方式是使用 shared_ptr 的建構子,

1
shared_ptr<Size> sp(wp);

其它參考
std::weak_ptr - cppreference.com
https://en.cppreference.com/w/cpp/memory/weak_ptr

其它相關文章推薦
C/C++ 新手入門教學懶人包
std::unique_ptr 用法與範例
std::shared_ptr 用法與範例

C/C++ 程式的 Parameter 與 Argument 差異

本篇 ShengYu 介紹 C/C++ 程式的 Parameter 與 Argument 差異,以前我不清楚 Parameter 與 Argument 這兩者的差異,直到有一次要寫 commit message 時,為了要明確地表達我的 commit 內容與意思,才真正地去理解這 Parameter & Argument 兩者的差異,Parameter 翻譯為參數,Argument 翻譯為引數,為了避免以後又混淆,這邊紀錄一下。

總結來說,Parameter (參數) 是函式宣告 (或函式簽章) 裡的變數,Argument (引數) 是表示呼叫函式時所帶入的變數或數值。

這邊來舉個例子幫助說明吧!
以下列子來說,在 main 主函式呼叫 add() 時所帶入的 a3 稱為引數 (Argument),而 xy 稱為 add() 的參數 (Parameter),

1
2
3
4
5
6
7
8
9
int add(int x, int y) {
// ...
}

int main() {
int a = 5;
int b = add(a, 3);
return 0;
}

以上就是 Parameter 與 Argument 的差異,也適用於其他程式語言。

另外我們平常在命令列下使用指令時,也會在指令後面帶入引數,例如下列中的 ls 指令,-l 為引數,

1
$ ls -l

或是下列中的 git 指令,log 為引數,

1
$ git log

其他參考文章
function - “Parameter” vs “Argument” - Stack Overflow
https://stackoverflow.com/questions/1788923/parameter-vs-argument
language agnostic - What’s the difference between an argument and a parameter? - Stack Overflow.
https://stackoverflow.com/questions/156767/whats-the-difference-between-an-argument-and-a-parameter

其它相關文章推薦
如果你想學習 C++ 相關技術,可以參考看看下面的文章,
C/C++ 新手入門教學懶人包

Github 如何更新 pull request

本篇 ShengYu 介紹 Github 如何更新 pull request,通常提交改動給原專案後,等 reviewer 看過沒問題後就會 merge,但是如果 reviewer 覺得哪些地方需要你再修改的話,你就需要學會如何更新你已經提交過的 pull request,

假設今天你已經有一個改動在 fix-xxx 的 branch 並且已經提交 pull request 了,
然後原專案 reviewer 需要你再做一些修改,
在 fix-xxx branch 修改完後直接使用下列指令 commit 起來,這樣的方式是修改原本的 commit 內容,
在很多時候原專案希望你的後續的改動不要另外新增一筆 commit 時就需要這麼做,

1
2
git add -A # 加入你的改動
git commit --amend # 修改前一次的 commit

再用強制更新的方式將改動推上遠端的 fix-xxx branch 去,

1
git push origin fix-xxx --force

接下來不用作任何事情,github 會自動將你的改動更新到原本的 pull request 去,
所以接下來去原專案的 pull request 頁面重新整理一下,就可以看到你的新的更新了。

其它參考
How to amend a commit on a GitHub Pull Request
https://www.burntfen.com/2015-10-30/how-to-amend-a-commit-on-a-github-pull-request
How to update a pull request ?
https://github.com/github/hub/issues/198
Github 發 Pull Request & 貢獻流程速查
https://gist.github.com/timdream/5968469

其它相關文章推薦
Github 提交你的修改貢獻到開源專案
Github 如何更新已經 fork 的專案與原專案紀錄同步
Git 顯示某個檔案的歷史修改記錄
Git 顯示整個repository的歷史修改記錄

C++ lhs 與 rhs 是什麼?

本篇 ShengYu 介紹 C++ 裡 lhs 與 rhs 是什麼?

那我們就開始吧!

看到參數名稱有 lhs 跟 rhs,lhs 全名是 left-hand side 指的是左側,想成等號的左側,同理,rhs 全名是 right-hand side 指的是右側,想成等號的右側,

以下舉個例子來說明 lhs 跟 rhs 的差異,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// g++ lhs-rhs.cpp -o a.out -std=c++11
#include <iostream>
using namespace std;

class Size {
public:
Size() {}
Size(int w, int h) : width(w), height(h) {}

int width = 0;
int height = 0;
};

inline bool operator==(const Size& lhs, const Size& rhs) {
cout << "lhs=" << lhs.width << "," << lhs.height << "\n";
cout << "rhs=" << rhs.width << "," << rhs.height << "\n";
return lhs.width == rhs.width && lhs.height == rhs.height;
}

int main() {
Size s1;
Size s2(10, 20);

if (s1 == s2) {
cout << "equal\n";
} else {
cout << "not equal\n";
}

return 0;
}

這邊寫個 Size 的 class,並且加上 operator== 可以比較兩個 Size 內容是否相同的功能,
所以 if (s1 == s2) 判斷式裡,s1 就是 lhs,s2 就是 rhs,
我們可以在 operator== 函式裡印出來就知道,結果如下,

1
2
3
lhs=0,0
rhs=10,20
not equal

這次我們加上 operator+= 來試試,使用 operator+= 來做 Size 的累加,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// g++ lhs-rhs-2.cpp -o a.out -std=c++11
#include <iostream>
using namespace std;

class Size {
public:
Size() {}
Size(int w, int h) : width(w), height(h) {}

int width = 0;
int height = 0;
};

inline bool operator==(const Size& lhs, const Size& rhs) {
cout << "lhs=" << lhs.width << "," << lhs.height << "\n";
cout << "rhs=" << rhs.width << "," << rhs.height << "\n";
return lhs.width == rhs.width && lhs.height == rhs.height;
}

inline Size& operator+=(Size& lhs, const Size& rhs) {
cout << "lhs=" << lhs.width << "," << lhs.height << "\n";
cout << "rhs=" << rhs.width << "," << rhs.height << "\n";
lhs.width += rhs.width;
lhs.height += rhs.height;
return lhs;
}

int main() {
Size s1;
Size s2(10, 20);

s1 += s2;
cout << "s1=" << s1.width << "," << s1.height << "\n";

Size s3(20, 40);
s3 += s2;
cout << "s3=" << s3.width << "," << s3.height << "\n";

return 0;
}

所以在 s1 += s2 運算式裡,,s1 就是 lhs,s2 就是 rhs,
而在 s3 += s2 運算式裡,,s3 就是 lhs,s2 就是 rhs,
我們可以印出結果試試,結果如下,

1
2
3
4
5
6
lhs=0,0
rhs=10,20
s1=10,20
lhs=20,40
rhs=10,20
s3=30,60

其他參考
opencv/modules/gapi/include/opencv2/gapi/own/types.hpp
https://github.com/opencv/opencv/blob/b3db37b99de4d2dcb8c4d8c7568029b3e06893b2/modules/gapi/include/opencv2/gapi/own/types.hpp

其它相關文章推薦
如果你想學習 C++ 相關技術,可以參考看看下面的文章,
C/C++ 新手入門教學懶人包
std::vector 用法與範例
std::deque 介紹與用法
std::queue 用法與範例
std::map 用法與範例
std::unordered_map 用法與範例
std::set 用法與範例
std::thread 用法與範例
std::mutex 用法與範例
std::find 用法與範例
std::sort 用法與範例
std::random_shuffle 產生不重複的隨機亂數
std::shared_ptr 用法與範例
std::async 用法與範例

C++ std::unordered_set 用法與範例

本篇 ShengYu 將介紹 C++ std unordered_set 用法與範例,C++ std::unordered_set 是一個關聯式容器,unordered_set 容器裡面的元素是唯一的,具有不重複的特性,而且是無排序的容器,unordered_set 容器裡面元素的值是不可修改,但 unordered_set 容器可以插入或刪除元素。

unordered_set 跟 set 不同之處是,set 是紅黑樹(red-black tree)實作,紅黑樹具有排序功能,
unordered_set 的實作方式通常是用雜湊表(hash table)實作的,資料插入和查詢的時間複雜度很低,為常數級別O(1),相對的代價是消耗較多的記憶體,空間複雜度較高,無自動排序功能。

以下 C++ unordered_set 用法與範例將分為這幾部分,

  • unordered_set 初始化用法
  • unordered_set 插入元素與讀取元素
  • 迴圈遍歷 unordered_set 容器
  • unordered_set 刪除指定元素
  • 清空 unordered_set 元素
  • 判斷 unordered_set 容器是否為空
  • unordered_set 判斷元素是否存在

要使用 unordered_set 容器的話,需要引入的標頭檔<unordered_set>

unordered_set 初始化用法

C++ unordered_set 初始化用法如下,

1
std::unordered_set<int> myunordered_set{1, 2, 3, 4, 5};

從 c-style 陣列來初始化

1
2
int arr[] = {1, 2, 3, 4, 5};
std::unordered_set<int> myunordered_set(arr, arr+5);

unordered_set 插入元素與讀取元素

unordered_set 插入元素的用法如下,

1
2
3
4
5
6
std::unordered_set<int> myunordered_set;
myunordered_set.insert(1);
myunordered_set.insert(2);
myunordered_set.insert(3);
myunordered_set.insert(4);
myunordered_set.insert(5);

由於 unordered_set 容器中沒有 at() 成員函數,也沒有 operator[],unordered_set 無法單純地隨機讀取某元素,但能透過 iterator 來讀取元素,可參考下節的介紹。

迴圈遍歷 unordered_set 容器

以下用迴圈來遍歷 unordered_set 容器並且印出來,這邊故意將元素不按順序初始化以及插入,然後我們再來觀察看看是不是 unordered_set 會將其排序,同時看看是不是具有不重複性,

std-unordered_set.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// g++ std-unordered_set.cpp -o a.out -std=c++11
#include <iostream>
#include <unordered_set>

int main() {
std::unordered_set<int> myunordered_set = {3, 1};
myunordered_set.insert(2);
myunordered_set.insert(5);
myunordered_set.insert(4);
myunordered_set.insert(5);
myunordered_set.insert(4);

for (const auto &s : myunordered_set) {
std::cout << s << " ";
}
std::cout << "\n";

return 0;
}

輸出內容如下,從這個輸出結果可以發現 unordered_set 容器裡元素是不會排序的,也沒有元素重複

1
4 5 2 1 3

迴圈也可以使用迭代器的方式,用法如下,

1
2
3
4
5
6
for (std::unordered_set<int>::iterator it = myunordered_set.begin(); it != myunordered_set.end(); it++) {
// or
// for (auto it = myunordered_set.begin(); it != myunordered_set.end(); it++) {
std::cout << *it << " ";
}
std::cout << "\n";

如果嫌 iterator 迭代器名稱太長的話可以善用 auto 關鍵字讓編譯器去推導該變數類型,
因為 unordered_set 沒有像 set 的 rbegin()rend() 可使用,自然就無法使用使用反向迭代器來反著印,

unordered_set 刪除指定元素

以下是 unordered_set 刪除指定元素的用法,unordered_set 刪除指定元素要使用 erase()

std-unordered_set2.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// g++ std-unordered_set2.cpp -o a.out -std=c++11
#include <iostream>
#include <unordered_set>

int main() {
std::unordered_set<int> myunordered_set{2, 4, 6, 8};

myunordered_set.erase(2);
for (const auto &s : myunordered_set) {
std::cout << s << " ";
}
std::cout << "\n";

return 0;
}

結果如下,

1
8 6 4

那 unordered_set 刪除不存在的元素呢?

std-unordered_set3.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// g++ std-unordered_set3.cpp -o a.out -std=c++11
#include <iostream>
#include <unordered_set>

int main() {
std::unordered_set<int> myunordered_set{2, 4, 6, 8};

auto ret = myunordered_set.erase(2);
std::cout << ret << "\n";
for (const auto &s : myunordered_set) {
std::cout << s << " ";
}
std::cout << "\n";

ret = myunordered_set.erase(3);
std::cout << ret << "\n";
for (const auto &s : myunordered_set) {
std::cout << s << " ";
}
std::cout << "\n";

return 0;
}

結果是可以這麼作的,不會發生什麼事。另外會回傳告訴你刪除了幾個元素。

1
2
3
4
1
8 6 4
0
8 6 4

unordered_set erase() 刪除元素還有另外兩種用法,關於這部分下次我再寫一篇給大家講解。

清空 unordered_set 元素

要清空 unordered_set 容器的的話,要使用 clear()

1
2
3
4
5
6
std::unordered_set<int> myunordered_set;
myunordered_set.insert(1);
myunordered_set.insert(2);
myunordered_set.insert(3);

myunordered_set.clear();

unordered_set 判斷元素是否存在

unordered_set 要判斷元素是否存在的話,可以使用 count(),存在回傳 1,不存在回傳 0,

1
2
3
4
5
6
std::unordered_set<int> myunordered_set;
myunordered_set.insert(2);
myunordered_set.insert(4);
myunordered_set.insert(6);
std::cout << myunordered_set.count(4) << "\n"; // 1
std::cout << myunordered_set.count(8) << "\n"; // 0

換成 char 字元試試,

1
2
3
4
5
6
7
std::unordered_set<char> myunordered_set;
myunordered_set.insert('a');
myunordered_set.insert('b');
myunordered_set.insert('c');
std::cout << myunordered_set.count('a') << "\n"; // 1
std::cout << myunordered_set.count('c') << "\n"; // 1
std::cout << myunordered_set.count('d') << "\n"; // 0

判斷 unordered_set 容器是否為空

要判斷 unordered_set 是否為空或是裡面有沒有元素的話,可以用 empty(),寫法如下,

std-unordered_set4.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// g++ std-unordered_set4.cpp -o a.out -std=c++11
#include <iostream>
#include <unordered_set>

int main() {
std::unordered_set<int> myunordered_set;
myunordered_set.clear();

if (myunordered_set.empty()) {
std::cout << "empty\n";
} else {
std::cout << "not empty, size is "<< myunordered_set.size() <<"\n";
}

return 0;
}

結果如下,

1
empty

以上就是 C++ std::unordered_set 用法與範例介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其它參考
unordered_set - C++ Reference
https://www.cplusplus.com/reference/unordered_set/unordered_set/
std::unordered_set - cppreference.com
https://en.cppreference.com/w/cpp/container/unordered_set
Unordered Sets in C++ Standard Template Library - GeeksforGeeks
https://www.geeksforgeeks.org/unordered_set-in-cpp-stl/

其它相關文章推薦
如果你想學習 C++ 相關技術,可以參考看看下面的文章,
C/C++ 新手入門教學懶人包
std::set 用法與範例
std::map 用法與範例
std::unordered_map 用法與範例
std::vector 用法與範例
std::deque 介紹與用法
std::queue 用法與範例
std::thread 用法與範例
std::mutex 用法與範例
std::find 用法與範例
std::sort 用法與範例
std::random_shuffle 產生不重複的隨機亂數
std::shared_ptr 用法與範例
std::async 用法與範例

Python OpenCV 影像垂直翻轉,水平翻轉 flip

本篇 ShengYu 介紹 Python OpenCV 來作影像垂直翻轉,影像水平翻轉 flip,在寫 Python 影像處理程式時遇到圖片顛倒需要翻轉的功能時,可以使用 OpenCV cv2.flip() 函式,接下來介紹怎麼使用 Python 搭配 OpenCV 模組來進行圖片垂直翻轉,水平翻轉 flip。

cv2.flip 垂直翻轉範例

以下範例是先將 lena.jpg 這張圖片透過 cv2.imread() 函式讀入後,再使用 cv2.flip() 作翻轉的處理,垂直翻轉的話就在 cv2.flip() 第二個參數帶入 0 即可,最後用 cv2.imshow() 顯示影像。詳細程式碼如下,

opencv-flip.py
1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import cv2

image = cv2.imread("lena.jpg")
cv2.imshow("before", image)

image2 = cv2.flip(image, 0) # 上下垂直翻轉

cv2.imshow("after", image2)
cv2.waitKey(0)

結果如下圖所示:

cv2.flip 水平翻轉範例

如果要水平翻轉或者是左右翻轉的話,就在 cv2.flip() 第二個參數帶入 1 即可,詳細程式碼如下,

opencv-flip2.py
1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import cv2

image = cv2.imread("lena.jpg")
cv2.imshow("before", image)

image2 = cv2.flip(image, 1) # 左右水平翻轉

cv2.imshow("after", image2)
cv2.waitKey(0)

結果如下圖所示:

cv2.flip 垂直水平翻轉範例

這邊結合上述兩種範例作垂直水平翻轉或者上下左右翻轉,在 cv2.flip() 第二個參數帶入 -1 即可,

opencv-flip3.py
1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import cv2

image = cv2.imread("lena.jpg")
cv2.imshow("before", image)

image2 = cv2.flip(image, -1) # 上下左右翻轉

cv2.imshow("after", image2)
cv2.waitKey(0)

結果如下圖所示:

cv2.flip 參數的詳細細節請參考這裡

其它相關文章推薦
如果你在學習 Python 或 OpenCV 影像處理相關技術,可以參考看看下面的文章,
Python OpenCV resize 圖片縮放
Python OpenCV 垂直vconcat 和水平hconcat 影像拼接
Python 新手入門教學懶人包

C/C++ 字串搜尋的3種方法

本篇 ShengYu 介紹 C/C++ 字串搜尋的3種方法,字串處理中字串搜尋是很常被使用的功能,例如:在檔案內容裡搜尋某個字串,瀏覽器上搜尋字串、文字編輯器上搜尋字串等等都是這個的應用,可見字串搜尋這功能多麼地頻繁與實用呀!在寫程式中字串搜尋是基本功夫,而且也蠻常會用到的,這邊紀錄我曾經用過與所知道的字串搜尋的幾種方式,
以下為 C/C++ 字串搜尋的內容章節,

  • C 語言的 strstr
  • C 語言的 strchr
  • C++ string 的 find()

那我們就開始吧!

C 語言的 strstr

C 語言要搜尋字串通常會使用 strstr,要使用 strstr 的話需要引入的標頭檔 <string.h>
strstr 函式原型為

1
char * strstr(char * str1, const char * str2);

strstr() 會將 str1 字串搜尋 str2 字串,有找到會回傳指向 str1 的指標並且指向第一次符合的位置,沒找到會回傳 NULL,來看看下面的 strstr 用法範例吧!

cpp-string-find.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// g++ cpp-string-find.cpp -o a.out
#include <stdio.h>
#include <string.h>

int main() {
char str[] ="This is a c-style string";
char *pch;
pch = strstr(str, "c-style");
if (pch != NULL)
printf("found: %s\n", pch);
else
printf("not found\n");

return 0;
}

結果如下,

1
found: c-style string

C 語言的 strchr

C 語言要在字串裡搜尋字元的話,除了自己寫迴圈處理外,也可以使用 C 語言提供的 strchr,
以下示範在字串裡搜尋 t 這個字元,第一次有成功找到,下一次要在搜尋時可以在找到的地方 pch+1 再往後搜尋,

cpp-string-find2.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// g++ cpp-string-find2.cpp -o a.out
#include <stdio.h>
#include <string.h>

int main() {
char str[] ="This is a c-style string";
char *pch;

// find first t
pch = strchr(str, 't');
if (pch != NULL)
printf("found at %d\n", pch-str+1);

// find second t
pch = strchr(pch+1, 't');
if (pch != NULL)
printf("found at %d\n", pch-str+1);

return 0;
}

結果輸出如下,

1
2
found at 14
found at 20

如果是使用 std::string 的話,在字串搜尋上就有一些方便的成員函式可以使用,以下介紹 C++ std::string 字串搜尋的方式,

C++ string 的 find()

這邊介紹 C++ string 的 find(),std::string 使用 find() 可以搜尋字串,有找到會回傳找到的位置,沒找到會回傳 string::npos,

cpp-string-find3.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// g++ cpp-string-find3.cpp -o a.out
#include <iostream>
#include <string>
using namespace std;

int main() {
string str = "This is a c++ string";

str.find("is");

size_t found = str.find("is");
if (found != string::npos) {
cout << "found at " << found << "\n";
}

return 0;
}

結果如下,

1
found at 2

其它參考
strstr - C++ Reference
https://www.cplusplus.com/reference/cstring/strstr/
strchr - C++ Reference
https://www.cplusplus.com/reference/cstring/strchr/
string::find - C++ Reference
https://www.cplusplus.com/reference/string/string/find/

其它相關文章推薦
如果你想學習 C++ 相關技術,可以參考看看下面的文章,
C/C++ 新手入門教學懶人包
C/C++ 字串比較的3種方法
C/C++ 字串連接的3種方法
C/C++ 字串分割的3種方法
std::find 用法與範例

C++ OpenCV 顯示camera攝影機串流影像

本篇 ShengYu 介紹如何用 C++ OpenCV 的 VideoCapture 來開啟攝影機並顯示攝影機串流的畫面。

使用範例

C++ OpenCV 要擷取攝影機影像,需要先建立一個 VideoCapture,可以參考下列範例中的 VideoCapture cap(0); 的初始化方式,
VideoCapture 建構子的參數代表攝影機裝置的代號(device index),如果有多台攝影機的話就可以從攝影機裝置的代號來指定,
但通常只有一台攝影機,所以這邊攝影機代號代號0,
之後使用 cap.isOpened() 來確認攝影機裝置有沒有開啟,之後在迴圈使用 cap.read() 每次從攝影機讀取一張影像,
來作進一步的影像處理,也可以用 cap >> frame; 這樣子的寫法來擷取影像,但是後面就要改成判斷 frame.empty() 回傳影像是否有擷取成功,這邊的例子簡單地使用 cv2.cvtColor() 的將影像從彩色轉成灰階,最後使用 imshow() 將影像顯示出來,
並且在迴圈內使用 waitKey(1) 等待按鍵事件發生,如果按下 q 鍵的話則 break 離開這個迴圈。
最後 VideoCapture 會自動在解構子裡釋放該攝影機裝置資源。

opencv-camera.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// g++ opencv-camera.cpp -o a.out `pkg-config --cflags --libs opencv`
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int main() {
VideoCapture cap(0);
if (!cap.isOpened()) {
cout << "Cannot open camera\n";
return 1;
}

Mat frame;
Mat gray;
//namedWindow("live", WINDOW_AUTOSIZE); // 命名一個視窗,可不寫
while (true) {
// 擷取影像
bool ret = cap.read(frame); // or cap >> frame;
if (!ret) {
cout << "Can't receive frame (stream end?). Exiting ...\n";
break;
}

// 彩色轉灰階
cvtColor(frame, gray, COLOR_BGR2GRAY);

// 顯示圖片
imshow("live", frame);
//imshow("live", gray);

// 按下 q 鍵離開迴圈
if (waitKey(1) == 'q') {
break;
}
}
// VideoCapture 會自動在解構子裡釋放資源
return 0;
}

其他參考
OpenCV: cv::VideoCapture Class Reference
https://docs.opencv.org/master/d8/dfe/classcv_1_1VideoCapture.html

其它相關文章推薦
Python OpenCV 顯示camera攝影機串流影像
在 Ubuntu 下寫第一支 OpenCV 程式