面向对象编程是一种很重要的程序设计模型,也是一种广泛应用的编程思想。通俗的讲,面向对象编程认为应用程序是由许多单个对象组成的,对象自身具有很大的灵活性、封装性和扩展性,既能方便开发者管理代码结构,也比较容易将代码模型和程序的业务逻辑紧密结合。
要理解面向对象编程,还得从实战入手,要通过编写代码,不断的练习,才能对面向对象编程有更为直观的理解。
3.1 类
类是从客观事物中进行抽象和总结出来的“蓝图”。例如,自行车是一种类型,它有几个轮子,高度是多少,是否具备变速等功能。定义数据类型是为了更好的组织和存储数据,这些数据是临时的,只存在于内存中,随时可以被清理,变量就是用于存放与某个类型相关的数据。
既然数据类型要存储数据,它内部肯定会包含必要的成员。比如,一个企业内部有多个职能部门(财务部、市场部、人力资源部等),每个部门负责不同的工作,彼此协作,整个企业才能正常运作。因此类的内部也会定义不同的成员这些成员包括:
(1)属性。属性用于描述对象的特征。例如,对一个汽车类来说,可以用产品型号、颜色、最大时速等特点来描述,这些都是汽车的属性。
(2)方法。我们可以把方法比喻为对象的行为。例如,一个表示人的类,他可以在打球,在跑步,在说话,在看电视等。
(3)事件。事件是在特定条件下触发的行为,可以理解为“条件反射”,例如下课铃响了,学生们就知道放学了。再如,一个气球内部充满了气体,然后拿一根针去扎他一下,由于遇到被扎这一事件气球会做出响应——爆炸。
(4)构造器。也叫构造函数,构造方法,它是一种特殊的方法在创建对象实例时调用,用来进行一些初始化工作。
定义类使用class关键字如下面代码所示定义了一个表示图书的Book类。
class Book
{
//类的成员
}
注意关键字和类型名之间要有空格。
3.1.1 字段
字段是在类(或结构)内部定义的一种变量。例如:
struct Point
{
public int x;
public int y;
}
上面的代码定义了一个Point结构,它表示一个平面坐标点其中X和Y两个字段分别表示横坐标和纵坐标的值。再如:
class Students
{
string name;
int age;
string address
}
上面的代码定义了一个表示学生信息的Sstudent类,其中包含三个字段:name表示学生姓名,age表示学生年龄,address表示学生的地址。
3.1.2 属性
属性用于描述类的特征,它可以对字段进行封装,通常属性带有get和set访问器,get访问器用来获取属性的值,而set访问器用来设置属性的值。
再定义一个Student类,不过,把name、agre、address三个字段用属性封装。代码如下:
class Student
{
//姓名
string name;
public string Name
{
get{return this.name;}
set{this.name=value;}
}
//年龄
int age;
public int Age
{
get{return this.age;}
set{this.age=value;}
}
//住址
string address;
public string Address
{
get{return this.address;}
set{this.address=value;}
}
}
以Name属性为例,当获取属性的值时,通过get访问器将name字段的值直接返回;修改name属性的值,通过set访问器把新值传递给Value关键字然后再把value赋值给name字段。另外两个属性情况类似。如果希望让属性只读,即只能获取其值而不允许对其进行赋值。
通过以上分析,可以发现,字段是真正储存数据值的变量,而属性只是一个对外公开的“窗口”,数据通过属性来进行传递。当获取属性的值时,可以通过return关键字 直接把字段中存放的值返回。当要设置属性的值时,调用set访问器把外部传进来的数据存放到value中,再以value作为纽带把数据赋给字段。
上述的事例几乎不足以说明为什么要使用属性,把上面的Student类进行如下修改:
class Student
{
//姓名
string name;
public string Name
{
get{return this.name;}
set{
if(value=="")
{
throw new ArgumentException("姓名不能为空字符串");
}
this.name=value;
}
}
//年龄
int age;
public int Age
{
get{return this.age;}
set{
if(value<1||value>100)
{
throw new ArgumentException("年龄超出了有效范围");
}
this.age=value;
this.age=value;}
}
//住址
string address;
public string Address
{
get{return this.address;}
set{this.address=value;}
}
}
经过修改后,Name属性不接受空白字符串Age属性不接受小于1或大于100的整数,如果设置的属性值不符合要求,就会抛出异常,即发生错误。因此上面代码充分展示属性封装的好处无论是回去还是设置属性的值代码都可以事先做出相应的验证和处理避免属性被设置为意外的值。如果直接把字段暴露给外部的调用代码,则字段很有可能被赋了不满足要求的值,严重时可能破坏整个类的数据结构。
如果属性值不需要特殊验证处理,可以使用简化的属性声明语法。例如:
public string name{get;set;}
在编译时会自动生成存储属性值的字段。由于这种简练语法省去了封装私有字段的过程,若希望在声明属性时设置默认值,可以在属性声明后面直接赋值。例如:
public int MyValue{get;set;}=700;
对于只读属性只需要在声明时直接忽略set语句即可, 例如:
public string ProductNo{get;}
对于只读属性还可以使用类似Lambda表达式的形式来声明。例如:
public int MaxTaskNum=>500;
MaxTaskNum属性是只读属性,返回整数值500。当使用“=>”操作符来声明只读属性时,不需要写get语句,也不需要return关键字,“=>”后面直接写上要返回的值即可。
3.1.3 方法
方法可以认为是类的行为,通常指的是一个动作。请考虑下面代码:
class Music
{
public string Title{get;set}
public int Year{get;set}
public void play()
{
//方法体内容
}
}
上面的代码使用了属性的快速定义方式。
代码定义了一个Music类,表示一段音乐的基本信息Title和Year是属性,用于描述音乐的特征(标题、发行年份),而Play是方法,因为播放音乐是一种行为,void表示方法不带有返回值。
如果希望方法返回处理结果,可以定义带返回值的方法,例如:
int ReturnIntInt()
{
return 100;
}
ReturnInt方法调用后会返回一个整数100。有时候需要提供一些数据给方法内部的代码进行处理,这样一来,方法不仅需要返回值,而且还得用上参数。例如下面的Add方法。
int Add(int a,int b)
{
return a+b;
}
Add 方法的功能是计算两个整数的和,所以他不但要返回计算结果,还需要提供两个参数a和b,以便代码在调用时可以传递用来进行加法运算的两个操作树。例如可以这样调用,Add(2,3),方法执行完成后返回5。
在调用方法时,最常用的方法是依据参数定义的类型和顺序来传递,如上面的Add(2,3),2就传给参数a,3就传给参数b。那么如果不想按照参数的声明顺序来传递,又如何处理呢?
方法也很简单,在调用方法时写上参数的名字就可以了,例如:
Add(b:5,a:3)
写上参数的名字,后跟一个英文的冒号,然后再写上要传递的值,如上面的代码,传递给a的值是3,而传递给b的参数是5。
可以在方法中定义可选参数,即在调用方法时,可以忽略的参数。正因为如此,可选参数要赋默认值,如下所示:
void DoWork(string p1,string p2="abc")
{
//方法内容
}
在这个方法中,p1是必选参数,p2由于已赋了默认值,就成了可选参数,DoWork方法可以这样调用:
DoWOrk("123");
因为P2是可选参数,所以以上调用是允许的。但是如果把DoWork方法改为以下形式就会出错:
static void DoWork(string p1="abc",string p2)
{
//方法内容
}
此时,P1就成了可选参数,
跟前面的属性的声明相似方法也可以用Lambda表达式的形式来声明。例如:
Public string PickName()=>"Jack";
PickName方法没有参数,返回一个字符串实例。
同样,带参数的方法也可以用Lambda表达式来声明。把上面的Add方法修改为:
public int Add(int a,int b)=>a+b;
在"=>”操作符右边可以省略Return关键字,直接写上a+b 运算结果。
3.1.4 构造函数与析构函数
构造函数是在类被实例化的时候(即创建类的实例对象的时候)调用,它也是类的成员,具有以下特点:
(1)构造函数的名称必须与类名相同,
(2)构造函数没有返回值。
(3)默认构造函数没有参数但也可以定义参数。
即使开发人员不为类编写构造函数,它默认就有一个不带参数的构造函数,