列出適合拿來秀程式碼的字型,以後不用花時間找
- Consolas
- Inconsolata
- Source Code Pro
類似的字型太多了,就列出最受歡迎的幾個。
列出適合拿來秀程式碼的字型,以後不用花時間找
在C語言的時候,const的用途很簡單,用來修飾變數的屬性。以下給個範例
1 | void func(const int *v) |
上面的*v = 10
會被編譯器指出v是不能被修改的。 值得注意的是 const int *
的寫法跟 int const *
是一樣的,不過我比較偏好前者。
如果改寫成
1 | void func(int * const v) |
會告訴你v = &p
這行錯了,由此可見int const *
表示被指向的 內容 不可改,而指標是可以改變的,這邊的const是用來修飾int
的。而int * const
表示指向的 指標 不可改,而這邊的const是用來修飾int *
的。
當然,如果要兩者間得,也可以寫成這樣
1 | void func(const int * const v) |
C++擴大const的使用範圍,允許const修飾Class的Member Function。表示這個函數是 Logic Constness,不影響外界看這物件的狀態。因此以下這段程式碼會出現問題。
1 | class Test { |
由於C++支援Cast Overloading,支援Function Signature相同,但const屬性不同的Overload,因此這樣的是合法的,,
1 | class Test { |
寫個程式來測試一下
1 | oid Test1(const Test &t) |
除了Test1
的Func是跑const版本之外,其他所有函數都是呼叫Non-const版本的Func
雖說const的Member function是不可修改 Logic Constness ,不過以下程式碼很難是合法的。
1 | class Test { |
之前說過,從外界看到的類別狀態是 Logic Constness 的,這代表我們可以在 const函數裡面動手腳,只要外界看起來正常就好,因此就有了mutable的誕生。將上面的範例重新改寫。
1 | class Test { |
這樣就能正常使用了…初看之下好像很沒用,不過以這個範例來說
1 | class Test { |
在Multithread的情況之下,有人會呼叫SetState,而有人會想知道GetState的值,雖然state在GetState不會被改變,但是mutex會變。所以需要mutable的存在。
另外一種情形是當做Cache使用
1 | class HashTable { |
不過在C++11之後,又有新用法了
1 | int x = 30; |
f1
在呼叫之後,x會變成123,不過離開f1之後,x又回到30,而f2
呼叫之後就整個變成123了。
不過我看不懂第一個範例的用途是什麼。
constexpr是C++11才有的觀念,原先就有Constant Expressions的觀念,不過還是有其不足之處。
假設我們要宣告個n*m的一維陣列,我們會這麼做。
1 | const int n = 5; |
假設我們已經有一個mul2
的函數,試著編譯以下這段程式就會出現錯誤。
1 | const int n = 5; |
結果我們只能藉由以下兩種方法解決,一是回到C語言的Preprocessor來做。
1 | #define mul2(x, y) ((x) * (y)) |
這方式可行,不過缺乏型態資料。在Secure Coding的時候容易出錯。
另一種是在Runtime時算出一個常數。
1 | int mul2(int x, int y) { return x * y; } |
如果要把array儀到global scope,問題又出現了。導致不得不使用Preprocessor的解法。因此就有了constexpr
的誕生。
有了constexpr之後,可以在編譯時期就算出答案,類似Template Metaprogramming,不過用途更廣。
上面的範例我們可以重新改寫
1 | #include <stdio.h> |
可以看到這邊的mul2不只可以用在compile-time,在runtime也可正常執行。
也可以在編譯時期使用物件,上面的範例我們可以用Functor
在寫一次。
1 | const int n = 5; |
如果對constexpr有更多了解,可以參考Constexpr - Generalized Constant Expressions in C++11,目前支援constexpr的編譯器也不多。
C++果然不愧是最難學的語言,每個環節都搞的特別複雜。我難過。
看到了fasd之後發現真是相見恨晚啊。整天在那邊切換目錄真是麻煩。
安裝方法很簡單,下載之後只要
1 | $ sudo make install |
接著再自己的.bashrc
之後加上這行
1 | $ eval "$(fasd --init auto)" |
然後就完成了…
最簡單的情況,假設我們現在在home目錄下有foo
跟bar
兩個目錄,而foo目錄下有bar這個檔案。
1 | ~ $ cd foo |
接著就可以用
1 | ~/bar $ z foo |
跳到你想要的目錄,當然也支援 Tab complementation 。
也可以使用
1 | $ zz |
來使用交互式的方式來跳躍。
假設我們要修改~/foo/bar
這個檔案,我們只需要
1 | ~/bar $ vim `f bar` |
如果我們要把bar這個檔案複製到bar這個目錄,也只要
1 | ~/bar $ cp `d foo`/bar . |
現在有兩個bar在兩個不同的目錄底下,如果要編輯的話,必須指定更多資訊供批配。
1 | ~/bar $ vim `f foo/bar` |
其他更複雜的使用方法就參照網站上的範例使用。
看到C#的yield之後,上網查了一下,發現這是Generator的概念。根據Wiki上的寫法
A generator is a special routine that can be used to control the iteration behaviour of a loop
以C#為程式語言示範,假設沒有控制Iterator的話,印出一到一百間所有質數,我們會這麼寫。
1 | static IList<int> GeneratorPrimes() |
假設我們想產生無限的質數列表,這方法就不適用,因此我們需要改變一個思緒,每次從中取得一個數,需要的時候在取下一個,因此,我們需要儲存狀態。
1 | foreach (var item in GeneratorPrime()) |
至於GeneratorPrime的作法就是我們要討論的部份
沒有語言支援,只好再內部維護一個狀態機,記住當今狀態,以及如何移動狀態。
1 | public class PrimesGenerator : IEnumerator<int> |
C++和Java的作法略有不同,不過重點是放在PrimesGenerator中各種狀態的保存及移轉。如果簡單的話還好,像是Tree Traversal這種複雜的情況就一個頭兩個大。
由於C#提供yield
,yield表示江控制權教還給呼叫者,而Iterator保持在回傳時的那個狀態,當再一度被呼叫時,可以重剛剛那個狀態繼續運行下去,於是我們程式碼可以寫成這樣。
1 | public static IEnumerable<int> GeneratorPrime() |
這邊的yield就是一個syntax sugar,透過編譯器的幫忙,將上面這段code改寫成類似上面的State Machine,有興趣的人可以透過Resharper來看看邊義氣幫你處理掉多少東西。
由於Haskell有Lazy evaluation,所有東西都是Generator,所以可以寫成這樣。
1 | primes = 2 : 3 : nextprime 5 where |
前面的primes產生一個無限質數數列,而printPrimes印出這個數列中滿足條件的部份,
有時候有其需要,知道Shared Library當中有哪些符號,這時候可以這樣做。
以下是我們的測試程式
1 | int bar; |
在Linux底下可以用nm
來指令
1 | $ nm -gC test.so |
g
列出所有External symbolsc
由於C++支援Function overload,要區別同名的函數名稱的話,需要在後方加上一些符號以供區別,而Demangle就是讓這些奇形怪狀的符號變成人可以看懂得版本。1 | 000000000020102c B bar |
需要額外一個步驟,用一個Exporting from a DLL Using DEF Files倒出foo這個函數。
Windows底下可以用DLL Export Viewer。
而我們只看到foo,而沒有clas被輸出。輸出一個class不能用Def那種方式,如果需要的話,可以用dllexport這種方式。
這邊可見Windows跟Unix設計上的不同,Unix是預設把所有Symbols列出,然後需要的在隱藏。而Windows是需要的在列出。
至於如何隱藏,Binary Hacks有介紹,日後在補上。
同樣來自VC Blog的靈感,來測試一下三大編譯器的能耐。
如果對這技術有興趣的話,可以操考Wiki跟Code Optimizations:
Partial Dead Code Elimination和Dead Code Elimination這兩份PDF。
測試程式如下
1 | int main() |
未開最佳化,如同VC Blog列的那樣
1 | mov QWORD PTR i$1[rsp], 1 |
最佳化版本,結果相同,什麼都沒有
1 | // Nothing |
未開最佳化
1 | movq $1, -8(%rbp) |
如同Visual C++的結果一樣,照實執行。
最佳化版
1 | // Nothing |
跟Visual C++的結果相同,完全沒有程式碼,證明了這段程式碼一樣被偵測到而被捨棄。根據這篇的說明,在-O2
的設定終究有暗示Dead Code Elimination的使用。
未開最佳化
1 | movq $1, -24(%rbp) |
可以看出三大編譯器對做同一件事,產生的程式碼風格相差甚大。
最佳化版
1 | // Nothing |
大家都能偵測到Dead Code Elimination。
在VC Blog的Option 2,在原來的程式碼後面,加上printf,使得Dead Code Elimination失效。
VC的結果如下
1 | xor edx, edx |
使用Loop unwinding的技巧加速。
不過GCC跟Clang更勝一籌,直接印答案了。
1 | movabsq $500000000500000000, %rsi # imm = 0x6F05B59F17F6500 |
如果把原先的程式碼改成這樣
1 | #include <stdio.h> |
Visual C++跟GCC的產生結果就比較像了,VC原先的Loop Unwinding被拿掉了。
1 | movl $1, %eax |
而Clang會根據x64指令集作最佳化一
1 | movq 8(%rsp), %rax |
看了VC Blog的Constant-Folding之後,測試一下VC2012,GCC和Clang對最佳化的演繹程度為何。
比較基準,未開最佳化(VC是/Od,而GCC/Clang是-O0),跟第二級最佳化(/O2和-O2)
第一個範例
1 | int main() { return 7 + 8; } |
不果是VC2012,GCC或是Clang都會直接將eax填入15,無論有沒有開最佳化。這邊的最佳化不需要後端Codegen的幫忙。
第二個範例
1 | int bump(int n) { return n + 1; } |
在未開最佳化的情況,VC產生的Assembly Code像這樣
1 | mov ecx, 6 |
而最佳化的Assembly Code則是
1 | mov eax, 10 |
這邊需要後端Codegen的幫忙,才能在編譯的時刻得到常數值。
而GCC跟Clang的表現也差不多,最大的差別是Intel語法跟AT&T對組合語言的表示方式不同。
未最佳化版本
1 | movl $6, %edi |
最佳化版本
1 | movl $10, %eax |
在這種最簡單的最佳化大家都有一樣的能力,接著可以看其他部分的差異。
把目前看到的資訊做個總結,分幾篇文章來寫
假設我們要抓取Web Response,可以這樣寫。
1 | static int BUFFER_SIZE = 1024; |
以上都是同步動作,如果有個Thread呼叫此函數,外面的Thread必須等待這個函數執行結束,才能繼續往下走。如果是UI Thread的話,通常會出現漏斗符號,然後整個UI僵住的情形。
解決方案之一是把耗時的工作丟到Thread之後,外面呼叫的就能繼續向下執行。
1 | static void ThreadStart() |
更甚一點的是用ThreadPool來,減少Thread的開銷
1 | ThreadPool.QueueUserWorkItem(state => ThreadStart()); |
不過這還是無法完全解決問題,這邊的行為是屬於IO Bounded Operation,不需要CPU計算能力,指需要完成之後,發個中斷通知CPU事件完成就好,使用Thread的解決方案還是不夠好。而.Net的IO Operation幾乎都有提供同步跟非同步的版本,就從這點開始。
如同上面所說,幾乎所有IO operation都有非同步版本,而其命名都是BeginXXX
/ EndXXX
開始,因此我們可以把程式改寫成這樣。
1 | static void getContentFromURI(string uri) |
程式碼被切成兩段,在GetResponse之前,跟GetResponse之後,相較於同步的版本,相差沒說很大。
談一下Exception部分,同步的版本指需要一個try-catch,而非同步的需要兩個,這邊變得很麻煩。
更進一步將Read的部份變成非同步的版本,程式碼會變成這樣。
1 | static void ReadHelper(Stream stream) |
這邊可以看到,用using釋放資源的方式已不可行,同樣的,用for/while的方式也不可行,必須使用Recursive的方式來持續讀取資料。而由於這樣,例外處理幾乎不可行。程式碼寫的支離破碎,難怪一堆人寧可寫同步版本。
眾所週知,Java跟C#都是從C++演變而來。
既然C++有Template
,Java在5.0,C#在2.0的時期分別引進了Generics
,
雖然看起來很像,看其中差異還是不小,列出重要的幾點份來討論。
由於實作方式的不同,Generics拿掉了這個
1 | template <typename T, int MAXSIZE> class Stack { |
不管是Full Specialization或是 Partial Specialization,通通不行。所以也寫不出這樣的程式碼。
1 | template <typename T> class Foo {}; |
這點才是最大的不同,假設我們要寫一個兩樹相加的Generic function。
在C++我們會這麼寫
1 | template <typename T> T sum2(T a, T b) { return a + b; } |
可以看到,只要有定義operator+
,我們什麼東西都能相加。
同樣的Code,如果改寫成C#的話
1 | public static T sum2<T>(T a, T b) |
連編譯都邊不過。原因出在Template是在Compile-Time完成的,在編譯的時刻就能檢查出operator+
是否存在,而Java跟C#的Generics是在Runtime則否,必須對Type有其一定的限制,在這個範例裡面,我們需要這個Type要有相加的能力,才能作相加的動作。
1 | public <T extends Number> T sum(T a, T b) { |
所有從Number衍生出來的子類別,Integer,Double等都可以使用,不過String不行。
不過.Net 4.0之後有更好的解法,掠過編譯時期的型別檢查,不過Java沒有此特性。
1 | public static T sum2<T>(T a, T b) |
這是另一個大題目,同樣在.Net 4.0之後,支援了Covariance和Contravariance。Java的版本有無支援也需要確認。
以後有時間再仔細描述。在C#可以寫這樣的Code
1 | class Base |
類似的程式碼在C++就失敗了…
1 | class Animal |
例如SFINAE,Template Template Parameter等,由於學習門檻相當高,所以Generics都把它拿掉了。
程式計算中有個技巧,叫做Memoization,避免重複計算相同的工作。
以下分別用C/Python/Haskell來當範例
以Fibonacci Number來說,遞迴的表示式這樣。
1 | int fib(int n) |
加上Memoization的程式碼會變這樣
1 | int _fib[100]; |
至於Python版Recursive的程式碼也相當相似
1 | def fib(n): |
不過由於Python將Function當First-Class Function,可以寫出像這樣的程式碼。
1 | def memoize(fn): |
更甚者可以使用Python的Decorator來簡化程式碼
1 | def memoize(fn): |
Haskell反而是裡面最麻煩的,因為他是個純Pure Functional Language。同樣的先來看Recursive版本。
1 | fib :: Int -> Integer |
而Memorize版的方法不少,不過比起上面兩種反而難以理解。
1 | memoized_fib :: Int -> Integer |
果然各種程式語言個有所長,看情況決定該用些什麼語言解決問題才是。