2019-03-07
面向对象编程
特点:封装,继承,多态(子类)
优点:易维护,易扩展,易开发
—— 与面向过程编程相对。
1. 继承
为什么要继承:不用写重复的代码
怎么实现继承:一个类派生于另一个基类,它拥有该基础类型的所有成员字段和函数。
理解:直接将基类里面的代码copy到派生类中
规则:单继承,只能继承一个父类。
语法:
class ChildClass : ParentClass
{...
}
2. 隐藏方法
派生类希望在从基类继承来的方法中,进行独特性的改变。
- 不能删除基类中任何方法,但可以使用相同名称的方法来屏蔽基类方法。
- 语法细节:在派生类中声明相同名称和类型的成员;并在前面添加new关键词
class Pet
{
public string Name;
public void PrintName()
{
Console.WriteLine("Pet's name is:" + Name);
}
}
class Dog:Pet
{//new 方法;在派生类中进行了方法修改
new public void PrintName()
{
Console.WriteLine("狗的名字是:" + Name);
}
}
//在Dog类实例化对象下,执行new方法
//在Pet类实例化对象,执行基类原来的方法
3. 虚方法和多态:virtual,override
设计原则:尽量依赖抽象类(Pet),而不依赖于具体类(Dog)
基类的引用:基类的引用,并指向派生类
Pet dog = new Dog();
dog能访问的内容是:Pet基类中正常部分+Pet基类中的虚方法在Dog派生类中override的重写对象。
- 提高效率:首先,我们希望能进行统一的管理,通过一个容器(数组)能保存所有对象(Dog,Cat),但是这些对象必须是同类型的。 所以,通过一个基类类型的数组储存所有对象,并通过虚方法和多态实现个性化。
Pet[] pets = new Pet[] { new Dog("Jack"), new Cat("Tom") };
- 虚方法和多态
虚方法:声明为virtual的方法是虚方法。基类的虚方法可以在派生类中使用override进行重写
多态:指向派生类的基类引用,调用虚函数时候,会调用派生类中的同名重写函数,便是多态。
//基类Pet
class Pet{
public string Name;
public void PrintName()
{
Console.WriteLine("Pet's name is:" + Name);
}
virtual public void Speak()
{
Console.WriteLine(Name+" is speaking");
}
}
//子类Dog
class Dog:Pet{
public Dog(string name)
{
this.Name = name;
}
public override void Speak()
{
Console.WriteLine(Name + " is speaking:"+"wangwang");
}
}
//子类Cat
class Cat:Pet {
public Cat(string name)
{
Name = name;
}
public override void Speak()
{
Console.WriteLine(Name + " is speaking:"+"miaomiao");
}
}
//主程序
class Program{
static void Main(string[] args){
Pet[] pets = new Pet[] { new Dog("Jack"), new Cat("Tom") };
for (int i = 0; i < pets.Length; i++)
{
pets[i].PrintName();
pets[i].Speak();
}
}
}
4. 派生类及构造函数
-
调用顺序
指向派生类的基类引用,要用派生类的构造函数,派生类的构造函数通过
public Dog(string name):base(name)
引用调用基类的构造函数。
class Pet{
public Pet(string name)
{
_name = name;
}
}
class Dog:Pet{
public Dog(string name):base(name)
{
}
}
class Cat:Pet{
public Cat(string name):base(name)
{
}
}
Pet[] pets = new Pet[] { new Dog("Jack"), new Cat("Tom") };
5. 抽象方法和类 abstract
abstract比virtual更加抽象,无函数体,必须要在子类进行重写
有抽象方法的一定是抽象类,需要声明为
abstract class Pet
抽象类的目的:被继承。
抽象类不能实例化,用abstract修饰
抽象类可以包含抽象成员和普通成员,抽象对象在派生类中需要用override重写
abstract class Pet
{
public Pet(string name)
{
_name = name;
}
abstract public void Speak();
}
6. 密闭类和密闭方法
- 关键词:sealed
- 为什么要密闭:不希望原来的类或者方法被其他人修改或重写(比如:string类是sealed类)
- 密闭方法:如果一个基类方法不希望子类对其重写,就可以不声明virual。如果是派生类方法不希望被子类重写,同时又是override重写,就可以使用sealed机制
sealed public override void Speak()
7. 接口
接口笔记链接
- 接口是什么:接口是指一组函数成员,而不实现他们的引用类型。(比抽象类还抽象,像没有普通函数的抽象类)
interface ICatchMice
{
void CatchMice(); //里面是空方法
}
-- 不能加任何访问修饰符,但默认是public
- 作用:用来被别的类实现,被继承
//定义接口
interface ICatchMice
{
void CatchMice();
}
interface IClimbTree
{
void ClimbTree();
}
//Cat类继承接口
class Cat:Pet,ICatchMice,IClimbTree
{
public Cat(string name):base(name)
{
}
public void CatchMice()
{
Console.WriteLine("Catch Mice");
}
public void ClimbTree()
{
Console.WriteLine("Climb Tree");
}
}
class Program
{
static void Main(string[] args)
{
Cat cat = new Cat("Tom2");
cat.CatchMice(); //用对象调用方法
IClimbTree climb = (IClimbTree)cat;
climb.ClimbTree(); //用接口调用方法
ICatchMice catchMice = (ICatchMice)cat;
catchMice.CatchMice();
}
}
8. 结构与类
- 不同点
- 结构是值类型(在栈中),类是引用类型(在堆中)
- 结构不支持继承但可以实现接口,类支持继承
- 结构不能定义默认构造函数,编译器会定义
- 适用类型
结构:由于分配内存块,作用域结束即被删除,不需要垃圾回收,用于小型结构类型。但传递过程中会复制,应该适用ref引用提高效率
类:用于需要继承体系的场合
9. 静态成员
什么是静态成员:标识为stactic的字段,方法,属性,构造函数,时间,就是静态成员。
static int Num
重点:静态成员被类的所有实例共享,所有实例都访问统一内存位置
如何访问静态成员:直接通过类名访问
Dog.Num+=1;
静态函数:独立于任何实例,没有实例也可以访问。静态函数不能访问实例成员,仅能访问静态成员
静态构造函数:静态构造函数用于初始化静态字段;在引用静态成员之前和创建任何实例之前调用;与类同名,使用static,无参数,无访问修饰符
static Dog()
class Dog:Pet
{
static int Num;
static Dog() //静态构造函数初始化Num,仅能访问静态成员
{
Num = 0;
}
public Dog(string name):base(name)
{
++Num;
}
static public void ShowNum()
{
Console.WriteLine("Dog's number is " + Num);
}
}
class Program
{
static void Main(string[] args)
{
Dog dog = new Dog("Tim");
Dog.ShowNum(); //通过类直接调用静态方法
}
}
10. 静态类
静态类:只包含静态的方法和属性,并标识为static。
静态类不能创建实例,不能被继承。可以为静态类定义一个静态构造函数。
作用:主要用于基础类库(如数学库)和扩展方法。
扩展方法:如有源代码,直接添加一个新方法;如果不能修改但也不是密闭类,可以派生子类扩展;如果以上条件都不满足,可使用静态类扩展方法
扩展方法的第一个参数类型,必须是this+类名。之后在实例对象中可以像自己的方法一样能直接调用静态类的方法
static class PetGuide
{
public static void HowToFeedDog(this Dog dog)
{
...
Console.WriteLine("Play a vedio about how to feed a dog");
}
}
class Program
{
static void Main(string[] args)
{
Dog dog = new Dog("Tim");
dog.HowToFeedDog(); //像自己的方法一样直接调用静态类的方法
}
}
11. 装箱和拆箱
- 装箱:值类型转换为引用类型,是一种隐式转换
- 为什么要装箱:进行统一的操作和统一的存储
int i = 3;
object oi = null;
oi = i;
- 拆箱:是将装箱后的对象转换为值类型的过程,是一种显示转换。
int i = 3;
object oi = i;
int j = (int) oi;
12. 自定义转换
- 什么是自定义转换:为自己的结构或者类定义显示和隐式转换
- 为什么:为了让结构或者类可以变成一个预期相关的类型,并且这种转换更简单
- 隐式转换语法:
static public implicit operator Cat(Dog dog)
{
return new Cat(dog._name);
}
- 显式转换语法:
static public explicit operator Dog(Cat cat)
{
return new Dog(cat._name);
}
- 应用:
Dog dog = new Dog("Jack");
dog.Speak();
Cat cat = dog;
cat.Speak();
Dog dog2 = (Dog)cat;
dog2.Speak();
13. 重载运算符
含义:利用现有的某种运算符,针对自定义类或结构,定义某种运算操作(公狗+母狗=新生狗狗)
意义:利用现有运算符,简化操作,具有一定相关性
-
运算符:
不能创造新运算符,不能改变运算符语法,不能重定义运算符处理预定义类型,不能改变运算符的优先级和结合性。
语法:
public static Dog operator +(Dog male,Dog female)
class Dog:Pet
{...
static public Dog operator +(Dog dog1,Dog dog2)
{
if ((dog1.Sex == (Sex)0 && dog2.Sex == (Sex)1) || (dog1.Sex == (Sex)1 &&dog2.Sex == (Sex)0) )
{
return new Dog(dog1._name + dog2._name, Sex.female);
}
else
{
Console.WriteLine("Can't bath new baby");
return null;
}
}
}
Dog dog1 = new Dog("Jack", Sex.male);
Dog dog2 = new Dog("Mary", Sex.female);
Dog littedog = dog1 + dog2; //运算符重载
littedog.ShowSex();