1. 程式人生 > >C 特性雜談

C 特性雜談

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!

               

文中充滿了各種C#與其他語言的對比及吐槽, 希望介意者勿觀… 當然, 鑑於太亂, 我懷疑有沒有人能看完.

學習C#

C#也的確不是當年那種微軟, Windows獨佔的語言了, mono專案已經將C#移植到了Mac和Linux上, 甚至還包括iOS和Android. 也就是說, 假如你願意的話, 你可以使用C#通吃所有平臺, 當然, 前提是你能接受巨貴的授權費用. 作為什麼事情都喜歡自己搞一套的微軟,(因為他都是壟斷的) 在C#這件事情上一開始就很開放(因為有JAVA在前), 總算是做對了一件事情.

Hello World

Mono在建立一個名為test的console的工程後, 給了我一個Hello World的程式碼,

using System;namespace test{  class MainClass  {    public static void Main (string[] args)    {      Console.WriteLine ("Hello World!");    }  }}

第一眼看去, 就知道C#中了JAVA的毒了, 用所謂完全面向物件的方式, 強迫你寫一堆臃腫而無用的程式碼… 我至今也沒有明白為什麼Main函式最後會變成Main類, 也沒有明白這有什麼好處… 想起最近看的一篇文章Why Java Sucks and C# Rocks, 說”自從C# 1.0誕生之日起,就只出現Java借鑑C#特性的情況”, 以此來駁斥JAVA一派對C#抄襲的指責. 其實, 光是從Hello World都能看出來C#對JAVA的模仿, 他的言論也的確是避重就輕了, 因為, 連他也無法否認C#誕生前及誕生過程中發生的事情…

值得一提的是, C#對傳統的printf進行了改進, {0}形式的佔位符不需要表明型別, 只按照數量和位置匹配.

變數與表示式

  1. C#沒有像JAVA一樣把無符號型別給去掉, 這點我覺得有些不可思議. 在C++新的使用傾向中都已經儘量的去使用有符號的型別了, 除非是進行位運算.
  2. 變數的宣告方式和C/C++一致, 為type name;的形式.
  3. 有++自增操作符, 本來很正常的事情, 但是因為最近老是在用Python和Ruby, 看到這個竟然有些親切.
  4. 列舉可以指定基本型別, 但是本身還是強型別的, 只能強制轉換, 不能預設轉換.
  5. 有類似C++ 11 auto的型別推理關鍵字var. 可以極大的簡化我們的生活~~~

動態型別

大家都知道類似C/C++, JAVA, C#這種對效率還稍微有些追求的語言都是靜態型別語言, 並且靠編譯期靜態型別檢查來排查錯誤, 而從C++以後, 各語言都以更加’真正的’強型別自豪. 而類似auto,var等自動型別推導的變數只不過是語法糖而已, 所以當我看到C#的確提供了動態型別dynamic, 我還是著實吃了一驚. 這也體現了C#的設計者們比C++, JAVA更加激進的一面. 很多年前, BS就說過(其實Mats也說過類似的), 語言不是一堆特性的堆積, 也不是說堆積的越多就越好. 因為, 很多時候JAVA, C#的擁護者們炫耀著JAVA, C#有著什麼樣的新特性的時候, 其實並不太感冒. 但是, 這一個, 夠讓人震撼的. 一個靜態語言裡面有動態型別會是什麼效果? 不禁想讓人嘗試一番.

public static dynamic Add(dynamic var1, dynamic var2){  return var1 + var2;}

類似上面的Add功能, 本來必須使用模版才能實現, var使用時, 因為是型別推導, 所以必須在初始化時才能使用, 不能用於函式引數, 而真正的動態型別dynamic就可以. 最近使用Python, Ruby比較多, 突然感覺C#有種Python, Ruby上身的感覺.

值型別和引用型別

當我看到Why Java Sucks and C# Rocks(2):基礎型別與面向物件一文時, 我還以為C#真的已經是所謂的”完全面向物件”了. 所以當我看到C#入門經典書中寫到變數的型別還是分為引用型別和值型別時, 我相當意外.(我先看的那一系列文章, 再看的C#入門經典) 等我看到書中寫到封箱和拆箱的時候, 就更加驚訝了… 都是一個物件, 為啥還要封箱和拆箱呢? 當然, 鑑於Objective-C連自動的封箱和拆箱都還沒有, 我也不能說這就有多麼落後. 
不過, 我用C++的時候就完全沒聽說過封箱和拆箱的概念, 容器的設計完全可以容納基礎數值, 為啥到了Objective-C, JAVA和C#裡反而不行了呢? 因為C++沒有統一的基類? 容器設計的時候就壓根不是光考慮儲存啥Object物件的. 這個倒是讓我想起一句話, 你以為你解決了一個問題, 因為你比以前更加優美了, 但是同時帶來了另外一個問題, 後來, 你們比較的是後一個問題誰更優美的解決了. 而這個問題本來並不存在…
另外, 當你其實還分值型別和引用型別的時候, 你就已經輸給Python, Ruby了, 何必還討論誰的箱子更好看呢… 要把這個問題上升到理念層次, 我更加就沒法認同了.

checked支援的受限強制轉換

增加了checked, unchecked(預設)關鍵字來應付型別轉換時的溢位問題. 比如下面的程式碼:

short source = 257;byte dest = (byte)source;

上面的程式碼在轉換時會發生溢位, 這往往不是我們要的結果, 也往往因此出現莫名而難以除錯的bug. 而類似下面的程式碼會在執行時會丟擲System.OverflowException: Number overflow.異常. 這個方案很值得欣賞. 簡單有效.

流程控制

  1. 保留了goto.
  2. 有foreach迴圈, 這個是在C++時代我羨慕的語法糖, 不過現在也不稀奇了.

陣列

  1. 從0開始計數. 越界訪問拋異常System.IndexOutOfRangeException, 這似乎是C++以後語言的標配了.
  2. 本身帶Length表示長度, 標配, 這個在意料之中了.

函式

  1. 看到了引用引數關鍵字ref, 也算結束了JAVA中痛苦的經歷. 在JAVA中都是pass by value, 連一個簡單的swap函式都不能直接實現. 還得通過一個構建一個數組來實現, 都不知道怎麼想的.
  2. 新增輸出引數關鍵字out, 與ref類似, 有以下區別:
  3. 把為賦值的變數用作ref引數是非法的, 但是可以把未賦值的變數用作out引數.
  4. out引數在函式中使用時, 必須看作時未賦值的.

可選引數

實際上就等於C++裡面的引數預設值, 在有預設值時, 在函式最後的引數為可選. 看書中說C#是在C#4後才支援, 為啥呢?

命名引數

命名引數在動態語言裡面是很常見的, 並且是個容易理解又很使用的功能. 但是C++並沒有支援, 看BS在C++語言的設計與演化中講到其實當時有過討論, 只是因為在C++中有所謂的介面與實現分離, 而看以前的程式碼, 很多介面(標頭檔案)使用的引數名和實現中用的引數名並不一樣, 所以這個有用的特性並沒有加入C++. 這也是為什麼我前面說介面與實現分離實在是太不DRY的一個原因.
當時BS給的例子是用Win32 API建立windows的程式碼(因為書在同事那裡, 記憶不準確請指出), 因為需要的引數實在是太多了. 非常的不方便, 見MSDN:

HWND WINAPI CreateWindow(    _In_opt_  LPCTSTR lpClassName,    _In_opt_  LPCTSTR lpWindowName,    _In_      DWORD dwStyle,    _In_      int x,    _In_      int y,    _In_      int nWidth,    _In_      int nHeight,    _In_opt_  HWND hWndParent,    _In_opt_  HMENU hMenu,    _In_opt_  HINSTANCE hInstance,    _In_opt_  LPVOID lpParam    );

事實上, 在Win32 API裡面, 你要完整的建立一個視窗, 還有類似註冊視窗類等巨多引數的介面, 而其實在這個API裡面, 每次呼叫時真正需要使用的又並不是所有的引數, 不用說有多不方便了. 可選引數(引數預設值), 只能在引數列表的最後使用, 讓這種簡化有的時候變成了一個排序遊戲, 到底哪個引數才是最不常用的呢?
BS給了在C++裡面我們的一種解決方案, 這種方案也是我們在實際中使用的方案, 那就是用struct, 當struct成員變數都有預設值的時候, 我們就只需要給我們真正需要的那個變數賦值即可. 具體的情況就不多說了, 書上都有, 但是在有命名引數後這些都是浮雲. 你只需要給你的確需要的引數賦值即可, 也不需要額外的建立類或結構. 比如上例, 有了命名引數後, 我假如只對視窗的寬度感興趣, 那麼如下呼叫即可:

public static int CreateWindow (  int lpClassName = 0,  int lpWindowName = 0,  int dwStyle = 0,  int x = 0,  int y = 0,  int nWidth = 0,  int nHeight = 0,  int hWndParent = 0,  int hMenu = 0,  int hInstance = 0,  int lpParam = 0){  return 0;}    public static void Main (string[] args){  CreateWindow(nWidth: 640, nHeight: 960);}

還有比這更方便的事情嗎? 順面吐槽一句, Objective-C裡面函式呼叫的方式簡直就是為命名引數準備的, 當然竟然完全不支援命名引數, 甚至不支援引數預設值, 崩潰啊…

委託(delegate)

這算是接觸到的第一個較新的概念, 多寫一點.
delegate是Objective-C裡面用的非常多的概念, 有很方便的一面, 但是是在類這個層次上的概念.
C#的委託更加想是Objective-C的SEL/@selector和C++ 11的function, 也就是為了方便函式呼叫(特別是回撥函式)和構建高階函式而存在的. 這個也是函式不是第一類值(first class)的語言裡面需要解決的問題. 函式指標有人說很方便, 但是那個語法實在太逆天了. 當然, 因為這個原因, 其實C#的委託也無法實現直接對<, >, +, -等操作符的控制, 而是需要用類似C++的方法提供輔助函式的方法來完成.

C#的委託:

using System;namespace test{  class MainClass  {    delegate int Actor (int leftParam, int rightParam);    static int Call (Actor fun, int leftParam, int rightParam)    {      return fun(leftParam, rightParam);    }    static int Multiply (int leftParam, int rightParam)    {      return leftParam * rightParam;    }    static int Divide (int leftParam, int rightParam)    {      return leftParam / rightParam;    }    public static void Main (string[] args)    {      Console.WriteLine ("{0}", Call(new Actor(Multiply), 10, 10));      Console.WriteLine ("{0}", Call(new Actor(Divide), 10, 10));      Console.ReadKey();    }  }}

匿名函式(Lambda)

有匿名函式的語言才算是現代語言啊… 我說這句話, C++, JAVA, Objective-C, C#無一中槍, 不管是加入的早晚(其實都是較晚), 上述語言都已經有了使用匿名函式的辦法. 對於Python, Ruby來說, 匿名函式就更不是什麼新鮮事物了. 比較有意思的是, 作為靜態語言的新事物, 上述語言都獨立的發展了一套自己的Lambda語法, 而且各有特色, 並且最終的目的似乎都是讓你搞不明白. C#的Lambda使用了=>來標記. 因為可以使用型別推導, 所以語法的簡潔性上可以做到極致.
參考上面委託的例子, 假如Multiply和Divide我們只是使用一次的話, 還按上面的形式定義就太麻煩了, 匿名函式可以簡化程式碼.

class MainClass{  delegate int Actor (int leftParam, int rightParam);  static int Call (Actor fun, int leftParam, int rightParam)  {    return fun(leftParam, rightParam);  }  public static void Main (string[] args)  {    Console.WriteLine ("{0}", Call((leftParam, rightParam) => {          return leftParam