文章目錄
一、繼承
面向對象的三個最重要的概念之一。(繼承封裝和多態性)
二、繼承的類型
單重繼承: 一個類可以派生自一個基類。(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;
}
}
由此證明:派生接口擁有被繼承接口的所有成員,且繼承了該派生接口的類必須要實現兩個接口中的所有成員。