C/C++ Linux/Unix pthread 建立多執行緒用法與範例

本篇 ShengYu 要介紹 C/C++ Linux/Unix pthread 建立多執行緒用法與範例,

  • pthread 建立新 thread 來執行一個函式
  • pthread 建立新 thread 來執行一個函式,且帶入參數
  • pthread 建立新 thread 來執行一個類別函式
  • pthread detach 不等待 thread 執行結束
  • pthread 用陣列建立多個 thread

pthread 建立新 thread 來執行一個函式

pthread 要建立新 thread 的話使用的函式為 pthread_create,
第三個參數為執行緒的函式指標,要注意的是 foo 的函式參數與回傳值類型要符合 pthread_create 的規定,
最後需要使用 pthread_join 來等待 t1 執行完成,這表示主執行緒會停在這一行,直到 t1 thread 執行完成才會往下繼續執行,

cpp-pthread_create.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// g++ cpp-pthread_create.cpp -o a.out -pthread
#include <iostream>
#include <pthread.h>
using namespace std;

void * foo(void *arg) {
cout << "foo\n";
pthread_exit(NULL);
cout << "foo 2\n";
return NULL;
}

int main() {
pthread_t t1;
if (pthread_create(&t1, NULL, foo, NULL) != 0) {
cerr << "Error: pthread_create\n";
}

pthread_join(t1, NULL);

return 0;
}

輸出結果如下,

1
foo

pthread 建立新 thread 來執行一個函式,且帶入參數

這邊示範 pthread 建立新 thread 如何傳遞參數,分別是傳字串跟傳整數,

cpp-pthread_create2.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
// g++ cpp-pthread_create2.cpp -o a.out -pthread
#include <iostream>
#include <pthread.h>
using namespace std;

void * foo(void *arg) {
cout << "foo, " << (char *)arg << "\n";
return NULL;
}

void * bar(void *arg) {
int *n = (int *)arg;
cout << "bar, " << *n << "\n";
return NULL;
}

int main() {
pthread_t t1;
if (pthread_create(&t1, NULL, foo, (void *)"foo thread") != 0) {
cerr << "Error: pthread_create\n";
}

pthread_t t2;
int n = 10;
if (pthread_create(&t2, NULL, bar, &n) != 0) {
cerr << "Error: pthread_create\n";
}

pthread_join(t1, NULL);
pthread_join(t2, NULL);

return 0;
}

輸出結果如下,

1
2
foo, foo thread
bar, 10

pthread 建立新 thread 來執行一個類別函式

這邊示範 pthread_create() 在 class 裡建立 thread,並指定成員函式為 FOO::run()FOO::run() 需要宣告成 static,可參考下面範例的兩種方式寫法,

cpp-pthread_create-class.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
39
40
41
42
43
44
45
46
47
48
49
// g++ cpp-pthread_create-class.cpp -o a.out -pthread
#include <iostream>
#include <pthread.h>
using namespace std;

class FOO {
public:
FOO(int id) {
this->id = id;
cout << "FOO id = " << id << "\n";
}

void create_thread() {
if (pthread_create(&t1, NULL, run, this) != 0) {
cerr << "Error: pthread_create\n";
}
}

void join_thread() {
pthread_join(t1, NULL);
}
private:
//static void * run(void *arg);
static void * run(void *arg) {
FOO *self = (FOO *)arg;
cout << "run id = " << self->id << "\n";

return NULL;
}

pthread_t t1;
int id;
};

/*void * FOO::run(void *arg) {
FOO *self = (FOO *)arg;
cout << "run id = " << self->id << "\n";

return NULL;
}*/

int main() {
FOO foo(1);
foo.create_thread();

foo.join_thread();

return 0;
}

結果輸出如下,

1
2
FOO id = 1
run id = 1

跟上例不同,這邊示範 pthread_create() 在 class 外建立 thread,並指定成員函式為 FOO::run()

cpp-pthread_create-class2.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
39
40
41
// g++ cpp-pthread_create-class2.cpp -o a.out -pthread
#include <iostream>
#include <pthread.h>
using namespace std;

class FOO {
public:
FOO(int id) {
this->id = id;
cout << "FOO id = " << id << "\n";
}

//static void * run(void *arg);
static void * run(void *arg) {
FOO *self = (FOO *)arg;
cout << "run id = " << self->id << "\n";

return NULL;
}
private:
int id;
};

/*void * FOO::run(void *arg) {
FOO *self = (FOO *)arg;
cout << "run id = " << self->id << "\n";

return NULL;
}*/

int main() {
FOO foo(1);
pthread_t t1;
if (pthread_create(&t1, NULL, FOO::run, &foo) != 0) {
cerr << "Error: pthread_create\n";
}

pthread_join(t1, NULL);

return 0;
}

結果輸出同上。

pthread detach 不等待 thread 執行結束

如果主執行緒不想等或是可以不用等待 t1 執行緒的話,
就可以使用 pthread_detach() 來讓 t1 執行緒分離,接著主執行緒就可以繼續執行,t1執行緒也在繼續執行,
也可以在 foo 裡使用 pthread_detach() 使用 pthread_self() 傳入自身的 pthread_t 即可,

cpp-pthread_create-detach.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++ cpp-pthread_create-detach.cpp -o a.out -pthread
#include <iostream>
#include <pthread.h>
#include <unistd.h> // for sleep
using namespace std;

void * foo(void *arg) {
// pthread_detach(pthread_self());
cout << "foo 1\n";
sleep(1);
cout << "foo 2\n";
//pthread_exit(NULL);
return NULL;
}

int main() {
pthread_t t1;
if (pthread_create(&t1, NULL, foo, NULL) != 0) {
cerr << "Error: pthread_create\n";
}
cout << "main 1\n";
pthread_detach(t1);
cout << "main 2\n";
return 0;
}

輸出結果如下,

1
2
3
main 1
main 2
foo 1

pthread 用陣列建立多個 thread

這邊示範用陣列建立多個 thread,傳入的參數也變成陣列各自獨立,

cpp-pthread_create-array.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++ cpp-pthread_create-array.cpp -o a.out -pthread
#include <iostream>
#include <pthread.h>
using namespace std;

void * foo(void *arg) {
int n = *(int *)arg;
//cout << "foo, n=" << n << "\n" << endl;
printf("foo, n=%d\n", n);
return NULL;
}

int main() {
pthread_t threads[3];
int args[3];
for (int i = 0; i < 3; i++) {
args[i] = i;
if (pthread_create(&threads[i], NULL, foo, &args[i]) != 0) {
cerr << "Error: pthread_create\n";
}
}

for (int i = 0; i < 3; i++) {
pthread_join(threads[i], NULL);
}

return 0;
}

執行結果如下,

1
2
3
foo, n=1
foo, n=0
foo, n=2

這邊要注意一點的是,傳遞參數時需要注意變數的生命週期,否則會遇到該 thread 會拿到無效/非預期的參考資料,如下範例,

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
#include <iostream>
#include <pthread.h>
using namespace std;

typedef struct {
int id;
int data;
} ThreadArgs;

void * foo(void *args) {
ThreadArgs* threadArgs = (ThreadArgs*)args;
printf("id: %d, data: %d\n", threadArgs->id, threadArgs->data);

pthread_exit(NULL);
return NULL;
}

int main() {
pthread_t threads[3];

for (int i = 0; i < 3; i++) {
ThreadArgs args;
args.id = i;
args.data = i * 2;
if (pthread_create(&threads[i], NULL, foo, &args) != 0) {
cerr << "Error: pthread_create\n";
}
}

for (int i = 0; i < 3; i++) {
pthread_join(threads[i], NULL);
}

return 0;
}

輸出結果可能如下,

1
2
3
id: 2, data: 4
id: 2, data: 4
id: 2, data: 4

修正後的作法應該會變成這樣寫,

cpp-pthread_create-array2.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++ cpp-pthread_create-array2.cpp -o a.out -pthread
#include <iostream>
#include <pthread.h>
using namespace std;

typedef struct {
int id;
int data;
} ThreadArgs;

void * foo(void *args) {
ThreadArgs* threadArgs = (ThreadArgs*)args;
printf("id: %d, data: %d\n", threadArgs->id, threadArgs->data);

pthread_exit(NULL);
return NULL;
}

int main() {
pthread_t threads[3];
ThreadArgs* args[3];

for (int i = 0; i < 3; i++) {
args[i] = (ThreadArgs*)malloc(sizeof(ThreadArgs));
args[i]->id = i;
args[i]->data = i * 2;
if (pthread_create(&threads[i], NULL, foo, args[i]) != 0) {
cerr << "Error: pthread_create\n";
}
}

for (int i = 0; i < 3; i++) {
pthread_join(threads[i], NULL);
free(args[i]);
}

return 0;
}

輸出結果如下,這次結果就符合我們的預期了,

1
2
3
id: 0, data: 0
id: 1, data: 2
id: 2, data: 4

以上就是 C/C++ Linux/Unix pthread 建立多執行緒用法與範例介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其它相關文章推薦
C/C++ 新手入門教學懶人包
C/C++ Linux pthread_join 用法與範例
C/C++ Linux pthread_exit 用法與範例
C/C++ Linux pthread_attr_setstacksize 設定執行緒堆疊大小用法
C/C++ sleep 用法與範例
C++ std::thread 建立多執行緒用法與範例

C/C++ dump stack backtrace 的方法

本篇 ShengYu 介紹 C/C++ dump stack backtrace 的方法,

C/C++ 印出 backtrace 的方法如下,

cpp-backtrace.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
39
// g++ -g -rdynamic cpp-backtrace.cpp -o a.out
#include <stdio.h>
#include <stdlib.h>
#include <execinfo.h>

void print_backtrace() {
void *buffer[64];
char **symbols;

int num = backtrace(buffer, 64);
printf("backtrace() returned %d addresses\n", num);

symbols = backtrace_symbols(buffer, num);
if (symbols == NULL) {
perror("backtrace_symbols");
exit(EXIT_FAILURE);
}

for (int j = 0; j < num; j++)
printf("%s\n", symbols[j]);

free(symbols);
}

void myfunc3() {
print_backtrace();
}

void myfunc2() {
myfunc3();
}

void myfunc1() {
myfunc2();
}

int main() {
myfunc1();
}

輸出如下,

1
2
3
4
5
6
7
8
backtrace() returned 7 addresses
./a.out(_Z15print_backtracev+0x2e) [0x400a94]
./a.out(_Z7myfunc3v+0x9) [0x400b79]
./a.out(_Z7myfunc2v+0x9) [0x400b85]
./a.out(_Z7myfunc1v+0x9) [0x400b91]
./a.out(main+0x9) [0x400b9d]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7fb39bc04840]
./a.out(_start+0x29) [0x400999]

addr2line 來看 0x400a94 address 是在程式碼的那一行,

1
2
3
addr2line -fie a.out 0x400a94
_Z15print_backtracev
cpp-backtrace.cpp:9

發現輸出結果 _Z15print_backtracev 看得似懂非懂,addr2line 加上 -C 來解析 C++ 的名稱,這樣就解決了!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
addr2line -Cfie a.out 0x400a94
print_backtrace()
cpp-backtrace.cpp:9
$ addr2line -Cfie a.out 0x400b79
myfunc3()
cpp-backtrace.cpp:30
$ addr2line -Cfie a.out 0x400b85
myfunc2()
cpp-backtrace.cpp:34
$ addr2line -Cfie a.out 0x400b91
myfunc1()
cpp-backtrace.cpp:38
$ addr2line -Cfie a.out 0x400b9d
main
cpp-backtrace.cpp:42

這篇是乾脆就順便用 abi::__cxa_demangle() 直接解析出 C++ 的符號了,過程中需要自行從 symbols 裡的 mangled function name 擷取出來後在執行 abi::__cxa_demangle(),還蠻費工的~

其它參考
Unix backtrace
https://man7.org/linux/man-pages/man3/backtrace.3.html
Windows CaptureBackTrace
http://msdn.microsoft.com/en-us/library/windows/desktop/bb204633(v=vs.85).aspx
C++ Code Snippet - Print Stack Backtrace Programmatically with Demangled Function Names
https://panthema.net/2008/0901-stacktrace-demangled/
C++的backtrace
https://owent.net/2018/1801.html

C/C++ 捕捉 ctrl+c 事件的 2 種方法

本篇 ShengYu 介紹 C/C++ 捕捉 ctrl+c 事件的 2 種方法,第 1 種是 signal 方式,第 2 種是 sigaction 方式,

signal 方式

這個方式適用於大多數系統,這是標準 C 函式庫就有支援,我們這邊要捕捉的是 ctrl+c 的事件,對應的是 SIGINT signal,
所以我們需要的是使用 signal() 函式註冊 SIGINT 這個事件發生時,要來執行我們預先定義好的 signal_handler 函式,
注意這邊的 sig_handler 是有註冊成功的 signal 才會進來,例如我這邊只有註冊 SIGINT,所以 SIGINT 這個 signal 會進來 sig_handler,其他的像 SIGABRT、SIGSEGV、SIGILL、SIGTERM 等等沒註冊的自然不會進 sig_handler,

cpp-signal-sigint.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++ cpp-signal-sigint.cpp -o a.out -std=c++11
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <thread> // for std::this_thread::sleep_for

void signal_handler(int signum) {
printf("signal_handler: caught signal %d\n", signum);
if (signum == SIGINT) {
printf("SIGINT\n");
exit(1);
}
}

int main() {
if (signal(SIGINT, signal_handler) == SIG_ERR) {
printf("Failed to caught signal\n");
}

while(1) {
std::this_thread::sleep_for(std::chrono::seconds(1));
}

return 0;
}

輸出如下,按下 Ctrl+C 後會看到 ^C 字樣,接著就進入到我們定義的 signal_handler 函式裡,然後判斷是 SIGINT 就離開程式,

1
2
^Csignal_handler: caught signal 2
SIGINT

sigaction 方式

這邊我們示範另外一種 sigaction 的方式,比上述的 signal 還強大,能處理更複雜的功能,不過我們這邊只是要用來攔住 ctrl+c 的事件而已,用法如下,
事先需要先初始化好 sigaction 結構的變數,再用 sigaction() 來註冊 SIGINT 的事件,這個事件發生時就會來執行我們預先定義好的 signal_handler 函式,

cpp-sigaction-sigint.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
// g++ cpp-sigaction-sigint.cpp -o a.out -std=c++11
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <thread> // for std::this_thread::sleep_for

void signal_handler(int signum) {
printf("signal_handler: caught signal %d\n", signum);
if (signum == SIGINT) {
printf("SIGINT\n");
exit(1);
}
}

int main() {
struct sigaction sa;
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;

if (sigaction(SIGINT, &sa, NULL) == -1) {
printf("Failed to caught signal\n");
}

while(1) {
std::this_thread::sleep_for(std::chrono::seconds(1));
}

return 0;
}

效果是跟前一個方法一樣。

其它參考
c++ - How can I catch a ctrl+c event? - Stack Overflow
https://stackoverflow.com/questions/1641182/how-can-i-catch-a-ctrl+c-event

其它相關文章推薦
Python 捕捉 ctrl+c 事件的方法
C/C++ 新手入門教學懶人包

Python hex string to int 16進位字串轉數字

本篇介紹 Python hex string to int 16進位字串轉數字,Python 沒有所謂的 hex 的變數型態,所以 16 進位都是要轉換到 int 去作運算,之後要顯示成 16 進位的話在轉成16進位的字串即可。

16進位字串轉成 int

16進位前面有個0x開頭用來表示16進位,如果想要將16進位的字串轉成 Python 的數字類型作運算的話,需要先作轉換
int() 可以將字串轉成整數數字,預設可以傳入基數 base 10進位的數字,要傳入基數 base 16進位的數字的話,要在第二個參數指定16,
hex() 可以將10進位的數字轉成16進位表示的字串,

python3-hex-to-int.py
1
2
3
4
5
6
7
8
9
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

hex_string = '0xff'
number = int(hex_string, 16)
print(number)
s = hex(number)
print(s)
print(type(s))

結果如下,

1
2
3
255
0xff
<class 'str'>

16進位字串轉成 int 作運算

這邊示範16進位字串轉成 int 作運算,最後在轉成字串印出來,

python3-hex-to-int2.py
1
2
3
4
5
6
7
8
9
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

hex_string = '0xa'
number = int(hex_string, 16)
print(number)
s = hex(number + 0xa)
print(s)
print(type(s))

結果如下,

1
2
3
10
0x14
<class 'str'>

其它相關文章推薦
Python 新手入門教學懶人包

C/C++ 二元搜尋法 Binary Search

本篇 ShengYu 介紹 C/C++ 二元搜尋法 Binary Search。

C/C++ Binary Search 二元搜尋法(迴圈版本)

這篇介紹 C/C++ Binary Search 二元搜尋法迴圈的版本,遞迴的版本下一節會介紹到,要使用二元搜尋法前有個前提,就是必須要確認資料是已排序過的,如果資料未排序可以用 sort 來先排序,接著使用迴圈每次將 key 值與中間值作比較,如果 key 值大於中間值的話,下次就往右側搜尋,右側為值較大的一邊,否則就往左側搜尋,左側為值較小的一邊,這的動作一直持續到比對到符合的 key 值或者迴圈結束未找到 key 值,範例如下,

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++ cpp-binary-search.cpp -o a.out -std=c++11
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int binary_search(const vector<int> &data, int key) {
int low = 0;
int high = data.size()-1;
while (low <= high) {
int mid = int((low + high) / 2);
if (key == data[mid])
return mid;
else if (key > data[mid])
low = mid + 1;
else
high = mid - 1;
}
return -1;
}

int main() {
vector<int> data = {1, 9, 2, 7, 4, 10, 3, 8, 5, 6};
int key = 7;

sort(data.begin(), data.end());

for (auto &i : data)
cout << i << " ";
cout << "\n";

int ret = binary_search(data, key);
if (ret == -1)
cout << "找不到\n";
else
cout << "找到索引值" << ret << "\n";
}

輸出結果如下,

1
2
1 2 3 4 5 6 7 8 9 10 
找到索引值6

C/C++ Binary Search 二元搜尋法(遞迴版本)

剛剛介紹的二元搜尋法是迴圈的版本,這邊要介紹二元搜尋法遞迴版本,遞迴的版本要稍微修改一下,思考方式也要稍微轉換一下,

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
// g++ cpp-binary-search2.cpp -o a.out -std=c++11
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int binary_search(const vector<int> &data, int low, int high, int key) {
if (low > high) {
return -1;
}

int mid = int((low + high) / 2);
if (key == data[mid])
return mid;
else if (key > data[mid]) {
low = mid + 1;
return binary_search(data, low, high, key);
} else {
high = mid - 1;
return binary_search(data, low, high, key);
}
}

int main() {
vector<int> data = {1, 9, 2, 7, 4, 10, 3, 8, 5, 6};
int key = 7;

sort(data.begin(), data.end());

for (auto &i : data)
cout << i << " ";
cout << "\n";

int ret = binary_search(data, 0, data.size()-1, key);
if (ret == -1)
cout << "找不到\n";
else
cout << "找到索引值" << ret << "\n";
}

輸出結果如下,

1
2
1 2 3 4 5 6 7 8 9 10 
找到索引值6

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

Python random.randint 產生隨機數字

本篇介紹 Python random.randint 產生隨機數字,

Python random.randint 產生隨機數字要用 randint() 來產生,randint() 會產生指定範圍內的隨機整數,傳入最小值與最大值,這樣生成的隨機數就會在這個範圍內,如下範例的產生 1~10 範圍內的隨機數字(包含1和10),

python3-random-randint.py
1
2
3
4
5
6
7
8
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import random

min = 1
max = 10
ret = random.randint(min, max)
print(ret)

某一次產生的結果如下,

1
2

以下示範多次產生隨機數字,

python3-random-randint2.py
1
2
3
4
5
6
7
8
9
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import random

min = 1
max = 10
for i in range(5):
ret = random.randint(min, max)
print(ret)

結果如下,

1
2
3
4
5
10
6
7
4
2

其它相關文章推薦
Python 產生 random 隨機不重複的數字 list
Python 新手入門教學懶人包
Python 寫檔,寫入 txt 文字檔
Python 讀取 csv 檔案
[Python] PyAutoGUI 使用教學
Python OpenCV resize 圖片縮放

C/C++ 循序搜尋法 Sequential Search

本篇 ShengYu 介紹 C/C++ 循序搜尋法 Sequential Search。

C/C++ Sequential Search 循序搜尋法

以下為 C/C++ 循序搜尋法的實作,要從串列 data 裡尋找數字 7,循序搜尋法是最簡單的實作方式,主要是由一個主迴圈從頭掃到尾,一個一個值拿來跟 key 作比對,一直到有比對成功救回傳索引值或者一直到結束都沒有比對成功,沒有比對成功的話就回傳 -1,範例如下,

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++ cpp-sequential-search.cpp -o a.out -std=c++11
#include <iostream>
#include <vector>
using namespace std;

int sequential_search(const vector<int> &data, int key) {
for (int i = 0; i < data.size(); ++i) {
if (key == data[i]) {
return i;
}
}
return -1;
}

int main() {
vector<int> data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int key = 7;

for (auto &i : data)
cout << i << " ";
cout << "\n";

int ret = sequential_search(data, key);
if (ret == -1)
cout << "找不到\n";
else
cout << "找到索引值" << ret << "\n";
}

結果如下,

1
2
1 2 3 4 5 6 7 8 9 10 
找到索引值6

使用循序搜尋法時資料不需要先排序過

使用循序搜尋法前時資料不需要先排序過就可以直接搜尋了,跟二元搜尋法比較不一樣,範例如下,

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++ cpp-sequential-search.cpp -o a.out -std=c++11
#include <iostream>
#include <vector>
using namespace std;

int sequential_search(const vector<int> &data, int key) {
for (int i = 0; i < data.size(); ++i) {
if (key == data[i]) {
return i;
}
}
return -1;
}

int main() {
vector<int> data = {1, 9, 2, 7, 4, 10, 3, 8, 5, 6};
int key = 7;

for (auto &i : data)
cout << i << " ";
cout << "\n";

int ret = sequential_search(data, key);
if (ret == -1)
cout << "找不到\n";
else
cout << "找到索引值" << ret << "\n";
}

結果如下,

1
2
1 9 2 7 4 10 3 8 5 6 
找到索引值3

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

Python import 用法與範例

本篇介紹 Python import 用法與範例,

導入模組

語法如下,

1
import 模組名稱

例如我有個 AAA 模組裡有 func1 函式
這樣就可以用 AAA.func1() 的方式來使用,

導入模組內某一個函式

1
from 模組名稱 import 函式名稱

這樣就可以不用再寫 AAA 前綴字,直接呼叫 func1() 即可,

導入模組內多個函式

1
from 模組名稱 import 函式名稱1, 函式名稱2, 函式名稱3

這樣就可以不用再寫 AAA 前綴字,直接呼叫 func1()func2()func3() 即可,

導入模組內所有函式

1
from 模組名稱 import *

這樣就可以不用再寫 AAA 前綴字,直接呼叫 AAA 裡的任一函式即可,

其它相關文章推薦
Python 新手入門教學懶人包
Python 寫檔,寫入 txt 文字檔
[Python] 讀取 csv 檔案
[Python] 寫入 csv 檔案
[Python] 讀寫檔案
[Python] 產生 random 隨機不重複的數字 list
[Python] PyAutoGUI 使用教學
Python OpenCV resize 圖片縮放

Python 二元搜尋法 Binary Search

本篇 ShengYu 介紹 Python 二元搜尋法 Binary Search。

Python Binary Search 二元搜尋法(迴圈版本)

這篇介紹 C/C++ Binary Search 二元搜尋法迴圈的版本,遞迴的版本下一節會介紹到,要使用二元搜尋法前有個前提,就是必須要確認資料是已排序過的,如果資料未排序可以用 sort 來先排序,接著使用迴圈每次將 key 值與中間值作比較,如果 key 值大於中間值的話,下次就往右側搜尋,右側為值較大的一邊,否則就往左側搜尋,左側為值較小的一邊,這的動作一直持續到比對到符合的 key 值或者迴圈結束未找到 key 值,範例如下,

python3-binary-search.py
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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

def binary_search(data, key):
low = 0
high = len(data)-1
while low <= high:
mid = int((low + high) / 2)
if key == data[mid]:
return mid
elif key > data[mid]:
low = mid + 1
else:
high = mid - 1
return -1

data = [1, 9, 2, 7, 4, 10, 3, 8, 5, 6]
key = 7
data.sort()
print(data)
ret = binary_search(data, key)
if ret == -1:
print('找不到')
else:
print('找到索引值' + str(ret))

輸出結果如下,

1
2
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
找到索引值6

Python Binary Search 二元搜尋法(遞迴版本)

剛剛介紹的二元搜尋法是迴圈的版本,這邊要介紹二元搜尋法遞迴版本,遞迴的版本要稍微修改一下,思考方式也要稍微轉換一下,

python3-binary-search2.py
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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

def binary_search(data, low, high, key):
if low > high:
return -1

mid = int((low + high) / 2)
if key == data[mid]:
return mid
elif key > data[mid]:
low = mid + 1
return binary_search(data, low, high, key)
else:
high = mid - 1
return binary_search(data, low, high, key)

data = [1, 9, 2, 7, 4, 10, 3, 8, 5, 6]
key = 7
data.sort()
print(data)
ret = binary_search(data, 0, len(data)-1, key)
if ret == -1:
print('找不到')
else:
print('找到索引值' + str(ret))

輸出結果如下,

1
2
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
找到索引值6

其它相關文章推薦
Python 循序搜尋法 Sequential Search
Python 新手入門教學懶人包

Python tkinter Canvas 用法與範例

本篇 ShengYu 介紹 Python tkinter Canvas 用法與範例,在 Python tkinter 裡可以使用 Canvas 來進行圖形的繪製,透過繪製一些基本的點、直線、矩形、圓型等等,進而組成自己需要的圖形,

以下的 Python tkinter Canvas 用法與範例將分為這幾部分,

  • 建立 tkinter Canvas
  • tkinter Canvas create_text 繪製文字
  • tkinter Canvas create_line 繪製直線
  • tkinter Canvas create_rectangle 繪製矩形
  • tkinter Canvas create_oval 繪製圓形、橢圓
  • tkinter Canvas create_arc 繪製圓弧
  • tkinter Canvas create_polygon 繪製多邊形
  • tkinter Canvas create_image 繪製影像

建立 tkinter Canvas

tkinter 簡單建立 Canvas 的用法如下,

python3-canvas.py
1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import tkinter as tk

root = tk.Tk()
root.title('my window')

mycanvas = tk.Canvas(root, width=300, height=200)
mycanvas.pack()

root.mainloop()

結果圖如下,

tkinter Canvas create_text 繪製文字

tkinter Canvas 繪製文字帶入的(x,y)座標是文字中心的座標,而不是文字的左上角座標,這點跟其它大部分 GUI api 比較不一樣,但是我們可以設定 anchor 參數來改變這件事,anchor=’nw’ 就可以將該座標表示為繪製的文字的左上表座標,如下例中的 123456 文字,

python3-canvas-create_text.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import tkinter as tk

root = tk.Tk()
root.title('my window')

mycanvas = tk.Canvas(root, width=300, height=200)
mycanvas.pack()

mycanvas.create_text(100, 50, text='Hello World')
mycanvas.create_text(100, 100, text='Python tkinter', font=('Arial', 18))
mycanvas.create_text(0, 0, text='123456', anchor='nw')

root.mainloop()

結果圖如下,

tkinter Canvas create_line 繪製直線

tkinter Canvas create_line 繪製直線時需要傳入兩組座標以上 (x1, y1) 與 (x2, y2) … (xn, yn),dash 參數是設定成虛線像式,width 參數是設定線條寬度,fill 參數則是設定線條顏色,

python3-canvas-create_line.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import tkinter as tk

root = tk.Tk()
root.title('my window')

mycanvas = tk.Canvas(root, width=300, height=200)
mycanvas.pack()

mycanvas.create_line(20, 20, 280, 20)
mycanvas.create_line(20, 40, 280, 40, dash=(4, 4))
mycanvas.create_line(20, 60, 280, 60, width=5)
mycanvas.create_line(20, 80, 280, 80, fill='red')

root.mainloop()

結果圖如下,

tkinter Canvas create_rectangle 繪製矩形

tkinter Canvas create_rectangle 繪製矩形時是帶入兩個座標 (x1, y1) 與 (x2, y2),(x1, y1) 為矩形的左上角座標,(x2, y2) 為矩形的右下角座標,dash 參數是設定成虛線像式,fill 參數則是設定矩形內的填充顏色,outline 參數是設定矩形的線條顏色,

python3-canvas-create_rectangle.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import tkinter as tk

root = tk.Tk()
root.title('my window')

mycanvas = tk.Canvas(root, width=400, height=200)
mycanvas.pack()

mycanvas.create_rectangle(10, 10, 90, 100)
mycanvas.create_rectangle(110, 10, 190, 100, dash=(4, 4))
mycanvas.create_rectangle(210, 10, 290, 100, fill='red')
mycanvas.create_rectangle(310, 10, 390, 100, outline='blue')

root.mainloop()

結果圖如下,

tkinter Canvas create_oval 繪製圓形、橢圓

tkinter Canvas 繪製圓形、橢圓時都是使用 create_oval 函式,傳入的 (x1, y1) 與 (x2, y2) 分別為圓形/橢圓外圍的矩形的左上角座標與右下角座標,fill 參數是設定圓形或橢圓內的填充顏色,outline 則是設定圓形或橢圓的線條顏色,

python3-canvas-create_oval.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import tkinter as tk

root = tk.Tk()
root.title('my window')

mycanvas = tk.Canvas(root, width=320, height=200)
mycanvas.pack()

mycanvas.create_oval(10, 10, 100, 100) # 圓形
mycanvas.create_oval(110, 10, 200, 100, fill='red') # 圓形
mycanvas.create_oval(210, 10, 300, 100, outline='blue') # 圓形
mycanvas.create_oval(10, 110, 290, 190) # 橢圓

root.mainloop()

結果圖如下,

tkinter Canvas create_arc 繪製圓弧

tkinter Canvas 繪製圓弧是使用 create_arc 函式,傳入的 (x1, y1) 與 (x2, y2) 分別為矩形的左上角座標與右下角座標,extent 參數是繪製圓弧的角度,範圍值是 1-359,0 跟 360 則會變成直線,預設是90,style 參數有三種樣式,ARC PIESLICE CHORD 三種可以選擇,預設是 PIESLICE。

python3-canvas-create_arc.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import tkinter as tk

root = tk.Tk()
root.title('my window')

mycanvas = tk.Canvas(root, width=350, height=200)
mycanvas.pack()

mycanvas.create_arc(10, 10, 100, 100)
mycanvas.create_arc(110, 10, 200, 100, extent=45)
mycanvas.create_arc(210, 10, 300, 100, extent=180)

mycanvas.create_arc(10, 110, 100, 210, style=tk.ARC)
mycanvas.create_arc(110, 110, 200, 210, style=tk.PIESLICE)
mycanvas.create_arc(210, 110, 300, 210, style=tk.CHORD)

root.mainloop()

結果圖如下,

tkinter Canvas create_polygon 繪製多邊形

tkinter Canvas 繪製多邊形是使用 create_polygon 函式,需要傳入兩組座標以上 (x1, y1) 與 (x2, y2) … (xn, yn),fill 參數是設定多邊形內的填充顏色,outline 則是設定多邊形的線條顏色,

python3-canvas-create_polygon.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import tkinter as tk

root = tk.Tk()
root.title('my window')

mycanvas = tk.Canvas(root, width=300, height=200)
mycanvas.pack()

mycanvas.create_polygon(40,40, 60,20, 80,40, 80,80, 40,80)
mycanvas.create_polygon(100,40, 120,20, 140,40, 140,80, 100,80, fill='', outline='black')
mycanvas.create_polygon(160,80, 200,80, 180,20, fill='yellow')
mycanvas.create_polygon(220,80, 260,80, 240,20, fill='red', outline='black')

root.mainloop()

結果圖如下,

tkinter Canvas create_image 繪製影像

由於 tkinter 的 PhotoImage 只能支援 GIF 與 PGM/PPM 的圖片格式,要讀取 jpg 的話就要花點功夫,這邊我們使用 PIL 套件來輔助完成,
如果你之前還沒安裝過 Python 的 PIL 套件的話,可以透過 sudo apt install python3-pil 指令來安裝,這邊還需要安裝 PIL 與 tkinter 的影像轉換套件 ImageTk,透過 sudo apt install python3-pil.imagetk 即可完成安裝,

在繪製影像時可以調整 anchor 參數,anchor 參數是設定影像輸出在該區域的位置,預設是 anchor=tk.CENTER 表示會以傳入的座標為影像中心,這邊我們改成 anchor=tk.NW 表示傳入的該座標為影像的左上角,如下例中的 (0,0) 為影像的左上角。

NW:左上
N:上
NE:右上
W:左
CENTER:中
E:右
SW:左下
S:下
SE:右下
tkinter 允許你可以用小寫的字串來作設定,例如:anchor='nw'

python3-canvas-create_image.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import tkinter as tk
from PIL import Image, ImageTk

root = tk.Tk()
root.title('my window')

img = Image.open('lena.jpg')
img2 = ImageTk.PhotoImage(img)
#img2 = tk.PhotoImage(file='lena.gif')

mycanvas = tk.Canvas(root, width=img.size[0], height=img.size[1])
mycanvas.pack()

mycanvas.create_image(0,0, anchor=tk.NW, image=img2)

root.mainloop()

結果圖如下,

以上就是 Python tkinter Canvas 用法與範例介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!
對 PIL 有興趣的可以參考這邊的文章

其它相關文章推薦
Python 新手入門教學懶人包
Python tkinter 新手入門教學