從這一篇開始,我們就正式開始構建C#的知識體系。我們先羅列一下C# 1.0發布時所包含的語法特性,如下:類(Class)
結構(Struct)
接口(Interface)
事件(Event)
屬性(Property)
委託(Delegates)
表達式
語句
特性(有時候也叫屬性)(Attribute)
C# 1.0的特性只有以上這些,基本上涵蓋了C#中最常用、最核心、最基礎部分的內容。有了以上這些特性,C# 1.0就足夠我們去寫一些應用程式了。
對於有點經驗的同學來說,以上的這些特性:除了結構(Struct)和事件(Event),大家都應該很熟悉了,最起碼在寫項目的時候都有接觸過。由於本文是比較偏向C#使用進階的,一些非常基礎的東西就不做過多地介紹了。
接下來,我們開始按順序簡單地過一下以上的特性。
2.1 我所理解的類(一)單純從功能上來說,類可以:
一提到類,第一時間想到的就是以上的這些功能。
往細了想,類還提供了很多的功能,不過這些都不是本文的重點。重點是,編程中有沒有類有什麼區別?
2.1.1 沒有類會怎麼樣?如果沒有類,在Unity中就無法方便地寫腳本,因為Unity中的腳本需要繼承MonoBehaviour類。
我們知道MonoBehaviour中包含了Transform、GameObject的引用,也有一些生命周期方法,比如:Awake、Start、Update、OnDestory、OnEnable、OnDisable、OnTriggerEnter等,提供了豐富的功能和API。
我們寫Unity腳本的時候,只需要簡單地繼承MonoBehaviour類就可以擁有以上所說的所有功能和API。也就是說類的繼承這個功能,讓用戶可以更方便地復用代碼,更方便地使用引擎。
以上僅僅是一個小例子,大家可以從自己的編程經驗出發,簡單思考下沒有類會怎麼樣?
2.1.2 類是編程生涯中的一個裡程碑早期,我們在學習一門語言的時候會接觸到一門語言的各種特性和概念。類這個概念,是比較容易接觸到、也比較容易理解的概念。但是並不是接觸過了、理解了就代表能夠使用了。
記得在學校的時候,我先後接觸了Java、Android、Objective-C、Cocos2D/Cocos2D-x、C++ 等平臺和語言。Java與Java Web學習了有半年,然後開始著手做一些學校的項目。後來接觸了Android,也做了半年。等到接觸Objective-C和Cocos2D的時候已經是大三了,當時距離我寫下第一行代碼已經過去了兩年,但是寫的代碼還是很混亂。
現在看來,亂的原因非常簡單,就是不會分類。
比如:在寫Android的時候,我會把很多代碼都寫到一個文件裡,也就是Android工程創建時自帶的Activity類(Activity算是界面類),寫著寫著很容易混亂。在寫Cocos2D項目的時候,也習慣把很多代碼寫到一個Scene類裡,不管是播放動畫,還是輸入監聽,全部都在同一個Scene類裡聲明方法並實現。
直到某天,由於實在過於混亂,於是決定自己試著把一些邏輯寫在一些類裡,把邏輯分出去,交給其它的類負責,這是我第一次有分類的意識。從寫下第一行代碼到這個時候,已經過去兩年多的時間了,這兩年多的時間做的項目都是小項目,簡單的Android App、簡單的網站、簡單的小遊戲(對對碰、2048)等等,沒法做一些體量更大的項目。
在初次嘗試分類之後,編程之路就順了一些,雖然項目到最後還是會混亂,不過已經能寫一些三消、跑酷、爬塔之類的遊戲了(體量不大,但是對一個大學生來說是個挑戰)。所以說,開始分類是我編程生涯的一個裡程碑。
分類不用做得非常好,只需要開始做,就可以得到非常大的回報。對於工作多年的人來說,分類應該不難,但如果是剛接觸編程不久的同學,分類也許是一個可以讓你突破瓶頸的開始。
開始分類之前,我是一個編程初學者,歷時兩年,這段期間寫出來的東西很混亂,一度覺得自己不適合做開發者,一直很沒自信。開始分類之後,我算初級開發者,從一開始,把邏輯交給靜態類的靜態方法,到之後慢慢把邏輯交給一些對象負責,編程能力在慢慢提高,做出來的項目的規模也在逐步擴大。
2.1.3 小結類可以繼承、包含屬性、包含方法、可以用來創建對象
沒有類,Unity引擎可能沒那麼好用了
分類是筆者編程生涯的一個裡程碑
2.1.4 知識地圖有了類能夠比較容易地進行面向對象編程,沒有類當然也可以做到,只不過沒有那麼容易。
什麼是面向對象編程呢?根據我的理解只有兩個字:建模。建模就是對需要實現的功能或者需要解決的問題建立業務模型。比如:要實現購買商品功能,就需要我們創建商品對象,還要把購買商品的行為歸屬到另一個對象中,這個對象有可能是顧客對象,也有可能是平臺對象,這就是非常典型的面向對象建模案例。
如果是面向過程中的購買商品會是什麼樣的呢?我們也許可以這樣考慮,購買商品前先創建一個購買商品函數,然後函數可能需要調用資料庫,那就再寫一個調用資料庫的函數交給購買商品函數調用,依次類推。
其實要區分面向對象編程與面向過程編程非常容易。面向對象編程是以對象為基礎進行思考的,而面向過程是以功能(函數)為基礎進行思考的,這就是兩者的差別。
2.2.2 面向對象與面向過程是對立的嗎?不是對立的,面向對象更擅長設計,而面向過程更擅長實現。在使用C#的時候,這兩種編程思維都會用到。
2.2.3 沒有類如何面向對象編程?面向對象編程,就需要有對象,如果沒有類,其實也可以實現面向對象編程。C語言雖然不支持類,但是也可以通過宏或者一些模擬的手段支持類和對象;Lua語言也沒有提供類功能,但是通過元表也能夠模擬類和對象。總之,面向對象編程的核心就是兩個字:建模。
2.2.4 知識地圖:2.3 我所理解的類(三)簡單說了一下面向對象和面向過程這兩個概念。現在來講一下引用類型和值類型這個話題。
2.3.1 引用類型和值類型幾乎在每一本程式語言書籍上都會談到引用類型和值類型這兩個概念,具體的引用類型和值類型相關的細節,大家可以在網上找各種文章。
這裡先給出我的理解:引用類型,是用類創建的類型就是引用類型。值類型包含基礎類型和結構體(Struct)創建出來的類型。如下:
2.3.2 定義值類型直接存儲其值,而引用類型存儲對其值的引用。
值類型和引用類型是非常重要但非常基礎的話題,所以想深入研究,網上能夠找到足夠多的文章。在這裡僅僅是強調一下重要性,具體細節就不浪費篇幅了。
2.3.3 類是引用類型記住這句話就足夠了。
2.3.4 知識地圖2.4 我所理解的類(四)前面我們聊了一些關於類的話題,如下:
類的功能
類在編程生涯中的作用
面向對象和面向過程
值類型和引用類型
除了以上這些話題,還有很多其它話題,如下:
類的訪問權限(封裝性)
類的命名
抽象類與接口(繼承性、多態性)內部類
Partial關鍵字
泛型類
等等
在這裡不會深入介紹這些內容,但接下來會簡單瀏覽一下。
2.4.2 類的訪問權限訪問權限有:Internal、Private、Public。Internal Class一般在打DLL的時候作用很大,可以控制有些類不讓用戶訪問到。Private Class用得不多,一般作為內部類存在。
如果不知道Internal是什麼作用,可以在評論區提問。
2.4.3 類的命名起名要合適,類的命名一般是名詞。
2.4.4 抽象類與接口抽象類中有的時候需要些抽象方法,抽象方法需要在子類中覆寫。實現接口可以顯式實現和隱式實現,顯式實現可以控制方法的訪問權限,這個在接口的部分會仔細講解。
2.4.5 內部類有的時候需要在類內部創建一些只需要在類內部使用的對象,這時候可以用內部類。
2.4.6 Partial關鍵字Partial可以實現將類的邏輯拆分到不同的文件。
2.4.7 泛型類類需要適配不同的類型的時候,可以用泛型類,比如單例的模板。
到此就簡單說了一些類相關的特性,只需要讀一遍有個印象即可,如果遇到不懂的部分,可以在評論區留言,也可以自行搜索。
2.4.8 知識地圖說到結構體,第一時間想到的就是:結構體是值類型。值類型具體是什麼樣的呢?
不知道大家記不記得Unity中有Vector3、Vector2這樣的向量類型,我們接觸Vector3比較多的地方是transform.position和transform.localPosition。在剛開始接觸 transform.position和transform.localPosition的時候,會對一件事情產生深刻印象。就是如果我想給一個Transform的x坐標賦值,代碼如下:
var position = transform.position; position.x = 1.0f; transform.position = position;一個簡單的賦值代碼,需要寫三行。如果覺得很麻煩,可以這樣寫:
transform.position.x = 1.0;
這樣寫會方便很多。這是因為Position的類型Vector3是Struct類型,Struct是值類型,同時Position是Transform的一個屬性(而不是成員變量),所以就必須要給整個 Position變量賦值,而不是Position變量中的某一個變量。
這就是Struct和Class最明顯的區別:作為一個屬性時,Struct無法對單一的成員變量賦值,而Class則是只要允許可以隨便賦值。
關於結構體,其實有很多細節可以進行學習,但是我們不用學那麼多。根據我自己的經驗,結構體只需要知道如下關鍵點就行了:
除了以上五點比較重要之外,在其它情況下,很少用到結構體。我從以前到現在,接觸到和結構體相關的編碼實踐也僅限於以上的五點特性。但是肯定還有很多其它的特 性,雖然也許不那麼常用,但是在這裡我們也可以簡單地過一遍。
2.5.1 其它的細節在結構聲明中,除非將欄位聲明為const或static,否則無法初始化。
結構不能聲明無參數構造函數(沒有參數的構造函數)或終結器。
結構在分配時進行複製。將結構分配給新變量時,將複製所有數據,並且對新副本所做的任何修改不會影響原始副本的數據。在處理值類型的集合(如 Dictionary)時,請務必記住這一點。
結構是值類型,不同於類,類是引用類型。
與類不同,無需使用new運算符即可對結構進行實例化。
結構可以聲明具有參數的構造函數。
一個結構無法繼承自另一個結構或類,並且它不能為類的基類。所有結構都直接繼承自ValueType,後者繼承自Object。
結構可以實現接口。
結構不能為null,並且不能向結構變量分配null,除非將變量聲明為可為空的值類型
2.5.2 知識地圖