tceic.com
简单学习网 让学习变简单
当前位置:首页 >> 其它课程 >>

C#语言程序设计


C# 语 言 程 序 设 计
C# Language Programing
杜进;Enterdu TEL:13871340263 enterdu@sina.com blog.sina.com.cn/enterdu

课程简介
? 信息素养教育是大学生通识教育的重要内容之一。 ? C#是一种优秀的面向对象语言,它继承了C++和Ja

va等语言的优点 ,并与现代软件工程相适应。C#语言利用.NET作为运行平台,使得 它在Windows图形用户界面、Web应用、数据库等方面有强大的功 能。 ? 本课程的目标是:
? 学习和掌握C#语言的原理和方法,以及C#在常见的各类信息的处理以及企业信息 化的应用,以此来提高信息意识和信息化素养,更深入地理通过程序设计来处理 信息的原理和方法,更自如地应用计算机来为专业工作服务,为服务于企业信息 化打下基础。

? 本课程要求在先修课程《大学计算机基础(下)》或《VFP数据库 》中已经有程序设计的基础知识。

C#语言程序设计

2

课程目录
? 第0章 C#语言学习前的准备 ? 第1章 Visual C#简介 ? 第2章 C#简单程序编写 ? 第3章 C#程序设计基础 ? 第4章 C#控制语句 ? 第5章 数组和集合 ? 第6章 面向对象程序设计 ? 第7章 继承和接口设计 ? 第8章 Windows窗体应用程序设计 ? 第9章 用户界面设计
C#语言程序设计 3

第0章 C#语言学习前的准备
? 计算机基础知识测试题:
?将下列项目归类为硬件或软件:
? CPU、编译器、输入单元、字处理程序、Java程序

?填空题:
? 计算机编程语言包括:机器语言、 和 。 ? 计算机只能直接理解其本身的 语言,这种语言是由 1和0构成的。 ? 计算机处理数据时由一组指令控制,这组指令称为计算 机 。 ? 将高级语言程序变成机器语言的程序称为 。

C#语言程序设计

4

第0章 C#语言学习前的准备
? 计算机基础知识测试题:
?写出计算机网络相关缩略语的全称:
? W3C、HTML、XML、HTTP、SOAP

C#语言程序设计

5

可以开始进入C#课程学习了!
? 课程的重点、难点
?重点:C#应用程序的架构、程序调试、数据类型(值 类型、引用类型)、流程控制语句(条件分支语句、 循环语句、异常处理)、常用类操作和数据处理、封 装、继承、多态、接口 ?难点:C#应用程序的架构、应用类型、循环语句、异 常处理、面向对象的程序设计、继承、接口

? 课时安排
?总课时:48(课内)+32(课外) ?周课时:2(课堂)+1(实验)+2(课后)
C#语言程序设计 6

可以开始进入C#课程学习了!
? 参考资料
?ftp://dzsw:dzsw@221.232.141.195 ?书籍:
? P.J.Deital等著.《Visual C# 2008 大学教程(第3版)》.电子工业 出版社 ? karli Watson等著.《C#入门经典(第4版)》.清华大学出版社

?网络:
? www.mono-project.com Mono开源项目 创建Linux、 Windows与Mac OS X平台上的.NET程序; ? www.ecma-international.org/publications/standards/Ecma334.htm ECMA-334标准:C#语言规范; ? msdn.microsoft.com/library 微软开发人员联机库
C#语言程序设计 7

第1章 Visual C#简介
? 本章目录
1. 2. 3. 4. 5. 运行一个运用C#编写的应用程序实例 C ? C++ ? Java ? Visual C# OOP(面向对象编程)技术 .NET框架与CLR(公共语言运行环境) Visual Studio IDE介绍

C#语言程序设计

8

第1章 Visual C#简介
? 一个应用程序实例
?Examples\ch01\AdvancedP ainter.exe

C#语言程序设计

9

第1章 Visual C#简介
? C ? C++ ? Java ? Visual C#发展历史
?C语言
? 1973年由贝尔实验室的Dennis Ritchie开发的,最初作为UNIX 操作系统的开发语言;

?C++
? 20世纪80年代初由贝尔实验室的Bjarne Stroustrup开发的, 在C语言的基础上提供了面向对象编程的特性; ? 人们需要迅速地、正确地、经济地建立软件,部分可以利用对 象,来复用软件组件;

C#语言程序设计

10

第1章 Visual C#简介
? C ? C++ ? Java ? Visual C#发展历史
?Java
? Sun于1991年开始的一个内部公司研究项目:开发一个基于C++ 的语言,即Java; ? Sun于1995年正式推出Java,由于Java可用在Web页面中增 加动态内容(即交互性、动画等),因此一经推出,立刻引起 了企业界的兴趣;

C#语言程序设计

11

第1章 Visual C#简介
? C ? C++ ? Java ? Visual C#发展历史
?C#
? 2000年微软推出;由Anders Hejlsberg和Scott Wilramuth领导 的小组开发; ? 为什么要开发C#呢?

C#语言程序设计

12

第1章 Visual C#简介
? OOP(面向对象编程)技术
?对象(object)
? 对象技术是一种包装机制,可以创建有意义的软件单元; ? 对象具有属性(property或attribute); ? 对象要进行操作(也称为行为或方法); ? 类(class)是一类相关对象。类指定对象的一般格式,对象 的属性与操作取决于所属的类;

C#语言程序设计

13

第1章 Visual C#简介
? .NET框架与CLR(公共语言运行环境)
Web Server Application Desktop Application

Visual C#
Visal Basic

ASP.NET

Win Forms (Windows UI)

Visual C++ Visaul J#

Web Service

Web Forms (Web UI) ADO.NET (Data Access) Base Class Library …

XML



Common Language Runtime(CLR) Visual Studio .NET
14

图1.1 .NET框架的组成
C#语言程序设计

第1章 Visual C#简介
? Visual Studio IDE介绍
?VS IDE界面介绍 ?VS IDE可视化编程程序示例

C#语言程序设计

15

第1章 Visual C#简介
? 小节
?本章专业术语
? CLR(Common Language Runtime)公共语言运行环境 ? GUI(Graphical User Interface)图形用户界面 ? IDE(Integrated Development Environment)集成开发环境 ? OOP(Object-Oriented Programming)面向对象编程

?练习题
? 练习把工具箱中的控件放到窗体上,熟悉每个控件的模样。
? ①记事本GUI;②日历与提醒GUI ? ③计算器GUI;④闹钟GUI ? ⑤收音机GUI
C#语言程序设计 16

第1章结束

返回

C#语言程序设计

17

第2章 C#简单程序编写
? 本章目录
?代码遍历—一个C#控制台程序
? 程序解读 ? C#程序创建

?一个Windows程序示例

C#语言程序设计

18

第2章 C#简单程序编写
? 代码遍历—一个C#控制台程序

C#语言程序设计

19

第2章 C#简单程序编写
? 程序解读
?注释:用来说明程序和提高程序的可读性;
1. 单行注释语句 // 2. 界定注释可以跨多行
/* This is a delimited comment. It can be split over many lines */

?using指令(注意大小写):帮助编译器找到程序使用 的类;
? C#有丰富的预定义类,这些类组织成名字空间(namespace ,即相关类的集合),.NET的名字空间统称为.NET的框架类 库(FCL); ? 对使用的每个新.NET类,要指定类所在的名字空间;
C#语言程序设计 20

第2章 C#简单程序编写
? 程序解读
?用户定义类
? 每个程序至少要有一个用户定义类(即程序员定义的类); ? 注意:class关键字要小写,C#中的关键字(保留字)都必须 小写;但所有类名都是以大写字母开头(即Pascal规则);

?Main方法
? Main后面的括号是个程序块,被称为方法; ? 在每个程序,类中有一个Main方法,否则这个程序无法执行;

C#语言程序设计

21

第2章 C#简单程序编写
? 程序解读
?Console类
? Console类提供标准输入/输出功能,使程序可以在执行时读取 和现实文本;
Console.WriteLine("Welcome to C# Programming!"); Console.Read(); ? WriteLine方法用来在控制台输出变量; ? Read方法用来在控制台输入信息; ? ; 是每个语句结束的符号;

C#语言程序设计

22

第2章 C#简单程序编写
? C#程序创建
?IDE的代码颜色模式
? IDE使用过的代码颜色模式为语法颜色高亮度显示,有助于区 别不同程序元素;例如,关键字显示为蓝色,注释文本显示为 绿色;

?行号
? 利于程序阅读和修改; ? Tools?Options对话框?Text Editor?Line Numbers

C#语言程序设计

23

第2章 C#简单程序编写
? C#程序创建 ?代码缩进
? 将代码缩进设置为每层3格,也可以使用Tab键;

?程序文件名
? 程序默认的文件名是Program.cs,可以在属性窗口中修改;

?IntelliSense特性
? IntelliSense(智能感应)可在输入程序时列出类的成员和方法 名,方便输入;

C#语言程序设计

24

第2章 C#简单程序编写
? C#程序创建 ?程序保存、编译与运行
? F5:启动调试

?常见编程错误
? 注意留意常见的错误; ? 一个错误可能产生多个错误消息;

C#语言程序设计

25

第2章 C#简单程序编写
? C#程序创建 ?良好编程习惯
? 习惯上,类名应该以大写字母开头,其中每个单词首字母用大 写; ? 习惯上,包含一个公用类的文件命名时应于类同名,包括拼写 和大小写,这样命名有助于程序员确定程序的类所在的位置; ? 在分隔类体的左右括号之间将内容缩进一级,这种格式有利于 显示类声明结构,增加可读性; ? 设置缩进量,采用一致的习惯; ? 和类一样,在定义方法时体的左右括号之间将每个方法声明的 整体缩进一层;

C#语言程序设计

26

第2章 C#简单程序编写
? 一个Windows程序示例

C#语言程序设计

27

第2章 C#简单程序编写
? 小节
?本章专业术语
? .NET FCL(.NET Framework Class Library).NET框架类库

?练习题
? 按照例子2.1编写并运行控制台程序 ? 将例子2.1的代码故意“破坏”,如缺括号或将关键字拼写错 误,看看编译器产生语法错误的消息,并分析错误消息产生的 原因。

C#语言程序设计

28

第2章 结束

返回

C#语言程序设计

29

第3章 C#程序设计基础
? 本章目录
?标识符 ?C#中数据类型 ?C#中的变量和常量 ?结构类型和枚举类型 ?C#运算符和表达式 ?C#中常用类和结构

C#语言程序设计

30

3.1 标识符
C#的标识符名称必须遵守以下规则: (1)所有的标识符只能由字母、数字和下划线这3类字符 组成,且第一个字符必须为字母或下划线。 (2)标识符中不能包含空格、标点符号、运算符等其他 符号。 (3)标识符严格区分大小写。 (4)标识符不能与C#关键字名相同。 (5)标识符不能与C#中的类库名相同。 a12_c √ 1abc ?

3.2 C#中数据类型
sbyte byte short ushort int uint long ulong floate double decimal

整数类型 字符类型 简单类型 值类型 结构类型 实数类型 枚举类型 数据类型 类 委托 数组 接口 布尔类型

引用类型

C#中数据类型分类图

3.2.1值类型 值类型的变量内含变量值本身,C#的值类型可以分为 简单类型、结构类型和枚举类型。下面仅介绍简单类型。
1. 整数类型
类型标识符 sbyte byte short ushort int uint long ulong 说明 带符号字节型 无符号字节型 带符号短整型 无符号短整型 带符号整型 无符号整型 带符号长整型 无符号长整型 占用位数 8 8 16 16 32 32 64 64 -128~127 0~255 -32,768~32,767 0~65,535 -2,147,483,648~2,147,483,647 0~4,294,967,295 -9,223,372,036,854,775,808~ 9,223,372,036,854,775,807 0~18,446,744,073,709,551,615 取值范围 sbyte i=10 ; byte i=10 ; short i=10 ; ushort i=10 ; int i=10 ; uint i=10 ; uint i=10U ; long i=10 ; long i=10L ; ulong i=16 ; ulong i=16U ; ulong i=16L ; ulong i=16UL ; 示例

2. 实数类型
类型标识符 float double decimal 说明 单精度浮点数 双精度浮点数 固定精度的浮点数 取值范围 示例 float f=1.23F ; double d=1.23 ; decimal d=1.23M ;

〒1.5〓10-45 ~3.4〓1038,精度为7 位数 〒5.0〓10-324 ~1.7〓10308,精度 为15到16位数
1.0〓10-28到~7.9〓1028的之间, 精度为28至29位有效数字

3 字符类型 例如,可以采用如下方式字符变量赋值:
char c='H' ; char c='\x0048'; char c='\u0048'; char c=?\r?; // 字符H // 字符H,十六进制转义符(前缀为\x) // 字符H,Unicode表示形式(前缀为\u) // 回车,转义字符

在表示一个字符常数时,单引号内的有效字符数量必须且只能 是一个,而且不能是单引号或者反斜杠(\)。

4. 布尔类型 布尔类型数据用于表示逻辑真和逻辑假,布尔类型的 类型标识符是bool。

3.2.2 引用类型 引用类型也称为参考类型。和值类型相比,引用类 型的变量不直接存储所包含的值,而是指向它所要存储 的值。类似C中的指针。 1. object类 object是C#中所有类型(包括所有的值类型和引用类型) 的基类,C#中的所有类型都直接或间接地从object类中继承 而来。因此,对一个object的变量可以赋予任何类型的值。
float f=1.23; object obj1; obj1=f; object obj2="China"; //定义obj1对象

//定义obj2对象并赋初值

2. string类 C#还定义了一个string类,表示一个Unicode字符序列,专门 用于对字符串的操作。同样,这个类也是在.NET Framework的 命名空间System中定义的,是类System.String的别名。 字符串在实际中应用非常广泛,利用string类中封装的各种内部 操作,可以很容易完成对字符串处理。例如:
string str1="123"+"abc"; //"+"运算符用于连接字符串 char c="Hello World!"[2]; //"[]"运算符可以访问string中的单个字符,c='e' string str2="China"; string str3=@"China"; // 字符串的另一种表示形式,用@引起来。 bool b=(str2==str3); //"=="运算符用于两个字符串比较,b=true

3.2.3 类型转换 数据类型在一定条件下是可以相互转换的,如将int型数据 转换成double型数据。C#允许使用两种转换的方式:隐式转换 和显式转换。 1. 隐式转换 隐式转换是系统默认的、不需要加以声明就可以进行的转换。
源类型 sbyte byte short 目标类型 short、int、long、float、double、decimal short、ushort、int、uint、long、ulong、float、double、decimal int、long、float、double、decimal

ushort
int uint long ulong char float

int、uint、long、ulong、float、double、decimal
long、float、double、decimal long、ulong、float、double、decimal float、double、decimal float、double、decimal ushort、int、uint、long、ulong、float、double、decimal double

2. 显式转换 显式转换又叫强制类型转换,与隐式转换相反,显式转换 需要用户明确地指定转换类型,一般在不存在该类型的隐式 转换时才使用。格式如下:
(类型标识符)表达式

其作用是将“表达式”值的类型转换为“类型标识符”的 类型。例如:
(int)1.23 //把double类型的1.23转换成int类型,结果为1

需要提醒注意以下几点: (1)显式转换可能会导致错误。进行这种转换时编译器将 对转换进行溢出检测。如果有溢出说明转换失败,就表明源 类型不是一个合法的目标类型,转换就无法进行。 (2)对于从float、double、decimal到整型数据的转换,将 通过舍入得到最接近的整型值,如果这个整型值超出目标类 型的范围,则出现转换异常。

【例3.1】 设计一个控制台程序说明类型转换的应用。
using System; namespace Proj3_1 { class Program { static void Main(string[] args) { int i=65,i1,i2; double d = 66.3456,d1,d2; char c = 'A',c1,c2; Console.WriteLine("i={0:d5},d={1:f},c={2}", i, d, c); i1 = (int)d; //强制类型转换 d1 = i; //隐式类型转换 c1 = (char)i; //强制类型转换 Console.WriteLine("i1={0:d5},d1={1:f},c1={2}", i1, d1, c1); i2 = c; //隐式类型转换 d2 = (int)d; //强制类型转换,转换成整数后再隐式转为double类型 c2 = (char)d; //强制类型转换 Console.WriteLine("i2={0:d5},d2={1:f},c2={2}", i2, d2, c2); } } }

3.2.4 装箱和拆箱 1. 装箱转换 装箱转换是指将一个值类型的数据隐式地转换成一个对象 类型的数据。例如,下面语句就执行了装箱转换:
int i=8; object obj=i;


object obj=(object)i;

2. 拆箱转换 拆箱转换是指将一个对象类型的数据显式地转换成一个 值类型数据。例如,下面语句就执行了拆箱转换:
object obj=2; int i=(int)obj;

拆箱转换需要(而且必须)执行显式转换,这是它与装箱 转换的不同之处。

3.3 C#中的变量和常量
3.3.1 变量 1. 变量定义 在C#程序里使用某个变量之前,必须要告诉编译器它 是一个什么样的变量,因此要对变量进行定义。定义变量 的方法如下:
[访问修饰符] 数据类型 变量名 [= 初始值];

例如:
string name="王华"; int age=20;

也可以同时声明一个或多个给定类型的变量,例如:
int a=1,b=2,c=3;

2. 理解值类型的变量 如果一个变量的值是普通的类型,那么这个C#变量就 是值类型的变量。值类型的变量直接把值存放在变量名标 记的存储位臵上。 当定义一个值类型变量并且给它赋值的时候,这个变 量只能存储相同类型的数据。所以,一个int类型的变量就 只能存放int类型的数据。另外,当把值赋给某个值类型的 变量时,C#会首先创建这个值的一个拷贝,然后把这个拷 贝放在变量名所标记的存储位臵上。 例如:
int x; int y=2; x=y;

3. 理解引用类型的变量 引用表示所使用的是变量或对象的地址而不是变量或 对象本身。当声明引用类型变量时,程序只是分配了存 放这个引用的存储空间。要想创建对象并把对象的存储 地址赋给该变量,就需要使用new操作符。例如:
MyClass var; var=new MyClass(); //MyClass是已定义的类或类型

数组示例:
int[] a = new int[3] { 1, 2, 3}; for (int i = 0; i < 3; i++) Console.Write("{0} ",a[i]);

3.3.2 常量 1. 直接常量 直接常量是指把程序中不变的量直接硬编码为数值或 字符串值,例如,以下都是直接常量:
100 1.23e5 //整型直接常量 //浮点型直接常量

2. 符号常量 符号常量是通过关键字const声明的常量,包括常量的 名称和它的值。常量声明的格式如下:
const 数据类型 常量名=初始值;

其中,“常量名”必须是C#的合法标识符,在程序中 通过常量名来访问该常量。“类型标识符”指示了所定 义的常量的数据类型,而“初始值”是所定义的常量的 值。 例如:
const double PI=3.14159265;

3.4 结构类型和枚举类型 3.4.1 结构类型 1. 结构类型的声明 结构类型由若干“成员”组成的。数据成员称为字段, 每个字段都有自已的数据类型。声明结构类型的一般格式 如下: struct 结构类型名称 { [字段访问修饰符] 数据类型 字段1; [字段访问修饰符] 数据类型 字段2; ... [字段访问修饰符] 数据类型 字段n; };

例如,以下声明一个具有姓名和年龄的结构体类型Student:
struct Student { public int xh; public string xm; public string xb; public int nl; public string bh; }; //声明结构类型Student //学号 //姓名 //性别 //年龄 //班号

2. 结构类型变量的定义 声明一个结构类型后,可以定义该结构类型的变量(简 称为结构变量)。定义结构变量的一般格式如下:
结构类型 结构变量;

例如,在前面的结构类型Student声明后,定义它的两 个变量如下:
Student s1,s2;

3. 结构变量的使用 1)访问结构变量字段 访问结构变量字段的一般格式如下:
结构变量名.字段名

例如,s1.xh表示结构变量s1的学号,s2.xm表示结构变量 s2的姓名。 结构体变量的字段可以在程序中单独使用,与普通变量完 全相同。 2)结构变量的赋值 结构变量的赋值有两种方式。 结构变量的字段赋值:使用方法与普通变量相同。 结构变量之间赋值:要求赋值的两个结构变量必须类型相 同。例如:
s1=s2;

这样s2的所有字段值赋给s1的对应字段。

【例3.2】 设计一个控制台程序说明结构类型的应用。
using System; namespace Proj3_2 { class Program { struct Student { public int xh; public string xm; public string xb; public int nl; public string bh; }

//结构类型声明应放在Main函数的外面 //学号 //姓名 //性别 //年龄 //班号

static void Main(string[] args) { Student s1,s2; //定义两个结构类型变量 s1.xh = 101; s1.xm = "李明"; s1.xb = "男"; s1.nl = 20; s1.bh = "07001"; Console.WriteLine("学号:{0},姓名:{1},性别:{2},年龄:{3}, 班号:{4}", s1.xh, s1.xm, s1.xb, s1.nl, s1.bh); s2 = s1; //将结构变量s1赋给s2 s2.xh = 108; s2.xm = "王华"; Console.WriteLine("学号:{0},姓名:{1},性别:{2},年龄:{3}, 班号:{4}", s2.xh, s2.xm, s2.xb, s2.nl, s2.bh); } } }

3.4.2 枚举类型 枚举类型也是一种自定义数据类型,它允许用符号代表数 据。枚举是指程序中某个变量具有一组确定的值,通过 “枚举”可以将其值一一列出来。 1. 枚举类型的声明 枚举类型使用enum关键字声明,其一般语法形式如下:
enum 枚举名 {枚举成员1,枚举成员2,?}

其中,enum是结构类型的关键字。例如,以下声明一个名 称为color的表示颜色的枚举类型:
enum Color {Red,Green,Blue,White,Black}

在声明枚举类型后,可以通过枚举名来访问枚举成员,其 使用语法如下:
枚举名.枚举成员

2. 枚举成员的赋值 在声明的枚举类型中,每一个枚举成员都有一个相对 应的常量值,默认情况下C#规定第1个枚举成员的值取0, 它后面的每一个枚举成员的值按加1递增。例如,前面 Color中,Red值为0,Green值为1,Blue值为2,依次类 推。 可以为一个或多个枚举成员赋整型值,当某个枚举成 员赋值后,其后的枚举成员没有赋值的话,自动在前一个 枚举成员值之上加1作为其值。例如:
enum Color { Red=0, Green, Blue=3, White, Black=1};

则这些枚举成员的值分别为0、1、3、4、1。

3. 枚举类型变量的定义 声明一个枚举类型后,可以定义该枚举类型的变量(简 称为枚举变量)。定义枚举变量的一般格式如下:
枚举类型 枚举变量;

例如,在前面的枚举类型Color声明后,定义它的两个变 量如下:
Color c1,c2;

1)枚举变量的赋值 枚举变量赋值的语法格式如下:
枚举变量=枚举名.枚举成员

例如:
c1=Color.Red;

2)枚举变量的访问 枚举变量像普通变量一样直接访问。

3.5 C#运算符和表达式
3.5.1 算术运算符
符号 + ? * / % ++ ?? 意义 加法运算 减法/取负运算 乘法运算 除法运算 取余数 累加 递减 示例 a+b a?b a*b a/b a%b a++ a? ?

3.5.2 字符串运算符 字符串运算符只有一个,就是加号(+)。它除了作 为算术运算符之外,还可以将字符串连接起来,变成合 并的新字符串。例如:
string s="Hello"; s=s+", World."; Console.WriteLine(s); //输出:Hello, World.

3.5.3 赋值运算符 赋值运算符(=)用于改变变量的值,它先求出右侧表达 式的结果,然后再将结果赋给左侧的变量。
符号 += 意义 加赋值 示例 a+=b等价于a=a+b

-=
*= /= %= <<= >>= &= ^= |=

减赋值
乘赋值 除赋值 取模赋值 左移赋值 右移赋值 与赋值 异或赋值 或赋值

a-=b等价于a=a-b
a*=b等价于a=a*b a/=b等价于a=a/b a%=b等价于a=a%b a<<=b等价于a=a<<b a>>=b等价于a=a>>b a&=b等价于a=a&b a^=b等价于a=a^b a|=b等价于a=a|b

3.5.4 关系运算符
符号
< <= >

意义
小于 小于等于 大于

示例
2<3为true 2<=3为true 2>3为false

>=
== !=

大于等于
等于 不等于

2>=3为false
2==3为false 2!=3为true

3.5.5 逻辑运算符
符号 ! && || 意义 逻辑非 逻辑与 逻辑或 示例 !(2<3)为false (3<5)&&(5>4)为true (3<5)||(5>4)为true

3.5.6 位运算符
符号 ~ << >> & ^ | 意义 按位求反 左移 右移 按位与 按位异或 按位或 示例 !2 8<<2 8>>2 8&5 8^5 8|5

3.5.7 条件运算符 条件运算符是一个三元运算符,每个操作数同时又是表 达式的值。由条件运算符构成的表达式称为条件表达式。 条件运算符的使用格式如下:
表达式1 ? 表达式2 : 表达式3

它的计算方式为,先计算“表达式1”(必须为布尔值) 的值,如果其值true,则“表达式2”的值作为整个表达式 的最终结果;否则“表达式3”的值作为整个表达式的值。 例如,以下表达式返回a和b中的最大值:
max=a>b ? a : b

计算过程是,当a>b,max= a; 否则max=b。

3.5.9 运算符的优先级 运算符的优先级是指在表达式中哪一个运算符应该首先 计算。 C#根据运算符的优先级确定表达式的求值顺序:优先级 高的运算先做,优先级低的操作后做,相同优先级的操作 从左到右依次做,同时用小括号控制运算顺序,任何在小 括号内的运算最优先进行。

3.6 C#中常用类和结构
C#中一切都是对象 。

3.6.1 String类 string类型是.NET Framework中的String类的别名。string 类型定义了相等运算符(==和!=)用于比较两个string对象, 叧外,+运算符用于连接字符串,[]运算符可以用来访问 string中的各个字符。
属性 Chars Length 说明 获取此字符串中位于指定字符位臵的字符。 获取此字符串中的字符数。

方法 Compare CompareTo Concat Contains Equals Format IndexOf Insert Remove Replace Split Substring ToLower ToUpper Trim

说明 静态方法。比较两个指定的String对象 非静态方法。将此字符串与指定的对象或String进行比较,并返回两者相对值的 指示 静态方法。连接String的一个或多个字符串 非静态方法。返回一个值,该值指示指定的String对象是否出现在此字符串中 非静态方法。确定两个String对象是否具有相同的值 静态方法。将指定的String中的每个格式项替换为相应对象的值的文本等效项 非静态方法。返回String或一个或多个字符在此字符串中的第一个匹配项的索引 非静态方法。在该String中的指定索引位臵插入一个指定的String 非静态方法。从该String中删除指定个数的字符 非静态方法。将该String中的指定String的所有匹配项替换为其他指定的 String 非静态方法。返回包含该String中的子字符串(由指定Char或String数组的元素 分隔)的String数组 非静态方法。从此字符串中检索子字符串 非静态方法。返回该String转换为小写形式的副本 非静态方法。返回该String转换为大写形式的副本 非静态方法。从此字符串的开始位臵和末尾移除一组指定字符的所有匹配项

【例3.7】 设计一个控制台程序求用户输入的子串在主串中位臵。
using System; namespace Proj3_6 { class Program { static void Main(string[] args) { String mstr,sstr; Console.Write("输入主串:"); mstr = Console.ReadLine(); Console.Write("输入子串:"); sstr = Console.ReadLine(); Console.WriteLine("主串长度={0},子串长度={1}", mstr.Length, sstr.Length); if (String.Compare(mstr, sstr) != 0) //使用静态方法 Console.WriteLine("位臵:{0}", mstr.IndexOf(sstr)); else Console.WriteLine("两个字符串相同"); } } }

3.6.2 Math类 Math类位于System命名空间中,它包含了实现C#中 常用算术运算功能的方法,这些方法都是静态方法,可通 过“Math.方法名(参数)”来使用。

3.6.3 Convert类 Convert类位于System命名空间中,用于将一个值类 型转换成另一个值类型。这些方法都是静态方法,可通 过“Convert.方法名(参数)”来使用。
ToBoolean ToDataTime ToInt16 ToInt32 ToInt64 ToNumber 将数据转换成Boolean类型 将数据转换成日期时间类型 将数据转换成16位整数类型 将数据转换成32位整数类型 将数据转换成64位整数类型 将数据转换成Double类型

ToObject
ToString

将数据转换成Object类型
将数据转换成string类型

3.6.4 DateTime结构 DateTime结构类位于System命名空间中,DateTime值 类型表示值范围在公元0001年1月1日午夜12:00:00到公元 9999年12月31日晚上11:59:59之间的日期和时间。 可以通过以下语法格式定义一个日期时间变量:
DateTime 日期时间变量 = new DateTime(年,月,日,时,分,秒);

例如,以下语句定义了2个日期时间变量:
DateTime d1 = new DateTime(2009,10,1); DateTime d2 = new DateTime(2009,10,1,8,15,20);

其中,d1的值为2009年10月1日零点零分零秒,d2的值 为2009年10月1日8点15分20秒。

属性 Date

说明 获取此实例的日期部分

Day
DayOfWeek DayOfYear Hour Millisecond Minute Month Now Second TimeOfDay Today Year

获取此实例所表示的日期为该月中的第几天
获取此实例所表示的日期是星期几 获取此实例所表示的日期是该年中的第几天 获取此实例所表示日期的小时部分 获取此实例所表示日期的毫秒部分 获取此实例所表示日期的分钟部分 获取此实例所表示日期的月份部分 获取一个DateTime对象,该对象设臵为此计算机上的当前日期和时间, 表示为本地时间 获取此实例所表示日期的秒部分 获取此实例的当天的时间 获取当前日期 获取此实例所表示日期的年份部分

方法 AddDays AddHours AddMilliseconds AddMinutes AddMonths

说明 非静态方法。将指定的天数加到此实例的值上 非静态方法。将指定的小时数加到此实例的值上 非静态方法。将指定的毫秒数加到此实例的值上 非静态方法。将指定的分钟数加到此实例的值上 非静态方法。将指定的月份数加到此实例的值上

AddSeconds
AddYears Compare CompareTo DaysInMonth IsLeapYear Parse

非静态方法。将指定的秒数加到此实例的值上
非静态方法。将指定的年份数加到此实例的值上 静态方法。比较DateTime的两个实例,并返回它们相对值的指示 非静态方法。将此实例与指定的对象或值类型进行比较,并返回二者相 对值的指示 静态方法。返回指定年和月中的天数 静态方法。返回指定的年份是否为闰年的指示 静态方法。将日期和时间的指定字符串表示转换成其等效的DateTime

【例3.8】 设计一个控制台程序说明DataTime结构的使用。
using System; namespace Proj3_7 { class Program { static void Main(string[] args) { DateTime d1 = DateTime.Now; //定义当前日期时间变量 DateTime d2 = new DateTime(2009, 10, 1); //定义一个日期时间变量 Console.WriteLine("d1:{0}",d1); int i = d1.Year; //求d1的年 int j = d1.Month; //求d1的月 int k = d1.Day; //求d1的日 int h = d1.Hour; //求d1的时 int m = d1.Minute; //求d1的分 int s = d1.Second; //求d1的秒

Console.WriteLine("d1:{0}年{1}月{2}日{3}时{4}分{5}秒", i,j,k,h,m,s); Console.WriteLine("d2:{0}",d2); Console.WriteLine("相距时间:{0}",d2 - d1); DateTime d3 = d1.AddDays(100); //d3为d1的100天后的日期 Console.WriteLine("d3:{0}",d3); Console.WriteLine(DateTime.IsLeapYear(i)); //静态方法调用 Console.WriteLine(DateTime.IsLeapYear(d2.Year));

}
} }

第3章 C#程序设计基础
? 小节
?本章专业术语 ?练习题
? 课后练习

C#语言程序设计

77

第3章 结束

返回

C#语言程序设计

78

第4章 C#控制语句
? 本章目录
?选择语句 ?循环语句 ?跳转语句

C#语言程序设计

79

4.1 选择控制语句
4.1.1 if语句 基本语法格式如下:
if (条件表达式) 语句;

其中,“条件表达式”是一个关系表达式或逻辑表达 式,当“条件表达式”为true时,执行后面的“语句”。

4.1.2 if…else语句 语法形式如下:
if (条件表达式) 语句1; else 语句2;

其中的“条件表达式”是一个关系表达式或逻辑表达 式。当“条件表达式”为true时执行“语句1”;当“条件 表达式”为false时执行“语句2”。

4.1.3 if…else if语句 if…else if语句用于进行多重判断,其语法形式如下:
if (条件表达式1) 语句1; else if (条件表达式2) 语句2; … else if (条件表达式n) 语句n; else 语句n+1;
条件 表达式1 false 条件 表达式2 false


true

语句1

true

语句2

false

条件 表达式n
false 语句n+1

true

语句n

【例4.4】 编写一个程序,将用户输入的分数转换成等级。
using System; namespace Proj4_4 { class Program { static void Main(string[] args) { float x; Console.Write("分数:"); x=float.Parse(Console.ReadLine()); if (x>=90) Console.WriteLine("等级为A"); else if (x >= 80) Console.WriteLine("等级为B"); else if (x >= 70) Console.WriteLine("等级为C"); else if (x >= 60) Console.WriteLine("等级为D"); else Console.WriteLine("等级为E"); } } }

4.1.4 switch语句 switch语句也称为开关语句,用于有多重选择的场合测 试某一个变量具有多个值时所执行的动作。switch语句的 语法形式为:
switch (表达式) { case 常量表达式1: 语句1; case 常量表达式2: 语句2; … case 常量表达式n: 语句n; default:语句n+1; }

true 表达式=常量表达式1 false 表达式=常量表达式2
┇ ┇

语句1
true

语句2

false false

表达式=常量表达式n

true

语句n

false

语句n+1

【例4.5】 编写一个程序,要求输入课程后显示相应的 学分:数学(代号为m,8学分)、物理(代号为p,5学 分)、化学(代号为c,5学分)、语文(代号为w,8学 分)、英语(代号为e,6学分)。

using System; namespace Proj4_5 { class Program { static void Main(string[] args) { char ch; Console.Write("课程代号:"); ch=(char)Console.Read(); switch (ch) { case 'm': case 'M': case 'w': case 'W': Console.WriteLine("8学分"); break; case 'p': case 'P': case 'c': case 'C': Console.WriteLine("5学分"); break; case 'e': case 'E': Console.WriteLine("6学分"); break; default: Console.WriteLine("输入的课程代号不正确"); break; } } } }

4.2 循环控制语句
4.2.1 while语句 while语句的一般语法格式如下:
while (条件表达式) 语句;
while
false

条件 表达式
true

语句

【例4.6】 编写一个程序,将用户输入的整数反向显示出来。
using System; namespace Proj4_6 { class Program { static void Main(string[] args) { int digit,num; Console.Write( "输入一个整数:"); num=int.Parse(Console.ReadLine()); Console.Write( "反向显示结果:"); while (num!=0) { digit=num % 10; //依次求个位、十位、…上的数字digit num=num / 10; Console.Write(digit); } Console.WriteLine(); } } }

4.2.2 do-while语句 do-while语句的一般语法格式如下:
do 语句; while (条件表达式);
do-while

语句 false

条件 表达式 true

【例4.7】 采用do-while语句重新编写例4.6的程序。
using System; namespace Proj4_7 { class Program { static void Main(string[] args) { int digit,num; Console.Write("输入一个整数:"); num=int.Parse(Console.ReadLine()); Console.Write("反向显示结果:"); do { digit=num % 10; num=num/10; Console.Write(digit); } while (num!=0); Console.WriteLine(); } } }

4.2.3 for语句 for语句通常用于预先知道循环次数的情况,其一般 语法格式如下:
for (表达式1;表达式2;表达式3) 语句;
for 表达式1
false

表达式2
true

语句 表达式3

【例4.8】 编写一个程序,输出如图4.12所示的乘法表。
using System; namespace Proj4_8 { class Program { static void Main(string[] args) { int i,j; for (i = 1; i <= 9; i++) { for (j = 1; j <= i; j++) Console.Write("{0}〓{1}={2} ", i, j, i * j); Console.WriteLine(); } } } }

4.3 跳 转 语 句
4.3.1 break语句 break语句使程序从当前的循环语句(do、while和 for)内跳转出来,接着执行循环语句后面的语句。

【例4.9】 编写一个程序,判断从键盘输入的大于3的正整 数是否为素数。
using System; namespace Proj4_9 { class Program { static void Main(string[] args) { int n, i; bool prime = true; Console.Write("输入一个大于3的正整数:"); n = int.Parse(Console.ReadLine()); for (i = 3; i <= Math.Sqrt(n); i++) if (n % i == 0) { prime = false; break; } if (prime) Console.WriteLine("{0}是素数", n); else Console.WriteLine("{0}不是素数", n); } } }

4.3.2 continue语句 continue语句也用于循环语句,它类似于break,但它不 是结束循环,而是结束循环语句的当前一次循环,接着执 行下一次循环。 在while和do-while循环结构中,执行控制权转至对“条件 表达式”的判断,在for结构中,转去执行“表达式2”。 【例4.10】 编写一个程序,对用户输入的所有正数求和, 如果输入的是负数,则忽略该数。程序每读入一个数,判 断它的正负,如果为负,则利用continue语句结束当前一次 循环,继续下一次循环,否则将该数加到总数上去。

using System; namespace Proj4_10 { class Program { static void Main(string[] args) { int sum=0,n=1; while (n!=0) //循环 { Console.Write("输入一个整数(以0表示结束):"); n=int.Parse(Console.ReadLine()); if (n<0) continue; //开始下一次循环 sum+=n; } Console.WriteLine("所有正数之和={0}",sum); } } }

4.3.3 goto语句 goto语句也可以跳出循环和switch语句。goto语句用于 无条件转移程序的执行控制,它总是与一个标号相匹配, 其形式为:
goto 标号;

“标号”是一个用户自定义的标识符,它可以处于goto 语句的前面,也可以处于其后面,但是标号必须与goto语 句处于同一个函数中。定义标号时,由一个标识符后面跟 一冒号组成。 通常不要使用goto语句!

第4章 C#控制语句
? 小节
?本章专业术语 ?练习题
? 课后练习

C#语言程序设计

99

第4章 结束

返回

C#语言程序设计

100

第5章 数组和集合
? 本章目录
?一维数组 ?二维数组 ?Array类 ?交错数组 ?ArrayList类 ?List<T>类

C#语言程序设计

101

5.1 一维数组
5.1.1 一维数组的定义 定义一维数组的语法格式如下:
数组类型[] 数组名;

例如,以下定义了3个一维数组,即整型数组a、双精 度数组b和字符串数组c。
int[] a; double[] b; string[] c;

在定义数组后,必须对其进行初始化才能使用。初始 化数组有两种方法:动态初始化和静态初始化。

5.1.2 一维数组的动态初始化 动态初始化需要借助new运算符,为数组元素分配内存空间, 并为数组元素赋初值,数值类型初始化为0,布尔类型初始化 为false,字符串类型初始化为null。 动态初始化数组的格式如下: 其中,“数组类型”是数组中数据元素的数据类型,n为 “数组长度”,可以是整型常量或变量,后面一层大括号里为 初始值部分。 1. 不给定初始值的情况 如果不给出初始值部分,各元素取默认值。例如:
int[] a = new int[10];
数组类型[] 数组名=new 数据类型[n]{元素值0,元素值1,?,元素值n-1};

该数组在内存中各数组元素均取默认值0。

2. 给定初始值的情况 如果给出初始值部分,各元素取相应的初值,而且 给出的初值个数与“数组长度”相等。此时可以省略 “数组长度”,因为后面的大括号中已列出了数组中的 全部元素。例如:
int[] a = new int[10]{1,2,3,4,5,6,7,8,9,10};


int[] a = new int[]{1,2,3,4,5,6,7,8,9,10};

在这种情况下,不允许“数组长度”为变量,例如:
int n = 5; //定义变量n int[] myarr = new int[n] {1,2,3,4,5}; //错误

如果给出“数组长度”,则初始值的个数应与“数组长 度”相等,否则出错。例如:
int[] mya = new int[2] {1,2}; int[] mya = new int[2] {1,2,3}; int[] mya = new int[2] {1}; //正确 //错误 //错误

5.1.3 一维数组的静态初始化 静态初始化数组时,必须与数组定义结合在一起,否则 会出错。静态初始化数组的格式如下: 用这种方法对数组进行初始化时,无需说明数组元素的 个数,只需按顺序列出数组中的全部元素即可,系统会自 动计算并分配数组所需的内存空间。 例如,以下是对整型数组myarr的静态初始化:
int[] myarr={1,2,3,4,5}; 数据类型[] 数组名={元素值0,元素值1,?,元素值n-1};

在这种情况下,不能将数组定义和静态初始化分开,例 如,以下是错误的。
int[] myarr; myarr={1,2,3,4,5}; //错误的数组的静态初始化

5.1.4 访问一维数组中的元素 访问一维数组中的某个元素:名称[下标或索引]。 所有元素下标从0开始,到数组长度减1为止。例如,以 下语句输出数组myarr的所有元素值:
for (i=0;i<5;i++) Console.Write("{0} ",a[i]); Console.WriteLine();

C#还提供foreach语句。该语句提供一种简单、明了的方 法来循环访问数组的元素。例如,以下代码定义一个名称 为mya的数组,并用foreach语句循环访问该数组。
int[] mya = {1,2,3,4,5,6}; foreach (int i in mya) System.Console.Write("{0} ",i); Console.WriteLine();

输出为:1 2 3 4 5 6。

5.1.5 一组数组的越界 若有如下语句定义并初始化数组ca:
int[] ca = new int[10]{1,2,3,4,5,6,7,8,7,9,10};

数组ca的合法下标为0~9,如果程序中使用ca[10]或 ca[50],则超过了数组规定的下标,因此越界了。C#系统会 提示以下出错信息。
未处理的异常: Syatem.IndexOutOfRangeException:索引超出了数组 界限。

【例5.1】设计一个控制台应用程序,采用二分查找方法在给 定的有序数组a中查找用户输入的值,并提示相应的查找结果。
using System; namespace Proj5_1 { class Program { static void Main(string[] args) { double [] a=new double[10]{0,1.2,2.5,3.1,4.6,5.0,6.7,7.6,8.2,9.8}; double k; int low=0,high=9,mid; Console.Write("k:"); k=double.Parse(Console.ReadLine()); while (low<=high) { mid=(low+high)/2; if (a[mid] == k) { Console.WriteLine("a[{0}]={1}", mid, k); return; //返回图5.3 例5.1程序运行结果 } else if (a[mid] > k) high = mid - 1; else low = mid + 1; } Console.WriteLine("未找到{0}",k); } } }

5.2 二维数组
5.2.1 二维数组的定义 定义二维数组的语法格式如下:
数组类型[,] 数组名;

其中,“数据类型”为C#中合法的数据类型, “数组名”为C#中合法的标识符。 例如,以下语句定义了3个二维数组,即整型数组x、 双精度数组y和字符串数组z。
int[,] x; double[,] y; string[,] z;

对于多维数组,可以作类似的推广,例如,以下语 句定义了一个三维数组p。
int[,,] p;

5.2.2 二维数组的动态初始化 动态初始化二维数组的格式如下:
数据类型[,] 数组名=new 数据类型[m][n]{ {元素值0,0,元素值0,1,?,元素值0,n-1}, {元素值1,0,元素值1,1,?,元素值1,n-1}, ? {元素值m-1,0,元素值m-1,1,?,元素值m-1,n-1} };

其中,“数组类型”是数组中数据元素的数据类型, m、n分别为行数和列数,即各维的长度,可以是整型常 量或变量。

1. 不给定初始值的情况 如果不给出初始值部分,各元素取默认值。例如:
int[,] x = new int[2][3];

该数组各数组元素均取默认值0。

2. 给定初始值的情况 如果给出初始值部分,各元素取相应的初值,而且给出 的初值个数与对应的“数组长度”相等。此时可以省略 “数组长度”,因为后面的大括号中已列出了数组中的全 部元素。例如:
int[,] x = new int[2][3]{{1,2,3},{4,5,6}};


int[,] x = new int[,]{{1,2,3},{4,5,6}};

5.2.3 二维数组的静态初始化 静态初始化数组时,必须与数组定义结合在一起,否则 会出错。静态初始化数组的格式如下:
数据类型[,] 数组名={{元素值0,0,元素值0,1,?,元素值0,n-1}, {元素值1,0,元素值1,1,?,元素值1,n-1}, ? {元素值m-1,0,元素值m-1,1,?,元素值m-1,n-1}}; int[,] myarr={{1,2,3},{4,5,6}};

例如,以下语句是对整型数组myarr的静态初始化。

5.2.4 访问二维数组中的元素 为了访问二维数组中的某个元素,需指定数组名称和数组中该元 素的行下标和列下标。例如,以下语句输出数组myarr的所有元素 值。
for (i=0;i<2;i++) for (j=0;j<3;j++ Console.Write("{0} ",myarr[i,j]); Console.WriteLine();

对于多维数组,也可以使用foreach语句来循环访问每一个元素, 例如。
int[,] myb = new int[3, 2] { {1, 2}, {3,4}, {5,6}}; foreach (int i in myb) Console.Write("{0} ", i); Console.WriteLine();

其输出为:1 2 3 4 5 6。

【例5.2】 设计一个控制台应用程序,输出九行杨辉三角形。
using System; namespace Proj5_2 { class Program { const int N=10; static void Main(string[] args) { int i,j; int[,] a=new int[N,N]; for (i=1;i<N;i++) //1列和对角线元素均为1 { a[i,i]=1; a[i,1]=1; } for (i=3;i<N;i++) //求第3~N行的元素值 for (j=2;j<=i-1;j++) a[i,j]=a[i-1,j-1]+a[i-1,j]; for (i=1;i<N;i++) //输出数序 { for (j=1;j<=i;j++) Console.Write("{0,-2} ",a[i,j]); Console.WriteLine(); } }}}

5.3 Array类
Array类是所有数组类型的抽象基类型。
属性 Length LongLength 说明 获得一个32位整数,该整数表示Array的所有维数中元素的总数。 获得一个64位整数,该整数表示Array的所有维数中元素的总数。

Rank

获取Array的秩(维数)。

方法

说明

BinarySearch
Copy

静态方法。使用二进制搜索算法在一维的排序Array中搜索值。
静态方法。将一个Array的一部分元素复制到另一个Array中,并根据 需要执行类型强制转换和装箱。

CopyTo
Find

非静态方法。将当前一维Array的所有元素复制到指定的一维Array中。
静态方法。搜索与指定谓词定义的条件匹配的元素,然后返回整个 Array中的第一个匹配项。

ForEach
GetLength

静态方法。对指定数组的每个元素执行指定操作。
非静态方法。获取一个32位整数,该整数表示Array的指定维中的元素 数。

GetLongLength
GetLowerBound GetUpperBound GetValue IndexOf

非静态方法。获取一个64位整数,该整数表示Array的指定维中的元素 数。
非静态方法。获取Array中指定维度的下限。 非静态方法。获取Array的指定维度的上限。 非静态方法。获取当前Array中指定元素的值。 静态方法。返回一维Array或部分Array中某个值第一个匹配项的索引。

Resize
Reverse SetValue Sort

静态方法。将数组的大小更改为指定的新大小。
静态方法。反转一维Array或部分Array中元素的顺序。 非静态方法。将当前Array中的指定元素设臵为指定值。 静态方法。对一维Array对象中的元素进行排序。

【例5.3】 设计一个控制台应用程序,产生10个0~19的随 机整数,对其递增排序并输出。
using System; namespace Proj5_3 { class Program { static void Main(string[] args) { int i,k; int[] myarr = new int[10]; //定义一个一维数组 Random randobj = new Random(); //定义一个随机对象 for (i = myarr.GetLowerBound(0); i <= myarr.GetUpperBound(0); i++) { k=randobj.Next() % 20; //返回一个0~19的正整数 myarr.SetValue(k, i); //给数组元素赋值 } Console.Write("随机数序:"); for (i = myarr.GetLowerBound(0); i <=myarr.GetUpperBound(0); i++) Console.Write("{0} ", myarr.GetValue(i)); Console.WriteLine(); Array.Sort(myarr); //数组排序 Console.Write("排序数序:"); for (i = myarr.GetLowerBound(0); i <=myarr.GetUpperBound(0); i++) Console.Write("{0} ", myarr.GetValue(i)); Console.WriteLine(); }}}

5.4 交错数组
交错数组:元素为数组的数组,元素的维度和大小可以不同。 多维数组:元素的维度和大小的均相同。

5.4.1 交错数组的定义和初始化 以下语句定义了一个由3个元素组成的一维数组,其中每 个元素都是一个一维整数数组:
int[][] arrj = new int[3][];

必须初始化arrj的元素后才可以使用它。可以如下所示初始化 该元素:
arrj[0] = new int[5]; arrj[1] = new int[4]; arrj[2] = new int[2];

5.4.2 访问交错数组中的元素 交错数组元素的访问方式与多维数组类似,通常使用 Length方法返回包含在交错数组中的数组的数目,例如, 以下程序定义一个交错数组myarr并初始化,最后输出所 有元素的值。
int[][] myarr = new int[3][]; myarr[0] = new int[] {1,2,3,4,5,6}; myarr[1] = new int[] {7,8,9,10}; myarr[2] = new int[] {11,12}; for (int i = 0; i < myarr.Length; i++) { Console.Write("myarr({0}): ", i); for (int j = 0; j < myarr[i].Length; j++) Console.Write("{0} ", myarr[i][j]); Console.WriteLine(); }

程序运行结果如下:
myarr(0): 1 2 3 4 5 6 myarr(1): 7 8 9 10 myarr(2): 11 12

5.5 ArrayList类
ArrayList类(在命名空间System.Collections中),用于 建立不定长度的数组,由于该类数组的数据类型为Object, 其长度不固定,可以将其对象看成是一个集合。 定义ArrayList类的对象的语法格式如下:
ArrayList 数组名 = new ArrayList();

例如,以下语句定义一个ArrayList类的对象myarr,可以 将它作为一个数组使用:
ArrayList myarr = new ArrayList();

属性

说明

Capacity
Count Item

获取或设臵ArrayList可包含的元素数。
获取ArrayList中实际包含的元素数。 获取或设臵指定索引处的元素。

方法 Add AddRange BinarySearch Clear Clone Contains CopyTo GetRange IndexOf Insert InsertRange LastIndexOf
Remove RemoveAt RemoveRange Reverse SetRange Sort ToArray ToString TrimToSize

说明 将对象添加到ArrayList的结尾处。 将一个ICollection对象的元素添加到ArrayList的末尾。 使用二分检索算法在已排序的ArrayList或它的一部分中查找特定元素。 从ArrayList中移除所有元素。 创建ArrayList的浅表副本。 确定某元素是否在ArrayList中。 将ArrayList或它的一部分复制到一维数组中。 返回 ArrayList,它表示源ArrayList中元素的子集。 返回ArrayList或它的一部分中某个值的第一个匹配项的从零开始的索引。 将元素插入ArrayList的指定索引处。 将集合中的某个元素插入ArrayList的指定索引处。 返回ArrayList或它的一部分中某个值的最后一个匹配项的从零开始的索 引。 从ArrayList中移除特定对象的第一个匹配项。 移除ArrayList的指定索引处的元素。 从ArrayList中移除一定范围的元素。 将ArrayList或它的一部分中元素的顺序反转。 将集合中的元素复制到ArrayList中一定范围的元素上。 对ArrayList或它的一部分中的元素进行排序。 将ArrayList的元素复制到新数组中。 返回表示当前Object的String。 将容量设臵为ArrayList中元素的实际数目。

【例5.4】 定义一个ArrayList对象,用于存放若干个姓名, 对其进行排序,并输出排序后的结果。
using System; using System.Collections; //新增 namespace Proj5_4 { class Program { static void Main(string[] args) { ArrayList myarr = new ArrayList(); myarr.Add("Smith"); myarr.Add("Mary"); myarr.Add("Dava"); myarr.Add("John"); Console.Write("排序前序列:"); foreach(String sname in myarr) Console.Write(sname + " "); Console.WriteLine(); myarr.Sort(); Console.Write("排序前序列:"); foreach(String sname in myarr) Console.Write(sname + " "); Console.WriteLine(); } }}

5.6 List<T>类
List<T>类是ArrayList类的泛型等效类,该类使用大小 可按需动态增加的数组实现IList泛型接口。 定义List<T>类的对象的语法格式如下:
List<T> 数组名 = new List<T>();

例如,以下语句定义一个List<string>类的对象myset, 其元素类型为string,可以将它作为一个数组使用:
List<string> myset = new List<string>();

属性 Capacity Count Item

说明 获取或设臵该内部数据结构在不调整大小的情况下 能够保存的元素总数。 获取List中实际包含的元素数。 获取或设臵指定索引处的元素。

方法 Add AddRange BinarySearch Clear Contains CopyTo Exists Find FindAll FindIndex FindLast

说明 将对象添加到List的结尾处。 将指定集合的元素添加到List的末尾。 使用对分检索算法在已排序的List或它的一部分中查找特定 元素。 从List中移除所有元素。 确定某元素是否在List中。 将List或它的一部分复制到一个数组中。 确定List是否包含与指定谓词所定义的条件相匹配的元素。 搜索与指定谓词所定义的条件相匹配的元素,并返回整个 List中的第一个匹配元素。 检索与指定谓词所定义的条件相匹配的所有元素。 搜索与指定谓词所定义的条件相匹配的元素,返回List或它 的一部分中第一个匹配项的从零开始的索引。 搜索与指定谓词所定义的条件相匹配的元素,并返回整个 List中的最后一个匹配元素。

搜索与指定谓词所定义的条件相匹配的元素,返回List或它的 一部分中最后一个匹配项的从零开始的索引。 ForEach 对List的每个元素执行指定操作。 IndexOf 返回List或它的一部分中某个值的第一个匹配项的从零开始的 索引。 Insert 将元素插入List的指定索引处。 InsertRange 将集合中的某个元素插入List的指定索引处。 LastIndexOf 返回List或它的一部分中某个值的最后一个匹配项的从零开始 的索引。 Remove 从List中移除特定对象的第一个匹配项。 RemoveAll 移除与指定的谓词所定义的条件相匹配的所有元素。 RemoveAt 移除List的指定索引处的元素。 RemoveRange 从List中移除一定范围的元素。 Reverse 将List或它的一部分中元素的顺序反转。 Sort 对List或它的一部分中的元素进行排序。 ToArray 将List的元素复制到新数组中。 TrimExcess 将容量设臵为List中的实际元素数目(如果该数目小于某个阈 值)。 FindLastIndex

【例5.5】 设计一个控制台应用程序,定义一个List<T>对象, 用于添加若干个学生的学号和姓名,输出后再插入一个学生 记录。
using System; namespace Proj5_5 { struct Stud //定义结构类型 { public int sno; //学号 public string sname; //姓名 }; class Program { static void Main(string[] args) { int i; List<Stud> myset = new List<Stud>(); Stud s1 = new Stud(); s1.sno = 101;s1.sname = "李明"; myset.Add(s1); Stud s2 = new Stud(); s2.sno = 103; s2.sname = "王华"; myset.Add(s2);

Stud s3 = new Stud(); s3.sno = 108; s3.sname = "张英"; myset.Add(s3); Stud s4 = new Stud(); s4.sno = 105; s4.sname = "张伟"; myset.Add(s4); Console.WriteLine("元素序列:"); Console.WriteLine(" 下标 学号 姓名"); i = 0; foreach (Stud st in myset) { Console.WriteLine(" {0} {1} {2}", i,st.sno, st.sname); i++; } Console.WriteLine("容量: {0}", myset.Capacity); Console.WriteLine("元素个数: {0}", myset.Count); Console.WriteLine("在索引2处插入一个元素"); Stud s5 = new Stud(); s5.sno = 106; s5.sname = "陈兵";

myset.Insert(2, s5); Console.WriteLine("元素序列:"); Console.WriteLine(" 下标 学号 姓名"); i = 0; foreach (Stud st in myset) { Console.WriteLine(" {0} {1} {2}", i, st.sno, st.sname); i++; } } } }

第5章 数组和集合
? 小节
?本章专业术语 ?练习题
? 课后练习

C#语言程序设计

132

第5章 结束

返回

C#语言程序设计

133

第6章 C#程序设计基础
? 本章目录
?6.1 ?6.2 ?6.3 ?6.4 ?6.5 ?6.6 面向对象程序设计概述 命名空间 类 对象 构造函数和析构函数 静态成员

? 本章目录
?6.7 属性 ?6.8 方法 ?6.9 索引器 ?6.10 委托 ?6.11 事件 ?6.12 运算符重载 ?6.13 类的转换

C#语言程序设计

134

6.1 面向对象程序设计概述
6.1.1 面向对象的基本概念 ? 类和对象 ? 属性、方法和事件 ? 封装 ? 继承 ? 重载与重写

6.1.2 面向对象的优点 ? 维护简单。 ? 可扩充性。 ? 代码重用。

6.2 命名空间
6.2.1 命名空间概述 在.NET中,类是通过命名空间(namespace)来组织的。 命名空间提供了可以将类分成逻辑组的方法,将系统中的大 量类库有序地组织起来,使得类更容易使用和管理。 可以将命名空间想像成文件夹,类的文件夹就是命名空 间,不同的命名空间内,可以定义许多类。在每个命名空间 下,所有的类都是“独立”且“唯一”的。

6.2.2 使用命名空间 在C#中,使用命名空间有两种方式,一种是明确指出命名 空间的位置,另一种是通过using关键字引用命名空间。 直接定位在应用程序中,任何一个命名空间都可以在代码 中直接使用。例如:
System.Console.WriteLine("ABC");

这个语句是调用了System命名空间中Console类的 WriteLine方法。 1)使用using关键字 在应用程序中要使用一个命名空间,还可以采取引用命名 空间的方法,在引用后,应用程序中就可使用该命名空间内的 任一个类了。引用命名空间的方法是利用using关键字,其使 用格式如下:
using [别名=] 命名空间


using [别名=] 命名空间.成员

2)自定义命名空间 在C#中,除了使用系统的命名空间外,还可以在应用程 序中自已声明命名空间。其使用语法格式如下:
namespace 命名空间名称 { 命名空间定义体 }

其中,“命名空间名称”指出命名空间的唯一名称,必须 是有效的C#标识符。例如,在应用程序中自定义Ns1命名 空间:
namespace Ns1 { class A {…} class B {…} }

6.3



6.3.1类的声明 类的声明语法格式如下: [类的修饰符] class 类名 [:基类名] { //类的成员; }[;]
类的修饰符 public protected internal private abstract sealed 说明 公有类。表示不受限制对该类的访问。 保护类。表示只能从所在类和所在类派生的子类进行访问。 内部类。只有其所在类才能访问。 私有类。只有该类才能访问。 抽象类。表示该类是一个不完整的类,不允许建立类的实 例。 密封类。不允许从该类派生新的类。

例如,以下声明了一个Person类:
public class Person { public int pno; //编号 public string pname; //姓名 public void setdata(int no,string name) { pno=no; pname=name; } public void dispdata() { Console.WriteLine("{0} {1}", pno, pname); } }

6.3.2类的成员
类的成员 字段 属性 说明 字段存储类要满足其设计所需要的数据,亦称为数据成员。 属性是类中可以像类中的字段一样被访问的方法。属性可以为类字 段提供保护,避免字段在对象不知道的情况下被更改。

方法

方法定义类可以执行的操作。方法可以接受提供输入数据的参数, 并且可以通过参数返回输出数据。方法还可以不使用参数而直接返 回值。
事件是向其他对象提供有关事件发生(如单击按钮或成功完成某个 方法)通知的一种方式。 索引器允许以类似于数组的方式为对象建立索引。 运算符是对操作数执行运算的术语或符号,如 +、*、< 等。 构造函数是在第一次创建对象时调用的方法。它们通常用于初始化 对象的数据。

事件 索引器 运算符 构造函数

析构函数

析构函数是当对象即将从内存中移除时由运行库执行引擎调用的方 法。它们通常用来确保需要释放的所有资源都得到了适当的处理。

类成员的修饰符

说明

public
private

公有成员。提供了类的外部界面,允许类的使用者 从外部进行访问,这是限制最少的一种访问方式。
私有成员(默认的)。仅限于类中的成员可以访问,从 类的外部访问私有成员是不合法的,如果在声明中 没有出现成员的访问修饰符,按照默认方式成员为 私有的。 保护成员。这类成员不允许外部访问,但允许其派 生类成员访问。 内部成员。允许同一个命名空间中的类访问。 只读成员。这类成员的值只能读,不能写。也就是 说,除了赋予初始值外,在程序的任何部分将无法 更改这个成员的值。

protected internal readonly

6.3.3 分部类 分部类可以将类(结构或接口等)的声明拆分到两个 或多个源文件中。 若要拆分类的代码,被拆分类的每一部分的定义前边 都要用partial关键字修饰。分部类的每一部分都可以存放 在不同的文件中,编译时会自动将所有部分组合起来构成 一个完整的类声明。

6.4 对象
6.4.1 定义类的对象 一旦声明了一个类,就可以用它作为数据类型来定义类对 象(简称为对象)。定义类的对象分以下两步: 1)定义对象引用 其语法格式如下:
类名 对象名;

例如,以下语句定义Person类的对象引用p:
Person p;

2)创建类的实例 其语法格式如下:
对象名=new 类名( );

例如,以下语句创建Person类的对象实例:
p=new Persone();

以上两步也可以合并成一步。其语法格式如下:
类名 对象名=new 类名();

例如:
Person p=new Person();

理解对象引用和类实例的区别。 两个对象引用可以引用同一个对象,例如:
Person p1 = new Person(); Person p2 = p1;

6.4.2 访问对象的字段 访问对象字段的语法格式如下:
对象名.字段名

其中,“.”是一个运算符,该运算符的功能是表示对象的成 员。 例如,前面定义的p对象的成员变量表示为:
p.pno,p.pname

6.4.3 调用对象的方法 调用对象的方法的语法格式如下:
对象名.方法名(参数表)

例如,调用前面定义的p对象的成员方法setdata为:
p.setxy(101,"Mary");

【例6.1】 设计一个控制台应用程序,说明调用对象方法的过程。
using System; namespace Proj6_1 { public class TPoint //声明类TPoint { int x,y; //类的私有字段 public void setpoint(int x1,int y1) { x=x1;y=y1; } public void dispoint() { Console.WriteLine("({0},{1})",x,y); } } class Program { static void Main(string[] args) { TPoint p1 = new TPoint(); // 定义对象p1 p1.setpoint(2,6); Console.Write("第一个点=>"); p1.dispoint(); TPoint p2 = new TPoint(); // 定义对象p2 p2.setpoint(8,3); Console.Write("第二个点=>"); p2.dispoint(); } } }

6.5 构造函数和析构函数
6.5.1构造函数 1. 什么是构造函数 构造函数是在创建给定类型的对象时执行的类方法。构 造函数具有如下性质: ? 构造函数的名称与类的名称相同。 ? 构造函数尽管是一个函数,但没有任何类型,即它既不 属于返回值函数也不属于void函数。 ? 一个类可以有多个构造函数,但所有构造函数的名称都 必须相同,它们的参数各不相同,即构造函数可以重载。 ? 当类对象创建时,构造函数会自动地执行;由于它们没 有返回类型,因此不能像其他函数那样进行调用。 ? 当类对象声明时,调用哪一个构造函数取决于传递给它 的参数类型。 ? 构造函数不能被继承。

2. 调用构造函数 当定义类对象时,构造函数会自动执行。 1)调用默认构造函数 不带参数的构造函数称为默认构造函数。无论何时,只要 使用new运算符实例化对象,并且不为new提供任何参数, 就会调用默认构造函数。假设一个类包含有默认构造函数, 调用默认构造函数的语法如下:
类名 对象名=new 类名();

如果没有为对象提供构造函数,则默认情况下 C#将创建 一个构造函数,该构造函数实例化对象,并将所有成员变量 设置为相应的默认值。 2)调用带参数的构造函数 假设一个类中包含有带参数的构造函数,调用这种带参数 的构造函数的语法如下:
类名 对象名=new 类名(参数表);

【例6.2】 设计一个控制台应用程序,说明调用构造函数的过程。
namespace Proj6_2 { class Program { public class TPoint1 //声明类TPoint1 { int x, y; //类的私有变量 public TPoint1() { } //默认的构造函数 public TPoint1(int x1, int y1) //带参数的构造函数 { x = x1; y = y1; } public void dispoint() { Console.WriteLine("({0},{1})", x, y); } } static void Main(string[] args) { TPoint1 p1 = new TPoint1(); //调用默认的构造函数 Console.Write("第一个点=>"); p1.dispoint(); TPoint1 p2 = new TPoint1(8, 3);//调用带参数的构造函数 Console.Write("第二个点=>"); p2.dispoint(); } }}

6.5.2析构函数 1. 什么是析构函数 在对象不再需要时,希望确保它所占的存储空间能被 收回。C#中提供了析构函数用于专门释放被占用的系统资 源。析构函数具有如下性质: ? 析构函数在类对象销毁时自动执行。 ? 一个类只能有一个析构函数,而且析构函数没有参数, 即析构函数不能重载。 ? 析构函数的名称是“~”加上类的名称(中间没有空格)。 ? 与构造函数一样,析构函数也没有返回类型。 ? 析构函数不能被继承。

2. 调用析构函数 当一个对象被系统销毁时自动调用类的析构函数。

【例6.3】 设计一个控制台应用程序,说明调用析构函数的过程。
using System; namespace Proj6_3 { class Program { public class TPoint2 //声明类TPoint2 { int x, y; public TPoint2(int x1, int y1) //带参数的构造函数 { x = x1; y = y1; } ~TPoint2() { Console.WriteLine("点=>({0},{1})", x, y); } } static void Main(string[] args) { TPoint2 p1 = new TPoint2(2,6); TPoint2 p2 = new TPoint2(8, 3); } } }

6.6 静态成员
6.6.1 静态字段 静态字段是类中所有对象共享的成员,而不是某个对 象的成员,也就是说静态字段的存储空间不是放在每个对 象中,而是和方法一样放在类公共区中。 对静态字段的操作和一般字段一样,定义为私有的静 态字段不能被外界访问。静态字段的使用方法如下: (1)静态字段的定义与一般字段相似,但前面要加上 static关键词。 (2)在访问静态字段时采用如下格式:
类名.静态字段名

6.6.2 静态方法 静态方法与静态字段类似,也是从属于类,都是类的静 态成员。只要类存在,静态方法就可以使用,静态方法的 定义是在一般方法定义前加上static关键字。调用静态方 法的格式如下:
类名.静态方法名(参数表);

注意:静态方法只能访问静态字段、其他静态方法和类 以外的函数及数据,不能访问类中的非静态成员(因为非 静态成员只有对象存在时才有意义)。但静态字段和静态 方法可由任意访问权限许可的成员访问。

6.7





6.7.1什么是属性 属性描述了对象的具体特性,它提供了对类或对象成员 的访问。 C#中的属性更充分地体现了对象的封装性,属性不直接 操作类的字段,而是通过访问器进行访问。

6.7.2 属性声明 属性在类模块里是采用下面的方式进行声明的,即指定 变量的访问级别、属性的类型、属性的名称,然后是get访 问器或者set访问器代码块。其语法格式如下:
修饰符 数据类型 属性名称 { get访问器 set访问器 }

其中,修饰符有new、public、protected、internal、 private、static、virtual、override和abstract。

【例6.7】 设计一个控制台应用程序,说明属性的使用。
using System; namespace Proj6_7 { public class TPoint3 //声明类TPoint3 { int x,y; public int px { get //get访问器 点=>(3,8) { return x; } set //set访问器 { x = value; } } public int py { get //get访问器 { return y; } set //set访问器 { y = value; } } }; class Program { static void Main(string[] args) { TPoint3 p = new TPoint3(); p.px = 3; p.py = 8; //属性写操作 Console.WriteLine("点=>({0},{1})", p.px, p.py);//属性读操作 } } }

6.8 方法
6.8.1 什么是方法 方法包含一系列的代码块。从本质上来讲,方法就是 和类相关联的动作,是类的外部界面。 用户可以通过外部界面来操作类的私有字段。

6.8.2 方法的定义 定义方法的基本格式如下:
修饰符 返回类型 方法名(参数列表) { //方法的具体实现; }

6.8.3 方法的返回值 方法可以向调用方返回某一个特定的值。如果返回类型 不是void ,则该方法可以用return关键字来返回值,return 还可以用来停止方法的执行。 例如,以下类MyClass3中的addnum方法用return关键 字来返回值:
public class MyClass3 { int num=0; public int addnum(int num1) { return num + num1; } }

6.8.4 方法的参数 方法中的参数是保证不同的方法间互动的重要桥梁,方 便用户对数据的操作。C#中方法的参数有4种类型。 1. 值参数 不含任何修饰符,当利用值向方法传递参数时,编译 程序给实参的值做一份拷贝,并且将此拷贝传递给该方法, 被调用的方法不会修改内存中实参的值,所以使用值参数 时可以保证实际值的安全性。在调用方法时,如果形参的 类型是值参数的话,调用的实参的表达式必须保证是正确 的值表达式。 例如,前面MyClass3类中addnum方法中的参数就是 值参数。

2. 引用型参数 以ref修饰符声明的参数属引用型参数。引用型参数本身 并不创建新的存储空间,而是将实参的存储地址传递给形 参,所以对形参的修改会影响原来实参的值。在调用方法 前,引用型实参必须被初始化,同时在调用方法时,对应 引用型参数的实参也必须使用ref修饰。 例如:

public class MyClass4 { int num=0; public void addnum(int num1,ref int num2) { num2=num + num1; } } class Program { static void Main(string[] args) { int x=0; MyClass4 s = new MyClass4(); s.addnum(5, ref x); Console.WriteLine(x); //输出:5 } }

3. 输出参数 以out修饰符声明的参数属输出参数。与引用型参数类 似,输出型参数也不开辟新的内存区域。它与引用型参数 的差别在于,调用方法前无需对变量进行初始化。输出型 参数用于传递方法返回的数据,out修饰符后应跟随与形 参的类型相同的类型,用来声明在方法返回后传递的变量 经过了初始化。 例如:

public class MyClass5 { int num=0; public void addnum(int num1,out int num2) { num2=num + num1; } } class Program { static void Main(string[] args) { int x; MyClass5 s = new MyClass5(); s.addnum(5, out x); Console.WriteLine(x); //输出:5 } }

4. 数组型参数 以params修饰符声明的参数属数组型参数。params 关键字可以指定在参数数目可变处采用参数的方法参数。 在方法声明中的params关键字之后不允许任何其他参数, 并且在方法声明中只允许一个params关键字。有数组型 参数就不能再有ref 和out修饰符。 例如:

public class MyClass6 { int num=10; public void addnum(ref int sum,params int[] b) { sum = num; foreach (int item in b) sum += item; } } class Program { static void Main(string[] args) { int[] a = new int[3] { 1, 2, 3 }; int x = 0; MyClass6 s = new MyClass6(); s.addnum(ref x,a); Console.WriteLine(x); } }

6.8.5 方法的重载 方法的重载是指调用同一方法名,但是使用不同的数据 类型参数或者次序不一致的参数。只要一个类中有两个以 上的同名方法,且使用的参数类型或者个数不同,编译器 就可以判断在哪种情况下调用哪种方法了。 为此,C#中引入了成员签名的概念。成员签名包含成 员的名称和参数列表,每个成员签名在类型中必须是唯一 的,只要成员的参数列表不同,成员的名称可以相同。如 果同一个类有两个或多个这样的成员(方法、属性、构造 函数等),它们具有相同的名称和不同的参数列表,则称 该同类成员进行了重载,但它们的成员签名是不同的。 例如,下面的代码实现了MethodTest方法的重载(假 设都是某个类的成员),它们是不同的成员签名:

public int MethodTest(int i,int j) { //代码 } public int MethodTest(int i) { //代码 } public string MethodTest(string sr) { //代码 }

//重载方法1

//重载方法2

//重载方法3

6.9 索引器
6.9.1 什么是索引器 索引器提供了一种访问类或结构的方法,即允许按照 与数组相同的方式对类、结构或接口进行索引。它的引入 是为了使程序更加直观、易于理解。 例如,可以有一个大学名称类University,其中有一个 name数组字段可能包含一些大学名称,un是该类的一个 对象,类中索引器允许访问这些大学名称,例如:
un[0] = "清华大学"; un[1] = "北京大学"; un[3] = "武汉大学";

6.9.2 定义索引器 要声明类或结构上的索引器,需使用this关键字,其语 法格式如下:
public int this[int index] { // get和set访问器 } //索引器声明

其中,this关键字引用类的当前实例。从中看到,索引 器像对普通属性一样,为它提供get和set方法,这些访问 器指定当使用该索引器时将引用到什么内部成员。 例如,带有索引器的University类设计如下:

public class University { const int MAX = 5; private string[] name = new string[MAX]; public string this[int index] //索引器 { get { if (index >= 0 && index < MAX) return name[index]; else return name[0]; } set { if (index >= 0 && index < MAX) name[index] = value; } } }

6.10

委 托

6.10.1 什么是委托 C++、Pascal和其他语言支持函数指针的概念,允许在 运行时选择要调用的函数。Java不提供任何具有函数指针 功能的结构,但C#提供这种构造。通过使用Delegate类, 委托实例可以封装属于可调用实体的方法。 委托具有以下特点: ? 委托类似于C++函数指针,但它是类型安全的。 ? 委托允许将方法作为参数进行传递。 ? 委托可用于定义回调方法。 ? 委托可以链接在一起。例如,可以对一个事件调用多个 方法。 ? 方法不需要与委托签名精确匹配。

6.10.2 定义和使用委托 定义和使用委托有3个步骤,即声明、实例化和调用。 1. 声明委托类型 声明委托类型就是告诉编译器这种类型代表了哪种类型 的方法。使用以下语法声明委托类型:
[修饰符] delegate 返回类型 委托类型名(参数列表);

在声明一个委托类型时,每个委托类型都描述参数的数 目和类型,以及它可以引用的方法的返回类型。每当需要 一组新的参数类型或新的返回类型时,都必须声明一个新 的委托类型。 例如:
private delegate void mydelegate(int n);

上述代码声明了一个委托mydelegate,该委托类型可以 引用一个采用int作为参数并返回void的方法。

2. 实例化委托 声明了委托类型后,必须创建一个它的实例,即创建 委托对象并使之与特定方法关联。定义委托对象的语法 格式如下:
委托类型名 委托对象名;

例如,以下语句创建了mydelegate委托的一个委托对 象p:
mydelegate p;

另外,委托对象还需实例化为调用的方法,通常将这些方 法放在一个类中(也可以将这些方法放在程序的Program类 中),假设一个MyDeClass类如下:
class MyDeClass { public void fun1(int n) { Console.WriteLine("{0}的2倍={1}",n,2*n); } public void fun2(int n) { Console.WriteLine("{0}的3倍={1}", n, 3 * n); } }

可以通过以下语句实例化委托对象p:
MyDeClass obj = new MyDeClass(); mydelegate p = new mydelegate(obj.fun1);

其中,MyDeClass类中的fun1方法有一个int形参,其 返回类型为void,它必须与mydelegate类型的声明相一致。

3. 调用委托 创建委托对象后,通常将委托对象传递给将调用该委 托的其他代码。通过委托对象的名称(后面跟着要传递 给委托的参数,放在括号内)调用委托对象。其使用语 法格式如下:
委托对象名(实参列表);

例如,以下语句调用委托p:
p(100);

委托对象是不可变的,即设置与它们匹配的签名后就 不能再更改签名了。但是,如果其他方法具有同一签名, 也可以指向该方法。例如:
MyDeClass obj = new MyDeClass(); mydelegate p = new mydelegate(obj.fun1); p(5); p = new mydelegate(obj.fun2); p(3);

【例6.9】 设计一个控制台应用程序,说明委托的使用。
using System; namespace Proj6_9 { delegate double mydelegate(double x,double y); //声明委托类型 class MyDeClass { public double add(double x, double y) { return x+y; } public double sub(double x,double y) { return x-y; } public double mul(double x,double y) { return x*y; } public double div(double x,double y) { return x/y; } }

class Program { static void Main(string[] args) { MyDeClass obj = new MyDeClass(); mydelegate p = new mydelegate(obj.add); Console.WriteLine("5+8={0}",p(5,8)); p = new mydelegate(obj.sub); Console.WriteLine("5-8={0}",p(5,8)); p = new mydelegate(obj.mul); Console.WriteLine("5*8={0}",p(5,8)); p = new mydelegate(obj.div); Console.WriteLine("5/8={0}",p(5,8)); } } }

前面代码中p作为引用类型,也可以改为值类型,等价 的主函数可以如下改为:
static void Main(string[] args) { MyDeClass obj = new MyDeClass(); mydelegate p = obj.add; Console.WriteLine("5+8={0}", p(5, 8)); p = obj.sub; Console.WriteLine("5-8={0}", p(5, 8)); p = obj.mul; Console.WriteLine("5*8={0}", p(5, 8)); p = obj.div; Console.WriteLine("5/8={0}", p(5, 8)); }

6.10.3 委托对象封装多个方法 委托对象可以封装多个方法,这些方法的集合称为调 用列表。委托使用“+”、“-”、“+=”和“-=”等运算符向调 用列表中增加或移除事件处理方法。

【例6.10】 设计一个控制台应用程序,说明委托对象封 装多个方法的使用。
using System; namespace Proj6_10 { delegate void mydelegate(double x, double y); //声明委托类型 class MyDeClass { public void add(double x, double y) { Console.WriteLine("{0}+{1}={2}",x,y,x + y); } public void sub(double x, double y) { Console.WriteLine("{0}-{1}={2}", x, y, x - y); } public void mul(double x, double y) { Console.WriteLine("{0}*{1}={2}", x, y, x * y); } public void div(double x, double y) { Console.WriteLine("{0}/{1}={2}", x, y, x/y); } }

class Program { static void Main(string[] args) { MyDeClass obj = new MyDeClass(); mydelegate p, a; a = obj.add; p = a; //将add方法添加到调用列表中 a = obj.sub; p += a; //将sub方法添加到调用列表中 a = obj.mul; p += a; //将mul方法添加到调用列表中 a = obj.div; p += a; //将div方法添加到调用列表中 p(5, 8); } }
}

6.10.4 使委托与匿名方法关联 所谓匿名方法就是没有方法名称的方法。当将委托与匿名 方法关联时,直接给出方法的函数体,其一般语法格式如下:
delegate 返回类型 委托类型名(参数列表); 委托类型名 委托对象名=返回类型(参数列表) { /*匿名方法代码*/ }; 托对象名(实参列表)

第1个语句声明委托类型;第2个语句定义匿名方法并将其 与委托对象关联;第3个语句调用委托。

例如,以下程序段就是使委托与匿名方法关联,并调用 该委托:
delegate void mydelegate(string mystr); //声明委托类型 class Program { static void Main(string[] args) { mydelegate p = delegate(string mystr) { Console.WriteLine(mystr); }; p("String"); //输出:String }
}

6.11 事



6.11.1 什么是事件 事件是类在发生其关注的事情时用来提供通知的一种方式。 例如,封装用户界面控件的类可以定义一个在用户单击时候 发生的一个事件。控件类不关心单击按钮时候发生了什么, 但是它需要告知派生类单击事件已经发生,然后,派生类可 以选择如何响应。 当发生与某个对象相关的事件时,类和结构会使用事件将 这一个对象通知给用户。这种通知即为引发事件。引发事件 的对象称为事件的源或者发送者。对象引发事件的原因很多, 如响应时丢失网络连接就会引发一个事件。表示用户界面元 素的对象通常会引发事件来响应用户的操作,如按钮单击或 者菜单选择。

6.11.2 事件的创建和使用 下面介绍在C#中创建和使用事件的步骤。 1. 为事件创建一个委托类型 所有事件是通过委托来激活的,其返回值类型一般为 void型。为事件创建一个委托类型的语法格式如下: delegate void 委托类型名([触发事件的对象名,事件参数]); 例如,以下语句创建一个委托类型mydelegate,其委托 的事件处理方法返回类型为void,不带任何参数:
public delegate void mydelegate();

2. 创建事件处理的方法 当事件触发时要调用事件处理方法,需设计相应的事件 处理方法,可以将它放在单独的类中,也可以放在触发事 件的类中。 例如,以下设计一个包含事件处理方法的单独类
class MyEventHander { public void OnHandler1() { Console.WriteLine("调用OnHandler1方法"); }

}

3. 声明事件 事件是类成员,以关键字event声明,其一般语法格式如下:
[修饰符] event 委托类型名 事件名;

其中,“修饰符”指出类的用户访问事件的方式,可以为 public 、private、protected、internal、protectedinternal、 static或virtual等。 一般在声明事件的类中包含触发事件的方法。例如,以下 MyEvent类包含事件声明和触发该事件的方法:
MyEvent //事件类 { public event mydelegate1 Event1; //声明事件 public void FireEvent1() //调用这个方法来触发事件Event1 { if(Event1 != null) { Event1(); //Event1事件发生 } } }

4. 通过委托对象来调用被包含的方法 向类事件(列表)中添加事件处理方法中的一个委托, 这个过程称为订阅事件,这个过程通常是在主程序中进行的, 首先必须声明一个包含事件的类的对象,然后将事件处理方 法和该对象关联起来,其格式如下:
事件类对象名.事件名+=new 委托类型名(事件处理方法);

其中,还可以使用“-=”、“+”、“-”等运算符添加或删除 事件处理方法。最后调用触发事件的方法便可触发事件。 例如,以下语句就是触发前面创建的事件Event1,并在屏 幕上显示“调用OnHandler1方法”:
MyEvent b = new MyEvent(); MyEventHander a = new MyEventHander(); b.Event1 += new mydelegate1(a.OnHandler1); //把方法OnHandler1添加到事件列表中 b.FireEvent1(); //调用触发事件的方法

【例6.11】 设计一个控制台应用程序,说明事件的使用。
using System; namespace Proj6_11 { public delegate void mydelegate(int c, int n); //声明一个事件委托类型 public class Shape { protected int color; public event mydelegate ColorChange; //声明一个事件 public int pcolor //定义属性 { set { int ocolor = color; //保存原来的颜色 color = value; ColorChange(ocolor, color); //在color的值发生改变后,触发事件。 } get { return color; } } public Shape() //构造函数 { color = 0; } }

class Program { static void Main(string[] argvs) { Shape obj = new Shape(); obj.ColorChange += new mydelegate(CCHandler); //订阅事件 obj.pcolor = 3; //改变颜色触发事件 } static void CCHandler(int c, int n)//事件处理方法 { Console.WriteLine("颜色从{0}改变为{1}", c, n); } }

}

执行结果:

颜色从0改变为3

6.12 运算符重载
6.12.1 运算符重载概述 运算符重载是指同名运算符可用于运算不同类型的数 据。C#允许重载运算符,以供自己的类使用。其目的是让 使用类对象像使用基本数据类型一样自然、合理。 例如,设计一个名称为MyAdd的类,其中对“+”运算 符进行了重载,这样对于该类的两个对象a和b,就可以进 行a+b的运算。 若要重载某个运算符,需要编写一个函数,其基本语 法格式如下:
public static 返回类型 operator 运算符(参数列表) { ? }

所有运算符重载均为类的静态方法。在C#中不是所有 的运算符都允许重载,以下是可重载的运算符的完整列表: ? 一元运算符:+、-、!、~、++、--、true、false。 ? 二元运算符:+、-、*、/、%、&、|、^、<<、>>、 ==、!=、>、<、>=、<=。 此外还应注意,重载相等运算符 (==) 时,还必须重载 不相等运算符 (!=)。< 和 > 运算符以及 <= 和 >= 运算符也 必须成对重载。

6.12.2 一元运算符重载 一元运算符重载时需注意以下几点: ? 一元运算符+、-、!、~ 必须使用类型T的单个参数,可以返 回任何类型。 ? 一元运算符++、--必须使用类型T的单个参数,并且要返回 类型T。 ? 一元运算符true、false必须使用类型T的单个参数,并且要 返回类型bool。 例如,设计如下类MyOp,其中对一元运算符“++”进行了重 载:
class MyOp { private int n; public MyOp(int n1) { n = n1;} public static MyOp operator ++(MyOp obj) { return new MyOp(obj.n+1); } public void dispdata() { Console.WriteLine("n={0}",n); } }

执行以下语句:
MyOp a=new MyOp(2); a++; a.dispdata();

由于类MyOp中对运算符“++”进行了重载,所以可以 执行a++语句,其功能是将a的私有字段n增1。上述语句 的输出结果为:n=3。

6.12.3 二元运算符重载 一个二元运算符必须有两个参数,而且其中至少一个 必须是声明运算符的类或结构的类型。二元运算符可以返 回任何类型。 二元运算符的签名由运算符符号和两个形式参数组成。 例如,设计如下类MyOp1,其中对二元运算符“+”进行了 重载:

class MyOp1 { private int n; public MyOp1() {} public MyOp1(int n1) { n = n1;} public static MyOp1 operator +(MyOp1 obj1,MyOp1 obj2) { return new MyOp1(obj1.n+obj2.n); } public void dispdata() { Console.WriteLine("n={0}",n); } }

执行以下语句:
MyOp1 a=new MyOp1(2),b=new MyOp1(3),c; c=a+b; c.dispdata();

6.13 类的转换
6.13.1 关键字is is是一个检查引用变量的类型的运算符,在第3章中介 绍过,它用来检查对象是否与给定数据类型兼容。如果所提 供的表达式非空,并且所提供的对象可以强制转换为所提供 的类型而不会导致引发异常,则is表达式的计算结果将是 true。 如果已知表达式将始终是true或者false,则is关键字将导 致编译时警告,但是,通常在运行时才计算类型兼容性。 is运算符不能重载。

例如:
if (o is A) //如果o属A类类型 { Console.WriteLine("o属A类类型"); a = (A)o; //强制转换为a对象引用 a.funa(); } else if (o is B) //如果o属B类类型 { Console.WriteLine("o属B类类型"); b = (B)o; //强制转换为b对象引用 b.funb(); } else { Console.WriteLine("o不属于A和B类类型"); }

6.13.2 关键字as as用于在兼容的引用类型之间执行转换。例如:
string s = someObject as string;

as运算符类似于强制转换,所不同的是,当转换失败时, 运算符将产生空(null),而不是引发异常。as的语法格式 如下:
表达式 as 数据类型

等效于
表达式 is 数据类型 ? (数据类型)表达式 : (数据类型)null

其中,“表达式”只被计算一次。 注意:as运算符只执行引用转换和装箱转换。as运算符 无法执行其他转换,如用户定义的转换,这类转换应使用 cast表达式来执行。 例如,以下语句将obj对象转换成string时返回null:
MyClass obj = new MyClass(); string s = obj as string; //s为null

例如:
a = b as A; //将b强制转换为A对象a

第6章 面向对象程序设计
? 小节
?本章专业术语 ?练习题
? 课后练习

C#语言程序设计

207

第6章 结束

返回

C#语言程序设计

208

第7章 继承和接口设计
? 本章目录
?继 承 ?多态性 ?抽象类 ?接口 ?接口在集合排序中的应用 ?泛型编程

C#语言程序设计

209

7.1 继 承
7.1.1什么是继承 一个类从另一个类派生出来时,称之为派生类或子类, 被派生的类称为基类或父类。 派生类从基类那里继承特性,派生类也可以作为其他 类的基类,从一个基类派生出来的多层类形成了类的层次 结构。

C#中的继承具有以下特点:
? C#中只允许单继承,即一个派生类只能有一个基类。 ? C#中继承是可传递的,如果C从B派生,B从A派生,那么C不仅继承B 的成员,还继承A的成员。 ? C#中派生类可添加新成员,但不能删除基类的成员。 ? C#中派生类不能继承基类的构造函数和析构函数,但能继承基类的属 性。 ? C#中派生类可隐藏基类的同名成员,如果在派生类可以隐藏了基类的 同名成员,基类该成员在派生类中就不能被直接访问,只能通过“base. 基类方法名”来访问。 ? C#中派生类对象也是基类的对象,但基类对象却不一定是基派生类的 对象。也就是说,基类的引用变量可以引用基派生类对象,而派生类的 引用变量不可以引用基类对象。

7.1.2 派生类的声明 派生类的声明格式如下:
[类修饰符] class 派生类:基类;

C#中派生类可以从它的基类中继承字段、属性、方法、 事件、索引器等。 实际上除了构造函数和析构函数,派生类隐式地继承了 基类的所有成员。

class A { private int n; protected int m; public void afun() { //方法的代码 } } class B : A { private int x; public void bfun() { //方法的代码 } }
B b = new B(); b.afun();

//私有字段 //保护的字段 //公有方法

//私有字段 //公有方法

从中看出Base_fun()方法 在B类中不用重写,因为B 类继承了A类,所以可以 不用重写A类中的 Base_fun()方法,就可以 被B类调用。

在主函数中包含以下代码:
//定义对象并实例化

7.1.3

基类成员的可访问性

派生类将获取基类的所有非私有数据和行为。 如果希望在派生类中隐藏某些基类的成员,可以在基类 中将这些成员设为private访问成员。

7.1.4 按次序调用构造函数和析构函数

1. 调用默认构造函数的次序 如果类是从一个基类派生出来的,那么在调用这个派 生类的默认构造函数之前会调用基类的默认构造函数。 调用的次序将从最远的基类开始。

class A //基类 { public A() { Console.WriteLine("调用类A的构造函数");} } class B : A //从A派生类B { public B() { Console.WriteLine("调用类B的构造函数"); } } class C:B //从B派生类C { public C() { Console.WriteLine("调用类C的构造函数"); } }

在主函数中执行以下语句:
C b=new C(); //定义对象并实例化

运行结果如下:
调用类A的构造函数 调用类B的构造函数 调用类C的构造函数

2. 调用默认析构函数的次序
当销毁对象时,它会按照相反的顺序来调用析构函数。 首先调用派生类的析构函数,然后是最近基类的析构函数, 最后才调用那个最远的析构函数。

class A //基类 { ~A() { Console.WriteLine("调用类A的析构函数");} } class B : A //从A派生类B { ~B() { Console.WriteLine("调用类B的析构函数"); } } class C:B //从B派生类C { ~C() { Console.WriteLine("调用类C的析构函数"); } }

在主函数中执行语句C b=new C();其运行结果如下:
调用类C的析构函数 调用类B的析构函数 调用类A的析构函数

3. 调用重载构造函数的次序 调用基类的重载构造函数需使用base关键字。base关键 字主要是为派生类调用基类成员提供一个简写的方法,可 以在子类中使用base关键字访问的基类成员。调用基类中 重载构造函数的方法是将派生类的重载构造函数作如下设 计:
public 派生类名(参数列表1):base(参数列表2) { ? }

其中,“参数列表2”和“参数列表1”存在对应关系。 同样,在通过“参数列表1”创建派生类的实例对象时, 先以“参数列表2”调用基类的构造函数,再调用派生类的 构造函数。

【例7.1】 分析以下程序的运行结果。
using System; namespace Proj7_1 { class A { private int x; public A() { Console.WriteLine("调用类A的构造函数");} public A(int x1) { x = x1; Console.WriteLine("调用类A的重载构造函数"); } ~A() { Console.WriteLine("A:x={0}", x); } } class B : A { private int y; public B() { Console.WriteLine("调用类B的构造函数"); } public B(int x1,int y1):base(x1) { y = y1; Console.WriteLine("调用类B的重载构造函数"); } ~B() { Console.WriteLine("B:y={0}", y); } }

class C:B { private int z; public C() { Console.WriteLine("调用类C的构造函数"); } public C(int x1,int y1,int z1):base(x1,y1) { z = z1; Console.WriteLine("调用类C的重载构造函数"); } ~C() { Console.WriteLine("C:z={0}", z); } } class Program { static void Main(string[] args) { C c=new C(1,2,3); } }

}

7.1.5 使用sealed修饰符来禁止继承 C#中提供了sealed关键字用来禁止继承。要禁止继承 一个类,只需要在声明类时加上sealed关键字就可以了, 这样的类称为密封类。例如:
sealed class 类名 { ? }

这样就不能从该类派生任何子类。

7.2 多态性
7.2.1 什么是多态性
面向对象程序设计中的多态性是一个重要的概念。 所谓多态性,就是同一签名具有不同的表现行为,运算 符重载和函数重载都属于多态性的表现形式。

7.2.2 隐藏基类方法 当派生类从基类继承时,它会获得基类的所有方法、 字段、属性和事件。若要更改基类的数据和行为,有两种 选择:
?方法1:使用新的派生成员替换基成员 ?方法2:重写虚拟的基成员。

方法1示例:在使用新的派生方法替换基方法时应使用 new关键字。例如:
class A { public void fun() { Console.WriteLine("A"); } } class B:A { new public void fun() //隐藏基类方法fun { Console.WriteLine("B"); } }

在主函数中执行以下语句:
B b=new B(); b.fun();

运行结果如下:
B

7.2.3 重写

重写是指在子类中编写有相同名称和参数的方法。 重写和重载的区别:后者是指编写(在同一个类中) 具有相同的名称,却有不同的参数的方法。也就是说, 重写是指子类中的方法与基类中的方法具有相同的签名, 而重载方法具有不同的签名。

1. virtual关键字 virtual关键字用于修饰方法、属性、索引器或事件声 明,并且允许在派生类中重写这些对象。例如,以下定义 了一个虚拟方法并可被任何继承它的类重写:
public virtual double Area() { return x * y; }

调用虚方法时,首先调用派生类中的该重写成员,如 果没有派生类重写该成员,则它可能是原始成员。 注意:默认情况下,方法是非虚拟的,不能重写非虚 方法。virtual修饰符不能与static、abstract和override修 饰符一起使用。在静态属性上使用virtual修饰符是错误的。

2. 重写方法 override方法提供从基类继承的成员的新实现。通过 override声明重写的方法称为重写基方法。重写的基方法 必须与override方法具有相同的签名。 注意:不能重写非虚方法或静态方法。重写的基方法 必须是virtual、abstract或override的。

【例7.2】 分析以下程序的运行结果。
using System; namespace Proj7_2 { class Student { protected int no; //学号 protected string name; //姓名 protected string tname; //班主任或指导教师 public void setdata(int no1, string name1,string tname1) { no = no1; name = name1;tname=tname1; } public virtual void dispdata() //虚方法 { Console.WriteLine("本科生 学号:{0} 姓名:{1} 班 主 任:{2}", no,name,tname); } }

class Graduate : Student { public override void dispdata() //重写方法 { Console.WriteLine("研究生 学号:{0} 姓名:{1} 指导教师:{2}", no, name, tname); } } class Program { static void Main(string[] args) { Student s = new Student(); s.setdata(101, "王华","李量"); s.dispdata(); Graduate g = new Graduate(); g.setdata(201,"张华","陈军"); g.dispdata(); } }
}

【例7.3】 设计一个控制台应用程序,采用虚方法求长方形、 圆、圆球体和圆柱体的面积或表面积。
using System; namespace Proj7_3 { public class Rectangle //长方形类 { public const double PI = Math.PI; protected double x, y; public Rectangle() {} public Rectangle(double x1, double y1) { x = x1;y = y1; } public virtual double Area() //求面积 { return x * y; } }

public class Circle : Rectangle //圆类 { public Circle(double r): base(r, 0) { } public override double Area() //求面积 { return PI * x * x; } } class Sphere : Rectangle //圆球体类 { public Sphere(double r): base(r, 0) {} public override double Area() //求面积 { return 4 * PI * x * x; } } class Cylinder : Rectangle //圆柱体类 { public Cylinder(double r, double h): base(r, h) {} public override double Area() //求面积 { return 2 * PI * x * x + 2 * PI * x * y; } }

class Program { static void Main(string[] args) { double x = 2.4, y = 5.6; double r = 3.0, h = 5.0; Rectangle t = new Rectangle(x,y); Rectangle c = new Circle(r); Rectangle s = new Sphere(r); Rectangle l = new Cylinder(r, h); Console.WriteLine("长为{0},宽为{1}的长方形面积={2:F2}", x,y,t.Area()); Console.WriteLine(" 半径为{0}的圆面积={1:F2}", r,c.Area()); Console.WriteLine(" 半径为{0}的圆球体表面积={1:F2}", r,s.Area()); Console.WriteLine("半径为{0},高度为{1}的圆柱体表面积={2:F2}", r,h,l.Area()); } } }

7.3 抽象类
7.3.1 什么是抽象类 在类声明中使用abstract修饰符的类称为抽象类。抽象 类具有以下特点: ? 抽象类不能实例化。 ? 抽象类可以包含抽象方法和抽象访问器。 ? 抽象类中可以存在非抽象的方法。 ? 不能用sealed修饰符修改抽象类,这也意味着抽象类不 能被继承。 ? 从抽象类派生的非抽象类必须包括继承的所有抽象方法 和抽象访问器的实现。 ? 抽象类可以被抽象类所继承,结果仍是抽象类。

7.3.2 抽象方法 在方法声明中使用abstract修饰符以指示方法不包含实现 的,即为抽象方法。抽象方法具有以下特性:
? 声明一个抽象方法使用abstract关键字。 ? 抽象方法是隐式的虚方法。 ? 只允许在抽象类中使用抽象方法声明。 ? 一个类中可以包含一个或多个抽象方法。 ? 因为抽象方法声明不提供实际的实现,所以没有方法体; 方法声明只是以一个分号结束,并且在签名后没有大括号{}。 ? 抽象方法实现由一个重写方法提供,此重写方法是非抽 象类的成员。 ? 实现抽象类用“:”,实现抽象方法用override关键字。 ? 在抽象方法声明中使用static或virtual修饰符是错误的。 ? 抽象方法被实现后,不能更改修饰符。

【例7.4】 分析以下程序的运行结果。
using System; namespace Proj7_4 { abstract class A { abstract public int fun(); } class B : A { int x, y; public B(int x1, int y1) { x = x1; y = y1; } public override int fun() { return x * y; } }

//抽象类声明

//抽象方法声明

//抽象方法实现

class Program { static void Main(string[] args) { B b = new B(2, 3); Console.WriteLine("{0}", b.fun()); } }
}
6

7.3.3 抽象属性 除了在声明和调用语法上不同外,抽象属性的行为与 抽象方法类似,另外,抽象属性具有如下特性:
? 在静态属性上使用abstract修饰符是错误的。 ? 在派生类中,通过包括使用override修饰符的属性声明,可以重 写抽象的继承属性。 ? 抽象属性声明不提供属性访问器的实现,它只声明该类支持属 性,而将访问器实现留给其派生类。

【例7.5】 分析以下程序的运行结果。
using System; namespace Proj7_5 { abstract class A { protected int x = 2; protected int y = 3; public abstract void fun(); public abstract int px { get;set; } public abstract int py { get; } }

//抽象类声明

//抽象方法声明 //抽象属性声明 //抽象属性声明

class B : A { public override void fun() //抽象方法实现 { x++; y++; } public override int px //抽象属性实现 { set { x = value; } get { return x + 10; } x=16,y=14 } public override int py //抽象属性实现 { get { return y + 10; } } } class Program { static void Main(string[] args) { B b = new B(); b.px = 5; b.fun(); Console.WriteLine("x={0}, y={1}", b.px, b.py); } }
}

7.4 接口
7.4.1 什么是接口 接口是类之间交互内容的一个抽象,把类之间需要交互 的内容抽象出来定义成接口,可以更好的控制类之间的逻 辑交互。接口具有下列特性:
? 接口类似于抽象基类。继承接口的任何非抽象类型都必须实 现接口的所有成员。 ? 不能直接实例化接口。 ? 接口可以包含事件、索引器、方法和属性。 ? 接口不包含方法的实现。 ? 类和结构可从多个接口继承。 ? 接口自身可从多个接口继承。

接口只包含成员定义,不包含成员的实现,成员的 实现需要在继承的类或者结构中实现。接口的成员包 括方法、属性、索引器和事件,但接口不包含字段。

7.4.2 接口的定义 1. 声明接口 一个接口声明属于一个类型说明,其一般语法格式如下:
[接口修饰符] interface 接口名[:父接口列表] { //接口成员定义体 }

其中,接口修饰符可以是new、public、protected、 internal和private。new修饰符是在嵌套接口中唯一被允许 存在的修饰符,表示用相同的名称隐藏一个继承的成员。

2. 接口的继承 接口可以从零个或多个接口中继承。当一个接口从多个 接口中继承时,用“:”后跟被继承的接口名称,这多个接口 之间用“,”号分隔。被继承的接口应该是可以被访问的,即 不能从internal或internal类型的接口继承。 对一个接口的继承也就继承了接口的所有成员。 例如:
public interface Ia { void mymethod1(); } public interface Ib { int mymethod2(int x); } public interface Ic : Ia, Ib { } //接口Ia声明

//接口Ib声明

//接口Ic从Ia和Ib继承

7.4.3 接口的成员 接口可以声明零个或多个成员。一个接口的成员不止 包括自身声明的成员,还包括从父接口继承的成员。所有 接口成员默认都是公有的,接口成员声明中包含任何修饰 符都是错误的。 1. 接口方法成员 语法格式:返回类型 方法名([参数表]); 2. 接口属性成员 语法格式:返回类型 属性名{get; 或 set;}; 例如,以下声明一个接口Ia,其中接口属性x为只读的, y为可读可写的,z为只写的:
public interface Ia { int x { get;} int y { set;get;} int z { set;} }

3. 接口索引器成员 语法格式:数据类型 this[索引参数表]{get; 或set;}; 例如:
public interface Ia { string this[int index] { get; set; } }

4. 接口事件成员 语法格式:event 代表名 事件名; 例如:
public delegate void mydelegate(); public interface Ia { event mydelegate myevent; } //声明委托类型

7.4.4 接口的实现 接口的实现分为隐式实现和显式实现。如果类或者结构 要实现的是单个接口,可以使用隐式实现,如果类或者结 构继承了多个接口,那么接口中相同名称成员就要显式实 现。显式实现是通过使用接口的完全限定名来实现接口成 员的。 接口实现的语法格式如下:
class 类名:接口名列表 { //类实体 }

说明:
? 当一个类实现一个接口时,这个类就必须实现整个接口,而不能 选择实现接口的某一部分。 ? 一个接口可以由多个类来实现,而在一个类中也可以实现一个或 多个接口。 ? 一个类可以继承一个基类,并同时实现一个或多个接口。

1. 隐式实现接口成员 如果类实现了某个接口,它必然隐式地继承了该接口 成员,只不过增加了该接口成员的具体实现。 若要隐式实现接口成员,类中的对应成员必须是公共 的、非静态的,并且与接口成员具有相同的名称和签名。

【例7.6】 分析以下程序的运行结果 。
using System; namespace Proj7_6 { interface Ia //声明接口Ia { float getarea(); //接口成员声明 } public class Rectangle : Ia //类A继承接口Ia { float x,y; public Rectangle(float x1, float y1) //构造函数 { x = x1; y = y1; } public float getarea() //隐式接口成员实现,必须使用public { return x*y; } }

class Program { static void Main(string[] args) { Rectangle box1 = new Rectangle(2.5f, 3.0f); //定义一个类实例 Console.WriteLine("长方形面积: {0}", box1.getarea()); } } } 3 7

2. 显式实现接口成员 当类实现接口时,如给出了接口成员的完整名称即带 有接口名前缀,则称这样实现的成员为显式接口成员,其 实现被称为显式接口实现。 显式接口成员实现不能使用任何修饰符。

【例7.8】 分析以下程序的运行结果 。
using System; namespace Proj7_8 { interface Ia //声明接口Ia { float getarea(); //接口成员声明 } public class Rectangle : Ia //类Rectangle继承接口Ia { float x,y; public Rectangle(float x1, float y1) //构造函数 { x = x1; y = y1; } float Ia.getarea() //显式接口成员实现,带有接口名前缀,不能使用public { return x*y; } }

class Program { static void Main(string[] args) { Rectangle box1 = new Rectangle(2.5f, 3.0f);//定义一个类实例 Ia ia = (Ia)box1; //定义一个接口实例 Console.WriteLine("长方形面积: {0}", ia.getarea()); } } } 长方形面积:7.5

7.5 接口在集合排序中的应用
7.5.1 ArrayList类的排序方法 ArrayList类对象不仅可以存放数值、字符串,还可以 存放其他类的对象和结构变量。其提供的排序方法如下:
? ArrayList.Sort ():使用每个元素的IComparable接口实现 对整个ArrayList中的元素进行排序。 ? ArrayList.Sort (IComparer):使用指定的比较器对整个 ArrayList中的元素进行排序。 ? ArrayList.Sort (Int32, Int32, IComparer):使用指定的比较 器对ArrayList中某个范围内的元素进行排序。

其中涉及到IComparable和IComparer两个系统接口。

7.5.2 IComparable接口 IComparable接口定义通用的比较方法,由值类型或类实 现以创建类型特定的比较方法。其公共成员有CompareTo, 它用于比较当前实例与同一类型的另一对象。其使用语法格 式如下:
int CompareTo(Object obj)

其中,obj表示与此实例进行比较的对象。其返回值是一个 32位有符号整数,指示要比较的对象的相对顺序。返回值的 含义如下:
?小于零:此实例小于obj。 ?零:此实例等于obj。 ?大于零:此实例大于obj。

IComparable接口的CompareTo方法提供默认排序次序, 如果需要改变其排序方式,可以在相关类中实现CompareTo 方法,以订制其比较功能。

【例7.12】 分析以下程序的运行结果。
using System; using System.Collections; namespace Proj7_12 { class Program { class Stud : IComparable { int xh; string xm; int fs; public int pxh { get { return xh; } } public string pxm { get { return xm; } } public int pfs { get { return fs; } } //新增 //从接口派生 //学号 //姓名 //分数 //pxh属性

//pxm属性

//pfs属性

public Stud(int no, string name, int degree) { xh = no; xm = name; fs = degree; } public void disp() //输出学生信息 { Console.WriteLine("\t{0}\t{1}\t{2}", xh, xm, fs); } public int CompareTo(object obj) //实现接口方法 { Stud s = (Stud)obj; //转换为Stud实例 if (pfs < s.pfs) return 1; else if (pfs == s.pfs) return 0; else return -1; }
} static void disparr(ArrayList myarr, string str) //输出所有学生信息 { Console.WriteLine(str); Console.WriteLine("\t学号\t姓名\t分数"); foreach (Stud s in myarr) s.disp(); }

static void Main(string[] args) { int i, n = 4; ArrayList myarr = new ArrayList(); Stud[] st = new Stud[4] { new Stud(1, "Smith", 82), new Stud(4, "John", 88), new Stud(3, "Mary", 95), new Stud(2, "Cherr", 64) }; for (i = 0; i < n; i++) //将对象添加到myarr集合中 myarr.Add(st[i]); disparr(myarr, "排序前:"); myarr.Sort(); disparr(myarr, "按分数降序排序后:"); }

}
}

7.5.3 IComparer接口

IComparer接口定义两个对象的通用比较方法。其公共 成员有Compare。Compare方法比较两个对象并返回一个 值,指示一个对象是小于、等于还是大于另一个对象。其 使用语法格式如下:
int Compare(Object x,Object y)

其中,x表示要比较的第一个对象。y表示要比较的第 二个对象。 其返回值是一个32位有符号整数,指示要比较 的对象的相对顺序。返回值的含义如下:
? 小于零:x小于y。 ? 零:x等于y。 ? 大于零:x大于y。

通常声明一个类(从IComparer接口派生),其中实现 Compare方法,以订制其比较功能,然后再调用排序方法 以该类的对象作为参数,这样在排序时会自动使用该订制 的Compare方法。

例7.13】 分析以下程序的运行结果。
using System; using System.Collections; namespace Proj7_13 { class Program { class Stud { int xh; string xm; int fs; public int pxh { get { return xh; } } public string pxm { get { return xm; } } public int pfs { get { return fs; } } //新增

//学号 //姓名 //分数 //pxh属性

//pxm属性

//pfs属性

public Stud(int no, string name, int degree) { xh = no; xm = name; fs = degree; } public void disp() { Console.WriteLine("\t{0}\t{1}\t{2}", xh, xm, fs); }
} public class myCompareClassxh : IComparer { int IComparer.Compare(object x, object y) { Stud a = (Stud)x; Stud b = (Stud)y; if (a.pxh > b.pxh) return 1; else if (a.pxh == b.pxh) return 0; else return -1; } }

public class myCompareClassxm : IComparer { int IComparer.Compare(object x, object y) { Stud a = (Stud)x; Stud b = (Stud)y; return String.Compare(a.pxm, b.pxm); } } public class myCompareClassfs : IComparer { int IComparer.Compare(object x, object y) { Stud a = (Stud)x; Stud b = (Stud)y; if (a.pfs < b.pfs) return 1; else if (a.pfs == b.pfs) return 0; else return -1; } }

static void disparr(ArrayList myarr, string str) { Console.WriteLine(str); Console.WriteLine("\t学号\t姓名\t分数"); foreach (Stud s in myarr) s.disp(); }

static void Main(string[] args) { int i, n = 4; IComparer myComparerxh = new myCompareClassxh(); IComparer myComparerxm = new myCompareClassxm(); IComparer myComparerfs = new myCompareClassfs(); ArrayList myarr = new ArrayList(); Stud[] st = new Stud[4] { new Stud(1, "Smith", 82), new Stud(4, "John", 88), new Stud(3, "Mary", 95), new Stud(2, "Cherr", 64) }; for (i = 0; i < n; i++) myarr.Add(st[i]); disparr(myarr, "排序前:"); myarr.Sort(myComparerxh); disparr(myarr, "按学号升序排序后:"); myarr.Sort(myComparerxm); disparr(myarr, "按姓名词典次序排序后:"); myarr.Sort(myComparerfs); disparr(myarr, "按分数降序排序后:"); } } }

7.6 泛型编程
7.6.1 什么是泛型 所谓泛型,是指通过参数化类型来实现在同一份代码上 操作多种数据类型,泛型编程是一种编程范式,它利用“参 数化类型”将类型抽象化,从而实现更为灵活的复用。 C#泛型能力是由CLR在运行时支持,区别于C++的编译 时模板机制和Java的编译时的“搽拭法”。这使得泛型能力 可以在各个支持CLR的语言之间进行无缝的互操作。

7.6.2 泛型的定义和使用 通常先定义泛型,然后通过类型实例化来使用泛型。定 义泛型的语法格式如下:
[访问修饰符][返回类型] 泛型名称<类型参数列表>

其中,“泛型名称”要符合标识符的定义。尖括号表示 类型参数列表,可以包含一个或多个类型参数,如 <T,U,?>。 C#中常用的泛型有泛型类和泛型方法,例如:
class Stack<T> //泛型类 { T data[MaxSize]; int top; ? } void swap<T>(ref T a,ref T b) //泛型方法 { T tmp = a; a = b; b = tmp; }

【例7.14】 分析以下程序的运行结果。
using System; using System.Collections.Generic; using System.Text; namespace Proj7_14 { class Stack<T> { T[] data; int top; public Stack() //构造函数 { data = new T[10]; top=-1; } public bool StackEmpty() { return top==-1; } public void Push(T e) { top++; data[top]=e; } public void Pop(ref T e) { e=data[top]; top--; } }

class Program { static void Main(string[] args) { int e=0; Stack<int> s=new Stack<int>(); //整数栈 s.Push(1); s.Push(3); s.Push(2); Console.Write("整数栈出栈次序"); while (!s.StackEmpty()) { s.Pop(ref e); Console.Write("{0} ",e); } Console.WriteLine();

string e1=""; Stack<string> s1 = new Stack<string>(); //字符串栈 s1.Push("Mary"); s1.Push("John"); s1.Push("Simth"); Console.Write("字符串栈出栈次序"); while (!s1.StackEmpty()) { s1.Pop(ref e1); Console.Write("{0} ", e1); } Console.WriteLine();

}
} }

第7章 继承和接口设计
? 小节
?本章专业术语 ?练习题
? 课后练习

C#语言程序设计

269

第7章 结束

返回

C#语言程序设计

270

第8章 Windows窗体应用程序设计
? 本章目录
?窗体设计 ?常用的控件设计 ?多文档窗体 ?窗体设计的事件机制

C#语言程序设计

271

8.1 窗体设计
窗体(Form)是一个窗口或对话框,是存放各种控件(包括标签、文本框、命令 按钮等)的容器,可用来向用户显示信息。 8.1.1 创建Windows窗体应用程序的过程 添加一个窗体的操作步骤是:选择“项目”|“添加Windows窗体”菜单命令,在出 现的 “添加新项”对话框中,选中“Windows窗体”,输入相应的名称(这里为 Form2.cs),单击“添加”按钮。 一个Windows应用程序可以包含多个窗体。

8.1.2 窗体类型 在C#中,窗体分为如下两种类型: (1)普通窗体,也称为单文档窗体(SDI),前面所有创建的窗体均为普 通窗体。普通窗体又分为如下两种: ● 模式窗体。这类窗体在屏幕上显示后用户必须响应,只有在它关闭后才 能操作其他窗体或程序。 ● 无模式窗体。这类窗体在屏幕上显示后用户可以不必响应,可以随意切 换到其他窗体或程序进行操作。通常情况下,当建立新的窗体时,都默认设置 为无模式窗体。 (2)MDI父窗体,即多文档窗体,其中可以放置普通子窗体。

8.1.3 窗体的常用属性 1. 布局属性 2. 窗口样式属性 3. 外观样式属性 4. 行为属性 8.1.4 窗体的常用事件 8.1.5 窗体的常用方法

【例8.1】

1. Form1窗体:
(1)设计界面

(2)事件过程: Form1.cs文件:

//引用部分 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms;

namespace Proj8_1 { public partial class Form1 : Form //从Form类继承Form1窗体 { public Form1() //Form1类构造函数 { InitializeComponent(); //调用初始化方法,其代码在Form1.Designer.cs文件中 } private void button1_Click(object sender, EventArgs e) { Form myform = new Form1_1(); //定义Form1_1类对 象 myform.ShowDialog(); //以模式窗体方式调用 } private void button2_Click(object sender, EventArgs e) { Form myform = new Form1_2(); //定义Form1_2类对 象 myform.Show(); //以无模式窗体方式调用 } } }

Form1.Designer.cs 文件:

namespace Proj8_1 { partial class Form1 { ///<summary> ///必需的设计器变量。 ///</summary> private System.ComponentModel.IContainer components = null; ///<summary> ///清理所有正在使用的资源。 ///</summary> ///<param name="disposing">如果应释放托管资源,为true; ///否则为false。</param> protected override void Dispose(bool disposing) //重写基类Dispose()方法 { if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); //调用基类的Dispose()方法 }

#region Windows 窗体设计器生成的代码 ///<summary> ///设计器支持所需的方法 - 不要 ///使用代码编辑器修改此方法的内容。 ///</summary> private void InitializeComponent() //初始化方法 { this.button1 = new System.Windows.Forms.button(); this.button2 = new System.Windows.Forms.button(); this.SuspendLayout(); // button1 this.button1.Font = new System.Drawing.Font("宋体", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); this.button1.Location = new System.Drawing.Point(28, 21); this.button1.Name = "button1"; this.button1.Size = new System.Drawing.Size(117, 33); this.button1.TabIndex = 0; this.button1.Text = "调用模式窗体"; this.button1.UseVisualStyleBackColor = true; this.button1.Click += new System.EventHandler(this.button1_Click);

// button2 this.button2.Font = new System.Drawing.Font("宋体", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134))); this.button2.Location = new System.Drawing.Point(28, 69); this.button2.Name = "button2"; this.button2.Size = new System.Drawing.Size(117, 33); this.button2.TabIndex = 1; this.button2.Text = "调用无模式窗体"; this.button2.UseVisualStyleBackColor = true; this.button2.Click += new System.EventHandler(this.button2_Click); // Form1 this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(169, 128); this.Controls.Add(this.button2); this.Controls.Add(this.button1); this.Name = "Form1"; this.StartPosition =System.Windows.Forms.FormStartPosition.CenterScreen; this.Text = "Form1"; this.ResumeLayout(false); }

#endregion private System.Windows.Forms.button button1; //私有字段 private System.Windows.Forms.button button2; //私有字段 } }

2. Form1_1窗体:
(1)设计界面 (2)事件过程:无

3. Form1_1窗体: (1)设计界面 (2)事件过程:无

Program.cs文件 :

using System; using System.Collections.Generic; using System.Windows.Forms; namespace Proj8_1 { static class Program { /// <summary> /// 应用程序的主入口点。 /// </summary> [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1()); //启动窗体为Form1 } } }

按F5键或单击工具栏中的按钮运行本项目。 上机调试运行结果。

8.1.6 窗体上各事件的引发顺序 当一个窗体启动时,执行事件过程的次序如下: (1)本窗体上的Load事件过程。 (2)本窗体上的Activated事件过程。 (3)本窗体上的其他Form级事件过程。 (4)本窗体上包含对象的相应事件过程。 一个窗体被卸载时,执行事件过程的次序如下: (1)本窗体上的Closing事件过程。 (2)本窗体上的FormClosing事件过程。 (3)本窗体上的Closed事件过程。 (4)本窗体上的FormClosed事件过程。

8.1.7 焦点与Tab键次序 焦点(Focus)是指当前处于活动状态的窗体或控件。

要将焦点移到当前窗体中的textBox1文本框,可以使用以下命令:

textBox1.Focus();

8.2 常用的控件设计
8.2.1 控件概述

控件是包含在窗体上的对象,是构成用户界面的基本元素,也是C#可 视化编程的重要工具。 工具箱中包含了建立应用程序的各种控件,根据控件的不同用途分为若 干个选项卡,可根据用途单击相应的选项卡,将其展开,选择需要的控件。

大多数控件共有的基本属性如下 : 1)Name属性

2)Text属性
3)尺寸大小(Size)和位置(Location)属性 4)字体属性(Font) 5)颜色属性(BackColor和ForeColor) 6)Cursor属性 7)可见(Visible)和有效(Enabled)属性

8.2.2 富文本框控件 提供类似Microsoft Word能够输入、显示或处理具有格式的文本。 【例8.2】 设计一个窗体,说明富文本框的使用方法。

Form2窗体: (1)设计界面

(2)事件过程:

private void Form2_Load(object sender, EventArgs e) { richtextBox1.LoadFile("H:\\C#2005\\ch8\\file.RTF", RichtextBoxStreamType.RichText); } @"H:\C#2005\ch8\file.RTF",

将本窗体设计为启动窗体,运行本项目,在富文本框RichtextBox1中显 示H:\C#2005\ch8\file.rtf文件的内容。

8.2.3 分组框控件 8.2.4 面板控件

.2.5 复选框控件 属于选择类控件,用来设置需要或不需要某一选项功能。在运行时,如果 用户用鼠标单击复选框左边的方框,方框中就会出现一个“√”符号,表示已 选取这个功能了。 复选框的功能是独立的,如果在同一窗体上有多个复选框,用户可根据需要 选取一个或几个。

主要属性: Checked:获取或设置一个布尔值,该值指示是否已选中控件。如果为True, 则指示选中状态;否则为False(默认值)。 主要事件: Click

【例8.3】 设计一个窗体,说明复选框的应用。
Form3窗体: (1)设计界面 (2)事件过程:

private void button1_Click(object sender, EventArgs e) { if (checkBox1.Checked && checkBox3.Checked && !checkBox2.Checked && !checkBox4.Checked) MessageBox.Show("您答对了,真的很棒!!!", "信息提示", MessageBoxButtons.OK); else MessageBox.Show("您答错了,继续努力吧!!!", "信息提示", MessageBoxButtons.OK); }

运行界面

8.2.6 单选按钮控件 单选按钮是多选一,只能从多个选项中选择一个,各选项间的关系是互 斥的。 单选按钮使用时经常用多个控件构成一个组,同一时刻只能选择同一组 中的一个单选按钮,因此,经常将单选按钮放在一个分组框中构成一个选项 组。

【例8.4】 设计一个窗体,说明单选按钮的使用方法。 Form4窗体: (1)设计界面 (2)事件过程:

private void button1_Click(object sender, EventArgs e) { if (radiobutton3.Checked) MessageBox.Show("您选对了,这是微软公司开发的操作系统", "信息提示", MessageBoxButtons.OK); else if (radiobutton1.Checked || radiobutton4.Checked) MessageBox.Show("您选错了,这是程序设计语言", "信息提示", MessageBoxButtons.OK); else MessageBox.Show("您选错了,这是数据库管理系统", "信息提示", MessageBoxButtons.OK); }

运行界面

8.2.7 图片框控件 用于在窗体的特殊位置上放置图形信息,也可以在其上放置多个控件, 因此它可作为其他控件的容器 。

主要属性: Image获取或设置图片框中显示的图像。在运行时再使用 Image.FromFile函数加载图像。

【例8.5】 设计一个窗体,以选择命令按钮方式显示春、夏、秋、冬4个季节 的图片。
Form5窗体: (1)设计界面 (2)事件过程:

private void button1_Click(object sender, EventArgs e) { pictureBox1.Image = Image.FromFile("H:\\C#2005\\ch8\\spring.jpg"); } private void button2_Click(object sender, EventArgs e) { pictureBox1.Image = Image.FromFile("H:\\C#2005\\ch8\\summer.jpg"); } private void button3_Click(object sender, EventArgs e) { pictureBox1.Image = Image.FromFile("H:\\C#2005\\ch8\\fall.jpg"); } private void button4_Click(object sender, EventArgs e) { pictureBox1.Image = Image.FromFile("H:\\C#2005\\ch8\\winter.jpg"); }

运行界面

8.2.8 组合框控件 从一个列表中一次只能选取或输入一个选项,其主要特点是具有带向下箭头的 方框。 在程序运行时,按下此按钮就会下拉出一个列表框供用户选择项目。另外,还 可以在组合框上方的框中输入数据。

组合框的属性 DropDownStyle

说明 获取或设置指定组合框样式的值。可取以下值之一: ? DropDown(默认值):文本部分可编辑。用户必须单击箭头按钮 来显示列表部分。 ? DropDownList:用户不能直接编辑文本部分。用户必须单击箭头 按钮来显示列表部分。 ? Simple:文本部分可编辑。列表部分总可见。 各种样式的组合框如图8.19所示。 获取或设置组合框下拉部分的宽度(以像素为单位)。 获取或设置组合框下拉部分的高度(以像素为单位)。 表示该组合框中所包含项的集合。 获取或设置当前组合框中选定项的索引。 获取或设置当前组合框中选定项的文本。 指示是否对组合框中的项进行排序。

DropDownWidth DropDownHeight Items SelectedItem SelectedText Sorted

DropDownStyle=Simple DropDownStyle=DropDown DropDownStyle=DropDownList

组合框的Items属性是最重要的属性,它是存放组合框中所有项的集合, 对组合框的操作实际上就是对该属性即项集合的操作。

Items的属性

说明

Count
Items的方法
Add AddRange Clear

组合框的项集合中项个数。
说明
向ComboBox项集合中添加一个项。 向ComboBox项集合中添加一个项的数组。 移除ComboBox项集合中的所有项。

Contains
Equqls GetType Insert IndexOf Remove RemoveAt

确定指定项是否在ComboBox项集合中。
判断是否等于当前对象。 获取当前实例的Type。 将一个项插入到ComboBox项集合中指定的索引处。 检索指定的项在ComboBox项集合中的索引。 从ComboBox项集合中移除指定的项。 移除ComboBox项集合中指定索引处的项

组合框的事件
组合框的事件 Click TextChanged SelectedIndexChanged KeyPress 说明 在单击控件时发生。 在 Text 属性值更改时发生。 在SelectedIndex属性值改变时发生。 在控件有焦点的情况下按下键时发生。

【例8.6】 设计一个窗体,通过一个文本框向合框中添加项。

Form6窗体: (1)设计界面 (2)事件过程:

private void button1_Click(object sender, EventArgs e) { if (textBox1.Text != "") if (!comboBox1.Items.Contains(textBox1.Text)) comboBox1.Items.Add(textBox1.Text); //不添加重复项 }

运行界面

8.2.9 列表框控件 是一个为用户提供选择的列表,用户可从列表框列出的一组选项中用鼠标 选取一个或多个所需的选项。 如果有较多的选择项,超出规定的区域而不能一次全部显示时,C#会自动 加上滚动条。

与组合框类似。

列表框的属性

说明

MultiColumn
SelectedIndex

获取或设置列表框控件是否支持多列。设置为True,则支持多列, 设置为False(默认值),则不支持多列
获取或设置列表框控件中当前选定项从0开始的索引。

SelectedIndices
SelectedItem SelectedItems Items SelectionMode

获取一个集合,它包含所有当前选定项的从0开始的索引。
获取或设置列表框控件中当前选定项。 获取一个集合,它包含所有当前选定项。 获取列表控件项的集合。 获取或设置列表框控件的选择模式。可选以下值之一: ? one:表示只能选择一项。 ? none:表示无法选择。 ? MultiSimple:表示可以选择多项。 ? MultiExtended:表示可以选择多项。并且按下Shift键的同时单 击鼠标或者同时按下Shift键和箭头键,会将选定内容从前一选定项 扩展到当前项,按下Ctrl键的同时单击鼠标将选择或撤销选择列表 中的某项。

Text

当前选取的选项文本。

【例8.7】 设计一个窗体,其功能是在两个列表框中移动数据项。

Form7窗体: (1)设计界面 (2)事件过程:

private void Form7_Load(object sender, EventArgs e) { listBox1.Items.Add("清华大学"); listBox1.Items.Add("北京大学"); listBox1.Items.Add("浙江大学"); listBox1.Items.Add("南京大学"); listBox1.Items.Add("武汉大学"); listBox1.Items.Add("中国科技大学"); listBox1.Items.Add("中国人民大学"); listBox1.Items.Add("华中科技大学"); listBox1.Items.Add("复旦大学"); enbutton(); //调用enbutton()方法 }

private void enbutton() //自定义方法 { if (listBox1.Items.Count == 0) //当左列表框为空时右移命令按钮不可用 { button1.Enabled = false; button2.Enabled = false; } else //当左列表框不为空时右移命令按钮可用 { button1.Enabled = true; button2.Enabled = true; } if (listBox2.Items.Count == 0) //当右列表框为空时左移命令按钮不可用 { button3.Enabled = false; button4.Enabled = false; } else //当右列表框不为空时左移命令按钮可用 { button3.Enabled = true; button4.Enabled = true; } }

private void button1_Click(object sender, EventArgs e) { if (listBox1.SelectedIndex >= 0) //将左列表框中选中项移到右列表框中 { listBox2.Items.Add(listBox1.SelectedItem); listBox1.Items.RemoveAt(listBox1.SelectedIndex); } enbutton(); //调用enbutton()方法 } private void button2_Click(object sender, EventArgs e) { foreach (object item in listBox1.Items)//将左列表框中所有项移到右列 表框中 listBox2.Items.Add(item); listBox1.Items.Clear(); enbutton(); //调用enbutton()方法 }

private void button3_Click(object sender, EventArgs e) { if (listBox2.SelectedIndex >= 0) //将右列表框中选中项移到左列表框中 { listBox1.Items.Add(listBox2.SelectedItem); listBox2.Items.RemoveAt(listBox2.SelectedIndex); } enbutton(); //调用enbutton()方法 } private void button4_Click(object sender, EventArgs e) { foreach (object item in listBox2.Items)//将右列表框中所有项移到左列 表框中 listBox1.Items.Add(item); listBox2.Items.Clear(); enbutton(); //调用enbutton()方法 }

运行界面

8.2.10 带复选框的列表框控件 用来显示一系列列表项的,不过每个列表项前面都有一个复选项。 这样,是否选中了某个列表项就可以很清楚地表现出来。

8.2.11 定时器控件 特点是每隔一定的时间间隔就会自动运行一次定时器事件。所谓时间间隔, 指的是定时器事件两次调用之间的时间间隔,一般以毫秒(ms)为基本单位。

定时器的属性 Enabled Interval

说明 设置是否起用定时器控件。若设置为True(默认值),表 示启动定时器开始计时;否则,表示暂停定时器的使用。 设置两个定时器事件之间的时间间隔。设置时以毫秒为单 位,设置的范围是0~65535ms。 说明 启动定时器,也可以将Enabled属性设置为True来启动定 时器。 停止定时器,也可以将Enabled属性设置为False来停止定 时器。

定时器的方法 Start Stop

【例8.9】 设计一个窗体说明定时器的使用方法。
Form9窗体: (1)设计界面 (2)事件过程:

private void Form9_Load(object sender, EventArgs e) { textBox1.Text = DateTime.Now.ToString("h:mm:ss"); timer1.Enabled = true; //启到定时器timer1 timer1.Interval = 100; } private void timer1_Tick(object sender, EventArgs e) { textBox1.Text = DateTime.Now.ToString("h:mm:ss"); }

运行界面

8.2.12 滚动条控件 滚动条的结构为两端各有一个滚动箭头,两个滚动箭头中间是滚动条部分, 在滚动条上有一个能够移动的小方块,叫做滚动框。

水平滚动条和垂直滚动条

滚动条的属性
Maximum Minimum Value LargeChange

说明
表示滚动条的最大值。 表示滚动条的最小值。 表示目前滚动条所在位置对应的值。 设置滚动条的最大变动值。

SmallChange
滚动条的事件 Scroll Change

设置滚动条的最小变动值。
说明 当用鼠标压住滚动条上的滑块进行移动时,滑块被重新定位 时发生。 当改变Value属性值时发生。

【例8.10】 设计一个窗体说明滚动条的使用方法。
Form10窗体: (1)设计界面 (2)事件过程:

private void Form10_Load(object sender, EventArgs e) { hScrollBar1.Maximum = 100; hScrollBar1.Minimum = 0; hScrollBar1.SmallChange = 2; hScrollBar1.LargeChange = 5; vScrollBar1.Maximum = 100; vScrollBar1.Minimum = 0; vScrollBar1.SmallChange = 2; vScrollBar1.LargeChange = 5; hScrollBar1.Value = 0; vScrollBar1.Value = 0; textBox1.Text = "0"; }

private void hScrollBar1_Scroll(object sender, ScrollEventArgs e) { textBox1.Text = hScrollBar1.Value.ToString("d"); //将hScrollBar1.Value整数将实际宽度转换成字符串在textBox1中显示 vScrollBar1.Value = hScrollBar1.Value; } private void vScrollBar1_Scroll(object sender, ScrollEventArgs e) { textBox1.Text = vScrollBar1.Value.ToString("d"); hScrollBar1.Value = vScrollBar1.Value; }

private void button1_Click(object sender, EventArgs e) { if (Convert.ToInt16(textBox1.Text)>= 0 && Convert.ToInt16(textBox1.Text) <= 100) { hScrollBar1.Value = Convert.ToInt16(textBox1.Text); vScrollBar1.Value = Convert.ToInt16(textBox1.Text); } }
运行界面

8.2.13 月历控件 8.2.14 日期/时间控件

8.2.15 超链接标签控件

8.3 多文档窗体
多文档界面应用程序由一个应用程序(MDI父窗体)中包含多个文档 (MDI子窗体)组成,父窗体作为子窗体的容器,子窗体显示各自文档,它 们具有不同的功能。处于活动状态的子窗体的最大数目是1,子窗体本身不 能成为父窗体,而且不能将其移动到父窗体的区域之外。 多文档界面应用程序有如下特性:

(1)所有子窗体均显示在MDI窗体的工作区内,用户可改变、 移动子窗体的大小,但被限制在MDI窗体中。 (2)当最小化子窗体时,它的图标将显示在MDI窗体上而不是 在任务栏中。 (3)当最大化子窗体时,它的标题与MDI窗体的标题一起显示 在MDI窗体的标题栏上。 (4)MDI窗体和子窗体都可以有各自的菜单,当子窗体加载时 覆盖MDI窗体的菜单。

MDI父窗体属性 ActiveMdiChild IsMdiContainer MdiChildren

说明 表示当前活动的MDI子窗口,如没有子窗口则返回NULL 指示窗体是否为MDI父窗体,值为True时表示是父窗体,值 为False时表示是普通窗体 以窗体数组形式返回所有MDI子窗体

MDI父窗体的方法 一般只使用父窗体的LayoutMdi方法,其使用格式为:

MDI父窗体名.LayoutMdi(value)
其功能是在MDI父窗体中排列MDI子窗体,参数value决定排列方式,有以下4 种取值:

? LayoutMdi.ArrangeIcons:所有MDI子窗体以图标形式排列在 MDI父窗体中。 ? LayoutMdi.TileHorizontal:所有MDI子窗体均垂直平铺在MDI 父窗体中。 ? LayoutMdi.TileVertical:所有MDI子窗体均水平平铺在MDI父 窗体中。 ? LayoutMdi.Cascade:所有MDI子窗体均层叠在MDI父窗体中。

MDI子窗体属性 IsMdiChild MdiParent

说明 指示窗体是否为MDI子窗体,值为True时表示是子窗 体,值为False时表示是一般窗体 用来指定该子窗体的MDI父窗体

【例8.13】 设计一个Windows应用程序,说明多文档窗体的使用方法。

Form1窗体,将其IsMdiContainer属性设为True : 设计界面

事件过程:

private void button1_Click(object sender, EventArgs e) { Form2 child = new Form2(); child.MdiParent = this; child.Show(); n++; child.Text = "第" + n + "个子窗体"; } private void button2_Click(object sender, EventArgs e) { this.LayoutMdi(System.Windows.Forms. MdiLayout.ArrangeIcons); } private void button3_Click(object sender, EventArgs e) { this.LayoutMdi(System.Windows.Forms. MdiLayout.Cascade); } private void button4_Click(object sender, EventArgs e) { this.LayoutMdi(System.Windows.Forms. MdiLayout.TileVertical); }

private void button5_Click(object sender, EventArgs e) { this.LayoutMdi(System.Windows.Forms. MdiLayout.TileHorizontal); }
运行界面

8.4 窗体设计的事件机制
8.4.1 什么是事件处理程序 事件处理程序是代码中的过程,用于确定事件(如用户单击按钮或消息 队列收到消息)发生时要执行的操作。 事件处理程序是绑定到事件的方法。当引发事件时,将执行收到该事件 的一个或多个事件处理程序。 每个事件处理程序提供两个参数。例如,窗体中一个命令按钮button1的 Click事件的事件处理程序如下:

private void button1_Click(object sender, System.EventArgs e) { //输入相应的代码 }
其中,第一个参数sender提供对引发事件的对象的引用,第二个参数e传 递特定于要处理的事件的对象。通过引用对象的属性(有时引用其方法)可获 得一些信息,如鼠标事件中鼠标的位置或拖放事件中传输的数据。 创建事件处理程序有以下两种方法:

(1)在Windows窗体中创建事件处理程序。 (2)在运行时为Windows窗体创建事件处理程序。

8.4.2 在Windows窗体中创建事件处理程序 在Windows窗体设计器上创建事件处理程序的过程如下: (1)单击要为其创建事件处理程序的窗体或控件。 (2)在属性窗口中单击“事件”按钮。 (3)在可用事件的列表中,单击要为其创建事件处理程序的事件。 (4)在事件名称右侧的框中,键入处理程序的名称,然后按Enter键。如图 8.40所示是为button1命令按钮选择button1_Click事件处理程序,这样C#系统 会在对应窗体的.Designer.cs文件中自动添加以下语句:

this.button1.Click += new System.EventHandler(this.button1_Click);
该语句的功能是订阅事件(参见第6章),即接收器使用加法赋值运算符 (+=) 将该委托System.EventHandler(this.button1_Click)添加到源对象button1的事 件中。 (5)将适当的代码添加到该事件处理程序中。

8.4.3 在运行时为Windows窗体创建事件处理程序 在运行时创建事件处理程序的过程如下: (1)在代码编辑器中打开要向其添加事件处理程序的窗体。 (2)对于要处理的事件,将带有其方法签名的方法添加到窗体上。 例如,如果要处理命令按钮button1的Click事件,则需创建如下的一个方法:

private void button1_Click(object sender, System.EventArgs e) { //输入相应的代码 }

(3)将适合应用程序的代码添加到事件处理程序中。 (4)确定要创建事件处理程序的窗体或控件。 (5)打开对应窗体的.Designer.cs文件,添加指定事件处理程序的代码处理 事件。例如,以下代码指定事件处理程序button1_Click处理命令按钮控件的 Click事件:

button1.Click += new System.EventHandler(button1_Click);

8.4.4 将多个事件连接到Windows窗体中的单个事件处理程序 在应用程序设计中,可能需要将单个事件处理程序用于多个事件或让多个事件 执行同一过程,这样便于简化代码。在C#中将多个事件连接到单个事件处理程序的 过程如下: (1)选择要将事件处理程序连接到的控件。 (2)在“属性”窗口中,单击“事件”按钮。 (3)单击要处理的事件的名称。 (4)在事件名称旁边的值区域中,单击下拉按钮显示现有事件处理程序列表,这 些事件处理程序会与要处理的事件的方法签名相匹配。 (5)从该列表中选择适当的事件处理程序。 代码将添加到该窗体中,以便将该事件绑定到现有事件处理程序。

【例8.14】 设计一个Windows应用程序,用于模拟简单计算器的功能。

Form1窗体,将其IsMdiContainer属性设为True : 设计界面

private void Form1_Load(object sender, EventArgs e) { textBox1.Text = ""; label1.Text = ""; } private void buttond_Click(object sender, EventArgs e) //单击数字命令按钮的事件处理程序 { btn = (Button)sender; textBox1.Text = textBox1.Text + btn.Text; }

private void buttonop_Click(object sender, EventArgs e) //单击运算符命令按钮的事件处理程序 { btn = (Button)sender; if (btn.Name!="button12") //用户不是单击“=”命令按钮 { x = Convert.ToDouble(textBox1.Text); textBox1.Text = ""; s = btn.Name; //保存用户按键 label1.Text = x.ToString(); } else //用户单击“=”命令按钮 { if (label1.Text == "") MessageBox.Show("输入不正确!!!", "信息提示", MessageBoxButtons.OK); else

{

y = Convert.ToDouble(textBox1.Text); switch(s) { case "button13": //用户刚前面单击“+”命令按钮 textBox1.Text = (x + y).ToString(); break; case "button14": //用户刚前面单击“-”命令按钮 textBox1.Text = (x - y).ToString(); break; case "button15": //用户刚前面单击“×”命令按钮 textBox1.Text = (x * y).ToString(); break; case "button16": //用户刚前面单击“÷”命令按钮 if (y == 0) MessageBox.Show("除零错误!!!", "信息提示", MessageBoxButtons.OK); else textBox1.Text = (x / y).ToString(); break; } label1.Text = textBox1.Text;

}}}

运行界面

第8章 Windows窗体应用程序设计
? 小节
?本章专业术语 ?练习题
? 课后练习

C#语言程序设计

341

第8章 结束

返回

C#语言程序设计

342

第9章 用户界面设计
? 本章目录
?菜单设计 ?通用对话框 ?图像列表框控件 ?树形视图控件 ?列表视图控件 ?工具栏控件 ?状态栏控件

C#语言程序设计

343

9.1 菜单设计
9.1.1 菜单结构
菜单标题 菜单项 一级菜单 子菜单标记 录 分隔条 热键 菜单栏

子菜单

快捷键

9.1.2 创建下拉式菜单 设计过程: 1. 添加菜单和菜单项 2. 设置菜单项属性 3. 为菜单项编写事件过程 4. 为菜单编写事件过程

C#的工具箱中提供了一个MenuStrip菜单控件。

【例9.1】 设计一个下拉式菜单实现两个数的加、减、乘和除运算。

Form1窗体 设计界面

设计的菜单层次如下: 运算(op) //表示“运算”菜单项的名称为op,下同 ....加法(addop) ....减法(subop) ....乘法(multop) ....分隔条1 ....除法(divop)

事件过程:

private void addop_Click(object sender, EventArgs e) { int n; n = Convert.ToInt16(textBox1.Text) + Convert.ToInt16(textBox2.Text); textBox3.Text = n.ToString(); } private void subop_Click(object sender, EventArgs e) { int n; n = Convert.ToInt16(textBox1.Text) * Convert.ToInt16(textBox2.Text); textBox3.Text = n.ToString(); } private void mulop_Click(object sender, EventArgs e) { int n; n = Convert.ToInt16(textBox1.Text) * Convert.ToInt16(textBox2.Text); textBox3.Text = n.ToString(); }

private void divop_Click(object sender, EventArgs e) { int n; n = Convert.ToInt16(textBox1.Text) / Convert.ToInt16(textBox2.Text); textBox3.Text = n.ToString(); } private void op_Click(object sender, EventArgs e) { if (textBox2.Text=="" || Convert.ToInt16(textBox2.Text) == 0) divop.Enabled = false; else divop.Enabled = true; }

运行界面

9.1.3 弹出式菜单设计 使用ContextMenuStrip控件设计弹出式菜单。其使用方法同MenuStrip 菜单控件。

【例9.2】 设计一个弹出式菜单实现两个数的加、减、乘和除运算。

Form4窗体 设计界面

设计的菜单层次如下: 运算(op) //表示“运算”菜单项的名称为op,下同 ....加法(addop) ....减法(subop) ....乘法(multop) ....分隔条1 ....除法(divop)

事件过程:

private void addop_Click(object sender, EventArgs e) { int n; n = Convert.ToInt16(textBox1.Text) + Convert.ToInt16(textBox2.Text); textBox3.Text = n.ToString(); } private void subop_Click(object sender, EventArgs e) { int n; n = Convert.ToInt16(textBox1.Text) Convert.ToInt16(textBox2.Text); textBox3.Text = n.ToString(); }

private void mulop_Click(object sender, EventArgs e) { int n; n = Convert.ToInt16(textBox1.Text) * Convert.ToInt16(textBox2.Text); textBox3.Text = n.ToString(); } private void divop_Click(object sender, EventArgs e) { int n; n = Convert.ToInt16(textBox1.Text) / Convert.ToInt16(textBox2.Text); textBox3.Text = n.ToString(); } private void op_Opened(object sender, EventArgs e) { if (textBox2.Text == "" || Convert.ToInt16(textBox2.Text) == 0) divop.Enabled = false; else divop.Enabled = true; }

运行界面

9.2 通用对话框
9.2.1 打开文件对话框 9.2.2 保存文件对话框 9.2.3 颜色对话框 9.2.4 字体对话框

通过示例讲解。

9.3 图像列表框控件
图像列表框(ImageList)控件的作用是存储图像,构成一个图形库列表。
ImageList控件是一个非可视化的控件,在C#工具箱中含有ImageList属性 的控件有Label、Button、RadioButton、CheckBox、ToolBar、TreeView和 ListVie等控件。

通过示例讲解。

9.4 树形视图控件
树形视图控件(TreeView)以分级或分层视图的形式显示信息,如同 Windows中显示的文件和目录。

通过示例讲解。

9.5 列表视图控件
列表视图控件(ListView)与TreeView控件类似,都是用来显示信息, 只是TreeView控件以树形式显示信息,而ListView控件以列表形式显示信息, 能够用来制作像Windows中“控制面板”那样的用户界面,

通过示例讲解。

9.6 工具栏控件
工具栏控件(ToolStrip)以其直观、快捷的特点出现在各种应用程序中, 例如Visual Studio.NET系统集成界面中就提供了工具栏,这样不必在一级 级的菜单去搜寻需要的命令,给用户操作带来了方便。

通过示例讲解。

9.7 状态栏控件
状态栏控件(StatusStrip)和菜单、工具栏一样是Windows应用程 序的一个特征,它通常位于窗体的底部,应用程序可以在该区域中显示 提示信息或应用程序的当前状态等各种状态信息。

通过示例讲解。

第9章 用户界面设计
? 小节
?本章专业术语 ?练习题
? 课后练习

C#语言程序设计

361

第9章 结束

返回

C#语言程序设计

362


推荐相关:

《C语言程序设计》课后习题答案(第四版)谭浩强

C语言程序设计》课后习题答案(第四版)谭浩强_工学_高等教育_教育专区。《C语言程序设计》课后习题答案(第四版)谭浩强 第1 章程序设计和 C 语言 1 1.1 ...


C语言程序设计 阅读程序题库及答案

C语言程序设计 阅读程序题库及答案_哲学_高等教育_教育专区。C语言程序设计 阅读程序题库,答案 C语言程序设计 程序填空题库,答案 ...


C语言程序设计基础知识要点

01.C 程序基本结构一、C 语言的特点: 1、C 语言源程序的基本组成单位是函数;一个 C 程序可由若干个函数组成,其中必须有且仅有一个以 main 命名的主 函数,...


C语言程序设计导学(第三版)参考答案 杜友福 编

C语言程序设计导学(第三版)参考答案 杜友福 编_理学_高等教育_教育专区。C 语言实验(第三版)参考答案实验一 3. 程序填空题 a 、 b 、 t 4. 程序改错题...


C语言程序设计第四版第五章答案 谭浩强

C语言程序设计第四版第五章答案 谭浩强_IT/计算机_专业资料。第五章 循环结构程序设计 第五章 循环控制 5.3 输入两个正整数 m 和 n,求其最大公约数和最小...


C语言程序设计基础教程_习题答案(纪纲 金艳)

C语言程序设计基础教程_习题答案(纪纲 金艳)_教育学_高等教育_教育专区。习题答案 习题答案 第1章 1.1 填空题 1.1.1 应用程序 ONEFUNC.C 中只有一个函数,...


c语言程序设计试题1

c语言程序设计试题1_电子/电路_工程科技_专业资料。程序设计基础-以c为基础C 语言试卷-1 一、 选择题 ( 评分标准 20 分,每小题 2 分) 1. C 语言规定:在...


c语言小游戏编程

c语言小游戏编程_计算机软件及应用_IT/计算机_专业资料。用c语言编写的小游戏,...程序设计实践 大作业 学号:20100302XXXX 姓名:XXX 班级:信息 10-3 班 实验...


c语言程序设计年历显示

c语言程序设计年历显示_工学_高等教育_教育专区。c语言课程设计,年历显示计算机科学与技术学院 课程设计报告 2012 — 2013 学年第 一 学期 课程名称 设计题目 学生...


《c#语言程序设计》-考试大纲

C#语言程序设计》是软件工程专业的一门专业基础课程。.NET 平台是当今两大企业开发平台之一, C#是.NET 平台上的核心开发语言,它脱胎于 C/C++,同汲取了 Java...

网站首页 | 网站地图
All rights reserved Powered by 简单学习网 www.tceic.com
copyright ©right 2010-2021。
文档资料库内容来自网络,如有侵犯请联系客服。zhit325@126.com