C# 重練筆記

終於回來惡補了。

過去靠著一本內容糟糕的Unity教學書用土法煉鋼法一路走到現在,至今為止已經有五項作品了,完成度不低,但其實自己很明白,在程式的概念上有明顯的不足,這樣下去是不會再進步的。

原本在找Unity相關的教學書,遺憾的是,不是太初學就是和3D有關,而3D並不是我的初衷。偶然在網上找到了一篇C#的基本教學,內容相當易懂,讀過以後,覺得全身充滿了力量 把過去大部分的疑惑都解決了,為了不怕忘記,決定做一次筆記。

《以下無標明參考來源的內容皆參考:》給Unity程式入門者的C#教學目錄– Delta Timer



目錄:


0. Blogger程式語法插入
1. 專有名詞
2. 進入點
3. 型別
4. string的運用
5. static靜態
6. Debug訊息
7. 運算子
8. namespace與using
9. 參考型別、實質型別
10. 多維陣列、陣列的陣列
11. enum概念
12. for迴圈
13. 類別、get與set
14. 繼承、virtual與override
15. 結構
16. abstract與interface
17. 字串插值$
18. List<T>








零、Blogger程式語法插入


在開始之前,必須先記得把程式插入到Blogger的方法。

無邊框、只有一行:

<code class="prettyprint"> ... </code>

有邊框、可換行:

<pre class="prettyprint">
    ...
</pre>


《Reference:》[Blogger] 如何在 Blogger 顯示程式碼 - Google Code Prettify






一、專有名詞


指示詞 (using)
命名空間 (namespace)
函式庫 (using System;)
靜態 (static)
進入點 (static void Main(){})
參數 (Main(string[] arg))
變數 (string text;)
結構 (struct)
類別 (class)
型別 (int, float, string)
修飾詞 (private, public, static)
區塊 ({})
列舉、枚舉(enum)







二、進入點


普通來說,任何程式都必須要有起點才可以開始運作,當進入程式以後,可以安排一個方法在程式運作時自動開始,這個當作起點的方法就稱為進入點。進入點是有命名規定的:

static void Main() { }

static int Main( string[] args ) { return 0; }






三、型別


除了在Unity常用的型別以外,C#本身還有其他的型別:

int:4byte,範圍「-2,147,483,648」 到「2,147,483,647」 的整數。(±20億)
short:2byte,範圍「-32,768」 到「32,767」 的整數。(±3萬)
long:8byte,範圍「-9,223,372,036,854,775,808」 到「9,223,372,036,854,775,807」 的整數。(±900萬兆)
sbyte:1byte,範圍「-128」 到「127」 的整數。(signed byte)
byte:1byte,範圍「0」 到「255」 的整數。
uint:4byte,範圍「0」 到「4,294,967,295」 的整數。(unsigned int)
ushortulong 同 uint 依此類推。

float:4byte,範圍「1.5 * 10^(-45)」到「 3.4 * 10^(38)」的7位數精確度。
double:8byte,範圍「5.0 * 10^(-324)」到「 1.7 * 10^(308)」的15位數精確度。

char:2byte,一個字元。( Ex: 'A', 'b', '@')






四、string的運用


\n換行(new line)。
\r歸位(return),回到該行字的開頭。
\tTab空白。
\\反斜線符號「\」。
\'單引號符號「'」。
\"雙引號符號「'"」。
\0NULL字元。



「@」逐字字串倘若字串使用許多特殊符號的轉換(\\, \', \'')造成閱讀上的困難,可以在字串前方輸入「@」,將可以不用轉換特殊符號。

string msg = "C:\\Users\\user\\Documents";

string msg = @"C:\Users\user\Documents";



「string.Format()」格式字串此函式可以在字串插入任意參數,使用方法如下:

string.Format( "Today is {0}\nIt's {1}", now.ToShortDateString(), now.ToShortTimeString())

在VisulStuio的開發環境下,游標移置可查看功能:


params表示可以放置任意數量的型別。






五、static靜態


靜態是一種能在public或private等修飾詞後方新增的修飾詞,可以讓該類別或方法不用實體就可以被外部共用。

public class something{
    public static count = 1;

    public static void StaticPrint(){
        Console.WriteLine("static print"));
    {

    public void Print(){
        Console.WriteLine(string.Format("count:{0}", count));
    {
}

class Test{
    static void Main(){
        something A = new something();
        something B = new something();
        A.Print();
        something.StaticPrint(); // 不用宣告就可使用
        B.count = 2; // 用B改動static
        A.Print(); // count:2 (B改動後全部被改動
    {
}

值被改動時,所有使用該類別的實體都會受影響,所謂牽一髮動全身。簡單來說,static歸類別所有,當使用靜態宣告的方法時,是從類別呼叫的,與實體無關。

static的好處是,不需要太多繁複的過程就可以使用,但由於常駐的特性,會消耗記憶體。

另外,static也可以運用在類別上面,如果類別使用了靜態,則類別內的方法皆須使用靜態。


《Reference 1:》C# 學習筆記 - static 到底是神馬鬼東西? | sqz777 der 技術小本本
《Reference 2:》一秒看破 static | Mr.Wei 的程式筆記






六、Debug訊息


常常使用的「print()」事實上是「Debug.Log()」的短版,只能在繼承「MonoBehaviour」的類別中使用。

一般在C#開發環境時,使用「Consle.Write()」或有換行功能的「Consle.WriteLine()」來進行Debug,其中函式也有「string.Format()」的功能。

Console.WriteLine( "Today is {0}\nIt's {1}", now.ToShortDateString(), now.ToShortTimeString() );






七、運算子


除法「/」在C#做除法一律無條件捨去。(Ex: 9 / 5 = 1)

求餘數「%」在C#求餘數不用「mod」而用「%」。

算法指派「+=」除「+=」外,「-=」、「*=」、「/=」、「%=」皆可使用。

遞增減運算「++, --」分為前置遞增/減(++x, --x)、後置遞增減(x++, x--),前置為運算之前先對參數做遞增減的動作,後置則為運算後再對參數所做增減動作。

位移運算「x<<y, x>>y」將x位元向左(<<)或向右(>>)位移y位,溢位部分消除,空缺部分補足0。
9 << 2 = 40 (0000 1010 → 0010 1000)
9 >> 2 = 2 (0000 1010 → 0000 0010)

邏輯運算「&. ^, |」用於計算出整數位元或布林的AND(&)、XOR(^)、OR(|)的結果,也可以使用再算法指派上面(&=, ^=, |=)。

次方「Math.pow(x,y)」x乘上y的次方數。在C#貌似不能使用「^」當次方運算,待求證。

條件邏輯「&&, ||」程式會先判斷左方結果再判斷右方,如果在左邊就得到成立(||:ture)或不成立(&&:false),會直接跳過右方的判斷進行回傳。

等號指派「=」一行代碼中可以疊用,例如:「x = y = z = 0;」。



優先度:
一元運算子:+x, -x, !x, ~x, ++x, --x
乘法運算子:*, /, %
加法運算子:+, –
移位運算子:<<, >>
關係運算子:>, <. >=, <=
等號運算子:==, !=
邏輯AND運算子:&
邏輯XOR運算子:^
邏輯OR運算子:|
條件AND運算子:&&
條件OR運算子:||
指派運算子:=, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=






八、namespace與using


「namespace」的作用就像檔案總管的資料夾一樣,同一資料夾中相同檔名的檔案不能重複,但只要各自分裝到不同的資料夾就能允許。

命名空間(namespace)一般來說,是可以任意呼叫的,也就是說,如果需要從外部呼叫命名空間內部的方法是可行的。

using System;

namespace first_name{
   class same_class{
      public void function(){
        Console.WriteLine("first_name same_class")
      }
   }
}

namespace second_name{
   class same_class{
      public void function(){
         Console.WriteLine("second_name same_class");
      }
   }
} 

class Test{
   static void Main(string[] args){
      first_name.same_class test1 = new first_space.same_class();
      second_name.same_class test2 = new second_space.same_class();
      test1.function();
      test2.function();
      Console.ReadKey();
   }
}

using是用來省略命名空間的,使用using獲取函式庫事實上只是省去命名空間的輸入而已,例如原本「System.Console.WriteLine ("Hello word");」這行代碼,只要宣告了「using System」做指示;,就可以直接使用「Console.WriteLine ("Hello word");」。

using System;
using first_space;
using second_space;

namespace first_name{
   class a_class{
      public void function(){
         Console.WriteLine("first_name a_class");
      }
   }
}

namespace second_name{
   class b_class{
      public void function(){
         Console.WriteLine("second_name b_class");
      }
   }
} 

class Test{
   static void Main(string[] args){
      a_class test1 = new a_class();
      b_class test2 = new b_class();
      test1.function();
      test2.function();
      Console.ReadKey();
   }
}

另外,在命名空間底下也是可以宣布命名空間的:

using System;
using big_name;
using big_name.small_name;

namespace big_name{
    public class big_class{
        static void Main(){
            Console.WriteLine("big_name big_class");
            small_class.function()
            Console.ReadKey();
        }
    }

    namespace small_name{
        public class small_class{
            public static void function(){
                Console.WriteLine("small_name small_class");
            }
        }
    }
}

補充一個題外話,「{}」大括號(區塊)即使沒有宣告也可以在任意位置使用。


《Reference:》C# 命名空间(Namespace) | 菜鸟教程






九、參考型別、實質型別


型別(int、string、class...etc)依屬性又分為參考與實質。

實質型別是指當有人需要使用它時,會建立一個暫時的值做運算,除了使用「=」運算元來定義數值,基本上實質型別是不會因為被當作其他參數而改變,如int、float、bool等。

參考型別是傳遞資料時,直接傳遞資料位址,而不產生新的暫用資料來做運算,這也意味著當被作為參數借用時,更改的值會直接寫入位址,陣列(int[]...)就是參考型別的例子。

「string(字串)」也是參考型別,但是在使用上卻和實質型別無異,這裡要特別注意。

參考型別的預設值為「null」,表示沒有指派任何位置保存資料。「null」只能使用在參考型別。

using System;

class Program {
    static void Main() {
        int a = 10;
        int b = EqualInt(a);
        Console.WriteLine("a = {0}, b = {1}", a, b); // Output: a = 10, b = 11
        Console.WriteLine("a " + (a == b ? "==" : "!=") + " b"); // Output: a != b
        Console.WriteLine();

        string s1 = "a";
        string s2 = EqualString( s1 ); 
        Console.WriteLine("s1 = {0}, s2 = {1}", s1, s2); // Output: s1 = "a", s2 = "aa"
        Console.WriteLine("s1 " + ( s1 == s2 ? "==" : "!=") + " s2"); // Output: s1 != s2
        Console.WriteLine();

        int[] a1 = new int[] { 1, 2, 3 };
        int[] a2 = EqualIntArray( a1 );
        WriteArray("a1", a1); // Output: a1 = [2, 3, 4]
        WriteArray("a2", a2); // Output: a2 = [2, 3, 4]
        Console.WriteLine("a1 " + ( a1 == a2 ? "==" : "!=" ) + " a2"); // Output: a1 == a2
        Console.WriteLine();
    }

    int EqualInt( int input ) {
        input = input + 1;
        return input;
    }

    string EqualString( string input ) {
        input = input + input;
        return input;
    }

    int[] EqualArray( int[] input ) {
        for ( int i = 0; i < input.Length; i++ ) {
            input[ i ] += 1;
        }
        return input;
    }

    void WriteArray( string input_1, int[] input_2 ) {
        Console.Write( input_1 + " = [" );
        for ( int i = 0; i < input_2.Length; i++ ) {
            Console.Write( input_2[ i ] + ", " );
            if ( i == input_2.Length - 1 ) Console.WriteLine( "]" );
        }
    }
}






十、多維陣列、陣列的陣列


多維陣列(int[,]...etc)定義的長度對於每個陣列都是固定的,例如int[3,4,2]表示3個二維陣列中有4個長度為2的一維陣列。如果想要不固定,可以透過將陣列宣告陣列的方式,例如:

int[][] a = new int[3][]; // 宣告3個「int[]」的陣列
a[0] = new int[5]; // a的第一個「int[]」給它「int[5]」
a[1] = new int[2]; // 以此類推
a[2] = new int[4];






十一、enum概念


稱列舉也稱枚舉,之前的Unity複習筆記有提過,這邊再說明一下。

枚舉其實就只是一個往上遞增的整數宣告,每一個定義的名稱下都有一個整數,換句話說,就是讓遞增的整數標記名字。

using System;

enum Season { Spring = 1, Summer, Autumn = 7, winter } //Spring沒有指定數值的話預設是0

class Test {
    static void Main() {
        Console.WriteLine( (int)Season.Spring ); // 1
        Console.WriteLine( (int)Season.Summer ); // 2
        Console.WriteLine( (int)Season.Autumn ); // 7
        Console.WriteLine( (int)Season.Winter ); // 8
        Console.WriteLine( (Season)1 ); // Spring
        Console.WriteLine( (Season)2 ); // Summer
        Console.WriteLine( (Season)7 ); // Autumn
        Console.WriteLine( (Season)8 ); // Winter
        Console.WriteLine( (Season)11 ); // 11
    }
}






十二、for迴圈


for(<初始設定> <持續條件> <迭代式>){
    <執行內容>
}

for迴圈的執行順序如下:「初始設定」「持續條件」→「執行內容」→「迭代式」→ 回到「持續條件」...

初始設定:在所有執行開始之前,執行唯一一次的動作。
持續條件:給一個bool值,如果是ture繼續下個動作,false則結束for迴圈。沒有寫的話預設是ture。
迭代式:結束「執行內容」後執行的動作,執行後回到「持續條件」。

以上四個內容可以都不寫,例如:「for( ; ; ){ }」。

在這裡補充一下,「break」和「continue」僅用在「for」、「while」等迴圈裡面,「if」所使用的並非用在自己本身而是作用在「if」存在的迴圈之中,和「if」的區塊無關。

「switch」也有「break」但無「continue」。






十三、類別、get與set


public class Pet {
    private string name; //欄位(型別)
    
    public Pet( string name ) { //建構函式
        this.name = name;
    }
    public Pet() { //建構函式
        name = "no name"
    }
    
    public string Name { //用來讀寫私有的欄位
        get{ return name; }
        set{ name = value; }
    }
}

class Test { //類別修飾詞預設為public
    static void Main() {        
        Pet pet1 = new Pet( "Doge" );
        Pet pet2 = new Pet( "Mew" );
        pet2.Name = "Momo";
        Pet pet3 = new Pet();
        Console.WriteLite(string.Format("pet1:{0} pet2:{1} pet3:{2}", 
        pet1.GetName(), pet2.GetName(), pet3.GetName()));
        // pet1:Doge pet2:Momo pet3:no name
    }    
}

類別(class)必須是公開(public),若無修飾詞則預設「public」;欄位則是私有(private),無修飾詞則預設「private」。

「get」與「set」可以用在宣告型別上,當讀取該型別時,回傳「get」的內容;資料存入時,執行「set」的內容。這兩個方法通常被用在存取類別的私有欄位(型別)上。

如果只是要單純存取,可以直接用「public int a { get; set; }」,上方的例子就可以用這個方法取代。使用此方法是為了維護系統的可讀性,實際上效果與沒有區塊的狀態一樣,如「public int a;」。






十四、繼承、virtual與override


只要在類別宣告的名稱後方加上「:」並輸入父類別的名稱即可繼承。例如「class Dog : Animal」。

繼承的類別可以使用父類別「public」及「protect」方法或欄位,而「protect」修飾詞就是用來讓繼承的子類別使用的,既沒有「public」的對外開放,也沒有「private」的不讓子類別擁有。

子類別、父類別換個說法,可以稱作「衍生類別 (子類別)」與「基底類別 (父類別)」。

「virtual (虛擬)」與「override (覆寫)」的修飾詞是一對的,「virtual」在父類別的方法上,子類別繼承父類別可以用「override」的同名方法覆寫「virtual」宣告的方法。

class A {
    public virtual void Print() {
        Console.WriteLine( "print by A" );
    }
}

class B : A { // B繼承A
    public override void Print() { // 覆寫A的功能
        Console.WriteLine( "print by B" );
    }
}

class C : A { // C繼承A

}

class Test {
    static void Main(){
        A a = new A();
        B b = new B();
        C c = new C();
        a.Print(); // print by A
        b.Print(); // print by B
        c.Print(); // print by A (如果沒有覆寫也會繼承有virtual的方法
}

補充一下,「override」也可以覆寫「abstract」,下面將會提到。






十五、結構


結構是一種實質型別,像是int、float等這種輕量的資料都是結構,而類別就是另一種的參考型別。

結構和類別的建立方法類似,但有幾個不同點:

一、不能繼承。
二、建構函式必須要有參數,並且內容必須把所有的欄位指派完成。

public struct Vector3 {
    public float x;
    public float y;
    public float z;

    public Vector3( float x, float y, float z ) { //必須要有參數
        this.x = x;
        this.y = y;
        this.z = z; //必須指派所有欄位
    }
}






十六、abstract與interface


「abstract (抽象)」這個修飾詞,只能用在「類別」及「有abstract類別裡的方法」上,意指此類別無法被實體化,通常被類別繼承當作一種模板(規範)使用。

「interface (介面)」和「abstract」的作用雷同,更直白的表示是作為一個模板用的,繼承的類別都必須宣告定義好的方法。

abstract class ABS{
    abstract public void function(); // 宣告的無實作方法必須有abstract修飾詞
    public void function_2() { } // 可以實作非abstract方法
    int count; // 可以宣告欄位

    //public void function(); 錯誤:無實作的方法必須有abstract修飾詞
}

class A : ABS{
    public override void function(){ //繼承abstract的類別必須用override實作方法
        throw new NotImplementedException();
    }
}

interface ITF{
    void fucntion(); // 只能宣告沒有實作的方法

    //void function() { } 錯誤:方法不能實作
    //public void function(); 錯誤:本身是public但不能輸入修飾詞
    //int count; 錯誤:不能宣告欄位
}

class B : ITF{
    public void function(){ // 繼承interface必須實作宣告的方法
        throw new NotImplementedException();
    }
}

補充一下,「throw <Exception>」是用來Debug用的,可以根據不同的需求使用不同的報錯訊息(Exception)。


《Reference:》interface, abstract, virtual 差異| 小e的心得整理房- 點部落






十七、字串插值$


為「String.Fromat()」的簡易寫法,用法如下 :

string fruit = "apples";
int count = 2;
string msg1 = String.Format("you had {0} {1}", count, fruit);
string msg2 = $"you had {count} {fruit}";
Console.WriteLine(msg1);
Console.WriteLine(msg2); //msg1 = msg2

補充一下,「String.Fromat()」可以修改大括號的內容來改變格式,例如「{0:000}」或「{0:D3}」表示會補足0到三位數等等,而字串插值也可以使用這個方法,例如「{count:000}」。


《Reference:》C# 6 的三個新的表示式- Huan-Lin 學習筆記






十八、List<T>


可以當作不用宣告長度的陣列(Array),「<T>」表示內容可以填入任意類別或型別,例如「List<string>」、「List<int>」、「List<Animal> (類別)」等等,用法如下:

List StringList = new List(); // 建立新的List
StringList.Add("no.1"); // 新增內容的方法
StringList.Add("no.3"); // 繼續新增的內容會往後遞補
StringList.Add("no.2");
StringList.Sort(); // 由小到大排序
foreach (var Show in StringList){ // 依順序執行List的內容
    Console.WriteLine(Show);
}

StringList.Remove("no.2"); // = StringList.RemoveAt(1); (刪除後後方資料會遞補
StringList.Insert(1, "no.4"); // 插入內容的方法
Console.WriteLine(StringList[1]);


《Reference:》[C#] List - 門外漢的筆記






沒有留言:

張貼留言