上期回顾 - https://www.cnblogs.com/liu-jinxin/p/10826971.html
一、类
当你定义一个类时,你定义了一个数据类型的蓝图。这实际上并没有定义任何的数据,但它定义了类的名称意味着什么,也就是说,类的对象由什么组成及在这个对象上可执行什么操作。对象是类的实例。构成类的方法和变量成为类的成员。
代码示例:
1 using System; 2 namespace BoxApplication 3 { 4 class Box 5 { 6 public double length; // 长度 7 public double breadth; // 宽度 8 public double height; // 高度 9 } 10 class Boxtester 11 { 12 static void Main(string[] args) 13 { 14 Box Box1 = new Box(); // 声明 Box1,类型为 Box 15 Box Box2 = new Box(); // 声明 Box2,类型为 Box 16 double volume = 0.0; // 体积 17 18 // Box1 详述 19 Box1.height = 5.0; 20 Box1.length = 6.0; 21 Box1.breadth = 7.0; 22 23 // Box2 详述 24 Box2.height = 10.0; 25 Box2.length = 12.0; 26 Box2.breadth = 13.0; 27 28 // Box1 的体积 29 volume = Box1.height * Box1.length * Box1.breadth; 30 Console.WriteLine("Box1 的体积: {0}", volume); 31 32 // Box2 的体积 33 volume = Box2.height * Box2.length * Box2.breadth; 34 Console.WriteLine("Box2 的体积: {0}", volume); 35 Console.ReadKey(); 36 } 37 } 38 } 39 //执行结果 40 //Box1 的体积: 210 41 //Box2 的体积: 1560
(一)、成员函数
类的成员函数是一个在类定义中有它的定义或原型的函数,就像其他变量一样。作为类的一个成员,它能在类的任何对象上操作,且能访问该对象的类的所有成员。
成员变量是对象的属性(从设计角度),且它们保持私有来实现封装。这些变量只能使用公共成员函数来访问。
代码示例:
1 using System; 2 namespace BoxApplication 3 { 4 class Box 5 { 6 private double length; // 长度 7 private double breadth; // 宽度 8 private double height; // 高度 9 public void setLength( double len ) 10 { 11 length = len; 12 } 13 14 public void setBreadth( double bre ) 15 { 16 breadth = bre; 17 } 18 19 public void setHeight( double hei ) 20 { 21 height = hei; 22 } 23 public double getVolume() 24 { 25 return length * breadth * height; 26 } 27 } 28 class Boxtester 29 { 30 static void Main(string[] args) 31 { 32 Box Box1 = new Box(); // 声明 Box1,类型为 Box 33 Box Box2 = new Box(); // 声明 Box2,类型为 Box 34 double volume; // 体积 35 36 37 // Box1 详述 38 Box1.setLength(6.0); 39 Box1.setBreadth(7.0); 40 Box1.setHeight(5.0); 41 42 // Box2 详述 43 Box2.setLength(12.0); 44 Box2.setBreadth(13.0); 45 Box2.setHeight(10.0); 46 47 // Box1 的体积 48 volume = Box1.getVolume(); 49 Console.WriteLine("Box1 的体积: {0}" ,volume); 50 51 // Box2 的体积 52 volume = Box2.getVolume(); 53 Console.WriteLine("Box2 的体积: {0}", volume); 54 55 Console.ReadKey(); 56 } 57 } 58 } 59 //执行结果 60 //Box1 的体积: 210 61 //Box2 的体积: 1560
(二)、构造函数
类的 构造函数 是类的一个特殊的成员函数,当创建类的新对象时执行。
构造函数的名称与类的名称完全相同,它没有任何返回类型。
代码示例:
1 using System; 2 namespace LineApplication 3 { 4 class Line 5 { 6 private double length; // 线条的长度 7 public Line() //构造函数 8 { 9 Console.WriteLine("对象已创建"); 10 } 11 12 public void setLength( double len ) 13 { 14 length = len; 15 } 16 public double getLength() 17 { 18 return length; 19 } 20 21 static void Main(string[] args) 22 { 23 Line line = new Line(); 24 // 设置线条长度 25 line.setLength(6.0); 26 Console.WriteLine("线条的长度: {0}", line.getLength()); 27 Console.ReadKey(); 28 } 29 } 30 } 31 //执行结果: 32 //对象已创建 33 //线条的长度: 6
默认的构造函数没有任何参数。但是如果你需要一个带有参数的构造函数可以有参数,这种构造函数叫做参数化构造函数。这种技术可以帮助你在创建对象的同时给对象赋初始值
代码示例:
1 using System; 2 namespace LineApplication 3 { 4 class Line 5 { 6 private double length; // 线条的长度 7 public Line(double len) // 参数化构造函数 8 { 9 Console.WriteLine("对象已创建,length = {0}", len); 10 length = len; 11 } 12 13 public void setLength( double len ) 14 { 15 length = len; 16 } 17 public double getLength() 18 { 19 return length; 20 } 21 22 static void Main(string[] args) 23 { 24 Line line = new Line(10.0); 25 Console.WriteLine("线条的长度: {0}", line.getLength()); 26 // 设置线条长度 27 line.setLength(6.0); 28 Console.WriteLine("线条的长度: {0}", line.getLength()); 29 Console.ReadKey(); 30 } 31 } 32 } 33 //执行结果: 34 //对象已创建,length = 10 35 //线条的长度: 10 36 //线条的长度: 6
(三)、析构函数
类的 析构函数 是类的一个特殊的成员函数,当类的对象超出范围时执行。
析构函数的名称是在类的名称前加上一个波浪形(~)作为前缀,它不返回值,也不带任何参数。
析构函数用于在结束程序(比如关闭文件、释放内存等)之前释放资源。析构函数不能继承或重载。
代码示例:
1 using System; 2 namespace LineApplication 3 { 4 class Line 5 { 6 private double length; // 线条的长度 7 public Line() // 构造函数 8 { 9 Console.WriteLine("对象已创建"); 10 } 11 ~Line() //析构函数 12 { 13 Console.WriteLine("对象已删除"); 14 } 15 16 public void setLength( double len ) 17 { 18 length = len; 19 } 20 public double getLength() 21 { 22 return length; 23 } 24 25 static void Main(string[] args) 26 { 27 Line line = new Line(); 28 // 设置线条长度 29 line.setLength(6.0); 30 Console.WriteLine("线条的长度: {0}", line.getLength()); 31 } 32 } 33 } 34 //执行结果: 35 //对象已创建 36 //线条的长度: 6 37 //对象已删除
构造函数在创建实例时调用,析构函数在结束程序时调用。
(四)、静态成员
我们可以使用 static 关键字把类成员定义为静态的。当我们声明一个类成员为静态时,意味着无论有多少个类的对象被创建,只会有一个该静态成员的副本。
关键字 static 意味着类中只有一个该成员的实例。静态变量用于定义常量,因为它们的值可以通过直接调用类而不需要创建类的实例来获取。静态变量可在成员函数或类的定义外部进行初始化。你也可以在类的定义内部初始化静态变量。
代码示例:
1 using System; 2 namespace StaticVarApplication 3 { 4 class StaticVar 5 { 6 public static int num; 7 public void count() 8 { 9 num++; 10 } 11 public int getNum() 12 { 13 return num; 14 } 15 } 16 class StaticTester 17 { 18 static void Main(string[] args) 19 { 20 StaticVar s1 = new StaticVar(); 21 StaticVar s2 = new StaticVar(); 22 s1.count(); 23 s1.count(); 24 s1.count(); 25 s2.count(); 26 s2.count(); 27 s2.count(); 28 Console.WriteLine("s1 的变量 num: {0}", s1.getNum()); 29 Console.WriteLine("s2 的变量 num: {0}", s2.getNum()); 30 Console.ReadKey(); 31 } 32 } 33 } 34 //执行结果 35 //s1 的变量 num: 6 36 //s2 的变量 num: 6
你也可以把一个成员函数声明为 static。这样的函数只能访问静态变量。静态函数在对象被创建之前就已经存在。
代码示例:
1 using System; 2 namespace StaticVarApplication 3 { 4 class StaticVar 5 { 6 public static int num; 7 public void count() 8 { 9 num++; 10 } 11 public static int getNum() 12 { 13 return num; 14 } 15 } 16 class StaticTester 17 { 18 static void Main(string[] args) 19 { 20 StaticVar s = new StaticVar(); 21 s.count(); 22 s.count(); 23 s.count(); 24 Console.WriteLine("变量 num: {0}", StaticVar.getNum()); 25 Console.ReadKey(); 26 } 27 } 28 } 29 //执行结果: 30 //变量 num: 3
将类成员函数声明为public static无需实例化即可调用类成员函数。
反之,如果不声明为static,即使和Main方法从属于同一个类,也必须经过实例化
代码示例:
1 //示例一: 2 using System; 3 4 namespace ConsoleApp 5 { 6 class Program 7 { 8 static void Main(string[] args) 9 { 10 int num = AddClass.Add(2, 3); //编译通过 11 Console.WriteLine(num); 12 } 13 } 14 15 class AddClass 16 { 17 public static int Add(int x,int y) 18 { 19 return x + y; 20 } 21 } 22 } 23 24 //示例二: 25 using System; 26 27 namespace ConsoleApp 28 { 29 class Program 30 { 31 static void Main(string[] args) 32 { 33 int num = Add(2, 3); //编译错误,即使改为Program.Add(2, 3);也无法通过编译 34 Console.WriteLine(num); 35 } 36 37 public int Add(int x, int y) 38 { 39 return x + y; 40 } 41 } 42 } 43 using System; 44 45 namespace ConsoleApp 46 { 47 class Program 48 { 49 static void Main(string[] args) 50 { 51 Program self = new Program(); 52 int num = self.Add(2, 3); //编译通过 53 Console.WriteLine(num); 54 } 55 56 public int Add(int x, int y) 57 { 58 return x + y; 59 } 60 } 61 }
注:
我们可以使用 static 关键字把类成员定义为静态的。当我们声明一个类成员为静态时,意味着无论有多少个类的对象被创建,只会有一个该静态成员的副本。
关键字 static 意味着类中只有一个该成员的实例。静态变量用于定义常量,因为它们的值可以通过直接调用类而不需要创建类的实例来获取。静态变量可在成员函数或类的定义外部进行初始化。你也可以在类的定义内部初始化静态变量。
二、封装
封装是面向对象程序设计中最重要的概念之一。封装 被定义为"把一个或多个项目封闭在一个物理的或者逻辑的包中"。在面向对象程序设计方法论中,封装是为了防止对实现细节的访问。
抽象和封装是面向对象程序设计的相关特性。抽象允许相关信息可视化,封装则使开发者实现所需级别的抽象。
C# 封装根据具体的需要,设置使用者的访问权限,并通过 访问修饰符 来实现。
一个 访问修饰符 定义了一个类成员的范围和可见性。C# 支持的访问修饰符如下所示:
1、public:所有对象都可以访问;
2、private:对象本身在对象内部可以访问;
3、protected:只有该类对象及其子类对象可以访问
4、internal:同一个程序集的对象可以访问;
5、protected internal:访问限于当前程序集或派生自包含类的类型。
(一)、public
Public 访问修饰符允许一个类将其成员变量和成员函数暴露给其他的函数和对象。任何公有成员可以被外部的类访问。
代码示例:
1 using System; 2 namespace RectangleApplication 3 { 4 class Rectangle 5 { 6 //成员变量 7 public double length; 8 public double width; 9 10 public double GetArea() 11 { 12 return length * width; 13 } 14 public void Display() 15 { 16 Console.WriteLine("长度: {0}", length); 17 Console.WriteLine("宽度: {0}", width); 18 Console.WriteLine("面积: {0}", GetArea()); 19 } 20 }// Rectangle 结束 21 22 class ExecuteRectangle 23 { 24 static void Main(string[] args) 25 { 26 Rectangle r = new Rectangle(); 27 r.length = 4.5; 28 r.width = 3.5; 29 r.Display(); 30 Console.ReadLine(); 31 } 32 } 33 } 34 //执行结果: 35 //长度: 4.5 36 //宽度: 3.5 37 //面积: 15.75
(二)、private
Private 访问修饰符允许一个类将其成员变量和成员函数对其他的函数和对象进行隐藏。只有同一个类中的函数可以访问它的私有成员。即使是类的实例也不能访问它的私有成员。
代码示例:
1 using System; 2 namespace RectangleApplication 3 { 4 class Rectangle 5 { 6 //成员变量 7 private double length; 8 private double width; 9 10 public void Acceptdetails() 11 { 12 Console.WriteLine("请输入长度:"); 13 length = Convert.ToDouble(Console.ReadLine()); 14 Console.WriteLine("请输入宽度:"); 15 width = Convert.ToDouble(Console.ReadLine()); 16 } 17 public double GetArea() 18 { 19 return length * width; 20 } 21 public void Display() 22 { 23 Console.WriteLine("长度: {0}", length); 24 Console.WriteLine("宽度: {0}", width); 25 Console.WriteLine("面积: {0}", GetArea()); 26 } 27 }//end class Rectangle 28 class ExecuteRectangle 29 { 30 static void Main(string[] args) 31 { 32 Rectangle r = new Rectangle(); 33 r.Acceptdetails(); 34 r.Display(); 35 Console.ReadLine(); 36 } 37 } 38 } 39 //执行结果: 40 //请输入长度: 41 //4.4 42 //请输入宽度: 43 //3.3 44 //长度: 4.4 45 //宽度: 3.3 46 //面积: 14.52
(三)、protected
Protected 访问修饰符允许子类访问它的基类的成员变量和成员函数。这样有助于实现继承。我们将在继承的章节详细讨论这个。更详细地讨论这个。
(四)、internal
Internal 访问说明符允许一个类将其成员变量和成员函数暴露给当前程序中的其他函数和对象。换句话说,带有 internal 访问修饰符的任何成员可以被定义在该成员所定义的应用程序内的任何类或方法访问。
代码示例:
1 using System; 2 3 namespace RectangleApplication 4 { 5 class Rectangle 6 { 7 //成员变量 8 internal double length; 9 internal double width; 10 11 double GetArea() 12 { 13 return length * width; 14 } 15 public void Display() 16 { 17 Console.WriteLine("长度: {0}", length); 18 Console.WriteLine("宽度: {0}", width); 19 Console.WriteLine("面积: {0}", GetArea()); 20 } 21 }//end class Rectangle 22 class ExecuteRectangle 23 { 24 static void Main(string[] args) 25 { 26 Rectangle r = new Rectangle(); 27 r.length = 4.5; 28 r.width = 3.5; 29 r.Display(); 30 Console.ReadLine(); 31 } 32 } 33 } 34 //执行结果: 35 //长度: 4.5 36 //宽度: 3.5 37 //面积: 15.75
(五)、protected internal
Protected Internal 访问修饰符允许在本类,派生类或者包含该类的程序集中访问。这也被用于实现继承。
注:
-
-
- Pubilc :任何公有成员可以被外部的类访问。
- Private :只有同一个类中的函数可以访问它的私有成员。
- Protected :该类内部和继承类中可以访问。
- internal : 同一个程序集的对象可以访问。
- Protected internal :3 和 4 的并集,符合任意一条都可以访问。
-
1、范围比较:private < internal/protected < protected internal < public
2、public 和 internal 修饰符的区别:
一个是国际妓女,谁用都可以,就是 public,一个是不懂外语的,只能在国内做生意,就是 internal,只能在当前所在的工程里面引用。你建两个工程,建立一个引用关系,被引用的里面建两个类,就能区别了。用 VS 的话,你根本点不出来那个不懂外语的。
三、继承
继承是面向对象程序设计中最重要的概念之一。继承允许我们根据一个类来定义另一个类,这使得创建和维护应用程序变得更容易。同时也有利于重用代码和节省开发时间。
当创建一个类时,程序员不需要完全重新编写新的数据成员和成员函数,只需要设计一个新的类,继承了已有的类的成员即可。这个已有的类被称为的基类,这个新的类被称为派生类。
继承的思想实现了 属于(IS-A) 关系。例如,哺乳动物 属于(IS-A) 动物,狗 属于(IS-A) 哺乳动物,因此狗 属于(IS-A) 动物。
基类与派生类:一个类可以派生自多个类或接口,这意味着它可以从多个基类或接口继承数据和函数。 - 父类与子类。
(一)、基类的初始化
派生类继承了基类的成员变量和成员方法。因此父类对象应在子类对象创建之前被创建。您可以在成员初始化列表中进行父类的初始化。
代码示例:
1 using System; 2 namespace RectangleApplication 3 { 4 class Rectangle 5 { 6 // 成员变量 7 protected double length; 8 protected double width; 9 public Rectangle(double l, double w) 10 { 11 length = l; 12 width = w; 13 } 14 public double GetArea() 15 { 16 return length * width; 17 } 18 public void Display() 19 { 20 Console.WriteLine("长度: {0}", length); 21 Console.WriteLine("宽度: {0}", width); 22 Console.WriteLine("面积: {0}", GetArea()); 23 } 24 }//end class Rectangle 25 class Tabletop : Rectangle 26 { 27 private double cost; 28 public Tabletop(double l, double w) : base(l, w) 29 { } 30 public double GetCost() 31 { 32 double cost; 33 cost = GetArea() * 70; 34 return cost; 35 } 36 public void Display() 37 { 38 base.Display(); 39 Console.WriteLine("成本: {0}", GetCost()); 40 } 41 } 42 class ExecuteRectangle 43 { 44 static void Main(string[] args) 45 { 46 Tabletop t = new Tabletop(4.5, 7.5); 47 t.Display(); 48 Console.ReadLine(); 49 } 50 } 51 } 52 //执行结果 53 //长度: 4.5 54 //宽度: 7.5 55 //面积: 33.75 56 //成本: 2362.5
(二)、多重继承
多重继承指的是一个类别可以同时从多于一个父类继承行为与特征的功能。与单一继承相对,单一继承指一个类别只可以继承自一个父类。
C# 不支持多重继承。但是,您可以使用接口来实现多重继承。
代码示例:
1 using System; 2 namespace InheritanceApplication 3 { 4 class Shape 5 { 6 public void setWidth(int w) 7 { 8 width = w; 9 } 10 public void setHeight(int h) 11 { 12 height = h; 13 } 14 protected int width; 15 protected int height; 16 } 17 18 // 基类 PaintCost 19 public interface PaintCost 20 { 21 int getCost(int area); 22 23 } 24 // 派生类 25 class Rectangle : Shape, PaintCost 26 { 27 public int getArea() 28 { 29 return (width * height); 30 } 31 public int getCost(int area) 32 { 33 return area * 70; 34 } 35 } 36 class RectangleTester 37 { 38 static void Main(string[] args) 39 { 40 Rectangle Rect = new Rectangle(); 41 int area; 42 Rect.setWidth(5); 43 Rect.setHeight(7); 44 area = Rect.getArea(); 45 // 打印对象的面积 46 Console.WriteLine("总面积: {0}", Rect.getArea()); 47 Console.WriteLine("油漆总成本: ${0}" , Rect.getCost(area)); 48 Console.ReadKey(); 49 } 50 } 51 } 52 //执行结果: 53 //总面积: 35 54 //油漆总成本: $2450
注:
为什么一个对象可以用父类声明,却用子类实例化?
这个实例是子类的,但是因为你声明时是用父类声明的,所以你用正常的办法访问不到子类自己的成员,只能访问到从父类继承来的成员。
在子类中用 override 重写父类中用 virtual 申明的虚方法时,实例化父类调用该方法,执行时调用的是子类中重写的方法;
如果子类中用 new 覆盖父类中用 virtual 申明的虚方法时,实例化父类调用该方法,执行时调用的是父类中的虚方法;
1 /// <summary> 2 /// 父类 3 /// </summary> 4 public class ParentClass 5 { 6 public virtual void ParVirMethod() 7 { 8 Console.WriteLine("父类的方法..."); 9 } 10 } 11 12 /// <summary> 13 /// 子类1 14 /// </summary> 15 public class ChildClass1 : ParentClass 16 { 17 public override void ParVirMethod() 18 { 19 Console.WriteLine("子类1的方法..."); 20 } 21 } 22 23 /// <summary> 24 /// 子类2 25 /// </summary> 26 public class ChildClass2 : ParentClass 27 { 28 public new void ParVirMethod() 29 { 30 Console.WriteLine("子类2的方法..."); 31 } 32 33 public void Test() 34 { 35 Console.WriteLine("子类2的其他方法..."); 36 } 37 } 38 39 //执行调用: 40 ParentClass par = new ChildClass1(); 41 par.ParVirMethod(); //结果:"子类1的方法",调用子类的方法,实现了多态 42 43 par = new ChildClass2(); 44 par.ParVirMethod(); //结果:"父类的方法",调用父类的方法,没有实现多态
深究其原因,为何两者不同,是因为原理不同: override是重写,即将基类的方法在派生类里直接抹去重新写,故而调用的方法就是子类方法;而new只是将基类的方法在派生类里隐藏起来,故而调用的仍旧是基类方法。
应用举例:
有这样的需要,比如 People 类有一个 Run 方法,Man 和 Woman 这两个类都是继承自 People 的类,并且都重写(override)了 Run 这个方法(男人女人跑起步来不一样)。
现在有一群人一起跑步,有男人有女人。
我们可以把这些都装进一个People数组(假设为peoples)。
然后:
foreach(People p in peoples) // peoples中对象不同(即有男有女),用于实例化的子类就不同。 { p.Run(); // 故而,调用的方法也不同,实现了多态 }
由于多态性,在调用 p.Run() 的时候 p 对象本身如果是男人就会自动调用男人的 Run 方法,是女人就会调用女人的 Run 方法。
依赖倒置原则
依赖倒置原则,DIP,Dependency Inverse Principle DIP的表述是:
1、高层模块不应该依赖于低层模块, 二者都应该依赖于抽象。
2、抽象不应该依赖于细节,细节应该依赖于抽象。
这里说的“依赖”是使用的意思,如果你调用了一个类的一个方法,就是依赖这个类,如果你直接调用这个类的方法,就是依赖细节,细节就是具体的类,但如果你调用的是它父类或者接口的方法,就是依赖抽象, 所以 DIP 说白了就是不要直接使用具体的子类,而是用它的父类的引用去调用子类的方法,这样就是依赖于抽象,不依赖具体。
其实简单的说,DIP 的好处就是解除耦合,用了 DIP 之后,调用者就不知道被调用的代码是什么,因为调用者拿到的是父类的引用,它不知道具体指向哪个子类的实例,更不知道要调用的方法具体是什么,所以,被调用代码被偷偷换成另一个子类之后,调用者不需要做任何修改, 这就是解耦了。
四、多态
多态性意味着有多重形式。在面向对象编程范式中,多态性往往表现为"一个接口,多个功能"。
多态性可以是静态的或动态的。在静态多态性中,函数的响应是在编译时发生的。在动态多态性中,函数的响应是在运行时发生的。
(一)、静态多态性
在编译时,函数和对象的连接机制被称为早期绑定,也被称为静态绑定。C# 提供了两种技术来实现静态多态性。分别为:
-
-
- 函数重载
- 运算符重载
-
运算符重载将在下一章节讨论,接下来我们将讨论函数重载。
(二)、函数重载
您可以在同一个范围内对相同的函数名有多个定义。函数的定义必须彼此不同,可以是参数列表中的参数类型不同,也可以是参数个数不同。不能重载只有返回类型不同的函数声明。下面的实例演示了几个相同的函数 print(),用于打印不同的数据类型:
代码示例:
1 using System; 2 namespace PolymorphismApplication 3 { 4 class Printdata 5 { 6 void print(int i) 7 { 8 Console.WriteLine("Printing int: {0}", i ); 9 } 10 11 void print(double f) 12 { 13 Console.WriteLine("Printing float: {0}" , f); 14 } 15 16 void print(string s) 17 { 18 Console.WriteLine("Printing string: {0}", s); 19 } 20 static void Main(string[] args) 21 { 22 Printdata p = new Printdata(); 23 // 调用 print 来打印整数 24 p.print(5); 25 // 调用 print 来打印浮点数 26 p.print(500.263); 27 // 调用 print 来打印字符串 28 p.print("Hello C++"); 29 Console.ReadKey(); 30 } 31 } 32 } 33 //执行结果: 34 //Printing int: 5 35 //Printing float: 500.263 36 //Printing string: Hello C++
(三)、动态多态性
C# 允许您使用关键字 abstract 创建抽象类,用于提供接口的部分类的实现。当一个派生类继承自该抽象类时,实现即完成。抽象类包含抽象方法,抽象方法可被派生类实现。派生类具有更专业的功能。
请注意,下面是有关抽象类的一些规则:
-
-
- 您不能创建一个抽象类的实例。
- 您不能在一个抽象类外部声明一个抽象方法。
- 通过在类定义前面放置关键字 sealed,可以将类声明为密封类。当一个类被声明为 sealed 时,它不能被继承。抽象类不能被声明为 sealed。
-
代码示例:
1 using System; 2 namespace PolymorphismApplication 3 { 4 abstract class Shape 5 { 6 abstract public int area(); 7 } 8 class Rectangle: Shape 9 { 10 private int length; 11 private int width; 12 public Rectangle( int a=0, int b=0) 13 { 14 length = a; 15 width = b; 16 } 17 public override int area () 18 { 19 Console.WriteLine("Rectangle 类的面积:"); 20 return (width * length); 21 } 22 } 23 24 class RectangleTester 25 { 26 static void Main(string[] args) 27 { 28 Rectangle r = new Rectangle(10, 7); 29 double a = r.area(); 30 Console.WriteLine("面积: {0}",a); 31 Console.ReadKey(); 32 } 33 } 34 } 35 //执行结果: 36 //Rectangle 类的面积: 37 //面积: 70
当有一个定义在类中的函数需要在继承类中实现时,可以使用虚方法。虚方法是使用关键字 virtual 声明的。虚方法可以在不同的继承类中有不同的实现。对虚方法的调用是在运行时发生的。
动态多态性是通过 抽象类 和 虚方法 实现的。
代码示例:
1 using System; 2 namespace PolymorphismApplication 3 { 4 class Shape 5 { 6 protected int width, height; 7 public Shape( int a=0, int b=0) 8 { 9 width = a; 10 height = b; 11 } 12 public virtual int area() 13 { 14 Console.WriteLine("父类的面积:"); 15 return 0; 16 } 17 } 18 class Rectangle: Shape 19 { 20 public Rectangle( int a=0, int b=0): base(a, b) 21 { 22 23 } 24 public override int area () 25 { 26 Console.WriteLine("Rectangle 类的面积:"); 27 return (width * height); 28 } 29 } 30 class Triangle: Shape 31 { 32 public Triangle(int a = 0, int b = 0): base(a, b) 33 { 34 35 } 36 public override int area() 37 { 38 Console.WriteLine("Triangle 类的面积:"); 39 return (width * height / 2); 40 } 41 } 42 class Caller 43 { 44 public void CallArea(Shape sh) 45 { 46 int a; 47 a = sh.area(); 48 Console.WriteLine("面积: {0}", a); 49 } 50 } 51 class Tester 52 { 53 54 static void Main(string[] args) 55 { 56 Caller c = new Caller(); 57 Rectangle r = new Rectangle(10, 7); 58 Triangle t = new Triangle(10, 5); 59 c.CallArea(r); 60 c.CallArea(t); 61 Console.ReadKey(); 62 } 63 } 64 } 65 //执行结果: 66 //Rectangle 类的面积: 67 //面积:70 68 //Triangle 类的面积: 69 //面积:25
注:
1、virtual和abstract都是用来修饰父类的,通过覆盖父类的定义,让子类重新定义。
-
-
- virtual修饰的方法必须有实现(哪怕是仅仅添加一对大括号),而abstract修饰的方法一定不能实现。
- virtual可以被子类重写,而abstract必须被子类重写。
- 如果类成员被abstract修饰,则该类前必须添加abstract,因为只有抽象类才可以有抽象方法。
- 无法创建abstract类的实例,只能被继承无法实例化。
-
2、overload和override
重载(overload)是提供了一种机制, 相同函数名通过不同的返回值类型以及参数来表来区分的机制。
重写(override)是用于重写基类的虚方法,这样在派生类中提供一个新的方法。
3、抽象方法和虚方法
-
-
- 虚方法必须有实现部分,抽象方法没有提供实现部分,抽象方法是一种强制派生类覆盖的方法,否则派生类将不能被实例化。
- 抽象方法只能在抽象类中声明,虚方法不是。如果类包含抽象方法,那么该类也是抽象的,也必须声明类是抽象的。
- 抽象方法必须在派生类中重写,这一点和接口类似,虚方法不需要再派生类中重写。
-
简单说,抽象方法是需要子类去实现的。虚方法是已经实现了的,可以被子类覆盖,也可以不覆盖,取决于需求。抽象方法和虚方法都可以供派生类重写。
对于面向对象的特性,推荐几篇不错的文章:
https://www.cnblogs.com/harrogath/p/6445793.html
https://www.cnblogs.com/autumn001/p/9036148.html
五、下期预告-敬请期待
->运算符重载
->接口
->命名空间
->预处理器指令
->正则表达式
->异常处理
->文件的输入输出
参考文献:https://www.runoob.com/csharp/csharp-encapsulation.html
根据w3school自我温习一下c#基础,分享给大家。