8.1 什麼是繼承
一個新類從已有的類那裏獲得其已有特性,這種現象稱爲類的繼承。通過繼承,一個新建子類從已有的父類那裏獲得父類的特性。
從另一角度說,從已有的類(父類)產生一個新的子類,稱爲類的派生。
- 父類:被繼承的類稱爲父類,也稱爲基類、超類;
- 子類:繼承了的類稱爲子類,也稱爲派生類;
- 關係:IS-A
繼承關係是向下傳遞的。
一個基類可以派生出多個派生類,每一個派生類又可以作爲基類再派生出新的派生類,因此基類和派生類是相對而言的,同時基類與派生類也是一個成對的概念,一個孤立的類既不可能是基類也不可能是派生類。
一個派生類有且只能有一個基類,即C#不支持多重繼承機制。
8.2 使用繼承機制
8.2.1 基類和派生類
C#中繼承的語法格式如下:
基類的成員如果只想讓子類訪問,即只讓父類和子類之間溝通,基類成員的修飾符通常使用protected修飾。
namespace ConsoleApplication9
{
public class Person
{
protected string ssn ;
protected string name ;
public void Display()
{
Console.WriteLine("姓名:{0}",name);
Console.WriteLine("編號:{0}",ssn);
}
}
public class Employeer : Person
{
public void Set()
{
ssn = "11-222-333-444";
name = "張三";
}
}
class Program
{
static void Main(string[] args)
{
Employeer zs = new Employeer();
zs.Set();
zs.Display();
Console.ReadLine();
}
}
}
C#語言的繼承機制有如下規則:
- 派生類應當被看作是基類所具有的特性與功能的繼承與擴展,而不是簡單的派生類“大於”基類;
- 派生類不能“選擇性”的繼承基類的屬性與方法,必須繼承基類的所有特性與方法;
- 派生類可以在繼承基類的基礎上自由定義自己特有的成員;
- 基類的構造方法與析構方法不能被派生類繼承,除此之外的其他成員都能被繼承,基類成員的訪問方式不影響他們成爲派生類的成員;
- 派生類中繼承的基類成員和基類中的成員只是相同,並非同一個成員。
8.2.2 base關鍵字與基類成員的訪問
在派生類中使用base關鍵字即可指代當前類的父類,但只限於構造函數、實例方法和實例屬性訪問器中使用,如:
namespace ConsoleApplication9
{
public class Person
{
protected string ssn = "11-222-444";
protected string name = "張三";
public void Display()
{
Console.WriteLine("姓名:{0}", name);
Console.WriteLine("編號:{0}", ssn);
}
}
public class Employeer : Person
{
public string id = “110723001";
public void GetInfo()
{
Console.WriteLine("I D: {0}",id);
base.Display();
}
}
class Program
{
static void Main(string[] args)
{
Employeer zs = new Employeer();
zs.GetInfo();
Console.ReadLine();
}
}
}
8.2.3 base關鍵字與構造函數的訪問
繼承機制並不能使派生類具有基類的帶參數的構造方法,要想通過訪問基類的構造方法爲派生類中的基類子對象進行初始化則需要通過base關鍵字。
namespace ConsoleApplication9
{
public class Person
{
protected string ssn = "11-222-333-444";
protected string name = "張三";
public void Display()
{
Console.WriteLine("姓名:{0}", name);
Console.WriteLine("編號:{0}", ssn);
}
public Person()
{
Console.WriteLine("我是父類中不帶參數的構造函數");
}
public Person(string i)
{
Console.WriteLine("我是父類中帶一個參數的構造函數,傳遞來的參數是:"+i);
}
public Person(string i, string j)
{
Console.WriteLine("我是父類中帶兩個參數的構造函數,傳遞來的參數分別是:\n"+i+"\n"+j);
}
}
public class Employeer : Person
{
public string id = "070423001";
public void GetInfo()
{
Console.WriteLine("I D: {0}",id);
base.Display();
}
public Employeer()
{
Console.WriteLine("我是子類中不帶參數的構造函數!");
}
public Employeer(string e) : base()
{
Console.WriteLine("我是子類中帶一個參數的構造函數,我要求繼承父類中不帶參數的構造函數,我的傳遞參數是:"+e);
}
public Employeer(string a, string b): base(b)
{
Console.WriteLine(“我是子類中帶兩個參數的構造函數,我要求繼承父類中帶一個參數的構造函數,我的傳遞參數分別是:\n"+a+"\n"+b);
}
}
class Program
{
static void Main(string[] args)
{
Employeer zs = new Employeer(“信息”,“信管");
// zs.GetInfo();
Console.ReadLine();
}
}
}
顯式的調用基類的構造方法使用base關鍵字,如:
public SonClass(int i,int j):base ([i,j])
{
}
C#中父類構造函數與子類構造函數:
子類的構造器無法從父類繼承,但子類的構造器一定要調用父類的構造器,如果沒有顯式地聲明調用哪個,系統將默認調用無參的那個。
實驗:用C#編寫一個程序,使用Animal和Mammal兩個類來說明一般動物和哺乳動物的繼承關係。Animal具有名稱、所屬門類等屬性,需要提供方法實現接收和顯示這些屬性的值。Mammal類具有代表哺乳動物習性的屬性,這些屬性表明哺乳動物與其他類型動物的區別。同樣地,需要提供方法實現接收和顯示這些屬性的值。
8.3 virtual、override及new關鍵字
多態性主要的表現形式就是在繼承中當派生類從基類繼承時,它會獲得基類的所有方法、字段、屬性和事件。當父類和子類的成員簽名(方法名)相同的時候,我們可以定義讓子類的這個同名成員以何種形式顯示出來,父類的這個的成員在子類中又用何種方式存在,這種多面的表現方法我們稱爲多態。
如果要更改基類的數據和功能,也就是說子類中可以以自己的方式去實現父類的功能,有兩種方式:
- 在子類中可以使用override關鍵字重寫基類中用virtual關鍵字修飾的虛擬的基成員;
- 可以在子類中使用new關鍵字讓派生成員替換基成員,此時基類成員只是被有意的隱藏掉了。
8.3.1 virtual、override關鍵字
使用virtual關鍵字修飾的方法稱爲是虛方法,在一個類中如果某個方法需要被派生類繼承,並且需要在派生類中修改方法的內容時可將該方法定義爲虛方法。在派生類中如果需要重寫該方法,可在派生類中定義同名的方法,其前加上override關鍵字修飾。在派生類中對虛方法使用override修飾後,基類中的該方法在派生類中將被屏蔽。
namespace ConsoleApplication9
{
public class Person
{
protected string ssn = "11-222-333-444";
protected string name = "張三";
public virtual void Display()
{
Console.WriteLine("姓名:{0}", name);
Console.WriteLine("編號:{0}", ssn);
}
}
public class Employeer : Person
{
public string id = "070423001";
public override void Display()
{
Console.WriteLine("I D: {0}",id);
base.Display();
}
}
class Program
{
static void Main(string[] args)
{
Person p = new Person();
Console.WriteLine("基類的成員:");
p.Display();
Employeer e = new Employeer();
Console.WriteLine("派生類的成員:");
e.Display();
Console.ReadLine();
}
}
}
- 有virtual關鍵字一定要有override嗎?不一定。
- 有override關鍵字一定要有virtual嗎?一定。
- 因此,virtual是使用override的前提條件。
8.3.2 new關鍵字
new關鍵字用來修飾派生類中一個方法,是全新定義一個子類的方法,這個方法只屬於子類,與基類的方法無關,該方法和基類中方法只是同名,但二者並無任何關聯。因此這個方法只在子類中起作用,而不影響基類的方法,也就是說new方法就是子類新定義的方法。
在使用new的時候對於父類中同名的方法而言既可以是虛方法,也可以不是虛方法。
namespace ConsoleApplication9
{
public class Person
{
protected string ssn = "11-222-333-444";
protected string name = "張三";
public void Display()
{
Console.WriteLine("姓名:{0}", name);
Console.WriteLine("編號:{0}", ssn);
}
}
public class Employeer : Person
{
public string id = "070423001";
public new void Display()
{
Console.WriteLine("I D: {0}",id);
base.Display();
}
}
class Program
{
static void Main(string[] args)
{
Person p = new Person();
Console.WriteLine("基類的成員:");
p.Display();
Employeer e = new Employeer();
Console.WriteLine("派生類的成員:");
e.Display();
Console.ReadLine();
}
}
}
在創建對象時,如果是子類對象,它調用的是子類中修改後的同名方法,產生一個運行結果。而如果聲明瞭父類的一個對象,父類對象調用同名的父類方法時,產生了另外一個運行結果。
using System;
using System.Collections.Generic;
using System.Text;
namespace ConsoleApplication7
{
class Parent
{
public void F() //定義非虛方法
{
Console.WriteLine("基類的F方法被調用");
}
public virtual void G() //定義虛方法
{
Console.WriteLine("基類的G方法被調用");
}
}
class Child : Parent
{
public new void F()
{ Console.WriteLine("派生類的F方法被調用");
}
public override void G()
{ Console.WriteLine("派生類的G方法被調用");
}
}
class Test
{
static void Main()
{ Child b = new Child();
Parent a = b;
a.F();
b.F();
a.G();
b.G();
Console.ReadLine();
}
}
}
當出現里氏替代現象時:
- 覆蓋(override)時,訪問父類子類皆調用子類的重寫方法;
- 重寫(new)時,訪問父類則調用父類的方法,訪問子類則調用子類的方法。
- override--覆蓋(父類的沒了,始終是子類的)
- new--新的(父子共存)
里氏替代原則:
父類對象引用子類實例,子類實例賦值給父類對象。
8.4 sealed關鍵字與密封類
在實際編程過程中,有的類已經沒有再被繼承的必要。C#提出了一個密封類(sealed class)的概念,來描述這一問題。密封類在聲明中使用sealed 修飾符,這樣就可以防止該類被其它類繼承。如果試圖將一個密封類作爲其它類的基類,C#將提示出錯。
如果在一個類的繼承體系中不想再使一個虛方法被覆蓋,可以使用sealed override (密封覆蓋)便可以達到這種目的。注意這裏一定是sealed和override同時使用,也一定是密封覆蓋一個虛方法,或者一個被覆蓋(而不是密封覆蓋)了的虛方法。密封一個非虛方法是沒有意義的。
using System;
using System.Collections.Generic;
using System.Text;
namespace ConsoleApplication7
{
class Parent
{
public void F()
{
Console.WriteLine("基類的F方法被調用");
}
public virtual void G()
{
Console.WriteLine("基類的G方法被調用");
}
}
class Child : Parent
{
new public void F()
{
Console.WriteLine("派生類的F方法被調用");
}
public sealed override void G()
{
Console.WriteLine("派生類的G方法被調用");
}
}
class Test
{
static void Main()
{
Child b = new Child();
Parent a = b;
a.F();
b.F();
a.G();
b.G();
Console.ReadLine();
}
}
}
1. sealed——“斷子絕孫”
即:密封類不會有子類,所以是“斷子絕孫”。
2. new——“你是我的,我是我的”
即:好比是不用祖宗的東西,而是用自己創造(new)的東西。
3. virtual——“爲了子孫後代”
即:virtual是爲了讓子孫後代可以實現各自的夢想而做的。
4. override——“一手遮天”
即:override好比不但不用祖宗的那套,而且還自己創一套新功夫代替祖宗那套。
8.5 Abstract關鍵字與抽象類
{
public abstract class Animal
{
public abstract void Eat();
}
public class Cat : Animal
{
public override void Eat()
{
Console.WriteLine("Cat Eat");
}
}
class Program
{
static void Main(string[] args)
{
Cat c = new Cat();
c.Eat();
Console.ReadLine();
}
}
}
8.6 多態性
從系統實現的角度看,多態性分爲兩類:靜態多態性和動態多態性。
- 以前學過的方法重載實現的多態性屬於靜態多態性,在程序編譯時系統就能決定調用的是哪個方法,因此靜態多態性又稱編譯時的多態性。靜態多態性是通過方法的重載實現的。
- 動態多態性是在程序運行過程中才動態地確定操作所針對的對象。它又稱運行時的多態性。動態多態性是通過虛方法實現的。
namespace ConsoleApplication9
{
public class Plane
{
public int wheels;
protected int weight;
public Plane()
{
}
public Plane(int w1, int w2)
{
wheels = w1;
weight = w2;
}
public virtual void Report()
{
Console.WriteLine("飛機的輪子的數目是:{0},重量是:{1}",wheels,weight);
}
}
public class Airliner : Plane
{
int passengers;
public Airliner(int w1, int w2, int p) : base(w1, w2)
{
passengers = p;
}
public override void Report()
{
Console.WriteLine("客機的輪子的數目是:{0},重量是:{1},乘客數量是:{2}",wheels,weight,passengers);
}
}
class Program
{
static void Main()
{
Plane p = new Plane();
Airliner a = new Airliner(2, 300, 100);
p.Report();
p = a;
p.Report();
Console.ReadLine();
}
}
}