c#學習筆記(一)

StaDark發表於2024-09-01

基礎語法

文件註釋&程式碼塊

	/// <summary>
	/// 待機
	/// </summary>

	#region 物體移動

    sq.transform.Translate(new Vector3(5,0,0));

	#endregion

字串格式化輸出

使用 $ 可進行格式化輸出

C# string 字串的前面可以加 @(稱作"逐字字串")將跳脫字元(\)當作普通字元對待

int age = 10;
string str1 = $"my age is {age}";
string str = @"C:\Windows";

型別轉換

C# 中的型別轉換可以分為兩種:隱式型別轉換顯式型別轉換(也稱為強制型別轉換)。

隱式型別轉換

隱式轉換是不需要編寫程式碼來指定的轉換,編譯器會自動進行。

隱式轉換是指將一個較小範圍的資料型別轉換為較大範圍的資料型別時,編譯器會自動完成型別轉換,這些轉換是 C# 預設的以安全方式進行的轉換, 不會導致資料丟失。

例如,從 int 到 long,從 float 到 double 等。

從小的整數型別轉換為大的整數型別,從派生類轉換為基類。將一個 byte 型別的變數賦值給 int 型別的變數,編譯器會自動將 byte 型別轉換為 int 型別,不需要顯示轉換。

byte b = 10;
int i = b; // 隱式轉換,不需要顯式轉換

int intValue = 42;
long longValue = intValue; // 隱式轉換,從 int 到 long

顯式轉換

顯式型別轉換,即強制型別轉換,需要程式設計師在程式碼中明確指定。

顯式轉換是指將一個較大範圍的資料型別轉換為較小範圍的資料型別時,或者將一個物件型別轉換為另一個物件型別時,需要使用強制型別轉換符號進行顯示轉換,強制轉換會造成資料丟失。

例如,將一個 int 型別的變數賦值給 byte 型別的變數,需要顯示轉換。

int i = 10;
byte b = (byte)i; // 顯式轉換,需要使用強制型別轉換符號

double doubleValue = 3.14;
int intValue = (int)doubleValue; // 強制從 double 到 int,資料可能損失小數部分

int intValue = 123;
string stringValue = intValue.ToString(); // 將 int 轉換為字串

C# 型別轉換方法

當然,c#也內建了許多型別轉換方法

序號 方法 & 描述
1 ToBoolean 如果可能的話,把型別轉換為布林型。
2 ToByte 把型別轉換為位元組型別。
3 ToChar 如果可能的話,把型別轉換為單個 Unicode 字元型別。
4 ToDateTime 把型別(整數或字串型別)轉換為 日期-時間 結構。
5 ToDecimal 把浮點型或整數型別轉換為十進位制型別。
6 ToDouble 把型別轉換為雙精度浮點型。
7 ToInt16 把型別轉換為 16 位整數型別。
8 ToInt32 把型別轉換為 32 位整數型別。
9 ToInt64 把型別轉換為 64 位整數型別。
10 ToSbyte 把型別轉換為有符號位元組型別。
11 ToSingle 把型別轉換為小浮點數型別。
12 ToString 把型別轉換為字串型別。
13 ToType 把型別轉換為指定型別。
14 ToUInt16 把型別轉換為 16 位無符號整數型別。
15 ToUInt32 把型別轉換為 32 位無符號整數型別。
16 ToUInt64 把型別轉換為 64 位無符號整數型別。

這些方法都定義在 System.Convert 類中,使用時需要包含 System 名稱空間。它們提供了一種安全的方式來執行型別轉換,因為它們可以處理 null值,並且會丟擲異常,如果轉換不可能進行。

例如,使用 Convert.ToInt32 方法將字串轉換為整數:

string str = "123";
int number = Convert.ToInt32(str); // 轉換成功,number為123

型別轉換方法

C# 提供了多種型別轉換方法,例如使用 Convert 類、Parse 方法和 TryParse 方法,這些方法可以幫助處理不同的資料型別之間的轉換。

使用 Convert

Convert 類提供了一組靜態方法,可以在各種基本資料型別之間進行轉換。

例項

string str = "123";
int num = Convert.ToInt32(str);

使用 Parse 方法

Parse 方法用於將字串轉換為對應的數值型別,如果轉換失敗會丟擲異常。

例項

string str = "123.45";
double d = double.Parse(str);

使用 TryParse 方法

TryParse 方法類似於 Parse,但它不會丟擲異常,而是返回一個布林值指示轉換是否成功。

例項

string str = "123.45";
double d;
bool success = **double**.TryParse(str, **out** d);

if (success) {
  Console.WriteLine("轉換成功: " + d);
} else {
  Console.WriteLine("轉換失敗");
}

C#中的引用引數傳遞

在C++中,我們需要傳遞實參時只需要使用&修飾形參即可實現引用引數傳遞。但在C#中,我們需要使用關鍵字ref來進行該操作

void test01()
{
    int a = 10;
    Add(ref a,10);
    Debug.Log(a);//20
}

int Add(ref int a, int b)
{
    return a+=b;//20
}

不同於ref關鍵字,我們也能用類似的out關鍵字來進行引用引數傳遞

out關鍵字

  • out也用於引用傳遞,允許方法修改傳遞給它的變數的值。
  • 不同於ref,呼叫方法時傳遞給out引數的變數不需要被初始化。
  • out引數通常用於方法需要返回一個以上的值,並且希望明確表示引數是輸出引數。

使用場景

  • 當你想要確保方法的呼叫者提供了一個有效的值,並且方法會修改這個值,使用ref
  • 當你想要方法的呼叫者提供一個變數,但不需要事先初始化,並且方法會提供這個變數的值,使用out

語法示例

csharpvoid MethodWithRef(ref int number) {
    number += 10; // number的原始值被修改
}

void MethodWithOut(out int number) {
    number = 10; // number被賦予一個值
}

呼叫示例

int value = 5;
MethodWithRef(ref value); // value現在是15
int anotherValue;
MethodWithOut(out anotherValue); // anotherValue現在是10

封裝

相比於C++,C#中的訪問修飾符要多出兩個型別,一個是internal,另一個是與protected結合的protected internal

  • public:所有物件都可以訪問;
  • private:物件本身在物件內部可以訪問;
  • protected:只有該類物件及其子類物件可以訪問
  • internal:同一個程式集的物件可以訪問;(可以被定義在該成員所定義的應用程式內的任何類或方法訪問)
  • protected internal:訪問限於當前程式集或派生自包含類的型別。(允許在本類,派生類或者包含該類的程式集中訪問。)

img


繼承

c#的繼承語法大體和c++類似,都是 訪問修飾符 class 子類: 父類

但是繼承後的子類如何去寫建構函式,則依靠於base

base:當前類的基類(父類)

public class Animal
{
    public int age;
    public string name;
    
    public Animal(string name, int age)
    {
        this.name = name;
        this.age = age;
    }
}

public class Dog: Animal
{
    public Dog(string name, int age):base(name, age)
    {}
    
    public void Bark()
    {
        
    }
}

多型

虛擬函式和重寫

與c++不同,c#中的重寫(覆蓋基類中的函式)需要加上關鍵字override

public class Animal
{
    /*
    虛擬函式
    1.提供預設邏輯,因為一些子類不需要重寫
    2.提供可選的邏輯(用基類base呼叫)
    */
    public virtual void Bark()
    {
     	Debug.Log("張嘴開叫");  
    }
}

public class Dog: Animal
{
    public override void Bark()
    {
        Debug.Log("張嘴狗叫");
    }
}

抽象類和抽象函式

c#中,抽象要加上關鍵字abstract,抽象函式也約等於c++中的純虛擬函式

// 抽象類
public abstract class Animal
{
    // 抽象方法,子類必須實現
    public abstract void MakeSound();

    // 非抽象方法,可以有實現
    public void Eat()
    {
        Console.WriteLine("The animal is eating.");
    }
}

// 繼承抽象類的子類,必須實現抽象方法
public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("Woof!");
    }
}

// 使用
public class Program
{
    public static void Main()
    {
        Animal myDog = new Dog(); // 可以建立抽象類的例項,但需要是其子類
        myDog.MakeSound(); // 輸出: Woof!
        myDog.Eat();       // 輸出: The animal is eating.
    }
}

里氏轉換

概念:

里氏轉換主要是:子類和父類的型別轉換

  1. 子類轉父類——隱式轉換
  2. 父類轉子類——顯示轉換(用的少,有風險,要確保型別相容)
public class Animal
{
    public virtual void MakeSound()
    {
        Console.WriteLine("Some sound");
    }
}

public class Dog: Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("Bark!");
    }
}

public class Program
{
    public static void Main()
    {
        Dog dog = new Dog;
        Animal ani = new Animal;
        doSound(dog);//子類轉父類——隱式轉換
        doBark(ani);//父類轉子類——顯示轉換
    }
    
    public void doSound(Animal animal)
    {
        Console.WriteLine("Some sound");
    }
    
    public void doBark(Animal animal)//此處約定傳遞過來的引數要麼是Animal,要麼是Animal的子類透過隱式轉換傳遞過來的
    {
        //強制轉換,會報錯
        Dog dog = (Dog)animal;
        
        //as轉換,不會報錯
        Dog dog2 = animal as Dog;
        //Cat cat = (Cat)animal;	型別不相容,會報錯
    }
}

萬物基類object

如果一個類不設定基類的話,那麼他的基類就是object,同時object下有幾個虛擬函式可以過載,這裡先重點講一下ToString()

因為萬物基類都是object,所以任何型別的物件都可以隱式轉換為object

介面

c#中的繼承是單繼承,即一個類只能繼承一個基類;但是無論是否有繼承某個類,都可以進行多個介面的繼承

介面關鍵字:interface

public interface ITalk
{
    void Talk();//實際上是抽象函式,預設為public,不需要定義訪問修飾符
}
public interface IMove
{
    void IMove();
}


namespace animal
{
    public abstract class Animal
    {
        public string name;

        public override string ToString()
        {
            return $"I'm {name}";
        }
    }
    
    public class Parrot: Animal, ITalk, IMove	//多個介面的繼承
    {
        public void Talk()	//介面不需要加關鍵字override
        {
            Console.WriteLine("Some sound");
        }
    }
}

public class Human: ITalk, IMove	//無論是否有基類
{
        public void Talk()
	{
            Console.WriteLine("Some sound");
	}
}
    
public class Robot: ITalk, IMove
{
	public void Talk()
    {
            Console.WriteLine("Some sound");
    }
}

public class Program
{
    public static void Main()
    {
        Robot robot = new Robot();
        Person person = new Person();
        Parrot parrot = new Parrot();
        Talk(robot);//隱式轉換
        Talk(person);//隱式轉換
        Talk(parrot);//隱式轉換
        
        ITalk talker = robot;//隱式轉換
        Robot robot1 = (Robot)talker//強制轉換
    }

//公共函式
public void Talk(ITalk talker)
{
    talker.Talk();
}

屬性

類似成員資料與成員函式相結合

概念

屬性由兩個訪問器組成:get訪問器和set訪問器。get訪問器用於返回屬性的值,而set訪問器用於設定屬性的值。屬性可以是隻讀的(只有get訪問器)、只寫的(只有set訪問器)或讀寫的(同時有getset訪問器)。

屬性的定義

屬性的定義通常遵循以下格式:

public class MyClass
{
    private int myField; // 私有欄位
    // 屬性
    public int MyProperty
    {
        get
        {
            return myField;
        }
        set
        {
            myField = value;
        }
    }
}    

在這個示例中,MyProperty 是一個屬性,它封裝了私有欄位 myFieldget 訪問器返回 myField 的值,set 訪問器將傳入的值(透過 value 關鍵字)賦給 myField

屬性的使用

屬性的使用與公共欄位類似,但可以在訪問時執行額外的邏輯。

MyClass obj = new MyClass();
obj.MyProperty = 42; // 呼叫 set 訪問器

int value = obj.MyProperty; // 呼叫 get 訪問器

自動實現的屬性

C# 還提供了自動實現的屬性,這種屬性在定義時不需要顯式宣告私有欄位,編譯器會自動生成一個隱藏的私有欄位。

public class Person
{
    public string Age { get; set; }
}

public class Program
{
    public static void Main()
    {
        myPerson.Age = 10;
        Console.WriteLine(myPerson.Age); // 輸出: 10
    }
}

自動實現的屬性使得程式碼更加簡潔,但在某些情況下可能需要自定義 get 和 set 訪問器的邏輯。

只讀和只寫屬性

只讀屬性只有 get 訪問器,只寫屬性只有 set 訪問器。

 public class Circle
{
    private double radius;   
    public Circle(double radius)
    {
        this.radius = radius;
    }

    public double Radius
    {
        get
        {
            return radius;
        }
    }

    public double Area
    {
        get
        {
            return Math.PI * radius * radius;
        }
    }
}    

值型別與引用型別

值型別

int a =3;
int b - a;

在b=a時,會重新開闢記憶體空間並完整的複製一次a的資料,相當於兩者無關,只是在複製的時兩者資料相等而已

主要的值型別:列舉、結構體、int、float、bool等

比較(相等/不等)時一般是判斷兩者的值是否相同

結論:值型別在賦值時都是複製值

函式呼叫的過程也是賦值,因此引數預設為值傳遞

引用型別

Dog dahuang = new Dog0;

Dog dog = dahuang;

dog=dahuang時,dog會引用dahuang的記憶體地址,相當於兩者是一個物件,只是名稱不一樣

主要的引用型別:class、interface

比較(相等/不等)時一般是判斷兩者是否引用同一物件

函式呼叫的引數型別是class時預設為引用

結論:引用型別在賦值時都是複製引用,和原先的變數是同一個物件

為什麼需要這種區別?

值型別都是輕量級資料,用完就放棄(銷燬、回收),在C#中一般過了的作用域就回收掉。但是會有傳遞的需求,因為是輕量級資料,複製成本也不高,所以採用這種方案。

相對的,引用型別一般是重量級資料,那麼它的複製成本就高。不適合採用值型別的方案。但是這也不意味著引用型別就不在銷燬,當一個物件再也沒有人引用時會被標記為“無用”狀態等待系統自動的回收,但是這個回收是有成本

class中的值型別:修改物件內的成員都要用物件名.成員名稱去做到

特殊的引用型別string

常用成員

  • Length:字串長度
  • IndexOf:A字串在B字串中第一次出現的地方
  • LastIndexOf:A字串在B字串中最後一次出現的地方
  • Replace:替換指定的字元
  • ToUpper:轉為大寫
  • ToLower:轉為小寫

字串的特殊性

首先string是引用型別,底層使用了“常量池”的技術,也就是如果存在某個字串,則不會例項化一個新的字串,而是複用之前的引用

string str1 = "ddd";
string str2 = "ddd";
str1 = "CCC"

上面程式碼中str1和str2是同一個應用,但是str1修改後,str2並不會修改,這就是string的特殊性

陣列

基礎概念
定義: 陣列是一種資料結構,用於儲存一系列相同型別的元素。

用途: 用於儲存和訪問一系列元素。

型別:

  • 陣列可以是任何基本資料型別(如整數、浮點數、字元等)或引用資料型別(如物件、陣列等)
  • 陣列中可以存放任何型別,但陣列本身是引用型別
  • 陣列中的資料如果沒有設定值,那麼會是該型別的預設值,引用就是null

陣列宣告&定義

在C#中,你可以使用以下方式宣告陣列:

int[] intArray; // 宣告一個整數陣列
string[] stringArray; // 宣告一個字串陣列

int[] intArray2 = new int[10];// 定義一個整數陣列
string[] stringArray2 = new string[10];// 定義一個字串陣列

陣列初始化

你可以使用以下方式初始化陣列:

int[] intArray = {1, 2, 3, 4, 5}; // 初始化一個整數陣列
int[] intArray = new int[] {1, 2, 3, 4, 5};

string[] stringArray = {"apple", "banana", "cherry"}; // 初始化一個字串陣列
string[] stringArray = new string[] {"apple", "banana", "cherry"};

陣列訪問

你可以使用索引來訪問陣列中的元素。索引從0開始。

int firstElement = intArray[0]; // 訪問第一個元素
string secondElement = stringArray[1]; // 訪問第二個元素

陣列長度

你可以使用Length屬性來獲取陣列的長度。

int arrayLength = intArray.Length; // 獲取陣列長度(定義長度而非有效長度)

陣列排序

你可以使用Array.Sort方法來對陣列進行排序。

Array.Sort(intArray); // 對整數陣列進行升序排序

陣列反轉

你可以使用Array.Reverse方法來反轉陣列。

Array.Reverse(intArray); // 反轉整數陣列

陣列遍歷

你可以使用for迴圈或foreach迴圈來遍歷陣列。

for (int i = 0; i < intArray.Length; i++)
{
    Console.WriteLine(intArray[i]); // 遍歷整數陣列
}

foreach (int item in intArray)
{
    Console.WriteLine(item); // 遍歷整數陣列
}

陣列排序示例

int[] intArray = {5, 3, 1, 4, 2};
Array.Sort(intArray);
foreach (int item in intArray)
{
    Console.WriteLine(item); // 輸出排序後的陣列
}

陣列反轉示例

int[] intArray = {5, 3, 1, 4, 2};
Array.Reverse(intArray);
foreach (int item in intArray)
{
    Console.WriteLine(item); // 輸出反轉後的陣列
}

交錯陣列

交錯陣列是陣列的陣列(因為陣列中可以放任何型別,所以也可以放陣列),其中每個子陣列可以有不同的長度。例如:

int[][] jaggedArray = new int[3][]; // 宣告一個包含3個子陣列的交錯陣列
jaggedArray[0] = new int[1]; // 初始化第一個子陣列

int[][] nums = new int[][]
{
    new int[] {0,1,2,3,4,5,6},
    new int[] {0,1,2,3,4},
    new int[] {0,1,2,3},
    new int[] {0,1},
};

多維陣列

多維陣列也是陣列的陣列,可以透過多個索引來訪問元素,子陣列的維度確定。例如,二維陣列的宣告和初始化如下:

int[,] multiDimensionalArray = new int[2, 3]; // 宣告一個2x3的二維陣列

int[,] nums2 = new int [2,3]
{
    {1,2,3},
    {4,5,6},
};

交錯陣列和多維陣列的區別

對比維度和儲存方式

  • 多維陣列具有固定的維度大小,即每一行或每一列的元素數量是相同的。多維陣列在記憶體中是連續儲存的,可以透過線性索引訪問元素。
  • 交錯陣列每一行可以有不同的長度,即它是由多個一維陣列組成的陣列。交錯陣列的元素是引用型別,指向各自的一維陣列,因此它們在記憶體中不是連續儲存的。

宣告和初始化差異

  • 多維陣列的宣告需要指定所有維度的大小,例如 int[3, 4] 宣告瞭一個3行4列的二維陣列。
  • 交錯陣列的宣告只需要指定第一維的長度,例如 int[][] 宣告瞭一個可以包含多個一維陣列的交錯陣列。後續的一維陣列可以在宣告後進行初始化,例如 jaggedArray[0] = new int[5]

訪問方式

  • 多維陣列透過兩個或多個索引來訪問元素,例如 array[i, j]
  • 交錯陣列透過兩個索引來訪問元素,第一個索引訪問一維陣列,第二個索引訪問該一維陣列中的元素,例如 jaggedArray[i][j]

記憶體使用和效能考量

  • 多維陣列由於其連續儲存的特點,通常在記憶體中佔用更緊湊,訪問速度較快
  • 交錯陣列由於非連續儲存的特點,可能在記憶體中佔用不如多維陣列緊湊,但它們提供了更大的靈活性,尤其是在處理不規則資料結構時。

相關文章