资源描述
C#网络应用编程基础,主讲教师:李璟 联系电话:13806419626 E-mail: ,第三讲 C#程序设计基础(二),理解和掌握C#面向对象基本编程方法。 掌握继承的编程方法。 掌握虚方法多态性编程。 重点和难点:C#面向对象基本编程方法、继承编程方法、虚方法多态性编程。,补充:C#数组的声明与使用,数组用于存储一组同类型的数据。 数组是按照数组名、元素类型和维数来描述的。一维数组在编程中经常用到,下面仅介绍C#中一维数组的声明与使用。 (1)一维数组的声明 语法为:数据类型 数组名; 如:int myArray; 注意:区分C语言的数组定义语法为: 数据类型 数组名元素个数; 如:int myArray10;,(2)一维数组的初始化,C#语言中数组在访问之前必须先初始化。不能像下面这样访问数组或给数组赋值: int myIntArray; /只声明但没有初始化。 myIntArray10=5;/未初始化,不能访问数组元素 数组的初始化有三种方式: 以字面形式指定数组的完整内容。 如:string strArray1=C, C+, C#; Int intArr2=5,6,7; 指定数组的大小,并使用关键字new初始化所有的数组元素,会自动给所有元素赋予同一个默认值。 如: string strArray2=new string3; int intArr1=new int4; 默认值规则如下: 数值型:如int、double等,初始化为0。 bool类型:初始化为false. 引用类型:初始化为null。,一维数组的初始化(续),还可以使用以上两种方式的组合,使用该方式,数组大小必须与元素个数相匹配。如: int intArr1=new int40,1,2,3; string mystring=new string3 “first”,”second”,”third”;,(3)一维数组的使用,数组元素的下标是从0开始索引的。如,访问共有32个元素数组integers中的单个元素时, integers0=23; /为第1个元素赋值 integers31=67; /为第32个元素赋值 integersi=90; /为第i+1个元素赋值 在C#中,由Array类提供创建、操作、搜索和排序数组的方法,因而在公共语言运行库中用作所有数组的基类。 Array类包括许多方法和属性用于数组的操作,其中经常使用的一个重要属性是数组的长度Length。调用形式:数组名.Length。如: int Integers=2,6,-1,10,12; int ArrayLength=Integers.Length;,使用数组举例,static void Main(string args) string friendNames=Robert, Mike, Bob; int i; Console.WriteLine(Here are 0 of my friends:,friendNames.Length); for (i=0;ifriendNames.Length;i+) Console.WriteLine(friendNamesi); Console.ReadLine(); 运行结果: Here are 3 of my friends. Robert Mike Bob,3 C#程序设计基础(二),3.1C#类和对象的概念、定义和使用 3.2C#面向对象高级编程,3.1C#类和对象的概念、定义和使用,3.1.1类的声明 3.1.2类成员 3.1.3访问修饰符 3.1.4字段 3.1.5构造函数 3.1.6方法 3.1.7属性 3.1.8类和对象定义和使用举例,3.1.1 类的声明,用class定义类,声明类的简单形式为: class 类名 /类体 字段声明 构造函数 属性 方法 ,类声明举例 /下例声明了两个类,一个是Child类,另一个是用于数据处理的Program类。 class Child private string name; /字段 private string sex; /字段 / 构造函数 public Child(string name, string sex) this.name = name; this.sex=sex; /方法 public void PrintChild() Console.WriteLine(0, 1., name,sex); ,class Program static void Main(string args) Child child1=new Child(亮亮,男); child1.PrintChild(); Console.ReadLine(); 运行结果: 亮亮,男.,3.1.2 类成员,在类中定义的数据和函数统称为类的成员。类的成员包括数据成员和函数成员。 数据成员:是指在类中定义的数据,包括常量和字段。 函数成员:是指在类中定义的用于操作类中数据的函数,主要包括构造函数、方法、属性。,类的常用成员描述,3.1.2类成员,类成员按是否为类所有对象共有,又分为静态成员和实例成员。 静态成员:属于类,为类所有对象所共有,在内存中只有一份,要等到应用程序结束时才会退出内存。定义时,有static关键字。如: public static int j;/静态字段 public static int GetNextSerialNo/静态方法 实例成员:属于类的实例,是只有创建了类的实例才能够使用的成员。定义时,没有static关键字,如: public int j; /实例字段 public int GetSerialNo() /实例方法,3.1.3 访问修饰符,类是封装了数据及施加于数据上的操作的封装体,其封装性体现在外部类对本类成员的访问权限是受到限制的,这种限制通过本类成员的访问修饰符来实现。 C#语言中主要有以下几种访问修饰符。,类成员访问修饰符举例 class Child public int age; /公有字段 private string name; /私有字段 private string sex; /私有字段 / 构造函数 public Child(string name, int age,string sex) this.name = name; this.age = age; this.sex=sex; /公有方法 public void PrintChild() Console.WriteLine(0, 1 ,2岁., name,sex,age); ,class Program static void Main(string args) Child child1=new Child(亮亮,1,男); child1.PrintChild(); child1.age=2;/age是公有字段,可以在外部访问 child1.PrintChild(); Console.ReadLine(); 运行结果: 亮亮,男,1岁 亮亮,男,2岁,3.1.4 字段,字段:指类一级定义的变量。分静态字段和实例字段。 字段的完整引用形式为: 引用静态字段,形式为:类名.字段名 引用实例字段,形式为:实例名.字段名 注意:区分静态字段和局部变量。 局部变量:指在构造函数、方法内定义的变量,局部变量的引用形式为:局部变量名 若字段名与局部变量不重名,则可以直接引用字段名。,字段与局部变量举例,using System; namespace ConsoleAppVariable class Program public static int j=20; /静态字段 public static int k=50; /静态字段 public int i; /实例字段 public static void Main() int j=30; /局部变量 int i=10; /局部变量 Console.WriteLine(局部变量:i=0,j=1,i,j); /局部变量:i=10,j=30 Program pro=new Program(); pro.i=5; Console.WriteLine(字段:i=0,j=1,k=2,pro.i,Program.j,k); /字段:i=5,j=20,k=50 Console.ReadLine(); 运行结果: 局部变量:i=10,j=30 字段:i=5,j=20,k=50,3.1.5 构造函数,构造函数是一个特殊的方法,用于在建立对象时进行对对象的字段进行初始化操作。 在C#中,每当创建一个对象时,都会先自动调用类中定义的构造函数。创建对象的形式如下: 类名 对象名=new 构造函数(参数列表) 如:Child child1=new Child(“亮亮”,1,”男”) 使用构造函数的好处是它能够确保每一个对象在被使用之前都适当地进行了初始化。 在C#中,没有析构函数,该对象所占的内存将被CLR在适当时间自动回收(垃圾回收机制)。,构造函数基本特点,构造函数具有以下基本特点: 1) 每个类至少有一个构造函数。若程序代码中没有构造函数则系统会自动提供一个默认的构造函数。 2) 一个构造函数必须和它的类名相同。 3) 构造函数不包含任何返回值。,构造函数默认构造函数,1. 默认构造函数 (1)如果在类中不定义构造函数,系统会提供一个默认的构造函数。 (2)默认构造函数没有参数。 (3)默认构造函数自动将实例字段初始化为: 数值型:如int、double等,初始化为0。 bool类型:初始化为false. 引用类型:初始化为null。 (4)如果自己定义了构造函数,则不会自动进行初始化。,构造函数重载构造函数,2. 重载构造函数 实际情况中,构造函数中需要传入的参数的数目和类型不同。解决这个问题最好的办法就是:重载(Overloading)构造函数。 方法重载是指允许在同一类中采用同一名称不同签名声明多个方法。当编译一个重载方法的调用时,编译器会自动找到最佳匹配自变量的方法,否则报错。 注:方法的签名由它的名称、参数的数目、每个参数的类型组成。返回值类型不是方法签名的组成部分。,构造函数举例,class Program public Program() Console.WriteLine(null); public Program(string str) Console.WriteLine(string str=0,str); public Program(int x) Console.WriteLine(int x=0 ,x); static void Main() Program aa = new Program(); Program bb = new Program(How are you!); Program cc = new Program(10); Console.ReadLine(); 运行结果: null How are you! int x=10,3.1.6 方法,方法(Method)是一种用于操作对象或类的数据成员完成某种功能的函数成员。 1. 方法的定义与使用 (1)方法必须放在某个类中。 (2)定义方法的语法形式为: 访问修饰符 返回值类型 方法名(参数列表) 语句序列 ,方法可以有参数,也可以没有参数,但是小括号都是必需的。若参数有多个,用逗号分开。 无返回值时,必须声明方法的返回值类型为void。 如果声明一个非void类型的方法,则方法中必须至少有一个return语句并返回一个值。,定义方法的注意事项,类方法举例: class Program public int IntMethod() Console.WriteLine(this is IntMethod.); int i = 10; return i ; public void VoidMethod() Console.WriteLine(this is VoidMethod.); static void Main() Program method = new Program(); int j = 5; method.VoidMethod(); Console.WriteLine(j=0., j); j = method.IntMethod(); Console.WriteLine(j=0., j); Console.ReadLine(); 运行结果: this is VoidMethod. j=5 this is IntMethod. j=10,方法-参数传递,2. 方法中的参数传递 1)值参数 2)引用参数 3)输出参数 4)参数数组 前三种用于参数个数确定的情况。 第四种用于参数类型相同、参数个数不确定的情况,用一个参数数组传递多个同类型的参数。,方法-参数传递,值参数 格式: 参数类型 参数名 值参数用于输入参数的传递。值参数相当于一个局部变量,它的初始值是从为该参数所传递的自变量或常量获得的。对值参数的修改不会影响所传递的自变量。,值参数举例,class Program public static void AddOne(int a) a+; static void Main() int x = 3; Console.WriteLine(调用AddOne之前,x=0, x);/x=3 AddOne(x); Console.WriteLine(调用AddOne之后,x=0, x);/x=3 Console.ReadLine(); 运行结果: 调用AddOne之前, x=3 调用AddOne之后, x=3,方法-参数传递,2) 引用参数 格式为: ref 参数类型 参数名 用于输入和输出参数的传递。用于引用参数的自变量必须是一个变量,并且在方法执行期间,引用参数和自变量所表示的是同一个存储位置。因此该引用参数既是输入参数又是输出参数。,参数传递举例,class Program public static void AddOne(ref int a) a+; static void Main() int x = 3; Console.WriteLine(调用AddOne之前,x=0, x); AddOne(ref x); Console.WriteLine(调用AddOne之后,x=0, x); Console.ReadLine(); 运行结果: x=3 x=4,方法参数传递,3) 输出参数 格式为: out 参数类型 参数名 用于输出参数的传递。输出参数类似于引用参数,不同之处在于调用方提供的自变量并不作为输入参数,其初始值无关紧要。,输出参数举例,class Program public static void Divide(int x,int y,out int result, out int remainder) result = x/y; remainder = x%y; static void Main() int res, rem; Divide (10,3,out res, out rem); Console.WriteLine(“调用Divide之后,res=0,rem=1,res, rem); Console.ReadLine(); 运行结果: 调用Divide之后,res=3,rem=1,方法-参数传递,4) 参数数组(传递个数不确定的同类型参数) 格式为: params 参数数组类型 数组名 参数数组允许将可变长的自变量列表传递给方法。 注意:采用params关键字声明参数的个数是不确定的。,参数数组举例,class Program public static float Average(params long data) long total=0, i; for (i = 0; i data.Length; +i) total += datai; return (float)total / data.Length; static void Main() float x = Average(1, 2, 3, 5); Console.WriteLine(1+2+3+5的平均值为0, x); x = Average(4, 5, 6, 7, 8); Console.WriteLine(4+5+6+7+8的平均值为0, x); Console.ReadLine(); 运行结果: 1+2+3+5的平均值为2.75 4+5+6+7+8的平均值为6,方法-静态方法与实例方法,静态方法 若一个方法声明中含有static修饰符,则称该方法为静态方法。 静态方法不对实例成员进行操作,只能访问静态成员。 静态方法调用形式:类名.静态方法名 实例方法 若一个方法声名中没有static修饰符,则称为该方法为实例方法。 实例方法既能访问静态成员,也能访问实例成员。 实例方法调用形式:实例名.实例方法名 静态方法的好处在于不用创建类实例仅通过类就能调用。,例:设计一个Entity类(实体类),设置一个静态字段保存下一个序列号(初始值为1000),设置一个实例字段保存实例当前的序列号,每个类对象在创建时自动获取一个唯一序列号并使下一个序列号自动加1。 分析: 设计两个字段: 静态字段nextSerialNo ,保存下个序列号。 实例字段serialNo,保存当前实例序列号。 设计以下方法: 构造方法Entity( ),初始化当前实例的序列号。 实例方法GetSerialNo( ),获取当前实例序列号。 静态方法SetNextSerialNo(int value),设置下一个序列号初值。 静态方法GetNextSerialNo( ),获取下一个序列号,静态方法和实例方法举例,class Entity static int nextSerialNo; /静态字段,保存下个序列号 int serialNo; /实例字段,保存当前实例序列号 public Entity( ) /构造函数 serialNo=nextSerialNo+; public int GetSerialNo( ) /实例方法 return serialNo; /获取当前实例序列号 public static int GetNextSerialNo( ) /静态方法 return nextserialNo; /获取下个序列号 public static void SetNextSerialNo(int value) /静态方法 nextserialNo=value; /设置下个序列号初值 ,静态方法和实例方法举例,static void Main() Entity.SetNextSerialNo(1000); Entity e1=new Entity(); Entity e2=new Entity(); Console.WriteLine(e1.GetSerialNo(); Console.WriteLine(e2.GetSerialNo(); Console.WriteLine(Entity.GetNextSerialNo(); 运行结果: 1000 1001 1002,静态方法和实例方法举例,方法-方法重载,4. 方法重载 有时会遇到完成相似的功能,但传入的参数列表不同的情况,C#使用方法重载来实现统一操作。 方法重载是指具有相同的方法名,但签名不同的多个方法可以同时出现在一个类中。当编译一个重载方法的调用时,编译器会自动找到最佳匹配自变量的方法,否则报错。,方法重载举例,class Program public static int Add(int i, int j) return i + j; public static string Add(string s1, string s2) return s1 + s2; public static long Add(long x) return x + 5; static void Main() Console.WriteLine(Add(1, 2); Console.WriteLine(Add(1, 2); Console.WriteLine(Add(10); Console.ReadLine(); 运行结果: 3 12 15,3.1.7 属性,一个设计良好的类不仅仅要将类的实现部分隐藏起来,还会限制外部对类中非公有字段的存取权限。在C#中,可以通过属性来实现。,1.属性的声明,属性的声明类似字段,不同之处在于属性的声明以定界符之间的get访问器和/或set访问器结束,而不是分号。 get访问器和set访问器不是必须同时有的,因需而定。 get访问器相当于一个具有属性类型返回值的无参数方法,用来有限制地读取类中非公有字段的值。 set访问器相当于一个具有单个名为value的参数和无返回类型的方法,用来有限制地设置类中非公有字段的值。,属性举例,class MyClass private int number = 0; public int Number get return number; set if (value 0) /value是关键字,其值是调用set访问器时传入的新值。 number = value; ,属性举例,class Program public static void Main() MyClass me = new MyClass(); Console.WriteLine(me.Number);/调用get访问器 me.Number = 5;/调用set访问器,value=5 Console.WriteLine(me.Number); /调用get访问器 Console.ReadLine(); 运行结果: 0 5,2. 属性与方法的区别,1) 属性不使用括号,但方法一定要使用括号。 2) 属性不能指定参数,方法可以指定参数。 3) 属性不能使用void类型,方法则可以使用void类型。,3.1.8 类和对象定义和使用举例,设计一个公司雇员类(employee),需要存储这些雇员的姓名name、编号(编号初始值缺省为1000),并设计以下功能: 为姓名name定义属性,设置其get访问器和set访问器。 实现无参构造方法,完成从键盘上输入雇员姓名的功能。 实现方法,完成从键盘上重新设置编号初始值的功能(setEmployeeNo)。 实现方法显示雇员编号和姓名(displayStatus)。,class employee private string name; /姓名 private int individualEmpNo; /个人编号 private static int employeeNo=1000; /雇员编号目前最大值 public string Name get return name; set if(value!=) name=value; ,public employee() /构造方法 Console.Write(请输入一个雇员的姓名:); name=Console.ReadLine(); individualEmpNo=employeeNo+; static public void setEmployeeNo() Console.Write(请输入雇员编号目前的初始值:); employeeNo=Convert.ToInt32(Console.ReadLine(); public void displayStatus() /显示人员信息 Console.WriteLine(“编号:”+individualEmpNo+“,姓名:+name); ,class Program static void Main(string args) employee.setEmployeeNo(); employee employee1=new employee(); employee1.displayStatus(); employee employee2=new employee(); employee2.displayStatus(); Console.Write(请重新输入最后一个雇员的姓名:); employee2.Name=Console.ReadLine(); employee2.displayStatus(); Console.ReadLine(); 运行结果: 请输入雇员编号目前的初始值:1000 请输入一个雇员的姓名:王丽 编号:1000,姓名:王丽 请输入一个雇员的姓名:刘强 编号:1001,姓名:刘强 请重新输入最后一个雇员的姓名:张刚 编号:1001,姓名:张刚,3.2 面向对象的高级编程,3.2.1封装 3.2.2继承 3.2.3多态性,3.2.1 封装,在面向对象编程中,封装是指把数据和处理这些数据的代码封装在一个类中,尽可能隐藏实现的细节,只提供给调用者需要知道的操作和数据。 封装使得外部类并不能直接访问类内部数据,只有类内部操作才能访问类内部数据,所以设计者修改实现细节时,只要不改变对外接口,可以不影响调用者与类的交互方式。,类封装举例:设计一个银行账户类,通过属性读取账户余额但不可任意修改账户余额,通过构造方法初始化账户余额,并可以通过方法扣减账户余额,在控制台应用程序中测试该类的功能。,分析:根据题意设计一个名为BankAccount的类,该类有: 一个私有字段accountBalance保存账户余额。 一个名为AccountBalance的属性,只提供get访问器不提供set访问器。 一个名为BankAccount的构造方法,可初始化帐户余额accountBalance。 一个名为Withdraw的公有方法,可以扣减帐户余额accountBalance的值。,class BankAccount private decimal accountBanlance; public decimal AccountBanlance get return accountBanlance; public BankAccount(decimal startAmount) accountBanlance=startAmount; public void Withdraw(decimal money) if(accountBanlance=money) accountBanlance-=money; Console.WriteLine(扣掉0:C,money); ,class Program static void Main() BankAccount ZhangSan=new BankAccount(1000); Console.WriteLine(账户的余额为0:C,ZhangSan.AccountBanlance); ZhangSan.Withdraw(100); Console.WriteLine(账户的余额为0:C,ZhangSan.AccountBanlance); Console.ReadLine(); 运行结果: 帐户的余额为¥1,000.00 扣掉¥100.00 帐户的余额为¥900.00,若上题中增加一个修改:若一次扣款超过500元,就提示“每次扣款额不能超过500元!”,则仅需修改类内方法Withdraw方法,修改如下: public void Withdraw(decimal money) if(accountBanlance=money) if(money=500.00) Console.WriteLine(每次扣款额不能超过500元! ); return; else accountBanlance-=money; Console.WriteLine(扣掉0:C,money); ,3.2.2 继承,继承的概念来自于遗传与分类, 遗传:每个人的特征或多或少都来自于父母的遗传,但每个人都不可能完全与父母一模一样,有着自己与众不同的特征。 分类:现实世界中的事物都是相互联系的,人们在认识过程中,根据他们的实际特征,抓住其共同特征和细小差别,利用分类的方法进行分析和描述他们。,3.2.2 继承,3.2.2.1 继承的概念 3.2.2.2 C#派生类的声明 3.2.2.3 C#派生类中的构造方法,3.2.2.1继承的概念,在现实世界中,许多实体或概念不是孤立的,它们具有共同的特征,但也有细微的差别,人们使用层次分类的方法来描述这些实体或概念之间的相似点和不同点。 继承是面向对象程序设计中最重要的机制,它支持层次分类的观点。 继承使得程序员可以在一个较一般的类的基础上很快地建立一个新类,而不必从零开始设计每个类。,基类和派生类的关系,在OOP中,继承使得派生类具有基类的特征,同时又允许在派生类的声明时增加自己的特征,或修改基类的特征。,3.2.2.2 C#派生类的声明,声明一个C#派生类的一般格式为: class 派生类名: 基类名 /派生类新增的数据成员和成员函数 ; 其中,“基类名”是一个已经定义的类的名称,“派生类名”是继承原有类的特性而生成的新类的名称。,3.2.2.3 C#派生类中的构造方法,基类的构造函数不能被继承,派生类必须自身的构造函数。 在派生类中对新增成员的初始化工作,是由派生类的构造函数完成的。 在派生类中对基类成员的初始化工作,还是由基类的构造函数完成。 我们必须在派生类的构造函数中对基类的构造函数所需要的参数进行设置。 通常情况下,当创建派生类对象时,首先执行基类的构造函数,随后再执行派生类的构造函数。,派生类构造函数的一般格式,派生类名(参数总表):base(参数表) / 派生类新增成员的初始化语句 默认情况下,派生类的所有构造函数会自动调用基类的无参构造函数。 当派生类的有参构造函数需要调用基类的有参构造函数时,需要在定义派生类构造函数时,缀上基类名base及其有参构造函数的初始化列表。如: public Car(int w, float g, int p, string id):base(w,g,p,id),例 派生类构造函数默认情况下自动调用基类的无参构造函数,class Animal protected string _name; public Animal() Console.WriteLine(你好,我是一只动物!); public Animal(string name) _name = name; Console.WriteLine(你好,我是一只动物!我的名字叫 + _name + !); public void Eat() Console.WriteLine(_name + 在吃食!); class Cat : Animal public Cat() Console.WriteLine(你好,我是一只猫!); public Cat(string name) _name = name; Console.WriteLine(你好,我是一只猫!我的名字叫 + _name + !); ,class Program static void Main() Cat e = new Cat(小白); e.Eat(); Cat h = new Cat(); Console.ReadLine(); 运行结果: 你好,我是一只动物! 你好,我是一只猫!我的名字叫小白! 小白在吃食! 你好!我是一只动物! 你好!我是一只猫! 说明:该例中Cat的两个构造函数都自动调用了基类Animal的无参构造Animal()。,例 派生类构造函数调用指定的基类有参构造函数,class Animal protected string _name; public Animal() Console.WriteLine(你好,我是一只动物!); public Animal(string name) _name = name; Console.WriteLine(你好,我是一只动物!我的名字叫 + _name + !); public void Eat() Console.WriteLine(_name + 在吃食!); class Cat : Animal public Cat() Console.WriteLine(你好,我是一只猫!); public Cat(string name):base(name) _name = name; Console.WriteLine(你好,我是一只猫!我的名字叫 + _name + !); ,class Program static void Main() Cat e = new Cat(小白); e.Eat(); Cat h = new Cat(); Console.ReadLine(); 运行结果: 你好,我是一只动物!我的名字叫小白! 你好,我是一只猫!我的名字叫小白! 小白在吃食! 你好!我是一只动物! 你好!我是一只猫! 说明: 在该例中,Cat(string name)调用了基类Animal中的有参构造函数Animal(string name)。,3.2.3 多态性,C#最重要的特征之一就是代码重用。多数情况下,越通用的代码越能够重用。 多态性是通过提高代码的通用性来实现代码重用的。 直观地讲,多态性是指类族中具有相似功能的不同函数使用同一名称来实现,从而可以使用相同的调用方式来调用这些具有不同功能的同名函数,即通过用相同的接口访问不同功能的函数,来实现“一个接口,多种方法”的通用性。 (注:类族是指由直接或间接派生自共同基类的所有派生类和共同基类组成的类的集合。) 从消息的角度说,多态性是指同样的消息被不同类型的对象接收时导致完全不同的行为。,C#多态性的实现方式,主要有以下几种实现多态性的方式。 第一种方式是通过继承实现多态性,分为两种: 1. 虚方法 2. 隐藏基类的方法 第二种方式是通过抽象类实现多态性。,3.2.3 多态性,3.2.3.1 虚方法 3.2.3.2 隐藏基类的方法 3.2.3.3 抽象类,3.2.3.1 虚方法,在基类中,如果想让某个方法被派生类重写,可以使用修饰符virtual声明该方法为虚方法,形式如下: public virtual 返回值类型 方法名() /程序代码 派生类则用override重写,形式如下: public override返回值类型 方法名() /程序代码 ,虚函数,例如,如果在基类中,如果想让方法myMethod被派生类重写,可以使用修饰符virtual声明: public virtual void myMethod() /程序代码 派生类则用override重写: public override void myMethod() /程序代码 ,使用虚方法与重写方法时,需要注意下面几个方面: 1) 虚方法不能声明为静态(static)的。因为静态的方法是应用在类这一层次的,而多态性只能在对象上运作。 2) virtual不能和private一起使用。因为声明为private就无法在派生类中重写了。 3) 重写方法的名称、参数个数、类型以及返回值都必须和虚方法的一致。 4)虚方法在派生类中不是必须被重写的,不重写时,派生类将自动调用虚方法在基类中的版本。,例 利用虚方法多态性编写汽车信息管理程序,class Vehicle /定义车基类 protected int passengers; /乘客数 protected int wheels; /轮子个数 protected float weight; /重量 protected string videntifier;/标志 public Vehicle(int w, float g, int p, string id) wheels = w; weight = g; videntifier = id; passengers = p; public virtual Speak() public void Print() Console.WriteLine(乘客数: +passengers); Console.WriteLine(轮子数: +wheels); Console.WriteLine(重量:+weight); Console.WriteLine(标志: +videntifier); ,class Truck:Vehicle /定义卡车类 private float load; /私有成员:载重量 public Truck(int w, float g, int p, float l, string id): base(w, g, p, id) load = l; public override void Speak() Console.WriteLine(卡车鸣笛:叭叭叭叭!); public new void Print() base.Print(); Console.WriteLine(载重: +load); ,class Car : Vehicle /定义轿车类 public Car(int w, float g, int p, string id):base(w,g,p,id) public override void Speak() Console.WriteLine(轿车鸣笛:嘀嘀嘀嘀!); class Bus:Vehicle /定义公共汽车类 public Bus(int w, float g, int p, string id): base(w, g, p, id) public override void Speak() Console.WriteLine(公共汽车鸣笛:嘟嘟嘟嘟!); ,class Program static void Main(string args) /乘客数 轮子数 重量 Car c1 = new Car(4, 2, 5, 标志307); /乘客数 轮子数 重量 载重量 Truck t1 = new Truck(6, 5, 3, 10, 解放); Bus b1 = new Bus(4, 3, 40, 金杯); /轿车 Console.WriteLine(/); Console.WriteLine(轿车信息:); c1.Speak(); c1.Print(); /嘀嘀 /卡车 Console.WriteLine(/); Console.WriteLine(卡车信息:); t1.Speak(); t1.Print(); /叭叭 /公共汽车 Console.WriteLine(/); Console.WriteLine(公共汽车信息:); b1.Speak(); b1.Print(); /嘟嘟 Console.WriteLine(/); Console.ReadLine(); ,3.2.3.2 隐藏基类的方法,在派生类中,可以使用new关键字来隐藏基类的方法,即使用一个完全不同的方法取代旧的方法。 与方法重写不同的是,使用new关键字时并不要求基类中的方法声明为virtual,只要在派生类的方法前声明为new,就可以隐藏基类的方法。,例 隐藏基类的方法,class Hello public void SayHello() Console.WriteLine(这是基类!); class NewHello : Hello public new void SayHello() Console.WriteLine(“这是派生类!); ,class Program static void Main(string args) Hello b = new Hello(); b.SayHello(); NewHello d = new NewHello(); d.SayHello(); Console.ReadLine(); 运行结果: 这是基类! 这是派生类!,3.2.3.3 抽象类,利用抽象类,可以声明仅定义了部分实现的类,让派生类提供某些或者全部方法的实现。 如果对于给定类型的大多数或者全部对象来说,某些行为是既定的,而有些行为只是对一个特定类有意义时,抽象类就很有帮助。 抽象类是至少带有一个抽象方法的类。,抽象方法,抽象方法是一个在基类中只说明函数原型以规定整个类族的统一接口形式的虚方法。 抽象方法在该基类中没有定义具体的操作内容,但要求在各派生类中根据实际需要定义自己的版本。 声明为抽象方法之后,基类中就不再给出函数的实现部分。抽象方法的函数体由派生类给出。,抽象方法的声明与实现形式,(1)抽象方法的声明形式如下: abstract 方法类型 方法名(参数表); 如: abstract void show(); (2)抽象方法在派生类中被实现的形式为: public override 返回类型 方法名() /方法体 如: public override void show() /方法体 ,抽象类,至少带有一个抽象方法的类是抽象类。 抽象类的主要作用是通过它为一个类族建立一个公共的接口,使它们能够更有效地发挥多态特性。 抽象类声明了一系列派生类的公共接口,而接口的完整实现,即抽象方法的函数体,要由派生类自己定义。 抽象类派生出新的类之后, 如果派生类给出所有抽象方法的函数实现,这个派生类就可以声明自己的对象,不再是抽象类,可以实例化; 反之,如果派生类没有给出全部抽象函数的实现,这时的派生类仍然是一个抽象类,不能实例化。,抽象类的声明形式,抽象类使用abstract修饰符,声明形式如下: abstract class 类名 public abstract 返回值类型 抽象方法名(); 如: abstract class Shape public abstract void Rotate();/抽象方法 ,抽象类与非抽象类的主要不同:,第一是抽象类不能直接被实例化,只能在派生类中通过继承使用。 第二是抽象类可以包含抽象成员,而非抽象类不能包含抽象成员。当从抽象类派生非抽象类时,这些非抽象类必须具体实现所继承的所有抽象成员。,例 抽象方法和抽象类,abstract class Shape public virtual void Draw() Console.WriteLine(画一种图形!); public abstract void Rotate();/抽象方法 class Square : Shape public override void Draw() Console.WriteLine(画一个正方形!); public override void Rotate() Console.WriteLine(顺时针方向旋转正方形!); ,class Program static void Main(string args) Square me = new Square(); me.Draw(); me.Rotate(); Console.ReadLine(); 运行结果: 画一个正方形! 顺时针方向旋转正方形!,例 利用抽象类多态性编写汽车信息管理程序,abstract class Vehicle /定义车基类 protected int passengers; /乘客数 protected int wheels; /轮子个数 protected float weight; /重量 protected string videntifier;/标志 public Vehicle(int w, float g, int p, string id) wheels = w; weight = g; videntifier = id; passengers = p; public abstract Speak(); /抽象方法 public void Print() Console.WriteLine(乘客数: +passengers); Console.WriteLine(轮子数: +wheels); Console.WriteLine(重量:+weight); Console.WriteLine(标志: +videntifier); ,class Truck:Vehicle /定义卡车类 private float load; /私有成员:载重量 public Truck(int w, float g, int p, float l, string id): base(w, g, p, id) load = l; public override void Speak() Console.WriteLine(卡车鸣笛:叭叭叭叭!); public new void Print() base.Print(); Console.WriteLine(栽重: +load); ,class Car : Vehicle /定义轿车类 public Car(int w, float g, int p, string id):base(w,g,p,id) public override void Speak() Console.WriteLine(轿车鸣笛:嘀嘀嘀嘀!); class Bus:Vehicle /定义公共汽车类 public Bus(int w, float g, int p, string id): base(w, g, p, id) public override
展开阅读全文