C#教程(3)-----繼承篇

一、繼承

面向對象的三個最重要的概念之一。(繼承封裝和多態性

二、繼承的類型

單重繼承: 一個類可以派生自一個基類。(C#就採用這種繼承)
多重繼承: 允許一個類派生字多個類。(C#不支持多重繼承,但允許接口的多重繼承)
多層繼承: 允許繼承有更大的層次結構。類B派生字類A,類C派生字類B。其中,類B也稱爲中間基類。
接口繼承: 這裏允許多重繼承。

1.多重繼承

一個類派生自多個類。多重繼承會給代碼增加複雜性並帶來額外開銷。因此C#開發人員決定不支持多重繼承。而C#允許類型派生自多個接口。

2.結構和類

  • 結構派生自System.ValueType,他們還可以派生自任意多個接口。
  • 類派生自System.Object或用戶定義的類,他們還可以派生字任意接口。

三、實現繼承

示例:(繼承)

class MyClass : MyBaseClass
        {
            //members
        }

示例:(結成並派生自接口)

class MyClass : MyBaseClass,interface1,interface2;
        {
            //members
        }

如果類和接口都用於派生,則類總是放在接口的前面。

對於結構,語法如下(只能用於接口繼承)

struct MyClass : interface1,interface2;
        {
            //members
        }

案例1:

	public class Position
    {
        public int X { get; set; }
        public int Y { get; set; }
    }
    
    public class Size
    {
        public int Width { get; set; }
        public int Height { get; set; }
    }
    
    public class Shape
    {
        public Position Position { get; } = new Position();
        public Size Size { get; } = new Size();
    }

如果類定義中沒有指定基類、C#編譯器就默認System.Object是基類

1.虛方法

把一個基類聲明爲virtual,就可以在任何派生類中重寫該方法;
接案例1

	public class Shape
    {
        public Position Position { get; } = new Position();
        public Size Size { get; } = new Size();

        public virtual void Draw()
        {
            Console.WriteLine($"Shape with {Position}and{Size}");
        }
    }

也可以這樣寫(可以忽略)

   public class Shape
    {
        public Position Position { get; } = new Position();
        public Size Size { get; } = new Size();

        public virtual void Draw() => Console.WriteLine($"Shape with {Position}and{Size}");
    }

也可以把屬性聲明爲virual(虛屬性或重寫屬性)語法和非虛屬性相同。例如:public virtual Size Size { get; } = new Size();

在重寫時要用override關鍵字聲明

 	public class Rectangle : Shape
    {
        public override void Draw() => Console.WriteLine($"Rectangle with {Position}and{Size}");
    }

在size和postion中重寫ToString()方法。這個方法在基類中聲明爲virual、

public class Position
    {
        public int X { get; set; }
        public int Y { get; set; }

        public override string ToString() => $"X:{X},Y{Y}";
        
    }
    public class Size
    {
        public int Width { get; set; }
        public int Height { get; set; }
        public override string ToString() => $"Width:{Width},Height{Height}";
}

然後再主函數中實例化矩形r、

static void Main(string[] args)
        {
            var r = new Rectangle();
            r.Position.X = 33;
            r.Position.Y = 22;
            r.Size.Width = 200;
            r.Size.Height = 100;
            r.Draw();
        }

結果:
在這裏插入圖片描述

2.多態性

使用多態性,,可以動態的定義調用的方法,而不是在編譯期間定義。編譯器會創建一個虛擬方法表(vtable),其中列出可以在運行期間調用的方法。
將主函數改爲:

		static void Main(string[] args)
        {
            var r = new Rectangle();
            r.Position.X = 33;
            r.Position.Y = 22;
            r.Size.Width = 200;
            r.Size.Height = 100;

            DrawShape(r);
        }

添加函數

		public static void DrawShape(Shape shape)
        {
            shape.Draw();
        }

如果基類的方法不是虛擬的或沒有重寫基類中的方法,就使用所生命對象(shape)的類型的方法(draw)。
此案例輸出結果爲
在這裏插入圖片描述

3.隱藏方法

在基類和派生類中同時有相同的方法,但不爲虛函數或重寫函數,這是派生類方法就會隱藏基類的方法
例如:在這裏插入圖片描述
派生類和基類都有一個A方法,這是就會出現警告。但是我們可能會用不到派生類,編譯器編譯時就不會出現錯誤。如果用到派生類,就可能因爲名稱相同出現編譯錯誤。爲了避免這種情況,我們進行如下改進:
在這裏插入圖片描述
這樣就編譯器就不會發出警告了。

4.調用方法的基類版本

關鍵字:base
例如:

 public class Position
    {
        public virtual void A()
        {
            Console.WriteLine("123");
        }
        
    }
    public class Position2:Position
    {
        override public void A()
        {
            Console.WriteLine("456");
            base.A();
        }

    }

主函數代碼:

static void Main(string[] args)
        {
            var r = new Position2();
            r.A();
		}

5.抽象類的抽象方法

C#允許將類和方法聲明爲abstract,抽象類不能直接實例化,抽象方法不能直接實現,他們只能在派生類中重寫。
例如:

	public abstract class Position
    {
        abstract public virtual void A();
    }

從抽象基類派生類型時,需要將所有抽象成員實現。

	public class Position2:Position
    {
        override public void A()
        {
            Console.WriteLine("456");A();
        }
    }

6.密封類和密封方法

如果某個類不允許派生出字類或方法不允許重寫,就應該密封。
關鍵字:sealed
在這裏插入圖片描述

7.派生類和構造函數

設有基類A,派生類B。那麼編譯器是怎樣調用兩個類的後遭函數的呢?
讓我們看一個例子:

	public class A
    {
        public A()
        {
            Console.WriteLine("調用了A的構造函數");
        }
    }
    public class B:A
    {
        public B()
        {
            Console.WriteLine("調用了B的構造函數");
        }
    }

輸出結果:
在這裏插入圖片描述
結論:
a派生b,b派生c,c派生d…
構造函數調用順序:a->b->c->d…

四、修飾符

1.訪問修飾符

修飾符 應用於 說明
public 所有類型或成員 任何代碼均可以訪問該項
protected 類型和內嵌類型的所有成員 只有派生的類型才能訪問
internal 所有類型或成員 只能在包含它的程序集中使用
private 類型和內嵌類型的所有成員 只能在它所屬的類型中使用
protected internal 類型和內嵌類型的所有成員 只能在它所屬的類型中或派生的類型的任意代碼訪問

public、protected和private均爲邏輯訪問修飾符,internal是物理訪問修飾符,邊界是一個程序集。

2.其他修飾符

修飾符 應用於 說明
new 函數成員 成員用相同的名稱時隱藏基類中相同的成員
static 所有成員 成員不做用於類的具體實現
virual 函數成員 成員可以由派生類重寫
abstract 函數成員 抽象類
override 函數成員 重寫虛擬或抽象成員
sealed 類、方法和屬性 不能派生出新類

五、接口

1.定義和實現接口

直接用一個小例子來說明如何定義和實現接口:

	public interface IBankAccount
    {
        void payIn(decimal Amount);
        bool withDraw(decimal Amount);
        decimal Balance { get; }
    }

接口名稱爲IBankAccount,接口名稱通常以I開頭。

編寫類繼承上面的接口:

 	public class SavaAccount : IBankAccount
    {
        private decimal _blance;

        public void payIn(decimal Amount) => _blance += Amount;

        public bool withDraw(decimal Amount)
        {
            if (_blance >= Amount)
            {
                _blance -= Amount;
                return true;
            }
            Console.WriteLine("Withdrawal attempt failed.");
            return false;
        }
        public decimal Balance => _blance;

        public override string ToString()
        {
            return $"Venus Bank Saver:Balance={_blance,6:C}";
        }
    }

接口不實現方法,所以要在派生類中將實現的代碼寫出來。如果缺少成員代碼,便會報錯。
主函數代碼:

		static void Main(string[] args)
        {
            IBankAccount s = new SavaAccount();

            s.payIn(200);
            s.withDraw(100);
            Console.WriteLine(s.ToString());
            }

結果:
在這裏插入圖片描述

2.派生的接口

接口也可以繼承,例如:

	public interface ITransferBankAccount : IBankAccount
    {
        bool TransferTo(IBankAccount bankAccount, decimal amount);
    }

因爲 ITransferBankAccount 派生自IBankAccount,所以 ITransferBankAccount有 IBankAccount的所有成員和它自己的成員。 用下面代碼來進行驗證
首先,編寫新類只實現 ITransferBankAccount中的TransferTo方法:

	public class textClass:ITransferBankAccount
    {
        public bool TransferTo(IBankAccount bankAccount, decimal amount)
        {
            return false;
        }
    }

這是編譯器會報錯:
在這裏插入圖片描述
當我們把從IBankAccount繼承過來的成員都實現,編譯器就不會報錯:

public class textClass:ITransferBankAccount
    {
        private decimal _blance;

        public void payIn(decimal Amount) => _blance += Amount;

        public bool withDraw(decimal Amount)
        {
            if (_blance >= Amount)
            {
                _blance -= Amount;
                return true;
            }
            Console.WriteLine("Withdrawal attempt failed.");
            return false;
        }
        public decimal Balance => _blance;

        public override string ToString()
        {
            return $"Venus Bank Saver:Balance={_blance,6:C}";
        }
        public bool TransferTo(IBankAccount bankAccount, decimal amount)
        {
            return false;
        }
    }

由此證明:派生接口擁有被繼承接口的所有成員,且繼承了該派生接口的類必須要實現兩個接口中的所有成員。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章