标签:
C#学习心得
1、C#的数据类型
C#的数据类型主要有2类,一类是值类型,一类是引用类型。(注:对象变量,和对象的概念要区分)
对于值类型,他们之间的对象传递,其实质就是一种“值传递”,两个值类型对象相互独立,一个对象的改变不会影响另一个对象,值类型对象包含实际的数值。
例:Int a,b;
a = 10;(a是对象变量)
b = a;
b = 5;
这时,a等于多少? a = 10;
对于引用类型,对象变量实际上包含的是指向对象的引用,类似于指针,这时,两个对象变量之间的传递就是指针地址的传递,一个对象变量的改变将会影响另一个变量。
例:object a ,b;
a = new object();(new object()这个表达式会生成一个对象)
b = a;
这时如果b对象变量的一个属性值发生了改变,那a对象呢??A对象变量的的同一个属性值也发生改变,因为他们实际上引用的是同一个对象。
值类型 |
结构,枚举,可以为Null的类型 |
引用类型 |
类,数组,委托,接口 |
2、Null和可以为Null的类型
对于很多类型来说,它的数值范围并不包含Null,数据的存储时,有的属性如果不存在,但是又不能取Null,这样将会产生很多麻烦。为此,C#规定:类型为Nullable<T>或T?的类型可以取Null,这时Nullable<T>或T?类型的取值范围是:T类型原本的取值范围再加上Null.
3、访问性的问题
对于类而言,默认的访问级别是internal,它表示在同一个程序集内可以当作Public,其他的程序集则不能访问。对于类中的成员,成员的默认访问级别是Private,私有的,表示只有类内的其他成员可以使用,其他类是看不见这个成员的。
使用场合 |
C# |
说明 |
Type(指类,接口等类型) |
public |
访问不受限制 |
internal |
访问范围仅限于同一个程序集内 |
|
Member(指类型的成员) |
public |
访问不受限制 |
private |
只有自己的类的成员可以访问 |
|
internal |
访问范围仅限于同一个程序集内 |
|
protected |
访问范围仅限于自己和派生出来的子类 |
|
Protected internal |
在同一程序集内访问不受限制,在不同程序集内仅有此类型派生出来的子类型可以访问 |
4、继承的一些问题
对于类而言,一个类最多可以继承一个类,这将极大的制约继承的范围和作用,为此,C#定义了接口的概念,这时,一个类就可以继承多个接口,极大的扩展了类的作用范围。
4.1、方法重载、隐藏与虚方法调用
由于子类对象同时“汇集了”父类和子类的所有公共方法,而C#并未对子类和父类的方法名称进行了过多限制,因此,一个问题产生了:
如果子类中某个方法与父类方法的签名一样,那当通过子类对象访问此方法是,访问的是子类还是父类定义的方法?
总的来说,子类方法与父类方法之间的关系可以概括为一下三种。
扩充(Extend):子类方法,父类没有;
重载(OverLoad):子类有父类的同名函数,但参数类型或数目不一样;
完全相同:子类方法与父类方法从方法名到参数类型完全一样;
对于第一种”扩充“关系,由于父类与子类方法名不同,所以不存在同名方法调用的问题,重点分析一下后两种情况。
(1)重载(OverLoad)
在前面介绍过方法重载的概念,在同一个类中构成重载的方法主要根据参数列表来决定
调用哪一个。这一基本判断方法可以推广到类的继承情况。
例如,以下代码在子类和父类中定义了一个重载的方法OverloadF():
class Parent
{
public void OverloadF()
{
}
}
class Child:Parent
{
public void OverloadF(int i)
{
}
}
使用代码如下:
Childobj = new Child();
obj.OverloadF(); //调用父类的重载方法
obj.OverloadF(100);//调用子类的重载方法
可以看到,虽然重载的方法分布在不同的类中,但仍然可以将其看成是定义在同一个类
中的,其使用方式与调用类的其他方法并无不同。
(2)隐藏(Hide)
当子类与父类拥有完全一样的方法时,称“子类隐藏了父类的同名方法”
class Parent
{
public voidHideF()
{
System.Console.WriteLine("Parent.HideF()");
}
}
class Child: Parent
{
public voidHideF()
{
System.Console.WriteLine("Child. HideF()");
}
}
请注意现在子类和父类都拥有了一个完全相同的方法HideF(),于是问题发生了,请看
以下代码:
Childc = new Child();
c.HideF(); //调用父类的还是子类的同名方法?
上述代码运行时,输出:
Child.HideF()
修改一下代码:
Parentp = new Parent();
p.HideF();/ /调用父类的还是子类的同名方法?
上述代码运行结果:
Parent.HideF()
由此可以得出一个结论:
当分别位于父类和子类的两个方法完全一样时,调用哪个方法由对象变量的类型决定。
然而,面向对象的继承特性允许子类对象被当成父类对象使用,这使问题复杂化了,请
读者看以下代码,想想会出现什么结果?
Childc = new Child();
Parentp;
p = c;
p.HideF(); //调用父类的还是子类的同名方法?
上述代码的运行结果是:
Parent.HideF()
这就意味着即使Parent变量p中实际引用的是Child类型的对象,通过p调用的方法还
是Parent类的!
如果确实希望调用的子类的方法,应这样使用:
((Child)p).HideF();
即先进行强制类型转换。
回到前面Parent和Child类的定义,Visual Studio在编译这两个类时,会发出一个警告:
警告 1 “HideExamples.Child.HideF()”隐藏了继承的成员
“HideExamples.Parent.HideF()”。如果是有意隐藏,请使用关键字new。
虽然上述警告并不影响程序运行结果,却告诉我们代码不符合C#的语法规范,修改
Child类的定义如下:
class Child: Parent
{
public newvoid HideF()
{
System.Console.WriteLine("Child.HideF()");
}
}
“new”关键字明确告诉C#编译器,子类隐藏父类的同名方法,提供自己的新版本。
由于子类隐藏了父类的同名方法,所以如果要在子类方法的实现代码中调用父类被隐藏
的同名方法,请使用base关键字,示例代码如下:
base.HideF(); //调用父类被隐藏的方法
(3)重写(Override)与虚方法的调用
在上述的隐藏中,对于父类与子类完全相同的方法使用对象变量来决定调用哪个方法是不合理的,同时也容易出错,如果由对象来决定调用哪个方法更符合C#的面向对象的思想,为了达到这个目的,我们可以在父类同名方法前加关键字Virtual,表示这是一个虚方法,子类对象可以重写这个方法,在子类的同名方法前加关键字Override,表明对父类同名方法进行了重写。
class Parent
{
public virtualvoid OverrideF()
{
System.Console.WriteLine("Parent.OverrideF()");
}
}
class Child: Parent
{
public overridevoid OverrideF()
{
System.Console.WriteLine("Child.OverrideF()");
}
}
请看以下使用代码:
Childc = new Child();
Parent p;
p = c;
p.OverrideF();//调用父类的还是子类的同名方法?
上述代码的运行结果是:
Child.OverrideF()
这一示例表明,将父类方法定义为虚方法,子类重写同名方法之后,通过父类变量调用
此方法,到底是调用父类还是子类的,由父类变量引用的真实对象类型决定,而与父类变量
无关!
换句话说,同样一句代码:
p.OverrideF();
在p引用不同对象时,其运行的结果可能完全不一样!因此,如果我们在编程时只针对
父类变量提供的对外接口编程,就使我们的代码成了“变色龙”,传给它不同的子类对象(这
些子类对象都重写了父类的同名方法),它就干不同的事。
这就是面向对象语言的“虚方法调用(Virtual Method Invoke)”特性。
很明显,“虚方法调用”特性可以让我们写出非常灵活的代码,大大减少由于系统功能
扩充和改变所带来的大量代码修改工作量。
由此给出以下结论:
面向对象语言拥有的“虚方法调用”特性,使我们可以只用同样的一个语句,在运行
时根据对象类型而执行不同的操作。
5、抽象基类与接口
在一个类前面加上“abstract”关键字,此类就成为了抽象类。
对应地,一个方法类前面加上“abstract”关键字,此方法就成为了抽象方法。
abstract class Fruit //抽象类
{
public abstract void GrowInArea(); //抽象方法
}
注意:抽象方法不能有实现代码,在函数名后直接跟一个分号。
抽象类专用于派生出子类,子类必须实现抽象类所声明的抽象方法,否则,子类仍是抽
象类。
从同一抽象类中继承的子类拥有相同的方法(即抽象类所定义的抽象方法),但这些方
法的具体代码每个类都可以不一样,如以下两个类分别代表苹果(Apple)和菠萝(Pineapple):
class Apple: Fruit //苹果
{
public override voidGrowInArea()
{
Console.WriteLine("南方北方都可以种植我。");
}
}
class Pineapple: Fruit //菠萝
{
public override voidGrowInArea()
{
Console.WriteLine("我喜欢温暖,只能在南方看到我。");
}
}
注意上述代码中的override关键字,这说明子类重写了基类的抽象方法。抽象类不能创
建对象,一般用它来引用子类对象。
Fruit f;
f = newApple();
f.GrowInArea();
f = newPineapple();
f.GrowInArea();
运行结果:
南方北方都可以种植我。
我喜欢温暖,只能在南方看到我。
注意同一句代码“f.GrowInArea();”会由于f所引用的对象不同而输出不同的结果。可
以看到,代码运行结果类似于上一节介绍的“虚方法调用”,两者没有本质差别。
可以按照以下公式编写代码:
抽象类 抽象类变量名=new 继承自此抽象类的具体子类名();
一个抽象类中可以包含非抽象的方法和字段。因此:
包含抽象方法的类一定是抽象类,但抽象类中的方法不一定是抽象方法。
5.2 抽象属性
除了方法可以是抽象的之外,属性也可以是抽象的,请看以下代码:
abstract class Parent
{
public abstract StringMessage //抽象属性
{
get;
set;
}
}
class Child: Parent
{
private String_msg;
public override StringMessage
{
get
{
return _msg;
}
set
{
_msg=value;
}
}
}
37
使用代码:
Parent p = newChild();
p.Message = "Hello";
5.1、接口
来看以下这句话:
鸭子是一种鸟,会游泳,同时又是一种食物。
如何在面向对象的程序中表达这种关系?
如果使用C++,可以设计成让鸭子(Duck)类继承自两个父类(鸟Bird和食物Food)。
但在C# 中所有的类都只能有一个父类,此方法不可行。
为了解决这一问题,C#引入了接口(interface)这一概念,并规定“一个类可以实现多
个接口”。
(1)接口的定义与使用
关键字interface用于定义接口:
//定义两个接口
public interfaceISwim
{
void Swim();
}
public interface IFood
{
void Cook();
}
接口可以看成是一种“纯”的抽象类,它的所有方法都是抽象方法。
可以用与继承相同的语法定义一个类实现某些接口:
//定义一个抽象类
public abstract class Bird
{
public abstract voidFly();
}
//继承自一个抽象类,实现两个接口
public class Duck: Bird, IFood, ISwim
{
///实现ISwim接口
public voidSwim()
{
Console.WriteLine("是鸭子就会游泳");
}
//实现IFood接口
public voidCook()
{
Console.WriteLine("鸭子经常被烧烤,北京烤鸭就很有名");
}
//实现抽象类Bird中的抽象方法
public override voidFly()
{
Console.WriteLine("只有野鸭才会飞");
}
}
可以看到,抽象类定义了对象所属的类别,而接口实际上定义了一种对象应具有的行为
特性。
可按以下公式使用接口:
接口类型名 变量名=new 实现了接口的类型名();
示例代码如下:
static voidMain(string[] args)
{
Duckd = new Duck();
//Duck对象d可以使用3种方法:
//1.自身定义的;
//2.父类定义的
//3.接口定义的
d.Fly();
d.Cook();
d.Swim();
//将子类(Duck)对象赋给基类变量
Birdb = d;
//现在只能使用基类定义的Fly()方法
b.Fly();
//将Duck对象赋给ISwin接口变量
ISwims = d;
//现在只能使用接口定义的Swim()方法
s.Swim();
//将Duck对象赋给另一个实现的接口IFood接口变量
IFoodf = d;
//现在只能使用接口定义的Cook()方法
f.Cook();
}
请读者仔细地阅读上述代码的注释,由于Duck类继承自抽象基类Bird,又实现了ISwim
和IFood两个接口,所以,Duck对象拥有这三者所定义的所有方法,并且可以赋值给这三
种类型的变量。
需要注意的是,虽然程序中始终都只有一个Duck对象,但将其赋值给不同类型的变量
后,其可以使用的方法是不一样的。
标签:
原文地址:http://www.cnblogs.com/gt641030/p/4433449.html