《C#中的类与对象》由会员分享,可在线阅读,更多相关《C#中的类与对象(69页珍藏版)》请在金锄头文库上搜索。
1、 第4章 类与对象 4.1类与对象 对象(对象(ObjectObject)是面向对象程序设计的核心。世界上的一)是面向对象程序设计的核心。世界上的一切人、事、物都是对象。因此可以说面向对象的程序设计,实切人、事、物都是对象。因此可以说面向对象的程序设计,实质上是用计算机的语言描绘世界。质上是用计算机的语言描绘世界。对象具有静态的属性和动态的行为。静态的属性用来描述对象对象具有静态的属性和动态的行为。静态的属性用来描述对象的特征,如汽车可以用型号、价格、颜色等属性来描述。动态的特征,如汽车可以用型号、价格、颜色等属性来描述。动态的行为则表示对象可执行的动作,如汽车的行为可以有加速、的行为则表示对
2、象可执行的动作,如汽车的行为可以有加速、转弯、后退等。在计算机中,属性用数据描述,而行为则用方转弯、后退等。在计算机中,属性用数据描述,而行为则用方法表示。法表示。 类类(Class)(Class)是一组具有相同属性和行为的对象的抽象,而对是一组具有相同属性和行为的对象的抽象,而对象则是类的实例。在象则是类的实例。在C# C# 中类实际上是用户自定义的一种数据中类实际上是用户自定义的一种数据类型,它定义了该数据类型的数据和行为。类型,它定义了该数据类型的数据和行为。4.1.1 类的定义格式:格式: 类的访问修饰符类的访问修饰符class class 类名类名: :基类类名基类类名 类的成员;类
3、的成员; 说明:说明: 类名要遵循标识符命名规则,一般而言,组成类类名要遵循标识符命名规则,一般而言,组成类名的单词的首字母要大写。名的单词的首字母要大写。 默认情况下,类声明为内部的。内部类意味着默认情况下,类声明为内部的。内部类意味着只有当前项目中的代码才能访问该类。我们可以使只有当前项目中的代码才能访问该类。我们可以使用用internalinternal这一访问修饰符关键字来显式指定。这一访问修饰符关键字来显式指定。 类的访问修饰符除了类的访问修饰符除了internalinternal外,还可以为外,还可以为publicpublic。publicpublic类称为公共类,不仅当前项目中的
4、代类称为公共类,不仅当前项目中的代码可以访问该类,其他项目中的代码也可以访问该码可以访问该类,其他项目中的代码也可以访问该类。类。 除了除了internalinternal和和publicpublic这两个访问修饰符关键字这两个访问修饰符关键字外,还可以为外,还可以为abstract(abstract(抽象类,不能实例化,只能抽象类,不能实例化,只能继承继承) )和和sealed(sealed(密封类,不能继承密封类,不能继承) )。关于这两种类。关于这两种类型的类我们在后续章节中介绍。型的类我们在后续章节中介绍。 类成员的定义格式如下:类成员的定义格式如下: 访问修饰符访问修饰符 数据成员数
5、据成员; ; 访问修饰符访问修饰符 成员方法成员方法; ; 数据成员用来描述每个对象所拥有的属性,成员方法则用数据成员用来描述每个对象所拥有的属性,成员方法则用来描述每个对象的行为。类成员的访问修饰符可以用以下关来描述每个对象的行为。类成员的访问修饰符可以用以下关键字之一来定义,键字之一来定义, 1.public1.public:该类成员可以由任何代码访问。:该类成员可以由任何代码访问。 2.private2.private:该类成员只能由类中的代码访问,如果不加以:该类成员只能由类中的代码访问,如果不加以指明,则默认取值为指明,则默认取值为privateprivate。 3.internal
6、3.internal:该类成员只能由定义它的项目内部的代码访问。:该类成员只能由定义它的项目内部的代码访问。 4.protected4.protected:该类成员只能由该类或派生类中的代码访问。:该类成员只能由该类或派生类中的代码访问。类成员的访问修饰符的详细介绍参见类成员的访问修饰符的详细介绍参见4.5.14.5.1节。节。 例例4-1 4-1 创建一个汽车类创建一个汽车类public class Carpublic class Car / /数据成员,即对象的静态特征或状态数据成员,即对象的静态特征或状态public string color;public string color; p
7、ublic string size; public string size; public string brand; public string brand; / /方法成员,即对象的动态特征或行为方法成员,即对象的动态特征或行为 public void beep()public void beep() 4.1.2 对象的创建 类是一个静态概念,要想使用类,需要对类进行实例类是一个静态概念,要想使用类,需要对类进行实例化,即创建对象。化,即创建对象。格式:格式:类名对象名类名对象名new new 构造函数;构造函数;/new/new关键字用来创建对象。关键字用来创建对象。 说明:说明: 1.
8、 new1. new关键字用来创建对象,为对象在内存中分配空间,关键字用来创建对象,为对象在内存中分配空间,创建对象的实体。创建对象的实体。 2. 2. 构造函数名与类名相同,关于构造函数的介绍将在后构造函数名与类名相同,关于构造函数的介绍将在后续章节进行,现在我们只需用类名后加上圆括号来代替。续章节进行,现在我们只需用类名后加上圆括号来代替。如:如:Car car1=new Car();Car car1=new Car();4.1.3 对象成员的访问创建对象后,就可以访问对象成员和运行对象中的方法。创建对象后,就可以访问对象成员和运行对象中的方法。格式:格式:对象名对象名. .成员名成员名比
9、如:比如:car1.color=”yellow”;car1.color=”yellow”;car1.size=”1.4”;car1.size=”1.4”;car1.brand=”NISSAN”;car1.brand=”NISSAN”;car1.beep();car1.beep(); 例例4-2 4-2 创建一个创建一个EmployeeEmployee类,创建该类的一个对象类,创建该类的一个对象e1e1,访问访问e1e1的成员的成员using System;using System;using using System.Collections.GenericSystem.Collections.
10、Generic; ;using using System.TextSystem.Text; ;namespace namespace LearnCsharpLearnCsharp class Employee class Employee string string mstrEmpIDmstrEmpID; /; /员工代号员工代号 string string mstrEmpNamemstrEmpName; ;/员工姓名员工姓名 public void public void SetEmpID(stringSetEmpID(string EmpIDEmpID) /) /设置员工代号设置员工代号
11、mstrEmpIDmstrEmpID=EmpIDEmpID; ; public string public string GetEmpIDGetEmpID() /() /取得员工代号取得员工代号 return return mstrEmpIDmstrEmpID; ; public void public void SetEmpName(stringSetEmpName(string EmpNameEmpName) /) /设置员工名称设置员工名称 mstrEmpNamemstrEmpName=EmpNameEmpName; ; public string public string GetEm
12、pNameGetEmpName() /() /取得员工名称取得员工名称 return return mstrEmpNamemstrEmpName; ; static void Main() static void Main() Employee Employeee1=new Employee();e1=new Employee(); e1.SetEmpID(001); e1.SetEmpID(001); e1.SetEmpName( e1.SetEmpName(王江王江);); Console . Console .WriteLineWriteLine(员工:代号员工:代号0,0,姓名姓名1“
13、);1“); e1.GetEmpID(),e1.GetEmpName(); e1.GetEmpID(),e1.GetEmpName(); 程序的运行结果如图4-1。图4-1 程序运行结果Main()函数虽然作为该类的方法成员,但它仍然是C#程序的入口点。 4.2 构造函数与析构函数 构造函数和析构函数是两个特殊的函数。构造构造函数和析构函数是两个特殊的函数。构造函数用来初始化对象,因此当类实例化时首先执行的函数用来初始化对象,因此当类实例化时首先执行的函数就是构造函数。构造函数根据有无参数的特点,函数就是构造函数。构造函数根据有无参数的特点,可以分为默认构造函数和带参数的构造函数。析构函可以分
14、为默认构造函数和带参数的构造函数。析构函数是销毁对象时执行的函数。这两个函数的执行不需数是销毁对象时执行的函数。这两个函数的执行不需要人工干预,系统会自动地根据情况来调用相应的函要人工干预,系统会自动地根据情况来调用相应的函数。数。4.2.1 默认构造函数 不带参数的构造函数称为默认构造函数。默认构造函数不带参数的构造函数称为默认构造函数。默认构造函数可以是由编译系统提供的,也可以是程序员手工添加的。不可以是由编译系统提供的,也可以是程序员手工添加的。不论是哪种情况,默认构造函数都具有以下共同的特点:论是哪种情况,默认构造函数都具有以下共同的特点: 1.1.函数名与类名相同;函数名与类名相同;
15、 2.2.不能有返回值,不能有返回值,voidvoid类型也不可以;类型也不可以; 3.3.没有任何参数;没有任何参数; 实例化类时,如果在类中没有显式定义构造函数,则会调实例化类时,如果在类中没有显式定义构造函数,则会调用编译系统自动提供的默认构造函数。该构造函数会初始化用编译系统自动提供的默认构造函数。该构造函数会初始化类成员,数值类型的成员初始化为类成员,数值类型的成员初始化为0 0,boolbool类型的成员初始化类型的成员初始化为为falsefalse,引用类型的成员初始化为,引用类型的成员初始化为nullnull。通常而言,默认构。通常而言,默认构造函数由编译系统提供。但我们可以根
16、据需要自定义默认构造函数由编译系统提供。但我们可以根据需要自定义默认构造函数,初始化类成员。造函数,初始化类成员。 例例4-3 4-3 默认构造函数的使用默认构造函数的使用using System;using System;using using System.Collections.GenericSystem.Collections.Generic; ;using using System.TextSystem.Text; ;namespace ConsoleApplication1namespace ConsoleApplication1 class Time class Time pub
17、lic public intint Hour; Hour; public public intint Minute; Minute; public public intint Second; Second; public void public void DisplayTimeDisplayTime()() Console.WriteLineConsole.WriteLine( now time is 0:1:2,Hour,Minute,Second); ( now time is 0:1:2,Hour,Minute,Second); / /默认构造函数默认构造函数 public Time()
18、public Time() Hour=12;Hour=12; Minute=30;Minute=30; Second=25;Second=25; class Testclass Test public static void Main() public static void Main() Time t=new Time(); Time t=new Time(); t.DisplayTimet.DisplayTime();(); 程序运行结果如图程序运行结果如图4-24-2所示。所示。图4-2 程序运行结果在上述程序中,自定义了一个默认构造函数Time(),该构造函数将Time类的Hour成员
19、初始化为12,将Minute成员初始化为30,将Second成员初始化为25。这样,在初始化对象t时,调用自定义的默认构造函数,t.Hour=12,t.Minute=30,t.Second=25。 4.2.2 带参数的构造函数 带参数的构造函数具有以下特点:带参数的构造函数具有以下特点:1.1.函数名与类名相同;函数名与类名相同;2.2.不能有返回值,不能有返回值,voidvoid类型也不可以;类型也不可以;3.3.具有一个或多个参数;具有一个或多个参数; 同一个类中可以有一个或多个带参数的构造函同一个类中可以有一个或多个带参数的构造函数,在初始化对象时可以根据需要选择不同参数的数,在初始化对
20、象时可以根据需要选择不同参数的构造函数来实例化类。构造函数来实例化类。 例例4-4 4-4 带参数构造函数的使用带参数构造函数的使用using System;using System;using using System.Collections.GenericSystem.Collections.Generic; ;using using System.TextSystem.Text; ;namespace ConsoleApplication1namespace ConsoleApplication1 public class Employeepublic class Employee st
21、ring ID; / string ID; /员工代号员工代号 string string empNameempName; ;/员工姓名员工姓名 double double empSalaryempSalary; /; /员工工资员工工资 public Employee()public Employee()/默认构造函数默认构造函数 ID = 9999; ID = 9999; empNameempName = = 李红李红; empSalaryempSalary=5000;=5000; public public Employee(stringEmployee(string EmpIDEmpI
22、D, double Salary) /, double Salary) /带两个参数的构造函数带两个参数的构造函数 ID = ID = EmpIDEmpID; ; empSalaryempSalary = Salary; = Salary; public public Employee(stringEmployee(string EmpIDEmpID, string , string EmpName,doubleEmpName,double Salary) /Salary) /带三个参数的构造函数带三个参数的构造函数 ID = ID = EmpIDEmpID; ; empNameempName
23、=EmpNameEmpName; ; empSalaryempSalary = Salary; = Salary; public string public string GetEmpIDGetEmpID()() return ID; return ID; public double public double GetSalaryGetSalary()() return return empSalaryempSalary; ; class Programclass Program static void Main() static void Main() Employee e1 = new E
24、mployee(001, 5000);/ Employee e1 = new Employee(001, 5000);/调用带调用带两个参数的构造函数两个参数的构造函数 Employee e2 = new Employee(001, Employee e2 = new Employee(001, 王华王华 ,5000);/ ,5000);/调用带三个参数的构造函数调用带三个参数的构造函数 Employee e3 = new Employee(); /Employee e3 = new Employee(); /调用默认构造函数调用默认构造函数 Console.WriteLineConsole.
25、WriteLine(员工:代号员工:代号0,0,工资工资1, 1, e1.GetEmpID(), e1.GetSalary();e1.GetEmpID(), e1.GetSalary(); Console.WriteLineConsole.WriteLine(员工:代号员工:代号0,0,工资工资1, 1, e2.GetEmpID(), e2.GetSalary();e2.GetEmpID(), e2.GetSalary(); Console.WriteLineConsole.WriteLine(员工:代号员工:代号0,0,工资工资1, 1, e3.GetEmpID(), e3.GetSalar
26、y();e3.GetEmpID(), e3.GetSalary(); 程序运行结果如图程序运行结果如图4-34-3所示。所示。图4-3 程序运行结果以上程序包含两个带参数的构造函数和一个自定义的默认构造函数。生成对象时,编译器会根据传入的参数个数、类型,自动调用合适构造函数。注意:构造函数的访问修饰符一般为public。4.2.3 析构函数 与构造函数相对应,析构函数用于清理对象。与构造函数相对应,析构函数用于清理对象。析构函数具有以下特点:析构函数具有以下特点:1.1.析构函数名是由类名和波浪号组成,即析构函数名是由类名和波浪号组成,即 类名;类名;2.2.不能由任何返回值,不能由任何返回值
27、,voidvoid类型也不可以;类型也不可以;3.3.不能有任何的访问修饰符;不能有任何的访问修饰符;4.4.最多只能有一个析构函数;最多只能有一个析构函数;C#C#中析构函数是在适当的时候被资源回收器调用的,中析构函数是在适当的时候被资源回收器调用的,不能保证一定会被调用,也不能保证其调用顺序,不能保证一定会被调用,也不能保证其调用顺序,析构函数也不能被外部程序显式调用。虽然在析构析构函数也不能被外部程序显式调用。虽然在析构函数中一般可以释放对象实例占用的重要资源,如函数中一般可以释放对象实例占用的重要资源,如数据库连接等,但由于析构函数的调用是随机的,数据库连接等,但由于析构函数的调用是随
28、机的,没有任何保证,所以这些释放重要资源的工作最好没有任何保证,所以这些释放重要资源的工作最好是存放在一个独立的成员函数中,由程序显式调用。是存放在一个独立的成员函数中,由程序显式调用。4.3 this关键字 thisthis关键字用来代表对象自身。关键字用来代表对象自身。thisthis关键字可以用来解决名关键字可以用来解决名称相同的问题。称相同的问题。 为为4.2.24.2.2节中的类节中的类EmployeeEmployee添加一个带参数的构造函数,添加一个带参数的构造函数,如下所示。如下所示。public public Employee(stringEmployee(string emp
29、NameempName, string ID), string ID) this.empNamethis.empName= =empNameempName; ; this. ID= this. ID=IDID; ; 该构造函数在初始化类的成员变量该构造函数在初始化类的成员变量empNameempName和和IDID时,参时,参数的名与数据成员的名正好相同,通过使用数的名与数据成员的名正好相同,通过使用thisthis关键字来区分关键字来区分哪个是参数,哪个是数据成员。此时哪个是参数,哪个是数据成员。此时thisthis关键字指向当前的对关键字指向当前的对象,所以带象,所以带thisthis的是
30、类的数据成员,而不带的是类的数据成员,而不带this this 的是参数。的是参数。 例例4-54-5使用使用thisthis关键字调用类的其它构造函数关键字调用类的其它构造函数using System;using System;using using System.Collections.GenericSystem.Collections.Generic; ;using using System.TextSystem.Text; ;namespace ConsoleApplication1namespace ConsoleApplication1 class Employee class E
31、mployee string ID; / string ID; /员工代号员工代号 double double empSalaryempSalary; /; /员工工资员工工资 public Employee():this(001,4500.5)public Employee():this(001,4500.5)/默认构造函默认构造函数数 public public Employee(stringEmployee(string ID, double ID, double empSalaryempSalary) /) /带两个带两个参数的构造函数参数的构造函数 this.IDthis.ID =
32、ID; = ID; this.empSalarythis.empSalary = = empSalaryempSalary; ; public string public string GetEmpIDGetEmpID()() return ID; return ID; public double public double GetSalaryGetSalary()() return return empSalaryempSalary; ; class Testclass Test static void Main() static void Main() EmployeeEmployeee1
33、=new Employee( );e1=new Employee( ); Console.WriteLineConsole.WriteLine(员工:代号员工:代号0,0,工资工资1,e1.GetEmpID(),e1.GetSalary();1,e1.GetEmpID(),e1.GetSalary(); 程序运行结果如图程序运行结果如图4-44-4所示。所示。图4-4 程序运行结果在上述程序中,用户自定义的默认构造函数中没有编写额外的代码,直接用this关键字来调用带参数的构造函数。如果有多个带参数的构造函数时,编译器如何判断要调用哪一个构造函数呢?此时编译器就根据传入参数的个数和类型来确定。
34、由于this后传入两个参数,一个为字符串,另一个为double类型,编译器会依此找到符合此条件的构造函数。 如果有多个带参数的构造函数时,编译器如何如果有多个带参数的构造函数时,编译器如何判断要调用哪一个构造函数呢?此时编译器就根据判断要调用哪一个构造函数呢?此时编译器就根据传入参数的个数和类型来确定。由于传入参数的个数和类型来确定。由于thisthis后传入两个后传入两个参数,一个为字符串,另一个为参数,一个为字符串,另一个为doubledouble类型,编译类型,编译器会依此找到符合此条件的构造函数。器会依此找到符合此条件的构造函数。 例例4-6 4-6 使用使用thisthis关键字来返
35、回当前对象的引用关键字来返回当前对象的引用using System;using System;using using System.Collections.GenericSystem.Collections.Generic; ;using using System.TextSystem.Text; ;namespace ConsoleApplication1namespace ConsoleApplication1 class Employee class Employee string ID; / string ID; /员工代号员工代号 string string empNameempNa
36、me; ; /员工姓名员工姓名 public Employee public Employee SetEmpName(stringSetEmpName(string name) name) this.empNamethis.empName = name; = name; return this; return this; public Employee public Employee SetEmpID(stringSetEmpID(string id) id) this. ID = this. ID = idid; ; return this; return this; /此时,你可以使用下面
37、的语法设置员工的代号与名称:此时,你可以使用下面的语法设置员工的代号与名称:class Testclass Test static void Main()static void Main() EmployeeEmployeee1=new Employee( );e1=new Employee( ); e1.SetEmpID(001).SetEmpName( e1.SetEmpID(001).SetEmpName(张木张木);); 4.4 属性 为了访问类的私有数据成员,通常需要提供一些公共的为了访问类的私有数据成员,通常需要提供一些公共的访问数据成员的方法,其他类要访问该类的私有数据成员,访问
38、数据成员的方法,其他类要访问该类的私有数据成员,必须通过调用该类的公共访问数据成员的方法来访问。必须通过调用该类的公共访问数据成员的方法来访问。语法:语法: 访问修饰符访问修饰符 数据类型属性名数据类型属性名 getget / /获取属性值获取属性值 setset / /设置属性值设置属性值 说明: 1.1.属性拥有两个类似于函数的块,属性拥有两个类似于函数的块,getget块用于获块用于获取属性的值,取属性的值,setset块用于设置属性的值。这两个块也块用于设置属性的值。这两个块也称为访问器,分别用称为访问器,分别用getget和和setset关键字来定义。可以忽关键字来定义。可以忽略其中
39、的一个块来创建只读或只写属性,忽略略其中的一个块来创建只读或只写属性,忽略getget块块创建只写属性,忽略创建只写属性,忽略setset块创建只读属性。块创建只读属性。 2.2.访问器可包含访问修饰符,一般应该将属性定访问器可包含访问修饰符,一般应该将属性定义成义成publicpublic的。的。 3.3.属性至少要包含一个块才是有效的。属性至少要包含一个块才是有效的。 4.4.属性名称一般要求单词首字母大写。属性名称一般要求单词首字母大写。4.4.1 Get块 GetGet块必须有一个与属性类型相同的返回值,简单的属块必须有一个与属性类型相同的返回值,简单的属性一般与一个私有数据成员相关联
40、,以控制对这个数据成员性一般与一个私有数据成员相关联,以控制对这个数据成员的访问,此时的访问,此时getget块可以直接返回该数据成员的值。块可以直接返回该数据成员的值。private private intint x; x; public public intint X X getreturngetreturn x; x; 属性属性X X只定义只定义getget块,所以该属性为只读属性。块,所以该属性为只读属性。4.4.2 Set块 SetSet块把一个值赋给数据成员,该值(即用户提块把一个值赋给数据成员,该值(即用户提供的值)使用关键字供的值)使用关键字valuevalue引用。引用。pr
41、ivate private intint x; x; public public intint X X setxsetx=value;=value; 属性属性X X只定义只定义setset块,所以该属性为只写属性。在属块,所以该属性为只写属性。在属性性X X的的setset块中,把数据成员的初始值通过关键字块中,把数据成员的初始值通过关键字valuevalue传递给私有的数据成员传递给私有的数据成员x x。例例4-7 4-7 属性的简单应用属性的简单应用using System;using System;using using System.Collections.GenericSystem.
42、Collections.Generic; ;using using System.TextSystem.Text; ;namespace ConsoleApplication1namespace ConsoleApplication1 class class TimePeriodTimePeriod private double seconds; private double seconds; public double Hours public double Hours get return seconds / 3600; get return seconds / 3600; set sec
43、onds = value * 3600; set seconds = value * 3600; class Programclass Program static void Main() static void Main() TimePeriodTimePeriod t = new t = new TimePeriodTimePeriod();(); t.Hourst.Hours = 24; = 24; System.Console.WriteLine(TimeSystem.Console.WriteLine(Time in hours: + in hours: + t.Hourst.Hou
44、rs);); 程序运行结果如图程序运行结果如图4-54-5所示。所示。图4-5 程序运行结果 这段程序定义了一个这段程序定义了一个TimePeriodTimePeriod类,该类具有一个属性类,该类具有一个属性Hours, Hours, 使用使用getget访问器获取数据成员访问器获取数据成员secondsseconds转换成的转换成的小时数,而使用小时数,而使用setset访问器通过访问器通过valuevalue关键字将小时转换成关键字将小时转换成秒赋值给数据成员秒赋值给数据成员secondsseconds。在。在MainMain函数中,语句函数中,语句t.Hourst.Hours=24=2
45、4对属性对属性HoursHours进行赋值操作,此时将调用进行赋值操作,此时将调用HoursHours属性的属性的setset访问器,关键字访问器,关键字 valuevalue代表的值就是用代表的值就是用户提供的值户提供的值2424,所以私有数据成员,所以私有数据成员secondsseconds的值为的值为24*360024*3600;语句;语句System.Console.WriteLine(“TimeSystem.Console.WriteLine(“Time in in Hours:”+t.HoursHours:”+t.Hours););访问属性访问属性HoursHours,此时将调用,
46、此时将调用HoursHours属性的属性的getget访问器,返回结果是私有数据成员访问器,返回结果是私有数据成员secondsseconds的的值所对应的小时数。值所对应的小时数。 使用属性还可以防止外部非法数据的入侵。比使用属性还可以防止外部非法数据的入侵。比如月份的取值应该在如月份的取值应该在1 11212之间,为了防止不合法之间,为了防止不合法数据的入侵,可以定义数据的入侵,可以定义MonthMonth属性来实现属性来实现 4.5方法 在面向对象的程序设计中,方法用于执行对象在面向对象的程序设计中,方法用于执行对象的各种操作,即对象的行为特征,方法是类的一个的各种操作,即对象的行为特征
47、,方法是类的一个重要成员。重要成员。格式:格式: 访问修饰符访问修饰符 方法名方法名( (参数列表参数列表) ) / /方法体方法体 4.5.1 访问修饰符 你的家人进入你的卧室,但是不能查看你的日你的家人进入你的卧室,但是不能查看你的日记本,你的邻居可以进入你的客厅,但是不能进入记本,你的邻居可以进入你的客厅,但是不能进入你的卧室,陌生人没有经过你的同意甚至不能进入你的卧室,陌生人没有经过你的同意甚至不能进入你的客厅。访问修饰符的概念与此非常相似,用来你的客厅。访问修饰符的概念与此非常相似,用来确定对类或成员的访问权限。确定对类或成员的访问权限。1.public public public成
48、员称为公共成员,公共成员访问级别最成员称为公共成员,公共成员访问级别最高,对公共成员的访问没有任何限制。高,对公共成员的访问没有任何限制。 例例4-8 public4-8 public成员的使用成员的使用在类在类MainClassMainClass中可以访问类中可以访问类PointPoint中的公共成员中的公共成员x x和和y y。因为因为x x和和y y是公共成员,所以访问它们不受任何限制。是公共成员,所以访问它们不受任何限制。class Pointclass Point public int x; public int x; public int y; public int y; clas
49、s class MainClassMainClass static void Main() static void Main() Point p = new Point(); Point p = new Point(); p.xp.x = 10; = 10; p.yp.y = 15; = 15; Console.WriteLine(x = 0, y = 1, p.x, p.y); Console.WriteLine(x = 0, y = 1, p.x, p.y); 2.private privateprivate成员称为私有成员,私有成员的访问级别成员称为私有成员,私有成员的访问级别最低,只能
50、在自身所属的类中才能被访问。最低,只能在自身所属的类中才能被访问。例例4-9 private4-9 private成员的使用成员的使用class Employeeclass Employee private string name = private string name = FirstNameFirstName, , LastNameLastName; private double salary = 100.0; private double salary = 100.0; public string public string GetNameGetName()() return name
51、; return name; public double Salary public double Salary get return salary; get return salary; class class MainClassMainClass static void Main() static void Main() Employee e = new Employee(); Employee e = new Employee(); string n = string n = e.namee.name;/;/错误的语句,错误的语句,namename是私有成是私有成员员 double s
52、= double s = e.salarye.salary;/;/错误的语句,错误的语句,salarysalary是私有是私有成员成员 string n = string n = e.GetNamee.GetName();/();/正确语句,正确语句,GetNameGetName是公共成员是公共成员 double s = double s = e.Salarye.Salary;/;/正确语句,正确语句,SalarySalary是公共成是公共成员员 3.protected protectedprotected成员称为受保护成员,受保护成员只成员称为受保护成员,受保护成员只能被自身所在的类或自身所
53、在类的派生类中的代码访能被自身所在的类或自身所在类的派生类中的代码访问。问。 例例4-10 protected4-10 protected成员的使用成员的使用关于基类和派生类的概念在关于基类和派生类的概念在“ “继承继承” ”一节中做详细介一节中做详细介绍。绍。class Point class Point protected protected intint x; x; protected protected intint y; y; class class DerivedPointDerivedPoint: Point /: Point /DerivedPointDerivedPoint类
54、是类是PointPoint的派生类的派生类 static void Main() static void Main() DerivedPointDerivedPoint dpdp = new = new DerivedPointDerivedPoint();(); dp.xdp.x = 10; / = 10; /正确,正确,DerivedPointDerivedPoint是类是类PointPoint的派生类,可以直的派生类,可以直接访问基类的受保护成员接访问基类的受保护成员dp.ydp.y = 15; / = 15; /正确,正确,DerivedPointDerivedPoint是类是类Poi
55、ntPoint的派生类,可以直接访问的派生类,可以直接访问基类的受保护成员基类的受保护成员 Console.WriteLine(xConsole.WriteLine(x = 0, y = 1, = 0, y = 1, dp.xdp.x, , dp.ydp.y); ); 4.internal internalinternal成员称为内部成员,内部成员只有在同一成员称为内部成员,内部成员只有在同一程序集的文件中才是可访问的。程序集的文件中才是可访问的。例例4-11 internal4-11 internal成员的使用成员的使用/ Assembly1.cs/ Assembly1.cs/ compil
56、e with: / compile with: /target:librarytarget:libraryinternal class internal class BaseClassBaseClass public static public static intint intMintM = 0; = 0; / Assembly2.cs/ Assembly2.cs/ compile with: /reference:Assembly1.dll/ compile with: /reference:Assembly1.dllclass class TestAccessTestAccess sta
57、tic void Main() static void Main() BaseClassBaseClass myBasemyBase = new = new BaseClassBaseClass(); / Error CS0122(); / Error CS0122 该示例包含两个文件:Assembly1.cs 和 Assembly2.cs。第一个文件包含内部基类 BaseClass。在第二个文件中,实例化 BaseClass 的尝试将产生错误。4.5.2 参数的分类 C#C#中方法的参数有四种类型:值参数、引用参数、输中方法的参数有四种类型:值参数、引用参数、输出参数和数组型参数。出参数和数
58、组型参数。1.值参数 值参数不含任何的修饰符。形参是实参的一份拷贝,值参数不含任何的修饰符。形参是实参的一份拷贝,方法中对形参的值的修改不会影响到实参的值。方法中对形参的值的修改不会影响到实参的值。例例4-12 4-12 值参数的运用值参数的运用using using System.TextSystem.Text; ;using System;using System;using using System.Collections.GenericSystem.Collections.Generic; ;namespace ConsoleApplication1namespace ConsoleA
59、pplication1class Testclass Test public static void public static void MyMethod(intMyMethod(int a) a) a +;a +; public static void Main()public static void Main() intint p=5; p=5; Console.WriteLineConsole.WriteLine(这是这是p=0,p);p=0,p); MyMethod(pMyMethod(p););Console.WriteLineConsole.WriteLine(这是调用方法后的这
60、是调用方法后的p=0,p);p=0,p); 程序运行结果如图4-6所示。图4-6 程序运行结果通过程序的运行结果可以看出,变量p的值调用方法前和调用方法后没有发生变化。因为方法MyMethod的形参a是值参数,a是p的一个副本,所以对a的值的修改不会影响到p的值。2.引用参数 引用参数用关键字引用参数用关键字refref声明。传递的参数实质上是实参声明。传递的参数实质上是实参的一个指针,即地址。所以方法中对形参的操作就是对实的一个指针,即地址。所以方法中对形参的操作就是对实参的操作。使用引用参数时,必须在方法定义和方法调用参的操作。使用引用参数时,必须在方法定义和方法调用时都明确的指明时都明确
61、的指明refref关键字,且要求实参变量在传递给方法关键字,且要求实参变量在传递给方法前必须初始化。前必须初始化。 例例4-13 4-13 引用参数的运用引用参数的运用using using System.TextSystem.Text; ;using System;using System;using using System.Collections.GenericSystem.Collections.Generic; ;namespace examplenamespace example class Test class Test public static void public sta
62、tic void RefParamRefParam( ref ( ref intint a) a) a +; a +; public static void Main() public static void Main() intint x=0; x=0; Console.WriteLineConsole.WriteLine(这是这是RefParamRefParam之前的之前的x=0, x);x=0, x); RefParam(refRefParam(ref x); x); Console.WriteLineConsole.WriteLine(这是调用这是调用RefParamRefParam方
63、法后的方法后的x=0, x);x=0, x); 程序运行结果如图程序运行结果如图4-74-7所示。所示。图4-7 程序运行结果变量x在调用方法RefParam()之前,初始化值为0。从程序的运行结果可以看出,变量x的值在调用方法RefParam()后发生改变了。这是因为a是引用参数,在调用方法RefParam()时,实参x将其自身在内存中的地址传递给了形参a,所以形参a与实参x在内存中共用同一内存单元,对形参a的操作实际上就是对实参x的操作,所以方法RefParam()对a值的修改会直接影响到x的值。3.输出参数 输出参数用关键字输出参数用关键字outout声明。与引用参数类似,声明。与引用参
64、数类似,输出参数也是直接对实参进行操作的,在方法声明输出参数也是直接对实参进行操作的,在方法声明和方法调用时都必须明确指明和方法调用时都必须明确指明outout关键字。输出参数关键字。输出参数不要求变量在传递给方法之前进行初始化,但是在不要求变量在传递给方法之前进行初始化,但是在方法返回前必须对输出参数进行赋值。方法返回前必须对输出参数进行赋值。 例例4-14 4-14 输出参数的运用输出参数的运用using using System.TextSystem.Text; ;using System;using System;using using System.Collections.Gener
65、icSystem.Collections.Generic; ;namespace ConsoleApplication1namespace ConsoleApplication1class Testclass Test public static void public static void OutParamOutParam( out ( out intint a) a) a=100;a=100; public static void Main()public static void Main() intint x ; x ; OutParam(outOutParam(out x); x);
66、 Console.WriteLineConsole.WriteLine(这是调用这是调用OutParamOutParam方法后的方法后的x=0, x=0, x);x); 程序运行结果如图程序运行结果如图4-84-8所示。所示。图4-8 程序运行结果 4.数组型参数 数组型参数用关键字数组型参数用关键字paramsparams声明,该关键字用声明,该关键字用来声明具有可变长度的参数列表,方法声明中最多来声明具有可变长度的参数列表,方法声明中最多只能包含一个只能包含一个paramsparams参数,并且该参数必须是参数参数,并且该参数必须是参数列表中最后一个参数。列表中最后一个参数。 例例4-15
67、 4-15 数组型参数的运用数组型参数的运用using using System.TextSystem.Text; ;using System;using System;using using System.Collections.GenericSystem.Collections.Generic; ;namespace ConsoleApplication1namespace ConsoleApplication1class Testclass Testpublic static double public static double VarLenParamVarLenParam( ( pa
68、ramsparams intint v) v) double sum=0;double sum=0; foreachforeach ( (intint i in v) i in v) sum += i;sum += i; double average = sum / double average = sum / v.Lengthv.Length; ; return average;return average; public static void Main()public static void Main() double x =VarLenParam(1,3,5,7,9);double x
69、 =VarLenParam(1,3,5,7,9); Console.WriteLineConsole.WriteLine(这是这是1+3+5+7+91+3+5+7+9的平均值的平均值=0, x);=0, x); x = VarLenParam(2, 4, 6, 8);x = VarLenParam(2, 4, 6, 8); Console.WriteLineConsole.WriteLine(这是这是2+4+6+82+4+6+8的平均值的平均值=0, x);=0, x); 程序运行结果如图程序运行结果如图4-94-9所示。所示。图4-9 程序运行结果在上述例子中,将变动长度的参数声明为一维数组
70、,调用方法时,根据实参的个数确定一维数组的长度。4.5.3 方法重载 在在C#C#中,为了给功能相似的方法提供同一名称,中,为了给功能相似的方法提供同一名称,实现编译时的多态性,引入了方法重载这一概念。实现编译时的多态性,引入了方法重载这一概念。方法重载方法重载 (overloaded methods)(overloaded methods)是面向对象程序设是面向对象程序设计的一个重要概念,它具有以下特点:计的一个重要概念,它具有以下特点: 1.1.方法名相同;方法名相同; 2.2.参数列表不完全相同,即参数的个数或参数的参数列表不完全相同,即参数的个数或参数的类型不同。类型不同。 当一个重载
71、方法被调用时当一个重载方法被调用时,C#,C#会根据参数的个数会根据参数的个数及其类型自动匹配调用具体的方法。及其类型自动匹配调用具体的方法。1.1.参数个数不同的方法重载参数个数不同的方法重载例例4-16 4-16 参数个数不同的方法重载参数个数不同的方法重载 using System;using System;using using System.Collections.GenericSystem.Collections.Generic; ;using using System.TextSystem.Text; ;namespace examplenamespace example cla
72、ss class OverLoadMethodOverLoadMethod void void Max(intMax(int num1, num1, intint num2) num2) intint max; max; Console.WriteLine(0 Console.WriteLine(0和和11中较大的数字是:中较大的数字是:2,num1,num2,max=(num1num2 ? num1:num2); 2,num1,num2,max=(num1num2 ? num1:num2); void void Max(intMax(int num1, num1, intint num2,
73、num2, intint num3) num3) int max = num1 num2 ? num1 : num2; int max = num1 num2 ? num1 : num2; max = max num3 ? max : num3; max = max num3 ? max : num3; Console.WriteLine(0 Console.WriteLine(0、11和和22中最大的数字是:中最大的数字是:3,num1, num2, num3, max);3,num1, num2, num3, max); static void Main(string args) stat
74、ic void Main(string args) OverLoadMethod p1 = new OverLoadMethod(); OverLoadMethod p1 = new OverLoadMethod(); p1.Max(23, 52);/ p1.Max(23, 52);/调用具有两个参数的调用具有两个参数的MaxMax方法方法 p1.Max(13, 25, 64);/p1.Max(13, 25, 64);/调用具有三个参数的调用具有三个参数的MaxMax方法方法 程序运行结果如图程序运行结果如图4-104-10所示。所示。图4-10程序运行结果以上程序中定义了两个Max方法,第一
75、个Max方法接受两个int型参数,第二个Max方法接受三个int型参数。所以在Main函数中,p1.Max(23,52)会自动根据参数的个数(两个)和参数的类型(int)进行匹配调用第一个max方法,而p1.Max(13,25,64)则根据参数个数(三个)和参数类型(int)进行匹配调用第二个max方法。2.2.参数类型不同的方法重载参数类型不同的方法重载例例4-17 4-17 参数类型不同的方法重载参数类型不同的方法重载using System;using System;using using System.Collections.GenericSystem.Collections.Gene
76、ric; ;using using System.TextSystem.Text; ;namespace examplenamespace example class class OverLoadMethodOverLoadMethod intint Maxinum(intMaxinum(int numbers) numbers) intint temp = numbers0; temp = numbers0; foreachforeach ( (intint i in numbers) i in numbers) if (temp i) if (temp i) temp = i; temp
77、= i; return temp; return temp; double double Maxinum(doubleMaxinum(double numbers) numbers) double temp = numbers0; double temp = numbers0; foreachforeach (double i in numbers) (double i in numbers) if (temp i) if (temp i) temp = i; temp = i; return temp; return temp; static void static void Main(st
78、ringMain(string argsargs) OverLoadMethodOverLoadMethod p = new p = new OverLoadMethodOverLoadMethod();(); intint num = 100, 0, 23, 45, -50, 300 ; num = 100, 0, 23, 45, -50, 300 ; double double mdbNummdbNum=0.5,8.3,12.8,45.9,21.7;=0.5,8.3,12.8,45.9,21.7; Console.WriteLineConsole.WriteLine(最大的整数是:最大的整
79、数是:0, 0, p.Maxinum(nump.Maxinum(num);); Console.WriteLineConsole.WriteLine(最大的小数是:最大的小数是:0, 0, p.Maxinum(mdbNump.Maxinum(mdbNum);); 程序运行结果如图程序运行结果如图4-114-11所示。所示。图411程序运行结果Main()方法中对象p调用Maxinum方法会自动根据参数的类型是int还是double决定调用哪个重载方法。参数num是一个int型数组,所以调用第一个Maxinum方法;参数mdbNum是一个double型数组,所以调用第二个Maxinum方法。4.
80、6 静态和实例类成员 属性、方法和数据成员可以分为两大类:静态成属性、方法和数据成员可以分为两大类:静态成员和实例成员。静态成员(包括静态方法、静态属员和实例成员。静态成员(包括静态方法、静态属性和静态字段)在类的实例之间是共享的,所以调性和静态字段)在类的实例之间是共享的,所以调用静态成员时,不需要实例化类,直接用类名调用。用静态成员时,不需要实例化类,直接用类名调用。而实例成员属于类的对象所有,直接由对象调用。而实例成员属于类的对象所有,直接由对象调用。 如果一个成员要声明为静态成员,只需要在该成如果一个成员要声明为静态成员,只需要在该成员的访问修饰符的后面加上员的访问修饰符的后面加上st
81、aticstatic关键字即可。关键字即可。例例4-18 4-18 静态和实例成员静态和实例成员class class MyClassMyClass public public intint a; a; public static public static intint b; b; void fun1() void fun1() a = 10; a = 10; b = 20; b = 20; static void fun2() static void fun2() a = 10; a = 10; b = 20; b = 20; class Program class Program static void static void Main(stringMain(string argsargs) ) MyClassMyClass A = new A = new MyClassMyClass();(); A.aA.a = 10;/ = 10;/正确,正确,a a是实例成员是实例成员 A.bA.b = 10;/ = 10;/错误,错误,b b是静态成员是静态成员 MyClass.aMyClass.a = 20;/ = 20;/错误,错误,a a是实例成员是实例成员 MyClass.bMyClass.b = 20;/ = 20;/正确,正确,b b是静态成员是静态成员