E N D
第8章 类 • 在前述各章中,侧重介绍了C#语言的一些过程化的基本语法规则和使用方法,而没有提到在所有举例的程序代码中都已经使用的一个关键字class,我们已发现所有的代码都是由class——类来封装组织的。本章正式讨论有关类的概念以及如何用类来构架一个更复杂的应用程序。类是C#语言实现面向对象程序设计的基础,它是C#封装的基本单元,类把对象、属性和方法这些类成员封装在一起构成一个有机整体,即数据结构。当类的成员定义为保护或私有(protected或private)时,外部不能访问;定义为公有(public)时,则任何用户均可访问。通过本章学习,要求掌握定义类和成员的方法,掌握访问成员方法的方式,理解被保护数据如何屏蔽外部访问的原理,从而更深入地理解类的封装性,体会面向对象程序设计的思想。 • 本章教学目的: • 理解面向对象程序设计的思想和基本概念 • 掌握创建并初始化类的实例的方法 • 掌握定义、访问类的成员的方法 • 掌握构造函数和析构函数的定义和使用方法 • 掌握定义和使用静态成员及实例成员的区别 • 掌握重载方法、运算符的概念和使用方法 • 理解类的封装性原理及实施机制(访问修饰符)
第8章 类 • 8.1 面向对象程序设计思想 • 8.2 类及其构成 • 8.3 创 建 对 象 • 8.4 类的数据成员 • 8.5 类的方法成员 • 8.6 类的属性成员 • 8.7 索 引 器 • 8.8 运算符重载
8.1 面向对象程序设计思想 • 传统的结构化编程(如用C语言)就是先设计一组函数,即解决问题的算法,然后针对问题要处理的数据特征找出相应的数据存储方法,即数据结构。这就是最初Pascal语言的设计者——Nicholase.Wirth提出的著名公式:程序=算法+数据结构。
8.1 面向对象程序设计思想 • 1. 解决问题的思路 • 首先在对解决问题的方法上,OOP与结构化的解决方法就有很大的差别。 • OOP对问题的分析和解决基于两个原则:抽象和分类。 • 抽象与具体相对应。例如,在日常生活中我们用洗衣机洗衣服时,先将衣服放入洗衣机,然后加入一些洗衣粉,打开水龙头向洗衣机放水,再在控制面板上设置洗衣时间并启动。 • 在上述使用洗衣机时,人们已处于下面的状态: • 不用为洗某种衣服而了解和重新设计改变洗衣机的内部结构。人们使用洗衣机,只需跟它的控制面板打交道,该面板就是洗衣机与人的“接口”,控制面板上的所有控制按钮和时间显示就是接口的工作参数,洗衣机的所有功能都是通过这个“接口”驱动完成的。 • 不用为某次洗衣而重新编制软件来驱动和控制洗衣机中的微处理器。 • 可见,人们仅仅把洗衣机看作是一个家用电器使用,在使用时不会去考虑它的内部结构、电路及处理器的设计等细节问题,只是通过它的“接口”,即控制面板来使用它。面向对象程序设计在分析处理问题时,就是采取这种“抽象”思维,即把某一程度的细节“抽象化”,将要解决的问题在不同的层面上进行抽象,设置“接口”驱动功能来实现应用需求。 • 在用洗衣机洗衣服过程中,人们只要按照面板显示的提示去做,就不会使洗衣机进入不正常的工作状态。如果在正常情况下,洗衣机冒水或不对衣物进行洗涤、甩干了,那就是洗衣机的质量问题,相应的这就是OOP中的类库设计问题。如果误操作引起不洗涤或不甩干,就需要调整操作,这就是OOP应用程序设计中调用类库的参数设置问题。所以在OOP中有两种分工明确的编程:一种是面向对象应用程序设计,另一种是类库设计。
8.1 面向对象程序设计思想 • 用面向对象的程序设计方法对上述洗衣机洗衣物问题的分析处理思路是: • 首先定义这个问题中的对象类型:衣物、洗衣机、水、洗衣粉。 • 设计对象的模型(创建对象),即洗衣机的制作。这时程序设计在对象一级上。 • 处理问题的方法设计(类库设计)。这时不用考虑洗衣机的制作,而是设计在洗衣机上可完成的各种不同时间的洗涤和甩干的洗涤方式。 • 面向对象主程序设计。这是更高级的抽象,程序设计在应用级上。程序流程可以是:放衣物、放水和洗衣粉等,调用某种洗涤方式,如在洗衣机中洗涤4分钟、甩干2分钟或其他洗法,这就是处理问题的方法。可见,面向对象程序描述既简单又完整,解决问题的层次清楚。 • 而结构化程序设计是将洗衣机的外壳、内部结构、衣物、洗衣粉和水都处于一个程序环境中,其程序显示出层的函数调用结构。控制从手到控制面板,从控制面板进入洗衣机内部,再在内部复杂的电路中流动,最后发出“洗好了”的声音。在这种环境中,没有抽象和问题分层处理的理念,即程序中没有对象,没有能隐藏事物固有复杂性的抽象。所以导致要用洗衣机洗衣服,必须首先是制作洗衣机的专家。而下次洗被单时,又要重复作一个专门洗被单的洗衣机了。
8.1 面向对象程序设计思想 • 2. 结构化程序和面向对象程序的结构 • 在面向过程的结构化程序结构中,程序的结构是:程序=(算法)+(数据结构)。 • 在这种结构中算法是一个独立的整体,数据结构(包含数据类型与数据)也是一个独立的整体。两者是分开设计的,且以算法(函数或过程)为主。 • 随着对问题结构的深入研究,软件开发人员对上述程序定律有了重新的认识,他们越来越注重于系统整体关系的表示和数据模型技术(把数据结构与算法看作一个独立功能模块),即:程序=(数据结构+算法)。算法与数据结构是一个整体,一个算法只能针对于特定的数据结构,它是对数据结构的访问。面向对象程序设计的思想就是要把算法与数据结构捆绑在一起,因为现实世界中任何对象都具有一定的属性和操作,所以也就总可以用数据结构和算法来全面地描述这个对象。这样程序定律又可进一步表示为: • 对象=(数据结构+算法) • 程序=(对象+对象+……) • 面向对象程序的结构:程序是由许多对象组成,对象是程序的实体,这个实体包含了对该对象属性的描述(数据结构)和对该对象进行的操作(算法),如图8.1所示。
8.1 面向对象程序设计思想 图8.1 面向对象程序结构
8.1 面向对象程序设计思想 • 3. 结构化程序和面向对象程序的实现机制 • 面向过程的结构化程序设计是围绕功能进行的,一个函数一个功能。所有的数据都是公用的,一个函数可以使用任意一组数据,而一组数据又能被多个函数所使用。程序设计者必须考虑对一个问题处理的整个细节,当问题规模较大又比较复杂时,就会感到难以应付。而且数据结构和算法的设计都是针对当前的数据特征考虑的,如果要重用,可能需要较大的修改,如数据类型不符等;而且调用函数的过程不是透明的(除非所有需要的函数都在一个程序文件中)。 • 由上所述,面向对象程序设计把相关的数据(数据结构)和操作(算法)放在一起,构成一个有机的整体(对象),与外界相对分隔,这就叫“封装”一个对象。设计者的任务:一是设计对象,即决定把哪些数据和操作封装在一个对象中;二是在此基础上怎样通知有关对象完成所需的任务。程序员只是扮演了一个总调度的角色,他不断向各个对象发出命令,要它们按照要求完成所需的工作。所以面向对象程序设计是通过对问题对象进行分类的分类技术,以及继承、多态、重用技术来实现对问题的处理。通过后续章节的学习,应掌握实现OOP的这些关键技术。
8.2 类及其构成 • 1. 类和对象 • 8.1节已经说明了对象的概念。每一个实体都是对象。而现实世界中有些实体(对象)具有相同的结构和特性,如:信息991班、信息992班和信息994班就是3个具有相同结构和特性的不同对象。用“类”来描述具有相同数据结构和特性的“一组对象”,可以说,“类”是对“对象”的抽象,而“对象”是“类”的具体实例,即一个类中的对象具有相同的“型”,但其中每个对象却具有各不相同的“值”。
8.2 类及其构成 • 2. 类的定义 • 在C#中用关键字class 来定义类,其基本格式为: • class 类名 • { • //类的成员定义 • }
8.2 类及其构成 • 3. 类的成员 • 类是由数据成员和函数成员组成,它们分别是面向对象理论中类的属性和方法。 • 类的数据成员包含类的常量成员和类的变量成员,它们可以是前面介绍的任何数据类型的变量,甚至可以是其他类。
8.2 类及其构成 • 类的函数成员指的是执行操作的方法,就是解决某个问题的代码。函数成员有以下 几种: • 方法成员(method) • 构造函数(constructor) • 析构函数(destructor) • 属性成员(property) • 索引成员(indexer) • 运算符成员(operator) • 事件成员(event)
8.2 类及其构成 • 4. 派生类 • 在C#中,如果一个类是另一个类派生的,或者说一个类是从另一个类继承来的,则定义该类的格式为: • class 类名:基类名 • { • //类的成员定义 • } • 关于类的继承将在第9章中详细介绍。
8.2 类及其构成 • 5. 类成员的访问权限(封装数据) • 类成员的访问权限用来限制外界对某一个类成员的访问。类成员的访问权限有以下几种: • public:允许外界访问。 • private:不允许外界访问,也不允许派生类访问,只能在定义该成员的类中调用。 • protected:只允许在定义该成员的类或其派生类的内部被调用(参见第9章)。 • internal:允许同一个命名空间中的类访问,例如,允许同一个工程项目中的类访问(参见第9章)。
8.2 类及其构成 • 6. 命名空间 • 命名空间又叫命名空间。在C#中使用命名空间(namespace)来管理各种类。例如,前面用的控制台类Console和各种简单的数据类型,这些类都定义在System命名空间中。
8.2 类及其构成 • 【例8.1】 使用别名表示命名空间的类。 • using BOARD= System.Console; //用BOARD作为System.Console类的别名 • public class nickname • { • public static void Main() • { • BOARD.WriteLine("Hello World!"); //使用类Console的别名调用 • //WriteLine()方法 • } • }
8.3 创 建 对 象 • 我们已经知道,类只是一种用于创建对象的定义,它本身并不能存储信息或执行代码,而对象是类的一个实例,它可以按类定义的框架(数据结构)来存储信息和执行代码。所以,定义类以后,便可以创建对象。创建类对象的过程就是实例化类,其语句格式如下: • 类名 对象名=new 类名(); • 其中对象名就是要创建的对象名称。 • 例如: • class Point • { • } • //定义(构造)一个Point类的对象StringPoint,并为该对象分配存储空间 • Point StringPoint=new Point();
8.4 类的数据成员 • 8.4.1 常量成员 • 8.4.2 变量成员 • 8.4.3 将类定义为数据成员
8.4 类的数据成员 • 类数据成员在C#中用来描述一个类的特征,即面向对象理论中类的属性。C#中有两类数据成员,一类是常量成员,另一类是变量成员(或者说是字段成员)。
8.4.1 常量成员 • 在某个类中定义的常量就是这个类的常量成员,这个类的所有其他成员都可以使用这个常量来代表某个值。比如,在一个涉及利率计算的类中,定义一个名为rate的double型常量,rate常量的值定义为某种利率的值,则在该类中所有需要用到这种利率的地方,就用常量rate来代表。
8.4.1 常量成员 • 1. 定义常量成员 • 在一个类中定义常量的基本格式: • class 类名 • { • [常量修饰符] const 数据类型 常量名1=常量表达式1, 常量名2=常量表达式2……; • }
8.4.1 常量成员 • 2. 访问常量成员 • 在程序中,可以通过以下格式访问一个类中具备public访问权限的常量成员: • 类名. 常量成员名 • 例如,现在想在类ClassB中定义一个int型常量x,而x的值等于ClassA中var1和var2常量的和,则ClassB的定义可以写为: • class ClassB • { • const int x=ClassA.var1+ClassA.var2; • }
8.4.2 变量成员 • 在类中定义的变量就称为该类的变量成员。变量成员同常量成员是不同的,如果类的对象(实例)不存在,即没有创建该类的对象,则表示没有使用变量的需求,这时系统不给这个类的变量成员分配存储空间,变量成员也就无法被访问。只有在生成类的对象(实例)后,变量成员才在这个对象所占用的空间中得到自己的存储空间。所以变量成员通常只属于一个特定的对象,而常量成员属于类本身。
8.4.2 变量成员 • 1. 定义变量成员 • 在类中定义变量成员的格式: • class 类名 • { • [变量修饰符] 数据类型 变量名1,变量名2……; • }
8.4.2 变量成员 • 2. 变量成员的初始化 • 在应用中,一般将变量成员分为两类: • 静态变量成员(带static修饰符定义的变量) • 非静态变量成员(又称为实例变量成员,即定义时不带static修饰的) • 对于类的变量成员(静态的或非静态的),可以在定义时初始化它们,但一般不这样做。在C#中对这两类类的变量成员的初始化工作有不同实现方式。 • 非静态变量成员的初始化创建一个类的实例时,该实例的所有非静态变量将通过这个类的构造函数来初始化为它们的默认值。有关构造函数的详细内容在8.5节中专门介绍,这里仅举例说明非静态变量初始化实现的过程。
8.4.2 变量成员 • 例如: • using System; • class InitUnstatic • { • protected int a; //定义时没有赋值 • protected int b=111; //定义时赋值 • public static void Main() • { • InitUnstatic app=new InitUnstatic(); //创建InitUnstatic类的对象实例app • Console.WriteLine("a={0}, b={1}", app.a , app.b); • } • } • 程序运行结果: • a=0, b=111 • 可见,对在定义时没有赋值的非静态变量成员a,在创建app实例时,系统调用其内部的构造函数为a分配内存空间,并执行默认的初始化操作( a被赋为0值),而在创建app实例前,a的值是不确定的。变量b的初始化过程为:在创建app实例时,系统也调用其内部的构造函数为b分配内存空间,但还要执行赋值的初始化操作( b被赋为111值)。 • 值得注意的是,在对类的非静态变量执行初始化时,不能引用类的非静态变量成员来进行其他变量的初始化,也不能引用this关键字。
8.4.2 变量成员 • 例如: • using System; • class InitUnstatic • { • protected int a=1; • protected int b=a+111; //出错,变量的初始值设定不能引用非静态 • 变量成员a的值 • public static void Main() • { • InitUnstatic app=new InitUnstatic(); • Console.WriteLine("a={0},b={1}", app.a ,app.b); • } • } • 程序中出错的原因是:非静态变量成员只在创建一个实例时才被初始化为对应的实例变量,这也是常把类的非静态变量称为实例变量的原因,而在本程序段中,执行语句b=a+111时还没有创建一个实例,所以变量a也就没有被初始化,这时a是个不确定的值,所以也就不能被引用。总之,在执行InitUnstatic app=new InitUnstatic();语句之前,类中定义的所有非静态变量的值都是不确定的,也不能被访问和引用。
8.4.2 变量成员 • 静态变量成员的初始化上面介绍了类的非静态变量成员的初始化过程和方法,类的静态变量成员的初始化也有相应的实现方法。类的静态变量的初始值是相应静态变量的数据类型的默认值。即当加载一个类时,类中的所有静态变量成员都被初始化为其数据类型的默认值(第3章中表3.3的各数据类型的默认值)。下面再次举例说明静态变量与非静态变量在初始化过程中的区别。 • 例如:using System; • class Inistatic • { • static int a=3; //正确 • static int b=a+111; //正确 • int x=8; • //int y=x+1; //错误 • public static void Main() • { • Inistatic app=new Inistatic(); • //对静态成员必须使用类名Inistatic来访问,如使用对象名app则出错 • Console.WriteLine("a={0},b={1}", Inistatic.a , Inistatic.b); • //对非静态成员必须使用对象名来访问 • Console.WriteLine("x={0}", app.x); • //Console.WriteLine("y={0}", app.y); • } • } • 对上述状态的代码运行,结果为: • a=3, b=114 • x=8
8.4.2 变量成员 • 3. 访问变量成员 • 由变量成员定义可知,通过修饰符可以对类中的变量成员访问加以控制。我们将会看到通过将一些变量设置为静态的(用static关键字),可以使程序员在编写程序过程中结合问题的特征,合理使用这些静态变量成员,从而简化问题的处理。 • (1) 静态成员和非静态成员(实例成员)类的成员分为两大类:静态成员和非静态成员(实例成员)。通过类的各种成员定义(目前只学了类的数据成员的定义),将可以使用static修饰符把类的变量成员、方法、属性、事件、运算符及构造函数等定义为各种静态成员。相应地,不用static修饰符定义的类的变量成员、方法、属性、事件、运算符及构造函数等,则表示定义的是各种实例成员。
8.4.2 变量成员 • ① 静态成员的特点 • 不管类创建了多少个实例,类中的一个静态变量成员只占据一个存储空间(只有一个副本),不同的实例将共享这同一内存。也就是说,静态变量成员和实例是一对多关系。 • 类的静态函数成员(包括方法、属性、索引器、运算符或构造函数等)在类中能被各个实例共享,它们不是用来专门针对特定的实例起作用,所以实例用this关键字调用静态函数成员时会出错。但在静态函数成员体中只能访问(引用)静态成员。 • 访问或引用静态成员的格式是:类名. 静态成员名 • ② 实例成员的特点 • 类中的每一个实例都包含了类的所有实例变量成员的不同存储空间,即类中的每个实例都包含有类的所有实例变量成员的独立副本。也就是说,实例和类中的实例变量是多对多关系。 • 类的实例函数成员(包括方法、属性、索引器、运算符或构造函数等)在类中是被各个实例分享的,用来对特定的实例起作用,所以实例可以用this关键字调用实例函数成员。另外在实例函数成员体中可以访问(引用)任何静态或非静态的成员。 • 访问或引用实例成员的格式是:实例名. 实例成员名 • 本节将只侧重介绍有关类的静态变量成员和非静态变量成员(实例变量成员)在访问和使用中的区别。
8.4.2 变量成员 • 【例8.2】静态成员与实例成员的区别。 • using System; • class Test • { • int x; //定义实例变量成员 • static int y; //定义静态变量成员 • public void Method1() //定义实例方法 • { • x=10; //正确,等价于this.x=10; • y=20; // 正确,等价于Test.y=20; • } • public static void Method2() //定义静态方法 • { • x=10; //错误,静态方法无法访问非静态变量成员,即不能访问this.x • y=20; //正确,静态方法可以访问静态变量成员,即等价于Test.y=20; • } • public static void Main() • { • Test app=new Test(); //创建类的实例 • app.x=10; //正确,通过实例访问实例变量成员 • app.y=20; //错误,类的实例不能访问静态变量成员,应改用类型名来 • 引用 • Test.x=10; //错误,不能通过类名访问实例成员 • Test.y=20; //正确,可以通过类名访问静态成员 • } • }
8.4.2 变量成员 • 【例8.3】一所大学要统计开学报到的学生人数。程序中定义了一个学生类,其中定义了一个静态变量成员,用来跟踪记录报到的学生人数,以便随时掌握报到信息。 • using System; • class Student • { • public static int studcount=0; //定义一个公共静态变量成员,用于统计 • //学生人数 • public Student() //定义构造函数 • { • studcount++; //统计学生人数 • } • } • class AppStud • { • public static void Main() • { • Student student1=new Student(); //创建类的一个实例 • Console.WriteLine("学生人数={0}", Student.studcount); • //通过类名访问静态变量 • Student student2=new Student(); //创建类的另一个实例 • Console.WriteLine("学生人数={0}", Student.studcount); • //通过类名访问静态变量 • } • } • 程序运行结果: • 学生人数=1 • 学生人数=2
8.4.2 变量成员 • 【例8.4】带有static修饰符的静态数据成员特征(以下以路程的起点和终点来说明)。 • class point • { • public int x; • public int y; • } • class line • { • static public point origin=new point(); //类point的对象origin是 • 类line的静态成员 • public point end=new point(); • } • class LineApp • { • public static void Main() • { • line line1=new line(); //建立路程对象line1 • line line2=new line(); //建立路程对象line2 • //设置路程的起点(设置了类line的起点坐标) • line.origin.x=1; • line.origin.y=2; • //设置路程1的终点值 • line1.end.x=5; • line1.end.y=6; • //设置路程2的终点值 • line2.end.x=7; • line2.end.y=8; • //打印起点使用类名line • System.Console.WriteLine("Line 1 start: ({0},{1})", line.origin.x , line.origin.y);
8.4.2 变量成员 • //打印终点使用对象名line1 • System.Console.WriteLine("Line 1 end: ({0},{1})", line1.end.x , line1.end.y); • //打印路线2 • System.Console.WriteLine("Line 2 start: ({0},{1})", line.origin.x , line.origin.y); • System.Console.WriteLine("Line 2 end: ({0},{1})", line2.end.x , line2.end.y); • //改变路程2的起点值 • line.origin.x=888; • line.origin.y=999; • //再打印 • System.Console.WriteLine("Line 1 start: ({0},{1})", line.origin.x , line.origin.y); • System.Console.WriteLine("Line 1 end: ({0},{1})", line1.end.x , line1.end.y); • System.Console.WriteLine("Line 2 start: ({0},{1})", line.origin.x , line.origin.y); • System.Console.WriteLine("Line 2 start: ({0},{1})", line2.end.x , line2.end.y); • } • } • 程序输出: • Line 1 start: (1, 2) • Line 1 end: (5, 6) • Line 2 start: (1, 2) • Line 2 end: (7, 8) • Line 1 start: (888, 999) • Line 1 end: (5, 6) • Line 2 start: (888, 999) • Line 2 end: (7, 8)
8.4.2 变量成员 • 【例8.5】观察静态成员变量和非静态变量成员的区别。 • using System; • public class FieldTest //定义类 • { • public static int field=1; //静态变量成员 • public int test=1; //非静态变量成员 • public FieldTest() //构造函数 • { • field++; • test++; • } • } • public class App • { • public static void Main() • { • Console.Write("{0}\t",FieldTest.field); //静态成员变量field必须 • 用类名调用 • FieldTest f1=new FieldTest(); //创建一个对象 • Console.Write("{0}\t",FieldTest.field); //静态field用类名调用 • Console.Write("{0}\t",f1.test); //非静态test用对象名调用 • FieldTest f2=new FieldTest(); //创建另一个对象 • Console.Write("{0}\t",FieldTest.field); //静态field用类名调用 • Console.WriteLine("{0}\t",f2.test); //非静态test用对象名调用 • } • } • 程序输出结果: • 1 2 2 3 2
8.4.2 变量成员 • 【例8.6】对象的独立性。 • using System; • calss Undepend • { • public int m; • } • class test • { • static void Main() • { • Undepend obj1=new Undepend (); //创建ClassA类的对象obj1 • Undepend obj2=new Undepend (); //创建ClassA类的对象obj2 • obj1.m=10; • obj2.m=100; • Console.Write("obj1.m ={0}\t obj2.m={1}\n", obj1.m, obj2.m ); • } • } • 程序运行结果: • obj1.m =10 obj2.m=100
8.4.2 变量成员 • 【例8.7】只读变量成员(readonly修饰符)。 • using System; • public class ReadOnly • { • public readonly int x=10; //只读成员变量 • public int y=40; //公有成员变量 • public readonly int z=80; //只读成员变量 • public ReadOnly() //构造ReadOnlyTest类的方法(构造函数) • { • x++; //在构造方法中修改成员变量的值 • y++; • z++; • } • public ReadOnly(int a, int b, int c) //重载构造方法(重载构造函数) • { • x=a; //在构造方法中修改成员变量的数值 • y=b; • z=c; • } • } • public class App • { • public static void Main() • {
8.4.2 变量成员 • ReadOnly f1=new ReadOnly(); //创建对象f1 • Console.Write("{0}\t",f1.x); • Console.Write("{0}\t",f1.y); • Console.WriteLine(f1.z); • //f1.z=78; //此语句非法,只读成员不可以被赋值,如果此语句参加运行,编译器出错 • ReadOnly f2=new ReadOnly(-5,-6,-7); //创建对象f2 • Console.Write("{0}\t",f2.x); • Console.Write("{0}\t",f2.y); • Console.WriteLine(f2.z); • } • } • 程序运行结果: • 11 41 81 • -5 -6 -7
8.4.2 变量成员 • 说明:被定义为只读的成员变量只允许在定义的过程中利用变量赋 初值为其赋值,或者在构造方法中为其赋值,在其余的地方, 无论是 类的方法或对象都不可以改变它的数值,但它可以参与 各种运算。 在实际问题中,如果所要用的常量无法在编译时“计算”出来, 而又需要使该常量为整个类所共享,此时就可以定义一个静态 只读变量,它既可以实现“计算”又可以当作常量使用。例如, 在下列代码中使用3个静态只读变量,分别表示本科生、硕士 生和博士生的三方面信息,并在静态构造函数中为这些信息变 量赋值,最后输出某类学生的信息:
8.4.2 变量成员 • using Sysytem; • class Student • { • public Student(double allow, string demand, string location ) • //构造函数 • { • this.allow=allow; //津贴 • this.demand=demand; //培养标准 • this.location=location; //住址 • } • public double allow; • public string demand; • public string location; • public static readonly Student Bachelor; • //定义静态只读的Student类型的变量Bachelor • public static readonly Student Master; • public static readonly Student Doctor; • static Student() //静态构造函数,初始化静态只读变量 • { • Bachelor=new Student(200, "studious", "1#楼"); • Master=new Student(1000, "creative", "2#楼"); • Doctor=new Student(2000, "inventive", "3#楼"); • } • } • class App • {
8.4.2 变量成员 • public static void Main() • { • Student student1=Student.Bachelor; //创建一个Student类本科 • //生实例,同时对其初始化 • Console.WriteLine("该学生每月的津贴为:{0}", student1.allow); • Console.WriteLine("对该学生的培养标准是:{0}", student1.demand); • Console.WriteLine("该学生住址:{0}", student1.location); • } • } • 在本例中,由于有静态构造函数对静态只读变量Bachelor、Master、Doctor进行初始化,所以不必将它们定义成常量,在整个类都可以使用,并且如需要对各类学生的这些基本条件进行修改,只需修改静态构造函数中的赋值即可,这就提高了应用的灵活性。
8.4.3 将类定义为数据成员 • 在实际应用中,我们可以将类定义为数据成员,即嵌套类。如创建一个FullName的类,它包括3个字符串,分别存储姓、名和中间名;也可以创建一个名为Address的类,它包含 FullName类和用于存储地址的其他字符串;还可以创建一个Customer类,它包含一个用于存储客户编号的long变量、一个Address类、一个用于存储账户结余的decimal变量和一个指定该账户是否有效的布尔型变量。
8.4.3 将类定义为数据成员 • 例如: • class Customer • { • long cusnum; • public class Address • { • public class FullName • { • string firstname; • string midname; • string lastname; • } • string address; • } • long telephone; • long fax; • string e_mail; • decimal sum; //存储账户结余 • bool y; //账户是否有效 • }
8.4.3 将类定义为数据成员 • 【例8.8】使用嵌套类。 • class point • { • public int x; • public int y; • } • class myline • { • public point starting=new point(); • public point ending=new point(); • } • class lineApp • { • public static void Main() • { • myline myline=new myline(); • myline.starting.x=2; //将myline的starting成员的x成员置2,或者说将2 • //赋给了myline对象的starting成员的x成员 • myline.starting.y=4; • myline.ending.x=16; • myline.ending.y=10; • System.Console.WriteLine("Point 1: ({0}, {1})" , myline.starting.x , myline.starting.y); • System.Console.WriteLine("Point 2: ({0}, {1})" , myline.ending.x , myline.ending.y); • } • } • 程序执行结果如下: • Point 1: (2 , 4) • Point 2: (16,10)
8.4.3 将类定义为数据成员 • 上例中myline类的成员及其名称可表示为图8.2所示。 图8.2 【例8.8】类的成员结构
8.4.3 将类定义为数据成员 • 在本例中没有定义嵌套类,只是平行定义了两个类point和myline。由此例可以看出,使用类创建的对象可以与其他任何数据类型的变量一样使用。就类本身而言,它并没有做任何工作,而只是一种描述。如对point类的定义只是一种描述,它没有使用内存,也没有任何动作,该描述只是定义了一种类型,即point类。通常如果只在myline中使用point,则可将point类的定义放到myline类中,即将一种类嵌套到其他类中,这样可以在line类中使用point类。嵌套point类的代码如下: • class myline • { • public class point //这里point类必须定义为公有的 • { • public int x; • public int y; • } • public point starting=new point(); • public point ending =new point(); • } • 将此段代码代替上例的前10行两个类point和myline定义语句,执行效果一样,读者不妨一试。
8.5 类的方法成员 • 8.5.1 定义类的方法成员 • 8.5.2 使用方法 • 8.5.3 带参数的方法 • 8.5.4 静态方法 • 8.5.5 方法重载 • 8.5.6 其他类方法 • 8.5.7 递归方法
8.5 类的方法成员 • 前面各章中几乎把所有的代码都集中在类的Main()方法中,由这一个方法执行所有的功能。由于前述的程序都较短,所以看不出有什么问题。而在实际应用编程中,程序通常复杂而冗长,所以程序员按照程序代码执行的功能或其他依据将程序分块,由此形成了类的方法。
8.5.1 定义类的方法成员 • 方法是指类中用于对数据进行某种处理操作的算法,它就是实现某种功能的程序代码模块,在C/C++中称作函数,在面向对象编程技术中,将函数称为方法。在方法中,代码必须是结构化的。方法是访问、使用私有成员变量的途径。 • 在C#中,方法与它操作的对象封装在一起构成类,所以方法是类的成员。在一个类中定义方法成员的格式为: • class 类名 • { • [方法修饰符] 返回的数据类型 方法名([形式参数列表]) //方法头 • { • …… //方法体 • } • }