提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
多态的实现
一、类的封装与继承
1.属性封装
C#提供了属性(property)这个更好的方法,把字段域和访问它们的方法相结合。对类的用户而言,属性值的读/写与字段域语法相同;对编译器来说,属性值的读/写是通过类中封装的特别方法get访问器和set访问器实现的。
属性的声明方法如下:
[属性集信息] [属性修饰符] 类型 成员名
{
访问器声明
}
其中:
属性修饰符—与方法修饰符相同,包括 new、static、virtual、abstract、override 和 4种访问修饰符的合法组合,它们遵循相同的规则。
类型—指定该声明所引入的属性的类型。
成员名—指定该属性的名称。
访问器声明——声明属性的访问器,可以是一个 get 访问器或一个 set 访问器,或者两个都有。
语法形式:
get // 读访问器
{
… // 访问器语句块
}
set // 写访问器
{
… // 访问器语句块
}
get 访问器的返回值类型与属性的类型相同,所以在语句块中的 return 语句必须有一个可隐式转换为属性类型的表达式。
set 访问器没有返回值,但它有一个隐式的值参数,其名称为 value,它的类型与属性的类型相同
using System;
namespace ConsoleApp3
{
class TextBox
{
private string text;
private string fontname;
private int fontsize;
private bool multiline;
public TextBox()
{
text = "text1";
fontname = "宋体";
fontsize = 12;
multiline = false;
}
public string Text
{
// Text属性,可读可写
get
{ return text; }
set
{ text = value; }
}
public string FontName
{
// FontName属性,只读属性
get
{ return fontname; }
}
public int FontSize
{
// FontSize属性,可读可写
get
{ return fontsize; }
set
{ fontsize = value; }
}
public bool MultiLine
{
// MultiLine属性,只写
set
{ multiline = value; }
}
}
class Test
{
static void Main(string[] args)
{
TextBox Text1 = new TextBox();
// 调用Text属性的get访问器
Console.WriteLine("Text1.Text= {0} ", Text1.Text);
// 调用Text属性的set访问器
Text1.Text = "这是文本框";
Console.WriteLine("Text1.Text= {0} ", Text1.Text);
// 调用FontName属性的get访问器
Console.WriteLine("Text1.Fontname= {0} ", Text1.FontName);
// 调用FontSize属性的set访问器
Text1.FontSize = 36;
// 调用FontSize属性的get访问器
Console.WriteLine("Text1.FontSize= {0} ", Text1.FontSize);
// 调用MultiLine属性的set访问器
Text1.MultiLine = true;
Console.Read();
}
}
}
属性是字段的自然扩展,当然属性也可作为特殊的方法使用,并不要求它和字段域一一对应,所以属性还可以用于各种控制和计算。
using System;
namespace ConsoleApp3
{
class Point
{
int x, y;
public int X
{
get
{ return x; }
}
public int Y
{
get
{ return y; }
}
public Point()
{ x = y = 0; }
public Point(int x, int y)
{
this.x = x;
this.y = y;
}
}
class Label
{
Point p1 = new Point();
Point p2 = new Point(5, 10);
public int Width // 计算两点之间在水平坐标轴上的投影长度
{
get
{
return p2.X - p1.X;
}
}
public int Height // 计算两点之间在垂直坐标轴上的投影长度
{
get
{
return p2.Y - p1.Y;
}
}
}
class Test
{
static void Main(string[] args)
{
Label Label1 = new Label();
Console.WriteLine("Label1.Width= {0} ", Label1.Width);
Console.WriteLine("Label1.Height= {0} ", Label1.Height);
Console.Read();
}
}
}
2.类的继承
代码如下(示例):
using System;
namespace ConsoleApp3
{ // 定义基类Shape
public class Shape
{
protected string Color;
public Shape() {}
public Shape(string Color)
{
this.Color = Color;
}
public string GetColor()
{
return Color;
}
}
// 定义Circle类,从Shape类中派生
public class Circle : Shape
{
private double Radius;
public Circle(string Color, double Radius)
{
this.Color = Color;
this.Radius = Radius;
}
public double GetArea()
{
return System.Math.PI * Radius * Radius;
}
}
// 派生类Rectangular,从Shape类中派生
public class Rectangular : Shape
{
protected double Length, Width;
public Rectangular()
{
Length = Width = 0;
}
public Rectangular(string Color, double Length, double Width)
{
this.Color = Color;
this.Length = Length;
this.Width = Width;
}
public double AreaIs()
{
return Length * Width;
}
public double PerimeterIs() // 周长
{
return (2 * (Length + Width));
}
}
// 派生类Square,从 Rectangular类中派生
public class Square : Rectangular
{
public Square(string Color, double Side)
{
this.Color = Color;
this.Length = this.Width = Side;
}
}
class TestInheritance
{
static void Main(string[] args)
{
Circle Cir = new Circle("orange", 3.0);
Console.WriteLine("Circle color is {0},Circle area is {1}", Cir.GetColor(), Cir.GetArea());
Rectangular Rect = new Rectangular("red", 13.0, 2.0);
Console.WriteLine("Rectangualr color is {0},Rectangualr area is {1},Rectangular perimeter is {2}",Rect.GetColor(), Rect.AreaIs(), Rect.PerimeterIs());
Square Squ = new Square("green", 5.0);
Console.WriteLine("Square color is {0},Square Area is {1}, Square perimeter is {2}", Squ.GetColor(),Squ.AreaIs(), Squ.PerimeterIs());
}
}
}
using System;
namespace ConsoleApp3
{ // 定义基类Shape
public class Person
{
protected string Phone = "444-555-666";
protected string Name = "李明";
public void GetInfoPerson()
{
Console.WriteLine("Phone: {0}", Phone);
Console.WriteLine("Name: {0}", Name);
}
}
class Employee : Person
{
public string ID = "ABC567EFG";
public void GetInfoEmployee()
{
// 调用基类Person的GetInfo方法
base.GetInfoPerson();
Console.WriteLine("Employee ID: {0}", ID);
}
}
class TestClass
{
static void Main(string[] args)
{
Employee Employees = new Employee();
Employees.GetInfoEmployee();
}
}
}
3.派生类的构造函数
当创建派生类的对象时,会展开一个链式的构造函数调用,在这个过程中,派生类构造函数在执行它自己的函数体之前,首先显式或隐式地调用基类构造函数。类似地,如果这个基类也是从另一个类派生而来的,那么这个基类的构造函数在执行之前也会先调用它的基类构造函数,以此类推,直到Object类的构造函数被调用为止。
using System;
namespace ConsoleApp3
{
public class Grandsire
{
public Grandsire()
{
Console.WriteLine("调用Grandsire的构造函数");
}
}
public class Father : Grandsire
{
public Father()
{
Console.WriteLine("调用Father的构造函数");
}
}
public class Son : Father
{
public Son()
{
Console.WriteLine("调用Son的构造函数");
}
}
class BaseLifeSample
{
static void Main(string[] args)
{
Son s1 = new Son();
Console.Read();
}
}
}
下面程序描述了派生类构造函数的格式,以及在初始化对象时构造函数的调用次序。
using System;
namespace ConsoleApp3
{
class Point
{
private int x, y;
public Point()
{
x = 0; y = 0;
Console.WriteLine("Point() constructor : {0} ", this);
}
public Point(int x, int y)
{
this.x = x;
this.y = y;
Console.WriteLine("Point(x,y) constructor : {0} ", this);
}
}
class Circle : Point
{
private double radius;
public Circle() // 默认约定调用基类的无参构造函数Point()
{
Console.WriteLine("Circle () constructor : {0} ", this);
}
public Circle(double radius) : base()
{
this.radius = radius;
Console.WriteLine("Circle (radius) constructor : {0} ", this);
}
public Circle(int x, int y, double radius) : base(x, y)
{
this.radius = radius;
Console.WriteLine("Circle (x, y, radius) constructor : {0} ", this);
}
}
class Test
{
static void Main(string[] args)
{
Point a = new Point();
Circle b = new Circle(3.5);
Circle c = new Circle(1, 1, 4.8);
Console.Read();
}
}
}
二、多态的实现
1.方法重载
一个方法的名字、形参个数、修饰符及类型共同构成了这个方法的签名,应用中经常需要为同名的方法提供不同的实现,如果一个类中有两个或两个以上的方法同名,但它们的形参个数或类型有所不同,这是允许的,这属于不同的方法签名。若仅仅是返回类型不同的同名方法,编译器是不能识别的。下面通过一个例子来介绍方法的重载。
using System;
namespace ConsoleApp3
{
class Myclass // 该类中有4个不同版本的max方法
{
public int max(int x, int y)
{
return x >= y ? x : y;
}
public double max(double x, double y)
{
return x >= y ? x : y;
}
public int max(int x, int y, int z)
{
return max(max(x, y), z);
}
public double max(double x, double y, double z)
{
return max(max(x, y), z);
}
}
class Test
{
static void Main(string[] args)
{
Myclass m = new Myclass();
int a, b, c;
double e, f, g;
a = 10; b = 20; c = 30;
e = 1.5; f = 3.5; g = 5.5;
// 调用方法时,编译器会根据实参的类型和个数调用不同的方法
Console.WriteLine("max({0},{1})= {2} ", a, b, m.max(a, b));
Console.WriteLine("max({0},{1},{2})= {3} ", a, b, c, m.max(a, b, c));
Console.WriteLine("max({0},{1})= {2} ", e, f, m.max(e, f));
Console.WriteLine("max({0},{1},{2})= {3} ", e, f, g, m.max(e, f, g));
Console.Read();
}
}
}
2.运算符重载
运算符重载包括一元运算符重载、二元运算符重载以及用户定义的数据类型转换,下面简单介绍前两种情况。
如果有一个复数Complex类对一元运算符“++”重载,可以写成:
public static Complex operator ++(Complex a)
{
//TODO…
}
对二元运算符“+”可以写成:
public static Complex operator +(Complex a, Complex b)
{
//TODO…
}
一元运算符有一个参数,二元运算符有两个参数。重载运算符必须以 public static 修饰符开始。
可以重载的运算符包括:
一元运算符——+ ! ~ ++ true false;
二元运算符——+ * / % & | ^ << >> == != > < >= <=。
using System;
namespace ConsoleApp3
{
class Complex
{
double r, v; //r+ v i
public Complex(double r, double v)
{
this.r = r;
this.v = v;
}
// 二元运算符“+”重载
public static Complex operator +(Complex a, Complex b)
{
return new Complex(a.r + b.r, a.v + b.v);
}
// 一元运算符“-”重载
public static Complex operator -(Complex a)
{
return new Complex(-a.r, -a.v);
}
// 一元运算符“++”重载
public static Complex operator ++(Complex a)
{
double r = a.r + 1;
double v = a.v + 1;
return new Complex(r, v);
}
public void Print()
{
Console.Write(r + " + " + v + "i\n");
}
}
class Test
{
static void Main(string[] args)
{
Complex a = new Complex(3, 4);
Complex b = new Complex(5, 6);
Complex c = -a;
c.Print();
Complex d = a + b;
d.Print();
a.Print();
Complex e = a++; // 先赋值后++
a.Print();
e.Print();
Complex f = ++a; // 先++后赋值
a.Print();
f.Print();
}
}
}
三、虚拟方与方法覆盖
1.继承中的方法隐藏及其局限
【例】 试用隐藏基类成员方法的方式,在运行时执行指定派生类方法的功能。
本例定义了一个基类 Shape,含有字段域 width 和 height,分别表示形状的宽和高,并定义了一个 area 方法,用来求形状的面积。它的派生类Triangle(三角形)和Trapezia(梯形)都用关键字new修饰了 area 方法,表明这是有意隐藏该方法的。若不加new修饰,为了警示,编译器会发出警告信息。
using System;
namespace ConsoleApp3
{
class Shape
{
protected double width;
protected double height;
public Shape()
{ width = height = 0; }
public Shape(double x)
{ width = height = x; }
public Shape(double w, double h)
{
width = w;
height = h;
}
public double area()
{ return width * height; }
}
class Triangle : Shape // 三角形
{
public Triangle(double x, double y) : base(x, y) { }
new public double area() // 加new隐藏基类的area方法
{
return width * height / 2;
}
}
class Trapezia : Shape // 梯形
{
double width2;
public Trapezia(double w1, double w2, double h) : base(w1, h)
{
width2 = w2;
}
new public double area() // 加new隐藏基类的area方法
{
return (width + width2) * height / 2;
}
}
class Test
{
static void Main(string[] args)
{
Shape A = new Shape(2, 4);
Triangle B = new Triangle(1, 2);
Trapezia C = new Trapezia(2, 3, 4);
Console.WriteLine("A.area= {0} ", A.area()); // 调Shape的area方法
Console.WriteLine("B.area= {0} ", B.area()); // 调Triangle的area方法
Console.WriteLine("C.area= {0} ", C.area()); // 调Trapezia的area方法
A = B;
Console.WriteLine("A.area= {0} ", A.area()); // 试图调Triangle的area方法失败
A = C;
Console.WriteLine("A.area= {0} ", A.area()); // 试图调Trapezia的area方法失败
Console.Read();
}
}
}
上段代码本来是想分别调用 Triangle 的 area 方法计算三角形面积,调用 Trapezia 的 area 方法计算梯形面积,但程序执行的仍然是基类的 area 方法,只计算了抽象的“形状面积”:
A.area=width * height=1×2=2≠width * height / 2
A.area=width * height=2×4=8≠(width + width2) * height / 2
2.虚方法覆盖技术
(1)虚方法的重载
在类的方法前加上关键字 virtual,就声明了一个虚方法。通过对虚方法的重载,实现在程序运行过程中确定调用的方法。
【例】 虚方法的重载。
在 A 类定义中提供了非虚的方法 F 和虚方法 G,派生类 B 对方法 F 进行隐藏,而对虚方法 G 则使用 override 关键字实现了覆盖。这样一来,语句“A a = b”中的a就仍是一个b对象.
using System;
namespace ConsoleApp3
{
class A
{
public void F() { Console.WriteLine("A.F"); }
public virtual void G() { Console.WriteLine("A.G"); }
}
class B : A
{
new public void F() { Console.WriteLine("B.F"); }
public override void G() { Console.WriteLine("B.G"); }
}
class Test
{
static void Main(string[] args)
{
B b = new B();
A a = b;
a.F();
b.F();
a.G();
b.G();
Console.Read();
}
}
}
(2)派生类方法覆盖基类方法
下面通过虚方法重载的机制,利用覆盖技术实现调用派生类方法计算三角形和梯形面积。
【例】Shape类中的方法area用virtual修饰,而在派生类Triangle和Trapezia中用关键字override修饰area方法,这样就可以在程序运行时决定调用哪个类的area方法。
using System;
namespace ConsoleApp3
{
class Shape
{
protected double width;
protected double height;
public Shape()
{ width = height = 0; }
public Shape(double x)
{ width = height = x; }
public Shape(double w, double h)
{
width = w;
height = h;
}
public virtual double area() //virtual
{ return width * height; }
}
class Triangle : Shape // 三角形
{
public Triangle(double x, double y) : base(x, y) { }
public override double area() //override
{
return width * height / 2;
}
}
class Trapezia : Shape // 梯形
{
double width2;
public Trapezia(double w1, double w2, double h) : base(w1, h)
{
width2 = w2;
}
public override double area() //override
{
return (width + width2) * height / 2;
}
}
class Test
{
static void Main(string[] args)
{
Shape A = new Shape(2, 4);
Triangle B = new Triangle(1, 2);
Trapezia C = new Trapezia(2, 3, 4);
Console.WriteLine("A.area= {0} ", A.area()); // 调Shape的area方法
Console.WriteLine("B.area= {0} ", B.area()); // 调Triangle的area方法
Console.WriteLine("C.area= {0} ", C.area()); // 调Trapezia的area方法
A = B;
Console.WriteLine("A.area= {0} ", A.area()); // 试图调Triangle的area方法成功
A = C;
Console.WriteLine("A.area= {0} ", A.area()); // 试图调Trapezia的area方法成功
Console.Read();
}
}
}
四、抽象类与抽象方法
抽象类是一种特殊的基类,并不与具体的事物相联系。抽象类的定义使用关键字abstract。将“图形”定义为抽象类,并由它派生出“圆形”、“四边形”这样一些可以产生具体实例的普通类。需要注意的是,抽象类不能被实例化,它只能作为其他类的基类。
将Shape类定义为抽象类:
public abstract class Shape
{
//TODO…
}
在抽象类中也可以使用关键字 abstract 定义抽象方法,要求所有的派生非抽象类都要重载实现该方法。引入抽象方法的原因在于,抽象类本身是一种抽象的概念,有的方法并不要求具体的实现,而是让派生类去重载实现。Shape 类中 GetArea 方法本身没什么具体的意义,而只有到了派生类Circle类和Rectangular类才可以计算具体的面积。
抽象方法的语法:
public abstract double GetArea();
则派生类重载实现为:
public override double GetArea()
{
//TODO…
}
【例】 抽象类和抽象方法的实现。
using System;
namespace ConsoleApp3
{
// 定义抽象基类Shape
public abstract class Shape
{
protected string Color;
public Shape() { }
public Shape(string Color)
{
this.Color = Color;
}
public string GetColor()
{
return Color;
}
public abstract double GetArea(); // 定义抽象方法
}
// 定义Circle类,从Shape类中派生
public class Circle : Shape
{
private double Radius;
public Circle(string Color, double Radius)
{
this.Color = Color;
this.Radius = Radius;
}
// 实现抽象方法
public override double GetArea()
{
return System.Math.PI * Radius * Radius;
}
}
// 定义Rectangular类,从Shape类中派生
public class Rectangular : Shape
{
protected double Length, Width;
public Rectangular(string Color, double Length, double Width)
{
this.Color = Color;
this.Length = Length;
this.Width = Width;
}
// 实现抽象方法
public override double GetArea()
{
return Length * Width;
}
public double PerimeterIs() // 周长
{
return (2 * (Length + Width));
}
}
// 派生类Square,从Rectangular类中派生
public class Square : Rectangular
{
public Square(string Color, double Side) : base(Color, Side, Side) { }
}
class TestAbstract
{
static void Main(string[] args)
{
Circle Cir = new Circle("orange", 3.0);
Console.WriteLine("Circle color is {0},Circle area is {1}", Cir.GetColor(), Cir.GetArea());
Rectangular Rect = new Rectangular("red", 13.0, 2.0);
Console.WriteLine("Rectangualr color is {0},Rectangualr area is {1},Rectangular perimeter is { 2 }",Rect.GetColor(), Rect.GetArea(), Rect.PerimeterIs()); Square Squ = new Square("green", 5.0);
Console.WriteLine("Square color is {0},Square Area is {1}, Square perimeter is { 2 }",Squ.GetColor(), Squ.GetArea(), Squ.PerimeterIs());
Console.Read();
}
}
}
抽象类只能作为基类,由其他类继承,不能被实例化。相对应地还有一种不能被其他类继承的类,叫密封类,使用sealed关键字定义。如将Rectangular类定义为密封类:
public sealed class Rectangular:Shape
{
// TODO…
}
这样Rectangular类中的派生类Square将不再保留,否则,程序编译时会报错。