【C#学习笔记】二、面向对象编程

2.1 抽象类与接口

1)概念

抽象类是特殊的类,只是不能被实例化;除此以外,具有类的其他特性;重要的是抽象类可以包括抽象方法,这是普通类所不能的。抽象方法只能声明于抽象类中,且不包含任何实现,派生类必须覆盖它们。另外,抽象类可以派生自一个抽象类,可以覆盖基类的抽象方法也可以不覆盖,如果不覆盖,则其派生类必须覆盖它们。接口是引用类型的,接口和抽象类实现了oop中的一个原则,把可变的与不可变的分离。抽象类和接口就是定义为不可变的,而把可变的作为子类去实现,接口和抽象类的相似之处有三点:

²        不能实例化;

²        包含未实现的方法声明;

²        派生类必须实现未实现的方法,抽象类是抽象方法,接口则是所有成员(不仅是方法包括其他成员);

2)抽象类和接口的区别

²        类是对对象的抽象,可以把抽象类理解为把类当作对象,抽象成的类叫做抽象类.而接口只是一个行为规范或规定

²        接口基本上不具备继承的任何具体特点,它仅仅承诺了能够调用的方法

²        一个类一次可以实现若干个接口,但是只能扩展一个父类

²        接口除了可以包含方法之外,还可以包含属性、索引器、事件,而且这些成员都被定义为公有的。除此之外,不能包含任何其他的成员,例如:常量、域、构造函数、析构函数、静态成员。一个类可以直接继承多个接口,但只能直接继承一个类(包括抽象类)。

3)抽象类和接口的使用:

²       
如果预计要创建组件的多个版本,则创建抽象类。抽象类提供简单的方法来控制组件版本。

²       
如果创建的功能将在大范围的全异对象间使用,则使用接口。如果要设计小而简练的功能块,则使用接口。

²       
如果要设计大的功能单元,则使用抽象类.如果要在组件的所有实现间提供通用的已实现功能,则使用抽象类。

²       
抽象类主要用于关系密切的对象;而接口适合为不相关的类提供通用功能。

以下是我在网上看到的几个形象比喻,真的非常不错,呵呵:
1.飞机会飞,鸟会飞,他们都继承了同一个接口“飞”;但是F22属于飞机抽象类,鸽子属于鸟抽象类。
2. 就像铁门木门都是门(抽象类),你想要个门我给不了(不能实例化),但我可以给你个具体的铁门或木门(多态);

而且只能是门,你不能说它是窗(单继承);一个门可以有锁(接口)也可以有门铃(多实现)。 门(抽象类)定义了你是什么,接口(锁)规定了你能做什么(一个接口最好只能做一件事,你不能要求锁也能发出声音吧(接口污染))。

2.2 接口多态性

假定不使用基类提供EatFood()方法,而是把该方法放到接口IConsume上,Cow和Chicken类也支持这个接口(Cow和Chicken类必须提供EatFood()方法的执行代码),接着就可以使用下述代码访问该方法:

Cow myCow=new Cow();

Chicken myChicken=new Chicken();

IConsume consumeInterface;

consumeInterface=myConw;

consumeInterface.EatFood();

consumeInterface=myChicken;

consumeInterface.EatFood;

2.3 Window应用程序中的OOP(P180)

【例子1】

private void button1_Click(object
sender,System.EventArgs e)

{

((CButton)sender).Text=”Clicked!”;

Button
newButton=new Button();

newButton.Text=”New
Button!”;

newButton.Click+=new
EventHandler(newButton_Click);

Controls.Add(newButton);

}

private void newButton_Click(object
sender,System.EventArgs e)

{

((Button)sender).Text=”Clicked!”;

}

2.4 类的定义(P184)

C#中只能有一个基类,如果继承了一个抽象类,就必须实现所继承的所有抽象成员(除非派生类也是抽象的)。一个类可指定多个接口,如:

public class MyClass: MyBase, IMyInterface,
IMySecondInterface

{

}


修饰符


含义


无或internal


类只能在当前项目中访问


public


类可以在任何地方访问


abstract或internal abstract


类只能在当前项目中访问,不能实例化,只能继承


public abstract


类可以在任何地方访问,不能实例化,只能继承


sealed 或internal sealed


类只能在当前项目中访问,不能派生,只能实例化


public sealed


类可以在任何地方访问,不能派生,只能实例化

关键字abstract和sealed不能在接口中使用,因为这两个修饰符在接口定义中无意义(接口不包含执行代码,所以不能直接实例化,且必须是可以继承的)。接口可使用多个基接口,如:

public interface IMyInterface:
IMyBaseInterface, IMyBaseInterface2

{

}

2.5 构造函数执行顺序(P192)

public class MyBaseClass

{

public
MyBaseClass(){}

public
MyBaseClass(int i){}

}

public class MyDerivedClass: MyBaseClass

{

public
MyDerivedClass(){}

public
MyDerivedClass(int i){}

public MyDerivedClass(int
i, int j){}

}

MyDerivedClass myObj=new MyDerivedClass();

l        
执行System.Object.Object()构造函数。

l        
执行MyBaseClass.MyBaseClass()构造函数。

l        
执行MyDerivedClass.MyDerivedClass()构造函数。

MyDerivedClass myObj=new MyDerivedClass(4);

l        
执行System.Object.Object()构造函数。

l        
执行MyBaseClass.MyBaseClass(int i)构造函数。

l        
执行MyDerivedClass.MyDerivedClass(int i)构造函数。

MyDerivedClass myObj=new
MyDerivedClass(4,8);

l        
执行System.Object.Object()构造函数。

l        
执行MyBaseClass.MyBaseClass()构造函数。

l        
执行MyDerivedClass.MyDerivedClass(int i,int j)构造函数。

若MyDerivedClass的定义做如下修改:

public class MyDerivedClass:MyBaseClass

{

….

public
MyDerivedClass(int i, int j) : base(i)

{}

}

则MyDerivedClass
myObj=new MyDerivedClass(4,8);其执行顺序如下:

l        
执行System.Object.Object()构造函数。

l        
执行MyBaseClass.MyBaseClass(i)构造函数。

l        
执行MyDerivedClass.MyDerivedClass(int i,int j)构造函数。

若MyDerivedClass定义作如下修改:

public class MyDerivedClass:MyBaseClass

{

public
MyDerivedClass():this(5,6)

{}

public
MyDerivedClass(int i, int j) :base(i)

}

则MyDerivedClass
myObj=new MyDerivedClass();的执行顺序如下:

l        
执行System.Object.Object()构造函数。

l        
执行MyBaseClass.MyBaseClass(i)构造函数。

l        
执行MyDerivedClass.MyDerivedClass(int i,int j)构造函数。

l        
执行MyDerivedClass.MyDerivedClass()构造函数。

2.6 定义类成员(P209)

1)成员修饰符

public:   成员可以由任何代码范围

private:   成员只能由类中的代码访问(如果没有使用任何关键字,就默认使用这个关键字)

internal:  成员只能由定义它的项目(程序集)内部的代码访问

protected:
成员只能由类或派生类中的代码访问

2)方法修饰符

static: 
只能通过类访问,不能通过对象实例化来访问

abstract: 方法必须在非抽象的派生类中重写(只用于抽象类中)

virtual: 
方法可以重写

override: 方法重写了一个基类方法(如果方法被重写,就必须使用该关键字)

extern:  
方法定义放在其它地方

【例子1】字段、属性与方法

public class MyClass

{

//使用readonly修饰,只能声明或构造函数中赋值

public
readonly string
Name;

private int
intVal;

//属性Val的访问器属性为protect所以不能直接在main中使用,但可在其派生类中使用

public
int Val

{

protected
get

{

return
intVal+1;

}

//不能同时设置两个访问器的权限

set

{

if (value >= 0 && value <= 10)

intVal = value;

else

//使用throw抛出异常

throw (new ArgumentOutOfRangeException("Val",value,"Val 必须是0~10"));

}

}

//重写了Object类的ToString方法,所以要用override修饰符

public
override string
ToString()

{

return
"Name:" + Name + "\nVal:" + Val;

}

//使用this关键字调用自身的MyClass(string newName)构造函数

private
MyClass(): this("Default
Name"){}

public
MyClass(string newName)

{

Name = newName;

intVal = 0;

}

}

public class MyClass2 : MyClass

{

Internal
int myVal;

//构造函数中使用base关键字调用基类的构造函数

public
MyClass2() : base("MyClass2"){}

//由于基类的Val属性的get访问器修饰符为protected,只能在类或派生类代码中访问

public
int Val

{

//派生类代码中可直接使用基类中Val属性的get和set访问器

get
{return base.Val;}

set
{base.Val = value;}

}

}

class Program

{

static void Main(string[] args)

{

MyClass2 obj2=new MyClass2();

Console.WriteLine(obj2.ToString());

obj2.myVal = 20;

Console.WriteLine("obj2.myVal={0}", obj2.myVal);

for
(int i = 0; i <= 11; i++)

{

obj2.Val = i;

Console.WriteLine("intVal={0}", obj2.Val);

}

Console.ReadKey();

}

}

【例子2】类中的静态字段和静态方法(Ch09Ex03)

class
Program

{

static void
Main(string[]
args)

{

//实例化时只能调用非静态函数及非静态字段,类调用时只能调用静态函数和静态字段

MyExternalClass
myClass = new MyExternalClass(2);

myClass.ShowIntVal();

MyExternalClass.strName
= "My Static Name";

MyExternalClass.ShowName();

Console.ReadKey();

}

}

public class MyExternalClass

{

private int
myIntVal;

public
static string
strName;

public
static void
ShowName()

{

Console.WriteLine(strName);

}

//使用构造函数来为私有变量myIntVal赋值

public MyExternalClass(int nVal)

{

myIntVal = nVal;

}

public
void ShowIntVal()

{

Console.WriteLine("MyIntVal={0}", myIntVal);

}

}

2.7 隐藏基类方法(P219)

class Program

{

static
void Main(string[] args)

{

DerivedClass1
C1 = new DerivedClass1();

DerivedClass2
C2 = new DerivedClass2();

C1.DoSomething1();

C2.DoSomething2();

BaseClass
C0;

C0 = C1;

C0.DoSomething1();   //执行基类的代码,输出 DoSomething1 In Base Class

C0=C2;

C0.DoSomething2();   //执行继承类的代码,输出DoSomething2 In DerivedClass2

}

}

class BaseClass

{

internal
void DoSomething1()

{

Console.WriteLine("DoSomething1 In Base Class");

}

//使用virtual修饰,表示方法可以被重写

virtual
internal void
DoSomething2()

{

Console.WriteLine("DoSomething2 In Base Class");

}

}

class DerivedClass1 : BaseClass

{

new
internal void
DoSomething1()

{

Console.WriteLine("DoSomething1 In DerivedClass1");

}

}

class DerivedClass2 : BaseClass

{

//override会重写基类代码

internal
override void
DoSomething2()

{

Console.WriteLine("DoSomething2 In DerivedClass2");

}

}

时间: 2024-10-30 12:19:29

【C#学习笔记】二、面向对象编程的相关文章

C++ Primer 学习笔记_34_面向对象编程(5)--虚函数与多态(二):纯虚函数、抽象类、虚析构函数、动态创建对象

C++ Primer 学习笔记_34_面向对象编程(5)--虚函数与多态(二):纯虚函数.抽象类.虚析构函数.动态创建对象 一.纯虚函数 1.虚函数是实现多态性的前提 需要在基类中定义共同的接口 接口要定义为虚函数 2.如果基类的接口没办法实现怎么办? 如形状类Shape 解决方法 将这些接口定义为纯虚函数 3.在基类中不能给出有意义的虚函数定义,这时可以把它声明成纯虚函数,把它的定义留给派生类来做 4.定义纯虚函数: class <类名> { virtual <类型> <函

C++ Primer 学习笔记_31_面向对象编程(2)--继承(二):继承与构造函数、派生类到基类的转换 、基类到派生类的转换

C++ Primer 学习笔记_31_面向对象编程(2)--继承(二):继承与构造函数.派生类到基类的转换 .基类到派生类的转换 一.不能自动继承的成员函数 构造函数 拷贝构造函数 析构函数 =运算符 二.继承与构造函数 基类的构造函数不被继承,派生类中需要声明自己的构造函数. 声明构造函数时,只需要对本类中新增成员进行初始化,对继承来的基类成员的初始化调用基类构造函数完成(如果没有给出则默认调用默认构造函数). 派生类的构造函数需要给基类的构造函数传递参数 #include <iostream

C++ Primer 学习笔记_73_面向对象编程 --再谈文本查询示例

面向对象编程 --再谈文本查询示例 引言: 扩展第10.6节的文本查询应用程序,使我们的系统可以支持更复杂的查询. 为了说明问题,将用下面的简单小说来运行查询: Alice Emma has long flowing red hair. Her Daddy says when the wind blows through her hair, it looks almost alive, like a fiery bird in flight. A beautiful fiery bird, he

C++ Primer 学习笔记_66_面向对象编程 --定义基类和派生类[续]

算法旨在用尽可能简单的思路解决问题,理解算法也应该是一个越看越简单的过程,当你看到算法里的一串概念,或者一大坨代码,第一感觉是复杂,此时不妨从例子入手,通过一个简单的例子,并编程实现,这个过程其实就可以理解清楚算法里的最重要的思想,之后扩展,对算法的引理或者更复杂的情况,对算法进行改进.最后,再考虑时间和空间复杂度的问题. 了解这个算法是源于在Network Alignment问题中,图论算法用得比较多,而对于alignment,特别是pairwise alignment, 又经常遇到maxim

C++ Primer 学习笔记_65_面向对象编程 --概述、定义基类和派生类

面向对象编程 --概述.定义基类和派生类 引言: 面向对象编程基于的三个基本概念:数据抽象.继承和动态绑定. 在C++中,用类进行数据抽象,用类派生从一个类继承另一个:派生类继承基类的成员.动态绑定使编译器能够在运行时决定是使用基类中定义的函数还是派生类中定义的函数. 继承和动态绑定在两个方面简化了我们的程序:[继承]能够容易地定义与其他类相似但又不相同的新类,[派生]能够更容易地编写忽略这些相似类型之间区别的程序. 面向对象编程:概述 面向对象编程的关键思想是多态性(polymorphism)

C++ Primer 学习笔记33_面向对象编程(4)--虚函数与多态(一):多态、派生类重定义、虚函数的访问、 . 和-&gt;的区别、虚析构函数、object slicing与虚函数

C++ Primer学习笔记33_面向对象编程(4)--虚函数与多态(一):多态.派生类重定义.虚函数的访问. . 和->的区别.虚析构函数.object slicing与虚函数 一.多态 多态可以简单地概括为"一个接口,多种方法",前面讲过的重载就是一种简单的多态,一个函数名(调用接口)对应着几个不同的函数原型(方法). 更通俗的说,多态行是指同一个操作作用于不同的对象就会产生不同的响应.或者说,多态性是指发出同样的消息被不同类型的对象接收时有可能导致完全不同的行为. 多态行分

C++ Primer 学习笔记_67_面向对象编程 --转换与继承、复制控制与继承

面向对象编程 --转换与继承.复制控制与继承 I.转换与继承 引言: 由于每一个派生类对象都包括一个基类部分,因此能够像使用基类对象一样在派生类对象上执行操作. 对于指针/引用,能够将派生类对象的指针/引用转换为基类子对象的指针/引用. 基类类型对象既能够作为独立对象存在,也能够作为派生类对象的一部分而存在,因此,一个基类对象可能是也可能不是一个派生类对象的部分,因此,没有从基类引用(或基类指针)到派生类引用(或派生类指针)的(自己主动)转换. 关于对象类型,尽管一般能够使用派生类型的对象对基类

C++ Primer 学习笔记_69_面向对象编程 --继承情况下的类作用域

面向对象编程 --继承情况下的类作用域 引言: 在继承情况下,派生类的作用域嵌套在基类作用域中:如果不能在派生类作用域中确定名字,就在外围基类作用域中查找该名字的定义. 正是这种类作用域的层次嵌套使我们能够直接访问基类的成员,就好像这些成员是派生类成员一样: Bulk_item bulk; cout << bulk.book() << endl; 名字book的使用将这样确定[先派生->后基类]: 1)bulk是Bulk_item类对象,在Bulk_item类中查找,找不到名

C++ Primer学习笔记32_面向对象编程(3)--继承(三):多重继承、虚继承与虚基类

C++ Primer学习笔记32_面向对象编程(3)--继承(三):多重继承.虚继承与虚基类 一.多重继承 在C++语言中,一个派生类可以从一个基类派生,称为单继承:也可以从多个基类派生,称为多继承. 多重继承--一个派生类可以有多个基类 class <派生类名> : <继承方式1> <基类名1>,<继承方式2> <基类名2>,... { <派生类新定义成员> }; 可见,多继承与单继承的区别从定义格式上看,主要是多继承的基类多于一个

C++ Primer 学习笔记_74_面向对象编程 --再谈文本查询示例[续/习题]

面向对象编程 --再谈文本查询示例[续/习题] //P522 习题15.41 //1 in TextQuery.h #ifndef TEXTQUERY_H_INCLUDED #define TEXTQUERY_H_INCLUDED #include <iostream> #include <fstream> #include <sstream> #include <vector> #include <set> #include <map&g