本学习主要参考Andrew Troelsen的C#与.NET4高级程序设计,这小节主要述说以下几个东西:
1、构建支持任意数量的构造函数的定义明确的类类型。
2、类和分配对象的基本知识
3、封装的作用
4、定义类属性以及静态成员、对象初始化语法、只读字段、常亮和分部类的作用
C#类类型
.net平台最基本的编程结构就是类类型,正式的说,类是由字段数据(成员变量)以及操作这个数据的成员(构造函数、属性、方法、事件等)所构成的自定义类型。总的来说,其中的字段数据用于表示类实例的‘状态’(对象)。
C#这样基于对象的语言的强大之处就在于,通过将数据和相关功能集合在类定义中,我们就可以仿照现实生活中的实体来设计软件。(面相对象)类定义:
//关键字是class,类名是Car
class Car
{
//Car的状态
public string petName;
public int currSpeed;
//Car的功能
public void PrintState()
{
Console.WriteLine("{0} is going {1} MPH.", petName, currSpeed);
}
public void SpeedUp(int delta)
{
currSpeed += delta;
}
}
说明,虽然上述我们用了两个共有字段表示类的‘状态’,但是类的字段很少定义为公有的,为了保护状态数据完整,我们一般将字段定义为私有,并且通过类型属性对数据提供受控制的访问。
上面定义了成员变量,那么描述其行为的就是成员,例如上述类中的一个SpeedUp()方法和一个PrintState()方法.
在Program类中写入如下代码:
static void Main(string[] args)
{
Console.WriteLine("****Fun with class types****");
Car myCar = new Car();
myCar.petName = "Henry";
myCar.currSpeed = 10;
for (int i = 0; i <= 10; i++)
{
myCar.SpeedUp(5);
myCar.PrintState();
}
Console.ReadLine();
}
当我们运行程序就看到类方法输出的状态数据。
new关键字:对象必须用new关键字来分配到内存中。new关键字主要是为了把引用付给对象。只有new之后才会把引用赋给对象,这个引用才会指向没村中的有效类实例。
构造函数
如果希望在使用对象前就先给对象字段数据赋值,那么就用到了构造函数。构造函数是类的特殊方法,在使用new关键字创建对象时被间接调用。然而和‘普通’方法不同,构造函数永远不会有返回值,并且他的名字总是和需要构造的类的名字相同。
1.默认构造函数的作用:
每个C#类都提供了内建的默认构造函数,需要时可以重新定义。根据定义,默认构造函数不会接受任何参数。
在上类中更新如下方法:
public Car()
{
petName = "GoodGirl";
currSpeed = 10;
}
当我们创建对象之后,直接调用PrintState方法就会打印出来GoodGirl is going 10 MPH.
2.定义自定义的构造函数:
除了默认构造函数,我们还可以自定义不同的构造函数。让构造函数彼此不同的是构造函数参数的个数和类型。当我们定义了具有同样名字但参数数量和类型不同的方法时,就是重载方法。因此,我们自定义构造函数相当于是方法重载。比如除了上述的构造函数,我们还可以构造如下:
public Car(string p)
{
petName = p;
}
3.再谈默认构造函数:
如果自定义了构造函数,那么默认的构造函数就会自动从类中移除,不再有效。因此,如果希望对象用户使用默认构造函数和自定义构造函数创建类型实例,就必须显示重新定义默认构造函数。如下新建一个类所示:
class Motorcycle
{
public int intensity;
public void PopAWheely()
{
for (int i = 0; i <= intensity; i++)
{
Console.WriteLine("aaaa hhhhh!");
}
}
//显示默认构造函数
public Motorcycle() { }
//自定义构造函数
public Motorcycle(int intensity)
{ this.intensity= intensity; }
}
this关键字的作用
C#支持this关键字来提供对当前类实例的访问。this关键字可能的用途就是,解决了当传入参数的名字和类型数据字段的名字相同时产生的作用域歧义。如上例所示,最后一个构造函数的里面,this.intensity指的是类中定义的字段,而给他赋值的那个intensity则是构造函数的参数。
说明:在静态成员的实现中实现this关键字,会产生编译错误。我们知道,静态成员在类(而不是对象)级别产生作用,因此在类级别没有当前对象(也就没有this)。
1.使用this进行串联构造函数调用(简化编程):
this关键字的另一种用法就是使用一项名为构造函数链的技术来设计类,当类定义了多个构造函数时,这个设计模式就会很有用。如果多个构造函数有相同的处理逻辑,但是参数不一样,则很容易产生代码冗余。这时候,this关键字串联就非常有用,我们可以如下构造:
就是让一个接受最多参数个数的构造函数做朱构造函数,并且实现必须的验证逻辑。
//默认构造函数
public Motorcycle() { }
//构造函数链
public Motorcycle(int intensity)
: this(intensity, "") { }
public Motorcycle(int intensity, string name)
{
if (intensity>10)
{
intensity = 10;
}
driverIntensity = intensity;
driverName = name;
}
2.构造函数流程:
我们要知道,在构造函数传递参数给指定的主构造函数(并且构造函数处理了数据)之后,调用者最初调用的构造函数还会执行所有剩余的代码语句。(即走完构造函数中所有流程)。构造函数的逻辑流程如下:
- 通过调用只有单个int的构造函数来创建对象。
- 构造函数将提供的数据转发给主构造函数,并且提供调用者没有提供的其他初始参数。
- 主构造函数把传入的参数赋值给对象的字段数据。
- 控制返回到最初调用的构造函数,并且执行所有剩余的代码语句。
3.再谈可选参数:
之前提过,可选参数允许我们对传入参数提供默认值,如果调用者希望使用这些默认值而不是使用自定义数据,就不必在单独制定这些参数。尽管可选/命名参数灵巧的简化了为给定类定义构造函数集的方式,但是要记住的是这种语法只能在C#2010中编译并且只能在.net4下运行(以及更高版本)。可选参数构造函数诸如下面形式:
public Motorcycle(int intensity = 0, string name = "")
{
//处理逻辑
}
static关键字:
C#类(或结构)可以通过static关键字来定义许多静态成员。如果这样的话,这些成员就只能直接从类级别而不是对象引用调用。比如Console中的WriteLine()方法。
简而言之,静态方法被(类设计者)认为是非常普遍的项,并且不需要再调用成员时创建类型的实例。
1.定义静态方法:
public static string A(){//处理逻辑};
静态成员只能操作静态数据或者调用类的静态方法,如果你常事在静态成员中使用非静态类数据或调用非静态类方法,就会收到编译时错误。
2.定义静态数据:
public static double currentNum=0.04;
不同于非静态数据那样类型每个对象都会维护字段的一个独立副本,静态字段只在内存中分配一次,之后所有的类型的对象操作的将会是同一个值。所以,要记住的是,静态数据字段是所有对象共享的,因此如果你要定义一个所有对象都可以分享的数据点,就可以使用静态成员。
3.定义静态构造函数:
构造函数用于在创建对象时设置对象的数据值,因此,如果在实例级别的构造函数中赋值给静态数据成员,你会惊奇的发现每次新建对象的时候,值都会重置。这种方法看起来违背了我们设计类的原则,但是如果需要在运行时获取静态数据的值的时候,将会非常有用。
简而言之,静态构造函数是特殊的构造函数,并且非常适用于初始化在编译时未知的静态数据的值。静态构造函数特点如下:
- 不能被重载。
- 不允许访问修饰符也不能接受参数。
- 静态构造函数只执行一次,不管创建多少类型的对象。
- 运行库创建类实例或调用者首次访问静态成员之前,运行库会调用静态构造函数。
- 静态构造函数执行先于任何实例级别的构造函数。
4.定义静态类:
如果直接在类级别应用static关键字,就不能使用new关键字来创建,并且只能包含static关键字标记的成员或字段。
说明:只包含静态功能的类或结构通常称为工具类,在设计工具类时,将类定义为静态类是一个非常好的做法。
OOP的支柱
oop支柱,也就是面向对象语言的原则。封装、继承、多态。
1.封装的作用:
封装是将对象用户不必了解的实现细节隐藏起来的一种语言能力。和封装编程逻辑紧密相关的概念是数据保护。理想状态下,对象的状态数据应该使用private关键字来指定。这样的话,外部世界就不能直接改变或获取底层的值,这样可以避免数据点被破坏。
2.继承的作用:
继承是指基于已有类定义来创建心累定义的语言能力。本质上,通过继承,子类可以继承基类(父类)核心的功能,并扩展基类的行为。
在oop中海油另一种形式的代码重用:包含/委托模型(has-a关系)。这种重用的形式不是用来建立父类/子类关系的。它意味着一个类可以定义另一个类的成员变量,并向对象用户间接公开它的功能。
3.多态的作用:
多态表示的是语言以同一种方式处理相关对象的能力。准确的说,这个面向对象语言的原则允许基类为所有的派生类定义一个成员集合(多态接口)。
C#访问修饰符
C#的访问修饰符主要有private、public、protected、internal、protected internal。作用如下表:
而C#成员类型的可修饰及默认修饰符如下表:
private、protected、protected internal访问修饰符可以应用到嵌套类型上。嵌套类型是直接声明在类或结构作用域中的类型。而非嵌套类上只能用public、internal修饰符定义。
C#封装服务(OOP第一个支柱)
封装的核心概念是,对象的内部数据不应该从对象实例直接访问。如果调用者想改变对象的状态,就要使用访问方法(getter)和修改方法(setter)。在C#中,封装是通过private、public、protected、internal关键字在语法级别上体现的。
封装提供了一种保护状态数据完整性的方法,与定义共有字段相比,应该定义更多的私有数据字段,这种字段可以由调用者间接地操作。定义私有字段的主要方式有以下两种(黑盒编程):
- 定义一对传统的访问方法和修改方法。
- 定义一个.net属性。
1.使用传统的访问方法和修改方法执行封装:
即用get方法和set方法。set方法可以改变当前实际状态数据的值。定义如下:
class A
{
private string name;
//get
public string GetName()
{
return name;
}
//set
public void SetName()
{
//处理逻辑
}
}
2.使用.net属性进行封装:
尽管可以使用传统的获取方法和设置方法封装这些字段数据,.net语言还是提倡使用属性来强制数据封装状态数据。
C#属性由属性作用域中定义的get作用域(访问方法)和set作用域(修改方法)构成 ,属性通过返回值指定了它所封装的数据类型。定义如下:其中set中的value标记用来表示调用者设置属性时传入的值。
private int id;
public int ID
{
get{return id;}
set{id = value;}
}
属性的好处主要是,让我们类型易于操作,因为属性可以结合C#内部操作符进行使用。
3.使用类的属性:
属性,特别是属性的set部分,常用语打包类的业务规则。在类中直接使用属性更好一些,将会减少一些重复的检查或者判断。
4.属性的内部表示:
许多程序员往往使用get_和set_前缀来命名传统的访问方法和修改方法。这种命名约定没有问题,但是在底层,C#属性也是使用相同的前缀在CIL代码中我们可以清晰的看到。可以用ildasm.exe打开exe程序观看。
说明,封装字段数据时,.net基础类库总是使用类型属性而不是传统的访问方法和修改方法,因此,如果想创建能与.net平台良好集成的自定义类,就不要定义传统的访问方法和修改方法。以避免和类中定义的方法名字产生雷同而产生编译错误。
5.控制属性的get/set语句的可见性级别:
在一些情况下,为获取和设置逻辑指定唯一的可访问性级别是有益的。为此,我们可以将可访问性关键字作为合适的get或set关键字的前缀:
public string A
{
get{return a;}
protected set{a = value;}
}
6.只读和只写属性:
当封装数据时,可能希望配置一个只读属性,为此,可以忽略set块。如果想要只写属性,则是忽略get块。这样,如果一个只读属性被试图赋值的时候,就会产生编译错误。
7.静态属性:
C#还支持静态属性,静态属性必须在静态数据上操作。
C#喜欢用属性来封装数据,属性的优点是,对象的用户可以只使用一个命名项就能操作内部数据。
自动属性
public int A{get;set;}
而自动属性不允许创建只读和只写属性,如public int A{get;}会产生编译错误。
1.与自动属性交互:
由于编译器在编译时才会定义私有的返回字段,所以定义自动属性的类通常都需要使用属性语法来获取和设置实际的值。属性语法:a.b="aa";
2.关于自动属性和默认值:
你可以直接在代码库中使用封装了数字或布尔数据的自动属性,因为隐藏的返回字段将设置一个可以直接使用的安全默认值。但如果自动属性包装了另一个类变量,隐藏的私有引用类型的默认值也将设置为null。
public int A {get; set;}//隐藏的int返回字段设置为0 public Car B{get;set;}//隐藏的Car返回字段设置为null;
由于私有的返回字段是在编译时创建的,所以你不能使用C#的字段初始化语法用new关键字直接分配引用类型,这项工作必须在类构造函数内部执行,以确保对象以安全的方式诞生。
class Car
{
public int A{get;set;}
public Car()
{
A=1;
}
}
对象初始化器语法
为了简化新建对象的过程,C#提供了对象初始化器语法,语法组成为:大括号内部用逗号分隔的指定值列表:
Car car = new Car(){ petName="abc";}
对象初始化语法只是使用默认构造函数创建类变量并设置各个属性状态数据的语法的简写形式。
常量数据
const int a=100;
C#通过const关键字来定义常量(它在赋初始值后从未变过)。而类的常量字段是隐式静态的。然而,我们可以在类型成员中定义和访问局部常量。
注意,常量在定义时必须为其指定初始值。否则将产生编译错误。
1.只读字段:
public readonly double pi;
public Car()//car的构造函数
{ pi = 3.14}
和常量相似,只读字段不能在赋初始值之后改变。然而,和常量不同的是,赋给只读字段的值可以在运行时决定,因此在构造函数作用域中进行赋值是合法的(其他地方不行)。
2.静态只读字段:
和常量不同,只读字段不是隐式静态的。因此,要从类级别公开pi,就必须显示使用static关键字:在上面添加static关键字,构造函数也需要是static的。这样才能直到运行时才知道静态只读字段的值。
分部类型
利用partial关键字可以创建分部类。使用分部类将构造函数和字段数据转移到全新的类文件中,这样可以避免单个cs文件中代码太多。
分部类的整个理念都是在设计时实现的,编译应用程序后,程序集中只存在唯一的类。定义分部类唯一的要求是类型名称必须相同,并且必须定义在相同的.net命名空间中。如果你用winform或者wpf就会发现许多的代码隐藏文件就利用了这种分部类的方法。
partial class A
{}
小结
这一小节主要介绍了类类型的作用,以及其构造函数和其中的成员以及成员变量的各种表示和定义。除此之外,还接触了封装的细节。封装这块,我们学习了C#的访问修饰符以及类型属性、对象初始化语法以及分部类的作用。下一小节将学习继承和多态来构建以一组相关的类。