第5章 面向对象的高级程序设计
5.1 静态成员与静态类
数据信息有没有可能属于类,而不属于特定实例呢?
5.1.1 类的静态成员
static
1
. 静态成员属于类,只能通过类名引用,不能通过对象名引用。与java
C++
的不同点!
2
. 静态方法只能访问静态成员。
5.1.2 静态构造函数
静态构造函数不是为了创建对象而设计的,而是用来初始化类的静态字段的,不能带有访问修饰符,参数列表和返回值。
在第一个实例创建之前或调用类的任何静态方法之前,系统会自动执行静态构造函数,仅此一次。
非静态构造函数 | 静态构造函数 |
---|---|
实例构造函数 | 全局构造函数或共享构造函数 |
static 静态构造函数名()
{
// 语句
}
5.1.3 静态类
-
静态类
- 只包含静态成员
1
. 静态类仅包含静态成员
2
. 静态类不能被实例化,即不能使用new
创建静态类对象
3
. 静态类是密封的
4
. 静态类不能包含实例构造函数
-
常见的静态类
-
Console
,Math
5.2 类的继承性
1
. 单一继承
2
. 不能继承基类的构造函数,析构函数,私有成员
3
. 继承可以传递
5.2.1 派生类的声明
public class Dog: Animal
{
}
5.2.2 构造函数
基类的构造函数和派生类的构造函数各司其职,即基类的构造函数负责初始化积累的成员字段,派生类的构造函数之初始化新添加的成员字段
1
. 无参数的默认构造函数
// 父类
class Father
{
protected string name;
public Father() {}
}
// 子类
class Son: Father
{
int age;
public Son() {} // 自动调用父类的构造函数,在调用自己的构造函数
}
2
. 带参数的构造函数
在声明派生类的构造函数是必须使用base
关键字想基类的构造函数传递参数
public Son(string name, int age):base(name)
5.2.3 密封类
-
密封类
- 为了阻止一个类的代码被其他类继承,通过密封类保护知识产权,避免他人共享代码
public sealed class Animal
{
}
5.3 类的多态性
多态的字面意思是事物有多种形态,其实质是不同事物在发展过程中逐渐体现出来的差异性,体现为一个派生类对基类的特征和行为的改变
为了使派生类能更改基类的数据和行为,C#
提供了两种选择:
- 使用新的派生成员替换基类成员
- 重写虚拟的基类成员
为什么称之为虚拟的
,因为不能实现,必须重写!
5.3.1 使用new
重新定义类的成员
注意,把new
关键字放置在要替换的类成员的数据类型之前
public new string Eat()
{
// 语句
}
使用new
关键字在派生类中重写基类的成员,实际上是对基类中的相应代码的彻底废除!
5.3.2 用 virtual
和 override
定义类的成员
首先在基类中用virtual
关键字声明类的成员(这种成员成为虚拟成员),然后在派生类中用override
关键字重载虚拟成员或覆盖虚拟成员。
1
. 虚方法及其重载
// 基类中声明虚方法
public class Animal
{
public virtual string Eat()
{
// 基类的虚方法
}
}
// 派生类中覆盖虚方法
public class Dog: Animal
{
public override string Eat()
{
// 派生类覆盖基类的虚方法
}
}
2
. 虚属性及其重载
必须保证基类和派生类中的属性的格式完全一致,包括可访问性、返回值类型、属性名称和属性体。
public class Animal
{
public virtual string Name
{
// 基类的虚属性
}
}
public class Dog: Animal
{
public override string Name
{
// 派生类覆盖基类的虚属性
}
}
比较
new | override |
---|---|
替换 | 覆盖 |
发生在程序编译时 | 发生在程序运行时 |
餐桌上已经有一张桌布,撤下这张桌布再铺一张新的桌布 | 餐桌上已经有一张桌布,在原先的桌布上再铺一张桌布 |
1
. 字段不能是虚拟的,只有方法、属性、事件和索引器才可以使虚拟的
2
. 使用 virtual
修饰之后,就不要再使用static
、abstract
或 override
修饰
3
. 派生类对象即使陪强制转换为基类对象,所引用的仍然是派生类成员
4
. 派生类可以通过密封来停止虚拟继承,使用sealed override
5.3.3 访问基类的成员
1
. 基类与派生类之间的转化
Animal a = new Animal();
Animal b = new Dog();
Animal b = new Dog();
中,虽然b
指向了派生类实例【Dog类】,但它的数据类型还是基类【Animal】。此时,若通过基类【Animal】对象来调用一个基类与派生类都具有的同名方法,则系统将调用基类的方法【Animal的方法】。
当基类对象【Animal】指向派生类实例【new Dog()】时,虽然数据类型被转化成了基类【Animal】,但其本质仍然没有改变,还是派生类的实例【Dog】,因此可以再次强制转换为派生类型。
派生类伪装成基类,表现得像基类【使用基类手下的人】
2
. 在派生类中调用基类的成员
在派生类重载或覆盖基类方法后,如果想调用基类中的同名方法,可以使用base
关键字。
public override void Eat()
{
base.Eat();
}
3
. 类的多态性的意义
对象引用变得更加灵活
使用 virtual
和 override
实现类的多态性,系统将具有自适应的能力,它会根据对象所引用的是基类的实例还是派生类的实例来自动调用覆盖之前还是覆盖之后的方法。
5.4 抽象类
凡是包含无法实现的成员的类就是抽象类,其中无法实现的操作就是类的抽象成员。
抽象成员必须在抽象类中声明,但抽象类不要求必须包含抽象成员。
5.4.1 抽象类及其抽象成员
1
. 抽象类与抽象方法
public abstract class Shape
{
protected double radius;
public Shape(double r)
{
radius = r;
}
public abstract double Cubage(); // 抽象方法
}
- 抽象类和抽象方法都必须使用
abstract
修饰 - 抽象类的用途是提供多个派生类可共享的基类的公共定义
2
. 抽象类与抽象属性
抽象属性只声明该属性的数据类型、名字、可访问性等!
public abstract double Length
{
get;
set;
}
5.4.2 重载抽象方法
派生类如果不想作为抽象类必须重载基类的抽象方法和抽象属性!
若继续作为抽象类,前面还是要加一个abstract
。
这点与虚拟方法不同,因为对于基类的虚方法,派生类可以选择不重载。
重载抽象方法
public override double Cubage()
{
}
5.5 接口
5.5.1 接口的声明
interface IUsb
{
int MaxSpeed
{
get;
}
string TransData(string from, string to);
}
5.5.2 接口的实现
public classMp3: IUsb
{
public int MaxSpeed
{
get
{
return 480;
}
}
public string TransData(string from, string to)
{
return string.Format("数据传输:从{0}到{1}", from, to);
}
}
5.5.3 接口的继承性
接口竟然支持多重继承!
interface IUsb
{
int MaxSpeed{get;}
string TransData(string from, string to);
}
interface IBluetooth
{
int MaxSpeed{get;}
string TransData(string from, string to);
}
interface IMp3:IUsb, IBluetooth
{
string Play(string mp3);
}
5.5.4 多重接口实现
C#
允许同时从基类和基接口派生,但要求类名必须位于基接口名之前。
使用接口姓名.接口成员
格式显示实现不易分辨的接口成员。
5.5.5 访问接口的成员
1
. 派生类对象转换为接口的实例
当派生类实现了接口所有成员之后,访问这些成员
- 通过派生类的实例来访问
Mp3 m = new Mp3();
- 通过接口的实例来访问接口通过间接实例化
Mp3 m = new Mp3();
IUsb iu = (IUsb)m;
2
. 测试对象是否支持接口
is
操作符
if(m is IUsb)
{
}
as
操作符:首先测试转换是否合法,若是则进行转换,否则返回null
Mp3 m = new Mp3();
IUsb iu = m as IUsb;
if(iu != null)
{
}
5.5.6 抽象类与接口的比较
抽象类 | 接口 |
---|---|
抽象类可以部分实现或不实现 | 完全没有实现 |
抽象类为管理组件版本提供了一个简单易行的方法,通过更新基类,所有的派生类都将自动进行相应的改动。 | 接口在创建后就不能改变,需要修改接口,就必须创建新的接口 |
5.6 嵌套类、分部类与命名空间
5.6.1 嵌套类
5.6.2 分部类
5.6.3 命名空间
命名空间可以嵌套,用.
分隔定义命名空间。