C#學習筆記--物件導向三大特徵

2023-10-12 15:01:32

C#核心

物件導向--封裝

用程式來抽象現實世界,(萬物皆物件)來程式設計實現功能。

三大特性:封裝、繼承、多型。

類與物件

宣告位置:namespace

樣式:class 類名{}

命名:帕斯卡命名法(首字母大寫)

範例化物件:根據類來新建一個物件。Person p=new Person();

成員變數

  1. 宣告在類語句塊中
  2. 用來描述物件的特徵
  3. 可以是任意變數型別
  4. 數量不做限制
  5. 是否賦值根據需求決定
enum E_SexType
{
    Man,
    Woman
}
struct Position{}//位置結構體
class Pet{}//寵物類
//類中的成員變數
class Person
{
    public string name="TonyChang";//區別於結構體--可以預設賦初始值
    public int age=21;
    public E_SexType sex;
    public Person bestFriend;//區別於結構體---類中可以有同類的成員型別(本質是因為類屬於參照型別,但不可以範例化,防止反覆new,陷入死迴圈)
    public Position pos;
    public Pet pet;
}

成員型別的預設值:

值型別的:數位的為0,bool型別的false

參照型別:null

檢視(int型別)預設值:default(int)

補充:class屬於參照型別,其中的值型別也放置在中。

成員方法

  1. 宣告在類語句塊中
  2. 用來描述物件行為
  3. 其返回值引數不做限制
  4. 數量不做限制
  5. 帕斯卡命名法(首字母大寫)

成員方法只有在範例化之後才可以使用呼叫。具體的一個物件的行為(方法),必須具體的物件呼叫。

//成員方法
class Person
{
    public string name;
    public int age;
    public void Speak()
    {
        Console.WriteLine("你好!");
    }    
}

//成員方法的使用
Person p=new Person;
p.Speak();

建構函式和解構函式

預設有一個無參建構函式,而類中可以允許自己宣告無參建構函式,而結構體不行。

一旦有自定義的建構函式,預設的無參建構函式則失效!

建構函式:

  1. public修飾
  2. 無返回值,名字和類名相同
class Person
{
    public string name;
    public int age;
    //建構函式
    public Person()
    {
        name="TonyChang";
        age=21;
    }
    //此時先呼叫age引數的建構函式 然後再呼叫兩個引數的建構函式
    public Person(string name,int age):this(age)
    {
        this.name=name;
        this.age=age;
    }
    public Person(string name)
    {
       this.name=name;
    }
    public Person(int age)
    {
        this.age=age;
    }
}

特殊的建構函式,在呼叫該函數之前先呼叫this的無參建構函式。

public Person(int age):this()
{
this.age=age;
}

解構函式:

由於C#中有自動的垃圾回收機制,一般不使用解構函式。

解構函式是當垃圾真正被回收時候才會呼叫。

~Person(){}//解構函式

成員屬性:

用於保護成員變數,為成員屬性的獲取和賦值新增邏輯處理。

//成員屬性 帕斯卡命名法
class Person
{
    private string name;
    public string Name
    {
        get{
            return name;
        }
        set{
            name=value;
        }
    }
    private int age;
    public int Age
    {
        //不可以獲得年齡(或者刪除set設定則可表明也無法獲取age)
        private get=>age;
        //可以設定年齡
        set
        {
            age=value;
        }
    }
    //額外:
    //自動成員屬性 (對於沒有特殊需要的成員)
    public float Height
    {
        get;
        set;
    }
}

索引器

可以讓物件像陣列一樣通過索引來存取其中的元素。

注意:結構體中也支援索引器。

//索引器
class Person
{
    private string name;
    private int age;
    private Person[] friends;
    private int[,] arry;
    //索引器
    public Person this[int index]
    {
        get
        {
            return friends[index];
        }
        set
        {
            //此處可以寫一些控制邏輯
            friends[index]=value;
        }
    }
    //索引器的過載
    public int this[int i,int j]
    {
        get 
        {
            return array[i,j];
        }
        set
        {
            array[i,j]=value;
        }
    }
}
//使用
Person p=new Person();
p[0]=new Person();//可以像陣列一樣進行存取

靜態成員

static修飾的成員變數/方法為靜態成員。

靜態成員歸屬為類,即不用初始化就可以使用的類的成員。

靜態成員函數中不可以使用非靜態成員,非靜態成員函數中可以使用靜態成員函數。

本質:在程式執行開始時,檢查到靜態成員,會再特定的區域為其開闢記憶體空間來儲存。所以說靜態成員與程式共生死。因為生命週期不同,所以靜態成員函數中不可以使用非靜態成員。

使用:全域性性,穩定不變的常數。例如固定的數值 Π,重力加速度g等包括固定的計算方法,可以供全域性成員來存取使用。但是靜態過多會佔用記憶體,引發GC。

常數與靜態成員

相同點:都可以通過類名點來使用

不同點:

  1. const 必須初始化,不能修改,而static可以
  2. const只能修飾變數,而static可以修飾很多
  3. const一定是寫在存取修飾符的後面,static則無此要求

靜態類與靜態建構函式

用 static修飾的類為靜態類,往往來作為工具類。例如System中的Console類。只能包含靜態成員,不能範例化。

靜態建構函式 :在靜態建構函式中初始化靜態成員變數。

  1. 靜態類和普通類中均可以有
  2. 不能使用存取修飾符
  3. 不能有引數
  4. 只會自動呼叫一次
//靜態建構函式
static class Test
{
    public static int testInt=100;
    public static float testFloat=20.5f;
    static Test()
    {
        //靜態建構函式
        Console.WriteLine("自動呼叫了!");
    }
}
class NormalTest
{
    public static int i=5;
    //首次使用靜態成員時候 自動呼叫一次
    //靜態成員函數
    static NormalTest()
    {
        Console.WriteLine("靜態建構函式");
    }
    public NormalTest()
    {
        Console.WriteLine("非靜態成員函數");
    }
}

拓展方法

拓展方法為現有的非靜態變數型別新增新方法。

作用:

  1. 提升程式的拓展性
  2. 不需要再對物件中重新寫方法
  3. 不需要繼承來新增方法
  4. 為別人封裝的型別新增額外的方法

特點:

  1. 一定寫在靜態類中
  2. 一定是個靜態函數
  3. 第一個引數為拓展目標(為誰而拓展)
  4. 第一個引數用this修飾
//拓展方法
static class expendTool
{
    //為int新增拓展方法
    public static void SpeakValue(this int value)
    {
        Console.WriteLine("這是int的拓展方法,int的數值為{0}",value);
    }
    //為string拓展方法
    public static void SpeakStringInfo(this string str,string name,string info)
    {
         Console.WriteLine("這是string的拓展方法,string的數值為{0},該拓展方法由{1}編寫,拓展內容為{2}",str,name,info);
    }
}
class Program
{
    static void Main(string[] args)
    {
        int i = 5;
        i.SpeakValue();
        string ss = "原始字串";
        ss.SpeakStringInfo("TonyChang", "附加字串");
    }
}

注意:如果拓展方法名稱與自身現有的方法名稱相同,則只會呼叫自身的方法,不會呼叫拓展的方法。

運運算元過載

關鍵字 operator

特點:1. 一定是一個公共的靜態成員方法

  1. 返回值寫在operator前
  2. 邏輯處理自定義

作用:可以使自定義的資料型別來完成後相同意義的運算。

注意:

  1. 條件運運算元要成對實現(有>必須有<)
  2. 一個符號可以有多個過載
  3. 不能使用ref與out
//運運算元過載
class Point
{
    public int x;
    public int y;
    public Point(int x,int y)
    {
        this.x=x;
        this.y=y;
    }
    
    //過載+運運算元
    //參數列中必須要有自己的類別出現
    public static Point operator +(Point p1,Point p2)
    {
        Point sum=new Point();
        sum.x=p1.x+p2.x;
        sum.y=p1.y+p2.y;
        return sum;
    }
}
class Program
{
    static void Main(string[] args)
    {
        Point p1=new Point(1,1);
        Point p2=new Point(2,2);
        Point p3=P1+p2;
        Console.WriteLine(p3.x);
    }
}

補充:大部運運算元可以過載,邏輯運運算元中只可以允許過載 邏輯非!

不能過載的運運算元有:

&& || 索引符[] 強制轉換符號() 特殊運運算元 點. 三目運運算元?:

*內部類和分部類(瞭解)

//內部類
class Person
{
    public class Body
    {
        class Arm
        {
            
        }
    }
}
class Program
{
    static void Main(string[] args)
    {
       Person.Body body=new Person.Body();
    }
}
//分佈類
//分佈類可以分佈在不同的指令碼檔案中
//分佈類的存取修飾符要一致
partial class Student
{
    public string name;
    public bool sex;
    partial void Speak();
}
partial class Student
{
    public int age;
    public string stuId;
    partial void Speak()
    {
        Console.WriteLine("分佈方法的具體實現");
    }
}

垃圾回收機制

垃圾回收,英文簡稱GC(Garbage Collector)

垃圾回收過程:遍歷堆(Heap)上的動態分配的所有物件

通過識別它們是否被參照來確定其是否是垃圾。垃圾是指沒有參照所指引的物件、變數,需要被回收釋放掉佔用的記憶體空間。

垃圾回收演演算法:

參照計數、標記清除、標記整理、複製集合。

注意:垃圾回收只回收heap堆上的 棧中的記憶體由系統管理

回收機制: 三代記憶體

0代記憶體 1代記憶體 2代記憶體

  1. 每一代記憶體滿掉之後便會清理垃圾

  2. 高代連鎖:1代清理會連帶0代清理,2代清理連帶0代和1代

  3. 清理完垃圾之後,非垃圾內容搬遷到下一代中(0代將非垃圾轉移到1代記憶體,

    1代記憶體將非垃圾轉移到2代記憶體)所以2代記憶體儲存的較為老的物件範例,還包括大的物件

    一般是85kb以上的物件

  4. 0代1代的讀取速度要高於1代,分配記憶體位置優先0代>1代>2代

手動GC:

GC.Collect();

一般在場景載入時候進行GC。


物件導向--繼承

繼承者(子類)繼承父類別(基礎類別、超類)的特性,同時也可以有自己獨特的方法性質。

只能單繼承。子類只能由一個父類別。

繼承特性:單根性、傳遞性。

//繼承
//老師類
class Teacher
{
    //姓名
    public string name;
    //職工號
    protected int number;
    //介紹名字
    public void SpeakName()
    {
        number = 10;
        Console.WriteLine(name);
    }
} 
//教學老師繼承老師類
class TeachingTeacher : Teacher
{
    //科目
    public string subject;
    //介紹科目
    public void SpeakSubject()
    {
        number = 11;
        Console.WriteLine(subject + "老師");
    }
}
//語文老師繼承教學老師類
class ChineseTeacher:TeachingTeacher
{
    public void Skill()
    {
        Console.WriteLine("一行白鷺上青天");
    }
}
 class Program
{
    static void Main(string[] args)
    {
        TeachingTeacher tt = new TeachingTeacher();
        tt.name = "汪老師";
        //tt.number = 1;
        tt.SpeakName();

        tt.subject = "Unity";
        tt.SpeakSubject();

        ChineseTeacher ct = new ChineseTeacher();
        ct.name = "張老師";
        //ct.number = 2;
        ct.subject = "語文";
        ct.SpeakName();
        ct.SpeakSubject();
        ct.Skill();
    }
}

里氏替換原則

父類別容器裝在子類物件。(任何父類別出現的地方,子類都可以替代)

class GameObject
{

}
class Player:GameObject
{
    public void PlayerAtk()
    {
        Console.WriteLine("玩家攻擊");
    }
}

class Monster:GameObject
{
    public void MonsterAtk()
    {
        Console.WriteLine("怪物攻擊");
    }
}

class Boss:GameObject
{
    public void BossAtk()
    {
        Console.WriteLine("Boss攻擊");
    }
}
class Program
{
    static void Main(string[] args)
    {
        //里氏替換原則
        Gameobjet player=new Player();
        Gameobjet monster=new Monster();
        Gameobjet boss=new Boss();
        //is 和 as
        if(player is Player)
        {
            (player as Player).PlayerAtk();
        }
    }
}

is和as

is是判斷一個物件是否為指定型別物件,返回值為true則為真,不是則為false

as用來將一個物件轉換為指定型別物件,返回值為指定型別物件,若轉換失敗則返回null

繼承中的建構函式

子類建構函式呼叫之前,先執行父類別的建構函式。(爺爺-->父親-->子類)

所以要保證父類別的建構函式(尤其為無參建構函式)

  1. 保證父類別的無參建構函式
  2. 通過base呼叫指定的有參建構函式
//繼承中的建構函式
class Father
{
    //父類別的無參建構函式很重要!
    public Father()
    {
        
    }
    public Father(int i)
    {
        Console.WriteLine("Father的有參構造");
    }
}
class Son:Father
{
    public Son(int i):base(i)
    {
        //建構函式
    }
}

萬物之父--裝箱和拆箱

object 是所有型別的基礎類別,

作用:

  1. 可以利用里氏替換原則,用父類別裝子類
  2. 可以用來表示不確定型別,作為函數的引數型別

裝箱:

用object存值型別。本該在棧中數值轉移到堆上

拆箱

將object轉換為值型別,將堆上的值型別轉移到棧上(配合 is和as 使用)

優點:統一物件型別(里氏替換原則),方便對不同型別物件數值的管理

缺點:消耗效能,

//裝箱拆箱
int i=5;
object obj=i;//裝箱
i=(int)obj;//拆箱

*密封類(瞭解)

使用sealed關鍵字修飾的類,不可以被派生。(結紮了!)

物件導向--多型

V: virtual(虛擬函式)

O: override(重寫)

B: base(父類別)

讓繼承同一父類別的子類們在執行相同方法有不同的表現與狀態。

就是說,繼承是一脈相承父類別的品質,而多型是由自己的個性,儘管做的和父輩的事情相同。

解決的問題:

class Father
{
    public void SpeakName()
    {
        Console.WriteLine("Father的方法");
    }
}
class Son:Father
{
    public new void SpeakName()
    {
        Console.WriteLine("Son的方法");
    }
}
class Program
{
    static void Main(string[] args)
    {
        #region 解決的問題
        Father f = new Son();
        f.SpeakName();//呼叫的是父親的方法
        (f as Son).SpeakName();//呼叫的是兒子的方法
        #endregion
    }
}

使用多型來保證(繼承類)一個類方法的獨立性

class GameObject
{
    public string name;
    public GameObject(string name)
    {
        this.name = name;
    }

    //虛擬函式 可以被子類重寫
    public virtual void Atk()
    {
        Console.WriteLine("遊戲物件進行攻擊");
    }
}

class Player:GameObject
{
    public Player(string name):base(name)
    {

    }

    //重寫虛擬函式
    public override void Atk()
    {
        //base的作用
        //代表父類別 可以通過base來保留父類別的行為
        base.Atk();
        Console.WriteLine("玩家物件進行攻擊");
    }
}

抽象類與抽象方法

抽象類不可以被範例化

abstract class Thing{

public string name;

}

抽象方法:沒有方法體的純虛方法,繼承的子類必須實現純虛方法。(子類必須重寫該方法,子類的子類不必強制實行,但也可以繼續重寫。)

抽象方法與virtual(虛擬函式)方法區別:

  1. 抽象方法只能在抽象類中出現,沒有方法體,子類必須重寫實現
  2. 虛擬函式則有方法體,可在普通類中出現,由子類選擇性的實現
abstract class Fruits
{
    public string name;

    //抽象方法 是一定不能有函數體的
    public abstract void Bad();

    public virtual void Test()
    {
        //可以選擇是否寫邏輯
    }
}
class Apple : Fruits
{
    public override void Bad()
    {

    }
    //虛方法是可以由我們子類選擇性來實現的
    //抽象方法必須要實現
}

介面(重要)

概念:介面是行為的抽象規範

關鍵字:interface

宣告規範:

  1. 不能包含成員變數
  2. 只能包含方法、屬性、索引器、事件
  3. 成員不能被實現
  4. 成員可以不用寫存取修飾符,預設為public,不能是private
  5. 介面不能繼承類,但是可以繼承另一個介面

使用規範:

  1. 類可以繼承多個介面
  2. 類繼承介面,必須實現介面中所有成員

特點:

  1. 和類的宣告相似
  2. 介面就是用來繼承的
  3. 介面不能被範例化,可以作為容器儲存物件(里氏替換原則,父類別裝子類)

介面是抽象行為的」基礎類別「

//介面的宣告
//命名規範 I+帕斯卡命名法
interface IFly
{
    void Fly();//方法
    string Name//屬性
    {
        get;
        set;
    }
    int this[int index]//索引器
    {
        get;
        set;
    }
    event Action doSomthing;//事件委託
}

介面的使用---類的繼承

  1. 一個類只能繼承一個基礎類別,但是可以繼承多個介面
  2. 繼承介面之後,必須實現其中的內容
//介面的使用
class Animal
{
    
}
class Person:Animal,IFly
{
    //實現介面方法也可以加virtual來實現
    public virtual void Fly()
    {
        
    }
    public string Name
    {
        set;
        get;
    }
    public int this[int index]
    {
        get
        {
            return 0;
        }
        set
        {
            
        }
    }
    public event Action doSomething;
}

介面的使用---介面的繼承

介面繼承基礎類別介面之後,不需要實現介面中的內容(抽象繼承抽象,還是抽象)

等到最後類來具體實現

//介面繼承介面
interface IWalk
{
    void Walk();
}
interface IMove:IFly,IMove
{
    
}
//必須實現所有相關的
//繼承來的抽象內容(介面,介面的父介面中的成員)
class Test:IMove
{
    
       public int this[int index] { 
           get => throw new NotImplementedException(); 
           set => throw new NotImplementedException(); 
       }

        public string Name { 
            get => throw new NotImplementedException(); 
            set => throw new NotImplementedException();
        }

        public event Action doSomthing;

        public void Fly()
        {
            throw new NotImplementedException();
        }

        public void Walk()
        {
            throw new NotImplementedException();
        }
}

顯示實現介面

//介面的使用--當作容器 父類別裝子類
 interface IAtk
{
    void Atk();
}

interface ISuperAtk
{
    void Atk();
}
//顯示實現介面
class Player : IAtk, ISuperAtk
{
    //遇到相同方法名字
    //顯示實現介面 就是用 介面名.行為名 去實現
    void IAtk.Atk()
    {

    }

    void ISuperAtk.Atk()
    {

    }

    public void Atk()
    {

    }
}
class Progarm
{
    static void Main(string[] args)
    {
        IFly f = new Person();
		//里氏替換原則
        IMove im = new Test();
        IFly ifly = new Test();
        IWalk iw = new Test();

        IAtk ia = new Player();
        ISuperAtk isa = new Player();
        ia.Atk();
        isa.Atk();

        Player p = new Player();
        (p as IAtk).Atk();//IAtk的
        (p as ISuperAtk).Atk();//ISuperAtk的Atk
         p.Atk();//自己的Atk
    }
}

*密封方法(瞭解)

sealed 修飾的重寫方法,子類不會被重寫方法


其它關聯知識點

名稱空間namespace

  1. 名稱空間是個工具包,用來管理類
  2. 不同名稱空間中允許由同名類
  3. 不同名稱空間相互使用時,using參照名稱空間 或者指明出處
  4. 名稱空間可以包裹名稱空間

萬物之父Object中的方法

string

  1. string 本質是char[]陣列 可以有

    string ss="Tony",char[0]='T'

  2. 字串的拼接

  3. 正向查詢字元的位置 IndexOf()

  4. 反向查詢字串的位置 LastIndexOf()

  5. 移除指定位置後的字元 Remove(index)//注意接受返回值

  6. 字串的替換

  7. 大小寫轉換

  8. 字串的擷取

  9. 字串的切割 str.Split(',');按照,切割

StringBuilder

字串頻繁拼接使用StringBuilder,不會再次頻繁的建立新的物件,減少垃圾產生。

容量問題:初始時候本身有一定容量,在容量允許範圍內,直接儲存。

超過容量之後,會以2倍大小擴容。相對於string每一次更改便會新建物件,可減少垃圾產生。

//StringBuilder
StringBuilder str=new StringBuilder("My Name is Tony");
//獲取容量
str.Capacity;
//增加
str.Append("Chang");
str.AppendFormat("{0}{1}",123,456);
//插入
str.Insert(0,"Hello");
//刪除
str.Remove(0,10);
//清空
str.Clear();
//查
str[1];
//替換
str.Replace("Name"."name");
//重新賦值
str.Clear();
str.Append("Hello World");
//equals
if(str.Equals("123456"))
{
    
}

String 還是StringBuilder?

String的方法種類較多,使用更加方便和靈活,但效能上不如StringBuilder,不如StringBuilder產生垃圾少

需要頻繁修改的字串選用StringBuilder更好些。

如何優化記憶體?

  1. 節約記憶體
    1. 少new物件 少產生垃圾
    2. 合理使用static
    3. 合理使用String與StringBuilder
  2. 減少GC產生

結構體與類的區別

  1. 儲存位置 結構體是值型別,儲存在棧中 類是參照型別,儲存在堆中
  2. 結構體中的成員變數不可以賦初始值,類中可以
  3. 結構體具備封裝特性但是不具備繼承和多型 而類都具有
  4. 結構體不具備繼承特性,所以不可以使用protected保護修飾符修飾
  5. 結構體宣告有參構造之後,無參構造不會被頂掉
  6. 結構體不能宣告解構函式,而類可以
  7. 結構體需要在建構函式中初始化所有成員變數,而類隨意
  8. 結構體不能被static修飾,不存在靜態的結構體,而類隨意
  9. 結構體不能在內部宣告與自己一樣的結構體變數(會無限建立...棧溢位),類可以(因為是參照)
  10. 結構體可以繼承介面(不可以繼承類、結構體)

如何選擇結構體和類:

  1. 如果想要用繼承和多型時,直接淘汰結構體,如玩家、怪物
  2. 儲存的物件是資料的集合時候,優先考慮結構體,如向量、座標等
  3. 從本質上考慮,如果經常要改變賦值物件,原有的數值不想跟著改變的,選用結構體(值型別,複製拷貝,不影響本身),如座標、向量、旋轉角等

抽象類與介面的區別

相同點:

  1. 都可以被繼承
  2. 都不可以直接範例化
  3. 都可以包含方法的宣告
  4. 子類必須實現未實現的方法
  5. 都遵循里氏替換原則(父類別裝子類)

區別:

  1. 抽象類中可以有建構函式,而介面不可以
  2. 抽象類只能被單一繼承,介面可以被繼承多個
  3. 抽象類中可以有成員變數,介面中不能
  4. 抽象類中可以有宣告成員方法,虛方法,抽象方法,靜態方法而介面中只能宣告沒有實現的抽象方法
  5. 抽象類方法可以使用存取修飾符;介面中建議不寫,預設為public

如何選擇抽象類與介面

表示物件的選用抽象類,表示行為拓展的用介面。不同物件的相同行為,我們可以抽象出行為,用介面來實現。

物件導向的七大原則

總目標:高內聚、低耦合

減少類內部對其它類的呼叫,減少模組與模組之間的互動複雜度。

  1. 單一職責原則(一個類專注於一個功能)
  2. 里氏替換原則(父類別可以裝子類)
  3. 開閉原則(對拓展開放,對修改關閉,要保持開放和擴充套件,減少修改)
  4. 依賴倒轉原則(依賴於抽象,不依賴於抽象具體)
  5. 迪米特原則(最少知識原則,不要和陌生人說話)
  6. 介面隔離原則(一個介面不應該提供過多的功能,)
  7. 合成複用原則(儘量使用組合複用實現功能,減少繼承高耦合行為)