C语言基础

C语言基础C 语言 基础 http blog csdn net mousebaby808 category aspx PageNumber 2 C 语言 第一部分 面向对象 一 类和 Main 方法 通过 面向对象基本原理 的学习 我们应该了解如何从 面向过程 过渡到 面向对象 也能基本了解面向

大家好,我是讯享网,很高兴认识大家。

C#语言 基础

http://blog.csdn.net/mousebaby808/category/.aspx?PageNumber=2
C#语言 第一部分 面向对象 (一) 类和Main方法

通过“面向对象基本原理”的学习,我们应该了解如何从“面向过程”过渡到“面向对象”,也能基本了解面向 对象编程的一些优点,这一章,我们进入到一门纯粹的面向对象语言C#语言的学习。

C# 语言是.net平台的一部分,.net平台提供了一种叫做“公共语言”的中间语言,几乎各种语言都可以映射倒公共语言上,例如 C++,Visual Basic,JScript等,C#是微软专门为.net平台开发的,该语言语法简洁,功能强大,已经成为一门重要的主流开发语言。

关于 C#语言的特色,可以参考各类教科书或查阅网络资料。这里我们只针对C#语言本身进行介绍,关于语言的其它方面,请大伙自学。

我们从最基本的HelloWorld程序开始,了解一下C#编程的基本结构。
1 // using关键字的作用是引入一个“外部程序集”,System是微软提供的.net平台最基本程序集,所有 的.net程序都必须引用此程序集 2 using System; 3 4 // .net规范允许每一个类都属于一个命名空间 5 // 命名空间的主要作用是用来防止出现“名字冲突”,即处于不同命名空间的类, 可以具有相同的类名 6 // 命名空间还可以用来组织类的层次,使用 组织名.公司名.项目名.模块名 形成一个命名空间,可以有效地隔离不同的类 7 namespace Edu.Study.OO.MainMethod { 8 9 /// <summary> 10 /// C#作为一种“纯面向对象”语言,类是所有代码的基础,使用关键字class可以定义一个类 11 /// </summary> 12 class Program { 13 14 /// <summary> 15 /// 在任意类中声明Main方法,该方法即可作为项目的启动入口点,程序从 Main方法开始运行。 16 /// Main 方法的标准写法:使用static关键字修饰,返回类型为void 类型,方法民为Main(首字母大写),具有一个字符串数组类型的参数 17 /// </summary> 18 /// <param name="args"> 控制台参数,该参数由程序的运行者从控制台传入,由程序处理 </param> 19 static void Main(string[] args) { 20 // Console表示控制台,WriteLine方法表示“输出一行”,参数 为要输出的字符串 21 Console.WriteLine("Hello World"); 22 } 23 } 24 }

可以看到,C#依旧秉承了C和C++的很多语法,另外还带有些许Java的影子,C#是一门简单易学的编程语言,只要努力学习,都可以掌握这门优秀 的编程语言。
C#语言 第一部分 面向对象 (二) 类

类是面向对象的基础,类表现出的最基本特性是其“封装性”。

类是某一些具有相同属性和行为对象的抽象。例如:波斯猫、野猫、家猫、花猫都具有相同的属性和行为,所以被抽象成为猫类。

类具有属性和方法,表示这个类所代表的对象具有的特性和行为。从类实例化对象后,对象就可以给类中定义的属性“赋值”或运行类中定义的方法,属性和 方法和每个对象相关,即相同类的不同的对象具有相同的属性,但属性的属性值可以不同;具有相同的方法,但方法运行的结果可以不同。

类需要将和其相关的属性和方法“封装”起来,并和对象进行绑定。

看下面的例子,自行理解:
1 using System; 2 3 namespace Edu.Study.OO.Class { 4 5 /// <summary> 6 /// 定义一个Person类,表示人 7 /// </summary> 8 public class Person { 9 10 /// <summary> 11 /// 在类中定义的变量称为“字段” 12 /// name为一个string类型的字段,保存人的名字 13 /// </summary> 14 private string name; 15 16 /// <summary> 17 /// 保存年龄 18 /// </summary> 19 private int age; 20 21 /// <summary> 22 /// 保存性别,true为男性,false为女性 23 /// </summary> 24 private bool sex; 25 26 /// <summary> 27 /// 属性定义,在面向对象编程里,字段作为保存属性值的变量,而属性则需要使用特殊的“行为”来表现 28 /// 一般来说,一对get/set行为用来展现对象的属性,其中get用于获取该对象的属性;set用于设置该对象的属 性 29 /// get/set称为属性的“访问器” 30 /// 31 /// 姓名属性 32 /// </summary> 33 public string Name { 34 get { 35 // get访问器必须返回一个值,作为该属性的“属性值 36 return this.name; 37 38 // . 运算符的作用是访问某个对象的属性或方法 39 // this关键字表示“当前实例”,即哪一个对象正在操作属性访问 器,this就表示哪个对象 40 // 所以一般说法,this表示“当前对象”,使用this.访问的是“声明在 本类中”的属性和方法 41 } 42 set { 43 // set访问器具有一个局部变量value,该值是从外部设置给该属性的值 44 this.name = value ; 45 } 46 47 // 属性的基本要求:get访问器必须返回一个值,该值类型和属性类型相 同;set访问器可以设置一个值,这个值保存在什么地方倒无所谓 48 } 49 50 /// <summary> 51 /// 年龄属性 52 /// </summary> 53 public int Age { 54 get { 55 return age; 56 } 57 set { 58 age = value ; 59 } 60 } 61 62 /// <summary> 63 /// 性别属性 64 /// </summary> 65 public bool Sex { 66 get { 67 return sex; 68 } 69 set { 70 sex = value ; 71 } 72 } 73 74 /// <summary> 75 /// 显示个人信息的方法 76 /// </summary> 77 public void ShowMe () { 78 // 输出,其中: 79 // 形如{0}, {1}称为“占位符”,当输出内容时,这些占位符会被第一个参数之后的参数 “依次取代”,例如: 80 // {0}会被this.Name的值取代,即被第一个参数取代 81 // {1}会被 this.Sex的值取代,即被第二个参数取代 82 // {n}会被第n+1个参数取代。 83 // 使用this.Name,此时属性Name的get访问器会运行,返回 Name属性的属性值,其它属性同理 84 Console.WriteLine("姓名:{0},性别:{1},年龄:{2}岁。" , this.Name, this.Sex, this.Age) ; 85 } 86 87 // 字段、属性和方法前面,都可以使用public, protected, private和internal这四种访问修饰符来修饰 88 // public表示字段、属性或方法为“公开”,公开的成员不仅可以被类中的 其它成员访问到,也可以通过这个类的对象访问到 89 // private表示字段、属性或方法为“私有”,私有的成员只能被类中的其 它成员访问到,无法通过类的对象访问到 90 // protected和internal稍候介绍 91 } 92 93 class Program { 94 static void Main(string[] args) { 95 // 通过new运算符可以实例化一个对象 96 // 实例化Person类的对象,保存在person变量中 97 Person p1 = new Person(); 98 Person p2 = new Person(); 99 100 // 设置person对象的各个属性,使用 person.Name = " 王宝宝",Name属性的set访问器会被运行,等号右边的值"王宝宝"传递给set访问器的value变量 101 p1.Name = "王宝宝"; 102 p1.Age = 21; 103 p1.Sex = false; 104 105 p2.Name = "李大大"; 106 p2.Age = 18; 107 p2.Sex = true; 108 109 // 使用 . 操作符访问 person对象的ShowMe方法,显示结果 110 p1.ShowMe(); 111 p2.ShowMe(); 112 113 } 114 } 115 }
C#语言 第一部分 面向对象 (三) 对象的构造

上一章我们介绍了一个Person 类,它可以正常工作,但从逻辑上存在很多问题。

首先我们注意到,Person类对象实例化完毕后,我们才去确定其属性,如 Name,Sex,Age属性。但客观情况下,这些属性应该是同对象生成 一起赋值的。

一句话,当对象被实例化的同时,对象的属性就应当有初始值,反映到代码中,就是“用来保存属性值的字段应该有初始值”

构造器,也成为构造方法,构造函数,就是专门用来做这件事情的。

一个类可以有0或多个构造器,在有多个构造器的情况里,各个构造器的参数必须不同。

在 C#类中,有这样一类特殊的方法,没有返回值类型,方法名必须和类名相同,可以有参数列表。这类方法就叫做构造器,构造器通过new操作符指定, 在实例化对象的同时运行。

我们用构造器来改造上一章讲到的Person类。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 namespace Edu.Study.OO.Constructor { 7 8 /// <summary> 9 /// 人类,这次我们修改一下Person类 10 /// 在Person类生成对象的同时,Name属性,Sex属性和Age属性的值就应该确 定,而不应该在生成对象后再进行设置,这是不符合客观规律的。 11 /// 在对象实例化的同时对属性进行设置,就要用到“构造器” 12 /// </summary> 13 public class Person { 14 15 /// <summary> 16 /// name 为一个string类型的字段,保存人的名字 17 /// </summary> 18 private string name; 19 20 /// <summary> 21 /// 保存年龄 22 /// </summary> 23 private int age; 24 25 /// <summary> 26 /// 保存性别,true为男性,false为女性 27 /// </summary> 28 private bool sex; 29 30 /// <summary> 31 /// 没有参数的构造器称为“默认”构造器 32 /// 如果一个类没有提供任何形式的构造器,则C#编译器会提供给该类一个默认构造器 33 /// 默认构造器应该赋予所有的字段“默认值”,系统提供的默认构造器将所有的字段设置为“类型默认值”,即0或null 34 /// </summary> 35 public Person() { 36 this.name = "小王"; 37 this.age = 22; 38 this.sex = true; 39 } 40 41 /// <summary> 42 /// 带有参数的构造器 43 /// </summary> 44 public Person(string name, int age, bool sex) { 45 this.Name = name; 46 this.Age = age; 47 this.sex = sex; // 这里,Sex属性 48 } 49 50 /// <summary> 51 /// 带有部分参数的构造器 52 /// </summary> 53 public Person(int age, bool sex) { 54 this.Name = "小王"; 55 this.Age = age; 56 this.sex = sex; // 这里,Sex属性 57 } 58 59 /// <summary> 60 /// 带有部分参数的构造器 61 /// </summary> 62 public Person(bool sex) { 63 this.Name = "小王"; 64 this.Age = 22; 65 this.sex = sex; // 这里,Sex属性 66 } 67 68 /// <summary> 69 /// 姓名属性 70 /// </summary> 71 public string Name { 72 get { 73 // get访问器必须返回一个值,作为该属性的“属性值 74 return this.name; 75 } 76 set { 77 // set访问器具有一个局部变量value,该值是从外部设置给该属性的值 78 this.name = value; 79 } 80 } 81 82 /// <summary> 83 /// 年龄属性 84 /// </summary> 85 public int Age { 86 get { 87 return age; 88 } 89 set { 90 age = value; 91 } 92 } 93 94 /// <summary> 95 /// 性别属性,这次我们去掉了Sex的set访问器。 96 /// 由于通过构造器已经设置了 Sex属性,所以无需通过set访问器来设置该属性 97 /// 按照一般情况,Sex属性在对象实例化后,不应该设置该属性。 98 /// 属性可以只具有set访问器或get访问器,即 “只读”或“只写”的属性 99 /// </summary> 100 public bool Sex { 101 get { 102 return sex; 103 } 104 } 105 106 /// <summary> 107 /// 显示个人信息的方法 108 /// </summary> 109 public void ShowMe() { 110 Console.WriteLine("姓名:{0},性别:{1},年龄:{2}岁。", this.Name, this.Sex, this.Age); 111 } 112 } 113 114 class Program { 115 static void Main(string[] args) { 116 117 // 删除掉public Person()构造器,查看程序编译情况 118 // 删除掉所有的构造器,查看程序编译情况 119 Person person = new Person(); 120 person.ShowMe(); 121 122 // person.Sex = true; // 只读属性禁止了给属性设置值 123 124 person = new Person(false); 125 person.ShowMe(); 126 127 person = new Person(18, true); 128 person.ShowMe(); 129 130 person = new Person("小丽", 23, false); 131 person.ShowMe(); 132 } 133 } 134 }

我们给Person类增加了若干个“构造器”,并修改Sex属性为只读,从而让Person类更更符合客观实际。

对于任何一个类,可以不人为提供构造器,此时编译器会赋予类一个“默认构造器”,即没有参数的构造器。这个构造器什么也不做,仅仅是为了new操作 符能够得以指定一个构造器。

而我们一旦定义了有参数的构造器,编译器便会这样认为“这个类的对象必须提供参数才能够实例化”,便不再会提供无参数的默认构造器了。所以一个类如 果同时需要有默认构造器和有参数的构造器,则默认构造器必须手动提供,不要指望编译器会自动生成。

现在我们可以完整定义new操作符的语法规范:类名 变量名 = new 类构造器名(构造器实参数列表);


C#语言 第一部分 面向对象 (四) 字段默认值

C# 中,可以给类字段以一个默认值,这样一方面可以简化构造器的代码,另一方面较为直观,是一种推荐的方式。

这一章比较简单,可结合上一章深入理解构造器的作用。

看代码:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 namespace Edu.Study.OO.DefaultValue { 7 8 /// <summary> 9 /// 还是那个人类。 10 /// 通过构造器 11 /// </summary> 12 public class Person { 13 14 /// <summary> 15 /// 保存名字的字段,默认值为 小王 16 /// </summary> 17 private string name = "小王"; 18 19 /// <summary> 20 /// 保存年龄的字段,默认值为22 21 /// </summary> 22 private int age = 22; 23 24 /// <summary> 25 /// 保存性别的字段,true为男性,false为女性,默认值为女性 26 /// </summary> 27 private bool sex = false; 28 29 /// <summary> 30 /// 默认构造器,由于字段定义了默认值,所以构造器中无需给字段赋值 31 /// </summary> 32 public Person() { 33 } 34 35 /// <summary> 36 /// 带有参数的构造器,这里的构造器给所有字段都赋了值,所以无视默认值 37 /// </summary> 38 public Person(string name, int age, bool sex) { 39 this.Name = name; 40 this.Age = age; 41 this.sex = sex; 42 } 43 44 /// <summary> 45 /// 带有部分参数的构造器,这里只给sex和age字段赋值,而name字段本身就有默认值 46 /// </summary> 47 public Person(int age, bool sex) { 48 this.Age = age; 49 this.sex = sex; // 这里,Sex属性 50 } 51 52 /// <summary> 53 /// 带有部分参数的构造器,这里只给sex字段赋值,其它字段都有默认值 54 /// </summary> 55 public Person(bool sex) { 56 this.sex = sex; 57 } 58 59 /// <summary> 60 /// 姓名属性 61 /// </summary> 62 public string Name { 63 get { 64 // get访问器必须返回一个值,作为该属性的“属性值 65 return this.name; 66 } 67 set { 68 // set访问器具有一个局部变量value,该值是从外部设置给该属性的值 69 this.name = value; 70 } 71 } 72 73 /// <summary> 74 /// 年龄属性 75 /// </summary> 76 public int Age { 77 get { 78 return age; 79 } 80 set { 81 age = value; 82 } 83 } 84 85 /// <summary> 86 /// 性别属性,这次我们去掉了Sex的set访问器。 87 /// 由于通过构造器已经设置了Sex属性,所以无需通过set访问器来设置该属性 88 /// 按照一般情况,Sex属性在对象实例化后,不应该设置该属性。 89 /// 属性可以只具有set访问器或get访问器,即“只读”或“只写”的属性 90 /// </summary> 91 public bool Sex { 92 get { 93 return sex; 94 } 95 } 96 97 /// <summary> 98 /// 显示个人信息的方法 99 /// </summary> 100 public void ShowMe() { 101 Console.WriteLine("姓名:{0},性别:{1},年龄:{2}岁。", this.Name, this.Sex, this.Age); 102 } 103 } 104 105 class Program { 106 static void Main(string[] args) { 107 108 // 删除掉public Person()构造器,查看程序编译情况 109 // 删除掉所有的构造器,查看程序编译情况 110 Person person = new Person(); 111 person.ShowMe(); 112 113 // person.Sex = true; // 只读属性禁止了给属性设置值 114 115 person = new Person(false); 116 person.ShowMe(); 117 118 person = new Person(18, true); 119 person.ShowMe(); 120 121 person = new Person("小丽", 23, false); 122 person.ShowMe(); 123 } 124 } 125 }

在声明字段的同时,可以用赋值运算符(=)在字段后面直接加上一个值(例如第17行),但这和给变量赋值是两个概念。

以赋值运算符跟随在字段之后的值称为字段的“默认值”。这只是一种形式,并不是写在这里就在这里发生赋值,真正 发生赋值的时机还是在构造器中,只不过……

无论我们通过哪个构造器创建类的对象实例,这个构造器中,没有显式赋值的字段,都会由编译器增加一句赋值代码,将该字段赋值为设定 的默认值。

其实,无论我们是否给字段增加了默认值,字段都具备默认值的,只不过如果我们没有人为增加默认值,字段的默认值将是0或null。

C#语言 第一部分 面向对象 (五) 继承

在现实生活中,类和类之间可能会有一种这样的关系:一个类是另一个类的扩展,反过来讲,后一个类是前一个类的基础。

例如:动物类是猫类的基础,猫类是动物类的扩展。这种关系称为继承,即猫类继承了动物类。

被继承的类称为父类(C++说法),基类(C#说法)或超类(Java说法),另一个类叫做子类。怎么叫无所谓,关键看效果。

所谓扩展,就是子类拥有超类的一切特征(包括属性和方法),而子类还可以在超类的基础上添加自身的属性和方法。例如:猫类拥有动物类的一切属性和行 为,但猫类还具有自身的属性(例如长胡子,喵喵叫)和行为(例如磨爪子,抓耗子)。

子类虽然拥有超类的一切特征,但并不是说子类可以任意的去访问继承下来的这些超类特征,子类只能访问到超类中访问修饰符为 public或proctected的那部分属性和方法,其余的属性和方法无法直接访问。

C#的这种继承成为无条件继承,即子类必须继承超类的所有特性,无法选择。

看代码:
1 using System; 2 3 namespace Edu.Study.OO.Inherit { 4 5 /// <summary> 6 /// 依旧是人类 7 /// </summary> 8 public class Person { 9 10 /// <summary> 11 /// 保存名字的字段 12 /// 访问修饰符protected表示被修饰的成员不仅可以被当前类访问,还可以被该类的所有子类访问,但不能通过对象被其它类访问。 13 /// </summary> 14 protected string name; 15 16 /// <summary> 17 /// 保存年龄的字段 18 /// </summary> 19 protected int age; 20 21 /// <summary> 22 /// 保存性别的字段 23 /// </summary> 24 protected bool sex; 25 26 /// <summary> 27 /// 带有参数的构造器 28 /// </summary> 29 public Person(string name, int age, bool sex) { 30 this.Name = name; 31 this.Age = age; 32 this.sex = sex; 33 } 34 35 /// <summary> 36 /// 姓名属性 37 /// </summary> 38 public string Name { 39 get { 40 return this.name; 41 } 42 set { 43 this.name = value; 44 } 45 } 46 47 /// <summary> 48 /// 年龄属性 49 /// </summary> 50 public int Age { 51 get { 52 return age; 53 } 54 set { 55 age = value; 56 } 57 } 58 59 /// <summary> 60 /// 性别属性 61 /// </summary> 62 public bool Sex { 63 get { 64 return sex; 65 } 66 set { 67 sex = value; 68 } 69 } 70 71 /// <summary> 72 /// 显示个人信息的方法 73 /// </summary> 74 public void ShowMe() { 75 Console.WriteLine("姓名:{0},性别:{1},年龄:{2}岁。", this.Name, this.Sex, this.Age); 76 } 77 } 78 79 /// <summary> 80 /// 男人类,从Person类继承 81 /// </summary> 82 public class Man : Person { 83 84 /// <summary> 85 /// 构造器。 86 /// 如果超类没有提供无参数的默认构造器,则子类就必须具备构造器 87 /// 子类的构造器参数没有必要和超类相同,但子类构造器必须使用base关键字调用超类的某个构造器 88 /// </summary> 89 public Man(string name, int age) 90 : base(name, age, true) { 91 } 92 93 /// <summary> 94 /// 表示男人抽烟的方法 95 /// </summary> 96 public void Smork() { 97 Console.WriteLine("{0}抽了一根烟", base.Name); 98 } 99 } 100 101 /// <summary> 102 /// 女人类,从Person类继承 103 /// </summary> 104 public class Woman : Person { 105 106 /// <summary> 107 /// 构造器 108 /// </summary> 109 public Woman(string name, int age) 110 : base(name, age, false) { 111 } 112 113 /// <summary> 114 /// 表示女人化妆的方法 115 /// </summary> 116 public void Makeup() { 117 Console.WriteLine("{0}化妆完毕", base.Name); 118 } 119 } 120 121 class Program { 122 static void Main(string[] args) { 123 Man man = new Man("小张", 19); 124 // 调用Man类从Person类继承的方法 125 man.ShowMe(); 126 // 调用Man类自身具有的方法 127 man.Smork(); 128 129 Woman woman = new Woman("小白", 20); 130 // 调用Woman类从Person类继承的方法 131 woman.ShowMe(); 132 // 调用Woman类自身具有的方法 133 woman.Makeup(); 134 } 135 } 136 }

继承最直接的好处是:再设计类的时候,如果存在继承关系,则可以通过继承省略大量代码的书写。

继承可以将类组成宗系,更符合软件开发的思想:组织功能模块(超类),细化功能(子类),功能异化(子类)。这句话可能暂时不好理解,随着对面向对 象学习的深入,慢慢去理解这句话的含义。看看在软件开发中,如何定义一个基本功能类,然后不断的继承它,完成功能的细化和扩展。

对于继承,语法上没有任何难点,关键要理解子类的构造器。

子类必须要调用超类的某个构造器,缺省情况下,子类调用超类的无参默认构造器,但如果超类中没有默认构造器,则必须显式说明,子类 要调用超类的哪个构造器。参考89、90行代码。所谓缺省情况,指的是无需使用base关键字显式调用超类构造器,此时子类自动调用超 类中没有参数的那个构造器。
C#语言 第一部分 面向对象 (六) 变量对对象的引用

很容易理解的概念,直接看代码,不多说了。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 namespace Edu.Study.OO.ReferenceRelationship { 7 8 /// <summary> 9 /// 依旧是人类 10 /// </summary> 11 public class Person { 12 13 /// <summary> 14 /// 保存名字的字段 15 /// </summary> 16 protected string name; 17 18 /// <summary> 19 /// 保存年龄的字段 20 /// </summary> 21 protected int age; 22 23 /// <summary> 24 /// 保存性别的字段 25 /// </summary> 26 protected bool sex; 27 28 /// <summary> 29 /// 带有参数的构造器 30 /// </summary> 31 public Person(string name, int age, bool sex) { 32 this.Name = name; 33 this.Age = age; 34 this.sex = sex; 35 } 36 37 /// <summary> 38 /// 姓名属性 39 /// </summary> 40 public string Name { 41 get { 42 return this.name; 43 } 44 set { 45 this.name = value; 46 } 47 } 48 49 /// <summary> 50 /// 年龄属性 51 /// </summary> 52 public int Age { 53 get { 54 return age; 55 } 56 set { 57 age = value; 58 } 59 } 60 61 /// <summary> 62 /// 性别属性 63 /// </summary> 64 public bool Sex { 65 get { 66 return sex; 67 } 68 set { 69 sex = value; 70 } 71 } 72 73 /// <summary> 74 /// 显示个人信息的方法 75 /// </summary> 76 public void ShowMe() { 77 Console.WriteLine("姓名:{0},性别:{1},年龄:{2}岁。", this.Name, this.Sex, this.Age); 78 } 79 } 80 81 /// <summary> 82 /// 男人类,从Person类继承 83 /// </summary> 84 public class Man : Person { 85 86 /// <summary> 87 /// 构造器。 88 /// </summary> 89 public Man(string name, int age) 90 : base(name, age, true) { 91 } 92 93 /// <summary> 94 /// 表示男人抽烟的方法 95 /// </summary> 96 public void Smork() { 97 Console.WriteLine("{0}抽了一根烟", base.Name); 98 } 99 } 100 101 class Program { 102 static void Main(string[] args) { 103 Man man = new Man("小王", 22); 104 man.ShowMe(); 105 man.Smork(); 106 107 Person person = man; 108 // 通过ShowMe执行结果可以看 到,person变量实际上就是前面实例化的Man类对象 109 // 从这个结果我们可以看出,无论是前面的man 变量,还是person变量,保存的都是一个实际对象的引用,对象客观存在,变量只是给这个对 110 // 象赋予的一个代称,一个对象可以拥有多个代 称,即一个对象可以被多个变量引用,例如: 111 // Man man2 = man; 112 // 此时,定义的新变量man2, 它和man变量具有相同的引用 113 person.ShowMe(); 114 115 // Person person = man这句代码表达了一个很容易理解的说法,男 人是人。 116 // 这种说法隐含了这样的含义:使用超类定义的变 量可以“引用”到某一个子类实例化的对象上 117 // 反过来呢?人是男人?这种说法显然是不通的, 除非我们确定我们所说得人确实是一个男人,所以 118 man = (Man)person; 119 // 这里我们应用了C语言风格的类型转换运算,由 于事先已经知道person变量确确实实是引用到Man类对象的变量,所以 120 // 可以将其转换为Man类的变量 121 122 // person.Smork(); 123 // 试着将上面这句话的注释删掉,这时候会发生一 个编译错误。Smork方法无效。这里注意,虽然我们确定person变量 124 // 引用着Man类的对象,但person变量的 类型依旧是Person类,而Person类是没有smork方法的 125 126 } 127 } 128 }

只要能够理解变量保存的是一个“对象的引用”,就可以理解上面代码的含义了。
C#语言 第一部分 面向对象 (七) 方法重载

一只狼狗无忧无虑的走在大街上,狼狗天性好斗,所以应该有一个Fight方法。

这时候他碰到了一只小狗,即Puppy类的一个对象,此时狼狗调用Fight方法,将这只puppy作为参数传入,将其狠狠修理了一顿。

不一会儿,它又碰到一只大狗,很凶恶的样子,此时狼狗的心里没有必胜的把握,但它依旧调用了Fight方法,将大狗作为参数传入。不过这 次,Fight方法只是**了一下而已,叫了几声,然后就结束了。

又过了一会儿,它碰到了一大群狗,即一个狗数组,此时狼狗吓得屁滚尿流,但还是调用了Fight方法,将狗数组作为参数传入。这次,Fight方法 执行的结果,狼狗逃之夭夭了。

可见,在我们现实生活中,总是存在这样一种情况:某类对象的某种行为,因为外界条件不同(参数不同),执行的具体流程也不同。体现在编程代码中,就 是方法的重载。

在同一个类(或某个类和它的子类)中,一系列同名的方法就构成了“重载”,重载的条件是:方法名相同,参数列表不同。 对于子类要重载超类方法的情况,超类该方法必须为protected或public类型。

看代码:
1 using System; 2 3 namespace Edu.Study.OO.Overload { 4 5 /// <summary> 6 /// 继续人类 7 /// </summary> 8 public class Person { 9 10 /// <summary> 11 /// 保存名字的字段 12 /// </summary> 13 protected string name; 14 15 /// <summary> 16 /// 保存年龄的字段 17 /// </summary> 18 protected int age; 19 20 /// <summary> 21 /// 保存性别的字段 22 /// </summary> 23 protected bool sex; 24 25 /// <summary> 26 /// 带有参数的构造器 27 /// </summary> 28 public Person(string name, int age, bool sex) { 29 this.Name = name; 30 this.Age = age; 31 this.sex = sex; 32 } 33 34 /// <summary> 35 /// 姓名属性 36 /// </summary> 37 public string Name { 38 get { 39 return this.name; 40 } 41 set { 42 this.name = value; 43 } 44 } 45 46 /// <summary> 47 /// 年龄属性 48 /// </summary> 49 public int Age { 50 get { 51 return age; 52 } 53 set { 54 age = value; 55 } 56 } 57 58 /// <summary> 59 /// 性别属性 60 /// </summary> 61 public bool Sex { 62 get { 63 return sex; 64 } 65 set { 66 sex = value; 67 } 68 } 69 70 /// <summary> 71 /// 显示个人信息的方法 72 /// </summary> 73 public void ShowMe() { 74 Console.WriteLine("姓名:{0},性别:{1},年龄:{2}岁。", this.Name, this.Sex, this.Age); 75 } 76 } 77 78 /// <summary> 79 /// 男人类 80 /// </summary> 81 public class Man : Person { 82 83 /// <summary> 84 /// 构造器。 85 /// </summary> 86 public Man(string name, int age) 87 : base(name, age, true) { 88 } 89 90 /// <summary> 91 /// 表示男人抽烟的方法 92 /// </summary> 93 public void Smork() { 94 Console.WriteLine("{0}抽了一根烟", base.Name); 95 } 96 } 97 98 /// <summary> 99 /// 女人类,从Person类继承 100 /// </summary> 101 public class Woman : Person { 102 103 /// <summary> 104 /// 构造器 105 /// </summary> 106 public Woman(string name, int age) 107 : base(name, age, false) { 108 } 109 110 /// <summary> 111 /// 表示女人化妆的方法 112 /// </summary> 113 public void Makeup() { 114 Console.WriteLine("{0}化妆完毕", base.Name); 115 } 116 } 117 118 /// <summary> 119 /// 厕所类 120 /// </summary> 121 public class Toilet { 122 123 /// <summary> 124 /// 厕所进入方法,参数为Man类型 125 /// </summary> 126 public void Comming(Man man) { 127 Console.WriteLine("{0}进了男厕所", man.Name); 128 man.Smork(); 129 } 130 131 /// <summary> 132 /// 厕所进入方法,参数为Woman类型 133 /// </summary> 134 public void Comming(Woman women) { 135 Console.WriteLine("{0}进了女厕所", women.Name); 136 women.Makeup(); 137 } 138 } 139 140 class Program { 141 static void Main(string[] args) { 142 143 // 实例化厕所对象 144 Toilet wc = new Toilet(); 145 146 // 实例化一个男人和一个女人 147 Man wang = new Man("小王", 22); 148 Woman li = new Woman("小丽", 19); 149 150 // 调用厕所类的Comming方法,分别传入 Man类型变量和Woman类型变量 151 wc.Comming(wang); 152 wc.Comming(li); 153 154 // 可以发现,通过方法的参数,C#编译器会自动 匹配最合适的方法进行调用。 155 // 方法重载可以使用同一个方法名,对传入不同参 数的各种调用场景进行自动适应 156 // 重载的要点:方法名相同,参数列表不同 157 } 158 } 159 }

熟练应用重载,虽然它并不能直接简化类的编程,但对于调用这个类的程序开发人员来说,重载则更好理解,也更直观。

前面我们讲过,只要参数列表不同,一个类可以拥有多个构造器。构造器的本质还是方法,所以多个构造器实际上还是构成了方法的重载。
C#语言 第一部分 面向对象 (九) 方法的抽象和类的抽象 练习

上一章我们讲了方法和类的抽象,这一章我们从一些例子,继续加深对抽象的理解。

第一个例子,我们定义了形状类(Shap类),从类名字就可以看出该类必然是一个抽象类。形状……本来就很抽象嘛。

好了,既然是形状,就必然有面积,Shap类的Area方法必然是一个抽象方法。

接下来,我们创建一个子类:正多边形类(Polygon类),继承自Shap类。这个类比形状类具体多了,我们知道了一个新的属性:边长。但只知道 边长依旧无法求出形状的面积,所以其继承下来的Area方法依旧只能作为抽象方法,所以Polygon类仍是一个抽象类。

最后,定义正四边形类和正五边形类,这两个类显然非常具体了,可以定义Area方法了。就这样,看代码:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 namespace Edu.Study.OO.UseAbstract { 7 8 /// <summary> 9 /// 定义形状类 10 /// </summary> 11 public abstract class Shap { 12 /// <summary> 13 /// 定义抽象方法,求面积,对于形状类,面积方法没有方法体 14 /// </summary> 15 /// <returns>面积</returns> 16 public abstract double Area(); 17 } 18 19 /// <summary> 20 /// 定义多边形类,此类继承了Area方法,但并未实现,所以依旧是抽象类 21 /// </summary> 22 public abstract class Polygon : Shap { 23 24 /// <summary> 25 /// 边长字段 26 /// </summary> 27 private double sideLength; 28 29 /// <summary> 30 /// 构造器,输入边长 31 /// </summary> 32 public Polygon(double sideLength) { 33 this.SideLength = sideLength; 34 } 35 36 /// <summary> 37 /// 边长属性 38 /// </summary> 39 public double SideLength { 40 get { 41 return sideLength; 42 } 43 set { 44 if (value > 0) { 45 sideLength = value; 46 } else { 47 Console.WriteLine("边长必须大于0。"); 48 } 49 } 50 } 51 } 52 53 /// <summary> 54 /// 定义正方形类,继承自多边形类(Polygon类) 55 /// </summary> 56 public class Square : Polygon { 57 58 /// <summary> 59 /// 构造器 60 /// </summary> 61 public Square(double sideLength) 62 : base(sideLength) { 63 } 64 65 /// <summary> 66 /// 求正方形面积,实现超类的抽象方法 67 /// </summary> 68 public override double Area() { 69 return Math.Pow(this.SideLength, 2); 70 } 71 } 72 73 /// <summary> 74 /// 定义五边形类 75 /// </summary> 76 public class Pentagon : Polygon { 77 78 /// <summary> 79 /// 构造器 80 /// </summary> 81 public Pentagon(double sideLength) 82 : base(sideLength) { 83 } 84 85 /// <summary> 86 /// 求五边形面积,这里使用工程计算求近似值,即边长的平方乘以系数1.72 87 /// </summary> 88 public override double Area() { 89 return Math.Pow(this.SideLength, 2) * 1.72F; 90 } 91 } 92 93 class Program { 94 95 static void Main(string[] args) { 96 97 // 使用Shap类变量引用到Square类实例 上 98 Shap shap = new Square(10); 99 // 输出面积结果 100 Console.WriteLine("面积={0}", shap.Area()); 101 102 // 使用Shap类变量引用到Pentagon类 实例上 103 shap = new Pentagon(10); 104 // 输出面积结果 105 Console.WriteLine("面积={0}", shap.Area()); 106 } 107 } 108 } 
我们继续,下面的例子展示了抽象的属性,我们说过,一个对象的属性由一组get/set方法来表现,所以属性也可以作为抽象存在。

下面的例子我们定义了Person类,其EyeColor属性为一个抽象属性,由其子类来具体定义。看代码:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 namespace Edu.Study.OO.UseAbstact2 { 7 8 /// <summary> 9 /// 又见人类 10 /// </summary> 11 public abstract class Person { 12 /// <summary> 13 /// 人名字字段 14 /// </summary> 15 private string name; 16 17 /// <summary> 18 /// 构造器,输入人名 19 /// </summary> 20 public Person(string name) { 21 this.Name = name; 22 } 23 24 /// <summary> 25 /// 人名属性 26 /// </summary> 27 public string Name { 28 get { 29 return name; 30 } 31 set { 32 name = value; 33 } 34 } 35 36 /// <summary> 37 /// 定义一个抽象属性,指明这个属性必须具备get/set访问器,都是抽象的 38 /// 这里为了明确,所以为该属性定义了set访问器,其实也不是没有道理,戴个隐形眼镜就可以改变眼睛的颜色了 39 /// 当然,也可以只定义某一个访问器,形成抽象只读属性或抽象只写属性 40 /// </summary> 41 public abstract string EyeColor { 42 get; 43 set; 44 } 45 } 46 47 48 /// <summary> 49 /// 定义亚洲人类,继承自人类 50 /// </summary> 51 public class AsiaPerson : Person { 52 53 /// <summary> 54 /// 隐形眼镜颜色字段 55 /// </summary> 56 private string lensesColor; 57 58 /// <summary> 59 /// 构造器,调用超类构造器 60 /// </summary> 61 public AsiaPerson(string name) 62 : base(name) { 63 } 64 65 /// <summary> 66 /// 眼睛颜色属性,实现超类抽象属性 67 /// </summary> 68 public override string EyeColor { 69 get { 70 // 如果戴隐形眼镜,则返回隐形眼镜的颜色,否则返回黑色 71 if (this.lensesColor != null) { 72 return this.lensesColor; 73 } else { 74 return "Black"; 75 } 76 } 77 set { 78 // 无法设置眼睛的颜色,所以给隐形眼镜加上颜色 79 this.lensesColor = value; 80 } 81 } 82 } 83 84 /// <summary> 85 /// 欧洲人类 86 /// </summary> 87 public class EuropePerson : Person { 88 89 /// <summary> 90 /// 隐形眼镜颜色字段 91 /// </summary> 92 private string lensesColor; 93 94 /// <summary> 95 /// 构造器,调用超类构造器 96 /// </summary> 97 public EuropePerson(string name) 98 : base(name) { 99 } 100 101 /// <summary> 102 /// 眼睛颜色属性,实现超类抽象属性 103 /// </summary> 104 public override string EyeColor { 105 get { 106 // 如果戴隐形眼镜,则返回隐形眼镜的颜色,否则返回蓝色 107 if (this.lensesColor != null) { 108 return this.lensesColor; 109 } else { 110 return "Blue"; 111 } 112 } 113 set { 114 // 无法设置眼睛的颜色,所以给隐形眼镜加上颜色 115 this.lensesColor = value; 116 } 117 } 118 } 119 120 class Program { 121 static void Main(string[] args) { 122 Person person = new AsiaPerson("王毛毛"); 123 Console.WriteLine("{0}眼睛颜色为:{1}", person.Name, person.EyeColor); 124 person.EyeColor = "Brown"; 125 Console.WriteLine("{0}眼睛颜色为:{1}", person.Name, person.EyeColor); 126 127 person = new EuropePerson("Wang Maomao"); 128 Console.WriteLine("{0}眼睛颜色为:{1}", person.Name, person.EyeColor); 129 person.EyeColor = "Green"; 130 Console.WriteLine("{0}眼睛颜色为:{1}", person.Name, person.EyeColor); 131 } 132 } 133 }
Technorati 标签: C#,教学

另外,我们也可以单独定义get或set访问器中的一个为抽象。这里就不做展示了。
C#语言 第一部分 面向对象 (十) 方法的隐藏

我们前面讲过了类和类之间的继承关系,我们了解到,超类方法和子类方法(或属性)可以具备三种联系方式。

    * 继承。超类修饰为public或protected的方法或属性可以被子类继承并访问;
    * 虚拟。虚拟的前提是可继承。超类中修饰为virtual关键字的方法或属性可以被子类继承或覆盖;
    * 抽象。抽象的前提是可继承。超类中修饰为abstract关键字的方法或属性可以被子类继承并实现,超类方法 没有方法体,由子类提供;

现在我们来学习超类方法(或属性)和子类方法(或属性)的第四种联系——隐藏。

子类继承超类方法,并一个和该方法同名、同参数的新方法,子类方法前不使用override关键字修饰。这种情况子类隐藏了超类继承的一个方法。

隐藏和覆盖最大的不同:隐藏的方法只能用该方法所属的类的变量访问,使用超类变量则无法访问,只能访问被隐藏的方法。看例子:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 namespace Edu.Study.OO.Hiden { 7 8 /// <summary> 9 /// 定义一个球体类 10 /// </summary> 11 public class Ball { 12 13 /// <summary> 14 /// 半径字段 15 /// </summary> 16 private double r; 17 18 /// <summary> 19 /// 构造器,输入球体半径 20 /// </summary> 21 public Ball(double r) { 22 this.R = r; 23 } 24 25 /// <summary> 26 /// 半径属性 27 /// </summary> 28 public double R { 29 get { 30 return this.r; 31 } 32 set { 33 if (value > 0) { 34 this.r = value; 35 } else { 36 Console.WriteLine("半径长度必须大于0。"); 37 } 38 } 39 } 40 41 /// <summary> 42 /// 球体面积方法,返回球体面积,该方法虚拟 43 /// </summary> 44 public virtual double Area() { 45 return 4 * Math.PI * Math.Pow(this.R, 2); 46 } 47 48 /// <summary> 49 /// 球体体积方法,返回球体体积,该方法虚拟 50 /// </summary> 51 public virtual double Volume() { 52 return (4F / 3F) * Math.PI * Math.Pow(this.R, 3); 53 } 54 } 55 56 57 /// <summary> 58 /// 圆柱体类,继承自球体 59 /// </summary> 60 public class Cylindrical : Ball { 61 62 /// <summary> 63 /// 圆柱体高度 64 /// </summary> 65 private double height; 66 67 /// <summary> 68 /// 构造器,输入半径和高度 69 /// </summary> 70 public Cylindrical(double r, double height) 71 : base(r) { 72 this.Height = height; 73 } 74 75 /// <summary> 76 /// 高度属性 77 /// </summary> 78 public double Height { 79 get { 80 return this.height; 81 } 82 set { 83 if (value > 0) { 84 this.height = value; 85 } else { 86 Console.WriteLine("高度必须大于0。"); 87 } 88 } 89 } 90 91 /// <summary> 92 /// 覆盖超类中虚拟的面积方法,求圆柱体表面积 93 /// </summary> 94 public override double Area() { 95 return (2 * Math.PI * this.R * this.Height) + (Math.PI * Math.Pow(this.R, 2) * 2); 96 } 97 98 /// <summary> 99 /// 隐藏超类中球体积的方法,改为求圆柱体体积 100 /// </summary> 101 public new double Volume() { 102 return Math.PI * Math.Pow(this.R, 2) * this.Height; 103 } 104 } 105 106 class Program { 107 108 static void Main(string[] args) { 109 110 // 输出Ball类对象Area方法和 Volume方法的返回值 111 Ball ball = new Ball(20); 112 Console.WriteLine("Ball's area = {0}", ball.Area()); 113 Console.WriteLine("Ball's area = {0}", ball.Volume()); 114 115 // 输出Cylindrical类对象Area方 法和Volume方法的返回值 116 Cylindrical cyl = new Cylindrical(20, 30); 117 Console.WriteLine("Cylindrical's area = {0}", cyl.Area()); 118 Console.WriteLine("Cylindrical's area = {0}", cyl.Volume()); 119 120 121 // 令Ball类的变量引用到 Cylindrical类的对象上 122 ball = cyl; 123 Console.WriteLine("Ball's area = {0}", ball.Area()); // 此处调用Area方法时,调用的其实是Cylindrical类的Area方法 124 Console.WriteLine("Ball's area = {0}", ball.Volume()); // 此处调用Volume方法时,调用的其实是Ball类的方法 125 } 126 } 127 }

从代码中可以清楚的看到覆盖和隐藏具体的区别,当使用超类变量引用到子类实例后,依旧可以访问子类覆盖后的方法,但无法访问到子类隐藏后的方法,只 能访问被子类隐藏的方法。

注意,本例是为了让大家看明白覆盖和隐藏的区别,对于隐藏来说,被隐藏的超类方法无须修饰为virtual。

一半,子类方法要隐藏父类方法,在方法声明前加上new关键字,表示此方法是被隐藏的方法。new关键字并不是必需的。

隐藏表示了这样一种含义:子类定义了和超类名称相同但流程不同的方法,但只能以子类类型可以访问,使用超类类型则无法访问。

隐藏的情况较之覆盖要少见一些,以至于许多面向对象语言(例如Java)并不提供方法的隐藏。C#提供此特性是为了保证语言的完整性,在一些特殊情 况下可以解决一些问题。
C#语言 第一部分 面向对象 (十一) 接口

学习过C++的童鞋都应该知道,在C++中允许将一个类的成员函数定义为virtual(虚拟函数),定义为虚拟的函数,例如:
1 class CTest 2 { 3 public: 4 virtual void TestA(); 5 virtual void TestB() = 0; 6 }; 7 8 void CTest::TestA() 9 { 10 cout<<"Hello"<<endl; 11 } 12 13 class CTestChild : public CTest 14 { 15 public: 16 virtual void TestB(); 17 }; 18 19 void CTestChild::TestB() 20 { 21 cout<<"Hello World"<<endl; 22 } 23 24 int main(int argc, char* argv[]) 25 { 26 CTest* pTest = new CTestChild(); 27 pTest->TestA(); 28 pTest->TestB(); 29 }

形如第4行的函数声明称为“虚拟函数”,可以被子类的同名函数(返回值类型,参数也必须完全相同)覆盖;形如第 5行的函数声明称为“纯虚拟函数”,这个函数没有函数体,必须被子类的同名函数(返回值类型,参数也必须完全相同)覆盖。

前面的课程,我们学习了C#中的虚拟方法(使用virtual修饰的方法)和抽象方法(使用abstract修饰的方法),相当于C++中的虚拟函数和纯虚拟函数。

我们可以在C++中设计这样的一种类,这个类中的所有成员函数都为纯虚拟函数,即都没有方法体。这种类型的类非常有用。在C#中,语言设计者为它起了一个新的名字——接口。

接口是那样的有用,以至于成为了一个程序员是否完全理解面向对象的重要标志。

接口在现实生活中非常常见,例如我们连接DVD影碟机和电视机,只要我们有这样的两个设备,使用电缆将它们连接在一起就可以了,无须考虑这两个设备是否可以相互兼容。这其中的道理非常简单,影碟机可以输出AV信号,电视机可以接受AV信号,所以可以连接。

电脑主机箱和显示器的道理也是如此;U盘和USB接口的道理也相同。

所以接口并不是一个实际存在的插口,而是双方都共同遵守的一种行为准则。因为双方都遵守这样的规范,所以双方可以毫无障碍的联系在一起。

什么叫做行为准则?例如我定义一个行为准则叫做Eatable(可食用的),凡是遵守这个准则的对象,就都可以塞进嘴巴里并咽下去,就像超市卖的食品上面蓝色的S标志,当得知对象遵循这个准则,则无需关心这个对象的成分、形状或任何其它属性,就可以把它和我们的嘴巴联系起来(当然营养与否,好吃不好吃那是另一码事儿了)。

聪明的童鞋可能已经反应过来了,所谓行为规范,就是不同对象所属的不同类,具有相同的超类,并且都实现了超类中定义的抽象方法。这样所有的类就具备相同的行为规范了。这种想法,在C++中是可行的。但在C#中,对于接口的定义则更为严格,原因有二:

   1. C#并不允许多继承,而C++可以,C#不想出现C++中难以理解的父子悖论而禁止多继承,所以在C#中,一旦使用超类来约束行为,则会对编程带来不小的麻烦——我们没法再继承其它类或引入其它行为准则了。
   2. 符合同一行为准则的,可以是完全不相干的不同类,例如同样符合 Eatable可食用准则的类,有可能是化学品类的子类,也有可能是生物类的子类或者植物种子类的子类。使用超类引入行为准则从继承的角度是非常不合理的。

我们来看一个例子:
1 public abstract class Runnable { 2 public abstract void Run(); 3 } 4 5 public class Mechanical{ 6 } 7 8 public class Car : Mechanical{ 9 } 10 11 public class Animal { 12 } 13 14 public class Cat : Animal { 15 }

代码中,我试图确立一种行为规范称之为“会跑的”(Runnable),有两个类需要遵循此规范,Car类和Cat类,显然汽车和猫都是会跑的东西,都可以实现抽象方法Run。

但问题出现了,Car类显然需要从机械类(Mechanical)继承,因为汽车是机械的一种,但显然并不是所有的机械都会跑(例如电动刮胡刀);而Cat类显然是从动物类(Animal)继承,但也不是所有的动物都会跑(例如鱼)。Car和Cat完全找不到相同的超类,但它们又可以遵循同样的行为规范,一个类不可能既继承自Animal类又继承自Runnable类!怎么办?

再看代码:
1 public interface Runnable { 2 void Run(); 3 } 4 5 public class Mechanical { 6 } 7 8 public class Car : Mechanical, Runnable { 9 public void Run() { 10 Console.WriteLine("汽车开走了!"); 11 } 12 } 13 14 public class Animal { 15 } 16 17 public class Cat : Animal, Runnable { 18 public void Run() { 19 Console.WriteLine("猫跑掉了!"); 20 } 21 }

我们使用了一个新的关键字interface,很容易就解决了上面的问题,用interface声明的我们称为接口。我们发现,接口中可以包含方法的声明,而所有的类除了可以继承一个超类外,还可以继承多个接口。当然,习惯上把对接口的继承不叫做继承,而叫做“实现”,即一个类实现了某些接口。

接口定义的特点:接口中定义的方法上,自动为public abstract,即公共抽象方法。接口本身也为抽象。接口中不允许以任何方式包含具有方法体的方法。

接口完美的解决了所有的问题:

    * 接口中只允许包含方法声明,所以即便接口被多继承,也不会产生任何父子悖论问题;
    * 接口不是实体类,不带有类别的含义,只是一种行为的规范,所以可以被不同含义的多个类同时继承;
    * 接口可以赋予不同的类相同的行为准则(接口中定义的方法声明),所以接口类型变量可以引用到所有实现此接口类的实例上。

属性是一对方法,所以接口中也可以包含属性的定义。接口中不允许出现字段的定义。
1 public interface Runnable { 2 // 定义抽象属性 3 double Speed { 4 get; 5 } 6 void Run(); 7 } 8 9 public class Mechanical { 10 } 11 12 public class Car : Mechanical, Runnable { 13 private double speed = 60.0; 14 15 public void Run() { 16 Console.WriteLine("汽车开走了!"); 17 } 18 19 /// <summary> 20 /// 实现接口中定义的抽象属性并添加 set访问器 21 /// </summary> 22 public double Speed { 23 get { 24 return this.speed; 25 } 26 set { 27 this.speed = value; 28 } 29 } 30 } 31 32 public class Animal { 33 } 34 35 public class Cat : Animal, Runnable { 36 37 public void Run() { 38 Console.WriteLine("猫跑掉了!"); 39 } 40 41 /// <summary> 42 /// 实现接口中定义的抽象属性 43 /// </summary> 44 public double Speed { 45 get { 46 return 5.0; 47 } 48 } 49 }

好了,我们看一段完整的代码,从中体会接口的特点。
1 using System; 2 3 namespace Edu.Study.OO.Interface { 4 5 public interface Runnable { 6 // 定义抽象属性,该属性只读 7 double Speed { 8 get; 9 } 10 void Run(); 11 } 12 13 /// <summary> 14 /// 定义机械类,为了简便此类没有定义属性和方法 15 /// </summary> 16 public class Mechanical { 17 } 18 19 /// <summary> 20 /// 定义汽车类,从机械类继承,实现Runnable接口 21 /// </summary> 22 public class Car : Mechanical, Runnable { 23 24 /// <summary> 25 /// 保存速度属性值的字段 26 /// </summary> 27 private double speed = 60.0; 28 29 /// <summary> 30 /// 实现接口定义的Run方法 31 /// </summary> 32 public void Run() { 33 Console.WriteLine("汽车开走了!"); 34 } 35 36 /// <summary> 37 /// 实现接口中定义的抽象属性并添加set访问器 38 /// </summary> 39 public double Speed { 40 get { 41 return this.speed; 42 } 43 // 注意:set访问器不是接口所定义 44 set { 45 this.speed = value; 46 } 47 } 48 } 49 50 /// <summary> 51 /// 定义动物类,为了简便此类没有定义属性和方法 52 /// </summary> 53 public class Animal { 54 } 55 56 /// <summary> 57 /// 定义猫类,继承自动物类,实现Runnable接口 58 /// </summary> 59 public class Cat : Animal, Runnable { 60 61 /// <summary> 62 /// 实现接口中的Run方法。 63 /// </summary> 64 public void Run() { 65 Console.WriteLine("猫跑掉了!"); 66 } 67 68 /// <summary> 69 /// 实现接口中定义的抽象属性 70 /// </summary> 71 public double Speed { 72 get { 73 return 5.2; 74 } 75 } 76 } 77 78 /// <summary> 79 /// 定义道路类 80 /// </summary> 81 public abstract class Road { 82 83 /// <summary> 84 /// 定义抽象属性,限速,只读 85 /// </summary> 86 public abstract double SpeedLimit { 87 get; 88 } 89 90 /// <summary> 91 /// 在路上跑的方法,所有可以跑的东西都可以作为参数 92 /// </summary> 93 public void RunAt(Runnable runner) { 94 // 如果传入对象的速度不够,则不运行其Run方法 95 if (runner.Speed < this.SpeedLimit) { 96 Console.WriteLine("对不起,速度不够,不能在道路上奔跑。"); 97 } else { 98 runner.Run(); 99 } 100 } 101 } 102 103 /// <summary> 104 /// 定义低速路,继承自Road类 105 /// </summary> 106 public class LowSpeedRoad : Road { 107 108 /// <summary> 109 /// 覆盖Road类抽象属性 110 /// </summary> 111 public override double SpeedLimit { 112 get { 113 return 5; 114 } 115 } 116 } 117 118 /// <summary> 119 /// 定义高速路,继承自Road类 120 /// </summary> 121 public class HighSpeedRoad : Road { 122 123 /// <summary> 124 /// 覆盖Road类抽象属性 125 /// </summary> 126 public override double SpeedLimit { 127 get { 128 return 90; 129 } 130 } 131 } 132 133 class Program { 134 static void Main(string[] args) { 135 // 定义一只猫 136 Cat cat = new Cat(); 137 138 // 定义一辆车 139 Car car = new Car(); 140 car.Speed = 100; // 由于car的Speed属性具有set访问器,所以可以设置属性值 141 142 // 定义高速路 143 Road road = new HighSpeedRoad(); 144 road.RunAt(cat); 145 road.RunAt(car); 146 147 // 定义低速路 148 road = new LowSpeedRoad(); 149 road.RunAt(cat); 150 } 151 } 152 }

 


讯享网

除了上面讲的接口概念,C#还有一个非常有趣的特性,即接口的显式实现 ,接口显式实现体现了这样一种效果:一 个类同时实现多个接口中的多个同名方法 。例如接口IA中有个方法void Test();,接口IB也有一个方法void Test();,类C实现IA和IB接口。如下:

1 /// <summary> 2 /// 接口IA 3 /// </summary> 4 public interface IA { 5 /// <summary> 6 /// 在接口IA中定义Test方法 7 /// </summary> 8 void Test(); 9 } 10 11 /// <summary> 12 /// 接口IB 13 /// </summary> 14 public interface IB { 15 /// <summary> 16 /// 在接口IB中定义Test方法(和IA中定义的Test方法一致) 17 /// </summary> 18 void Test(); 19 } 20 21 /// <summary> 22 /// 定义类C,同时实现IA接口和IB接口 23 /// 这时候可以有三种选择: 24 /// 1、实现IA和IB接口定义的Test方法 25 /// 2、针对接口IA实现其定义的Test方法 26 /// 3、针对接口IB实现其定义的Test方法 27 /// </summary> 28 public class C : IA, IB { 29 30 /// <summary> 31 /// 实现IA和IB接口定义的Test方法 32 /// 以public关键字修饰,表示该方法公共 33 /// </summary> 34 public void Test() { 35 Console.WriteLine("OK"); 36 } 37 38 /// <summary> 39 /// 指明实现IA接口中定义的Test方法, 40 /// 使用 接口名.方法 来指定要实现的方法是由哪一个接口定义的。 41 /// 显式实现的方法前无访问修饰符,表示其至少不是public的,所以无法使用C类对象直接访问 42 /// </summary> 43 void IA.Test() { 44 Console.WriteLine("Hello"); 45 } 46 47 /// <summary> 48 /// 指明实现IB接口中定义的Test方法 49 /// </summary> 50 void IB.Test() { 51 Console.WriteLine("World"); 52 } 53 } 54 55 /// <summary> 56 /// 主方法 57 /// </summary> 58 static void Main(string[] args) { 59 60 // 实例化C类的对象 61 C c = new C(); 62 63 // 通过C类对象调用Test方法,这时调用的是以public修饰符修饰的Test实现 64 c.Test(); 65 66 // 将C类的引用类型提升为IA类的引用类型,此时,调用的是C类中显式实现的 IA.Test方法 67 IA a = c; 68 a.Test(); 69 70 // 将C类的引用类型提升为IB类的引用类型,此时,调用的是C类中显式实现的 IB.Test方法 71 IB b = c; 72 b.Test(); 73 }

小讯
上一篇 2025-01-28 22:52
下一篇 2025-03-23 16:46

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/48495.html