<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.7.3">Jekyll</generator><link href="https://xiwan.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://xiwan.io/" rel="alternate" type="text/html" /><updated>2018-08-23T07:26:37+00:00</updated><id>https://xiwan.io/</id><title type="html">西灣筆記</title><subtitle>擷英採華，以備不需！</subtitle><author><name>Kenmux Lee</name></author><entry><title type="html">【譯】後綴樹快速字串搜尋</title><link href="https://xiwan.io/archive/fast-string-searching-with-suffix-trees.html" rel="alternate" type="text/html" title="【譯】後綴樹快速字串搜尋" /><published>2018-08-05T10:00:00+00:00</published><updated>2018-08-05T10:00:00+00:00</updated><id>https://xiwan.io/archive/fast-string-searching-with-suffix-trees</id><content type="html" xml:base="https://xiwan.io/archive/fast-string-searching-with-suffix-trees.html">&lt;p&gt;這篇筆記、翻譯自博文：&lt;a href=&quot;http://facweb.cs.depaul.edu/mobasher/classes/csc575/Suffix_Trees/index.html&quot;&gt;Fast String Searching With Suffix Trees&lt;/a&gt;，作者Mark Nelson。尊重他人勞動果實，轉載請註明！&lt;br /&gt;
&lt;!--
於《Dr. Dobb's Journal》1996年8月刊[發表](http://www.drdobbs.com/database/algorithm-alley-fast-string-searching-wi/184409945?queryText=Suffix%2BTrees)
Fast String Searching With Suffix Trees
by Mark Nelson
Dr. Dobb's Journal
August, 1996
--&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;i&gt;I think that I shall never see&lt;/i&gt;&lt;br /&gt;
&lt;i&gt;A poem lovely as a tree.&lt;/i&gt;&lt;br /&gt;
&lt;i&gt;Poems are made by fools like me,&lt;/i&gt;&lt;br /&gt;
&lt;i&gt;But only God can make a tree.&lt;/i&gt;&lt;/p&gt;

  &lt;p&gt;–Joyce Kilmer&lt;/p&gt;
&lt;/blockquote&gt;

&lt;!--
I think that I shall never see
A poem lovely as a tree. 
Poems are made by fools like me,
But only God can make a tree.

--Joyce Kilmer
--&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;i&gt;A tree’s a tree. How many more do you need to look at?&lt;/i&gt;&lt;/p&gt;

  &lt;p&gt;–Ronald Reagan-&lt;/p&gt;
&lt;/blockquote&gt;

&lt;!--
A tree's a tree. How many more do you need to look at?

--Ronald Reagan-
--&gt;

&lt;p&gt;字串序列匹配是電腦程式設計師經常需要面對的問題。一些編程任務，例如數據壓縮或DNA測序，可以從字串匹配演算法的改進中獲益匪淺。本文探討了一種相對未知的數據結構，即&lt;b&gt;後綴樹&lt;/b&gt;，並展示如何使用它的特性着手解决字串匹配的難題。&lt;br /&gt;
&lt;!--
Matching string sequences is a problem that computer programmers face on a regular basis. Some programming tasks, such as data compression or DNA sequencing, can benefit enormously from improvements in string matching algorithms. This article discusses a relatively unknown data structure, the suffix tree, and shows how its characteristics can be used to attack difficult string matching problems.
--&gt;
&lt;!-- more --&gt;&lt;/p&gt;

&lt;h3 id=&quot;問題的提出&quot;&gt;問題的提出&lt;/h3&gt;
&lt;!--
The problem
--&gt;

&lt;p&gt;想像一下，你剛剛被聘為一名DNA測序項目的程式設計師。研究人員正在忙著分切病毒遺傳物質，產生碎片化的核苷酸序列。他們將這些序列發送到你的伺服器，期望將序列在基因組數據庫中定位。給定病毒的基因組可能有成千上萬個核苷酸鹼基，數據庫中有數百種病毒。你需要實作客戶端/伺服器系統，以提供實時反饋給等不及的博士們。什麼才是最好的方法呢？&lt;br /&gt;
&lt;!--Imagine that you've just been hired as a programmer working on a DNA sequencing project. Researchers are busy slicing and dicing viral genetic material, producing fragmented sequences of nucleotides. They send these sequences to your server, which is then expected to locate the sequences in a database of genomes. The genome for a given virus can have hundreds of thousands of nucleotide bases, and you have hundreds of viruses in your database. You are expected to implement this as a client/server project that gives real-time feedback to the impatient Ph.D.s. What's the best way to go about it?
--&gt;&lt;/p&gt;

&lt;p&gt;顯而易見，暴力字串搜尋將非常低效。這種搜尋需要對數據庫中每個基因組的每個核苷酸進行字串比較。測試部分匹配命中率很高的長片段將使你的客戶端/伺服器系統看起來像中古批處理機。你面臨的挑戰是提出一種高效的字串匹配解決方案。&lt;br /&gt;
&lt;!--
It is obvious at this point that a brute force string search is going to be terribly inefficient. This type of search would require you to perform a string comparison at every single nucleotide in every genome in your database. Testing a long fragment that has a high hit rate of partial matches would make your client/server system look like an antique batch processing machine. Your challenge is to come up with an efficient string matching solution.
--&gt;&lt;/p&gt;

&lt;h3 id=&quot;直觀的解決方案&quot;&gt;直觀的解決方案&lt;/h3&gt;
&lt;!--
The intuitive solution
--&gt;

&lt;p&gt;由於要測試的數據庫是不變的，因此對它預處理以簡化搜尋似乎是一個好主意。一種預處理的方法是構建搜尋字典樹。對於搜尋輸入文本，構建搜尋字典樹的直觀方法產生了一種稱為&lt;b&gt;後綴字典樹&lt;/b&gt;的東西。（後綴字典樹距離我的最終標的，即&lt;b&gt;後綴樹&lt;/b&gt;，只有一步之遙。）字典樹是一種樹，每個節點有N個可能的分支，其中N是字母表中的字符數。使用「後綴」一詞來指代這樣的情形：字典樹包含給定文本塊（可能是病毒基因組）的所有後綴。&lt;br /&gt;
&lt;!--
【[trie](https://en.wikipedia.org/wiki/Trie)，又稱前綴樹或字典樹，是一種有序樹，用於保存關聯陣列，其中的鍵通常是字串。本文一律翻作「字典樹」】
Since the database that you are testing against is invariant, preprocessing it to simplify the search seems like a good idea. One preprocessing approach is to build a search trie. For searching through input text, a straightforward approach to a search trie yields a thing called a suffix trie. (The suffix trie is just one step away from my final destination, the suffix tree.) A trie is a type of tree that has N possible branches from each node, where N is the number of characters in the alphabet. The word 'suffix' is used in this case to refer to the fact that the trie contains all of the suffixes of a given block of text (perhaps a viral genome.)
--&gt;&lt;/p&gt;

&lt;div style=&quot;text-align:center&quot;&gt;
  &lt;p&gt;&lt;img src=&quot;/assets/imgs/2018/08/05/fast-string-searching-with-suffix-trees/2018-08-05-01.gif&quot; alt=&quot;2018-08-05-01.gif&quot; /&gt;&lt;br /&gt;
圖1 用後綴字典樹表示「BANANAS」&lt;/p&gt;
&lt;/div&gt;
&lt;!--Figure 1

The Suffix Trie Representing &quot;BANANAS&quot;--&gt;

&lt;div style=&quot;word-wrap:break-word;word-break:break-all;&quot;&gt;
圖1顯示了BANANAS這個詞的後綴字典樹。關於這個字典樹，有兩個重點需要注意。首先，從根節點開始，BANANAS的每個後綴都在字典樹中：從BANANAS，ANANAS，NANAS開始，然後用一個單獨的S結束。第二，由於這種組織方式，你可以搜尋這個詞的任何子串：從根開始，然後沿著樹一直向下找，直到終了。
&lt;/div&gt;
&lt;!--
Figure 1 shows a Suffix trie for the word BANANAS. There are two important facts to note about this trie. First, starting at the root node, each of the suffixes of BANANAS is found in the trie, starting with BANANAS, ANANAS, NANAS, and finishing up with a solitary S. Second, because of this organization, you can search for any substring of the word by starting at the root and following matches down the tree until exhausted.
--&gt;

&lt;p&gt;第二點成就了後綴字典樹這麼好的結構。如果有一個長度為&lt;b&gt;&lt;i&gt;N&lt;/i&gt;&lt;/b&gt;的輸入文本和一個長度為&lt;b&gt;&lt;i&gt;M&lt;/i&gt;&lt;/b&gt;的搜尋字串，則一次傳統的暴力搜尋需要完成N*M次字符比較。最佳化的搜尋技術，例如Boyer-Moore演算法，可以保證搜尋不超過M+N次比較，並具有更好的平均性能。但後綴字典樹完全超越了這種性能：僅需M次字符比較，無論待搜尋文本的長度如何！&lt;br /&gt;
&lt;!--
The second point is what makes the suffix trie such a nice construct. If you have a input text of length N, and a search string of length M, a traiditonal brute force search will take as many as N*M character comparison to complete. Optimized searching techniques, such as the Boyer-Moore algorithm can guarantee searches that require no more than M+N comparisons, with even better average performance. But the suffix trie demolishes this performance by requiring just M character comparisons, regardless of the length of the text being searched!
--&gt;&lt;/p&gt;

&lt;p&gt;這可能看起來很不錯，意味著我可以通過執行區區七次字符比較來確定BANANAS這個詞是否在威廉·莎士比亞的作品集中！當然，只有一個小問題：構建字典樹所需的時間。&lt;br /&gt;
&lt;!--
Remarkable as this might seem, it means I could determine if the word BANANAS was in the collected works of William Shakespeare by performing just seven character comparisons! Of course, there is just one little catch: the time needed to construct the trie.
--&gt;&lt;/p&gt;

&lt;p&gt;你不怎麼聽到後綴字典樹應用的原因是基於一個簡單的事實，即構建一個需要O(N&lt;sup&gt;2&lt;/sup&gt;)的時間和空間。這種平方階性能使之無法用在最需要使用後綴字典樹的場合：搜尋長數據塊。&lt;br /&gt;
&lt;!--
The reason you don't hear much about the use of suffix tries is the simple fact that constructing one requires O(N2) time and space. This quadratic performance rules out the use of suffix tries where they are needed most: to search through long blocks of data.
--&gt;&lt;/p&gt;

&lt;h3 id=&quot;在後綴樹蔭下&quot;&gt;在後綴樹蔭下&lt;/h3&gt;
&lt;!--
Under the spreading suffix tree
--&gt;

&lt;p&gt;1976年，Edward McCreight提出了一個合理的方法來解決這個困境。他發表了一篇論文來闡述&lt;b&gt;後綴樹&lt;/b&gt;。給定數據塊的後綴樹保留與後綴字典樹相同的拓撲，但它消除了只有一個後代的節點。這個過程稱為&lt;b&gt;路徑壓縮&lt;/b&gt;，意味著樹中的各個邊現在可以表示文本序列而不是單個字符。&lt;br /&gt;
&lt;!--
A reasonable way past this dilemma was proposed by Edward McCreight in 1976, when he published his paper on what came to be known as the suffix tree. The suffix tree for a given block of data retains the same topology as the suffix trie, but it eliminates nodes that have only a single descendant. This process, known as path compression, means that individual edges in the tree now may represent sequences of text instead of single characters. 
--&gt;&lt;/p&gt;

&lt;div style=&quot;text-align:center&quot;&gt;
  &lt;p&gt;&lt;img src=&quot;/assets/imgs/2018/08/05/fast-string-searching-with-suffix-trees/2018-08-05-02.gif&quot; alt=&quot;2018-08-05-02.gif&quot; /&gt;&lt;br /&gt;
圖2 用後綴樹表示BANANAS&lt;/p&gt;
&lt;/div&gt;
&lt;!--
Figure 2

A suffix tree representing BANANAS
--&gt;

&lt;p&gt;圖2顯示了圖1的後綴字典樹轉換為後綴樹的樣子。你可以看到樹仍然具有相同的一般形狀，只是擁有更少的節點。通過消除所有的僅有單個後代的節點，計數從23減少到11。&lt;br /&gt;
&lt;!--
Figure 2 shows what the suffix trie from Figure 1 looks like when converted to a suffix tree. You can see that the tree still has the same general shape, just far fewer nodes. By eliminating every node with just a single descendant, the count is reduced from 23 to 11.
--&gt;&lt;/p&gt;

&lt;p&gt;實際上，節點數量的減少使得構造後綴樹的時間和空間要求從O(N&lt;sup&gt;2&lt;/sup&gt;)減少到O(N)。在最壞的情況下，構建後綴樹最多需要2N個節點，其中N是輸入文本的長度。因此，只需與輸入文本長度成比例的一次性投資，我們就可以創建一個用於增強字串搜尋的樹。&lt;br /&gt;
&lt;!--
In fact, the reduction in the number of nodes is such that the time and space requirements for constructing a suffix tree are reduced from O(N2) to O(N). In the worst case, a suffix tree can be built with a maximum of 2N nodes, where N is the length of the input text. So for a one-time investment proportional to the length of the input text, we can create a tree that turbocharges our string searches.
--&gt;&lt;/p&gt;

&lt;h3 id=&quot;即使你可以做一棵樹&quot;&gt;即使你可以做一棵樹&lt;/h3&gt;
&lt;!--
Even you can make a tree
--&gt;

&lt;p&gt;構造後綴樹的原始McCreight演算法有一些缺點。其中的一項原則是要求樹以相反的順序構建，這意味著字符從輸入末端開始添加。這使得在線處理成為不可能，使得它更難以用於數據壓縮等應用程式。&lt;br /&gt;
&lt;!--
McCreight's original algorithm for constructing a suffix tree had a few disadvantages. Principle among them was the requirement that the tree be built in reverse order, meaning characters were added from the end of the input. This ruled the algorithm out for on line processing, making it much more difficult to use for applications such as data compression.
--&gt;&lt;/p&gt;

&lt;p&gt;二十年後，來自赫爾辛基大學的Esko Ukkonen打破了窘境。他稍微修改了演算法，使其從左向右運作。我的示例代碼和後面的描述均基於Ukkonen的學術論文，該論文發表在1995年9月的&lt;b&gt;&lt;i&gt;Algorithmica&lt;/i&gt;&lt;/b&gt;雜誌上。&lt;br /&gt;
&lt;!--
Twenty years later, Esko Ukkonen from the University of Helsinki came to the rescue with a slightly modified version of the algorithm that works from left to right. Both my sample code and the descriptions that follow are based on Ukkonen's work, published in the September 1995 issue of Algorithmica.
--&gt;&lt;/p&gt;

&lt;p&gt;對於給定的文本字串，T，Ukkonen的演算法以空樹開始，然後逐步將T的N個前綴逐個添加到後綴樹中。例如，在為BANANAS創建後綴樹時，將B插入樹中，然後插入BA，然後插入BAN，依此類推。最後插入BANANAS時，樹已完成。&lt;br /&gt;
&lt;!--
For a given string of text, T, Ukkonen's algorithm starts with an empty tree, then progressively adds each of the N prefixes of T to the suffix tree. For example, when creating the suffix tree for BANANAS, B is inserted into the tree, then BA, then BAN, and so on. When BANANAS is finally inserted, the tree is complete. 
--&gt;&lt;/p&gt;

&lt;div style=&quot;text-align:center&quot;&gt;
  &lt;p&gt;&lt;img src=&quot;/assets/imgs/2018/08/05/fast-string-searching-with-suffix-trees/2018-08-05-03.gif&quot; alt=&quot;2018-08-05-03.gif&quot; /&gt;&lt;br /&gt;
圖3 逐步創建後綴樹&lt;/p&gt;
&lt;/div&gt;
&lt;!--
Figure 3

Progressively Building the Suffix Tree
--&gt;

&lt;h3 id=&quot;後綴樹運作方式&quot;&gt;後綴樹運作方式&lt;/h3&gt;
&lt;!--
Suffix tree mechanics
--&gt;

&lt;p&gt;向樹中添加新前綴是通過遍歷樹並訪問當前樹的每個後綴來完成的。我們從最長的後綴（圖3中的BAN）開始，然後向下遍歷到最短的後綴，即空字串。每個後綴都以這三種類型中的一種節點結束：&lt;br /&gt;
&lt;!--
Adding a new prefix to the tree is done by walking through the tree and visiting each of the suffixes of the current tree. We start at the longest suffix (BAN in Figure 3), and work our way down to the shortest suffix, which is the empty string. Each suffix ends at a node that consists of one of these three types:
--&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;葉節點。在圖4中，標記為1，2，4和5的節點是葉節點。&lt;/li&gt;
  &lt;li&gt;顯式節點。在圖4中，標記為0和3的非葉節點是顯式節點。它們代表樹上的一個點，其上兩條或兩條以上的邊分開。&lt;/li&gt;
  &lt;li&gt;隱式節點。在圖4中，諸如BO，BOO和OO之類的前綴都在邊的中間結束。這些位置稱為&lt;b&gt;隱式&lt;/b&gt;節點。它們代表後綴字典樹中的節點，但路徑壓縮消除了它們。在構建樹時，隱式節點有時會轉換為顯式節點。&lt;br /&gt;
&lt;!--
1.A leaf node. In Figure 4, the nodes labeled 1,2, 4, and 5 are leaf nodes.
2.An explicit node. The non-leaf nodes that are labeled 0 and 3 in Figure 4 are explicit nodes. They represent a point on the tree where two or more edges part ways.
3.An implicit node. In Figure 4, prefixes such as BO, BOO, and OO all end in the middle of an edge. These positions are referred to as implicit nodes. They would represent nodes in the suffix trie, but path compression eliminated them. As the tree is built, implicit nodes are sometimes converted to explicit nodes.
--&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div style=&quot;text-align:center&quot;&gt;
  &lt;p&gt;&lt;img src=&quot;/assets/imgs/2018/08/05/fast-string-searching-with-suffix-trees/2018-08-05-04.gif&quot; alt=&quot;2018-08-05-04.gif&quot; /&gt;&lt;br /&gt;
圖4 添加BOOK後的BOOKKEEPER&lt;/p&gt;
&lt;/div&gt;
&lt;!--
Figure 4

BOOKKEEPER after adding BOOK--&gt;

&lt;p&gt;在圖4中，在將BOOK添加進來之後，樹中有五個後綴（包括空字串）。將下一個前綴BOOKK添加到樹中意味著訪問現有樹中的每個後綴，並在後綴的末尾添加字母K。&lt;br /&gt;
&lt;!--
In Figure 4, there are five suffixes in the tree (including the empty string) after adding BOOK to the structure. Adding the next prefix, BOOKK to the tree means visiting each of the suffixes in the existing tree, and adding letter K to the end of the suffix.
--&gt;&lt;/p&gt;

&lt;p&gt;前四個後綴BOOK，OOK，OK和K都以葉節點結束。由於後綴樹使用了路徑壓縮，向葉節點添加新字符將始終只添加到該節點上的字串。它不會為新添加的字母創建新節點。&lt;br /&gt;
&lt;!--
The first four suffixes, BOOK, OOK, OK, and K, all end at leaf nodes. Because of the path compression applied to suffix trees, adding a new character to a leaf node will always just add to the string on that node. It will never create a new node, regardless of the letter being added.
--&gt;&lt;/p&gt;

&lt;p&gt;在更新了所有葉節點之後，我們仍然需要在空字串中添加字符「K」，該字串在節點0處找到。由於已經有一條以字母K開頭的離開節點0的邊，我們不需要做任何事情。新添加的後綴K將在節點0處找到，並且將在隱式節點處（沿著此處通向節點2的邊向下有一個字符）結束。&lt;br /&gt;
&lt;!--
After all of the leaf nodes have been updated, we still need to add character 'K' to the empty string, which is found at node 0. Since there is already an edge leaving node 0 that starts with letter K, we don't have to do anything. The newly added suffix K will be found at node 0, and will end at the implicit node found one character down along the edge leading to node 2.
--&gt;&lt;/p&gt;

&lt;p&gt;結果樹的最終形狀如圖5所示。&lt;br /&gt;
&lt;!--
The final shape of the resulting tree is shown in Figure 5.
--&gt;&lt;/p&gt;

&lt;div style=&quot;text-align:center&quot;&gt;
  &lt;p&gt;&lt;img src=&quot;/assets/imgs/2018/08/05/fast-string-searching-with-suffix-trees/2018-08-05-05.gif&quot; alt=&quot;2018-08-05-05.gif&quot; /&gt;&lt;br /&gt;
圖5 添加bookk後的同一棵樹&lt;/p&gt;
&lt;/div&gt;
&lt;!--Figure 5

The same tree after adding BOOKK--&gt;

&lt;h3 id=&quot;事情變得棘手了&quot;&gt;事情變得棘手了&lt;/h3&gt;
&lt;!--
Things get knotty
--&gt;

&lt;p&gt;更新圖4中的樹相對容易。我們執行了兩種類型的更新：第一種只是邊的擴充，第二種是隱式更新，不做任何事情。將BOOKKE添加到圖5中所示的樹將演示另外兩種類型的更新。第一種類型，創建一個新節點以在隱式節點處拆分現有邊，然後添加新邊。第二種類型，向顯式節點添加新邊。&lt;br /&gt;
&lt;!--
Updating the tree in Figure 4 was relatively easy. We performed two types of updates: the first was simply the extension of an edge, and the second was an implicit update, which involved no work at all. Adding BOOKKE to the tree shown in Figure 5 will demonstrate the two other types of updates. In the first type, a new node is created to split an existing edge at an implicit node, followed by the addition of a new edge. The second type of update consists of adding a new edge to an explicit node.
--&gt;&lt;/p&gt;

&lt;div style=&quot;text-align:center&quot;&gt;
  &lt;p&gt;&lt;img src=&quot;/assets/imgs/2018/08/05/fast-string-searching-with-suffix-trees/2018-08-05-06.gif&quot; alt=&quot;2018-08-05-06.gif&quot; /&gt;&lt;br /&gt;
圖6 拆分並添加更新&lt;/p&gt;
&lt;/div&gt;
&lt;!--
Figure 6

The Split and Add Update
--&gt;

&lt;p&gt;當將BOOKKE添加到圖5中的樹時，我們再次以最長的後綴BOOKK開始，並遍歷到最短的空字串。只要我們更新葉節點，更新較長的後綴還是很容易的。在圖5中，以葉節點結尾的後綴是BOOKK，OOKK，OKK和KK。圖6中的第一個樹顯示了使用簡單字串擴充更新這些後綴後樹的樣子。&lt;br /&gt;
&lt;!--
When adding BOOKKE to the tree in Figure 5, we once again start with the longest suffix, BOOKK, and work our way to the shortest, the empty string. Updating the longer suffixes is trivial as long as we are updating leaf nodes. In Figure 5, the suffixes that end in leaf nodes are BOOKK, OOKK, OKK, and KK. The first tree in Figure 6 shows what the tree looks like after these suffixes have been updated using the simple string extension.
--&gt;&lt;/p&gt;

&lt;p&gt;圖5中未在葉節點處終止的第一個後綴是K。當更新後綴樹時，第一個非葉節點被定義為樹的活躍點。所有比活躍點定義的後綴長的後綴都將以葉節點結束。此點之後的後綴都不會在葉節點處終止。&lt;br /&gt;
&lt;!--
The first suffix in Figure 5 that doesn't terminate at a leaf node is K. When updating a suffix tree, the first non-leaf node is defined as the active point of the tree. All of the suffixes that are longer than the suffix defined by the active point will end in leaf nodes. None of the suffixes after this point will terminate in leaf nodes.
--&gt;&lt;/p&gt;

&lt;p&gt;後綴K終止於KKE定義的沿邊向下的隱式節點。在測試非葉節點時，我們需要查看它們是否有任何與要追加的新字符匹配的後代。在這種情況下，那將是E。&lt;br /&gt;
&lt;!--
The suffix K terminates in an implicit node part way down the edge defined by KKE. When testing non-leaf nodes, we need to see if they have any descendants that match the new character being appended. In this case, that would be E.
--&gt;&lt;/p&gt;

&lt;p&gt;快速查看KKE中的第一個K，發現它只有一個後代：K。所以這意味著我們必須添加一個代表字母E的葉節點。這個過程需要兩步。首先，我們拆分保持弧的邊，以使待測後綴的末尾有一個顯式節點。圖6中間的樹顯示了拆分後樹的樣子。&lt;br /&gt;
&lt;!--
A quick look at the first K in KKE shows that it only has a single descendant: K. So this means we have to add a descendent to represent Letter E. This is a two step process. First, we split the edge holding the arc so that it has an explicit node at the end of the suffix being tested. The middle tree in Figure 6 shows what the tree looks like after the split.
--&gt;&lt;/p&gt;

&lt;p&gt;一旦拆分了邊，並且添加了新節點，你就會看到一個類似於圖6第三個位置的樹。注意成長為KE的K節點，已成為葉節點。&lt;br /&gt;
&lt;!--
Once the edge has been split, and the new node has been added, you have a tree that looks like that in the third position of Figure 6. Note that the K node, which has now grown to be KE, has become a leaf node.
--&gt;&lt;/p&gt;

&lt;h3 id=&quot;更新顯式節點&quot;&gt;更新顯式節點&lt;/h3&gt;
&lt;!--
Updating an explicit node
--&gt;

&lt;p&gt;更新後綴K後，我們仍然需要更新下一個較短的後綴，即空字串。空字串在顯式節點0結束，所以我們只需檢查它是否有一個以字母E開頭的後代。快速查看圖6中的樹表明節點0沒有葉節點，所以添加了另一個葉節點，產生如圖7所示的樹。&lt;br /&gt;
&lt;!--
After updating suffix K, we still have to update the next shorter suffix, which is the empty string. The empty string ends at explicit node 0, so we just have to check to see if it has a descendant that starts with letter E. A quick look at the tree in Figure 6 shows that node 0 doesn't have a descendent, so another leaf node is added, which yields the tree shown in Figure 7.
--&gt;&lt;/p&gt;

&lt;div style=&quot;text-align:center&quot;&gt;
  &lt;p&gt;&lt;img src=&quot;/assets/imgs/2018/08/05/fast-string-searching-with-suffix-trees/2018-08-05-07.gif&quot; alt=&quot;2018-08-05-07.gif&quot; /&gt;&lt;br /&gt;
圖7&lt;/p&gt;
&lt;/div&gt;
&lt;!--
Figure 7
--&gt;

&lt;h3 id=&quot;泛化演算法&quot;&gt;泛化演算法&lt;/h3&gt;
&lt;!--
Generalizing the algorithm
--&gt;

&lt;p&gt;通過利用後綴樹的一些特性，我們可以得到一個相當高效的演算法。第一個重要特徵是：一旦為葉節點，總是葉節點。葉節點自創建後永遠不會有後代，它只會通過字符串接進行擴充。更重要的是，每次我們向樹添加新後綴時，我們將自動地向通往每個葉節點的邊擴充單個字符。該字符將是新後綴中的最後一個字符。&lt;br /&gt;
&lt;!--
By taking advantage of a few of the characteristics of the suffix tree, we can generate a fairly efficient algorithm. The first important trait is this: once a leaf node, always a leaf node. Any node that we create as a leaf will never be given a descendant, it will only be extended through character concatenation. More importantly, every time we add a new suffix to the tree, we are going to automatically extend the edges leading into every leaf node by a single character. That character will be the last character in the new suffix.
--&gt;&lt;/p&gt;

&lt;p&gt;這使得對邊的管理變得容易。每當創建一個新的葉節點時，我們會自動設置其邊以表示從其起點到輸入文本末尾的所有字符。即使不知道這些字符是什麼，我們也知道它們最終會被添加到樹中。因此，一旦創建了葉節點，我們就可以忘掉它！如果邊被拆分，它的起始點可能會改變，但它仍然會一直延伸到輸入文本的末尾。&lt;br /&gt;
&lt;!--
This makes management of the edges leading into leaf nodes easy. Any time we create a new leaf node, we automatically set its edge to represent all the characters from its starting point to the end of the input text. Even if we don't know what those characters are, we know they will be added to the tree eventually. Because of this, once a leaf node is created, we can just forget about it! If the edge is split, its starting point may change, but it will still extend all the way to the end of the input text.
--&gt;&lt;/p&gt;

&lt;p&gt;這意味著只需要關心在活躍點（第一個非葉節點）更新顯式和隱式節點。鑑於此，我們必須從活躍點到空字串，測試每個節點是否需要更新。&lt;br /&gt;
&lt;!--
This means that we only have to worry about updating explicit and implicit nodes at the active point, which was the first non-leaf node. Given this, we would have to progress from the active point to the empty string, testing each node for update eligibility.
--&gt;&lt;/p&gt;

&lt;p&gt;然而，提前停止更新可以節約時間。當遍歷後綴時，我們將為每個沒有以正確字符開頭的後代邊的節點添加一個新邊。當最終找到具有正確字符作為後代的節點時，我們就可以停止更新了。了解了構造演算法的工作原理，你可以看出：如果某個字符是特定後綴的後代，那它也是每個較小後綴的後代。&lt;br /&gt;
&lt;!--
However, we can save some time by stopping our update earlier. As we walk through the suffixes, we will add a new edge to each node that doesn't have a descendant edge starting with the correct character. When we finally do reach a node that has the correct character as a descendant, we can simply stop updating. Knowing how the construction algorithm works, you can see that if you find a certain character as a descendent of a particular suffix, you are bound to also find it as a descendent of every smaller suffix.
--&gt;&lt;/p&gt;

&lt;p&gt;找到第一個匹配後代的點稱為終止點。終止點有一個額外的特別有用的特性。由於在活躍點和終止點之間的每個後綴中添加了葉節點，我們現在知道每個長於終止點的後綴是葉節點。這意味著終止點將在下一輪遍歷時變為活躍點!&lt;br /&gt;
&lt;!--
The point where you find the first matching descendent is called the end point. The end point has an additional feature that makes it particularly useful. Since we were adding leaves to every suffix between the active point and the end point, we now know that every suffix longer than the end point is a leaf node. This means the end point will turn into the active point on the next pass over the tree!
--&gt;&lt;/p&gt;

&lt;p&gt;通過將更新限制在活躍點和終止點之間的後綴，我們削減了更新樹所需的處理。通過追踪終止點，我們會自動知道下一輪的活躍點。使用此訊息的更新演算法的第一輪可能看起來像這樣（以類C的偽代碼）：
&lt;!--
By confining our updates to the suffixes between the active point and the end point, we cut way back on the processing required to update the tree. And by keeping track of the end point, we automatically know what the active point will be on the next pass. A first pass at the update algorithm using this information might look something like this (in C-like pseudo code) :
--&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;Update( new_suffix )
{
  current_suffix = active_point
  test_char = last_char in new_suffix
  done = false;
  while ( !done ) {
    if current_suffix ends at an explicit node {
      if the node has no descendant edge starting with test_char 
        create new leaf edge starting at the explicit node
      else
        done = true;
    } else {
      if the implicit node's next char isn't test_char {
        split the edge at the implicit node
        create new leaf edge starting at the split in the edge
      } else
        done = true;
    }
    if current_suffix is the empty string
      done = true; 
    else
       current_suffix = next_smaller_suffix( current_suffix )
  }
  active_point = current_suffix
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;後綴指標&quot;&gt;後綴指標&lt;/h3&gt;
&lt;!--
The Suffix Pointer
--&gt;

&lt;p&gt;上面顯示的偽代碼演算法大致正確，但它掩蓋了一個問題。當我們瀏覽樹時，我們通過調用next_smaller_suffix()移動到下一個較小的後綴。該程式必須找到對應於特定後綴的隱式或顯式節點。&lt;br /&gt;
&lt;!--
The pseudo-code algorithm shown above is more or less accurate, but it glosses over one difficulty. As we are navigating through the tree, we move to the next smaller suffix via a call to next_smaller_suffix(). This routine has to find the implicit or explicit node corresponding to a particular suffix.
--&gt;&lt;/p&gt;

&lt;p&gt;如果簡單地順著樹從上往下直至找到正確的節點，演算法的運行時間將不是線性的。為了解決這個問題，我們必須為樹添加一個額外的指標：後綴指標。後綴指標是在每個內部節點都有的指標。每個內部節點表示從根開始的字符序列。後綴指標指向該字串的第一個後綴的節點。因此，如果特定字串包含輸入文本的字符0到N，則該字串的後綴指標將指向作為從根開始的字串（該字串表示輸入文本1到N的字符）的終止點的節點。&lt;br /&gt;
&lt;!--
If we do this by simply walking down the tree until we find the correct node, our algorithm isn't going to run in linear time. To get around this, we have to add one additional pointer to the tree: the suffix pointer. The suffix pointer is a pointer found at each internal node. Each internal node represents a sequence of characters that start at the root. The suffix pointer points to the node that is the first suffix of that string. So if a particular string contains characters 0 through N of the input text, the suffix pointer for that string will point to the node that is the termination point for the string starting at the root that represents characters 1 through N of the input text.
--&gt;&lt;/p&gt;

&lt;p&gt;圖8顯示了字串ABABABC的後綴樹。第一個後綴指標位於表示ABAB的節點上。該字串的第一個後綴是BAB，這是在ABAB的後綴指標所指向的。同樣，BAB有自己的指向AB的節點的後綴指標。&lt;br /&gt;
&lt;!--
Figure 8 shows the suffix tree for the string ABABABC. The first suffix pointer is found at the node that represents ABAB. The first suffix of that string would be BAB, and that is where the suffix pointer at ABAB points. Likewise, BAB has its own suffix pointer, which points to the node for AB.
--&gt;&lt;/p&gt;

&lt;div style=&quot;text-align:center&quot;&gt;
  &lt;p&gt;&lt;img src=&quot;/assets/imgs/2018/08/05/fast-string-searching-with-suffix-trees/2018-08-05-08.gif&quot; alt=&quot;2018-08-05-08.gif&quot; /&gt;&lt;br /&gt;
圖8 ABABABC後綴樹，後綴指標顯示為虛線&lt;/p&gt;
&lt;/div&gt;
&lt;!--Figure 8

The suffix tree for ABABABC with suffix pointers shown as dashed lines--&gt;

&lt;p&gt;後綴指標是在對樹進行更新的同時構建的。當從活躍點移動到終止點時，我會跟踪創建的每個新葉子的父節點。每次創建新邊時，我也會從上一個創建的葉邊的父節點創建一個後綴指標來指向當前父邊。（顯然，我不能為在更新中創建的第一條邊執行此操作，但我會對所有剩餘的邊執行此操作。）&lt;br /&gt;
&lt;!--
The suffix pointers are built at the same time the update to the tree is taking place. As I move from the active point to the end point, I keep track of the parent node of each of the new leaves I create. Each time I create a new edge, I also create a suffix pointer from the parent node of the last leaf edge I created to the current parent edge. (Obviously, I can't do this for the first edge created in the update, but I do for all the remaining edges.)
--&gt;&lt;/p&gt;

&lt;p&gt;隨著後綴指針的到位，從一個後綴轉到下一個後綴只要跟隨指標就行了。這是將演算法簡化為O(N)的重要補充。&lt;br /&gt;
&lt;!--
With the suffix pointers in place, navigating from one suffix to the next is simply a matter of following a pointer. This critical addition to the algorithm is what reduces it to an O(N) algorithm.
--&gt;&lt;/p&gt;

&lt;h3 id=&quot;樹屋&quot;&gt;樹屋&lt;/h3&gt;
&lt;!--
Tree houses
--&gt;

&lt;p&gt;為了幫助說明這篇文章，我寫了一個簡短的程序STREE.CPP，它從標準輸入中讀取一串文本並構建一個後綴樹。清單1中顯示的是註釋完全的C++版本。第二個版本STREED.CPP加上了大量的調試輸出，可以從DDJ列表服務以及我的主頁獲得。&lt;br /&gt;
&lt;!--
To help illustrate this article, I wrote a short program, STREE.CPP, that reads in a string of text from standard input and builds a suffix tree. The version shown in Listing 1, is fully documented C++. A second version, STREED.CPP, has extensive debug output as well, and is available from the DDJ listing service, as well as my home page.
--&gt;&lt;/p&gt;

&lt;p&gt;理解STREE.CPP實際上只是理解它包含的數據結構的工作原理。 最重要的數據結構是Edge物件。Edge的類別定義是：&lt;br /&gt;
&lt;!--
Understanding STREE.CPP is really just a matter of understanding the workings of the data structures that it contains. The most important data structure is the Edge object. The class definition for Edge is:
--&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c--&quot; data-lang=&quot;c++&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Edge&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;first_char_index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;last_char_index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;end_node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start_node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Insert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Remove&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Edge&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Edge&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;init_first_char_index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
              &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;init_last_char_index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
              &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parent_node&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SplitEdge&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Suffix&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Edge&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;每次創建後綴樹中的新邊時，都會創建一個新的Edge物件來表示它。該物件的四個數據成員定義如下：&lt;br /&gt;
&lt;!--
Each time a new edge in the suffix tree is created, a new Edge object is created to represent it. The four data members of the object are defined as follows:
--&gt;&lt;/p&gt;

&lt;table style=&quot;border-collapse:collapse;border:none;width:100%&quot;&gt;
  &lt;tr style=&quot;border:none;&quot;&gt;
    &lt;td style=&quot;border:none;vertical-align:top;width:20%&quot;&gt;
      first_char_index,
      last_char_index:
    &lt;/td&gt;
    &lt;td style=&quot;border:none;vertical-align:top;width:80%&quot;&gt;
      樹中的每個邊都有與之關聯的輸入文本的字串。為了確保每個邊的存儲大小相同，我們只在輸入文本中存儲兩個索引來表示字串。
    &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr style=&quot;border:none;&quot;&gt;
    &lt;td style=&quot;border:none;vertical-align:top;&quot;&gt;
      start_node:
    &lt;/td&gt;
    &lt;td style=&quot;border:none;vertical-align:top;&quot;&gt;
      表示此邊的起始節點的節點數。節點0是樹的根。
    &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr style=&quot;border:none;&quot;&gt;
    &lt;td style=&quot;border:none;vertical-align:top;&quot;&gt;
      end_node:
    &lt;/td&gt;
    &lt;td style=&quot;border:none;vertical-align:top;&quot;&gt;
      表示此邊的結束節點的節點數。每次創建新邊時，也會創建新的結束節點。每個邊的結束節點在樹的生命週期內不會改變，因此這也可以用作邊ID。
    &lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;
&lt;!--
first_char_index, last_char_index:	Each of the edges in the tree has a sequence of characters from the input text associated with it. To ensure that the storage size of each edge is identical, we just store two indices into the input text to represent the sequence.
start_node:	The number of the node that represents the starting node for this edge. Node 0 is the root of the tree.
end_node:	The number of the node that represents the end node for this edge. Each time an edge is created, a new end node is created as well. The end node for every edge will not change over the life of the tree, so this can be used as an edge id as well.
--&gt;

&lt;p&gt;構建後綴樹時執行的最常見任務之一是基於其字串首字符來搜尋從特定節點發出的邊。在面向位元組的計算機上，可能有多達256條邊來自單個節點。為了使搜索合理快速和簡單，我將邊存儲在雜湊表中，使用基於其起始節點編號和子字串首字符的雜湊鍵。成員函數Insert()和Remove()用於管理進出雜湊表的邊的傳輸。&lt;br /&gt;
&lt;!--
One of the most frequent tasks performed when building the suffix tree is to search for the edge emanating from a particular node based on the first character in its sequence. On a byte oriented computer, there could be as many as 256 edges originating at a single node. To make the search reasonably quick and easy, I store the edges in a hash table, using a hash key based on their starting node number and the first character of their substring. The Insert() and Remove() member functions are used to manage the transfer of edges in and out of the hash table.
--&gt;&lt;/p&gt;

&lt;p&gt;構建後綴樹時使用的第二個重要數據結構是後綴物件。請記住，更新樹是通過處理當前存儲在樹中的字串的所有後綴來完成的，從最長點開始，到終止點結束。後綴只是一個字串，從節點0開始，到樹中的某個點結束。&lt;br /&gt;
&lt;!--
The second important data structure used when building the suffix tree is the Suffix object. Remember that updating the tree is done by working through all of the suffixes of the string currently stored in the tree, starting with the longest, and ending at the end point. A Suffix is simply a sequence of characters that starts at node 0 and ends at some point in the tree.
--&gt;&lt;/p&gt;

&lt;p&gt;合情合理地，我們可以通過僅定義其末字符在樹中的位置來安全地表示任何後綴，因為我們知道首字符從節點0開始，即根。後綴物件定義了使用該系統的後綴，其定義如下：
&lt;!--
It makes sense that we can then safely represent any suffix by defining just the position in the tree of its last character, since we know the first character starts at node 0, the root. The Suffix object, whose definition is shown here, defines a given suffix using that system:
--&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c--&quot; data-lang=&quot;c++&quot;&gt;    &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Suffix&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;origin_node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;first_char_index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;last_char_index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;Suffix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stop&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Explicit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Implicit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Canonize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;後綴物件從特定節點開始，跟隨由成員first_char_index和last_char_index指向的輸入文本中的字串，定義字串中的末字符。例如，在圖8中，最長的後綴「ABABABC」的origin_node為0，first_char_index為0，last_char_index為6。&lt;br /&gt;
&lt;!--
The Suffix object defines the last character in a string by starting at a specific node, then following the string of characters in the input sequence pointed to by the first_char_index and last_char_index members. For example, in Figure 8, the longest suffix &quot;ABABABC&quot; would have an origin_node of 0, a first_char_index of 0, and a last_char_index of 6.
--&gt;&lt;/p&gt;

&lt;p&gt;Ukkonen的演算法要求我們以&lt;b&gt;正規&lt;/b&gt;形式使用這些後綴定義。每次修改後綴物件時，都會調用函數&lt;b&gt;&lt;i&gt;Canonize()&lt;/i&gt;&lt;/b&gt;來執行此轉換。後綴的正規表示只需要後綴物件中的origin_node是與字串終止點最接近的父。這意味著由一對(0, “ABABABC”)表示的後綴字串將通過以下步驟進行正規化：先移動到(1, “ABABC”)，然後(4, “BC”)，最後(8, “”)。&lt;br /&gt;
&lt;!--
Ukkonen's algorithm requires that we work with these Suffix definitions in canonical form. The Canonize() function is called to perform this transformation any time a Suffix object is modified. The canonical representation of the suffix simply requires that the origin_node in the Suffix object be the closest parent to the end point of the string. This means that the suffix string represented by the pair (0, &quot;ABABABC&quot;), would be canonized by moving first to (1, &quot;ABABC&quot;), then (4, &quot;ABC&quot;), and finally (8,&quot;&quot;).
--&gt;&lt;/p&gt;

&lt;p&gt;當後綴字串在顯式節點上結束時，正規表示將使用空字串來定義字串中的剩餘字符。通過將first_char_index設置為大於last_char_index來定義空字符串。在這種情況下，我們知道後綴在&lt;b&gt;顯式&lt;/b&gt;節點上結束。如果first_char_index小於或等於last_char_index，則表示後綴字串在&lt;b&gt;隱式&lt;/b&gt;節點上結束。&lt;br /&gt;
&lt;!--
When a suffix string ends on an explicit node, the canonical representation will use an empty string to define the remaining characters in the string. An empty string is defined by setting first_char_index to be greater than last_char_index. When this is the case, we know that the suffix ends on an explicit node. If first_char_index is less than or equal to last_char_index, it means that the suffix string ends on an implicit node.
--&gt;&lt;/p&gt;

&lt;p&gt;給定這些數據結構的定義，我認為你會發現STREE.CPP中的程式碼是Ukkonen演算法的簡單實現。為了更加清晰，請使用STREED.CPP以便在運行時吐出大量的調試訊息。&lt;br /&gt;
&lt;!--
Given these data structure definitions, I think you will find the code in STREE.CPP to be a straightforward implementation of the Ukkonen algorithm. For additional clarity, use STREED.CPP to dump copious debug information out at runtime.
--&gt;&lt;/p&gt;

&lt;h3 id=&quot;致謝&quot;&gt;致謝&lt;/h3&gt;
&lt;!--
Acknowledgments
--&gt;

&lt;p&gt;通過閱讀Jesper Larsson關於1996年IEEE數據壓縮會議的論文，我終於解決了後綴樹的構建問題。Jesper也非常友好地向我提供了示例代碼和Ukkonen論文的連結。&lt;br /&gt;
&lt;!--
I was finally convinced to tackle suffix tree construction by reading Jesper Larsson's paper for the 1996 IEEE Data Compression Conference. Jesper was also kind enough to provide me with sample code and pointers to Ukkonen's paper.
--&gt;&lt;/p&gt;

&lt;h3 id=&quot;參考書目&quot;&gt;參考書目&lt;/h3&gt;
&lt;!--
References
--&gt;

&lt;p&gt;E.M. McCreight. A space-economical suffix tree construction algorithm. Journal of the ACM, 23:262-272, 1976.&lt;br /&gt;
E. Ukkonen. On-line construction of suffix trees. Algorithmica, 14(3):249-260, September 1995.&lt;/p&gt;

&lt;h3 id=&quot;原始碼下載&quot;&gt;原始碼下載&lt;/h3&gt;
&lt;!--
Source Code
--&gt;

&lt;p&gt;&lt;a href=&quot;http://facweb.cs.depaul.edu/mobasher/classes/csc575/Suffix_Trees/stree.cpp&quot;&gt;stree.cpp&lt;/a&gt;    一個簡單的程式，可以從輸入字串創建後綴樹。&lt;br /&gt;
&lt;a href=&quot;http://facweb.cs.depaul.edu/mobasher/classes/csc575/Suffix_Trees/streed.cpp&quot;&gt;streed.cpp&lt;/a&gt;     相同的程式，添加了大量調試代碼。&lt;br /&gt;
&lt;!--
stree.cpp	A simple program that builds a suffix tree from an input string.
streed.cpp	The same program with much debugging code added.
--&gt;&lt;/p&gt;</content><author><name>Kenmux Lee</name></author><category term="Suffix Tree" /><category term="Trie" /><category term="Algorithm" /><summary type="html">這篇筆記、翻譯自博文：Fast String Searching With Suffix Trees，作者Mark Nelson。尊重他人勞動果實，轉載請註明！ I think that I shall never see A poem lovely as a tree. Poems are made by fools like me, But only God can make a tree. –Joyce Kilmer A tree’s a tree. How many more do you need to look at? –Ronald Reagan- 字串序列匹配是電腦程式設計師經常需要面對的問題。一些編程任務，例如數據壓縮或DNA測序，可以從字串匹配演算法的改進中獲益匪淺。本文探討了一種相對未知的數據結構，即後綴樹，並展示如何使用它的特性着手解决字串匹配的難題。</summary></entry><entry><title type="html">【譯】C語言「右左規則」</title><link href="https://xiwan.io/archive/c-right-left-rule.html" rel="alternate" type="text/html" title="【譯】C語言「右左規則」" /><published>2017-11-05T10:00:00+00:00</published><updated>2017-11-05T10:00:00+00:00</updated><id>https://xiwan.io/archive/c-right-left-rule</id><content type="html" xml:base="https://xiwan.io/archive/c-right-left-rule.html">&lt;p&gt;這篇筆記、翻譯自博文：&lt;a href=&quot;http://ieng9.ucsd.edu/~cs30x/rt_lt.rule.html&quot;&gt;C Right-Left Rule&lt;/a&gt;，作者Rick Ord。尊重他人勞動果實，轉載請註明！&lt;/p&gt;

&lt;p&gt;「右左」規則是解讀C宣告十分實用的規則。創建宣告時它也同樣有用。&lt;!-- more --&gt;&lt;br /&gt;
&lt;!--
The &quot;right-left&quot; rule is a completely regular rule for deciphering C
declarations.  It can also be useful in creating them.
--&gt;&lt;/p&gt;

&lt;p&gt;首先，符號。在宣告中，視&lt;br /&gt;
  *   為「指向&lt;u&gt;&amp;emsp;&lt;/u&gt;的指標」   通常在左邊&lt;br /&gt;
  []  為「&lt;u&gt;&amp;emsp;&lt;/u&gt;的陣列」     通常在右邊&lt;br /&gt;
  ()  為「返回&lt;u&gt;&amp;emsp;&lt;/u&gt;的函式」   通常在右邊&lt;br /&gt;
&lt;!--
First, symbols.  Read
     *		as &quot;pointer to&quot;			- always on the left side
     [] 	as &quot;array of&quot;			- always on the right side
     ()		as &quot;function returning&quot;		- always on the right side
as you encounter them in the declaration.
--&gt;&lt;/p&gt;

&lt;p&gt;第一步&lt;br /&gt;
----------&lt;br /&gt;
找到標識符。這是初始點。然後妳知道，這個宣告是:「標識符是&lt;u&gt;&amp;emsp;&lt;/u&gt;」。
&lt;!--
STEP 1
------
Find the identifier.  This is your starting point.  Then say to yourself,
&quot;identifier is.&quot;  You've started your declaration.
--&gt;&lt;/p&gt;

&lt;p&gt;第二步&lt;br /&gt;
----------&lt;br /&gt;
查看標識符右邊的符號，如果是「()」，那就表示這是一個函式宣告：「標識符是返回&lt;u&gt;&amp;emsp;&lt;/u&gt;的函式」。抑或是「[]」，則是「標識符是&lt;u&gt;&amp;emsp;&lt;/u&gt;的陣列」。繼續查看右邊，直到沒有符號&lt;b&gt;或者&lt;/b&gt;遇到一個&lt;b&gt;右&lt;/b&gt;圓括號「)」。（如果遇到左圓括號，那是符號()的起始，即使括號間還有東西。更多以下詳解。）&lt;br /&gt;
&lt;!--
STEP 2
------
Look at the symbols on the right of the identifier.  If, say, you find &quot;()&quot;
there, then you know that this is the declaration for a function.  So you
would then have &quot;identifier is function returning&quot;.  Or if you found a 
&quot;[]&quot; there, you would say &quot;identifier is array of&quot;.  Continue right until
you run out of symbols *OR* hit a *right* parenthesis &quot;)&quot;.  (If you hit a 
left parenthesis, that's the beginning of a () symbol, even if there
is stuff in between the parentheses.  More on that below.)
--&gt;&lt;/p&gt;

&lt;p&gt;第三步&lt;br /&gt;
----------&lt;br /&gt;
查看標識符左邊的符號。如果不是上面說的符號（比如「int」），直接代到句子裏面。否則，使用上述表格轉譯成文字。繼續查看左邊，直到沒有符號&lt;b&gt;或者&lt;/b&gt;遇到&lt;b&gt;左&lt;/b&gt;圓括號「(」。&lt;br /&gt;
&lt;!--
STEP 3
------
Look at the symbols to the left of the identifier.  If it is not one of our
symbols above (say, something like &quot;int&quot;), just say it.  Otherwise, translate
it into English using that table above.  Keep going left until you run out of
symbols *OR* hit a *left* parenthesis &quot;(&quot;.
--&gt;&lt;/p&gt;

&lt;p&gt;接下來重複第二步和第三步，直到宣告解讀完畢。以下是一些範例：&lt;br /&gt;
&lt;!--
Now repeat steps 2 and 3 until you've formed your declaration.  Here are some examples:
--&gt;&lt;/p&gt;

&lt;pre&gt;    int *p[];

1）找到標識符。
                       int *p[]
                            ^
    「p是&lt;u&gt;  &lt;/u&gt;」

2）向右邊移動，直到沒有符號或者遇到右圓括號。
                       int *p[]
                             ^^
    「p是&lt;u&gt;  &lt;/u&gt;的陣列」

3）不能再向右邊移動了（沒有符號），因而向左邊移動並得到：
                       int *p[]
                           ^
    「p是指向&lt;u&gt;  &lt;/u&gt;的指標的陣列」

4）繼續向左邊移動並得到：
                       int *p[]
                       ^^^
    「p是指向int的指標的陣列」
    （或者「p是一個陣列，其元素型別為指向int的指標」）&lt;/pre&gt;
&lt;!--
     int *p[];

1) Find identifier.          int *p[];
                                  ^
   &quot;p is&quot;

2) Move right until out of symbols or right parenthesis hit.
                             int *p[];
                                   ^^
   &quot;p is array of&quot;

3) Can't move right anymore (out of symbols), so move left and find:
                             int *p[];
                                 ^
   &quot;p is array of pointer to&quot;

4) Keep going left and find:
                             int *p[];
                             ^^^
   &quot;p is array of pointer to int&quot;. 
   (or &quot;p is an array where each element is of type pointer to int&quot;)--&gt;

&lt;p&gt;另一個範例：&lt;br /&gt;
&lt;!--
Another example:
--&gt;&lt;/p&gt;

&lt;pre&gt;    int *(*func())();

1）找到標識符。
                       int *(*func())();
                              ^^^^
    「func是&lt;u&gt;  &lt;/u&gt;」

2）向右邊移動。
                       int *(*func())();
                                  ^^
    「func是返回&lt;u&gt;  &lt;/u&gt;的函式」

3）遇到右圓括號，不能再向右邊移動了，因而向左邊移動：
                       int *(*func())();
                             ^
    「func是返回指向&lt;u&gt;  &lt;/u&gt;的指標的函式」

4）遇到左圓括號，不能再向左邊移動了，因而繼續向右邊移動：
                       int *(*func())();
                                     ^^
    「func是返回指向返回&lt;u&gt;  &lt;/u&gt;的函式的指標的函式」

5）沒有符號，不能再向右邊移動了，因而轉向左邊：
                       int *(*func())();
                           ^
    「func是返回指向返回指向&lt;u&gt;  &lt;/u&gt;的指標的函式的指標的函式」

6）最後，因為右邊沒有了，繼續向左邊移動。
                       int *(*func())();
                       ^^^
    「func是返回指向返回指向int的指標的函式的指標的函式」
&lt;/pre&gt;
&lt;!--
   int *(*func())();

1) Find the identifier.      int *(*func())();
                                    ^^^^
   &quot;func is&quot;

2) Move right.               int *(*func())();
                                        ^^
   &quot;func is function returning&quot;

3) Can't move right anymore because of the right parenthesis, so move left.
                             int *(*func())();
                                   ^
   &quot;func is function returning pointer to&quot;

4) Can't move left anymore because of the left parenthesis, so keep going
   right.                    int *(*func())();
                                           ^^
   &quot;func is function returning pointer to function returning&quot;

5) Can't move right anymore because we're out of symbols, so go left.
                             int *(*func())();
                                 ^
   &quot;func is function returning pointer to function returning pointer to&quot;

6) And finally, keep going left, because there's nothing left on the right.
                             int *(*func())();
                             ^^^
   &quot;func is function returning pointer to function returning pointer to int&quot;.
--&gt;

&lt;p&gt;正如妳所看到的，這個規則相當有用。妳也可以在創建宣告時用它來做完整性檢查，或提示下一個符號的位置以及括號是否必要。&lt;br /&gt;
&lt;!--
As you can see, this rule can be quite useful.  You can also use it to
sanity check yourself while you are creating declarations, and to give
you a hint about where to put the next symbol and whether parentheses
are required.
--&gt;&lt;/p&gt;

&lt;p&gt;有些宣告因原型中陣列長度和參數列表而看起來比實際複雜得多。如「[3]」，意為「&lt;u&gt;__&lt;/u&gt;的（長度為3的）陣列。又如「(char *,int)」，意為「參數為(char *,int)，返回&lt;u&gt;__&lt;/u&gt;的函式」。來一個比較有趣的：&lt;br /&gt;
&lt;!--
Some declarations look much more complicated than they are due to array
sizes and argument lists in prototype form.  If you see &quot;[3]&quot;, that's
read as &quot;array (size 3) of...&quot;.  If you see &quot;(char *,int)&quot; that's read
as &quot;function expecting (char *,int) and returning...&quot;.  Here's a fun
one:
--&gt;&lt;/p&gt;

&lt;pre&gt;                int (*(*fun_one)(char *,double))[9][20];&lt;/pre&gt;
&lt;!--                 int (*(*fun_one)(char *,double))[9][20];--&gt;

&lt;p&gt;這裡就不詳細羅列解讀過程，直接揭曉答案：&lt;/p&gt;

&lt;pre&gt;「fun_one是指向參數為(char *,double)並返回指向int陣列（長度為20）的陣列（長度為9）
的指標的函式的指標。」&lt;/pre&gt;

&lt;!--
I won't go through each of the steps to decipher this one.

Ok.  It's:

     &quot;fun_one is pointer to function expecting (char *,double) and 
      returning pointer to array (size 9) of array (size 20) of int.&quot;
--&gt;

&lt;p&gt;正如妳所看到的，去掉陣列長度和參數列表後，其實並不復雜：&lt;/p&gt;
&lt;pre&gt;                int (*(*fun_one)())[][];&lt;/pre&gt;
&lt;!--
As you can see, it's not as complicated if you get rid of the array sizes
and argument lists:
     int (*(*fun_one)())[][];
--&gt;

&lt;p&gt;妳可以先這樣解讀，然後再添上陣列長度和參數列表。&lt;br /&gt;
&lt;!--
You can decipher it that way, and then put in the array sizes and argument
lists later.
--&gt;&lt;/p&gt;

&lt;p&gt;結語：&lt;br /&gt;
&lt;!--
Some final words:
--&gt;&lt;/p&gt;

&lt;p&gt;使用這個規則很有可能做出非法宣告，因而關於什麼在C中是合法的基本常識是必要的。例如，如果把上述語句寫作：&lt;br /&gt;
&lt;!--
It is quite possible to make illegal declarations using this rule,
so some knowledge of what's legal in C is necessary.  For instance,
if the above had been:
--&gt;&lt;/p&gt;

&lt;pre&gt;                int *((*fun_one)())[][];&lt;/pre&gt;
&lt;!--
     int *((*fun_one)())[][];
--&gt;

&lt;p&gt;這樣就解讀為「fun_one是指向&lt;span style=&quot;border-bottom-style:dotted;&quot;&gt;返回&lt;/span&gt;指向int的指標的陣列的&lt;span style=&quot;border-bottom-style:dotted;&quot;&gt;陣列的函式&lt;/span&gt;的指標」。因為函式不能返回陣列，而是指向陣列的指標，因而這樣宣告是非法的。&lt;br /&gt;
&lt;!--span style=&quot;border-bottem:2px dotted
it would have been &quot;fun_one is pointer to function returning array of array of
                                          ^^^^^^^^^^^^^^^^^^^^^^^^
pointer to int&quot;.  Since a function cannot return an array, but only a 
pointer to an array, that declaration is illegal.--&gt;&lt;/p&gt;

&lt;p&gt;非法的組合還有：&lt;br /&gt;
&lt;!--Illegal combinations include:--&gt;&lt;/p&gt;

&lt;pre style=&quot;margin-left:2em;&quot;&gt;
[]() - 不存在函數陣列
()() - 不存在返回函式的函式
()[] - 不存在返回陣列的函式
&lt;/pre&gt;
&lt;!--
	 []() - cannot have an array of functions
	 ()() - cannot have a function that returns a function
	 ()[] - cannot have a function that returns an array
--&gt;

&lt;p&gt;在以上所有情況下，為了使宣告合法，妳需要用一對括號去結合左邊*符號和右邊()和[]間的符號。&lt;br /&gt;
&lt;!--
In all the above cases, you would need a set of parens to bind a *
symbol on the left between these () and [] right-side symbols in order
for the declaration to be legal.
--&gt;&lt;/p&gt;

&lt;p&gt;以下是一些合法和非法的例子：&lt;/p&gt;
&lt;pre style=&quot;margin-left:2em;&quot;&gt;
int i;                  int的變數
int *p;                 int的指標（指向int的指標）
int a[];                int的陣列
int f();                返回int的函式
int **pp;               指向int的指標的指標
int (*pa)[];            指向int的陣列的指標
int (*pf)();            指向返回int的函式的指標
int *ap[];              int的指標的陣列（指向int的指標的陣列）
int aa[][];             int的陣列的陣列
int af[]();             返回int的函式的陣列（非法）
int *fp();              返回int的指標的函式
int fa()[];             返回int陣列的函式（非法）
int ff()();             返回返回int的函式的函式（非法）
int ***ppp;             指向指向int的指標的指標的指標
int (**ppa)[];          指向指向int的陣列的指標的指標
int (**ppf)();          指向指向返回int的函式的指標的指標
int *(*pap)[];          指向int的指標的陣列的指標
int (*paa)[][];         指向int的陣列的陣列的指標
int (*paf)[]();         指向返回int的函式的陣列的指標（非法）
int *(*pfp)();          指向返回int指標的函式的指標
int (*pfa)()[];         指向返回int的陣列的函式的指標（非法）
int (*pff)()();         指向返回返回int的函式的函式的指標（非法）
int **app[];            指向int指標的指標的陣列
int (*apa[])[];         指向int的陣列的指標的陣列
int (*apf[])();         指向返回int的函式的指標的陣列
int *aap[][];           int指標的陣列的陣列
int aaa[][][];          int的陣列的陣列的陣列
int aaf[][]();          返回int的函式的陣列的陣列（非法）
int *afp[]();           返回int指標的函式的陣列（非法）
int afa[]()[];          返回int的陣列的函式的陣列（非法）
int aff[]()();          返回返回int的函式的函式的陣列（非法）
int **fpp();            返回指向int指標的指標的函式
int (*fpa())[];         返回指向int的陣列的指標的函式
int (*fpf())();         返回指向返回int的函式的指標的函式
int *fap()[];           返回int指標的陣列的函式（非法）
int faa()[][];          返回int的陣列的陣列的函式（非法）
int faf()[]();          返回返回int的函式的陣列的函式（非法）
int *ffp()();           返回返回int指標的函式的函式（非法）
&lt;/pre&gt;
&lt;!--
Here are some legal and illegal examples:

int i;                  an int
int *p;                 an int pointer (ptr to an int)
int a[];                an array of ints
int f();                a function returning an int
int **pp;               a pointer to an int pointer (ptr to a ptr to an int)
int (*pa)[];            a pointer to an array of ints
int (*pf)();            a pointer to a function returning an int
int *ap[];              an array of int pointers (array of ptrs to ints)
int aa[][];             an array of arrays of ints
int af[]();             an array of functions returning an int (ILLEGAL)
int *fp();              a function returning an int pointer
int fa()[];             a function returning an array of ints (ILLEGAL)
int ff()();             a function returning a function returning an int
                                (ILLEGAL)
int ***ppp;             a pointer to a pointer to an int pointer
int (**ppa)[];          a pointer to a pointer to an array of ints
int (**ppf)();          a pointer to a pointer to a function returning an int
int *(*pap)[];          a pointer to an array of int pointers
int (*paa)[][];         a pointer to an array of arrays of ints
int (*paf)[]();         a pointer to a an array of functions returning an int
                                (ILLEGAL)
int *(*pfp)();          a pointer to a function returning an int pointer
int (*pfa)()[];         a pointer to a function returning an array of ints
                                (ILLEGAL)
int (*pff)()();         a pointer to a function returning a function
                                returning an int (ILLEGAL)
int **app[];            an array of pointers to int pointers
int (*apa[])[];         an array of pointers to arrays of ints
int (*apf[])();         an array of pointers to functions returning an int
int *aap[][];           an array of arrays of int pointers
int aaa[][][];          an array of arrays of arrays of ints
int aaf[][]();          an array of arrays of functions returning an int
                                (ILLEGAL)
int *afp[]();           an array of functions returning int pointers (ILLEGAL)
int afa[]()[];          an array of functions returning an array of ints
                                (ILLEGAL)
int aff[]()();          an array of functions returning functions
                                returning an int (ILLEGAL)
int **fpp();            a function returning a pointer to an int pointer
int (*fpa())[];         a function returning a pointer to an array of ints
int (*fpf())();         a function returning a pointer to a function
                                returning an int
int *fap()[];           a function returning an array of int pointers (ILLEGAL)
int faa()[][];          a function returning an array of arrays of ints
                                (ILLEGAL)
int faf()[]();          a function returning an array of functions
                                returning an int (ILLEGAL)
int *ffp()();           a function returning a function
                                returning an int pointer (ILLEGAL)
--&gt;</content><author><name>Kenmux Lee</name></author><category term="C" /><summary type="html">這篇筆記、翻譯自博文：C Right-Left Rule，作者Rick Ord。尊重他人勞動果實，轉載請註明！ 「右左」規則是解讀C宣告十分實用的規則。創建宣告時它也同樣有用。</summary></entry><entry><title type="html">Android原始碼下載</title><link href="https://xiwan.io/archive/android-source-download.html" rel="alternate" type="text/html" title="Android原始碼下載" /><published>2016-07-17T10:00:00+00:00</published><updated>2016-07-17T10:00:00+00:00</updated><id>https://xiwan.io/archive/android-source-download</id><content type="html" xml:base="https://xiwan.io/archive/android-source-download.html">&lt;p&gt;Android原始碼包含兩個部分：Android開源專案（Android Open Source Project，AOSP）原始碼與Android內核原始碼。&lt;/p&gt;

&lt;p&gt;下載前、首先確認以下事項：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;git&lt;/li&gt;
  &lt;li&gt;curl&lt;/li&gt;
  &lt;li&gt;python&lt;/li&gt;
  &lt;li&gt;POSIX相容系統&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;前三項都是下載所需的組件，不必多說；說說最後一項：一開始打算在Windows下使用MinGW32進行下載的，但是遇到這樣的錯誤：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;ImportError: No module named fcntl&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;於是、果斷投奔Mac OS X的懷抱。但也有人改換Cygwin，感興趣的可以一試。&lt;/p&gt;

&lt;p&gt;鑑於龐大的程式碼規模、請務必使用高速且穩定的網路：Android開源專案原始碼藉由專用工具repo、可以實現斷點續傳；而Android內核原始碼、一旦中斷就得重新開始。&lt;/p&gt;

&lt;p&gt;Android開源專案原始碼的下載教程請參考&lt;a href=&quot;https://source.android.com/source/downloading.html&quot;&gt;這裡&lt;/a&gt;，Android內核原始碼的下載教程請參考&lt;a href=&quot;https://source.android.com/source/building-kernels.html#downloading-sources&quot;&gt;這裡&lt;/a&gt;。以下簡單的做下筆記：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;其一、&lt;a href=&quot;/archive/android-source-download.html#aosp-source-download&quot;&gt;Android開源專案原始碼下載&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;其二、&lt;a href=&quot;/archive/android-source-download.html#android-kernel-source-download&quot;&gt;Android內核原始碼下載&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;!-- more --&gt;

&lt;h1 id=&quot;android開源專案原始碼下載&quot;&gt;&lt;a name=&quot;aosp-source-download&quot;&gt;&lt;/a&gt;Android開源專案原始碼下載&lt;/h1&gt;

&lt;p&gt;下載安裝repo：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;nawix-osx:~ kenmux$ mkdir bin
nawix-osx:~ kenmux$ cd bin
nawix-osx:bin kenmux$ curl https://storage.googleapis.com/git-repo-downloads/repo &amp;gt; ~/bin/repo
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 26223  100 26223    0     0  17546      0  0:00:01  0:00:01 --:--:-- 17552
nawix-osx:bin kenmux$ chmod a+x ~/bin/repo
nawix-osx:bin kenmux$ &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;注：由於沒有將&lt;code class=&quot;highlighter-rouge&quot;&gt;~/bin&lt;/code&gt;加入&lt;code class=&quot;highlighter-rouge&quot;&gt;PATH&lt;/code&gt;，故以下採用完整路徑來呼叫repo。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;初啟化repo客戶端：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;nawix-osx:bin kenmux$ mkdir android
nawix-osx:bin kenmux$ cd android/
nawix-osx:android kenmux$ ~/bin/repo init -u https://android.googlesource.com/platform/manifest
warning: gpg (GnuPG) is not available.
warning: Installing it is strongly encouraged.

......

Your identity is: kenmux &amp;lt;kenmux@gmail.com&amp;gt;
If you want to change this, please re-run 'repo init' with --config-name

Testing colorized output (for 'repo diff', 'repo status'):
  black    red      green    yellow   blue     magenta   cyan     white 
  bold     dim      ul       reverse 
Enable color display in this user account (y/N)? y

repo has been initialized in /Users/kenmux/bin/android
nawix-osx:android kenmux$ &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;下載Android原始碼樹：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;nawix-osx:android kenmux$ ~/bin/repo sync
Fetching project device/google/atv
Fetching project device/htc/flounder
Fetching project platform/prebuilts/devtools
Fetching project platform/hardware/marvell/bt

......

Checking out files: 100% (507/507), done.ng out files:  27% (140/507)   
Checking out files: 100% (15778/15778), done.ut files:   3% (627/15778)   
Checking out files: 100% (19/19), done.
Syncing work tree: 100% (515/515), done.  

nawix-osx:android kenmux$ &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;程式碼規模：&lt;/p&gt;

&lt;div style=&quot;text-align:center&quot;&gt;
  &lt;p&gt;&lt;img src=&quot;/assets/imgs/2016/07/17/android-source-download/2016-07-17-01.png&quot; alt=&quot;2016-07-17-01.png&quot; /&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;檢視內容：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;nawix-osx:android kenmux$ ls -al
total 48
drwxr-xr-x   32 kenmux  staff  1088  7 14 14:08 .
drwxr-xr-x    6 kenmux  staff   204  7 13 18:52 ..
-rw-r--r--@   1 kenmux  staff  8196  7 14 14:08 .DS_Store
drwxr-xr-x   10 kenmux  staff   340  7 14 13:39 .repo
lrwxr-xr-x    1 kenmux  staff    19  7 14 13:39 Android.bp -&amp;gt; build/soong/root.bp
-r--r--r--    1 kenmux  staff    87  7 14 13:39 Makefile
drwxr-xr-x   26 kenmux  staff   884  7 14 13:39 art
drwxr-xr-x   19 kenmux  staff   646  7 14 13:39 bionic
drwxr-xr-x    3 kenmux  staff   102  7 14 13:39 bootable
lrwxr-xr-x    1 kenmux  staff    26  7 14 13:39 bootstrap.bash -&amp;gt; build/soong/bootstrap.bash
drwxr-xr-x   14 kenmux  staff   476  7 14 13:39 build
drwxr-xr-x   18 kenmux  staff   612  7 14 13:40 cts
drwxr-xr-x   14 kenmux  staff   476  7 14 13:40 dalvik
drwxr-xr-x    5 kenmux  staff   170  7 14 13:40 developers
drwxr-xr-x   22 kenmux  staff   748  7 14 13:41 development
drwxr-xr-x   12 kenmux  staff   408  7 14 13:41 device
drwxr-xr-x    3 kenmux  staff   102  7 14 13:42 docs
drwxr-xr-x  245 kenmux  staff  8330  7 14 13:48 external
drwxr-xr-x   17 kenmux  staff   578  7 14 13:49 frameworks
drwxr-xr-x   13 kenmux  staff   442  7 14 13:50 hardware
drwxr-xr-x    3 kenmux  staff   102  7 14 13:50 kernel
drwxr-xr-x   31 kenmux  staff  1054  7 14 13:50 libcore
drwxr-xr-x   15 kenmux  staff   510  7 14 13:50 libnativehelper
drwxr-xr-x   23 kenmux  staff   782  7 14 13:50 ndk
drwxr-xr-x    9 kenmux  staff   306  7 14 13:52 packages
drwxr-xr-x    8 kenmux  staff   272  7 14 13:52 pdk
drwxr-xr-x    7 kenmux  staff   238  7 14 13:52 platform_testing
drwxr-xr-x   20 kenmux  staff   680  7 14 13:58 prebuilts
drwxr-xr-x   31 kenmux  staff  1054  7 14 13:59 sdk
drwxr-xr-x   22 kenmux  staff   748  7 14 13:59 system
drwxr-xr-x    3 kenmux  staff   102  7 14 13:59 toolchain
drwxr-xr-x    5 kenmux  staff   170  7 14 14:00 tools
nawix-osx:android kenmux$ &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;建議在做研究之前、先做備份：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;nawix-osx:android kenmux$ cd ..
nawix-osx:bin kenmux$ rsync -av android android.bak
building file list ... done
created directory android.bak
android/
android/.DS_Store
android/Android.bp -&amp;gt; build/soong/root.bp
android/Makefile
android/bootstrap.bash -&amp;gt; build/soong/bootstrap.bash
android/.repo/

......

android/tools/test/connectivity/acts/tests/google/wifi/WifiScannerMultiScanTest.py
android/tools/test/connectivity/acts/tests/google/wifi/WifiScannerScanTest.py
android/tools/test/connectivity/acts/tests/google/wifi/WifiScannerTests.config
android/tools/test/connectivity/acts/tests/sample/
android/tools/test/connectivity/acts/tests/sample/SampleTest.py

sent 35318215998 bytes  received 13530514 bytes  8087844.00 bytes/sec
total size is 35274344962  speedup is 1.00
nawix-osx:bin kenmux$ &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;羅列下屬專案：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;nawix-osx:bin kenmux$ cd android
nawix-osx:android kenmux$ ~/bin/repo forall -c 'echo &quot;$REPO_PATH -- $REPO_PROJECT&quot;'
art -- platform/art
bionic -- platform/bionic
bootable/recovery -- platform/bootable/recovery
build -- platform/build
build/blueprint -- platform/build/blueprint
build/kati -- platform/build/kati
build/soong -- platform/build/soong
cts -- platform/cts

......

toolchain/binutils -- toolchain/binutils
tools/apksig -- platform/tools/apksig
tools/external/fat32lib -- platform/tools/external/fat32lib
tools/external/gradle -- platform/tools/external/gradle
tools/test/connectivity -- platform/tools/test/connectivity
nawix-osx:android kenmux$ &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;檢視Git分支：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;nawix-osx:android kenmux$ cd .repo/manifests/
nawix-osx:manifests kenmux$ ls -a
.		..		.git		default.xml
nawix-osx:manifests kenmux$ git branch -r
  m/master -&amp;gt; origin/master
  origin/adt_23.0.3
  origin/android-1.6_r1
  origin/android-1.6_r1.1
  origin/android-1.6_r1.2
  origin/android-1.6_r1.3
  origin/android-1.6_r1.4
  origin/android-1.6_r1.5

  ......

  origin/webview-m40_r1
  origin/webview-m40_r2
  origin/webview-m40_r3
  origin/webview-m40_r4
nawix-osx:manifests kenmux$ &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;這樣、依次執行以下指令、便可以切換特定版本的Android原始碼：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;~/bin/repo init -u https://android.googlesource.com/platform/manifest -b [ANDROID_BRANCH]
~/bin/repo sync&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;關於repo指令的使用說明，請參考&lt;a href=&quot;https://source.android.com/source/using-repo.html&quot;&gt;Repo command reference&lt;/a&gt;，或者Efalk資料檔之&lt;a href=&quot;http://www.efalk.org/Docs/Git/repo.html&quot;&gt;Repo&lt;/a&gt;，這裡就不多加著墨了。&lt;/p&gt;

&lt;h1 id=&quot;android內核原始碼下載&quot;&gt;&lt;a name=&quot;android-kernel-source-download&quot;&gt;&lt;/a&gt;Android內核原始碼下載&lt;/h1&gt;

&lt;p&gt;Android內核原始碼下載則比較簡單，直接從遠端存儲庫複製即可：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;git clone https://android.googlesource.com/kernel/common.git
git clone https://android.googlesource.com/kernel/goldfish.git
git clone https://android.googlesource.com/kernel/hikey-linaro
git clone https://android.googlesource.com/kernel/x86_64.git
git clone https://android.googlesource.com/kernel/exynos.git
git clone https://android.googlesource.com/kernel/msm.git
git clone https://android.googlesource.com/kernel/omap.git
git clone https://android.googlesource.com/kernel/samsung.git
git clone https://android.googlesource.com/kernel/tegra.git&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;注：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;common專案、其原始碼通用&lt;/li&gt;
  &lt;li&gt;goldfish專案、其原始碼用於模擬器&lt;/li&gt;
  &lt;li&gt;其他類別專案、其原始碼用於不同廠商/設備&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;這裡下載的是common專案下的原始碼：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;cd nawix-osx:~ kenmux$ cd ~/bin
nawix-osx:bin kenmux$ mkdir kernel
nawix-osx:bin kenmux$ cd kernel
nawix-osx:kernel kenmux$ git clone https://android.googlesource.com/kernel/common.git
Cloning into 'common'...
remote: Sending approximately 1.01 GiB ...
remote: Total 4614730 (delta 3857285), reused 4614730 (delta 3857285)
Receiving objects: 100% (4614730/4614730), 1.01 GiB | 1.04 MiB/s, done.
Resolving deltas: 100% (3857285/3857285), done.
Checking connectivity... done.
nawix-osx:kernel kenmux$ &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;程式碼規模：&lt;/p&gt;

&lt;div style=&quot;text-align:center&quot;&gt;
  &lt;p&gt;&lt;img src=&quot;/assets/imgs/2016/07/17/android-source-download/2016-07-17-02.png&quot; alt=&quot;2016-07-17-02.png&quot; /&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;檢視Git分支：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;nawix-osx:kernel kenmux$ cd common/
nawix-osx:common kenmux$ ls -a
.	..	.git
nawix-osx:common kenmux$ git branch -av
* master                                                  fe8bf45 empty commit
  remotes/origin/HEAD                                     -&amp;gt; origin/master
  remotes/origin/android-3.10                             e11ce62 UPSTREAM: ppp: take reference on channels netns
  remotes/origin/android-3.10.y                           ed22006 update defconfig to enable ext4 encryption
  remotes/origin/android-3.14                             6186bde UPSTREAM: ppp: take reference on channels netns
  remotes/origin/android-3.18                             0433cb1 UPSTREAM: ppp: take reference on channels netns
  remotes/origin/android-3.4                              cb38f01 UPSTREAM: ppp: take reference on channels netns
  remotes/origin/android-4.1                              7436668 UPSTREAM: ppp: take reference on channels netns
  remotes/origin/android-4.4                              f8a27f3 UPSTREAM: ppp: take reference on channels netns
  remotes/origin/android-trusty-3.10                      78dd8a2 trusty-ipc: Add support for default Trusty IPC device
  remotes/origin/android-trusty-3.18                      182c86e trusty-virtio: Fix trusty_virtio_notify prototype
  remotes/origin/android-trusty-4.4                       096c9fe trusty-irq: Add support for secure interrupt mapping
  remotes/origin/bcmdhd-3.10                              4f750ce net: wireless: bcmdhd: Use different structs for scan reporting
  remotes/origin/brillo-m10-dev                           82c6fea ANDROID: dm-crypt: run in a WQ_HIGHPRI workqueue
  remotes/origin/brillo-m10-release                       22b269c BACKPORT: FROMLIST: mm: ASLR: use get_random_long()
  remotes/origin/brillo-m7-dev                            829d8c9 staging: ion: debugfs to shrink pool
  remotes/origin/brillo-m7-mr-dev                         829d8c9 staging: ion: debugfs to shrink pool
  remotes/origin/brillo-m7-release                        829d8c9 staging: ion: debugfs to shrink pool
  remotes/origin/brillo-m8-dev                            bd355208 Merge branch 'android-3.18-zram' into android-3.18
  remotes/origin/brillo-m8-release                        bd355208 Merge branch 'android-3.18-zram' into android-3.18
  remotes/origin/brillo-m9-dev                            7682bfa UPSTREAM: HID: hid-input: allow input_configured callback return errors
  remotes/origin/brillo-m9-release                        7682bfa UPSTREAM: HID: hid-input: allow input_configured callback return errors
  remotes/origin/deprecated/android-2.6.39                b8d9d67 Merge remote-tracking branch 'common/linux-bcm43xx-2.6.39' into common-39
  remotes/origin/deprecated/android-3.0                   563897d pstore: selinux: add security in-core xattr support for rootfs, pstore and debugfs
  remotes/origin/deprecated/android-3.10-adf              ac4ddb0 video: adf: expose adf_modeinfo_set_{name,vrefresh} to drivers
  remotes/origin/deprecated/android-3.3                   6c3978c Fix 3.3 merge error in: drivers: power: Add watchdog timer to catch drivers which lockup during suspend.
  remotes/origin/deprecated/android-3.4-compat            a7827a2 Merge branch 'android-3.4' into android-3.4-compat
  remotes/origin/deprecated/coupled-cpuidle               abcca36 cpuidle: coupled: add parallel barrier function
  remotes/origin/deprecated/experimental/android-3.10     3bfc1e9 sync: Fix a race condition between release_obj and print_obj
  remotes/origin/deprecated/experimental/android-3.10-rc5 4dc805f kconfig: Fix defconfig when one choice menu selects options that another choice menu depends on
  remotes/origin/deprecated/experimental/android-3.14     8e9fc46 xt_qtaguid: Fix boot panic
  remotes/origin/deprecated/experimental/android-3.18     1ba6b2f proc: make oom adjustment files user read-only
  remotes/origin/deprecated/experimental/android-3.8      e7f415e netfilter: xt_qtaguid: fix bad tcp_time_wait sock handling
  remotes/origin/deprecated/experimental/android-3.9      c98631b pstore: Update Documentation/android.txt
  remotes/origin/deprecated/experimental/android-3.9-rc2  33bc629 pstore: Update Documentation/android.txt
  remotes/origin/deprecated/experimental/android-3.9-rc7  6bc79ce fuse: Fix build after change to synchronize with library
  remotes/origin/deprecated/linux-bcm43xx-2.6.39          2277c65 net: wireless: bcmdhd: Report proper mcs rate mask
  remotes/origin/experimental/android-4.1                 01873ac trace: cpufreq: Add tracing for min/max cpufreq
  remotes/origin/experimental/android-4.4                 f782fce Revert &quot;HACK: input: evdev: disable EVIOCREVOKE&quot;
  remotes/origin/experimental/android-4.4-llvm            5e501df clang makefile
  remotes/origin/master                                   fe8bf45 empty commit
nawix-osx:common kenmux$ &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;</content><author><name>Kenmux Lee</name></author><category term="Android" /><category term="AOSP" /><category term="kernel" /><category term="repo" /><summary type="html">Android原始碼包含兩個部分：Android開源專案（Android Open Source Project，AOSP）原始碼與Android內核原始碼。 下載前、首先確認以下事項： git curl python POSIX相容系統 前三項都是下載所需的組件，不必多說；說說最後一項：一開始打算在Windows下使用MinGW32進行下載的，但是遇到這樣的錯誤： ImportError: No module named fcntl 於是、果斷投奔Mac OS X的懷抱。但也有人改換Cygwin，感興趣的可以一試。 鑑於龐大的程式碼規模、請務必使用高速且穩定的網路：Android開源專案原始碼藉由專用工具repo、可以實現斷點續傳；而Android內核原始碼、一旦中斷就得重新開始。 Android開源專案原始碼的下載教程請參考這裡，Android內核原始碼的下載教程請參考這裡。以下簡單的做下筆記： 其一、Android開源專案原始碼下載 其二、Android內核原始碼下載</summary></entry><entry><title type="html">閏年的最佳效率演算法</title><link href="https://xiwan.io/archive/most-efficient-leap-year-algorithm.html" rel="alternate" type="text/html" title="閏年的最佳效率演算法" /><published>2016-06-26T10:00:00+00:00</published><updated>2016-06-26T10:00:00+00:00</updated><id>https://xiwan.io/archive/most-efficient-leap-year-algorithm</id><content type="html" xml:base="https://xiwan.io/archive/most-efficient-leap-year-algorithm.html">&lt;p&gt;所謂閏年，維基百科做了如是&lt;a href=&quot;https://zh.wikipedia.org/wiki/%E9%97%B0%E5%B9%B4&quot;&gt;介紹&lt;/a&gt;：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;閏年是比普通年分多出一段時間的年分，在各種曆法中都有出現，目的是為了彌補人為規定的紀年與地球公轉產生的差異。&lt;/p&gt;

  &lt;p&gt;目前使用的格里曆閏年規則如下：&lt;/p&gt;

  &lt;ol&gt;
    &lt;li&gt;西元年分除以400可整除，為閏年。&lt;/li&gt;
    &lt;li&gt;西元年分除以4可整除但除以100不可整除，為閏年。&lt;/li&gt;
    &lt;li&gt;西元年分除以4不可整除，為平年。&lt;/li&gt;
    &lt;li&gt;西元年分除以100可整除但除以400不可整除，為平年。&lt;/li&gt;
  &lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;在C，C++，C#，Java，以及許多類C編程語言的入門書籍裏，舉凡講到運算子一節，一般都會提及閏年的演算法；且不出意外、一般皆為：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;400&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/* leap year */&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;這演算法簡單明瞭、但在效率上卻顯得差強人意；因而、在stackoverflow上有&lt;a href=&quot;https://stackoverflow.com/a/11595914/2518851&quot;&gt;討論&lt;/a&gt;，認為若以效率考量、則最佳演算法為：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;25&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/* leap year */&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;那麼、這「最佳效率」演算法如何得來呢？以下便是推演過程。&lt;!-- more --&gt;&lt;/p&gt;

&lt;h1 id=&quot;短路求值&quot;&gt;&lt;a name=&quot;short-circuit-evaluation&quot;&gt;&lt;/a&gt;短路求值&lt;/h1&gt;

&lt;p&gt;有以下兩點：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;許多編程語言都有&lt;a href=&quot;https://zh.wikipedia.org/wiki/%E7%9F%AD%E8%B7%AF%E6%B1%82%E5%80%BC&quot;&gt;短路求值&lt;/a&gt;的策略&lt;/li&gt;
  &lt;li&gt;不能被4整除的數、亦不能被400整除&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;則演算法可以重寫為：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;400&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/* leap year */&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;這樣、舉凡不能被4整除的年份皆為平年（格里曆閏年規則#3）；不用再去運算&lt;span style=&quot;background-color:#333333;color:white&quot;&gt;(year % 400) == 0&lt;/span&gt;、大大提高了演算法的效率。&lt;/p&gt;

&lt;h1 id=&quot;因式分解&quot;&gt;&lt;a name=&quot;factoring&quot;&gt;&lt;/a&gt;因式分解&lt;/h1&gt;

&lt;p&gt;因有等式&lt;span style=&quot;background-color:#333333;color:white&quot;&gt;100=4×25&lt;/span&gt;，則可被100整除等同於可被4和25整除。依據短路求值策略，在進行運算&lt;span style=&quot;background-color:#333333;color:white&quot;&gt;(year % 100) != 0&lt;/span&gt;時必有先決條件：運算式&lt;span style=&quot;background-color:#333333;color:white&quot;&gt;(year % 4) == 0&lt;/span&gt;為真，即年份可被4整除。&lt;/p&gt;

&lt;p&gt;故而、演算法可以重寫為：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;25&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;400&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/* leap year */&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;同理、因有&lt;span style=&quot;background-color:#333333;color:white&quot;&gt;400=25×16&lt;/span&gt;，演算法進一步可以重寫為：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;25&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/* leap year */&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1 id=&quot;位元與取代模除&quot;&gt;&lt;a name=&quot;bitwise-and-in-place-of-modulo&quot;&gt;&lt;/a&gt;位元與取代模除&lt;/h1&gt;

&lt;p&gt;執行模除運算需要除法。而除法運算在某些低端CPU上非常耗時。因而、最好避免不必要的模除運算。&lt;/p&gt;

&lt;p&gt;特別地、當模數為2的指數冪時、模除運算可用位元與&lt;a href=&quot;https://en.wikipedia.org/wiki/Modulo_operation#Performance_issues&quot;&gt;代替&lt;/a&gt;，即：&lt;span style=&quot;background-color:#333333;color:white&quot;&gt;x % 2^n == x &amp;amp; (2^n - 1)&lt;/span&gt;。&lt;/p&gt;

&lt;p&gt;因而、演算法可以重寫為：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;25&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/* leap year */&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;至此、推演告一段落；不過、以下兩點更加有趣哦～&lt;/p&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;15還是12&quot;&gt;&lt;a name=&quot;15-or-12&quot;&gt;&lt;/a&gt;15還是12&lt;/h1&gt;

&lt;p&gt;有以下三點：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;運算式&lt;span style=&quot;background-color:#333333;color:white&quot;&gt;(year &amp;amp; 3) == 0&lt;/span&gt;檢查年份的[0..1]位元是否皆為0&lt;/li&gt;
  &lt;li&gt;運算式&lt;span style=&quot;background-color:#333333;color:white&quot;&gt;(year &amp;amp; 12) == 0&lt;/span&gt;檢查年份的[2..3]位元是否皆為0&lt;/li&gt;
  &lt;li&gt;運算式&lt;span style=&quot;background-color:#333333;color:white&quot;&gt;(year &amp;amp; 15) == 0&lt;/span&gt;檢查年份的[0..3]位元是否皆為0&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;因而、#3可由#1與#2協同完成；而依據短路求值策略，在進行運算&lt;span style=&quot;background-color:#333333;color:white&quot;&gt;(year &amp;amp; 15) == 0&lt;/span&gt;時必有先決條件：運算式&lt;span style=&quot;background-color:#333333;color:white&quot;&gt;(year &amp;amp; 3) == 0&lt;/span&gt;為真。&lt;/p&gt;

&lt;p&gt;故15也可以12替代、演算法也可重寫為：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;25&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/* leap year */&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1 id=&quot;4000年問題&quot;&gt;&lt;a name=&quot;year-4000-problem&quot;&gt;&lt;/a&gt;4000年問題&lt;/h1&gt;

&lt;p&gt;按照現行閏年規則，西元4000年應當為閏年；但到西元8000時，時日又會有所差錯，故有提議西元4000年為平年，並修改規則為：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;ol start=&quot;0&quot;&gt;
  &lt;li&gt;西元年分除以4000可整除，為平年。&lt;/li&gt;
  &lt;li&gt;西元年分除以400可整除但除以4000不可整除，為閏年。&lt;/li&gt;
  &lt;li&gt;西元年分除以4可整除但除以100不可整除，為閏年。&lt;/li&gt;
  &lt;li&gt;西元年分除以4不可整除，為平年。&lt;/li&gt;
  &lt;li&gt;西元年分除以100可整除但除以400不可整除，為平年。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;據此、又有人提出了「最終極」「最佳效率」&lt;a href=&quot;https://gist.github.com/rindeal/8ed6fdb24650c9d17416&quot;&gt;演算法&lt;/a&gt;：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;25&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;year&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/* leap year */&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;</content><author><name>Kenmux Lee</name></author><category term="Leap Year" /><category term="Algorithm" /><summary type="html">所謂閏年，維基百科做了如是介紹： 閏年是比普通年分多出一段時間的年分，在各種曆法中都有出現，目的是為了彌補人為規定的紀年與地球公轉產生的差異。 目前使用的格里曆閏年規則如下： 西元年分除以400可整除，為閏年。 西元年分除以4可整除但除以100不可整除，為閏年。 西元年分除以4不可整除，為平年。 西元年分除以100可整除但除以400不可整除，為平年。 在C，C++，C#，Java，以及許多類C編程語言的入門書籍裏，舉凡講到運算子一節，一般都會提及閏年的演算法；且不出意外、一般皆為： if (((year % 4) == 0 &amp;amp;&amp;amp; (year % 100) != 0) || (year % 400) == 0) { /* leap year */ } 這演算法簡單明瞭、但在效率上卻顯得差強人意；因而、在stackoverflow上有討論，認為若以效率考量、則最佳演算法為： if ((year &amp;amp; 3) == 0 &amp;amp;&amp;amp; ((year % 25) != 0 || (year &amp;amp; 15) == 0)) { /* leap year */ } 那麼、這「最佳效率」演算法如何得來呢？以下便是推演過程。</summary></entry><entry><title type="html">Mac OS X下Git遇到編輯器vi錯誤</title><link href="https://xiwan.io/archive/macosx-git-commit-error-with-vi.html" rel="alternate" type="text/html" title="Mac OS X下Git遇到編輯器vi錯誤" /><published>2015-12-06T10:00:00+00:00</published><updated>2015-12-06T10:00:00+00:00</updated><id>https://xiwan.io/archive/macosx-git-commit-error-with-vi</id><content type="html" xml:base="https://xiwan.io/archive/macosx-git-commit-error-with-vi.html">&lt;p&gt;Mac OS X下Git進行提交時，會遇到這樣的錯誤：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;error: There was a problem with the editor ‘vi’.&lt;br /&gt;
Please supply the message using either -m or -F option.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;大致搜尋了下，stackoverflow上的這個&lt;a href=&quot;https://stackoverflow.com/a/14607585/2518851&quot;&gt;解答&lt;/a&gt;剛好對症。&lt;!-- more --&gt;&lt;/p&gt;

&lt;p&gt;而且，解答下還附有對此進行深入探究的博文：&lt;a href=&quot;http://tooky.co.uk/there-was-a-problem-with-the-editor-vi-git-on-mac-os-x/&quot;&gt;Fixing “There was a problem with the editor ‘vi’” for Git on Mac OS X Snow Leopard&lt;/a&gt;（也是2010年的老物，看來這是老問題呀～）&lt;/p&gt;

&lt;p&gt;解決方案為設置Git的編輯器為vim，具體實現有兩種：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/archive/macosx-git-commit-error-with-vi.html#edit-gitconfig&quot;&gt;其一、修改配置檔.gitconfig&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/archive/macosx-git-commit-error-with-vi.html#call-git-config&quot;&gt;其二、呼叫&lt;code class=&quot;highlighter-rouge&quot;&gt;git config&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;修改配置檔gitconfig&quot;&gt;&lt;a name=&quot;edit-gitconfig&quot;&gt;&lt;/a&gt;修改配置檔.gitconfig&lt;/h1&gt;

&lt;p&gt;使用vim打開配置檔.gitconfig：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;vim ~/.gitconfig&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;然後、確保有這兩列即可：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;[core]
  editor = /usr/bin/vim&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;/usr/bin/vim&lt;/code&gt;即vim路徑，妳可以通過指令&lt;code class=&quot;highlighter-rouge&quot;&gt;which vim&lt;/code&gt;進行查詢。&lt;/p&gt;

&lt;h1 id=&quot;呼叫git-config&quot;&gt;&lt;a name=&quot;call-git-config&quot;&gt;&lt;/a&gt;呼叫&lt;code class=&quot;highlighter-rouge&quot;&gt;git config&lt;/code&gt;&lt;/h1&gt;

&lt;p&gt;這是stackoverflow上給的解答：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;git config &lt;span class=&quot;nt&quot;&gt;--global&lt;/span&gt; core.editor /usr/bin/vim&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;不過，我更推薦這樣用：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;git config &lt;span class=&quot;nt&quot;&gt;--global&lt;/span&gt; core.editor &lt;span class=&quot;k&quot;&gt;$(&lt;/span&gt;which vim&lt;span class=&quot;k&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;</content><author><name>Kenmux Lee</name></author><category term="Mac OS X" /><category term="Git" /><category term="vi" /><summary type="html">Mac OS X下Git進行提交時，會遇到這樣的錯誤： error: There was a problem with the editor ‘vi’. Please supply the message using either -m or -F option. 大致搜尋了下，stackoverflow上的這個解答剛好對症。</summary></entry><entry><title type="html">【譯】如何撰寫Git提交訊息</title><link href="https://xiwan.io/archive/how-to-write-a-git-commit-message.html" rel="alternate" type="text/html" title="【譯】如何撰寫Git提交訊息" /><published>2015-11-01T10:00:00+00:00</published><updated>2015-11-01T10:00:00+00:00</updated><id>https://xiwan.io/archive/how-to-write-a-git-commit-message</id><content type="html" xml:base="https://xiwan.io/archive/how-to-write-a-git-commit-message.html">&lt;p&gt;這篇筆記、翻譯自博文：&lt;a href=&quot;http://chris.beams.io/posts/git-commit/&quot;&gt;How to Write a Git Commit Message&lt;/a&gt;，作者Chris Beams。尊重他人勞動果實，轉載請註明！&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/imgs/2015/11/01/how-to-write-a-git-commit-message/2015-11-01-01.png&quot; alt=&quot;2015-11-01-01.png&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/archive/how-to-write-a-git-commit-message.html#intro&quot;&gt;引言&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/archive/how-to-write-a-git-commit-message.html#seven-rules&quot;&gt;七條規則&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/archive/how-to-write-a-git-commit-message.html#tips&quot;&gt;建議&lt;/a&gt;&lt;br /&gt;
&lt;!--Introduction | The Seven Rules | Tips--&gt;&lt;!-- more --&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;引言為什麼好的提交訊息很重要&quot;&gt;&lt;a name=&quot;intro&quot;&gt;&lt;/a&gt;引言：為什麼好的提交訊息很重要&lt;/h1&gt;
&lt;!--Introduction: Why good commit messages matter--&gt;

&lt;p&gt;如果你隨意瀏覽一些Git存儲庫的日誌，你可能會發現提交訊息或多或少都亂成一團。例如，看看我早些時候向Spring提交的一些&lt;a href=&quot;https://github.com/spring-projects/spring-framework/commits/e5f4b49?author=cbeams&quot;&gt;gems&lt;/a&gt;：&lt;br /&gt;
&lt;!--If you browse the log of any random git repository you will probably find its commit messages are more or less a mess. For example, take a look at these gems from my early days committing to Spring:--&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;$ git log --oneline -5 --author cbeams --before &quot;Fri Mar 26 2009&quot;

e5f4b49 Re-adding ConfigurationPostProcessorTests after its brief removal in r814. @Ignore-ing the testCglibClassesAreLoadedJustInTimeForEnhancement() method as it turns out this was one of the culprits in the recent build breakage. The classloader hacking causes subtle downstream effects, breaking unrelated tests. The test method is still useful, but should only be run on a manual basis to ensure CGLIB is not prematurely classloaded, and should not be run as part of the automated build.
2db0f12 fixed two build-breaking issues: + reverted ClassMetadataReadingVisitor to revision 794 + eliminated ConfigurationPostProcessorTests until further investigation determines why it causes downstream tests to fail (such as the seemingly unrelated ClassPathXmlApplicationContextTests)
147709f Tweaks to package-info.java files
22b25e0 Consolidated Util and MutableAnnotationUtils classes into existing AsmUtils
7f96f57 polishing&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;呀！相比之下，再看看這一存儲庫&lt;a href=&quot;https://github.com/spring-projects/spring-framework/commits/5ba3db?author=philwebb&quot;&gt;近些時候&lt;/a&gt;的提交訊息：&lt;br /&gt;
&lt;!--Yikes. Compare that with these more recent commits from the same repository:--&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;$ git log --oneline -5 --author pwebb --before &quot;Sat Aug 30 2014&quot;

5ba3db6 Fix failing CompositePropertySourceTests
84564a0 Rework @PropertySource early parsing logic
e142fd1 Add tests for ImportSelector meta-data
887815f Update docbook dependency and generate epub
ac8326d Polish mockito usage&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;你更願意看到哪一種？&lt;br /&gt;
&lt;!--Which would you rather read?--&gt;&lt;/p&gt;

&lt;p&gt;前者長短不一、格式各異；後者簡潔明了、前後一致。前者天然雕飾；後者精心構築。&lt;br /&gt;
&lt;!--The former varies wildly in length and form; the latter is concise and consistent. The former is what happens by default; the latter never happens by accident.--&gt;&lt;/p&gt;

&lt;p&gt;雖然許多存儲庫日誌看起來像前者，但凡事皆有例外。&lt;a href=&quot;https://github.com/torvalds/linux/commits/master&quot;&gt;Linux內核&lt;/a&gt;和&lt;a href=&quot;https://github.com/git/git/commits/master&quot;&gt;Git本身&lt;/a&gt;都是很好的例子。看看&lt;a href=&quot;https://github.com/spring-projects/spring-boot/commits/master&quot;&gt;Spring Boot&lt;/a&gt;，或任何由&lt;a href=&quot;https://github.com/tpope/vim-pathogen/commits/master&quot;&gt;Tim Pope&lt;/a&gt;管理的存儲庫。&lt;br /&gt;
&lt;!--While many repositories' logs look like the former, there are exceptions. The Linux kernel and git itself are great examples. Look at Spring Boot, or any repository managed by Tim Pope.--&gt;&lt;/p&gt;

&lt;p&gt;這些存儲庫的貢獻者們知道，一條精心撰寫的Git提交訊息是和其他開發人員就一個改動的上下文進行溝通的最佳方式（實際上也是和未來的他們自己）。一個diff會告訴你作了哪些改動，但只有提交訊息能正確地告訴你為什麼。Peter Hutterer做了很好的詮釋：&lt;br /&gt;
&lt;!--The contributors to these repositories know that a well-crafted git commit message is the best way to communicate context about a change to fellow developers (and indeed to their future selves). A diff will tell you what changed, but only the commit message can properly tell you why. Peter Hutterer makes this point well:--&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;重建一段程式碼的上下文是一種浪費。我們不能完全避免，所以我們應竭盡所能去&lt;a href=&quot;http://www.osnews.com/story/19266/WTFs_m&quot;&gt;減少它&lt;/a&gt;、越多越好。提交訊息完全可以做到這點，因此，提交訊息顯示了這個開發人員是否是一個好的協作者。&lt;br /&gt;
&lt;!--Re-establishing the context of a piece of code is wasteful. We can't avoid it completely, so our efforts should go to reducing it [as much] as possible. Commit messages can do exactly that and as a result, a commit message shows whether a developer is a good collaborator.--&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;如果你還不曾考慮過如何更好地撰寫Git提交訊息的話，這可能是因為你沒有花多少時間使用&lt;code class=&quot;highlighter-rouge&quot;&gt;git log&lt;/code&gt;和相關工具。這是一個惡性循環：因為提交歷史是結構混亂、前後矛盾的，人們不會在使用或打理它上花太多時間。而且，因為不被常用或打理，它將保持結構混亂、前後矛盾的。&lt;br /&gt;
&lt;!--If you haven't given much thought to what makes a great git commit message, it may be the case that you haven't spent much time using git log and related tools. There is a vicious cycle here: because the commit history is unstructured and inconsistent, one doesn't spend much time using or taking care of it. And because it doesn't get used or taken care of it, it remains unstructured and inconsistent.--&gt;&lt;/p&gt;

&lt;p&gt;但精心打理的日誌是優雅和有用的。 &lt;code class=&quot;highlighter-rouge&quot;&gt;git blame&lt;/code&gt;、&lt;code class=&quot;highlighter-rouge&quot;&gt;revert&lt;/code&gt;、&lt;code class=&quot;highlighter-rouge&quot;&gt;rebase&lt;/code&gt;、&lt;code class=&quot;highlighter-rouge&quot;&gt;log&lt;/code&gt;、&lt;code class=&quot;highlighter-rouge&quot;&gt;shortlog&lt;/code&gt;等子指令都會復活。查看其他人的提交和拉取請求成為值得做的事，並可獨立完成。理解數月或數年前所發生的原委就變得不僅可能，而且高效。&lt;br /&gt;
&lt;!--But a well-cared for log is a beautiful and useful thing. git blame, revert, rebase, log, shortlog and other subcommands come to life. Reviewing others' commits and pull requests becomes something worth doing, and suddenly can be done independently. Understanding why something happened months or years ago becomes not only possible but efficient.--&gt;&lt;/p&gt;

&lt;p&gt;一個專案的長期成功取決於（和其他方面相比）維護工作，而對於維護人員來說、鮮有比專案日誌功能更強大的工具。花時間去學習如何正確地打理是值得的。起初可能是個麻煩、不久變成習慣，最後成為所有參與者自豪感和生產力的來源。&lt;br /&gt;
&lt;!--A project's long-term success rests (among other things) on its maintainability, and a maintainer has few tools more powerful than his project's log. It's worth taking the time to learn how to care for one properly. What may be a hassle at first soon becomes habit, and eventually a source of pride and productivity for all involved.--&gt;&lt;/p&gt;

&lt;p&gt;在這篇文章中，我將探討保持一個健康的提交歷史的最基本的要點：如何撰寫個人提交訊息。其他重要實踐如壓縮提交則不會涉及。也許我會在隨後的文章裡進行探討。&lt;br /&gt;
&lt;!--In this post, I am addressing just the most basic element of keeping a healthy commit history: how to write an individual commit message. There are other important practices like commit squashing that I am not addressing here. Perhaps I'll do that in a subsequent post.--&gt;&lt;/p&gt;

&lt;p&gt;大多數編程語言都有完善的構成慣用風格的慣例，例如，命名、格式等等。當然，這些慣例千差萬別；但大多數開發者都同意：選擇一種並堅持下去、遠比各行其是而混亂不堪強的多。&lt;br /&gt;
&lt;!--Most programming languages have well-established conventions as to what constitutes idiomatic style, i.e. naming and formatting and so on. There are variations on these conventions, of course, but most developers agree that picking one and sticking to it is far better than the chaos that ensues when everybody does their own thing.--&gt;&lt;/p&gt;

&lt;p&gt;一個團隊訪問提交日誌的方式應當相同。為了創建有用的修訂版本歷史記錄，團隊應首先在提交訊息的慣例上達成一致，並至少確定以下三樣事情：&lt;br /&gt;
&lt;!--A team's approach to its commit log should be no different. In order to create a useful revision history, teams should first agree on a commit message convention that defines at least the following three things:--&gt;&lt;/p&gt;

&lt;p&gt;&lt;b&gt;樣式。&lt;/b&gt;標記語法、自動換行間距、語法、大小寫、標點符號。把這些東西都寫出來，而不是靠臆測，並讓這一切盡可能的簡單。最終的結果將是一個非常一致的日誌，不僅讀來引人入勝，而且確實會定期進行審閱。&lt;br /&gt;
&lt;!--Style. Markup syntax, wrap margins, grammar, capitalization, punctuation. Spell these things out, remove the guesswork, and make it all as simple as possible. The end result will be a remarkably consistent log that's not only a pleasure to read but that actually does get read on a regular basis.--&gt;&lt;/p&gt;

&lt;p&gt;&lt;b&gt;內容。&lt;/b&gt;哪些信息應當包含在提交訊息的正文（如果有的話）中？哪些不應包含？
&lt;!--Content. What kind of information should the body of the commit message (if any) contain? What should it not contain?--&gt;&lt;/p&gt;

&lt;p&gt;&lt;b&gt;元資料。&lt;/b&gt;應當如何引用議題跟踪ID、拉取請求編號等等？&lt;br /&gt;
&lt;!--Metadata. How should issue tracking IDs, pull request numbers, etc. be referenced?--&gt;&lt;/p&gt;

&lt;p&gt;幸運的是，如何生成規範的Git提交訊息已經有完善的慣例。事實上，它們中相當一部分業已內置於Git命令中。你並不需要重新造輪子。只要遵循以下&lt;a href=&quot;/archive/how-to-write-a-git-commit-message.html#seven-rules&quot;&gt;七條規則&lt;/a&gt;，你就可以撰寫出合乎規範的提交日誌。&lt;br /&gt;
&lt;!--Fortunately, there are well-established conventions as to what makes an idiomatic git commit message. Indeed, many of them are assumed in the way certain git commands function. There's nothing you need to re-invent. Just follow the seven rules below and you're on your way to committing like a pro.--&gt;&lt;/p&gt;

&lt;h1 id=&quot;好的git提交訊息的七條規則&quot;&gt;&lt;a name=&quot;seven-rules&quot;&gt;&lt;/a&gt;好的Git提交訊息的七條規則&lt;/h1&gt;
&lt;!--The seven rules of a great git commit message--&gt;

&lt;blockquote&gt;
  &lt;p&gt;請謹記：&lt;a href=&quot;http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html&quot;&gt;這&lt;/a&gt;  &lt;a href=&quot;http://www.git-scm.com/book/en/Distributed-Git-Contributing-to-a-Project#Commit-Guidelines&quot;&gt;些&lt;/a&gt;  &lt;a href=&quot;https://github.com/torvalds/subsurface/blob/master/README#L82-109&quot;&gt;都&lt;/a&gt;  &lt;a href=&quot;http://who-t.blogspot.co.at/2009/12/on-commit-messages.html&quot;&gt;曾&lt;/a&gt;  &lt;a href=&quot;https://github.com/erlang/otp/wiki/writing-good-commit-messages&quot;&gt;說&lt;/a&gt;  &lt;a href=&quot;https://github.com/spring-projects/spring-framework/blob/30bce7/CONTRIBUTING.md#format-commit-messages&quot;&gt;過&lt;/a&gt;。&lt;br /&gt;
&lt;!--Keep in mind: This has all been said before.--&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;1.&lt;a href=&quot;/archive/how-to-write-a-git-commit-message.html#separate&quot;&gt;以空行隔開主題與正文&lt;/a&gt;&lt;br /&gt;
2.&lt;a href=&quot;/archive/how-to-write-a-git-commit-message.html#limit-50&quot;&gt;限制主題行長度在50個字符以內&lt;/a&gt;&lt;br /&gt;
3.&lt;a href=&quot;/archive/how-to-write-a-git-commit-message.html#capitalize&quot;&gt;主題行首字母大寫&lt;/a&gt;&lt;br /&gt;
4.&lt;a href=&quot;/archive/how-to-write-a-git-commit-message.html#end&quot;&gt;主題行結尾不要使用句號&lt;/a&gt;&lt;br /&gt;
5.&lt;a href=&quot;/archive/how-to-write-a-git-commit-message.html#imperative&quot;&gt;在主題行使用祈使語氣&lt;/a&gt;&lt;br /&gt;
6.&lt;a href=&quot;/archive/how-to-write-a-git-commit-message.html#wrap-72&quot;&gt;正文在72個字符處換行&lt;/a&gt;&lt;br /&gt;
7.&lt;a href=&quot;/archive/how-to-write-a-git-commit-message.html#why-not-how&quot;&gt;用正文來解釋是什麼以及為什麼，而不是怎麼樣&lt;/a&gt;&lt;br /&gt;
&lt;!--
1.Separate subject from body with a blank line
2.Limit the subject line to 50 characters
3.Capitalize the subject line
4.Do not end the subject line with a period
5.Use the imperative mood in the subject line
6.Wrap the body at 72 characters
7.Use the body to explain what and why vs. how
--&gt;&lt;/p&gt;

&lt;p&gt;例如：&lt;br /&gt;
&lt;!--For example:--&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;Summarize changes in around 50 characters or less

More detailed explanatory text, if necessary. Wrap it to about 72
characters or so. In some contexts, the first line is treated as the
subject of the commit and the rest of the text as the body. The
blank line separating the summary from the body is critical (unless
you omit the body entirely); various tools like `log`, `shortlog`
and `rebase` can get confused if you run the two together.

Explain the problem that this commit is solving. Focus on why you
are making this change as opposed to how (the code explains that).
Are there side effects or other unintuitive consequenses of this
change? Here's the place to explain them.

Further paragraphs come after blank lines.

 - Bullet points are okay, too

 - Typically a hyphen or asterisk is used for the bullet, preceded
   by a single space, with blank lines in between, but conventions
   vary here

If you use an issue tracker, put references to them at the bottom,
like this:

Resolves: #123
See also: #456, #789&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;1以空行隔開主題與正文&quot;&gt;&lt;a name=&quot;separate&quot;&gt;&lt;/a&gt;1.以空行隔開主題與正文&lt;/h3&gt;
&lt;!--1. Separate subject from body with a blank line--&gt;

&lt;p&gt;援引&lt;code class=&quot;highlighter-rouge&quot;&gt;git commit&lt;/code&gt;&lt;a href=&quot;https://www.kernel.org/pub/software/scm/git/docs/git-commit.html#_discussion&quot;&gt;手冊頁&lt;/a&gt;：&lt;br /&gt;
&lt;!--From the git commit manpage:--&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;雖然不是必需的，在提交訊息中、以簡短（少於50個字符）的一行文字來概述改動、緊接著空一行、然後再接上更為詳盡的描述，不失為一個好主意。在提交訊息中，空行前的文本被視為提交的標題，並且這個標題會在Git中到處使用。例如，git-format-patch(1)將提交轉換為電子郵件，會將標題作為主題，而提交的其餘部分則為正文。&lt;br /&gt;
&lt;!--Though not required, it's a good idea to begin the commit message with a single short (less than 50 character) line summarizing the change, followed by a blank line and then a more thorough description. The text up to the first blank line in a commit message is treated as the commit title, and that title is used throughout Git. For example, git-format-patch(1) turns a commit into email, and it uses the title on the Subject line and the rest of the commit in the body.--&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;首先，並非所有提交都需要主題正文俱全。有時單獨一行即可，特別是當改動非常簡單、並不需要上下文時。例如：&lt;br /&gt;
&lt;!--Firstly, not every commit requires both a subject and a body. Sometimes a single line is fine, especially when the change is so simple that no further context is necessary. For example:--&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;Fix typo in introduction to user guide&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;無需多言；如果讀者想知道哪裡拼寫錯了，她只需查看下改動即可，例如，使用&lt;code class=&quot;highlighter-rouge&quot;&gt;git show&lt;/code&gt;或&lt;code class=&quot;highlighter-rouge&quot;&gt;git diff&lt;/code&gt;或&lt;code class=&quot;highlighter-rouge&quot;&gt;git log -p&lt;/code&gt;。&lt;br /&gt;
&lt;!--Nothing more need be said; if the reader wonders what the typo was, she can simply take a look at the change itself, i.e. use git show or git diff or git log -p.--&gt;&lt;/p&gt;

&lt;p&gt;如果你在命令列下進行類似的提交時，只需在&lt;code class=&quot;highlighter-rouge&quot;&gt;git commit&lt;/code&gt;時使用開關&lt;code class=&quot;highlighter-rouge&quot;&gt;-m&lt;/code&gt;即可：&lt;br /&gt;
&lt;!--If you're committing something like this at the command line, it's easy to use the -m switch to git commit:--&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;$ git commit -m &quot;Fix typo in introduction to user guide&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;但是，當提交需要一些解釋和上下文時，你需要撰寫正文。例如：&lt;br /&gt;
&lt;!--However, when a commit merits a bit of explanation and context, you need to write a body. For example:--&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;Derezz the master control program

MCP turned out to be evil and had become intent on world domination.
This commit throws Tron's disc into MCP (causing its deresolution)
and turns it back into a chess game.&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;這時使用開關&lt;code class=&quot;highlighter-rouge&quot;&gt;-m&lt;/code&gt;進行提交就不那麼容易了。你真的需要一個適當的編輯器。如果你還沒有設置在命令列下配合Git使用的編輯器，請閱讀&lt;a href=&quot;http://git-scm.com/book/ch7-1.html#Basic-Client-Configuration&quot;&gt;Pro Git的這個章節&lt;/a&gt;。&lt;br /&gt;
&lt;!--This is not so easy to commit this with the -m switch. You really need a proper editor. If you do not already have an editor set up for use with git at the command line, read this section of Pro Git.--&gt;&lt;/p&gt;

&lt;p&gt;在任何情況下，隔開主題與正文會在瀏覽日誌時得到回報。下面是完整的日誌條目：&lt;br /&gt;
&lt;!--In any case, the separation of subject from body pays off when browsing the log. Here's the full log entry:--&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;$ git log
commit 42e769bdf4894310333942ffc5a15151222a87be
Author: Kevin Flynn &amp;lt;kevin@flynnsarcade.com&amp;gt;
Date:   Fri Jan 01 00:00:00 1982 -0200

 Derezz the master control program

 MCP turned out to be evil and had become intent on world domination.
 This commit throws Tron's disc into MCP (causing its deresolution)
 and turns it back into a chess game.&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;現在&lt;code class=&quot;highlighter-rouge&quot;&gt;git log --oneline&lt;/code&gt;，僅僅只會打印出主題行：&lt;br /&gt;
&lt;!--And now git log --oneline, which prints out just the subject line:--&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;$ git log --oneline
42e769 Derezz the master control program&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;或者，&lt;code class=&quot;highlighter-rouge&quot;&gt;git shortlog&lt;/code&gt;，將提交以使用者進行分組，同樣也只簡潔地顯示了主題行：&lt;br /&gt;
&lt;!--Or, git shortlog, which groups commits by user, again showing just the subject line for concision:--&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;$ git shortlog
Kevin Flynn (1):
      Derezz the master control program

Alan Bradley (1):
      Introduce security program &quot;Tron&quot;

Ed Dillinger (3):
      Rename chess program to &quot;MCP&quot;
      Modify chess program
      Upgrade chess program

Walter Gibbs (1):
      Introduce protoype chess program&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;區分開主題行與正文，在git中還有其他若干應用場景——但若兩者間沒有空行、它們都不會正常工作。&lt;br /&gt;
&lt;!--There are a number of other contexts in git where the distinction between subject line and body kicks in—but none of them work properly without the blank line in between.--&gt;&lt;/p&gt;

&lt;h3 id=&quot;2限制主題行長度在50個字符以內&quot;&gt;&lt;a name=&quot;limit-50&quot;&gt;&lt;/a&gt;2.限制主題行長度在50個字符以內&lt;/h3&gt;
&lt;!--2. Limit the subject line to 50 characters--&gt;

&lt;p&gt;50個字符並不是硬性的限制，而只是經驗法則【&lt;a href=&quot;https://en.wikipedia.org/wiki/Rule_of_thumb&quot;&gt;Rule of thumb&lt;/a&gt;，拇指規則，又作：經驗法則】。保持主題行在這個長度內可確保其可讀性，並迫使作者花點時間考慮下以最簡潔的方式來闡述發生了什麼。&lt;br /&gt;
&lt;!--50 characters is not a hard limit, just a rule of thumb. Keeping subject lines at this length ensures that they are readable, and forces the author to think for a moment about the most concise way to explain what's going on.--&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;建議：如果你難於概述主題，那可能是你一次提交了太多的改動。爭取&lt;a href=&quot;http://www.freshconsulting.com/atomic-commits/&quot;&gt;原子提交&lt;/a&gt;（一個提交一個議題）。&lt;br /&gt;
&lt;!--Tip: If you're having a hard time summarizing, you might be committing too many changes at once. Strive for atomic commits (a topic for a separate post).--&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;GitHub的使用者介面充分照顧了這些慣例。如果你超過了50個字符的限制，它會給予警告：&lt;br /&gt;
&lt;!--GitHub's UI is fully aware of these conventions. It will warn you if you go past the 50 character limit:--&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/imgs/2015/11/01/how-to-write-a-git-commit-message/2015-11-01-02.png&quot; alt=&quot;2015-11-01-02.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;而且、會以省略號截斷任何長度超過69個字符的主題行：&lt;br /&gt;
&lt;!--And will truncate any subject line longer than 69 characters with an ellipsis:--&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/imgs/2015/11/01/how-to-write-a-git-commit-message/2015-11-01-03.png&quot; alt=&quot;2015-11-01-03.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;所以爭取50個字符以內，但以69為硬性限制。&lt;br /&gt;
&lt;!--So shoot for 50 characters, but consider 69 the hard limit.--&gt;&lt;/p&gt;

&lt;h3 id=&quot;3主題行首字母大寫&quot;&gt;&lt;a name=&quot;capitalize&quot;&gt;&lt;/a&gt;3.主題行首字母大寫&lt;/h3&gt;
&lt;!--3. Capitalize the subject line--&gt;

&lt;p&gt;這正如聽起來那麼簡單。所有主題行起始於大寫字母。&lt;br /&gt;
&lt;!--This is as simple as it sounds. Begin all subject lines with a capital letter.--&gt;&lt;/p&gt;

&lt;p&gt;例如：&lt;br /&gt;
&lt;!--For example:--&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;font color=&quot;green&quot;&gt;Accelerate to 88 miles per hour&lt;/font&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;而不是：&lt;br /&gt;
&lt;!--Instead of:--&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;font color=&quot;red&quot;&gt;accelerate to 88 miles per hour&lt;/font&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;4主題行結尾不要使用句號&quot;&gt;&lt;a name=&quot;end&quot;&gt;&lt;/a&gt;4.主題行結尾不要使用句號&lt;/h3&gt;
&lt;!--4. Do not end the subject line with a period--&gt;

&lt;p&gt;主題行結尾處的標點符號是沒有必要的。再說，當你試圖將其保持在&lt;a href=&quot;/archive/how-to-write-a-git-commit-message.html#limit-50&quot;&gt;50個字符以內&lt;/a&gt;時，空間就很珍貴。&lt;br /&gt;
&lt;!--Trailing punctuation is unnecessary in subject lines. Besides, space is precious when you're trying to keep them to 50 chars or less.--&gt;&lt;/p&gt;

&lt;p&gt;例如：&lt;br /&gt;
&lt;!--Example:--&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;font color=&quot;green&quot;&gt;Open the pod bay doors&lt;/font&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;而不是：&lt;br /&gt;
&lt;!--Instead of:--&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;font color=&quot;red&quot;&gt;Open the pod bay doors.&lt;/font&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;5在主題行使用祈使語氣&quot;&gt;&lt;a name=&quot;imperative&quot;&gt;&lt;/a&gt;5.在主題行使用祈使語氣&lt;/h3&gt;
&lt;!--5. Use the imperative mood in the subject line--&gt;

&lt;p&gt;祈使語氣僅僅表示「如發號施令般發言或書寫」。舉幾個例子：&lt;br /&gt;
&lt;!--Imperative mood just means &quot;spoken or written as if giving a command or instruction&quot;. A few examples:--&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Clean your room&lt;/li&gt;
  &lt;li&gt;Close the door&lt;/li&gt;
  &lt;li&gt;Take out the trash&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;你現在讀到的七條規則的每一條都是以祈使語氣書寫（「正文在72個字符處換行」，等等）。&lt;br /&gt;
&lt;!--Each of the seven rules you're reading about right now are written in the imperative (&quot;Wrap the body at 72 characters&quot;, etc).--&gt;&lt;/p&gt;

&lt;p&gt;祈使語氣聽起來稍顯失禮；因而我們並不常用。但它卻很適合於Git提交的主題行。原因之一、&lt;b&gt;Git本身在代替你創建提交時使用了祈使語氣。&lt;/b&gt;&lt;br /&gt;
&lt;!--The imperative can sound a little rude; that's why we don't often use it. But it's perfect for git commit subject lines. One reason for this is that git itself uses the imperative whenever it creates a commit on your behalf.--&gt;&lt;/p&gt;

&lt;p&gt;例如，當使用&lt;code class=&quot;highlighter-rouge&quot;&gt;git merge&lt;/code&gt;時默認創建的訊息讀起來就像這樣：&lt;br /&gt;
&lt;!--For example, the default message created when using git merge reads:--&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;Merge branch 'myfeature'&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;而使用&lt;code class=&quot;highlighter-rouge&quot;&gt;git revert&lt;/code&gt;時：&lt;br /&gt;
&lt;!--And when using git revert:--&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;Revert &quot;Add the thing with the stuff&quot;

This reverts commit cc87791524aedd593cff5a74532befe7ab69ce9d.&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;或者點擊GitHub拉取請求上的「合併」按鈕時：&lt;br /&gt;
&lt;!--Or when clicking the &quot;Merge&quot; button on a GitHub pull request:--&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;Merge pull request #123 from someuser/somebranch&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;所以，當你使用祈使語氣撰寫提交訊息時，你在遵循git本身內置的慣例。例如：&lt;br /&gt;
&lt;!--So when you write your commit messages in the imperative, you're following git's own built-in conventions. For example:--&gt;&lt;/p&gt;

&lt;ul&gt;&lt;font color=&quot;green&quot;&gt;
  &lt;li&gt;Refactor subsystem X for readability&lt;/li&gt;
  &lt;li&gt;Update getting started documentation&lt;/li&gt;
  &lt;li&gt;Remove deprecated methods&lt;/li&gt;
  &lt;li&gt;Release version 1.0.0&lt;/li&gt;
&lt;/font&gt;&lt;/ul&gt;

&lt;p&gt;起初這麼撰寫可能有點彆扭。我們更習慣於以陳述語氣發言、如同報告事實一般。這就是為什麼提交訊息最終讀起來經常像這樣：&lt;br /&gt;
&lt;!--Writing this way can be a little awkward at first. We're more used to speaking in the indicative mood, which is all about reporting facts. That's why commit messages often end up reading like this:--&gt;&lt;/p&gt;

&lt;ul&gt;&lt;font color=&quot;red&quot;&gt;
  &lt;li&gt;Fixed bug with Y&lt;/li&gt;
  &lt;li&gt;Changing behavior of X&lt;/li&gt;
&lt;/font&gt;&lt;/ul&gt;

&lt;p&gt;並且有時、提交訊息被撰寫成了內容的描述：&lt;br /&gt;
&lt;!--And sometimes commit messages get written as a description of their contents:--&gt;&lt;/p&gt;

&lt;ul&gt;&lt;font color=&quot;red&quot;&gt;
  &lt;li&gt;More fixes for broken stuff&lt;/li&gt;
  &lt;li&gt;Sweet new API methods&lt;/li&gt;
&lt;/font&gt;&lt;/ul&gt;

&lt;p&gt;為了消除困惑，這裡有一條簡單的屢試不爽的規則。&lt;br /&gt;
&lt;!--To remove any confusion, here's a simple rule to get it right every time.--&gt;&lt;/p&gt;

&lt;p&gt;&lt;b&gt;格式正確的git提交的主題行應該總是能夠完成下面的句子：&lt;/b&gt;&lt;br /&gt;
&lt;!--A properly formed git commit subject line should always be able to complete the following sentence:--&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;If applied, this commit will &lt;b&gt;&lt;u&gt;你的主題行在這&lt;/u&gt;&lt;/b&gt;&lt;br /&gt;
&lt;!--If applied, this commit will your subject line here--&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;例如：&lt;br /&gt;
&lt;!--For example:--&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;If applied, this commit will &lt;b&gt;&lt;i&gt;&lt;font color=&quot;green&quot;&gt;refactor subsystem X for readability&lt;/font&gt;&lt;/i&gt;&lt;/b&gt;&lt;/li&gt;
  &lt;li&gt;If applied, this commit will &lt;b&gt;&lt;i&gt;&lt;font color=&quot;green&quot;&gt;update getting started documentation&lt;/font&gt;&lt;/i&gt;&lt;/b&gt;&lt;/li&gt;
  &lt;li&gt;If applied, this commit will &lt;b&gt;&lt;i&gt;&lt;font color=&quot;green&quot;&gt;remove deprecated methods&lt;/font&gt;&lt;/i&gt;&lt;/b&gt;&lt;/li&gt;
  &lt;li&gt;If applied, this commit will &lt;b&gt;&lt;i&gt;&lt;font color=&quot;green&quot;&gt;release version 1.0.0&lt;/font&gt;&lt;/i&gt;&lt;/b&gt;&lt;/li&gt;
  &lt;li&gt;If applied, this commit will &lt;b&gt;&lt;i&gt;&lt;font color=&quot;green&quot;&gt;merge pull request #123 from user/branch&lt;/font&gt;&lt;/i&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;請注意，這對於其他非祈使語氣的格式是行不通的：&lt;br /&gt;
&lt;!--Notice how this doesn't work for the other non-imperative forms:--&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;If applied, this commit will &lt;b&gt;&lt;i&gt;&lt;font color=&quot;red&quot;&gt;fixed bug with Y&lt;/font&gt;&lt;/i&gt;&lt;/b&gt;&lt;/li&gt;
  &lt;li&gt;If applied, this commit will &lt;b&gt;&lt;i&gt;&lt;font color=&quot;red&quot;&gt;changing behavior of X&lt;/font&gt;&lt;/i&gt;&lt;/b&gt;&lt;/li&gt;
  &lt;li&gt;If applied, this commit will &lt;b&gt;&lt;i&gt;&lt;font color=&quot;red&quot;&gt;more fixes for broken stuff&lt;/font&gt;&lt;/i&gt;&lt;/b&gt;&lt;/li&gt;
  &lt;li&gt;If applied, this commit will &lt;b&gt;&lt;i&gt;&lt;font color=&quot;red&quot;&gt;sweet new API methods&lt;/font&gt;&lt;/i&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;謹記：使用祈使語氣很重要這一點、僅限於主題行。當你撰寫正文時，可以放寬這個限制。&lt;br /&gt;
&lt;!--Remember: Use of the imperative is important only in the subject line. You can relax this restriction when you're writing the body.--&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;6正文在72個字符處換行&quot;&gt;&lt;a name=&quot;wrap-72&quot;&gt;&lt;/a&gt;6.正文在72個字符處換行&lt;/h3&gt;
&lt;!--6. Wrap the body at 72 characters--&gt;

&lt;p&gt;Git從不會自動換行。當你撰寫提交訊息正文時，你得留意右邊距，並手動換行。&lt;br /&gt;
&lt;!--Git never wraps text automatically. When you write the body of a commit message, you must mind its right margin, and wrap text manually.--&gt;&lt;/p&gt;

&lt;p&gt;推薦在72個字符處換行，這樣git在保持長度在80個字符以內的同時有足夠的空間來縮進文本。&lt;br /&gt;
&lt;!--The recommendation is to do this at 72 characters, so that git has plenty of room to indent text while still keeping everything under 80 characters overall.--&gt;&lt;/p&gt;

&lt;p&gt;這裡一個好的文本編輯器可以有所幫助。例如，當你撰寫git提交時，配置Vim在72個字符處換行是很容易的。不過，通常IDE們對於提交訊息的文本換行所提供的智能支持都很糟糕（儘管在最近的版本中，IntelliJ IDEA  &lt;a href=&quot;http://youtrack.jetbrains.com/issue/IDEA-53615&quot;&gt;終於&lt;/a&gt;  &lt;a href=&quot;http://youtrack.jetbrains.com/issue/IDEA-53615#comment=27-448299&quot;&gt;變得&lt;/a&gt;  &lt;a href=&quot;http://youtrack.jetbrains.com/issue/IDEA-53615#comment=27-446912&quot;&gt;好多&lt;/a&gt;  了）。&lt;br /&gt;
&lt;!--A good text editor can help here. It's easy to configure Vim, for example, to wrap text at 72 characters when you're writing a git commit. Traditionally, however, IDEs have been terrible at providing smart support for text wrapping in commit messages (although in recent versions, IntelliJ IDEA has finally gotten better about this).--&gt;&lt;/p&gt;

&lt;h3 id=&quot;7用正文來解釋是什麼以及為什麼而不是怎麼樣&quot;&gt;&lt;a name=&quot;why-not-how&quot;&gt;&lt;/a&gt;7.用正文來解釋是什麼以及為什麼，而不是怎麼樣&lt;/h3&gt;
&lt;!--7. Use the body to explain what and why vs. how--&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/bitcoin/bitcoin/commit/eb0b56b19017ab5c16c745e6da39c53126924ed6&quot;&gt;來自Bitcoin Core的提交&lt;/a&gt;是一個解釋改動內容和原因的很好的例子：&lt;br /&gt;
&lt;!--This commit from Bitcoin Core is a great example of explaining what changed and why:--&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;commit eb0b56b19017ab5c16c745e6da39c53126924ed6
Author: Pieter Wuille &amp;lt;pieter.wuille@gmail.com&amp;gt;
Date:   Fri Aug 1 22:57:55 2014 +0200

   Simplify serialize.h's exception handling

   Remove the 'state' and 'exceptmask' from serialize.h's stream
   implementations, as well as related methods.

   As exceptmask always included 'failbit', and setstate was always
   called with bits = failbit, all it did was immediately raise an
   exception. Get rid of those variables, and replace the setstate
   with direct exception throwing (which also removes some dead
   code).

   As a result, good() is never reached after a failure (there are
   only 2 calls, one of which is in tests), and can just be replaced
   by !eof().

   fail(), clear(n) and exceptions() are just never called. Delete
   them.&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;看看&lt;a href=&quot;https://github.com/bitcoin/bitcoin/commit/eb0b56b19017ab5c16c745e6da39c53126924ed6&quot;&gt;完整的diff&lt;/a&gt;，並想想作者當時花時間去提供的這個上下文節約了同事和未來的提交者多少時間。如果他沒有這麼做，那麼它可能永遠丟失了。&lt;br /&gt;
&lt;!--Take a look at the full diff and just think how much time the author is saving fellow and future committers by taking the time to provide this context here and now. If he didn't, it would probably be lost forever.--&gt;&lt;/p&gt;

&lt;p&gt;在大多數情況下，你可以略去改動的具體細節。在這方面程式碼一般都是自解釋的（如果程式碼複雜到需要額外的解釋，那就是源碼註釋的工作了）。僅專注於把改動原因言明擺在首位——改動前它們如何工作（那樣有什麼問題），現在如何工作，以及為什麼你決定以這種方式解決問題。&lt;br /&gt;
&lt;!--In most cases, you can leave out details about how a change has been made. Code is generally self-explanatory in this regard (and if the code is so complex that it needs to be explained in prose, that's what source comments are for). Just focus on making clear the reasons you made the change in the first place—the way things worked before the change (and what was wrong with that), the way they work now, and why you decided to solve it the way you did.--&gt;&lt;/p&gt;

&lt;p&gt;在未來感謝你的維護者也許就是你自己！&lt;br /&gt;
&lt;!--The future maintainer that thanks you may be yourself!--&gt;&lt;/p&gt;

&lt;h1 id=&quot;建議&quot;&gt;&lt;a name=&quot;tips&quot;&gt;&lt;/a&gt;建議&lt;/h1&gt;
&lt;!--Tips--&gt;

&lt;h3 id=&quot;學著去熱愛命令列把ide丟在一邊吧&quot;&gt;學著去熱愛命令列。把IDE丟在一邊吧。&lt;/h3&gt;
&lt;!--Learn to love the command line. Leave the IDE behind.--&gt;

&lt;p&gt;明智的做法是去擁抱命令行，理由如git子指令那樣多。Git極其強大；IDE們同樣也是，但卻各不相同。我每天都在用IDE (IntelliJ IDEA)、其它用的也比較多 (Eclipse)，但我從未見過有整合git的IDE能夠匹敵命令列的易用與強大（一旦你了解它）。&lt;br /&gt;
&lt;!--For as many reasons as there are git subcommands, it's wise to embrace the command line. Git is insanely powerful; IDEs are too, but each in different ways. I use an IDE every day (IntelliJ IDEA) and have used others extensively (Eclipse), but I have never seen IDE integration for git that could begin to match the ease and power of the command line (once you know it).--&gt;&lt;/p&gt;

&lt;p&gt;某些git相關的IDE功能是極好的，比如呼叫&lt;code class=&quot;highlighter-rouge&quot;&gt;git rm&lt;/code&gt;刪除一個檔案，以及用&lt;code class=&quot;highlighter-rouge&quot;&gt;git&lt;/code&gt;重命名檔案。當你開始嘗試通過IDE去提交、合併、重訂、或做複雜的歷史分析時，一切都將分崩離析。&lt;br /&gt;
&lt;!--Certain git-related IDE functions are invaluable, like calling git rm when you delete a file, and doing the right stuff with git when you rename one. Where everything falls apart is when you start trying to commit, merge, rebase, or do sophisticated history analysis through the IDE.--&gt;&lt;/p&gt;

&lt;p&gt;當談到充分發揮git的強大功能時，那就非命令列莫屬了。
&lt;!--When it comes to wielding the full power of git, it's command-line all the way.--&gt;&lt;/p&gt;

&lt;p&gt;謹記，不管你用Bash或Z Shell，都有&lt;a href=&quot;http://git-scm.com/book/en/Git-Basics-Tips-and-Tricks&quot;&gt;tab補齊腳本&lt;/a&gt;來減輕記住子指令和開關的痛苦。
&lt;!--Remember that whether you use Bash or Z shell, there are tab completion scripts that take much of the pain out of remembering the subcommands and switches.--&gt;&lt;/p&gt;

&lt;h3 id=&quot;閱讀pro-git&quot;&gt;閱讀Pro Git&lt;/h3&gt;
&lt;!--Read Pro Git--&gt;

&lt;p&gt;&lt;a href=&quot;http://git-scm.com/book&quot;&gt;Pro Git&lt;/a&gt;這本書是網上免費提供的，寫的也非常棒。好好使用吧！&lt;br /&gt;
&lt;!--The Pro Git book is available online for free, and it's fantastic. Take advantage!--&gt;&lt;/p&gt;</content><author><name>Kenmux Lee</name></author><category term="Git" /><category term="Commit Message" /><summary type="html">這篇筆記、翻譯自博文：How to Write a Git Commit Message，作者Chris Beams。尊重他人勞動果實，轉載請註明！ 引言 七條規則 建議</summary></entry><entry><title type="html">Markdown/HTML常用語法小結</title><link href="https://xiwan.io/archive/markdown-html-common-syntax-summary.html" rel="alternate" type="text/html" title="Markdown/HTML常用語法小結" /><published>2015-09-13T10:00:00+00:00</published><updated>2015-09-13T10:00:00+00:00</updated><id>https://xiwan.io/archive/markdown-html-common-syntax-summary</id><content type="html" xml:base="https://xiwan.io/archive/markdown-html-common-syntax-summary.html">&lt;p&gt;Markdown語法請參考&lt;a href=&quot;http://markdown.tw/&quot;&gt;這裡&lt;/a&gt;，HTML語法請參考&lt;a href=&quot;http://www.powmo.com/&quot;&gt;這裡&lt;/a&gt;。&lt;br /&gt;
以下是類比HTML語法做的小結，只涵蓋了最基本最通用的語法。&lt;/p&gt;

&lt;!-- more --&gt;

&lt;table border=&quot;1&quot; width=&quot;100%&quot; height=&quot;100%&quot; style=&quot;table-layout:fixed;word-wrap:break-word;word-break;break-all;&quot;&gt;
  &lt;colgroup&gt;
    &lt;col span=&quot;1&quot; style=&quot;width:20%;&quot; /&gt;
    &lt;col span=&quot;1&quot; style=&quot;width:80%;&quot; /&gt;
  &lt;/colgroup&gt;
  &lt;tr&gt;
    &lt;th&gt;
      顯示
    &lt;/th&gt;
    &lt;th&gt;
      語法
    &lt;/th&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td align=&quot;center&quot; rowspan=&quot;2&quot;&gt;
      斷行
    &lt;/td&gt;
    &lt;td&gt;
      在行尾加上兩個以上的&lt;span style=&quot;background-color:lightgrey&quot;&gt;空格&lt;/span&gt;然後按&lt;span style=&quot;background-color:lightgrey&quot;&gt;回車&lt;/span&gt;&lt;br /&gt;
      &lt;ul class=&quot;noindent&quot; type=&quot;circle&quot;&gt;
        &lt;li&gt;Markdown預設&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;br&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
      &lt;ul class=&quot;noindent&quot; type=&quot;circle&quot;&gt;
        &lt;li&gt;或可寫作&lt;span style=&quot;background-color:lightgrey&quot;&gt;&amp;lt;br&amp;gt;&lt;/span&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td rowspan=&quot;2&quot;&gt;
      &lt;h1&gt;標題一&lt;/h1&gt;
      &lt;h2&gt;標題二&lt;/h2&gt;
      &lt;h3&gt;標題三&lt;/h3&gt;
      &lt;h4&gt;標題四&lt;/h4&gt;
      &lt;h5&gt;標題五&lt;/h5&gt;
      &lt;h6&gt;標題六&lt;/h6&gt;
    &lt;/td&gt;
    &lt;td&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-md&quot; data-lang=&quot;md&quot;&gt;&lt;span class=&quot;p&quot;&gt;# 標題一&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;## 標題二&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;### 標題三&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;#### 標題四&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;##### 標題五&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;###### 標題六&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
      &lt;ul class=&quot;noindent&quot; type=&quot;circle&quot;&gt;
        &lt;li&gt;&lt;span style=&quot;background-color:lightgrey&quot;&gt;#&lt;/span&gt;與標題文字間須有&lt;span style=&quot;background-color:lightgrey&quot;&gt;空格&lt;/span&gt;&lt;/li&gt;
        &lt;li&gt;標題前後須有空行&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;標題一&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;標題二&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;標題三&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;h4&amp;gt;&lt;/span&gt;標題四&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h4&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;h5&amp;gt;&lt;/span&gt;標題五&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h5&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;h6&amp;gt;&lt;/span&gt;標題六&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h6&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td align=&quot;center&quot; rowspan=&quot;2&quot;&gt;
      &lt;hr /&gt;
    &lt;/td&gt;
    &lt;td&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-md&quot; data-lang=&quot;md&quot;&gt;&lt;span class=&quot;p&quot;&gt;---&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
      &lt;ul class=&quot;noindent&quot; type=&quot;circle&quot;&gt;
        &lt;li&gt;前後須有空行&lt;/li&gt;
        &lt;li&gt;或可寫作&lt;span style=&quot;background-color:lightgrey&quot;&gt;***&lt;/span&gt;&lt;/li&gt;        
      &lt;/ul&gt;
    &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;hr&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
      &lt;ul class=&quot;noindent&quot; type=&quot;circle&quot;&gt;
      &lt;li&gt;或可寫作&lt;span style=&quot;background-color:lightgrey&quot;&gt;&amp;lt;hr&amp;gt;&lt;/span&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td align=&quot;center&quot; rowspan=&quot;2&quot;&gt;
      &lt;blockquote&gt;引言&lt;/blockquote&gt;
    &lt;/td&gt;
    &lt;td&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-md&quot; data-lang=&quot;md&quot;&gt;&lt;span class=&quot;gt&quot;&gt;&amp;gt; 引言  &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
      &lt;ul class=&quot;noindent&quot; type=&quot;circle&quot;&gt;
        &lt;li&gt;引言區塊前後須有空行&lt;/li&gt;
        &lt;li&gt;行尾須有兩個以上的&lt;span style=&quot;background-color:lightgrey&quot;&gt;空格&lt;/span&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;blockquote&amp;gt;&lt;/span&gt;引言&lt;span class=&quot;nt&quot;&gt;&amp;lt;/blockquote&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td align=&quot;center&quot; rowspan=&quot;3&quot;&gt;
      &lt;b&gt;粗體&lt;/b&gt;
    &lt;/td&gt;
    &lt;td&gt;
      &lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-md&quot; data-lang=&quot;md&quot;&gt;&lt;span class=&quot;gs&quot;&gt;**粗體**&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;b&amp;gt;&lt;/span&gt;粗體&lt;span class=&quot;nt&quot;&gt;&amp;lt;/b&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
      &lt;ul class=&quot;noindent&quot; type=&quot;circle&quot;&gt;
        &lt;li&gt;STRONG標籤效果類同&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;span&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;style=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;font-weight:bold;&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;粗體&lt;span class=&quot;nt&quot;&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td align=&quot;center&quot;&gt;
      &lt;i&gt;italics&lt;/i&gt;
    &lt;/td&gt;
    &lt;td&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;i&amp;gt;&lt;/span&gt;italics&lt;span class=&quot;nt&quot;&gt;&amp;lt;/i&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
      &lt;ul class=&quot;noindent&quot; type=&quot;circle&quot;&gt;
      &lt;li&gt;傳統上漢字並沒有斜體、故而不建議對其使用&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td align=&quot;center&quot; rowspan=&quot;2&quot;&gt;
      &lt;font color=&quot;blue&quot;&gt;藍色&lt;/font&gt;
    &lt;/td&gt;
    &lt;td&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;font&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;color=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;blue&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;藍色&lt;span class=&quot;nt&quot;&gt;&amp;lt;/font&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;span&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;style=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;color:blue;&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;藍色&lt;span class=&quot;nt&quot;&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td align=&quot;center&quot;&gt;
      &lt;font style=&quot;text-decoration:overline&quot;&gt;上劃線&lt;/font&gt;
    &lt;/td&gt;
    &lt;td&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;font&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;style=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text-decoration:overline&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;上劃線&lt;span class=&quot;nt&quot;&gt;&amp;lt;/font&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td align=&quot;center&quot;&gt;
      &lt;u&gt;下劃線&lt;/u&gt;
    &lt;/td&gt;
    &lt;td&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;u&amp;gt;&lt;/span&gt;下劃線&lt;span class=&quot;nt&quot;&gt;&amp;lt;/u&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
      &lt;ul class=&quot;noindent&quot; type=&quot;circle&quot;&gt;
        &lt;li&gt;不要採用在文字兩端加&lt;span style=&quot;background-color:lightgrey&quot;&gt;_&lt;/span&gt;的方式&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td align=&quot;center&quot;&gt;
      &lt;del&gt;刪除線&lt;/del&gt;
    &lt;/td&gt;
    &lt;td&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;del&amp;gt;&lt;/span&gt;刪除線&lt;span class=&quot;nt&quot;&gt;&amp;lt;/del&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
      &lt;ul class=&quot;noindent&quot; type=&quot;circle&quot;&gt;
        &lt;li&gt;不要採用在文字兩端加&lt;span style=&quot;background-color:lightgrey&quot;&gt;~~&lt;/span&gt;的方式&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td align=&quot;center&quot;&gt;
      文字&lt;sub&gt;下標&lt;/sub&gt;
    &lt;/td&gt;
    &lt;td&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;文字&lt;span class=&quot;nt&quot;&gt;&amp;lt;sub&amp;gt;&lt;/span&gt;下標&lt;span class=&quot;nt&quot;&gt;&amp;lt;/sub&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td align=&quot;center&quot;&gt;
      文字&lt;sup&gt;上標&lt;/sup&gt;
    &lt;/td&gt;
    &lt;td&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;文字&lt;span class=&quot;nt&quot;&gt;&amp;lt;sup&amp;gt;&lt;/span&gt;上標&lt;span class=&quot;nt&quot;&gt;&amp;lt;/sup&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td align=&quot;center&quot;&gt;
      &lt;ruby&gt;
      注&lt;rp&gt;(&lt;/rp&gt;&lt;rt&gt;ㄓㄨˋ&lt;/rt&gt;&lt;rp&gt;)&lt;/rp&gt;
      音&lt;rp&gt;(&lt;/rp&gt;&lt;rt&gt;ㄧㄣˉ&lt;/rt&gt;&lt;rp&gt;)&lt;/rp&gt;
      &lt;/ruby&gt;
    &lt;/td&gt;
    &lt;td&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;ruby&amp;gt;&lt;/span&gt;
注&lt;span class=&quot;nt&quot;&gt;&amp;lt;rp&amp;gt;&lt;/span&gt;(&lt;span class=&quot;nt&quot;&gt;&amp;lt;/rp&amp;gt;&amp;lt;rt&amp;gt;&lt;/span&gt;ㄓㄨˋ&lt;span class=&quot;nt&quot;&gt;&amp;lt;/rt&amp;gt;&amp;lt;rp&amp;gt;&lt;/span&gt;)&lt;span class=&quot;nt&quot;&gt;&amp;lt;/rp&amp;gt;&lt;/span&gt;
音&lt;span class=&quot;nt&quot;&gt;&amp;lt;rp&amp;gt;&lt;/span&gt;(&lt;span class=&quot;nt&quot;&gt;&amp;lt;/rp&amp;gt;&amp;lt;rt&amp;gt;&lt;/span&gt;ㄧㄣˉ&lt;span class=&quot;nt&quot;&gt;&amp;lt;/rt&amp;gt;&amp;lt;rp&amp;gt;&lt;/span&gt;)&lt;span class=&quot;nt&quot;&gt;&amp;lt;/rp&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/ruby&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
      &lt;ul class=&quot;noindent&quot; type=&quot;circle&quot;&gt;
        &lt;li&gt;RP標籤或可省略&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td align=&quot;center&quot; rowspan=&quot;2&quot;&gt;
      &lt;ul class=&quot;noindent&quot;&gt;
        &lt;li&gt;週一&lt;/li&gt;
        &lt;li&gt;週二&lt;/li&gt;
        &lt;li&gt;週三&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/td&gt;
    &lt;td&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-md&quot; data-lang=&quot;md&quot;&gt;&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; 週一  
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; 週二  
&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; 週三  &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
      &lt;ul class=&quot;noindent&quot; type=&quot;circle&quot;&gt;
        &lt;li&gt;&lt;span style=&quot;background-color:lightgrey&quot;&gt;-&lt;/span&gt;與項目間須有&lt;span style=&quot;background-color:lightgrey&quot;&gt;空格&lt;/span&gt;&lt;/li&gt;
        &lt;li&gt;項目行尾須有兩個以上的&lt;span style=&quot;background-color:lightgrey&quot;&gt;空格&lt;/span&gt;&lt;/li&gt;
        &lt;li&gt;項目清單前後須有空行&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;li&amp;gt;&lt;/span&gt;週一&lt;span class=&quot;nt&quot;&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;li&amp;gt;&lt;/span&gt;週二&lt;span class=&quot;nt&quot;&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;li&amp;gt;&lt;/span&gt;週三&lt;span class=&quot;nt&quot;&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
      &lt;ul class=&quot;noindent&quot; type=&quot;circle&quot;&gt;
        &lt;li&gt;項目符號的種類由type屬性指定：&lt;/li&gt;
          &lt;ul type=&quot;square&quot;&gt;
            &lt;li&gt;disc、實心圓形、預設&lt;/li&gt;
            &lt;li&gt;circle、空心圓形&lt;/li&gt;
            &lt;li&gt;square、實心方形&lt;/li&gt;
          &lt;/ul&gt;
      &lt;/ul&gt;
    &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td align=&quot;center&quot; rowspan=&quot;2&quot;&gt;
      &lt;ol class=&quot;noindent&quot; start=&quot;1&quot;&gt;
        &lt;li&gt;蘋果&lt;/li&gt;
        &lt;li&gt;香蕉&lt;/li&gt;
        &lt;li&gt;葡萄&lt;/li&gt;
      &lt;/ol&gt;
    &lt;/td&gt;
    &lt;td&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-md&quot; data-lang=&quot;md&quot;&gt;&lt;span class=&quot;p&quot;&gt;1.&lt;/span&gt; 蘋果  
&lt;span class=&quot;p&quot;&gt;2.&lt;/span&gt; 香蕉  
&lt;span class=&quot;p&quot;&gt;3.&lt;/span&gt; 葡萄  &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
      &lt;ul class=&quot;noindent&quot; type=&quot;circle&quot;&gt;
        &lt;li&gt;編號不可為0&lt;/li&gt;
        &lt;li&gt;編號與項目間須有&lt;span style=&quot;background-color:lightgrey&quot;&gt;空格&lt;/span&gt;&lt;/li&gt;
        &lt;li&gt;項目行尾須有兩個以上的&lt;span style=&quot;background-color:lightgrey&quot;&gt;空格&lt;/span&gt;&lt;/li&gt;
        &lt;li&gt;項目清單前後須有空行&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;ol&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;start=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;li&amp;gt;&lt;/span&gt;蘋果&lt;span class=&quot;nt&quot;&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;li&amp;gt;&lt;/span&gt;香蕉&lt;span class=&quot;nt&quot;&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;li&amp;gt;&lt;/span&gt;葡萄&lt;span class=&quot;nt&quot;&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/ol&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
      &lt;ul class=&quot;noindent&quot; type=&quot;circle&quot;&gt;
        &lt;li&gt;項目編號由type與start屬性指定&lt;/li&gt;
        &lt;li&gt;start屬性指定起始編號、預設為1&lt;/li&gt;
        &lt;li&gt;type屬性可為：&lt;/li&gt;
          &lt;ul type=&quot;square&quot;&gt;
            &lt;li&gt;1、阿拉伯數字、預設&lt;/li&gt;
            &lt;li&gt;a、小寫英文字母&lt;/li&gt;
            &lt;li&gt;A、大寫英文字母&lt;/li&gt;
            &lt;li&gt;i、小寫羅馬數字&lt;/li&gt;
            &lt;li&gt;I、大寫羅馬數字&lt;/li&gt;
          &lt;/ul&gt;
      &lt;/ul&gt;
    &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td rowspan=&quot;2&quot;&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;程式碼&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;/td&gt;
    &lt;td&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;&lt;span class=&quot;nt&quot;&gt;{&lt;/span&gt;% highlight text &lt;span class=&quot;nt&quot;&gt;%&lt;/span&gt;}
程式碼
&lt;span class=&quot;nt&quot;&gt;{&lt;/span&gt;% endhighlight &lt;span class=&quot;nt&quot;&gt;%&lt;/span&gt;}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
      &lt;ul class=&quot;noindent&quot; type=&quot;circle&quot;&gt;
        &lt;li&gt;highlight後隨字串指定程式碼所採編程語言&lt;/li&gt;
        &lt;li&gt;程式碼所採編程語言或可略去不寫&lt;/li&gt;
        &lt;li&gt;rouge所支援編程語言清單參考&lt;a href=&quot;https://github.com/jneen/rouge/wiki/List-of-supported-languages-and-lexers&quot;&gt;這裡&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;```text
程式碼
```&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
      &lt;ul class=&quot;noindent&quot; type=&quot;circle&quot;&gt;
        &lt;li&gt;&lt;span style=&quot;background-color:lightgrey&quot;&gt;```&lt;/span&gt;後隨字串指定程式碼所採編程語言&lt;/li&gt;
        &lt;li&gt;不太推薦採用這種風格&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td align=&quot;center&quot;&gt;
      空格
    &lt;/td&gt;
    &lt;td&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;amp;nbsp;&lt;/span&gt; : 空 格(x1)&lt;br /&gt;&lt;span class=&quot;nt&quot;&gt;&amp;amp;ensp;&lt;/span&gt; : 空  格(x2)&lt;br /&gt;&lt;span class=&quot;nt&quot;&gt;&amp;amp;emsp;&lt;/span&gt; : 空    格(x4)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
      &lt;ul class=&quot;noindent&quot; type=&quot;circle&quot;&gt;
        &lt;li&gt;當需要向MD檔插入複數空格時格外有用&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td align=&quot;center&quot; rowspan=&quot;2&quot;&gt;
      &lt;img src=&quot;/assets/icons/favicon-32x32.png&quot; alt=&quot;favicon-32x32.png&quot; /&gt;
    &lt;/td&gt;
    &lt;td&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-md&quot; data-lang=&quot;md&quot;&gt;&lt;span class=&quot;p&quot;&gt;![&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;favicon-32x32.png&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;/assets/icons/favicon-32x32.png&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;img&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/assets/icons/favicon-32x32.png&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;alt=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;favicon-32x32.png&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td align=&quot;center&quot; rowspan=&quot;2&quot;&gt;
      &lt;a href=&quot;https://xiwan.info&quot;&gt;西灣筆記&lt;/a&gt;
    &lt;/td&gt;
    &lt;td&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-md&quot; data-lang=&quot;md&quot;&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;西灣筆記&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;https://xiwan.io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;
&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://xiwan.io&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;西灣筆記&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;/td&gt;
  &lt;/tr&gt;  
&lt;/table&gt;</content><author><name>Kenmux Lee</name></author><category term="Markdown" /><category term="HTML" /><category term="Syntax" /><summary type="html">Markdown語法請參考這裡，HTML語法請參考這裡。 以下是類比HTML語法做的小結，只涵蓋了最基本最通用的語法。</summary></entry><entry><title type="html">字串切割：strtok、strtok_r與strsep</title><link href="https://xiwan.io/archive/string-split-strtok-strtok-r-strsep.html" rel="alternate" type="text/html" title="字串切割：strtok、strtok_r與strsep" /><published>2015-09-06T10:00:00+00:00</published><updated>2015-09-06T10:00:00+00:00</updated><id>https://xiwan.io/archive/string-split-strtok-strtok-r-strsep</id><content type="html" xml:base="https://xiwan.io/archive/string-split-strtok-strtok-r-strsep.html">&lt;p&gt;對於字串切割、C標準函式庫提供了這幾個函式：strtok，strtok_r，strsep；使用時、只需要包含表頭檔string.h即可。詳細說明，參考連結：&lt;a href=&quot;http://man7.org/linux/man-pages/man3/strtok.3.html&quot;&gt;strtok(3)&lt;/a&gt;，&lt;a href=&quot;http://man7.org/linux/man-pages/man3/strsep.3.html&quot;&gt;strsep(3)&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;由於C語言本身的限制以及在執行效率/資源佔有方面的考量與妥協、這些函式顯得並不那麼好用，甚而讓人不禁懷疑「誒？這真的是標準庫提供的函式麼？」。這篇筆記用來記述它們的使用方法、以及一些注意點。&lt;/p&gt;

&lt;p&gt;在分述它們的不同之處之前、先說說它們的共同之處吧：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;其一、呼叫函式會修改原字串內容（將切割符改為&lt;code class=&quot;highlighter-rouge&quot;&gt;'\0'&lt;/code&gt;），故待切割字串不可為無法修改的字串字面常數(string literal)。&lt;/li&gt;
  &lt;li&gt;其二、單次呼叫只能完成一次切割、而不是直接提供切割完畢的字串數組。&lt;/li&gt;
  &lt;li&gt;其三、關於切割符字串（更確切的說、切割符集合），其內任一字符（而不是整體）都是切割符，而且在每次呼叫函式時都可以依據需求採用相同/不同的字串。&lt;!-- more --&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;strtok&quot;&gt;strtok&lt;/h1&gt;

&lt;p&gt;函式原型：char *strtok(char *str, const char *delim);&lt;/p&gt;

&lt;p&gt;三個函式中，它的「資歷」最老、兼容性也是最好的。由於只修改字串內容、故待切割字串也可存放在字符數組中。但因其內置了用於指示下次掃描位置的指標，故不能用於多執行緒(multi-thread)情景中。在切割字串的過程中，參數str在呼叫一次之後必須設為空(NULL)。&lt;/p&gt;

&lt;p&gt;在下面的範例中，將待分割字串存放在字符數組中：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span class=&quot;cp&quot;&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;argc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;MAC-:00-1C-42-A7-F1-D9&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delim&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;-:&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;substr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;strcpy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;original string: %s (@%p)&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;substr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strtok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#%d sub string: %s (@%p)&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;substr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;substr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;substr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strtok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;substr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;original string after 'strtok': &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strlen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;%c&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?:&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;'*'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot; (@%p)&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;輸出結果為：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;original string: MAC-:00-1C-42-A7-F1-D9 (@0x7fff5fbff7e0)  
#0 sub string: MAC (@0x7fff5fbff7e0)  
#1 sub string: 00 (@0x7fff5fbff7e5)  
#2 sub string: 1C (@0x7fff5fbff7e8)  
#3 sub string: 42 (@0x7fff5fbff7eb)  
#4 sub string: A7 (@0x7fff5fbff7ee)  
#5 sub string: F1 (@0x7fff5fbff7f1)  
#6 sub string: D9 (@0x7fff5fbff7f4)  
original string after 'strtok': MAC*:00*1C*42*A7*F1*D9 (@0x7fff5fbff7e0)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;注：最後一列、被&lt;code class=&quot;highlighter-rouge&quot;&gt;'\0'&lt;/code&gt;替換的字符已用&lt;code class=&quot;highlighter-rouge&quot;&gt;'*'&lt;/code&gt;打印出來。下同、不再贅述！&lt;/p&gt;

&lt;p&gt;由此可以看出，每次呼叫strtok、它都會從掃描位置開始掃描字串、將其後第一個出現的切割符改為&lt;code class=&quot;highlighter-rouge&quot;&gt;'\0'&lt;/code&gt;、然後修改掃描位置、最後將原掃描位置返回。有趣的是、當切割符連續出現時、strtok並不會返回空字串（即&lt;code class=&quot;highlighter-rouge&quot;&gt;&quot;&quot;&lt;/code&gt;）、而是選擇跳過。&lt;/p&gt;

&lt;p&gt;在上一範例中，第8列引入了固定長度的字符數組，在不確定待切割字串長度的情景中，容易引發緩衝區溢位(buffer overflow)，故不推薦，可參考下面的範例：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span class=&quot;cp&quot;&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;argc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;MAC-:00-1C-42-A7-F1-D9&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delim&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;-:&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dupstr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strdup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;substr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;original string: %s (@%p)&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dupstr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dupstr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;substr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strtok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dupstr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#%d sub string: %s (@%p)&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;substr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;substr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;substr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strtok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;substr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;original string after 'strtok': &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strlen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;%c&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dupstr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?:&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;'*'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot; (@%p)&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dupstr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;free&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dupstr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;輸出結果為：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;original string: MAC-:00-1C-42-A7-F1-D9 (@0x1002068d0)  
#0 sub string: MAC (@0x1002068d0)  
#1 sub string: 00 (@0x1002068d5)  
#2 sub string: 1C (@0x1002068d8)  
#3 sub string: 42 (@0x1002068db)  
#4 sub string: A7 (@0x1002068de)  
#5 sub string: F1 (@0x1002068e1)  
#6 sub string: D9 (@0x1002068e4)  
original string after 'strtok': MAC*:00*1C*42*A7*F1*D9 (@0x1002068d0)  &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1 id=&quot;strtok_r&quot;&gt;strtok_r&lt;/h1&gt;

&lt;p&gt;函式原型：char *strtok_r(char *str, const char *delim, char **saveptr);&lt;/p&gt;

&lt;p&gt;此為strtok的多執行緒安全的重入版本，新增參數saveptr，用於存放指示下次掃描位置的指標、其值不能為空；strtok_r在第一次呼叫時會忽略它的值（這意味著其初始值可為NULL）。與strtok一樣、待切割的字串亦可存放在字符數組中。&lt;/p&gt;

&lt;p&gt;參考範例如下：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span class=&quot;cp&quot;&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;argc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;MAC-:00-1C-42-A7-F1-D9&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delim&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;-:&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dupstr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strdup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;saveptr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;substr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;original string: %s (@%p)&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dupstr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dupstr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;substr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strtok_r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dupstr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;saveptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#%d sub string: %s (@%p)&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;substr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;substr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;substr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strtok_r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;saveptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;substr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;original string after 'strtok_r': &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strlen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;%c&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dupstr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?:&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;'*'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot; (@%p)&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dupstr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;free&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dupstr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;輸出結果為：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;original string: MAC-:00-1C-42-A7-F1-D9 (@0x1002068d0)  
#0 sub string: MAC (@0x1002068d0)  
#1 sub string: 00 (@0x1002068d5)  
#2 sub string: 1C (@0x1002068d8)  
#3 sub string: 42 (@0x1002068db)  
#4 sub string: A7 (@0x1002068de)  
#5 sub string: F1 (@0x1002068e1)  
#6 sub string: D9 (@0x1002068e4)  
original string after 'strtok_r': MAC*:00*1C*42*A7*F1*D9 (@0x1002068d0)  &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1 id=&quot;strsep&quot;&gt;strsep&lt;/h1&gt;

&lt;p&gt;函式原型：char *strsep(char **stringp, const char *delim);&lt;/p&gt;

&lt;p&gt;最後說說strsep。與strtok_r同樣、它可以用在多執行緒情景中；但是、與前者不同的是、它不單修改字串的內容，而且還修改了字串的位址指標。&lt;/p&gt;

&lt;p&gt;這就意味著：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;使用字符數組存放待分割字串是不允許的。&lt;/li&gt;
  &lt;li&gt;存放待分割字串的位址的常數指標是不允許的。&lt;/li&gt;
  &lt;li&gt;待分割字串的位址是需要使用額外的指標進行備份的。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;參考範例如下：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span class=&quot;cp&quot;&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;argc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;MAC-:00-1C-42-A7-F1-D9&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delim&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;-:&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dupstr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strdup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sepstr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dupstr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;substr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;original string: %s (@%p)&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sepstr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sepstr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;substr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strsep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sepstr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#%d sub string: %s (@%p)&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;substr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;substr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;substr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strsep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sepstr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;substr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;original string after 'strsep': &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strlen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;%c&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dupstr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?:&lt;/span&gt;&lt;span class=&quot;sc&quot;&gt;'*'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot; (@%p)&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sepstr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;free&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dupstr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;輸出結果為：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;original string: MAC-:00-1C-42-A7-F1-D9 (@0x1002068d0)  
#0 sub string: MAC (@0x1002068d0)  
#1 sub string:  (@0x1002068d4)  
#2 sub string: 00 (@0x1002068d5)  
#3 sub string: 1C (@0x1002068d8)  
#4 sub string: 42 (@0x1002068db)  
#5 sub string: A7 (@0x1002068de)  
#6 sub string: F1 (@0x1002068e1)  
#7 sub string: D9 (@0x1002068e4)  
original string after 'strsep': MAC**00*1C*42*A7*F1*D9 (@0x0)  &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;有一點須注意：與strtok/strtok_r不同、strsep會返回空字串。這在某些情景中相當有用。&lt;/p&gt;

&lt;h1 id=&quot;小結&quot;&gt;小結&lt;/h1&gt;

&lt;p&gt;關於它們的概覽、可以參考以下表格：&lt;/p&gt;

&lt;table border=&quot;1&quot; width=&quot;100%&quot; height=&quot;100%&quot;&gt;
&lt;tr&gt;
&lt;th width=&quot;5%&quot;&gt;函式&lt;/th&gt;
&lt;th width=&quot;20%&quot;&gt;屬性&lt;/th&gt;
&lt;th width=&quot;25%&quot;&gt;值&lt;/th&gt;
&lt;th width=&quot;50%&quot;&gt;合規&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;strtok&lt;/td&gt;
&lt;td&gt;執行緒安全&lt;/td&gt;
&lt;td&gt;非多執行緒安全&lt;/td&gt;
&lt;td&gt;POSIX.1-2001, POSIX.1-2008, C89, C99, SVr4, 4.3BSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;strtok_r&lt;/td&gt;
&lt;td&gt;執行緒安全&lt;/td&gt;
&lt;td&gt;多執行緒安全&lt;/td&gt;
&lt;td&gt;POSIX.1-2001, POSIX.1-2008&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;strsep&lt;/td&gt;
&lt;td&gt;執行緒安全&lt;/td&gt;
&lt;td&gt;多執行緒安全&lt;/td&gt;
&lt;td&gt;4.4BSD&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;那麼、何時使用它們？以下建議、僅供參考：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;如果需要空字串、且不允許多個切割符相鄰、及不考慮可移植性，則可選用strsep。&lt;/li&gt;
  &lt;li&gt;如果允許多個切割符相鄰、且不需要空字串，則可選用strtok_r。&lt;/li&gt;
  &lt;li&gt;如果有萬分之一之可能、優先選用或自行實作strtok_r/strsep、而不是使用strtok；strtok可取的只有可移植性佳這一點，可以新作滿足多執行緒安全的函式呼叫之。&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Kenmux Lee</name></author><category term="C" /><category term="strtok" /><category term="strtok_r" /><category term="strsep" /><summary type="html">對於字串切割、C標準函式庫提供了這幾個函式：strtok，strtok_r，strsep；使用時、只需要包含表頭檔string.h即可。詳細說明，參考連結：strtok(3)，strsep(3)。 由於C語言本身的限制以及在執行效率/資源佔有方面的考量與妥協、這些函式顯得並不那麼好用，甚而讓人不禁懷疑「誒？這真的是標準庫提供的函式麼？」。這篇筆記用來記述它們的使用方法、以及一些注意點。 在分述它們的不同之處之前、先說說它們的共同之處吧： 其一、呼叫函式會修改原字串內容（將切割符改為'\0'），故待切割字串不可為無法修改的字串字面常數(string literal)。 其二、單次呼叫只能完成一次切割、而不是直接提供切割完畢的字串數組。 其三、關於切割符字串（更確切的說、切割符集合），其內任一字符（而不是整體）都是切割符，而且在每次呼叫函式時都可以依據需求採用相同/不同的字串。</summary></entry><entry><title type="html">Git修改提交(commits)的作者資訊</title><link href="https://xiwan.io/archive/git-correct-commits-of-author-info.html" rel="alternate" type="text/html" title="Git修改提交(commits)的作者資訊" /><published>2015-08-30T10:00:00+00:00</published><updated>2015-08-30T10:00:00+00:00</updated><id>https://xiwan.io/archive/git-correct-commits-of-author-info</id><content type="html" xml:base="https://xiwan.io/archive/git-correct-commits-of-author-info.html">&lt;p&gt;前幾日，在Github上新做存儲庫時、一時失察、居然使用了工作專用的作者資訊。發現時、已經推送了多個提交……怎麼辦？難道要砍掉重做麼？&lt;/p&gt;

&lt;p&gt;緊急Google了一番、發現完全不用！解決方法也可以有兩種：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;其一、使用Github提供的方法：&lt;a href=&quot;https://help.github.com/articles/changing-author-info/&quot;&gt;Changing author info&lt;/a&gt;。&lt;/li&gt;
  &lt;li&gt;其二、使用Stack Overflow提供的方法：&lt;a href=&quot;https://stackoverflow.com/a/3042512/2518851&quot;&gt;Change commit author at one specific commit&lt;/a&gt;。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;由於是新做的存儲庫、所慮極少，故選用了第一種方法。&lt;!-- more --&gt;步驟如下：&lt;/p&gt;

&lt;p&gt;1) 從存儲庫複製一份裸倉庫(bare repository)：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;$ git clone --bare https://github.com/kenmux/kenmux.github.io.git
$ cd kenmux.github.io.git&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;2) 創建名為&lt;code class=&quot;highlighter-rouge&quot;&gt;git-author-rewrite.sh&lt;/code&gt;的程式腳本：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;$ touch ~/git-author-rewrite.sh
$ chmod u+x ~/git-author-rewrite.sh
$ vim ~/git-author-rewrite.sh&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;並加入如下內容：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/sh&lt;/span&gt;

git filter-branch &lt;span class=&quot;nt&quot;&gt;--env-filter&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'

OLD_EMAIL=&quot;your-old-email@example.com&quot;
CORRECT_NAME=&quot;Your Correct Name&quot;
CORRECT_EMAIL=&quot;your-correct-email@example.com&quot;

if [ &quot;$GIT_COMMITTER_EMAIL&quot; = &quot;$OLD_EMAIL&quot; ]
then
    export GIT_COMMITTER_NAME=&quot;$CORRECT_NAME&quot;
    export GIT_COMMITTER_EMAIL=&quot;$CORRECT_EMAIL&quot;
fi
if [ &quot;$GIT_AUTHOR_EMAIL&quot; = &quot;$OLD_EMAIL&quot; ]
then
    export GIT_AUTHOR_NAME=&quot;$CORRECT_NAME&quot;
    export GIT_AUTHOR_EMAIL=&quot;$CORRECT_EMAIL&quot;
fi
'&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--tag-name-filter&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--branches&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--tags&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;注：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;腳本來源：&lt;a href=&quot;https://gist.github.com/octocat/0831f3fbd83ac4d46451#file-git-author-rewrite-sh&quot;&gt;git-author-rewrite.sh&lt;/a&gt;。&lt;/li&gt;
  &lt;li&gt;替換變數：&lt;b&gt;OLD_EMAIL&lt;/b&gt;、&lt;b&gt;CORRECT_NAME&lt;/b&gt;、&lt;b&gt;CORRECT_EMAIL&lt;/b&gt;。&lt;/li&gt;
  &lt;li&gt;這裡依據：郵箱查找替換，亦可改為依據名字、不再贅述。&lt;/li&gt;
  &lt;li&gt;這裡假定：作者(author)=提交者(committer)，注意甄別；如有必要，可只改其一。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;3) 執行腳本，以修改作者資訊：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;$ chmod u+x ~/git-author-rewrite.sh
$ ~/git-author-rewrite.sh&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;4) 將改動強行推送(force push)到存儲庫，以及善後、清理：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;$ git push --force --tags origin 'refs/heads/*'
$ cd ..
$ rm -rf kenmux.github.io.git
$ rm -rf ~/git-author-rewrite.sh&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;5) 為了不再重蹈覆轍，複製存儲庫後第一件事便是設置作者：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;$ git clone https://github.com/kenmux/kenmux.github.io.git
$ cd kenmux.github.io
$ git config user.name &amp;lt;user-name&amp;gt;
$ git config user.email &amp;lt;user-email&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;當然、如果不嫌煩絮的話、也可以在每次創建提交時指定作者（在以下示例中，名字為kenmux，郵箱為kenmux@gmail.com，請酌情修改）：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;$ git commit --author=&quot;kenmux &amp;lt;kenmux@gmail.com&amp;gt;&quot; -m &quot;commit message&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;</content><author><name>Kenmux Lee</name></author><category term="Git" /><category term="Change Author Info" /><summary type="html">前幾日，在Github上新做存儲庫時、一時失察、居然使用了工作專用的作者資訊。發現時、已經推送了多個提交……怎麼辦？難道要砍掉重做麼？ 緊急Google了一番、發現完全不用！解決方法也可以有兩種： 其一、使用Github提供的方法：Changing author info。 其二、使用Stack Overflow提供的方法：Change commit author at one specific commit。 由於是新做的存儲庫、所慮極少，故選用了第一種方法。</summary></entry><entry><title type="html">GitHub Pages使用自訂網域名稱</title><link href="https://xiwan.io/archive/github-pages-use-custom-domain-name.html" rel="alternate" type="text/html" title="GitHub Pages使用自訂網域名稱" /><published>2015-08-23T10:00:00+00:00</published><updated>2015-08-23T10:00:00+00:00</updated><id>https://xiwan.io/archive/github-pages-use-custom-domain-name</id><content type="html" xml:base="https://xiwan.io/archive/github-pages-use-custom-domain-name.html">&lt;p&gt;前幾日，把Godaddy域名重新設置了下，從WordPress網站那兒拿來給GitHub Pages用了。趁著記憶猶新，先做個筆記！&lt;/p&gt;

&lt;p&gt;總的來說，設置還是蠻簡單的，可以分為兩步：&lt;br /&gt;
其一，&lt;a href=&quot;/archive/github-pages-use-custom-domain-name.html#github-pages-settings&quot;&gt;GitHub Pages設置&lt;/a&gt;&lt;br /&gt;
其二，&lt;a href=&quot;/archive/github-pages-use-custom-domain-name.html#godaddy-domain-settings&quot;&gt;Godaddy域名設置&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;注：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;文中示例，域名為XIWAN.INFO，請酌情修改為自訂域名。&lt;/li&gt;
  &lt;li&gt;參考文章：&lt;a href=&quot;http://andrewsturges.com/blog/jekyll/tutorial/2014/11/06/github-and-godaddy.html&quot;&gt;Configuring a Godaddy domain name with github pages&lt;/a&gt;，感興趣的可以戳進去看看。&lt;!-- more --&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;github-pages設置&quot;&gt;&lt;a name=&quot;github-pages-settings&quot;&gt;&lt;/a&gt;GitHub Pages設置&lt;/h1&gt;
&lt;p&gt;首先，將&lt;code class=&quot;highlighter-rouge&quot;&gt;_config.yml&lt;/code&gt;檔案中變數&lt;code class=&quot;highlighter-rouge&quot;&gt;url&lt;/code&gt;的值修改為域名:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;url: &quot;http://xiwan.info&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;其次，在根目錄下創建名為&lt;code class=&quot;highlighter-rouge&quot;&gt;CNAME&lt;/code&gt;的檔案：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;$ cd kenmux.github.io
$ touch CNAME&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;修改&lt;code class=&quot;highlighter-rouge&quot;&gt;CNAME&lt;/code&gt;內容，將域名填入即可：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;xiwan.info&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h1 id=&quot;godaddy域名設置&quot;&gt;&lt;a name=&quot;godaddy-domain-settings&quot;&gt;&lt;/a&gt;Godaddy域名設置&lt;/h1&gt;
&lt;p&gt;訪問Godaddy：&lt;a href=&quot;https://www.godaddy.com/&quot;&gt;https://www.godaddy.com/&lt;/a&gt;。登入帳號，開始設定域名。點選標籤頁「&lt;b&gt;DNS ZONE FILE&lt;/b&gt;」，要進行設定的有兩個地方：&lt;a href=&quot;/archive/github-pages-use-custom-domain-name.html#a-host-setting&quot;&gt;A (Host)&lt;/a&gt;和&lt;a href=&quot;/archive/github-pages-use-custom-domain-name.html#cnname-alias-setting&quot;&gt;CNname (Alias)&lt;/a&gt;。&lt;br /&gt;
&lt;img src=&quot;/assets/imgs/2015/08/23/github-pages-use-custom-domain-name/2015-08-23-01.png&quot; alt=&quot;2015-08-23-01.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;注：如果標籤頁「&lt;b&gt;DNS ZONE FILE&lt;/b&gt;」並不是像上圖顯示的那樣，有很大可能是因為客製了名稱伺服器。回到「&lt;b&gt;SETTINGS&lt;/b&gt;」標籤頁，將「&lt;b&gt;Nameservers&lt;/b&gt;」回復到預設值，如下圖所示：
&lt;img src=&quot;/assets/imgs/2015/08/23/github-pages-use-custom-domain-name/2015-08-23-02.png&quot; alt=&quot;2015-08-23-02.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a name=&quot;a-host-setting&quot;&gt;&lt;/a&gt;先設置&lt;code class=&quot;highlighter-rouge&quot;&gt;A (Host)&lt;/code&gt;吧。點擊下方連結&lt;u&gt;&lt;font color=&quot;blue&quot;&gt;Add Record&lt;/font&gt;&lt;/u&gt;，出現如下彈出框：&lt;br /&gt;
&lt;img src=&quot;/assets/imgs/2015/08/23/github-pages-use-custom-domain-name/2015-08-23-03.png&quot; alt=&quot;2015-08-23-03.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;「&lt;b&gt;HOST:&lt;/b&gt;」那一欄填入&lt;code class=&quot;highlighter-rouge&quot;&gt;@&lt;/code&gt;，而「&lt;b&gt;POINTS TO:&lt;/b&gt;」那一欄填入&lt;code class=&quot;highlighter-rouge&quot;&gt;192.30.252.153&lt;/code&gt;或者&lt;code class=&quot;highlighter-rouge&quot;&gt;192.30.252.154&lt;/code&gt;，如下圖所示：&lt;br /&gt;
&lt;img src=&quot;/assets/imgs/2015/08/23/github-pages-use-custom-domain-name/2015-08-23-04.png&quot; alt=&quot;2015-08-23-04.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;填寫完畢後，點選「&lt;b&gt;FINISH&lt;/b&gt;」以完成輸入。&lt;code class=&quot;highlighter-rouge&quot;&gt;A (Host)&lt;/code&gt;那一欄看起來應該是這樣的：&lt;br /&gt;
&lt;img src=&quot;/assets/imgs/2015/08/23/github-pages-use-custom-domain-name/2015-08-23-05.png&quot; alt=&quot;2015-08-23-05.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;注：記得點選「&lt;b&gt;Save Changes&lt;/b&gt;」以保存修改哦。這裡採用了兩條記錄，實際上一條足矣。&lt;/p&gt;

&lt;p&gt;&lt;a name=&quot;cnname-alias-setting&quot;&gt;&lt;/a&gt;接下來設置&lt;code class=&quot;highlighter-rouge&quot;&gt;CNname (Alias)&lt;/code&gt;。同樣是點選&lt;u&gt;&lt;font color=&quot;blue&quot;&gt;Add Record&lt;/font&gt;&lt;/u&gt;，在出現的彈出框中選擇&lt;code class=&quot;highlighter-rouge&quot;&gt;CNNAME (Alias)&lt;/code&gt;，如下圖所示：&lt;br /&gt;
&lt;img src=&quot;/assets/imgs/2015/08/23/github-pages-use-custom-domain-name/2015-08-23-06.png&quot; alt=&quot;2015-08-23-06.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;「&lt;b&gt;HOST:&lt;/b&gt;」那一欄填入&lt;code class=&quot;highlighter-rouge&quot;&gt;www&lt;/code&gt;，而「&lt;b&gt;POINTS TO:&lt;/b&gt;」那一欄填入GitHub Pages預設URL（形如&lt;code class=&quot;highlighter-rouge&quot;&gt;username.github.io&lt;/code&gt;），如下圖所示：&lt;br /&gt;
&lt;img src=&quot;/assets/imgs/2015/08/23/github-pages-use-custom-domain-name/2015-08-23-07.png&quot; alt=&quot;2015-08-23-07.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;注：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;若如圖例中所示、已有「&lt;b&gt;www&lt;/b&gt;」項，則直接編輯修改此項即可。&lt;/li&gt;
  &lt;li&gt;若要使用二級域名，則「&lt;b&gt;HOST:&lt;/b&gt;」需要填寫為二級域名。以&lt;code class=&quot;highlighter-rouge&quot;&gt;blog.xiwan.info&lt;/code&gt;為例，則需要填寫為&lt;code class=&quot;highlighter-rouge&quot;&gt;blog&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;填寫完畢後，點選「&lt;b&gt;FINISH&lt;/b&gt;」以完成輸入。再次點選「&lt;b&gt;Save Changes&lt;/b&gt;」以保存修改。設置完畢後，看起來應該是這樣：&lt;br /&gt;
&lt;img src=&quot;/assets/imgs/2015/08/23/github-pages-use-custom-domain-name/2015-08-23-08.png&quot; alt=&quot;2015-08-23-08.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;大功告成！如此一來，&lt;code class=&quot;highlighter-rouge&quot;&gt;http://xiwan.info&lt;/code&gt;、&lt;code class=&quot;highlighter-rouge&quot;&gt;http://www.xiwan.info&lt;/code&gt;都將指向&lt;code class=&quot;highlighter-rouge&quot;&gt;http://kenmux.github.io&lt;/code&gt;。&lt;/p&gt;</content><author><name>Kenmux Lee</name></author><category term="GitHub Pages" /><category term="Domain Name" /><category term="Godaddy" /><summary type="html">前幾日，把Godaddy域名重新設置了下，從WordPress網站那兒拿來給GitHub Pages用了。趁著記憶猶新，先做個筆記！ 總的來說，設置還是蠻簡單的，可以分為兩步： 其一，GitHub Pages設置 其二，Godaddy域名設置 注： 文中示例，域名為XIWAN.INFO，請酌情修改為自訂域名。 參考文章：Configuring a Godaddy domain name with github pages，感興趣的可以戳進去看看。</summary></entry></feed>