Python 檢查 dict 字典是否為空

本篇介紹如何在 python 檢查 dict 字典是否為空,

使用 not operator 運算子

使用 not operator 運算子來檢查 dict 字典是否為空,
同時也是官方推薦的作法,

1
2
3
4
5
6
7
8
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
mydict = {} # or mydict = dict()
print(type(mydict))
print(mydict)

if not mydict:
print('mydict is empty')

結果輸出:

1
2
3
<class 'dict'>
{}
mydict is empty

使用 len 判斷長度

使用 len() 函式來檢查 dict 字典是否為空,

1
2
3
4
5
6
7
8
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
mydict = dict() # or mydict = {}
print(type(mydict))
print(len(mydict))

if len(mydict) == 0:
print('mydict is empty')

結果輸出:

1
2
3
<class 'dict'>
0
mydict is empty

其它相關文章推薦
如果你想學習 Python 相關技術,可以參考看看下面的文章,
Python 新手入門教學懶人包
Python dict 字典用法與範例

C++ explicit 用法與範例

本篇 ShengYu 介紹 C++ explicit 用法與範例,C++ 裡有隱性轉換 (implicit conversion) 跟顯性轉換 (explicit conversion),今天來介紹什麼是隱性轉換?什麼是顯性轉換?並且示範一下這兩者的差異。

C++ explicit 基本用法與範例

在 C++ 中 explicit 這個關鍵字最常出現在建構子前面,我們就來介紹這者差異是什麼,如下列範例所示,MyInteger n1 = 5; 寫法是數值寫在等號右邊,編譯器會自動幫你去轉換成 MyInteger(int n),這個轉換就是隱性轉換,而 MyInteger n2(6); 寫法是很明確地直接呼叫建構子,這個轉換就是顯性轉換,這就是這兩種的差異。

以下這個範例示範支援隱性轉換和顯性轉換的寫法,

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

class MyInteger {
public:
MyInteger(int n) : data(n) {
cout << "data = " << data << "\n";
}
private:
int data;
};

int main() {
MyInteger n1 = 5;
MyInteger n2(6);
return 0;
}

輸出如下,

1
2
data = 5
data = 6

那今天我們想要禁止隱性轉換這種寫法的話只需要在 MyInteger(int n) 建構子前面加上 explicit 關鍵字,告訴編譯器只能用顯性轉換,那這樣的隱性轉換寫法編譯器就會在編譯時期檢查時就會擋下來,出現編譯錯誤給開發者知道,這樣我們就能夠成功地防止開發者去寫這種程式碼,

1
2
3
4
cpp-explicit.cpp: In function ‘int main()’:
cpp-explicit.cpp:15:20: error: conversion from ‘int’ to non-scalar type ‘MyInteger’ requested
MyInteger n1 = 5;
^

接著開發者只能寫 MyInteger n2(6); 呼叫建構子 direct-initialization 的寫法了,而原本的 MyInteger n1 = 5; copy-initialization 寫法就不行使用了,如下所示,

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

class MyInteger {
public:
explicit MyInteger(int n) : data(n) {
cout << "data = " << data << "\n";
}
private:
int data;
};

int main() {
//MyInteger n1 = 5;
MyInteger n2(6);
return 0;
}

輸出如下,

1
data = 6

C++ explicit 實際範例

剛剛上述已經了解了隱性轉換 implicit conversion 跟顯性轉換 explicit conversion 的差異,但什麼時候會需要用到 explicit 呢?這種情形大概常發生在以下多個建構子情形中,
如下列範例所示,有一個 MyString 類別裡有兩種建構子,一種是傳入整數 n 以便動態記憶體配置 n 的大小的 char,另一種是傳入指向字元陣列的指標然後在裡面複製一份,

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

class MyString {
public:
MyString(int n) { // allocate n bytes to the MyString object
cout << "MyString(int n)\n";
}

MyString(const char *p) { // initializes object with char *p
cout << "MyString(const char *p)\n";
}
};

int main() {
MyString s1 = 10;
MyString s2(10);
MyString s3 = "hello world";
MyString s4("123456");
return 0;
}

輸出如下,那我們目前可以接受這四種初始化寫法,以及實際上這四種寫法對應到的哪個建構子,

1
2
3
4
MyString(int n)
MyString(int n)
MyString(const char *p)
MyString(const char *p)

可是 s1 的寫法讓人容易誤會,跟 s4 很像,為了不讓其他開發者混淆,所以我想避免這種隱式轉換,那麼我就在 MyString(int n) 前加上 explicit,結果就變成下面這樣的例子,為了更好理解這個範例,我特地把程式碼寫的更詳細完整一點,讓我們看看結果會怎樣吧!

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-explicit4.cpp -o a.out
#include <iostream>
#include <string.h>
using namespace std;

class MyString {
public:
explicit MyString(int n) { // allocate n bytes to the MyString object
data = new char[n];
cout << "MyString(int n)\n";
}

MyString(const char *p) { // initializes object with char *p
int len = strlen(p);
data = new char[len+1];
strcpy(data, p);
cout << "MyString(const char *p), data = " << data << "\n";
}

~MyString() {
delete[] data;
}
private:
char *data;
};

int main() {
//MyString s1 = 10;
MyString s2(10);
MyString s3 = "hello world";
MyString s4("123456");
return 0;
}

結果輸出如下,如果再使用 s1 這種 copy-initialization 寫法就會出現編譯錯誤,開發者就不會使用這種寫法了,改完之後剩下這幾種寫法就比較原本的明確多了,

1
2
3
MyString(int n)
MyString(const char *p), data = hello world
MyString(const char *p), data = 123456

當然實際上可能不只這種情形,但我們了解了 explicit 用法後,我們就能依照當時的狀況選擇要不要使用 explicit 這個關鍵字,以上就是 ShengYu 對 C++ explicit 的簡單用法介紹,希望讓你對 explicit 有更深刻的體會。

其它參考
explicit specifier - cppreference.com
https://en.cppreference.com/w/cpp/language/explicit
c++ - What does the explicit keyword mean? - Stack Overflow
https://stackoverflow.com/questions/121162/what-does-the-explicit-keyword-mean

其它相關文章推薦
C/C++ 新手入門教學懶人包
C/C++ 字串轉數字的4種方法
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 用法與範例
std::deque 用法與範例
std::vector 用法與範例

Python 取得 ip 的方法

本篇介紹 Python 取得 ip 的方法。

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

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
print(s.getsockname()[0])
s.close()

結果如下,

1
192.168.1.2

寫成函式的話,範例如下,

python3-get-ip2.py
1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import socket

def get_ip():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
ip = s.getsockname()[0]
s.close()
return ip

print(get_ip())

取得對外 ip

如果是要取得對外 ip 的話,可以用下列方式,簡單說就是用第三方網站所提供的功能,這些網站通常都可以讓你查詢你目前對外網的 ip 是多少,

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

def get_ip():
ip = requests.get('https://api.ipify.org').text
return ip

print(get_ip())

結果如下,

60.251.61.121

這邊附上我之前寫的圖形化 GUI 工具PyGetMyPublicIP,有好幾種取得外網 ip 的方式,有興趣可以去看看。

其它參考
networking - Finding local IP addresses using Python’s stdlib - Stack Overflow
https://stackoverflow.com/questions/166506/finding-local-ip-addresses-using-pythons-stdlib
本文的方法不是文中的最佳解

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

Hexo 套件推薦

本篇紀錄 Hexo 的套件,基本上都是我目前有在用的,有些則是跟搜尋引擎優化 SEO 有相關的非裝不可,

發布上傳 GitHub 必備

發布上傳 GitHub 必備

1
npm install hexo-deployer-git --save

https://www.npmjs.com/package/hexo-deployer-git

另外有其他部署方式,例如:
Heroku:hexo-deployer-heroku
Rsync:hexo-deployer-rsync
OpenShift:hexo-deployer-openshift
FTPSync:hexo-deployer-ftpsync

網站地圖

網站地圖 sitemap

1
npm install hexo-generator-sitemap --save

https://www.npmjs.com/package/hexo-generator-sitemap

外部連結 nofollow

基本上我們是希望 Google 爬蟲不要爬去外部連結,否則可能爬一爬就再也回不來繼續爬你的網站了,所以會在外部連結加上 rel="nofollow 的屬性,告訴 Google 爬蟲不要跟蹤這連結。rel="external nofollow" 是進一步告訴 Google 爬蟲這是一個外部連結,不要跟蹤它。
原本 hexo-autonofollow 這個套件已經很久沒維護了,上一次更新時間是 2016 年,而且 hexo-autonofollow 這個套件的相依性很多,所以還會另外裝一堆相依套件,如果你已經裝 hexo-autonofollow 套件的,可以透過下列指令移除,

1
npm uninstall hexo-autonofollow

這邊推薦的是 hexo-filter-nofollow 這個套件,這套件跟上面差不多,但是設定上多了一個參數可以設定,另外完全沒有相依套件,目前已成為 Hexo 官方套件,
會在所有外部連結上加 rel="noopener external nofollow noreferrer" 的屬性,

1
npm install hexo-filter-nofollow --save

https://www.npmjs.com/package/hexo-filter-nofollow

1
2
3
4
5
6
nofollow:
enable: true
field: site
exclude: # 不加上 nofollow 的連結放在這邊
- 'exclude1.com'
- 'exclude2.com'

詳細內容可參考這篇
後來還有一個很厲害的 hexo-filter-nofollow-with-goto,包含以上功能外,還有一個 goto 功能,讓外站連結可以加上 prefix 轉換成內部連結,
https://www.npmjs.com/package/hexo-filter-nofollow-with-goto

301重新導向的替代方案

要改網頁網址通常會需要 301 轉址,是對頁面權重傷害最低的方式,但是如果你沒有網站的主機權限是沒法作這種設定的,由於我的網站放在 github page 上,所以只能另找其他方案,另外一種方式就是使用 canonical link,

1
<link rel="canonical" href="標準網址"/>

大概意思就是宣稱你原本的頁面跟新頁面內容重複,在這邊設定新頁面才是標準網址,原本網址則會被視為「重複」網址,檢索頻率會比較低。日子久了權重就會轉去新頁面上。需要的話安裝 hexo-generator-alias 這個套件,

1
npm install hexo-generator-alias --save

https://www.npmjs.com/package/hexo-generator-alias

文章置頂

如果有文章需要置頂的話可以安裝 hexo-generator-index-pin-top 這個套件,

1
npm install hexo-generator-index-pin-top --save

https://www.npmjs.com/package/hexo-generator-index-pin-top

在需要置頂的文章的 Front-matter 中加上 top: true 即可。

1
2
3
4
5
6
7
8
9
---
title: 'Hexo 套件推薦'
categories: []
tags:
- Hexo
toc: false
date: 2021-03-02 22:00:00
top: true
---

如果有多篇置頂的文章,則會按時間排序,

設定置頂的圖示,可以參考這篇

短網址

有機會試試看
hexo-abbrlink
如果有看過痞客幫,應該知道它們家網址後面都是6位數的網址,

1
npm install hexo-abbrlink --save

https://www.npmjs.com/package/hexo-abbrlink

文章目錄(非內建的文章目錄)

內建的文章目錄不太好用,只能在固定位置放置目錄,這個套件可以讓你在想要放文章目錄的地方,使用 `

` 語法就會直接在那邊展開文章目錄,安裝指令如下,

1
npm install hexo-toc --save

https://www.npmjs.com/package/hexo-toc

之後可以在 _config.yml 加入以下設定,可以做細部調整,

1
2
3
4
5
6
7
8
9
toc:
maxdepth: 3
class: toc
slugify: transliteration
decodeEntities: false
anchor:
position: after
symbol: '#'
style: header-anchor

其他參考
https://hsiangfeng.github.io/hexo/20190514/2072033203/
https://wylu.me/posts/78c745f0/

相關文章
Hexo 使用 Google Analytics 進行網站流量分析
Hexo 本機測試時如何關閉 Google Analytics
Hexo codeblock 插入程式碼區塊與各種程式語言預覽
升級更新 Hexo upgrade
Ubuntu 安裝 Hexo
Mac OS 安裝 Hexo

C++ new 動態記憶體配置用法與範例

本篇 ShengYu 介紹 C++ new 動態記憶體配置 / delete 釋放記憶體用法,

以下 C++ new 動態記憶體配置內容將分為這幾部份,

  • new int 的用法
  • new int 一維陣列
  • new int 二維陣列
  • new struct 範例
  • new struct 一維陣列範例

new int 的用法

以下是動態記憶體配置一個 int 的範例

1
int *p = new int;

也可以加上小括號這樣寫,

1
int *p = new int();

如果要分成兩行寫的話,

1
2
int *p;
p = new int();

如果要順便設定這個 int 的初始值的話,可以在 int 的建構子傳入預設值,示範一下如果我要初始值為 5 的用法,

1
int *p = new int(5);

當變數用完後很重要的一件事就是將這個動態配置記憶體的 int 釋放,以下為釋放記憶體的寫法,

1
delete p;

來看看實際範例吧!

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

int main() {
int *p = new int();
*p = 10;
cout << p << "\n";
cout << *p << "\n";
delete p;

int *p2 = new int(15);
cout << p2 << "\n";
cout << *p2 << "\n";
delete p2;

return 0;
}

輸出如下,

1
2
3
4
0x1590c20
10
0x1590c20
15

new int 一維陣列

這邊示範動態記憶體配置一個 int 一維陣列的寫法,new 陣列是要用中括號 [],不要跟上述的小括號 () 混淆唷,這兩個可是天差地遠,

1
2
3
int *p = new int[3]; // 不要寫成 int *p = new int(3); 唷!
...
delete[] p;

如果要分成兩行寫的話,

1
2
3
4
int *p;
p = new int[3];
...
delete[] p;

再來看看怎麼配置一維陣列時同時給初始值,

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

int main() {
int *p1 = new int[3](); // 0,0,0
int *p2 = new int[3]{1,2,3}; // 1,2,3

cout << p1[0] << "," << p1[1] << "," << p1[2] << "\n";
cout << p2[0] << "," << p2[1] << "," << p2[2] << "\n";

delete[] p1;
delete[] p2;

return 0;
}

輸出如下,

1
2
0,0,0
1,2,3

new int 二維陣列

動態配置二維陣列這個通常會在影像處理中使用到這個技巧,假設我們要配置 3 * 4 大小的 int 二維陣列,注意在使用完該變數後還是要將其變數 delete 歸還記憶體,二維陣列怎麼 new 的,delete 時就怎麼 delete。

cpp-new-delete3.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-new-delete3.cpp -o a.out
#include <iostream>
using namespace std;

int main() {
int **p = new int*[3];
for (int i = 0; i < 3; i++) {
p[i] = new int[4]{0};
}

p[0][1] = 1;
p[1][1] = 2;
p[2][1] = 3;

for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
cout << p[i][j] << " ";
}
cout << "\n";
}

for (int i = 0; i < 3; i++) {
delete[] p[i];
}
delete[] p;

return 0;
}

二維陣列輸出結果如下,

1
2
3
0 1 0 0 
0 2 0 0
0 3 0 0

new struct 範例

這邊示範怎麼 new struct,以 Point 為例,

cpp-new-delete4.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-new-delete4.cpp -o a.out
#include <iostream>
using namespace std;

struct Point {
int x;
int y;
};

int main() {
Point *p1 = new Point;
Point *p2 = new Point();
//Point *p3 = new Point(3); // error 沒有對應的建構子
Point *p4 = new Point({10,20});

cout << p1->x << "," << p1->y << "\n";
cout << p2->x << "," << p2->y << "\n";
cout << p4->x << "," << p4->y << "\n";

delete p1;
delete p2;
delete p4;

return 0;
}

輸出如下,

1
2
3
0,0
0,0
10,20

再舉個 Student 例子,

cpp-new-delete5.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
// g++ cpp-new-delete5.cpp -o a.out
#include <iostream>
#include <string>
using namespace std;

struct Student {
int id;
string name;
};

int main() {
Student *p1 = new Student;
Student *p2 = new Student();
//Student *p3 = new Student(3); // error 沒有對應的建構子
Student *p4 = new Student({10, "Amy"});

cout << p1->id << "," << p1->name << "\n";
cout << p2->id << "," << p2->name << "\n";
cout << p4->id << "," << p4->name << "\n";

delete p1;
delete p2;
delete p4;

return 0;
}

輸出結果如下,

1
2
3
0,
0,
10,Amy

new struct 一維陣列範例

這邊示範怎麼 new struct 一維陣列,以 Point 為例,

cpp-new-delete6.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-new-delete6.cpp -o a.out
#include <iostream>
using namespace std;

struct Point {
int x;
int y;
};

int main() {
Point *p1 = new Point[3];
Point *p2 = new Point[3]{{1,2}, {3,4}, {5,6}};

cout << p1[0].x << "," << p1[0].y << " "
<< p1[1].x << "," << p1[1].y << " "
<< p1[2].x << "," << p1[2].y << "\n";
cout << p2[0].x << "," << p2[0].y << " "
<< p2[1].x << "," << p2[1].y << " "
<< p2[2].x << "," << p2[2].y << "\n";

delete[] p1;
delete[] p2;

return 0;
}

輸出如下,

1
2
0,0  0,0  0,0
1,2 3,4 5,6

再舉個 Student 例子,

cpp-new-delete7.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
// g++ cpp-new-delete7.cpp -o a.out
#include <iostream>
#include <string>
using namespace std;

struct Student {
int id;
string name;
};

int main() {
Student *p1 = new Student[3];
Student *p2 = new Student[3]{{10, "Amy"}, {11, "Sam"}, {12, "Tom"}};

cout << p1[0].id << "," << p1[0].name << "|"
<< p1[1].id << "," << p1[1].name << "|"
<< p1[2].id << "," << p1[2].name << "\n";
cout << p2[0].id << "," << p2[0].name << "|"
<< p2[1].id << "," << p2[1].name << "|"
<< p2[2].id << "," << p2[2].name << "\n";

delete[] p1;
delete[] p2;

return 0;
}

輸出結果如下,

1
2
0,|0,|0,
10,Amy|11,Sam|12,Tom

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

其它相關文章推薦
C/C++ 新手入門教學懶人包
C/C++ 字串轉數字的4種方法
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 用法與範例
std::deque 用法與範例
std::vector 用法與範例

Linux wc 計算數量用法與範例

本篇 ShengYu 將介紹如何使用 Linux wc 指令來計算數量。

例如我用 find 指令找 jpg 檔案後,但我想要知道總共有多少個 jpg 檔案,我就可以這樣寫,

1
find -name "*.jpg" | wc -l

其它相關文章推薦
Linux 常用指令教學懶人包
Linux find 尋找檔案/尋找資料夾用法與範例
Linux sed 字串取代用法與範例
Linux cut 字串處理用法與範例
Linux tail 持續監看檔案輸出用法與範例
Linux ag 搜尋字串用法與範例(比 grep 還快)
Linux kill 指令砍掉指定的 process name

Ubuntu cmake 編譯安裝

本篇介紹如何在 Ubuntu 編譯安裝 cmake,

用 apt 安裝 cmake

在 Ubuntu 用 apt 安裝 cmake 的指令如下,

1
$ sudo apt install cmake

下載 cmake 原始碼編譯安裝

這邊介紹如何下載 cmake 原始碼編譯安裝,
Ubuntu 16.04 裝的 cmake 版本為 3.5.1,最近發現有些專案使用 cmake 新的語法,看來不得不升級 cmake 了,所以這邊要先把之前 apt 裝的 cmake 給移除掉,

只移除 cmake,這樣會移除 cmake 跟 cmake-qt-gui,

1
$ sudo apt purge cmake

移除 cmake 與相關的套件,這算是乾乾淨淨完整移除

1
$ sudo apt autoremove cmake

到官網下載 https://cmake.org/download/ 新版,我下載的是 3.19.6,

1
2
3
4
5
6
$ wget https://github.com/Kitware/CMake/releases/download/v3.19.6/cmake-3.19.6.tar.gz
$ tar xvf cmake-3.19.6.tar.gz
$ cd cmake-3.19.6/
$ ./bootstrap
$ make -j4
$ sudo make install

檢查一下目前 cmake 的版本,

1
2
$ cmake --version
cmake version 3.19.6

以上這樣只有 cmake 指令,但沒有 cmake-gui 的,如果你想要裝 cmake-gui 的話,就需要在編譯前設定時 ./bootstrap 加入選項,像這樣再去編譯跟安裝,這樣結果就會有 cmake-gui 了,

1
$ ./bootstrap --qt-gui

其他參考
如何從命令行安裝最新版本的cmake? - Ubuntu問答
https://ubuntuqa.com/zh-tw/article/1838.html
ubuntu - Where is the CMake GUI for Linux? - Stack Overflow
https://stackoverflow.com/questions/32425599/where-is-the-cmake-gui-for-linux

相關文章
Windows 編譯 cmake-gui
CMake 專案裡 include .cmake 檔案

GoogleTest 寫 C++ 單元測試的用法與教學

本篇 ShengYu 介紹 GoogleTest 用法與教學,Google test 又稱為 gtest,gtest 是一個 C++ 的單元測試框架(unit testing framework),gtest 是由 Google 所開發出來的,

以下 Google Test 內容將分為這幾部份,

  • 下載編譯安裝
  • googletest 常用語法
  • googletest 基本的 C++ 單元測試
  • 查詢 googletest 的版本
  • 有在使用 googletest 的開源專案
  • 其它有 test 的開源專案

下載編譯安裝

以下是 GoogleTest 的下載與編譯步驟,

1
2
3
4
5
6
$ git clone https://github.com/google/googletest
$ cd googletest
$ mkdir build
$ cd build
$ cmake -DCMAKE_CXX_FLAGS=-std=c++11 -Dgtest_build_samples=ON ..
$ make -j4

googletest 原始碼目錄下有 googletest 與 googlemock,
注意要切換到 googletest/ 頂層目錄去編譯,不是切換到 googletest/googletest/ 下編譯,否則你可能會遇到 cmake 的錯誤,

1
2
3
4
5
6
7
8
9
CMake Error at CMakeLists.txt:129 (set_target_properties):
set_target_properties called with incorrect number of arguments.


CMake Error at CMakeLists.txt:131 (set_target_properties):
set_target_properties called with incorrect number of arguments.


-- Configuring incomplete, errors occurred!

在 Ubuntu 16.04 下可能會遇到編譯錯誤的訊息,需要加上 std=c++11 的編譯選項,一種方法是在 CMakeLists.txt 裡加入 set(CMAKE_CXX_STANDARD 11)-DCMAKE_CXX_FLAGS=-std=c++11

另外如果電腦是多核心可以下 make -j4 有助於提升編譯速度,

如果要安裝到系統的話,請輸入下列指令,

1
sudo make install

你可以在 /usr/local/include/ 目錄下發現有新的 gtest/gtest.h 標頭檔
/usr/local/include/gtest/gtest.h
你也可以在 /usr/local/lib/ 目錄下發現有新的 libgtest 靜態函式庫
/usr/local/lib/libgtest.a
這些待會都會用到,

googletest 常用語法

這邊列出幾個 googletest 常用斷言,googletest 中定義的斷言如下,

基本斷言
判斷 condition 為真
ASSERT_TRUE(condition);
EXPECT_TRUE(condition);

判斷 condition 為假
ASSERT_FALSE(condition);
EXPECT_FALSE(condition);

數字比較
語法為 ASSERT_EQ(期待值,實際值),期待值和實際值不等時,測試失敗
判斷兩數是否相等,expected == actual
ASSERT_EQ(expected,actual);
EXPECT_EQ(expected,actual);

判斷兩數是否不相等,val1 != val2
ASSERT_NE(val1,val2);
EXPECT_NE(val1,val2);

判斷 val1 是否小於 val2,val1 < val2
ASSERT_LT(val1,val2);
EXPECT_LT(val1,val2);

判斷 val1 是否小於等於 val2,val1 <= val2
ASSERT_LE(val1,val2);
EXPECT_LE(val1,val2);

判斷 val1 是否大於 val2,val1 > val2
ASSERT_GT(val1,val2);
EXPECT_GT(val1,val2);

判斷 val1 是否大於等於 val2,val1 >= val2
ASSERT_GE(val1,val2);
EXPECT_GE(val1,val2);

字元字串比較
判斷兩個 C-style 字元字串有相同的內容
ASSERT_STREQ(expected_str,actual_str);
EXPECT_STREQ(expected_str,actual_str);

判斷兩個 C-style 字元字串有不同的內容
ASSERT_STRNE(str1,str2);
EXPECT_STRNE(str1,str2);

判斷兩個 C-style 字元字串有相同的內容,忽略大小寫
ASSERT_STRCASEEQ(expected_str,actual_str);
EXPECT_STRCASEEQ(expected_str,actual_str);

判斷兩個 C-style 字元字串有不同的內容,忽略大小寫
ASSERT_STRCASENE(str1,str2);
EXPECT_STRCASENE(str1,str2);

googletest 基本的 C++ 單元測試

這邊就示範一下怎麼用 GoogleTest 來寫單元測試,假如我要測試我寫的 myadd 函式的話,

gtest-add.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <gtest/gtest.h>

int myadd(int a, int b) {
return a + b;
}

TEST(testCase, test1) {
EXPECT_EQ(myadd(2, 3), 5);
}

int main(int argc, char **argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

接下來用 cmake 編譯或 g++ 編譯都可以,我這邊簡單的用 g++ 作示範,

1
g++ gtest-add.cpp -o a.out -std=c++11 -lgtest -lpthread

或者使用 pkg-config 來輔助,

1
g++ gtest-add.cpp -o a.out -std=c++11 `pkg-config --libs gtest`

執行 a.out 後就可以看到測試的輸出囉~

1
2
3
4
5
6
7
8
9
10
11
$ ./a.out
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from testCase
[ RUN ] testCase.test1
[ OK ] testCase.test1 (0 ms)
[----------] 1 test from testCase (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[ PASSED ] 1 test.

多加幾個測試項目,

gtest-arithmetic.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++ gtest-arithmetic.cpp -o a.out -std=c++11 -lgtest -lpthread
#include <gtest/gtest.h>

int myadd(int a, int b) {
return a + b;
}

int mysub(int a, int b) {
return a - b;
}

int mymul(int a, int b) {
return a * b;
}

int mydiv(int a, int b) {
return a / b;
}

TEST(testCase, test1) {
EXPECT_EQ(myadd(2, 3), 5);
}

TEST(testCase, test2) {
EXPECT_EQ(mysub(2, 3), -1);
}

TEST(testCase, test3) {
EXPECT_EQ(mymul(3, 4), 12);
}

TEST(testCase, test4) {
EXPECT_EQ(mydiv(21, 7), 3);
}

int main(int argc, char **argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

輸出結果如下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ ./a.out
[==========] Running 4 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 4 tests from testCase
[ RUN ] testCase.test1
[ OK ] testCase.test1 (0 ms)
[ RUN ] testCase.test2
[ OK ] testCase.test2 (0 ms)
[ RUN ] testCase.test3
[ OK ] testCase.test3 (0 ms)
[ RUN ] testCase.test4
[ OK ] testCase.test4 (0 ms)
[----------] 4 tests from testCase (0 ms total)

[----------] Global test environment tear-down
[==========] 4 tests from 1 test suite ran. (0 ms total)
[ PASSED ] 4 tests.

另外如果 link 連結 gtest_main 的話,就會幫你連結一個 main 主程式入口,那麼你就可以省略不用寫 main 主函式了,

1
g++ gtest-add.cpp -o a.out -std=c++11 -lgtest -lgtest_main -lpthread

那麼程式碼就可以改成這樣,

gtest-arithmetic2.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
// g++ gtest-arithmetic2.cpp -o a.out -std=c++11 -lgtest -lgtest_main -lpthread
#include <gtest/gtest.h>

int myadd(int a, int b) {
return a + b;
}

int mysub(int a, int b) {
return a - b;
}

int mymul(int a, int b) {
return a * b;
}

int mydiv(int a, int b) {
return a / b;
}

TEST(testCase, test1) {
EXPECT_EQ(myadd(2, 3), 5);
}

TEST(testCase, test2) {
EXPECT_EQ(mysub(2, 3), -1);
}

TEST(testCase, test3) {
EXPECT_EQ(mymul(3, 4), 12);
}

TEST(testCase, test4) {
EXPECT_EQ(mydiv(21, 7), 3);
}

如果是選用 cmake 編譯系統的話 CMakeLists.txt 可以這樣寫,

CMakeLists.txt
1
2
3
4
5
6
7
8
9
10
11
12
cmake_minimum_required(VERSION 3.0)

project(gtest-example)

#set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
add_executable(gtest-arithmetic gtest-arithmetic2.cpp)
target_link_libraries(gtest-arithmetic PRIVATE gtest gtest_main pthread)
#set_property(TARGET gtest-arithmetic PROPERTY CXX_STANDARD 11)

enable_testing()
add_test(test_arithmetic1 gtest-arithmetic)

查詢 googletest 的版本

googletest 版本號碼在 CMakeLists.txt 裡會去設定的 GOOGLETEST_VERSION 變數,

1
set(GOOGLETEST_VERSION 1.10.0)

有在使用 googletest 的開源專案

這邊列出比較知名的大型開源專案有在用 googletest,你可以參考學習他們的測試程式碼如何撰寫,有在使用 googletest 的知名開源專案如下,
OpenCV
測試範例:
test_pyramid.cpp
test_grabcut.cpp
test_jpeg.cpp

Kodi(舊名xbmc)
測試範例:
TestNetwork.cpp
TestWebServer.cpp
TestCPUInfo.cpp
Testlog.cpp

gRPC
測試範例:
cmdline_test.cc
stack_tracer_test.cc
mock_stream_test.cc

glog
測試範例:
stl_logging_unittest.cc
logging_unittest.cc

其它有 test 的開源專案

以下是其它有 test 的開源專案,也可以參考看看,
bitcoin
使用 boost test,測試範例:
base64_tests.cpp
crypto_tests.cpp
logging_tests.cpp
sock_tests.cpp

spdlog
使用 Catch2,測試範例:
test_backtrace.cpp
test_misc.cpp

其他參考
C++ 单元测试框架-gtest | Mike’s Blog
https://mikeblog.top/2019/01/02/googletest/
快速上手Google C++ 測試框架googletest_記錄學習的過程-CSDN博客
https://blog.csdn.net/weiwei9363/article/details/103469525
c++单元测试之gtest测试框架快速上手_guotianqing的博客-CSDN博客
https://blog.csdn.net/guotianqing/article/details/104055221
C Unit Test Framework 介紹 (Googletest) | by 亮谷 | Medium
https://medium.com/@ktvexe/c-unit-test-framework-%E4%BB%8B%E7%B4%B9-googletest-9713dadceb7a
輕鬆編寫 C++ 單元測試 | 開源互助社區
https://coctec.com/docs/linux/show-post-68880.html

其它相關文章推薦
C/C++ 新手入門教學懶人包
C/C++ 字串轉數字的4種方法
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 用法與範例
std::deque 用法與範例
std::vector 用法與範例

Android adb 同步時間/設定時間

本篇教學介紹如何在 Android 下使用 adb 指令對 Android 裝置同步時間/設定時間。

Android 裝置一般可以使用 adb shell date 指令來查看 Android 裝置目前的系統時間,

手動用 adb 來設定 Android 的時間

adb shell date 也可以用來設定時間,設定時間的格式為

1
adb shell date MMDDhhmmYY.ss

例如要設定為 2021/02/25 20:40:59的話,就這樣輸入 adb 指令

1
adb shell date 022520402021.59

adb shell date <欲設定的日期時間> 更改時間需要 root 權限,所以記得要先 adb root

用 Linux/macOS 電腦時間去設定 Android 的時間

在 Linux/macOS 系統下可以用 date 指令取得 Linux/macOS 電腦系統時間的指令像這樣

1
date +%m%d%H%M%Y.%S

那可不可以取得 Linux/macOS 系統時間後再順便用這個時間去 adb shell date <欲設定的日期時間> 設定 Android 裝置的時間呢?

當然可以!所以整合這兩者變成一個超級懶人指令就會是這樣,

1
2
adb root
adb shell "date `date +%m%d%H%M%Y.%S`"

這樣就會取得目前 Linux/macOS 系統時間再將其時間設定給 Android 裝置囉!你可以用檢查 adb shell date 看看有沒有成功。

其他參考
android - Set date/time using ADB shell - Stack Overflow
https://stackoverflow.com/questions/19496907/set-date-time-using-adb-shell
踩坑记:adb 时间同步 - SegmentFault 思否
https://segmentfault.com/a/1190000038303155
设置android设备时间与pc时间同步的批处理 - 月色深潭 - 博客园
https://www.cnblogs.com/moonpool/p/5692656.html
用 Windows 時間去設定

其他技巧推薦
如果你對 Android adb 不熟悉的話可以看看我之前Android adb 指令的安裝與用法教學
如果你是常常在Android adb shell下做事情的話,尤其是需要使用到vi,建議安裝busybox,使用busybox附帶的vi會方便很多,
如果還想知道busybox支援哪些指令或busybox基本用法的話請看這篇
其他的 Android 系列文章可以看這篇
下一篇來介紹Android fastboot指令的安裝與用法教學吧~

Python 字串轉時間日期

本篇介紹 Python 字串轉時間日期,在 Python 將 string 轉 datetime 的方法如下,

Python string 轉 datetime

在 Python 日期 string to datetime 的方法如下,
假設我們要轉換的日期字串為 2021-01-01,要使用 datetime.datetime.strptime() 然後會回傳一個 datetime.datetime 物件,

1
2
3
4
5
6
from datetime import datetime

s = '2021-01-01'
day = datetime.strptime(s, '%Y-%m-%d')
print(type(day))
print(day)

datetime.strptime() 第一個參數傳入日期時間字串,第二個參數傳入這個日期時間字串的格式是什麼,例如:%Y-%m-%d 就是 西元年-月-日 這樣的格式,結果如下,

1
2
<class 'datetime.datetime'>
2021-01-01 00:00:00

如果幾點幾分也要傳入呢?Python 日期時間轉 datetime 的方法如下,
這邊示範將 2021-01-10 11:30:202021-01-10 15:40:50 兩個日期時間轉換成 datetime,

1
2
3
4
5
6
7
8
9
10
from datetime import datetime

s1 = '2021-01-10 11:30:20'
s2 = '2021-01-10 15:40:50'
day1 = datetime.strptime(s1, '%Y-%m-%d %H:%M:%S')
day2 = datetime.strptime(s2, '%Y-%m-%d %H:%M:%S')
print(type(day1))
print(type(day2))
print(day1)
print(day2)

結果如下,

1
2
3
4
<class 'datetime.datetime'>
<class 'datetime.datetime'>
2021-01-10 11:30:20
2021-01-10 15:40:50

Python string 轉 datetime 並且作加法

通常日期時間轉換為 datetime 後會作加法,
Python 將日期 string 轉 datetime 並且作加 3 天,範例如下,

1
2
3
4
5
6
7
import datetime

s = '2021-01-10'
day = datetime.datetime.strptime(s, '%Y-%m-%d')
delta = datetime.timedelta(days=3)
day2 = day + delta
print(day2)

結果如下,

1
2021-01-13 00:00:00

Python string 轉 datetime 並且作減法

Python string 轉 datetime 並且作減 2 天,

1
2
3
4
5
6
7
import datetime

s = '2021-01-10'
day = datetime.datetime.strptime(s, '%Y-%m-%d')
delta = datetime.timedelta(days=2)
day2 = day - delta
print(day2)

結果如下,

1
2021-01-08 00:00:00

再舉另一個範例,兩個日期字串轉成 datetime 再相減,

1
2
3
4
5
6
7
8
import datetime

s1 = '2021-01-10 11:30:20'
s2 = '2021-01-08 11:30:30'
day1 = datetime.datetime.strptime(s1, '%Y-%m-%d %H:%M:%S')
day2 = datetime.datetime.strptime(s2, '%Y-%m-%d %H:%M:%S')
delta = day2 - day1
print(delta)

結果如下,

1
-2 days, 0:00:10

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