《高级程序设计》 编者:杜元胜 祝惠新 山东科技职业学院 2010.2 目录 第一章 C#与.NET 概述 ................................................. 5 1.1、.NET Framework 简介 .............................................. 6 1.2、.NET Framework 体系结构 .......................................... 6 1.3、.NET Framework 的组件 ............................................ 7 1.4、C#的常用命名空间 ................................................ 9 1.5、Visual Studio .NET 2005 简介 .................................... 10 1.6、Visual Studio .NET 2005 集成开发环境 ............................ 13 1.7、Visual Studio.NET 2005 环境设置 ................................. 18 1.8、C# 应用程序文件夹结构 .......................................... 19 1.9、创建和编译 HelloWorld 控制台应用程序 ............................ 19 第二章 C#数据类型、运算符和表达式 ................................... 23 2.1、C#中的数据类型 ................................................. 24 2.2 常量与变量 ..................................................... 31 2.3 运算符和表达式 ................................................. 33 第三章 C#结构化程序设计 ............................................. 43 3.1 C#程序结构 ..................................................... 44 3.2 输入/输出操作 .................................................. 47 3.3 结构化程序设计的概念 ........................................... 52 3.4 顺序结构 ....................................................... 53 3.5 选择结构 ....................................................... 53 3.6 循环结构 ....................................................... 61 3.7 异常处理 ....................................................... 73 3.8、数组 ........................................................... 79 3.9、结构 ........................................................... 80 3.10、枚举 .......................................................... 80 3.11、C#的预处理指令 ................................................ 82 3.12、C#的字符串处理 ................................................ 82 第四章 在 C#中实现面向对象概念 ...................................... 83 4.1、对象和类 ....................................................... 84 4.2、构造函数和析构函数 ............................................. 86 4.3、方法 ........................................................... 89 4.4、方法重载 ....................................................... 91 4.5、属性 ........................................................... 93 4.6、索引器 ......................................................... 98 4.7、命名空间 ...................................................... 101 第五章 WinForms 基础知识 ........................................... 104 5.1、windows 窗体简介 ............................................... 105 5.2、windows 窗体的常用控件 ......................................... 110 5.3、消息框窗口 .................................................... 113 5.4、应用程序示例 1 ................................................. 114 5.5、窗体容器简介 .................................................. 116 5.6、其他控件 ...................................................... 118 5.7、应用程序示例 .................................................. 121 第六章 调试、测试和异常处理 ........................................ 127 6.1、调试 .......................................................... 128 6.2、异常 .......................................................... 130 第七章 数据库编程:连接数据库 ...................................... 137 7.1、ADO.NET 介绍 ................................................... 138 7.2、.NET 数据提供程序 .............................................. 140 7.3、基本组件 ...................................................... 140 7.4、事务处理 ...................................................... 142 第八章 数据库编程:检索和操作数据 .................................. 150 8.1、数据集 DataSet ................................................. 151 8.2、DataAdapter 对象 ............................................... 156 8.3、DataReader .................................................... 158 8.4、用于查询和检索数据的 Windows 应用程序示例 ...................... 159 8.5、DataGridView 控件 .............................................. 163 第九章 C#高级编程 .................................................. 172 9.1、继承 .......................................................... 173 9.2、方法重写 ...................................................... 174 9.3、抽象类和抽象方法 .............................................. 176 9.4、接口 .......................................................... 177 9.5、委托 .......................................................... 182 9.6、事件 .......................................................... 183 第十章 数组、集合对象、泛型 ........................................ 185 10.1、System.Array 介绍 ............................................. 186 10.2、System.Collections 介绍 ....................................... 187 10.3、ArrayList 类 .................................................. 187 10.4、泛型 ......................................................... 188 第十一章 WinForms 高级编程 ......................................... 190 11.1、SDI 和 MDI 简介 ................................................ 191 11.2、菜单 ......................................................... 193 11.3、ImageList 控件 ................................................ 196 11.4、ToolBar 控件 .................................................. 196 11.5、StatusBar 控件 ............................................... 198 11.6、应用程序示例 ................................................. 198 11.7、Timer 控件 .................................................... 202 11.8、ListView 控件 ................................................ 204 11.9、TreeView 控件 ................................................. 206 第十二章 简单设计模式及应用 ........................................ 213 12.1、设计模式简介 ................................................. 214 12.2、简单工厂设计模式 ............................................. 215 12.3、抽象工厂设计模式的应用 ....................................... 215 第十三章 文件和注册表操作 .......................................... 217 13.1、System.IO 命名空间 ............................................ 218 13.2、读写文本文件 ................................................. 219 13.3、读写二进制文件 ............................................... 222 13.4、读写内存流 ................................................... 224 13.5、path 类和 directory 类 ......................................... 225 13.6、读写注册表 ................................................... 226 第一章 C#与.NET 概述 本章主要目标 通过本章的学习,主要把握以下内容:  了解.NET Framework 的结构。  理解.NET Framework 的基本概念  CLR  JIT  CTS  MSIL  了解 .NET Framework 命名空间  了解 C#程序的基本结构  熟悉 Visual Studio.NET 的配置  熟悉如何创建一个控制台应用程 本章重点  .NET Framework 的体系结构及其组件。  熟悉 VS.NET 2005,并能够使用该工具来创建 C#应用程序。 本章难点  什么是 CLS、CTS、MSIL 和 JIT  VS.NET 2005 的环境配置 1.1、.NET Framework 简介 .NET Framework 是.NET 应用程序开发和运行的环境,提供了.NET 应用程序中使用的类库,类 似于 Java 的虚拟机。它不但使 Internet 上运行的应用程序更容易被开发,而且也可用于开发运行 于 Windows 桌面上的传统应用程序。 对.NET 的需求源于 Internet 的高速发展。应用程序的概念不再单一的的是桌面运行的可执行 文件,应用程序的模式已经发生了根本的变化,这些变化体现在以下方面。  应用程序随时、随地并且在任何设备中都可用  软件应作为一个服务来提供,电子邮件服务  应用程序必须具有互操作性,应用程序之间协同工作、数据交换的需求也越来越高。 MicroSoft.NET 就是在这样的大环境下诞生的,.NET 刺激了下一代计算机技术的发展,加速了 新一代 Internet 的来临,并为后一代应用程序的开发提供了便利。它为创建、部署以及管理安全、 强大、高效的应用程序提供了前所未有的最大支持。 .NET Framework 主要包含下列两个组件:  CLR:公共语言运行时  统一的类库集 重用代码,避免重复开发并缩短开发时间,这一直是软件开发人员的目标。.NET Framework 提 供了许多开发人员重用的基础类,包括线程、文件输入/输出 (I/O)、数据库支持、XML 解析和数据 结构等各个方面。并且这些类库可用于所有支持.NET Framework 的编程语言。通过 CLR 支持,任何.NET 语言都可以使用.NET 类库中的所有类,例如 VB.NET、C#、C++.NET,实际上使用的都是.NET 提供的 统一的基础类,这意味者对一种语言可用的功能对于其他任何.NET 语言也是可用的。 当然,除 CLR 和统一的类库集之外,还包括编程语言和 ASP.NET。其中支持.NET Framework 的 一些编程语言为 C#、VC++、VB.NET 和 Jscript。ASP.NET 主要用于简化 WEB 应用和服务的开发,不 但是传统意义上的应用和服务,而且包括移动设备上的应用和开发。 1.2、.NET Framework 体系结构 .NET Framework 是一个创建、部署和运行应用程序的多语言平台环境。它使程序员能够开发多 种平台的应用程序,其体系结构如图 1 所示: 图 1 .NET Framework 的体系结构 .NET Framework 使程序员能够开发用于 Windows、Internet、Pocket PC(个人掌上电脑)、 SmartPhone(智能电话)和 Table PC(平板电脑)等多种平台的应用程序。此外它还提供了多种编程语 言可供选择。 程序员使用.NET 支持的语言之一来开发应用程序。这些应用程序使用的基类库由.NET Framework 类库(FCL)提供,例如:要显示文本信息,可编写以下代码: System.Console.WriteLine(“.NET 体系结构”); 可以看到,只要支持.NET Framework,就支持.NET 的开发,这与支持 JVM 就支持 Java 开发的 机制是一样的,程序通过使 FCL 成为所有.NET 语言的公共类库来实现。 1.3、.NET Framework 的组件 .NET Framework主要由FCL(框架类库)和CLR(公共语言运行时)构成,图2显示了.NET Framework 的组件及其要素。 图 2 .NET Framework 的组件 其中:  Web Forms:与 HTML 类似,提供一组网页设计的类。  Web Services:包括一组设计 Web 服务的类,可用来构造网络服务器的应用程序。  WinForms::提供一组 Windows 的窗体应用程序设计和开发的类。  ASP.NET:提供一组用于创建 Web 应用程序的类。  ADO.NET:提供一些可与数据库进行交互的类。  XML 类启用 XML 操纵、搜索和转换。  基本框架类:提供基本功能,如 I/O、字符串处理、安全性管理和网络通信等。  通用语言规范 CLS:规定了使用所有语言都毕业遵循的基本语言功能的公共子集。  公共类型系统 CTS:描述如何在运行时声明、使用和管理像类、结构、枚举、接口等类型, 从而便于在各种语言之间使用这些类型。 1.3.1 CLR 和 MSIL 公共语言运行时是 .NET 框架应用程序的执行引擎。该名称不能准确反映它的全部功能。实际 上,公共语言运行时在组件的开发及运行过程中,都扮演着非常重要的角色。在组件运行过程中, 运行时负责管理内存分配、启动或删除线程和进程、实施安全性策略,同时满足当前组件对其他组 件的需求。在开发阶段,运行时的作用有些变化,与 COM 相比,运行时的自动化程度大为提高(比 如可自动执行内存管理),因而开发人员的工作变得非常轻松,尤其是映射功能将锐减开发人员将 业务逻辑程序转化成可复用组件的代码编写量。对编程语言而言,运行时这个概念并不新奇。实际 上每种编程语言都有自己的运行时。.NET 框架的关键作用在于,它提供了一个跨编程语言的统一编 程环境,这也是它能独树一帜的根本原因。 在编译使用.NET 框架创建的代码时,不是立即创建操作系统特定的本机代码,而是把代码编译 为微软中间语言(Microsoft Intermediate Language,MSIL)代码,这些 MSIL 代码不专用于任何 一种操作系统,也不专用于任何一种语言,有些类似于 JAVA 的字节码。C#及其他.NET 语言,如 VB.NET 在编译阶段都编译为这种语言。 1.3.2 通用语言规范(CLS)和公共类型系统(CTS) CLS: 规定所有 .NET 语言都应遵循的规则;生成可与其他语言互操作的应用程序。 CTS:公共类型系统,包含标准数据类型和准则集。 1.4、C#的常用命名空间 命名空间用来将具有相关功能的相似类在逻辑上进行分组。命名空间还可用来对相似类型数据 类型进行分组。可将命名空间视为容纳相似类型物品的某种容器。例如一所大学中的人员模型可能 包括以下几种类型:个人、学员、职员、大学生等。一般认为,命名空间有助于改善数据的构成, 从而使每个人都可以轻松地获得想要的数据。 在.NET Framework 中,所有的命名空间基本上从 System 中形成的。System 命名空间为根命名 空间,所有其他的命名空间都从根命名空间中形成。.NET Framework 包含了定义.NET 中使用的公共 数据类型,这些数据类型包括 Boolean、DateTime 和 Int32 等。此命名空间中包括的另一个重要的 数据类型为“Object”。Object 数据类型形成所有其他.NET 对象继承的基本对象。常用的命名空间 如表 1 所示: 表 1 System 下的二级命名空间 命名空间 说明 System.Drawing 处理图形和绘图,包括打印 System.Data 处理数据存取和管理,在定义 ADO.NET 技术中扮演重要角色 System.IO 管理对文件和流的同步和异步访问 System.Windows 处理基于窗体的窗口的创建 System.Reflection 包含从程序集读取元数据的类 System.Threading 包含用于多线程编程的类 System.Collections 包含定义各种对象集的接口和类 1.5、Visual Studio .NET 2005 简介 1.5.1、安装 Visual Studio 2005 ⑴ 首先将 Visual Studio.NET 的第一张光盘放入驱动器中,安装程序会自动启动,然后打开“Visual Studio.NET 安装程序”对话框,对话框中给出了 3 个选项,如图所示。装 ⑵⑵ 单击“安装 Visual Studio 2005”进入加载安装组件,如图所示。框中给出了 3 个选项,如 ⑶ 单击“下一步” 按钮,进入安装程序的起始页,如图所示。 ⑷ 选择“接受”单选按钮,并在“名称”文本框中输入用户名,然后单击“下一步”按钮,进入安 装程序的选项页,如图所示。 ⑸ 选择一单选按钮,单击“安装”,进入正式安装,,如图所示。 安装程序在安装的同时显示.NET 的功能和安装进度,完成第一张安装光盘的安装之后,按安装 提示顺序在光盘驱动器中插入第 2 张、第 3 张安装光盘,并单击“确定”按钮,则分别复制各张光 盘中的内容。 ⑹ 完成第 3 张安装光盘的安装后,会打开一个对话框,提示用户安装完成,并允许用户查看安装日 志,单击“完成”按钮,即完成安装。 1.6、Visual Studio .NET 2005 集成开发环境 1.6.1 C#的启动 选择“开始”→“程序” →“Microsoft Visual Studio 2005”→“Microsoft Visual Studio 2005” 命令,打开“起始页”窗口。 要启动 VC#开发环境有两种方式,一种是单击“起始页”上的“打开项目”按钮,选择现在已 存在的 C#项目文件,另一种是单击“起始页”上的“新建项目”按钮,则打开一个“新建项目”对 话框,如图所示。 在“项目类型”框中选择“Visual C# 项目”,然后在“模板”框中任意选择一个项目模板(若 是开发 Windows 应用项目,则选择“Windows 应用程序”),并在下面“名称”文本框中设置新项 目名称,然后单击“确定”按钮,一个新的 VC#的项目就创建了,并进入 Visual Studio.NET 强大 的集成开发环境,如图所示。 1.6.2 C#集成开发环境 C#的集成开发环境集成了设计、开发、编辑、测试和调试的多种功能,使得开发人员能够方便、 快速地开发应用程序。 集成开发环境标题下面是菜单栏和工具栏,中央工 作区是用来设计程序界面的窗体设计器和代码编辑窗 口。除此之外,即成开发环境的四周,有很多浮动窗口。 1. 菜单栏 在菜单栏中,共有 11 个菜单标题,每个菜单标题都 有一个下拉式菜单。 2. 工具栏 工具栏是由多个图标按钮组成的,可提供对常用命 令的快速访问。除了在菜单栏下面显示的标准工具栏外, 还有 Web 工具栏、控件布局工具栏等多种特定功能工具 栏。 3. 工具箱 工具箱中包含了建立应用程序的 各种控件以及非图形化的组件。工具 箱由不同的选项卡组成,各类控件、 组件分别放在“数据”、“组件”、 “Windows 窗体”、“剪贴板循环”、 “常规”5 个选项卡下面。如图所示。 4. 解决方案资源管理器 在 C#中,项目是一个独立的编程单位, 其中包含一些相关的文件,若干个项目 就组成了一个解决方案。 在 C#中所有包含 C#代码的源文件都 是以 .cs 为扩展名,在解决方案资 源管理器中显示这个文件。 5. 属性窗口 属性窗口如图所示,它用于 显示和设置所选定的控件或者窗 体等对象的属性。在应用程序设 计时,可通过属性窗口设置或修 改对象的属性。 属性窗口由以下部分组成: (1)对象列表框 (2)选项按钮 (3)属性列表框 6. 代码编辑窗口 代码编辑窗口是专门用来 进行代码设计的窗口,各种事 件过程、模块和类等源程序代 码的编写和修改均在此窗口进 行。如图所示。 1.6.3 Visual Studio .NET 2005 的功能 Visual Studio .NET 2005 是一套完整的开发工具,用于构建高性能的桌面应用程序、XML Web Services、移动应用程序和 ASP Web 应用程序。而且,使用 Visual Studio.NET2005 还可以使基 于团队进行的企业解决方案的设计、开发和部署更加简单。Visual Studio.NET 2005 是一个开发工 具包,他明显提高了开发人员的开发效率。 这个集成开发环境(IDE)主要包括:  自定义 IDE  窗口管理  Visual Studio .NET 中的窗口 项目:包含指向最近用过的项目链接。对于最近查看过的项目,还会显示相应的修改日期。该 选项卡有“打开项目”和“新建项目”两个按钮,分别用于打开或新建一个项目 联机资源:提供在线的 MSDN 更新链接。当计算机连接到 Internet 时,只要选定窗格中的链接, Visual Studio.NET 2005 IDE 就会自动下载更新。 示例配置文件:用户可以在此获取与选定的配置文件和提供的关键字相关的示例。 新增功能:此部分显示 Visual Studio.NET 2005 IDE 的新增功能和更新信息,包括示例下载和新的 编程工具。 网上社区:此部分包含利用新闻组、网页和其他联机资源联系其他软件开发人员的方式。 标题新闻:这部分提供浏览文章、新闻及提供指南的方式。 联机搜索:这部分用于联机浏览 MSDN(微软在线库)。 下载:这部分允许用户获取代码示例和更新。 XML Web Services:允许用户搜索已注册的 XML Web Services,以便用于应用程序开发和发布 XML Web Services。 WEB 宿主:这部分为用户提供机会以使用 Visual Studio.NET 扩展其解决方案。该链接让我们 可以访问一系列 ASP.NET Web 托管商,这些托管商提供免费的 Web 空间,并且集成了 Visual Studio.NET,以便通过非常简单的向导简化整套应用程序的部署。 我的配制文件:包含自动设置“键盘方案”、“窗口布局”和“帮助筛选器”选项的默认配置文 件。 选项卡式窗口管理 Visual Studio.NET 2005 让我们每次都可在屏幕上轻松地查看更多的代码行。它包括大量的工 具和选项,可帮助用户管理集成开发环境(IDE)中的窗口。Visual Studio.NET 2005 主要功能有:  自动隐藏  设置方法:单击窗口上的“关闭”按钮旁边的图钉。  可停靠窗口:.NET IDE 提供了灵活的可停靠窗口,这些窗口可放置在工作区的任一位置。  选项卡式文档:该功能可以使文档窗口在 IDE 中一起显示为选项卡。  IDE 定位:使用向前、向后按钮可以轻松定位打开的文档。  收藏夹:借助于收藏夹,可以访问使用 Visual Studio.NET 2005 IDE 添加了书签的站点。  菜单栏:提供了各种命令可用于管理 IDE 以及开发、调试和执行程序。  Visual Studio .NET 中的窗口  解决方案资源管理器:显示解决方案文件,该文件以树形结构显示所有项目以及对应的引用和 文件。  工具箱:包含可添加到 Visual Studio 项目的工具或项。  服务器资源管理器:是一个共享工具窗格,该窗格可帮助开发人员对有权限进入的任何计算机 上的资源进行访问和操纵。“服务器资源管理器”可用于将计算机连接到服务器并查看其资源, 这包括消息队列、性能计数器、服务、进程、事件日志和数据库对象。另外,“服务器资源管理 器”还能以编程的方式在 Visual Studio .NET 2005 应用程序中引用服务器组件和资源,还可 以帮助访问 XML Web Services.  类视图:以树形方式显示一个项目的命名空间、函数、方法和类的逻辑视图。  属性窗格:用于查看与设置控件、类和项目的属性。  使用动态帮助:MSDN 在线库提供的所有主题和帮助都可以在“动态帮助”窗格中获得。 1.7、Visual Studio.NET 2005 环境设置 利用“工具”菜单中的“选项”对话框可定义各种设置。主要可以设置如下选项:  改变窗口的默认外观及其布局  指定保存项目的默认位置  指定常用命令的快捷键  确定“任务列表”和“解决方案资源管理器”的默认行为  确定在生成项目或项目的解决方案时是否自动保存已更改的文件 1.8、C# 应用程序文件夹结构 在创建项目时,Visual Studio.NET 2005 自动创建一个与项目同名的文件夹,此处为“Hello World” 。该文件夹包含项目文件“Hello World.csproj”和其他关联文件。每个新项目都创建了 bin 和 obj 两个文件夹。这两个文件夹下都有一个 Debug 子目录,其中包含可执行文件 HelloWorld.exe。在“解决方案资源管理器”中启用“显示所有文件”选项,可查看“Hello World” 项目的结构。项目的目录结构如图所示: 图 4 “Hello World”的文件夹结构 1.9、创建和编译 HelloWorld 控制台应用程序 (1)用记事本创建一个 C#控制台程序 示例程序:如何利用记事本创建一个 C#应用程序。 using System; //导入 System 命名空间 namespace Notepad //声明命名空间 Notepad { class HelloWorld //声明 HelloWorld 类 { public static void Main() //程序入口点,Main 的返回类型为 void { Console.WriteLine("Hello World"); //控制台类的 WriteLine()方法用于显示输出结果 } } }  说明 using System:引入命名空间 System 中的类文件,使其存在的方法成为程序的一部分。 namespace Notepad:定义一个命名空间 Notepad,表示生成的类 HelloWorld 放在该目录中。 class HelloWorld:定义一个类 HelloWorld。 public static void Main():此方法是应用程序的入口,此方法声明为 public satic,表示该方 法可以被程序的任何地方访问。 Console.WriteLine():向控制台输出数据。如果从控制台接受单个字符数据,可以采用 Console.ReadLine()方法。  执行步骤 将文件保存为 Example.cs。 进入 DOS 界面(如下图),切换到存储 Example.cs 程序的目录中,键入命令: csc Example.cs 进行编译生成相应的 Example.exe 文件  运行:Example 图 dos 界面 (2)用 Visual Studio.NET2005 创建一个 C#控制台应用程序。 主要步骤: 单击“开始”→“程序”,选择“Visual Stdio.NET 2003”,启动 Visual Studio .NET 2005。 选择“文件”→“新建”→“项目”,出现图 6 所示的窗口。Visual Stdio.NET2005。提供以 下各种不同的项目类型:  Visual Basic 项目:用 VB.NET 作为编程语言开发项目。  Visual C#项目:用 C#作为编程语言开发项目。  安装和部署项目:开发可用于安装和部署应用程序的项目。  其他项目:包括可用于创建数据库项目、分布式应用程序、Web 应用程序测试项目和 Visual Studio 分析器项目等的各种项目类型。  Visual Studio 解决方案:创建不包含任何项目的空解决方案。 (3) 在其中选择“Visual C#”作为项目类型,选择“控制台应用程序”作为模板。 图 6 新建项目  点击“确定”后,“解决方案资源管理器”将自动显示该项目的内容: HelloWorld.csproj:执行应用程序时运行的启动项目。因此,在解决方案资源管理器中, 此文件显示为粗体。  App.ico:与项目关联的默认空白图标文件。  Assemblyinfo.cs:包含通用程序集信息。所有程序集信息都放置在此文件中。  Class1.cs:类声明的文件,也是我们编程中书写代码的主要文件,并且通常我们会将其重 新命名。  在 Class1.cs 的 main 方法中添加如下语句: Console.WriteLine("Hello World");  编译生成 C# 项目。 小结  .NET Framework 由 .NET Framework 类库和公共语言运行时两个主要组件组成  CLR 是管理用户代码执行的现代运行时环境,它提供 JIT 编译、内存管理、异常管理和调 试等方面的服务  CTS 定义声明、定义和管理所有类型所遵循的规则,而无需考虑源语言  CLS 是所有针对 .NET 的编译器都必须支持的一组最低标准,以确保语言的互操作性  命名空间是一组包含相关方法的相似类,专门用于避免类与类之间的名称冲突  即时 (JIT) 编译器将 MSIL 代码编译为特定于目标操作系统和计算机体系结构的本机代码  Visual Studio .NET 2005 是用于创建、编写、运行和调试程序的集成开发环境  用户使用 VS.NET 中的“起始页”可以自定义 IDE。IDE 还包含许多有用的链接,如最近 的项目、下载和联机新闻组等  VS.NET 中的程序组成项目和解决方案。项目是一组相关的文件,解决方案则是一组相关的 项目  Visual Studio .NET 为用户提供了用于浏览文件的各种窗口 ,“动态帮助”窗口为用户 提供了与当前光标所在位置相关的文章 第二章 C#数据类型、运算符和表达式 本章主要目标 通过本章的学习,主要把握以下内容:  C#的主要数据类型以及类型标识符  数据类型的转换  常量和变量、变量的赋值  C#常用的运算符 本章重点  熟悉 C#的数据类型、运算符和表达式 本章难点  装箱和拆箱的概念及作用 2.1、C#中的数据类型 C#中数据类型主要分为两大类:值类型和引用类型。这里我们先讲解这两种类型,然后再讨论 数据类型之间的转换。 2.1.1 值类型 C#中值类型包括三种:简单类型、结构类型和枚举类型。不同的类型是不同数据的集合,不同 的类型在 C#中用不同的类型标识符来表示。这里我们只介绍简单类型,结构类型和枚举类型将在后 面介绍。 简单类型包括整数类型、浮点类型、小数类型、字符类型和布尔类型等。 1.整数类型 整数类型的数据值只能是整数,计算机语言所提供的数据类型有一定的范围。 类型标识符 描述 可表示的数值范围 sbyte 8 位有符号整数 -128 ~ +127 byte 8 位无符号整数 0 ~ 255 short 16 位有符号整数 -32768 ~ +32767 ushort 16 位无符号整数 0 ~ 65535 int 32 位有符号整数 -2147483648 ~ +2147483647 uint 32 位无符号整数 0 ~ 232-1 long 64 位有符号整数 -9223372036854775805 ~ +9223372036854775807 ulong 64 位无符号整数 0 ~ 264-1 2. 浮点类型 浮点类型的数据包含两种:单精度浮点型(float)和双精度浮点型(double),其区别在于取 值范围和精度的不同。 float 类型是 32 位宽,double 类型是 64 位宽。 单精度:取值范围在+ 1.5×10-45 ~ 3.4×1038 之间,精度为 7 位数。 双精度:取值范围为+5.0×-324 ~ 1.7×10308 之间,精度为 15~16 位数。 3.小数类型 小数类型(decimal)占用 16 个字节(128 位),主要为了满足需要高精度的财务和金融计算 机领域。 小数类型数据的取值范围和精度如下: 取值范围在+ 1.0×10-28 ~ 7.9×1028 之间,精度为 29 位数。 注意:小数类型数据的后面必须跟 m 或者 M 后缀来表示它是 decimal 类型的,如 3.14m、0.28m 等,否则就会被解释成标准的浮点类型数据,导致数据类型不匹配。 例: 138f 代表 float 类型的数值 138.0 518u 代表 uint 类型的数值 518 36897123ul 代表 ulong 类型的数值 36897123 22.1m 代表 decimal 类型的数值 22.1 12.68 代表 double 类型的数值 12.68 36 代表 int 类型的数值 36 刚开始学习,不可能一下子掌握这么多数值类型,先牢记以下几种: int 型 : 凡是要表示带符号的整数时,先考虑使用 int 型; uint 型 : 凡是需要不带符号的整数时,先考虑使用 uint 型; double 型:凡是需要做科学计算,并且精度要求不是很高时,考虑使用 double 型。 4. 字符类型 字符类型的类型标识符是 char,采用 Unicode 字符集。 凡是在单引号中的一个字符,就是一个字符常数, 如: ‘你’、 ‘A’、 ‘?’、 ‘6’、 ‘2’ 注意: 在表示一个字符常数时,单引号内的有效字符数量必须且只能是一个,并且不能是单引 号或者反斜杠(\)。 为了表示单引号和反斜杠等特殊的字符常数,C#提供了转义符。 C#常用的转义符 转义符 字符名称 \' 单引号 \" 双引号 \\ 反斜杠 \0 空字符(Null) \a 发出一个警告 \b 倒退一个字符 \f 换页 \n 新的一行 \r 换行并移到同一行的最前面 \t 水平方向的 Tab \v 垂直方向的 Tab 如 Console.WriteLine(“Hello,”+”\’”+”打印出单引号“+”\‘“); 例: using System; class StrDemo { static void Main( ) { Console.WriteLine(“FirstLine\nSecondLine”); Console.WriteLine(“A\tB\tC”); Console.WriteLine(“D\tE\tF”); } } 5.布尔类型 布尔类型的类型标识符是 bool。 布尔类型常数只有两种值:true(代表“真”)和 false(代表“假”)。 布尔类型数据主要应用在流程控制中。 例: bool b=5>3; //b 的值为 true; b=false; 2.1.2 引用类型 1. object 类 object 类是系统提供的基类型,是所有类型的基类,C#中所有的类型都直接或间接派生于对象 类型。 对于任一个 object 变量,均可以赋以任何类型的值。 double d=3.14; object obj1; obj1=d; obj1=‘k’; 对于 object 类型的变量,声明必须使用 object 关键字。 2. string 类 一个字符串是被双引号包含的一系列字符。 string 类是专门用于对字符串进行操作的。 如: string str1="中国,"; string str2="你好!"; string str3=str1+str2; //这相当于 str3="中国,你好!" char c=str3[0]; //取出 str3 的第一个字符,即“中”字。 C#支持两种形式的字符串常数。 (1)常规字符串常数 如: “this is a test” “C#程序设计教程” 例: using System; class StrDemo { static void Main( ) { Console.WriteLine("First\0line\nSecond\0line"); Console.WriteLine("你好\a"); Console.WriteLine(“1\t2\t3"); Console.WriteLine(“*\t**\t***”); } } 例: using System; class StringDemo { public static void Main( ) { string str1=“A string”; string str2="Another string."; Console.WriteLine(“{0}\n{1}”,str1,str2); } } 运行结果如下: (2)逐字字符串常数 逐字字符串常数以@开头,后跟一对双引号,在双引号中放入字符。如: @“电子高专” @“This is a book.” 逐字字符串常数同常规字符串常数的区别: 在逐字字符串常数的双引号中,每个字符都代表其最原始的意义,在逐字字符串常数中没有转 义字符。 注意:如果要包含双引号("),就必须在一行中使用两个双引号("")。 例: string str1; //定义字符串类型 string str2="hello, world"; //规则字符串常数:hello, world string str3=@"hello, world"; //逐字字符串常数:hello, world string str4="hello \t world"; //hello world string str5=@ "hello \t world"; //hello \t world string str6=“He said\" Hello \" to you"; //Tom said "Hello" to you string str7=@“He said ""Hello"" to you"; //Tom said "Hello" to you 例: using System; class Test { static void Main( ) { Console.WriteLine(@"This is a verbatim string literal that spans several lines. "); Console.WriteLine(@"Here is some tabbed output: 1 2 3 4 5 6 7 8 "); Console.WriteLine(@"Programmers say, " "I like C#"""); } } 运行结果如下: 2.1.3 类型转换 数据类型在一定条件下是可以相互转换的。 C#允许使用两种转换的方式:隐式转换和显式转换。 1. 隐式转换 隐式转换是系统默认的、不需要加以声明就可以进行的转换。 隐式数据转换的使用方法如下: int i=518; //a 为整型数据 long b=i; //b 为长整型数据 float f=i; //f 为单精度浮点型数据 2.显式转换 显式转换又叫强制类型转换,显式转换要明确指定转换类型。 显式转换格式: (类型标识符)表达式 意义为:将表达式的值的类型转换为类型标识符的类型。 比如: (char)65 //把 int 类型的 65 转换成 char 类型 注意:(1)显式转换可能会导致错误。 (2)对于将 float,double, decimal 转换为整数,将通过舍 入 得到最接近的整型值,如果这个整型值超出目标域,则出现转换异常。 比如: (int)6.28m // 转换的结果为 6 (int) 3e25f //将产生溢出错误 2.1.4 装箱和拆箱 1.装箱转换 装箱转换是指将一个值类型的数据隐式地转换成一个对象类型(object)的数据。 例如:下面的两条语句就执行了装箱转换: int i=518; object obj=i; 例:在程序中执行装箱转换。 using System; class BoxingDemo { static void Main( ) { Console.WriteLine("执行装箱转换:"); int k=200; object obj=k; k=300; Console.WriteLine("obj={0}",obj); Console.WriteLine("k={0}", k); } } 2.拆箱转换 和装箱相反,拆箱转换是指将一个对象类型的数据显式地转换成一个值类型数据。 例如: object obj=228; int k=(int)obj; 例:在程序中使用拆箱转换。 using System class UnboxingDemo { static void Main( ) { int k=228; object obj=k; //装箱转换 int j=(int ) obj; //拆箱转换 Console.WriteLine("k={0}\tobj={1}\tj={2}", k, obj, j); } } 该程序执行后,输出结果如下: k=228 obj=228 j=228 2.2 常量与变量 2.2.1 常量 在 C#中,常量在程序的运行过程中其值是不能改变的,例如,数字 100 就是一个常量,这样的 常量一般被称作常数。 声明常量的格式: const 类型标识符 常量名 = 表达式; 例: const double PI=3.14159265; double r=5.2; double s=PI*r*r; Console.Write(“面积={0}”,s); 常量特点:  在程序中,常量只能被赋予初始值。  定义常量时,表达式中的运算符对象只允许出现常量和常数,不能有变量存在。 例如: int b=18; const int a=26; const int k=b+10; //错误,表达式中不允许出现变量 const int d=a+23; //正确,因为 a 是常量 a=56; //错误,不能修改常量的值 2.2.2 变量 变量是程序运行过程中用于存放数据的存储单元。 变量的值在程序的运行过程中可以改变。 1.变量的定义 在定义变量时,首先必须给每一个变量起名,称为变量名,变量名代表存储地址。 变量的类型决定了存储在变量中的数值的类型。 变量定义格式: 类型标识符 变量名 1,变量名 2,„„ 例: double fsum; string strName; char b; int x; 注意:C#规定,任何变量在使用前,必须先定义,后使用。 2.变量的赋值 变量的赋值,就是将数据保存到变量所代表的存储单元中的过程。 格式: 变量名=表达式; 意义:计算表达式的值,然后将这个值赋予变量。 例: double nAverage; int nAgeSum; nAgeSum=210; //给 nAgeSum 变量赋予数值 210 在程序中,可以给一个变量多次赋值。变量的当值等于最近一次给变量所赋的值。 如: nAgeSum=68; //这时 nAgeSum 等于 68 nAgeSum=36+24; //这时 nAgeSum 等于 60 nAgeSum= nAgeSum+40; //这这时 nAgeSum 等于 100 在对变量进行赋值时,表达式的值的类型必须同变量的类型相同。 string sName; int nScore; sName="Jack"; //正确 sName="Tom"; //正确 nScore=98; sName=5; //错误, 不能将整数赋予字符串对象 nScore="Hello"; //错误, 不能字符串赋予整型变量 3.变量的初始化 在定义变量的同时,可以对变量赋值,称为变量的初始化。 对变量进行初始化的格式如下: 类型标识符 变量名=表达式; 例: string str=“This is a book”; 2.3 运算符和表达式 2.3.1 运算符 运算符是表示各种不同运算的符号。 C#中,运算符有多个级别,如下表 2 所示: 表 2 运算符 类别 运算符 说明 表达式 算术 运算符 + 执行加法运算(如果两个操作数是字符串, 则该运算符用作字符串连接运算符,将一个 字符串添加到另一个字符串的末尾) 操作数 1 + 操作数 2 - 执行减法运算 操作数 1 — 操作数 2 * 执行乘法运算 操作数 1 * 操作数 2 / 执行除法运算 操作数 1 / 操作数 2 % 获得进行除法运算后的余数 操作数 1 % 操作数 2 ++ 将操作数加 1 操作数++ 或++操作数 -- 将操作数减 1 操作数—或—操作数 ~ 将一个数按位取反 ~操作数 比较 运算符 > 检查一个数是否大于另一个数 操作数 1 > 操作数 2 < 检查一个数是否小于另一个数 操作数 1 < 操作数 2 >= 检查一个数是否大于或等于另一个数 操作数 1 >= 操作数 2 <= 检查一个数是否小于或等于另一个数 操作数 1 <= 操作数 2 == 检查两个值是否相等 操作数 1 == 操作数 2 != 检查两个值是否不相等 操作数 1 != 操作数 2 条件 运算符 ?: 检查给出的第一个表达式 expression 是否 为真。如果为真,则计算 operand1,否则计 算 operand2。这是唯一带有三个操作数的运 算符 表达式? 操作数 1:操作数 2 赋值 运算符 = 给变量赋值 操作数 1 = 操作数 2 逻辑 运算符 && 对两个表达式执行逻辑“与”运算 操作数 1 && 操作数 2 || 对两个表达式执行逻辑“或”运算 操作数 1 || 操作数 2 ! 对两个表达式执行逻辑“非”运算 ! 操作数 强制类型 转换符 ( ) 将操作数强制转换为给定的数据类型 (数据类型) 操作数 成员 访问符 . 用于访问数据结构的成员 数据结构.成员 快捷运算符 += 运算结果 = 操作数 1 + 操作数 2 -= 运算结果 = 操作数 1 - 操作数 2 *= 运算结果 = 操作数 1 * 操作数 2 /= 运算结果 = 操作数 1 / 操作数 2 %= 运算结果 = 操作数 1%操作数 2 1.算术运算符 算术运算符用于对操作数进行算术运算。C#的算术运算符同数学中的算术运算符是很相似的。 例: using System; class ModDemo { static void Main( ) { int iresult,irem; double dreult,drem; iresult=10/3; irem=10%3; dresult=10.0/3.0; drem=10.0%3.0; Console.WriteLine("10/3={0}\t 10%3={1}", iresult, irem); Console.WriteLine("10.0/3.0={0}\t10.0%3.0={1}",dresult, drem); } } 程序的输出如下所示: 10/3=3 10%3=1 10.0/3.0=3.33333333333333 10.0%3.0=1 特殊的算术运算符:++(自增运算符) --(自减速运算符) 作用:使变量的值自动增加 1 或者减少 1。 例: x=x+1; 可以被写成 ++x; //前缀格式 或者 x++; //后缀格式 当一个自增或自减运算符在它的操作数前面时,C#将在取得操作数的值前执行自增或自减操作。 如果运算符在操作数的后面,C#将先取得操作数的值,然后进行自增或自减运算。 例: x=8; y=++x; 在这种情况下,x 和 y 被赋值为 9。 但是,如果代码如下所写: x=8; y=x++; 那么 y 被赋值为 8, x 被赋值为 9 。 例: using System; class Test { static void Main( ) {int x=5; int y=x--; Console.WriteLine("y={0}", y); y=--x; Console.WriteLine("y={0}", y); } } 结果: y=5 y=3 注意:++、 --只能用变量,而不能用于常量或表达式,例如 5++或--(x+y)都是错误的。 例: using System; class count { static void Main() { int Val1=2; int Val2=3; Console.WriteLine(“Val1*Val2={0}”, Val1*Val2); Console.WriteLine(“Val1/Val2={0}”, Val1/Val2); Console.WriteLine(“Val1%Val2={0}”, Val1%Val2); Console.WriteLine( ++Val1); Console.WriteLine(--Val2); Console.WriteLine(Val1++); Console.WriteLine(Val2--); } } 2.赋值运算符 赋值运算符用于将一个数据赋予一个变量,赋值操作符的左操作数必须是一个变量,赋值结果 是将一个新的数值存放在变量所指示的内存空间中。 例如: int x=8; x=x+x; x=16-x; 可以把表达式的值通过复合赋值运算符赋予变量,这时复合赋值运算右边的表达式是作为一个 整体参加运算的。 例: int a=8,b=3; a%=b*2-5; /*相当于 a%=(b*2-5),它与 a=a%(b*2-5)是等价的。*/ 对变量可以进行连续赋值。 例: int z=3; x=y=z; //等价于 x=(y=z)。 3. 关系运算符 关系运算符用于比较两个值的大小,关系运算的结果不是 true 就是 false。 例: bool a=‘a’<‘b’; //a 的值为 true a=3+6>5-2 //a 的值为 false 例: using System; class RelaOpr { static void Main( ) { int a=50; int x=30; int y=60; int b; b=x+y; bool j; j=a==b-40; Console.WriteLine(“a=b is {0}”, j); } } 该程序运行后,输出结果为:a=b is True 4.逻辑运算符 逻辑运算符用于表示两个布尔值之间的逻辑关系,逻辑运算结果是布尔类型。 逻辑非(!):运算的结果是原先的运算结果的逆。 逻辑与(&&):只有两个运算对象都为 true,结果才为 true;只要其中有一个是 false,结果 就为 false。 逻辑或(|| ):只要两个运算对象中有一个是 true,结果就为 true,只有两个条件均为 false, 结果才为 false。 当需要多个判定条件时,可以很方便地使用逻辑运算符将关系表达式连接起来。 例: x>y&&x>0 如果表达式中同时存在着多个逻辑运算符,逻辑非的优先级最高,逻辑与的优先级高于逻辑或。 例: 3>2||!(5-3<6)&&’a’<‘b’ 5. 位运算符 (1) “~”运算符 把二进制数的 0 转换为 1,1 转换为 0。 例:6 的二进制表示:00000110 ~6 的结果: 11111001 (2) “&”运算符 0&0=0 0&1=0 1&0=0 1&1=1 例:7 的二进制表示: 00000111 11 的二进制表示:00001011 ————————————— “&”运算的结果是: 00000011 即:7&11=3 (3) “|”运算符 0| 0=0 0|1=1 1|0=1 1|1=1 例:7 的二进制表示: 00000111 11 的二进制表示:00001011 ————————————— “|”运算的结果是: 00001111 即:7&11=15 (4) “^”运算符 0^0=0 0^1=1 1^0=1 1^1=0 例:7 的二进制表示: 00000111 11 的二进制表示:00001011 ————————————— “^”运算的结果是: 00001100 即:7^11=12 (5) “<<”运算符 二进制位全部按位左移,高位被丢弃,低位顺序补 0。 例:7 的二进制表示: 00000111 7<<1 结果是 00001110(十进制是 14 ) (6) “>>”运算符 二进制位全部按位右移。 例:7 的二进制表示: 00000111 7>>1 结果是 00000011(十进制是 3) 6. 条件运算符 格式: 操作数 1?操作数 2:操作数 3 含义:进行条件运算时,首先判断问号前面的布尔值是 true 还是 false,如果是 true,则值等 于操作数 2 的值;如果为 false,则值等于操作数 3 的值。 例如:条件表达式“6>8?15+a:39”,由于 6>8 的值为 false,所以整个表达式的值是 39。 7. 其他运算符 (1) 字符串连接符(+) 就是将两个字符串连接在一起,形成新的字符串。 比如: “abc”+“efg” //结果是 abcefg “36812”+“3.14” // 结果是 368123.14 (2) is 运算符 is 运算符用于检查表达式是否指定的类型,如果是,结果为 true,否则结果为 false。 例如: int k=2; bool isTest=k is int; //isTest=true (3) sizeof 运算符 sizeof 运算符用于获得值类型数据在内存占用的字节数。 例如: int a=sizeof(double); //a=8 2.3.2 表达式 表达式是运算符、常量和变量等组成的符号序列。 1.算术表达式 算术表达式是用算术运算符将运算对象连接起来的符合语法规则的式子。 自增运算符和自减运算符的优先级别高于其他的算术运算符。 例如表达式 8+x++,应看作 8+(x++)。如果 x 的原值是 6,则表达式 8+x++的值是 14,运算结束 后 x 的值是 7。 2.赋值表达式 由赋值运算符将变量和表达式连接起来的式子称为赋值表达式。 例如: y=x=8*8+3 这个赋值表达式的值是 67。由于赋值运算符的结合性是自右至左的,所以 y=x=8*8+3 和 y=(x=8*8+3)是等价的。 3.关系表达式 用关系运算符将两个表达式连接起来的式子称为关系表达式。关系表达式的值是布尔类型,即 真(true)或假(false)。 例如: x=8; y=6; z=x>y+3; //结果为 false a=x>y&&z; //结果为 false 4.逻辑表达式 用逻辑运算符将关系表达式或者逻辑值连接起来的式子称为逻辑表达式。逻辑表达式的值只能 取 true 或 false。 三个逻辑运算符的运算顺序为“逻辑非”最高,其次是“逻辑与”,最后为“逻辑或”。 例如: !(3>6)||(5<8)&&(2>=9)||(7>=1) 5.条件表达式 由条件运算符和表达式组成的式子称为条件表达式。 例如:8>3?5:2; 其结果为 5,因为 8>3 为 true,则整个表达式的值为“:”前面表达式的值,这里是常数 5。 例:已知: int i = 0; bool result = false result = (++i) + i == 2?true:false; 则变量 result 的值为? 注意:表达式 i++和++i 的区别。 在实际运算中,往往有多个运算符参与运算,这时要把握一个问题:优先级与结合性问题。在 C#中,优先级和结合性如下表 3 所示: 表 3 优先级和结合性 优先级 说明 运算符 结合性 1 括号 ( ) 从左到右 2 自加/自减运算符 ++/-- 从右到左 3 乘法运算符 除法运算符 取模运算符 * / % 从左到右 4 加法运算符 减法运算符 + - 从左到右 5 小于 小于等于 大于 < <= > 从左到右 大于等于 >= 6 等于 不等于 = != 从左到右 从左到右 7 逻辑与 && 从左到右 8 逻辑或 || 从左到右 9 赋值运算符和快捷运算符 = += *= /= %= -= 从右到左 第三章 C#结构化程序设计 本章主要目标 通过本章的学习,主要把握以下内容:  C#程序设计相关的基础知识  C#程序的基本结构、标识符的相关概念  C#程序的编译和执行  控制台的输入/输出操作  使用 C#中的运算符,选择结构和循环结构  定义和使用数组,了解结构和枚举  熟悉 C#中的预处理指令  熟悉 C#中的字符串处理 本章重点  掌握 C#程序设计相关的基础知识  理解常用的四个有关控制台输入、输出方法  熟悉三种控制结构:顺序结构、选择结构和循环结构  熟悉数组的定义和使用  熟悉结构和枚举 本章难点  C#的结构和枚举  Main()方法、using  WriteLine( )和 Writel( )方法中各种参数的含义  ReadLine( )和 Read( )方法的区别 3.1 C#程序结构 3.1.1 第一个 C#程序 创建 C#控制台应用程序,首先选择【文件】|【新建】|【项目】命令打开【新建项目】对话框。 最后,单击【确定】按钮,关闭【新建项目】对话框,让 Visual Studio.NET 为用户自动生成 代码。 删除窗口中的代码,输入如下所示的代码。 using System; class Welcome { static void Main( ) {Console.WriteLine(“欢迎使用 C#”); //运行后在窗口中显示的字符串 } } 3.1.2 编译和执行程序 在编译程序时,将会打开一个输出窗口显示编译过程中所遇到的错误和警告等信息。 在 Visual Studio.中,可以采用两种方式运行程序:一种是调试运行,通过使用【调试】|【启 动】命令或工具栏的调试按钮 或者直接按下 F5 键;另一种是不进行调试而直接运行,使用【调 试】|【开始执行】命令或 Ctrl+F5 键。 3.1.3 C#程序结构分析 1. 命名空间 using System 语句表示导入 System 命名空间。 Console.WriteLine(“欢迎使用 C#” )这条语句中的 Console 是 System 命名空间中包含的系 统类库中定义好的一个类,它代表系统控制台,即字符界面的输入和输出。 C#程序是用命名空间来组织代码,要访问某个命名空间中的类或对象,必须用如下语法: 命名空间.类名 由于 Console 类位于 System 命名空间中,所以在访问 Console 类时,完整的写法应该是: System.Console 但是,在程序的第一行,使用了: using System; 这条语句用 using 语句导入 System 命名空间,这样在程序中可以直接使用 Sytem 命名空间中的 类或对象,所以直接写 Console 即可。 2、类 C#要求其程序中的每一个元素都要属于一个类。如: class Welcome 声明了一个类,类的名字叫 Welcome。这个程序的功能就是依靠它来完成的。C# 程序由大括号“{”和“}”构成,程序中每一对大括号“{ }”构成一个块。 注意: C#程序中的语句以“;”表示结束。 3.Main( )方法 程序的入口从下面的代码开始: static void Main( ) 这行代码所定义的其实是类 Welcome 的一个静态方法,C#规定,名字为 Main( )的静态方法就 是程序的入口。方法的执行从左括号“{”开始,到右括号“}”结束。 4.注释 在 C#语言中,提供了两种注释方法: (1) 每一行中“//”后面的内容作为注释内容,该方式 只对本行生效; (2) 需要多行注释的时候,在第一行之前使用“/*”, 在末尾一行之后使用“*/”,也就是说被“/*”与*/所包含 的内容都作为注释内容。 通过上面的分析,可以看出 C#程序的基本结构如下: /*导入.NET 系统类库提供的命名空间 System*/ using System; class Welcome //定义类 { static void Main( ) /*程序的入口。其中 static 表示 Main( )方法 是一个静态方法,void 表示该方法没有返回值*/ { Cosole.WriteLine("欢迎使用 C#"); //输出 欢迎使用 C# } } 注意: C#语言对大小写是敏感的 一个程序不允许出现两个甚至两个以下的 Main( )方法 C#程序中的源代码被包含在“{”与“}”之间,必须一一对应 3.1.4 标识符 标识符(identifier)是一串字符,在程序中作为各种标识,用来代表一个名字。并不是任何 一串字符都可以作为 C#的标识符。 C#的标识符有如下规则: (1)一个合法的 C#标识符,是以字母或者下划线开头、其后可以跟任意个字母、数字或者下 划线 。 (2)C#的标识符严格区分大小写,即使两个标识符的区别仅仅字母的大小写不同,也认为是两 完全不同的标识符。 (3)关键字也可以作为标识符,只要在关键字前加上@前缀。 直接使用关键字作为标识符是不允许的,比如: uint lock 通过给关键字加“@”前缀,它们就变成合法的标识符了。 @uint @operator 3.2 输入/输出操作 控制台(console)输入/输出主要通过命名空间 System 中的类 Console 来实现,它提供了从控 制台读写字符的基本功能。控制台输入主要通过 Console 类的 Read 方法 ReadLine 方法来实现的, 控制台输出主要通过 Console 类的 Write 和 WriteLine 方法来实现的。 3.2.1 Console.WriteLine( ) 方法 WriteLine( )方法的作用是将信息输出到控制台,但是 WriteLine 方法在输出信息的后面添加 一个回车换行符用来产生一个新行。 在 WriteLine( )方法中,可以采用“{N[,M][:格式化字符串]}”的形式来格式化输出字符串, 其中的参数含义如下: 花括号(“{}”)用来在输出字符串中插入变量。 N 表示输出变量的序号,从 0 开始,如当 N 为 0 时,则对应 输出第 1 个变量的值,当 N 为 5 时,则对应输出第 6 个变量的值,依次类推。 [,M][:格式化字符串]是可选项,其中 M 表示输出的变量所占的字符个数,当这个变量的值为负 数时,输出的变量按照左对齐方式排列;如果这个变量的值为正数的时候,输出的变量按照右对齐 方式排列。 [:格式化字符串]也是可选项,因为在向控制台输出时,常常需要指定输出字符串的格式。通过 使用标准数字格式字符串,可以使用 Xn 的形式来指定结果字符串的格式,其中 X 指定数字的格式, n 指定数字的精度,即有效数字的位数。这里提供 8 个常用的格式字符。 1. 货币格式 货币格式 C 或者 c 的作用是将数据转换成货币格式,在格式字符 C 或者 c 后面的数字表示转换 后的货币格式数据的小数位数。 例如: double k=1234.789; Console.WriteLine(“{0,8:c}”, k); //结果是¥1,234.79 Console.WriteLine(“{0,10:c4}”, k); //结果是¥1,234.7890 2. 整数数据类型格式 格式字符 D 或者 d 的作用是将数据转换成整数类型格式。 例如: int k=1234; Console.WriteLine(“{0:D}”, k); //结果是 1234 Console.WriteLine(“{0:d3}”, k); //结果是 1234 Console.WriteLine(“{0:d5}”, k); //结果是 01234 3. 科学计数法格式 格式字符 E 或者 e 的作用是将数据转换成科学计数法格式。 例如: int k=123000; double f=1234.5578; Console.WriteLine(“{0:E}”, k); //结果是 1.230000E+005 Console.WriteLine(“{0:e}”, k); //结果是 1.230000e+005 Console.WriteLine(“{0:E}”, f); //结果是 1.234558E+003 Console.WriteLine(“{0:e}”, f); //结果是 1.234558e+003 Console.WriteLine(“{0:e4}”, k); //结果是 1.2300e+005 Console.WriteLine(“{0:e4}”, f); //结果是 1.2346e+003 4. 浮点数据类型格式 格式字符 F 或者 f 的作用是将数据转换成浮点数据类型格式。 例如: int a=123000; double b=1234.5578; Console.WriteLine(“{0,-8:f}”,a);//结果是 123000.00 Console.WriteLine(“{0:f}”,b);//结果是 1234.56 Console.WriteLine(“{0,-8:f4}”,a);//结果是 123000.0000 Console.WriteLine(“{0:f3}”,b);//结果是 1234.558 Console.WriteLine(“{0:f6}”,b);//结果是 1234.557800 5. 通用格式 格式字符 G 或者 g 的作用是将数据转换成通用格式。 例如: double k=1234.789; int j=123456; Console.WriteLine(“{0:g}”, j); //结果是 123456 Console.WriteLine(“{0:g}”, k); //结果是 1234.789 Console.WriteLine(“{0:g4}”, k); //结果是 1235 Console.WriteLine(“{0:g4}”, j); //结果是 1.235e+05 6. 自然数据格式 格式字符 N 或者 n 的作用是将数据转换成自然数据格式。 例如: double k=211122.12345; int j=1234567; Console.WriteLine(“{0:N}”,k); //结果是 211,122.12 Console.WriteLine(“{0:n}”, j); //结果是 1,234,567.00 Console.WriteLine(“{0:n4}”, k); //结果是 211,122.1235 Console.WriteLine(“{0:n4}”, j); //结果是 1,234,567.0000 7. 十六进制数据格式 格式字符 X 或者 x 的作用是将数据转换成十六进制数据格式,在格式字符 X 或者 x 后面的数字 表示转换后的十六进制数据的数据位数。 例如: int j=123456; Console.WriteLine("{0:x}", j); //结果是 1e240 Console.WriteLine("{0:x6}", j); //结果是 01e240 还可以不使用参数调用 WriteLine( )方法,这时将在控制台中产生一个新行。 例: 利用 Console.WriteLine( )方法输出变量值。程序代码如下: using System; class Test { static void Main( ) { int i=12345; double j=123.45678; Console.WriteLine("i={0,8:D} j={1, 10: F3}", i , j); Console.WriteLine( ); Console.WriteLine("i={0,-8:D} j={1, -10: F3}", i , j); } } 输出结果是: 3.2.2 Console.Write( )方法 Write( )方法和 WriteLine( )方法类似,都是将信息输出到控制台,但是输出到屏幕后并不会 产生一个新行,即换行符不会连同输出信息一起输出到屏幕上,光标将停留在所输出信息的末尾。 在 Write( )方法中,也可以采用“{N[,M][:格式化字符串]}”的形式来格式化输出字符串,其 中的参数含义如同 WriteLine( )方法。 例: 利用 Console.WriteLine( )方法输出变量值。程序代码如下: using System; class Test { static void Main( ) { int i=12345; double j=123.45678; Console.Write("i={0,8:D} j={1, 10: F3} ", i , j); Console.Write("i={0,-8:D} j={1, -10: F3}", i , j); } } 输出结果是: 3.2.3 Console.ReadLine( )方法 ReadLine( )方法用来从控制台读取一行数据,一次读取一行字符的输入,并且直到用户按下回 车键它才会返回。但是,ReadLine( )方法并不接收回车键。如果 ReadLine( )方法没有接收到任何 输入,或者接收了无效的输入,那么 ReadLine( )方法将返回 null。 例:用 ReadLine( )方法接收用户输入,然后输出。 using System; class Test { static void Main( ) { string str; Console.WriteLine(“请输入你的姓名:”); str=Console.ReadLine( ); Console.WriteLine(“{0},欢迎你!”,str); } } 输出结果是: 3.2.4 Console.Read( )方法 Read( )方法的作用是从输入流(控制台)读取下一个字符,Read( )方法一次只能从输入流 读取一个字符,并且直到用户按回车键才会返回。当这个方法返回时,如果输入流中包含有效的输 入,则它返回一个表示输入字符的整数;如果输入流中没有数据,则返回-1。 例:通过 Console.Read( )方法从控制台接收用户的输入,然后显示接收的内容。程序代码如 下: using System; class TestIo { static void Main( ) { Console.Write("请输入字符:"); int a=Console.Read( ); Console.WriteLine("用户输入的内容为:{0}",a); } } 输出结果是: 3.3 结构化程序设计的概念 3.3.1 结构化程序设计的概念及算法的概念 结构化程序设计方法,是比较广泛使用的程序设计方法。 用这种方法编制的程序具有结构清晰,可读性强,易查错等特点。 结构化程序设计有三种基本结构,即:顺序结构、选择结构、循环结构。每种基本结构可以包 含若干条语句。 程序设计的主要步骤: (1) 分析问题。 (2) 确定算法。 (3) 画出程序流程图。 (4) 编写程序。 (5) 调试程序。 (6) 建立健全的文档资料。 最关键的是第 2 个步骤,即“算法设计”。 所谓“算法”,粗略地讲,是为解决一个特定问题而采取的确定的有限的步骤。 3.3.2 流程图 流程图(Flowchart),亦称框图,它是用一些几何框图、流向线和文字说明表示各种类型的操作。 计算机算法可以用流程图来表示。 3.4 顺序结构 3.4.1 顺序结构的概念 有些简单的程序是按程序语句的编写顺序依次执行的,这种结构称为顺序结构。 3.4.2 顺序结构的实例 例:编写程序计算圆的周长和面积。 using System; class Circle { static void Main( ) { const double PI= 3.141; double R, L, S; Console.Write("请输入圆的半径值:"); R=double.Parse(Console.ReadLine( )); L=2*PI*R; S=PI*R*R; Console.WriteLine("圆的周长为:{0}",L); Console.WriteLine("圆的面积为:{0} ",S); } } Parse( )方法: 主要用于将数字的字符串表示形式转换为它的等效的其他基本数字类型。 例:string s=“18”; int a=int.Parse(s)-10; //a=8 int b=a+int.Parse(Console.ReadLine()); 3.5 选择结构 3.5.1 选择结构的概念 选择结构,是一种常用的主要基本结构,是计算机根据所给定选择条件为真与否,而决定从各 实际可能的不同操作分支中执行某一分支的相应操作。 3.5.2 条件语句 1.if 语句 语法形式: if (表达式) { 语句; } 说明:如果表达式的值为 true,则执行后面 if 语句所控制的语句;如果表达式的值为 false, 则不执行 if 语句控制的语句,而直接跳转执行后面的语句。 注意:如果 if 语句块中只有一条语句,则大括号“{ }”可以省略。 编程:计算下面公式中的 b 的值。 2a+1 (a ≤100) b= a (a >100) using System; class value { static void Main( ) { Console.Write("请输入 a 的值:"); int a=int.Parse(Console.ReadLine()); int b=a; if(a<=100) { b=2*a+1; } Console.WriteLine("b={0}",b); } } 编程:输入三个数,将它们从大到小排序 int a, b, c, t=0; Console.Write(“请输入第一个数:”); a= int.Parse(Console.ReadLine()); Console.Write(“请输入第二个数:”); b= int.Parse(Console.ReadLine()); Console.Write(“请输入第三个数:”); c= int.Parse(Console.ReadLine()); if (a < b) //本条件语句实现 a>=b { t = a; a = b; b = t; } if (a < c) //本条件语句实现 a>=c { t = a; a = c; c = t; } if (b < c) //本条件语句实现 b>=c { t = b; b = c; c = t; } Console.WriteLine(“排序结果为: {0},{1},{2}”,a,b,c); 2.if„ else 语句 语法: if (表达式) { 语句块 1; } else { 语句块 2; } 说明:如果表达式的值为 true,则执行 if 语句所控制的语句块 1;如果表达式的值为 false, 则执行 else 语句所控制的语句块 2。 例:输入一个数,对该数进行四舍五入。 using System; class Value { static void Main( ) { Console.WriteLine(“请输入 a 的值:”); double a=double.Parse(Console.ReadLine( )); int b; if(a-(int)a>=0.5) { b=(int)a+1; } else { b=(int)a; } Console.WriteLine("{0}进行四舍五入后的值为:{1} ",a,b); } } 在 if„ else 语句中可以嵌套使用多层 if„ else 语句,如: if (表达式 1) if(表达式 2) if(表达式 3) „„ 语句 1; else 语句 2; else 语句 3; else 语句 4; 在使用这种结构时,要注意 else 和 if 的配对关系,其原则是:从第 1 个 else 开始,一个 else 总和它上面离它最近的可配对的 if 配对。 例: int a=int.Parse(Console.ReadLine( )); if (a>0) { if (a<20) a=2*a; if (a>100) a=a-50; else a=a+10; //这个 else 与 if (a>100)相配 } else a=-a; // 这个 else 与 if (a>0)相配 Console.WriteLine(“a=”,a); 练习: 1.若 int m, n, r; 则以下正确的是( ) A.if (m<>n) r--; B.if (m= =n) r--; C.if (!m) r--; D.if (m=100 时,x=0.25*50+0.35*50+0.45*(w-100) double w,x=0; Console.Write(“请输入重量”); w = double.Parse(Console.ReadLine()) if (w <= 50) x = 0.25 * w if (w>50 &&w <= 100) x = 0.25 * 50 + 0.35 * (w - 50); if (w>100) x = 0.25 * 50 + 0.35 * 50 + 0.45 * (w - 100) Console.WriteLine(“运费为{0}”,x); 3.else if 语句 else if 语句是 if 语句和 if„ else 语句的组合,其一般形式如下: if (表达式 1) 语句 1; else if (表达式 2) 语句 2; „„ else if (表达式 n-1) 语句 n-1; else 语句 n; 例:编写一个实现如下函数值的程序 -1, x<0 f(x)= 0, x=0 1, x>0 using System; class Test { static void Main( ) { Console.Write(“请输入 x 的值: "); double x=double.Parse(Console.ReadLine( )); int y; if (x>0) y=1; else if (x==0) y=0; else y=-1; Console.WrteLine(“Y={0}“,y); } } 3.5.3 分支语句 格式:switch(表达式) { case 常量表达式 1: 语句 1; break; case 常量表达式 2: 语句 2; break; „„ [default: 语句 n; break; ] } 说明:  各个 case 标签不必连续,也不必按特定顺序排列  default 标签可位于 switch„ case 结构中的任意位置  default 标签不是必选的,但使用 default 标签是一个良好的编程习惯  每两个 case 标签之间的语句数不限 选择变量的类型可以是整型、字符型或 string。 注意: C#要求每个 case 后使用 break 语句或跳转语句 goto。所有常量表达式的值不能相同。 编程:查询学生姓名,输入一个字符,如输入“k”时,显示 Kate;输入“m”时,显示 Mary;输入 “r”时,显示 Rose;输入“T”时,显示 Tom ;输入其他字符时,显示 Other Students。 Console.Write(“Enter a character:"); char Name=(char)Console.Read(); switch(Name) { case 'k': Console.WriteLine("Kate."); break; case 'm': Console. WriteLine("Mary."); break; case 'r': Console. WriteLine("Rose."); break; case 'T': Console. WriteLine("Tom."); break; default: Console.WriteLine("Other students. "); break; } 注意:在 C#中,两个或更多的 case 语句可以共用同一程序代码: 例: using System; class Demo { public static void Main() { Console.Write("enter i="); int i=int.Parse(Console.ReadLine()); switch(i) { case 1: case 2: case 3: Console.WriteLine(“i is 1,2 or 3."); break; case 4: Console.WriteLine(" i is 4. "); break; } } } [思考与练习] 1. 以下程序运行时,输出结果是什么? int y = 1,x; if (y!=0) x = 5; else if y < 0 x = 4; else x = 3; Console.WriteLine("x={0}“, x); 3.6 循环结构 循环是指在指定的条件下多次重复执行一组语句。被重复执行的一组语句称为循环体。 采用循环结构可以解决一些按一定规则重复执行的问题。例如,统计一个班几十名学生,甚至 全校几千名学生的学期成绩,如求平均分、不及格人数等。 3.6.1 循环结构的概念 循环结构按其循环体是否嵌套从属的子循环结构,可分为单循环结构和多重循环结构。 3.6.2 while 语句和 do—while 语句 1.while 语句 语法格式: while (条件表达式) { 循环体 } 功能:只要条件为真,则执行循环体中的语句。 说明:可利用 break 和 continue 来控制循环  break:提前结束循环,一般和条件配合使用  continue:跳过当前循环并开始下一循环 例:写出下列程序的运行结果。 using System; class Sumw { static void Main( ) { int n=1; while (n<6) { Console.WriteLine(“n={0}”, n); n++; } } } 结果: 例:使用 while 语句,编程计算 1+2+„ +1000。 using System; class Sumw { static void Main( ) { int i=1, sum=0; while (i<=1000) { sum+=i; i++; } Console.WriteLine(“1+2+„+1000={0}”,sum); } } [思考与练习] (1)如果要实现 sum=1+2+3+„ +10 , 怎样修改程序? (2)如果要实现 sum=30+31+32+„ +50 , 怎样修改程序? 例:使用 while 语句,编程计算 10 的阶乘值。 using System; class Value { static void Main( ) { int i=10; long x=1; while (i>0) { x*=i; i--; } Console.WriteLine(“10!={0}”, x); } } 输出结果: 练习: 1. 下列程序所计算的数学式是( ) int a=0, i=2; while(i<100) { a+=i; i+=2; } A. a=1+2+4+„+98 B. a=1+2+4+„+100 C. a=2+4+6+„+98 D. a=2+4+6+„+100 循环语句允许嵌套,即 while 语句里面还可以再套 while 语句。 例:写出下列程序的运行结果。 using System; class example { static void Main( ) { int i=1,j,s=0; while(i<=3) { j=1; while(j<=2) { s++; j++; } i++; } Console.WriteLine(“s={0}”, s); } } 输出结果: 练习: 1.以下程序段的循环次数为( ) int i=0, j=0; while(i<3) { while(j<2) j++; i++; } 2.do-while 语句 语法格式: do { 循环体; } while(条件表达式); 功能:与 while 类似,但有区别:do„ while 循环中即使条件为假时也至少执行一次该循环体中的 语句 循环语句 条件表达式 false true 条件表达式 循环语句 true false do„ while 执行过程 while 执行过程 例:写出下列程序的运行结果。 using System; class TestDoWhile { static void Main( ) { int x, y=0; do { x=y++; Console.WriteLine(x); }while(y<5); } } 输出结果: 例:编程利用 do-while 语句实现 100 以内的能被 5 整除的数累加。 using System; class EvenSum { static void Main( ) { int i=5, esum=0; do { esum+=5; i+=5; }while(i<=100); Console.WriteLine("esum={0} ", esum); } } 例:使用 do-while 语句,编程计算 1!+2!+„„ +10!。 using System; class Sum { static void Main( ) { int i=1, j; long m, sum=0; do { m=1; j=1; do { m*=j; j++; } while(j<=i); sum+=m; i++; } while (i<=10); Console.WriteLine(“1!+2!+„„+10!={0}”, sum); } } 输出结果: 练习: 1.while 语句循环结构和 do...while 语句循环结构的区别在于( ) A.while 语句的执行效率较高 B.do...while 语句编写程序较复杂 C.无论条件是否成立,while 语句都要执行一次循环体 D.do...while 循环是先执行循环体,后判断条件表达式是否成立,而 while 语句是先判断条件表 达式,再决定是否执行循环体 3.6.3 for 语句和 foreach 语句 1.for 语句 语法格式: for(表达式 1;表达式 2;表达式 3) { 循环体; } 说明:  for 循环要求只有在对特定条件进行判断后才允许执行循环  这种循环用于将某个语句或语句块重复执行预定次数的情形 例:使用 for 循环语句,编程输出 1~20 的每个奇数的平方。 using System; class square { static void Main( ) { int s; for (int i=1;i<20;i+=2) { s=i*i; Console.Write("{0}\t ", s); } } } 结果: for 语句的几点说明: (1) 如果对循环变量在 for 语句前已赋初值,则在 for 语句中可省略表达式 1,但要保留其后的 分号。 ... int i=1; for( ;i<=20;i++) ... (2)for 语句可以省略表达式 2,即不判断表达式条件是否成立,循环将一直进行下去,但应保留 表达式 2 后面的分号。此时,需要在循环体中添加跳出循环的控制语句。 例: for(int i=1; ;i++) { s=i*i; Console.WriteLine("{0}\t ", s); if (i==10) break; } (3) for 中可以省略表达式 3。此时应在循环体中添加改变循环变量值的语句,以结束循环。 例: for( int i=1; i<=10; ) { s=i*i; Console.WriteLine("{0}\t ", s); i++; } (4) for 语句中的 3 个表达式可同时省略。 int i=1; for( ; ; ) { s=i*i; Console.WriteLine("{0}\t ", s); i++; if (i==10) break; } 编程:用 for 语句输出 10!的值: using System; class Test { static void Main( ) { int i=1; long m=1; for(i=1;i<=10;i++) { m*=i; } Console.WriteLine("{0}!={1}",i-1,m); } } 结果: for 循环语句也可以嵌套。 例:利用 for 循环嵌套语句,求 1!+2!+3!+...+10!的和。 using System; class MultiSum { static void Main( ) { long s=0, m=1; for (int i=1; i<=10; i++) { m=1; for (int j=1;j<=i; j++) m*=j; s=s+m; } Console.WriteLine("1!+2!+3!+...+10!={0} ", s); } } 结果: 2.foreach 语句 foreach 语句用于列举集合中的每一个元素,并且通过执行循环体对每一个元素进行操作。 foreach 语句只能对集合中的元素进行循环操作。 foreach 语句的一般语法格式如下: foreach (数据类型 标识符 in 表达式) { 循环体 } 例:写出下列程序段的结果: using System; class Test { static void Main( ) { double[ ] Array={16,12,31,8,21}; int[ ] intArray={1,2,3}; foreach(int i in intArray) { Console.WriteLine(Array[i] ); } } } 运行结果: 练习: 1.以下程序段的循环次数为( ) int[ ] a=new int[ ]{1,2,3 ,4,5}; foreach(int i in a) Console.WriteLine(t); A.0 B.4 C.5 D.6 2. 以下程序运行后,变量 a 的值为( ) int a=100; for(int j=10;j>0;j-=2) a-=j*3; A.100 B.16 C.13 D.10 3.6.4 跳转语句 在 C#中还可以用跳转语句来改变程序的执行顺序。 1.break 语句 在 switch 语句中,break 用来使程序流程跳出 switch 语句,继续执行 switch 后面的语句;在 循环语句中,break 用来从当前所在的循环内跳出。 语法格式: break; 编程: 任意给定一个整数 n,判定其是否为素数(即只能被 1 和自身整除)。 using System; class Prime { static void Main() { Console.Write("输入 n 的值:"); int i=1,n; n=int.Parse(Console.ReadLine()); while(++iy)?x:y)>z)?m:z”的值为( )。 3.8、数组 数组是同一数据类型的一组值,数组属于引用类型,因此存储在堆内存中,数组元素初始化或 给数组元素赋值都可以在声明数组时或在程序的后面阶段中进行。 定义: 数据类型[元素个数] 数组名称; 数据类型[ ] 数组名称=new 数据类型[元素个数]; 如:int[6] arrayHere; 又如: static void Main(string[] args) { int count; Console.WriteLine("请输入您要登记的学生人数 "); count=int.Parse(Console.ReadLine()); // 声明一个存放姓名的字符串数组,其长度等于提供的学生人数 string[] names = new string[count]; // 用一个 for 循环来接受姓名 for(int i=0; i { // 类的主体 } 如: class Employee { // 类的主体 }  成员变量  语法:[访问修饰符] 类型 成员变量;  说明: 访问修饰符主要有以下几种:  public:可以被所有类访问  internal:可以被当前程序集访问  protected:可被所属类和子类访问  private:只能被所属类访问 类的默认访问修饰符为 internal,而类成员为 private。 protected、public 和 internal 修饰的成员变量采用帕斯卡命名法,私有成员采用骆驼命名法。 示例:分析员工类 Employee 中需要包含姓名、性别、职称、薪水等信息,因此可以定义一个如下所 示的类: class Employee { private string _name; private char _gender; private string _qualification; private uint _salary; } 若要访问 Employee 类中的属性,则应先创建类的实例或对象。 Employee objEmployee = new Employee(); 使用点号访问成员变量 objEmployee._name = “张三"; objEmployee._name = 'M'; 4.2、构造函数和析构函数 4.2.1 构造函数 类的一种特殊方法,每次创建类的实例都会调用它,其功能是将成员变量的值初始化为某个默 认值。 语法: [访问修饰符] 类名([参数]) { // 构造函数的主体 } 说明:  可以带参数  无返回值  若类中没有定义构造函数,则运行库将自动提供默认的无参的构造函数。 示例 1:无参的构造函数 class Employee { private string _name; private char _gender; private string _qualification; private uint _salary; // 默认构造函数 private Employee() { _qualification = “研究生"; } static void Main(string[] args) { // 调用默认构造函数 Employee objEmployee = new Employee(); Console.WriteLine(“资格= " + objEmployee._qualification); Console.WriteLine(“薪水= " + objEmployee._salary); } } 示例 2:带参的构造函数 class Employee { private string _name; private char _gender; private string _qualification; private uint _salary; // 默认构造函数 private Employee() { _qualification = “大学毕业生"; } // 参数化构造函数 private Employee(string strQualification, string strName,char gender, uint empSalary) { _qualification = strQualification; _name = strName; _gender = gender; _salary = empSalary; } public static void Main(string[] args) { // 调用默认构造函数 Employee objGraduate = new Employee(); // 调用参数化构造函数 Employee objMBA = new Employee(“ACCPS3“, ”张亮影“, ‘男', 5500); Console.WriteLine( “ 默 认 构 造 函 数 输 出 : \n 资格= “ + objGraduate._qualification); Console.WriteLine( “\n 参 数 化 构 造 函 数 输 出 : \n 资格= " +objMBA._qualification); } 4.2.2 析构函数 是 C#中的另一种特殊方法,用于执行清除操作。声明方法与构造函数类似,只是在名字前加~。 语法 ~ 类名() { // 析构函数的主体 }  说明 一个类只能有一个析构函数 析构函数不能重载 不能显式或手动调用,只能由垃圾回收器自动调用。  示例: ~ Employee() {„//实现析构函数} 4.3、方法 方法可执行对象的行为,类中的方法通常包括在调用类时要使用的操作语句。 • 方法声明 语法: [访问修饰符] 返回类型 方法名([参数列表]) { //方法的主体 } • 说明: 方法不返回值,则返回类型为 void。若需要,则用 return [表达式]带回返回值。 参数列表可选。 访问修饰符默认为 private。 • 示例 class Point { int x; int y; void Assign() { System.Console.WriteLine(“输入点的值"); x = int.Parse(System.Console.ReadLine()); y = int.Parse(System.Console.ReadLine()); } } • 调用方法 格式:对象名.方法([参数列表]); 示例 class ComplexNumber { private int _real; private int _imaginary; „ private void Accept() { Console.WriteLine(“请输入实数部分"); _real = int.Parse(Console.ReadLine()); Console.WriteLine(“请输入虚数部分"); _imaginary = int.Parse(Console.ReadLine()); } void ShowResult() { Console.WriteLine(“相加之和为:"); Console.WriteLine(_real + "+" + _imaginary + "i"); } // 此方法用于将两个复数相加 ComplexNumber Add(ComplexNumber objParam1) { objParam1._real += _real ; objParam1._imaginary += _imaginary; return objParam1; } [STAThread] public static void Main(string[] args) { ComplexNumber objNumber1 = new ComplexNumber(); ComplexNumber objNumber2 = new ComplexNumber(); objNumber1.Accept(); objNumber2.Accept(); ComplexNumber objTemp = objNumber1.Add(objNumber2); objTemp.ShowResult(); } 4.4、方法重载 在实际生活中,有些职责或任务有共同的名称,例如,我们每个月都会有各种需要支付的费用, 如电话费、手机费、电费、煤气费、水费等。虽然都叫付费,但其支付的过程可能不同,因此,在 C#中,可以利用方法重载来实现付费这一任务。例如: Class BillPayment { „ void PayBill(int telephoneNumber) { //此方法用于支付固定电话话费 } void PayBill(long consumerNumber) { //此方法用于支付电费 } void PayBill(long consumerNumber, double amount) { //此方法用于支付移动电话话费 } „ } 注意,不能根据返回类型来重载方法。 基于不同数量的参数的方法重载 int greatest(int num1, int num2) { Console.WriteLine(“{0} 和 {1} 相比, 最大的是: ", num1, num2); if(num1 > num2) { return num1; } else { return num2; } } int greatest(int num1, int num2, int num3) { Console.WriteLine(“{0}, {1} 和 {2} 相比, 最大的是: ", num1, num2, num3); if(num1 > num2 && num1 > num3) { return num1; } else if(num2 > num1 && num2 > num3) { return num2; } else { return num3; } } 基于不同类型的参数的方法重载 int greatest(int[] numbers) { int temp = numbers[0]; for(int i = 1;i < numbers.Length;i++) { if(temp < numbers[i]) temp = numbers[i]; } return temp; } double greatest(double[] numbers) { double temp = numbers[0]; for(int i = 1;i < numbers.Length;i++) { if(temp < numbers[i]) temp = numbers[i]; } return temp; } 4.5、属性 C#中通过属性特性来读取和写入字段,而不直接进行读取和写入,以此来提供对字段的进一步 保护。用户可以象访问字段那样访问属性。因此,属性进一步对类的数据进行了封装。 格式: [访问修饰符] 数据类型 属性名 { get{ }; set{ }; } 例如: class Employee { private static string _name; private static string _id; public string Id //属性名,公有的采用帕斯卡命名,私有的采用骆驼命名 {//读取属性 get { return _id; } // 设置属性 set { // 验证输入长度小于 2 if (_id.Length > 2) _id = value; } } } 属性类型  可读可写  只读 [访问修饰符] 数据类型 属性名 { get{ }; } 只写 [访问修饰符] 数据类型 属性名 { set{ }; } 静态属性 [访问修饰符]static 数据类型 属性名 {get{}; set{};} 该属性属于整个类,而不是类的实例。 综合示例: class SavingsAccount{ // 类字段用于存储帐号、余额和已获利息 private int _accountNumber; private double _balance; private double _interestEarned; // 利率是静态的,因为所有帐户获得的利息相同 private static double _interestRate; // 构造函数初始化类成员 public SavingsAccount(int accountNumber, double balance) { this._accountNumber = accountNumber; this._balance = balance; } // 只读 AccountNumber 属性 public int AccountNumber { get { return _accountNumber; } } // 只读 Balance 属性 public double Balance{ get { if (_balance < 0) Console.WriteLine("没有可用余额"); return _balance; } } //读写 InterestEarned 属性 public double InterestEarned{ get { return _interestEarned; } set { // 验证数据 if (value < 0.0) { Console.WriteLine(“利息不能为负数"); return; } _interestEarned = value; } } //静态属性 InterestRate public static double InterestRate { get { return _interestRate; } set { // 验证数据 if (value < 0.0) { Console.WriteLine(“利率不能为负数"); return; } else { _interestRate = value / 100; } } static void Main(string[] args) { // 创建 SavingsAccount 的对象 SavingsAccount objSavingsAccount = new SavingsAccount(12345, 5000); Console.WriteLine(“输入到现在为止已获得的利息和利率"); objSavingsAccount.InterestEarned = Int64.Parse(Console.ReadLine()); SavingsAccount.InterestRate = Int64.Parse(Console.ReadLine()); objSavingsAccount.InterestEarned += objSavingsAccount.Balance * SavingsAccount.InterestRate; Console.WriteLine(" 获 得 的 总 利 息 为 : {0}", objSavingsAccount.InterestEarned); } } 4.6、索引器 一家公司某个部门人数很多,假设部门经理需要一份员工记录,或许是用于更新资料,或许只 是了解一个信息,不定义传统的方法来设置职员记录或其他方法来获取职员记录,而是在类中定义 一个索引器,是很有道理的,索引器可以为员工的编号。索引器是一种特殊的方法,索引器允许使 用方括号进行访问,而不是常规的 get 和 set 方法调用。使用索引器,使客户端代码更简洁。  语法 [访问修饰符] 数据类型 this[数据类型 标识符] { get{}; set{}; }  说明 索引器一般用于类的数组元素 允许按数组方式来检索对象的数组元素。  示例 class Photo{ string _title; public Photo(string title) { this._title = title; } public string Title { get { return _title; } } } class Album { // 该数组用于存放照片 Photo[] photos; public Album(int capacity) { photos = new Photo[capacity]; } public Photo this[int index] { get { // 验证索引范围 if (index < 0 || index >= photos.Length) { Console.WriteLine("索引无效"); // 使用 null 指示失败 return null; } // 对于有效索引,返回请求的照片 return photos[index]; } set { if (index < 0 || index >= photos.Length) { Console.WriteLine("索引无效"); return; } photos[index] = value; } } public Photo this[string title] { get { // 遍历数组中的所有照片 foreach (Photo p in photos) { // 将照片中的标题与索引器参数进行比较 if (p.Title == title) return p; } Console.WriteLine("未找到"); // 使用 null 指示失败 return null; } } } class TestIndexer{ static void Main(string[] args){ // 创建一个容量为 3 的相册 Album friends = new Album(3); // 创建 3 张照片 Photo first = new Photo("Jenn"); Photo second = new Photo("Smith"); Photo third = new Photo("Mark"); // 向相册加载照片 friends[0] = first; friends[1] = second; friends[2] = third; // 按索引检索 Photo obj1Photo = friends[2]; Console.WriteLine(obj1Photo.Title); // 按名称检索 Photo obj2Photo = friends["Jenn"]; Console.WriteLine(obj2Photo.Title); } } 4.7、命名空间 在处理大型项目时会创建许多类,有时这些类的名称会相同而发生冲突。为避免这个冲突,我 们引入命名空间来避免类的重名问题及降低代码重用的复杂性。  语法 namespace 命名空间的名称 { // 该名称空间的所有类都放在这里 }  说明  在概念和作用上类似与 java 中的包。  命名采用帕斯卡命名法。  示例 namespace Samsung { class Monitor { public void ListModels() { Console.WriteLine(“供应以下型号的显示器:"); Console.WriteLine("14\", 15\" \n"); } [STAThread] static void Main(string[] args) { // // TODO: 在此处添加代码以启动应用程序 // } } } //在其他文件中使用 namespace Sony { public class Monitor { public void ListModelStocks() { Console.WriteLine(“以下是 Sony 显示器的规格及其库存量:"); Console.WriteLine("14\"=1000, 15\"=2000, 17\"=3000"); } static void Main(string[] args) { Samsung.Monitor objSamsung = new Samsung.Monitor(); Monitor objSony = new Monitor(); objSamsung.ListModels(); objSony.ListModelStocks(); } } } 小结  类是 C# 中的一种结构,用于在程序中模拟现实生活的对象  成员变量表示对象的特征  方法表示对象可执行的操作  如果类中未定义构造函数,则由运行库提供默认构造函数  析构函数不能重载,并且每个类只能有一个析构函数  可以根据不同数量的参数或不同数据类型参数对方法进行重载,不能根据返回值进行方法 重载  属性通过使用访问器读取和写入类中的字段,对字段进行保护  属性分类为以下四种不同的类型: 读/写属性 只读属性 只写属性  可以在类中定义索引器,允许使用下标对该类的对象中的数据进行访问  索引器必须总是命名为 this,因为对它们的访问是通过其所属的对象进行的 命名空间用 来界定类所属的范围,类似于 Java 中的包 第五章 WinForms 基础知识 本章主要目标 通过本章的学习,主要把握以下内容:  理解 Windows 窗体  使用基本控件如标签、文本、按钮、列表框和组合框  掌握窗体的常用属性和方法  使用 WinForms 中的高级控件 Ø 单选按钮 Ø 图片框 Ø 选项卡控件 Ø 滚动条 Ø 进度条 本章重点 了解 windows 窗体及控件,熟悉各种控件的使用。 本章难点 列表框和组合框的使用 5.1、windows 窗体简介 我们经常会通过显示属性窗口来设置计算机屏幕显示效果,如分辨率、背景色等。图 1 即为设 置外观效果的界面。 图 1 显示属性 实际上我们每天都接触不同的 windows 窗体,而且窗口上的元素也通常会重复出现,如一些文 本框、按钮或下拉列表框等。这些元素均为 GUI 界面的设计元素,对于程序员而言,我们不需要化 时间去独立编写这些元素,而只要根据界面需要选择合适的元素搭建即可。 windows 窗体(图 2 所示)也称 WinForms,开发人员可以使用 WinForms 创建用户界面,并使用 任何一种.NET 支持的语言编写相关的功能。 图 2 Windows 窗体 windows 窗体的特点  简单而强大  改善了接口和基类 IntelliSense  新的管理数据提供程序  安全  灵活的控件  通晓数据  向导 创建 WinForms 应用程序 选择“开始”→ “程序”→ “Microsoft Visual Studio.NET 2005” →“Microsoft Visual Studio.NET 2005”,如图 3 所示: 图 3 起始页 新建一个 C#为模板的 windows 应用程序。打开后如图 4 所示。 图 4 “设计窗口” 示例程序: using System; //基础核心命名空间 using System.Drawing;// 提供了大量绘图工具的访问权限 using System.Collections;//提供 ArrayList,BitArray,Hashtable,Stack,StringCollection,StringTable 类 using System.ComponentModel; using System.Windows.Forms; //大量窗体和控件 namespace SampleProject { /// /// Form1 的摘要说明。 /// public class Form1 : System.Windows.Forms.Form //从 System.Windows.Forms.Form 派 生 { /// /// 必需的设计器变量. /// private System.ComponentModel.Container components = null; public Form1() { // // Windows 窗体设计器支持所必需的 // InitializeComponent(); // // TODO:在 InitializeComponent 调用之后 添加任何构造函数代码 // } private void InitializeComponent() { this.components = new System.ComponentModel.Container(); this.Size = new System.Drawing.Size(300,300); this.Text = "Form1"; } /// /// 清理所有正在使用的资源。 /// protected overide void Dispose( bool disposing ) { if( disposing ) { if(components != null) { components.Dispose(); } } base.Dispose( disposing ); } [STAThread] //程序的入口点 static void Main() { Application.Run(new Form1()); } } } 5.2、windows 窗体的常用控件 C#中,常用的控件如图 5、6 所示: 图 5 控件的类层次结构 图 6 常用控件 标签 Label:用于显示用户不能编辑的文本或图象,起标注或说明作用。该控件不能获得焦 点,可用于为其他控件创建访问键。 表 1 标签的属性、事件与方法 属性 说明 Text 该属性用于设置或获取与该控件关联的文本 Image 指定标签要显示的图象 方法 说明 Hide 隐藏控件,调用该方法时,即使 Visible 属性设置为 True,控件也不可见 Show 相当于将控件的 Visible 属性设置为 True 并显示控件 事件 说明 Click 用户单击控件时将发生该事件 文本框:用于获取用户输入的信息或向用户显示文本。 表 2 文本框的属性、事件与方法 属性 说明 MaxLength 可在文本框中输入的最大字符数 Multiline 表示是否可在文本框中输入多行文本 Passwordchar 机密和敏感数据,密码输入字符 ReadOnly 文本框中的文本为只读 方法 说明 Clear 删除现有的所有文本 事件 说明 KeyPress 用户按一个键结束时将发生该事件 按钮:提供用户与应用程序的交互,单击按钮来执行相应的操作。 表 3 按钮的属性、事件与方法 属性 说明 Enabled 确定是否可以启用或禁用该控件 方法 说明 PerformClick Button 控件的 Click 事件 事件 说明 Click 单击按钮时将触发该事件 列表框:显示一个完整的选项列表,用户可以从中选取一个或多个选项。 表 4 列表框的属性、事件与方法 属性 说明 Items 所有项 SelectionMode 选择模式 SelectedIndex 选中的索引号,从 0 开始 Text 当前选中项的文本 SelectedItem 选中的项 SelectedItems 所有被选中的项 方法 说明 ClearSelected 清除选中的选项 事件 说明 SelectedIndexChanged 选中时触发 示例:添加选项 private void frmUserAdd_Load(object sender, System.EventArgs e) { this. lstCurrDeptName.Items.Add("软件部"); this. lstCurrDeptName.Items.Add("硬件部"); this. lstCurrDeptName.Items.Add("财务部"); this. lstCurrDeptName.Items.Add("人事部"); } 组合框:结合文本框和列表框的特点,允许用户输入文本或选择某选项。 表 5 组合框的属性、事件与方法 属性 说明 DropDownStyle ComboBox 控件的样式 MaxDropDownItems 下拉区显示的最大项目数 方法 说明 Select 在 ComboBox 控件上选定指定范围的文本 示例程序: private void frmUserAdd_Load(object sender, System.EventArgs e) { „„ this.cboDesig.Items.Add("总裁"); this. cboDesig.Items.Add("副总裁"); this. cboDesig.Items.Add("首席执行官"); this. cboDesig.Items.Add("经理"); this. cboDesig.SelectedIndex = 1; //默认的选择是"总裁" } private void cboDesig_SelectedIndexChanged(object sender, System.EventArgs e) { MessageBox.Show( "选择的是第“+ (this.cboDesig.SelectedIndex+1).ToString() , "选择的 信息"); MessageBox.Show( "选择的职务是“ + this.cboDesig.Text, "选择的信息"); } 5.3、消息框窗口 用于显示消息,也可向用户请求消息。要显示消息,可用以下语法: MessageBox.show(“[消息内容]”); 该方法返回一个枚举值表示你所按的按钮,具体的枚举值有 Abort,Cancel,Ignore,No,None,OK,Retry 和 Yes 等。 下列程序片段演示了如何检查条件。 if (MessageBox.Show(“保存文件”,“保存", MessageBoxButtons.YesNo MessageBoxIcon.Information, MessageBoxDefaultButton.Button1) == DialogResult.Yes) { //保存文件所用的代码 } 表 6 消息框的重载方法 重载方法 Show(string text); Show(string text, string caption); Show(string text, string caption, MessageBoxButtons buttons); Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon); „„ 5.4、应用程序示例 1 本例将创建一个 windows 界面(图 7 所示)应用程序。在初始状态下,这些控件是禁用的, “添加按钮可启动所有控件,“取消”按钮可清除控件中的值,“退出”按钮显示列表框选定的项 目并退出应用程序,用户为组合框选择项目后,选定的项目将显示在消息框中。 图 7 界面 主要的代码 private void btnExit_Click (object sender, System.EventArgs e)//退出按钮 { string str=""; for(int ctr=0;ctr<=this.lstCurrDeptName.SelectedItems.Count-1;ctr++) str += "\n"+this.lstCurrDeptName.SelectedItems[ctr].ToString(); MessageBox.Show(“选定的项目为\n" +str); Application.Exit(); } private void btnCancel_Click(object sender, System.EventArgs e) //取消按钮 { this.txtEmpName.Text=""; this.txtAddress.Text=""; this.cboDesignation.Text=“经理"; } private void btnAdd_Click(object sender, System.EventArgs e) //添加按钮 { this.txtEmpName.Enabled=true; this.txtAddress.Enabled=true; this.cboDesignation.Enabled=true; this.lstCurrDeptName.Enabled=true; } //选择的项目显示在消息框中 private void cboDesignation_SelectedIndexChanged(object sender, System.EventArgs e ) { MessageBox.Show(“您已经选定了" +this.cboDesignation.SelectedItem.ToString()); } 5.5、窗体容器简介 窗体是可以容纳各种控件的容器,它提供了在用户和应用程序之间交换信息的方式,增强了 应用程序的功能,在所有基于图形的操作系统和网页中均广泛使用了窗体。在.NET 中,Form 类层次 如图 8 所示: 图 8 Form 类 窗体类型 SDI [单文档界面] MDI [多文档界面],如图 9 所示: 图 9 MDI 窗体 模式窗口 窗体的属性,如表 7 所示 表 7 窗体属性 属性 属性 ActiveForm HelpButton CancelButton KeyPreview ControlBox MainMenu FormBorderStyle Modal WindowState ShowInTaskbar 窗体的方法与事件,如表 8 所示 表 8 方法与事件 方法 Activate LayoutMdi ShowDialog 事件 Activated Closed Closing Load 窗体连接:一个应用程序可能有多个窗体,若要在当前窗体中显示另一窗体,可采用如下方 法: [被调用的窗体类] [窗体实例] = new [被调用的窗体类](); [窗体实例].Show(); 5.6、其他控件 单选按钮:允许用户从多个选项中选择一个。每次只能选择一个。通常以组的形式出现。 表 9 单选按钮属性和方法 属性 说明 Checked 确定是否已选定控件 方法 说明 Focus 将输入焦点移至控件 图片框:可用于显示图像的 Windows 图片,支持显示位图、元文件、图标、JPEG、GIF 或 PNG 等格式的图形,其常用属性和方法如表 10 所示。 表 10 图片框的属性和方法 属性 说明 Image 用于指定图片框显示的图像。该图像可在设计或运行时 设置 SizeMode 用于指定图像的显示方式。可以指定的各种大小模式包 括 AutoSize、CenterImage、Normal 和 StretchImage。 默认值为 Normal 方法 说明 Show 显示控件 选项卡控件:用于将相关的控件集中在一起,放在一个页面中,用于显示多个选项卡,其中 每个选项卡均可包含图片和其他控件。选项卡(如图 10)相当于另一个窗体,可以容纳其他控件。 其属性和事件如表 11 所示。 图 10 选项卡界面 表 11 选项卡的属性和事件 属性 说明 MultiLine 指定是否可以显示多行选项卡。如果可以显 示多行选项卡,该值应为 True,否则为 False。默认值为 False SelectedIndex 当前所选选项卡页的索引值。该属性的值为 当前所选选项卡页的基于 0 的索引。默认值 为 -1,如果未选定选项卡页,则为同一值 SelectedTab 当前选定的选项卡页。如果未选定选项卡页, 则值为 NULL 引用 ShowToolTips 指定在鼠标移至选项卡时,是否应显示该选 项卡的工具提示。 如果对带有工具提示的选 项卡显示工具提示,该值应为 True,否则为 False TabCount 检索选项卡控件中选项卡的数目 事件 说明 SelectedIndexChanged 更改 SelectedIndex 属性值时,将触发该事 件 滚动条:用于滚动整个窗体。windows 窗体支持两种滚动条:HScrollBar 水平和 VScrollBar 垂直滚动条。表 12 列出了滚动条的常用属性和方法。 表 12 滚动条属性和事件 属性 说明 Maximum 用于表示滚动范围的上限值。默认值为 100 Minimum 用于表示滚动范围的下限值。默认值为 0 Value 该属性表示滚动条控件中代表滚动框的当前位置的 数字。默认值为 0 事件 说明 Scroll 移动滚动条上的滚动框时,将触发该事件 ValueChanged 更改 Value 属性的值时,将触发该事件。Value 属 性的值可由滚动事件更改,也可以通过程序来更改 进度条:用于指示操作的进度、完成的百分比,外观是排列在水平条中的一定数目的矩形。 图 11 进度条 进度条常用的属性和方法如表 13 所示。 表 13 进度条的属性和方法 属性 说明 Maximum 进度条控件的最大值。默认值为 100 Minimum 进度条控件的最小值。进度条从最小值开始递增,直 至达到最大值。默认值为 0 Step PerformStep 方法应据以增加进度条的光标位置的 值。 默认值为 10 Value 进度条控件中光标的当前位置。默认值为 0 方法 说明 Increment 按指定的递增值移动进度条的光标位置 PerformStep 按 Step 属性中指定的值移动进度条的光标位置 5.7、应用程序示例 设计一个接受职员信息(包括个人和职业信息)的窗体应用程序。个人信息界面和职业信息 界面分别如图 12、13 所示。 图 12 个人信息界面 图 13 职业信息界面 在图 13 中,点击“上一步”能回到第一个选项卡,点“完成”,则显示“感谢您输入信息” 消息框。 主要的控件属性设置如表 14 所示。 表 14 主要的控件 控件 名称 文本 分组框 grpGeneral 一般信息 分组框 grpGender 性别 标签 lblName 姓名: 标签 lblAddress 住址: 标签 lblPhone 电话号码: 文本框 txtName 文本框 txtAddress 文本框 txtPhone 图片框 picLogo 单选按钮 radMale 男 单选按钮 radFemale 女 按钮 btnNext 下一步(&N) 标签 lblQual 输入学历: 标签 lblWorkExp 工作经验(年): 文本框 txtQual 文本框 txtWorkExp 按钮 btnBack 上一步(&B) 按钮 btnDone 完成(&D) 主要的代码 private void tabMain_Click(object sender, System.EventArgs e) //选项卡切换 { if (tabMain.SelectedIndex ==0) { vsbPersonal.Visible =false; tabMain.SelectedIndex = 1; } else if (tabMain.SelectedIndex ==1) { vsbPersonal.Visible = true; tabMain.SelectedIndex = 0; } } private void btnNext_Click(object sender, System.EventArgs e) //下一步按钮 { if (tabMain.SelectedIndex ==0) { vsbPersonal.Visible = false; tabMain.SelectedIndex = 1; } } private void btnBack_Click(object sender, System.EventArgs e) //点上一步 { if (tabMain.SelectedIndex ==1) { vsbPersonal.Visible = true; tabMain.SelectedIndex = 0; } } private void btnDone_Click(object sender, System.EventArgs e) //完成按钮 { MessageBox.Show(“感谢您输入信息”,“信息"); Application.Exit(); } private void vsbPersonal_Scroll(object sender, System.Windows.Forms.ScrollEventArgs e) //滚动条 { vAbsPos = (float)(vsbPersonal.Value - vsbPersonal.Minimum); SetVerticalScrollMultiplier(grpGeneral); //检查滚动方向是自上往下还是自下往上 if (vsbPersonal.Value > oldValue) { grpGeneral.Top = grpGeneral.Top - (int)(vScrollMultiplier * vAbsPos); picLogo.Top = picLogo.Top - (int)(vScrollMultiplier * vAbsPos); grpGender.Top = grpGender.Top - (int)(vScrollMultiplier * vAbsPos); btnNext.Top = btnNext.Top - (int)(vScrollMultiplier * vAbsPos); } else if (vsbPersonal.Value < oldValue) { grpGeneral.Top = grpGeneral.Top + (int)(vScrollMultiplier * vAbsPos); picLogo.Top = picLogo.Top + (int)(vScrollMultiplier * vAbsPos); grpGender.Top = grpGender.Top + (int)(vScrollMultiplier * vAbsPos); btnNext.Top = btnNext.Top + (int)(vScrollMultiplier * vAbsPos); } oldValue = vsbPersonal.Value; } private void SetVerticalScrollMultiplier(Control ctrl) //计算垂直滚动量 { float hsb = (float)(vsbPersonal.Height - ctrl.Height); float ticks = (float)(vsbPersonal.Maximum - vsbPersonal.Minimum); vScrollMultiplier = hsb / ticks; } 小结  WinForms 可用于 Windows 窗体应用程序开发  Windows 窗体控件是从 System.Windows.Forms.Control 类派生的类  标签控件用于显示用户不能编辑的文本或图像  按钮控件提供用户与应用程序交互的最简便方法  组合框控件是列表框控件和文本框控件的组合,用户可以键入文本,也可以从所提供的列 表中选择项目  窗体提供了收集、显示和传送信息的界面,是 GUI 的重要元素  消息框显示消息,用于与用户交互  WinForms 单选按钮控件允许用户进行设置  WinForms 的图片框控件允许用户在窗体上添加和显示位图、元文件、JPEG、GIF 或 PNG 等 格式的图形  WinForms 的选项卡控件将类似的功能集中在一起,放在一个对话框或窗口中  WinForms 的 HScrollBar 和 VScrollBar 用作水平和垂直滚动条,分别以水平和垂直方式 浏览(或滚动)整个文档  进度条控件用于指示操作的进度,并显示排列在水平条中一定数目的矩形,通常通过在程 序中设置其 Value 值来显示任务完成的百分比 第六章 调试、测试和异常处理 本章主要目标 通过本章的学习,主要把握以下内容:  理解如何调试应用程序和排除错误  掌握如何测试 C# 应用程序  了解测试和调试的区别  在程序中进行错误捕获和错误处理 本章重点  调试 C#应用程序并处理其中的错误 本章难点  自定义异常 6.1、调试 开发应用程序后,必须确保其开发的成果无错误、无故障、可靠和稳健。当然,一些实际的项目 是不能保证完全没有错误,但程序员要保证尽力发现软件存在的错误信息并修正这些错误。 查找和排除错误或故障的过程称为调试。在部署应用程序前必须先对其进行调试,常见的错误 信息有以下三种: 语法错误:如常见的语法错误、缺少括号等,这类错误在编译时确定,并且也容易更正。 逻辑错误:一般是由于错误的算法引起的,如结果不对、公式不对等,这类错误在执行过程 时出现,难以调试 运行时错误:在运行是出现,如内存泄漏、以零作除数、异常等,难以调试。 如何调试 利用.NET 提供的调试器,其功能有:  观察程序的运行时行为  跟踪变量的值  确定语义错误的位置  查看寄存器的内容  查看内存空间  跨语言调试:可以调试使用 VB.NET、VC++.NET 等以及和 sql 编写的程序  调试使用 .NET 框架编写的应用程序以及 Win32 本机应用程序  加入正在运行的程序  调试多个程序 大致过程  设置断点  调试模式运行:编译有两种手段。  调试模式:可用来重复编译应用程序和排除错误,直至能够成功运行。  发布模式:当应用程序无需重复编译即可发布时,再改成发布模式编译,然后发布。 调试工具 局部变量”窗口 显示当前作用域下的变量并跟踪他们的值,控制权一旦转移到其他方法,则系统会自动清除列 出的变量,显示当前方法的变量。如图 1 所示: 图 1 局部变量窗口 “监视”窗口 用计算变量和表达式的值,并跟踪他们的变化。如图 2 所示 快速计算变量和表达式的值。如图 3 所示 用于检查变量的值,给变量赋值以及运行一行代码。要查找变量的值,必须在变量名前加 “?”。如图 4 所示 图 2 监视窗口 图 3 快速监视对话框 图 4 即时窗口 6.2、异常 一个性能良好且稳健的程序应该允许异常情况发生、避免终止运行,这就要求编程人员能够预 知可能发生的特情况,并且在程序编码处理这些特殊情况,我们称这个过程叫“异常处理”或“错 误处理”。 如下列代码在执行过程会出现“算术除 0”这样的异常: INPUT Divisor IF Divisor = 0 THEN Result = Divident/Divisor .... System.Exception .NET Framework 提供了很多处理异常的类,如图 5 所示: 图 5 Exception 类的层次结构 Exception 类的属性 Message:描述错误信息 Source:显示异常发生时的应用程序或对象名 StackTrace:显示异常发生时的堆栈信息 InnerException:对内部异常的引用 引发异常的方式 使用显式 throw 语句来引发异常。在此情况下,控制权将无条件转到处理异常的部分代码 使用语句或表达式在执行过程中激发了某个异常的条件,使得操作无法正常结束,从而引发异 常 try 和 catch 块 格式: try { //可能发生异常的程序代码 } catch (异常对象 1) { //错误处理程序 } catch(异常对象 2) { //错误处理程序 } finally { //不管异常有无发生都要执行的代码 } 执行流程如图 6 所示 图 6 执行流程 示例 1: try { //程序代码 } catch(System.Exception E)//可以处理各种异常 { //错误处理代码 } 示例 2: try { //程序代码 } catch (IOException E) //捕捉 IO 异常 { //错误处理代码 } 使用 throw 引发异常 使用 throw 可以引发系统异常,也可以引发自定义异常,格式如下列代码所示: if (UserInput < 1 && UserInput > 100) { throw new InvalidNumberInput(UserInput + “不是有效输入(请输入 1 和 100 之 间的数字)”); } 自定义异常 写一个类,这个类必须直接或间接继承 System.ApplicationException 类。 示例 定义一个异常类: using System; public class EmailErrorException:ApplicationException { public string _message; //重写构造函数 public EmailErrorException():base() { _message = null; } public EmailErrorException(string message):base() { _message = message.ToString(); } public EmailErrorException(string message, Exception myNew):base(message,myNew) { _message = message.ToString(); } //Message 属性的重载 public override string Message { get { return "Email 格式错误。"; } } } throw 引发自定义异常 private bool SaveInfo(string name, string email) { string[] subStrings = email.Split('@'); //如果输入的 Email 不是被“@”字符分割成两段,则抛出 Email 错误异常 if(subStrings.Length != 2) { throw new EmailErrorException(); } else { int index = subStrings[1].IndexOf("."); //查找被“@”字符分成的两段的后一段中“.”字符的位置,没有“.” //或者“.”字符是第一个字符,则抛出 EmailErrorException 异常 if(index <= 0) { throw new EmailErrorException(); } //如果“.”字符是最后一个字符,抛出 EmailErrorException 异常 if(subStrings[1][subStrings[1].Length -1] == '.') { throw new EmailErrorException(); } } return true; } Catch 自定义异常 private void btnSubmit_Click(object sender, System.EventArgs e) { if(txtName.Text.Length == 0 && txtEmail.Text.Length == 0) { MessageBox.Show("请填写姓名和 Email。", "填写不完整" , MessageBoxButtons.OK , MessageBoxIcon.Information); return; } try { SaveInfo(txtName.Text, txtEmail.Text); } catch(EmailErrorException ex) { MessageBox.Show(ex.Message, "Email 格式错误" , System.Windows.Forms.MessageBoxButtons.OK , MessageBoxIcon.Information); return; } MessageBox.Show("保存成功。", "成功" , MessageBoxButtons.OK , MessageBoxIcon.Information); } 小结  调试是搜寻和消除应用程序中的错误的过程  语法错误表示编译器无法理解代码  调试模式可用来重复编译和排除应用程序中的错误,直至能够成功运行  “局部变量”窗口允许用户监控当前程序中所有变量的值  单元测试和集成测试是测试大型应用程序的常用技术  当应用程序遇到运行时错误时,就会引发异常  C# 中的所有异常都派生自 Exception 类 第七章 数据库编程:连接数据库 本章主要目标 通过本章的学习,主要把握以下内容:  了解 ADO.NET 结构  了解 ADO.NET 的组件  使用 Command 对象和 Connection 对象  使用 ADO.NET 进行事务处理 本章重点  了解基本的.NET 数据对象,如 Connection、Command 和 DataSet  学会使用不同的.NET 使用不同的程序及数据源 本章难点  事务处理  DataAdapter 和 Command、Connection 之间的关系。 7.1、ADO.NET 介绍 ADO.NET 是一组允许基于.NET 的应用程序访问数据库以便读取和更新信息的类,要使用这些类 需要引用 System.Data 命名空间。它以 ActiveX 数据对象 (ADO) 为基础以 XML(扩展标记语言) 为格式传送和接收数据,访问数据存储无需连接。 (1) 优点:  互操作性  性能  可伸缩性  标准化  可编程能力 (2) 结构:如图 1 所示 图 1 ADO.NET 结构 (3) ADO.NET 访问数据库的过程如图 2 所示 图 2 访问过程 (4) 数据库操作过程  对于 Insert,Update,Delete 等单向操作,其过程如图 3 所示 图 3 数据库操作模型-1  对于 Select 的双向操作,其过程如图 4 所示 图 4 数据库操作模型-2 7.2、.NET 数据提供程序 ADO.NET 允许在.NET Framwork 中访问和数据库数据,它主要有两个组件:  DataSet  .NET 数据提供程序,它又由:Connection、Command、DataReader、DataAdapter 对象组成。 目前,有四种类型的.NET 数据提供程序,具体包括:  SQLClient:位于 System.Data.SqlClient,适用于 Sql Servers 数据库 7.0 或以上版本  OLEDB:位于 System.Data.OleDb,用于连接可通过 OLDDB 提供程序访问的任何数据源,如 access。  Oracle:位于 System.Data.OracleClient,适用于 Oracle 数据库。  ODBC:位于 System.Data.Odbc,允许通过 ODBC 驱动程序管理器来访问数据。 访问过程如图 5 所示: 图 5 数据访问过程 其中:1,2,3 是从服务器检索数据的过程;A、B 是对数据库修改的过程。 7.3、基本组件 (1) Connection 对象: 用于应用程序和数据库之间的连接,每个数据提供程序都有自己的连接类,常见的如表 1 所示 表 2 .NET 提供程序及其连接类 .NET Framework 数据提供程序 Connection 类 SQL 数据提供程序 SqlConnection OLE DB 数据提供程序 OleDbConnection Oracle 数据提供程序 OracleConnection ODBC 数据提供程序 OdbcConnection Connection 对象的主要属性:  ConnectionString:指定连接数据库的字符串描述。 例如要连接到 IP 地址为 123.111.101.200 的 sql server 数据库 Test,则可以这样书写连 接字符串: SqlConnection objSqlConnection = new SqlConnection ("server = 123.111.101.200;uid = sa; pwd = password; database = Test"); server 为服务器的名字,可以采用 IP 地址和数据库服务器的名称两种形式。 Database:与 Connection 对象连接的数据库。 主要方法  Close():关闭连接 注意:在 ADO.NET 中,必须显式关闭连接,才能释放实际的数据库连接。  Open():打开连接 (2) Command 对象 指定数据库执行的操作。与 Connection 一样,不同的提供程序有不同的 Command 对象,如表 3 所示 表 3 .NET 提供程序及其命令类 .NET Framework 数据提供程序 Command 类 SQL 数据提供程序 SqlCommand OLE DB 数据提供程序 OleDbCommand Oracle 数据提供程序 OracleCommand ODBC 数据提供程序 OdbcCommand 与数据库建立连接后,可用 Command 对象执行命令并从数据源返回结果。 属性:  CommandText:欲执行的内容,可以是 SQL 语句或者存储过程名称  CommandType:命令类型,如:StoreProcedure,TahleDirect 和 Text.  Connection:使用的活动连接。  方法:  ExecuteNonQuery():返回受影响的行数  ExecuteReader():返回 DataReader 类型值  ExecuteScalar():返回第一行第一列 示例 1:用 SQL 语句的 Command 设置 SqlCommand objComm=new SqlCommand(); objComm.CommandText="SQL 语句"; objComm.CommandType=CommandType.Text ; objComm. Connection=objConnection; 示例 2:用存储过程的 Command 设置 SqlCommand objComm=new SqlCommand(); objComm.CommandText=“sp_DeleteName";// Sp_DeleteName 为创建的存储过程 objComm.CommandType=CommandType. StoredProcedure ; objComm. Connection=objConnection; Sp_DeleteName 是在 SQL Server 服务器上创建的存储过程 7.4、事务处理 事务处理是一组数据操作,这些操作要么必须全部成功,要么必须全部失败,以保证数据的一 致性和完整性。 事务处理命令主要有:  Begin: 在执行事务处理中的任何操作之前,必须使用 Begin 命令来开始事务处理。  Commit: 在成功将所有修改都存储于数据库时,才算是提交了事务处理。  Rollback: 由于在事务处理期间某个操作失败,而取消事务处理已做的所有修改,这时将 发生回滚。 表 4 列出了用于实现事务处理的类。 表 4 事务类 类 说明 OdbcTransaction 表示对数据源进行的 SQL 事务处理 OleDbTransaction 表示对数据源进行的 SQL 事务处理 OracleTransaction 表示对数据库进行的事务处理 SqlTransaction 表示要对 SQL Server 数据库进行的 Transact-SQL 事务处理  SqlTransaction 类 属性 Connection:获取与事务处理关联的 SqlConnection 对象。 方法 Save():保存所有提交的事务处理 Commit( ):提交事务 Rollback( ):撤消事务  事务处理步骤 打开数据库连接 SqlConnection objSqlConnection = new SqlConnection ("server=SQLDB; uid=sa; pwd=password; database=pubs"); objSqlConnection.Open(); 开始事务 SqlTransaction objSqlTransaction = objSqlConnection.BeginTransaction(); 执行操作 objSqlCommand.Transaction = objSqlTransaction; insertCommand = "Insert into Student (Id, Name)values (111, "Jim"); objSqlCommand.CommandText = insertCommand; objSqlCommand.ExecuteNonQuery (); 提交事务 //如果操作过程中没有错误,则提交事务处理。 objSqlTransaction.Commit(); //如果操作过程中发生错误,则回滚已完成的所有修改 //objSqlTransaction.Rollback(); 关闭连接 objSqlConnection.Close(); 示例:以航空公司为例,演示如何建立连接以及如何插入、更新和删除记录。界面如图 6 所 示。 图 6 frmFlight 窗体 表 5 列出了所涉及到的控件及其属性 表 5 所用控件 控件 名称 属性 值 Form frmFlight Text 航班详细信息 Button btnAdd Text 添加(&A) btnModify Text 修改(&M) btnDelete Text 删除(&D) btnCancel Text 取消(&C) Label lblFlightCode Text 航班号: lblAirline Text 航空公司: lblDestination Text 终到站: lblSource Text 始发站: lblDeparture Text 起飞: lblArrival Text 到达: ComboBox cboSeats Text 座位数量: 主要代码 // 对象和变量声明 // Connection 对象和 Command 对象 private SqlConnection objSqlConnection; private SqlCommand objSqlCommand; // 类变量 private string insCmd; private string modCmd; private string delCmd; 在 load 事件中,进行连接 private void frmFlight_Load(object sender, System.EventArgs e) { // 初始化 connection 对象 objSqlConnection = new SqlConnection ("server = VIJAYK; database = Flights; uid = sa; pwd = playware"); // 将座位数量添加到组合框中 this.cboSeats.Items.Clear(); this.cboSeats.Items.Add("100"); this.cboSeats.Items.Add("150"); this.cboSeats.Items.Add("200"); this.btnModify.Enabled = false; this.btnDelete.Enabled = false; } // 添加记录代码 private void btnAdd_Click(object sender, System.EventArgs e) { // 插入命令 insCmd = "insert into FlightDetails values ('"+this.txtFlightCode.Text+"', '"+this.txtAirline.Text+"','“ +this.txtDestination.Text+"', '“+this.txtSource.Text+"','“ +this.txtArrival.Text+"', '"+this.txtDeparture.Text+"', “ +this.cboSeats.SelectedItem.ToString()+")"; // 初始化 command 对象 objSqlCommand = new SqlCommand(insCmd, objSqlConnection); try { // 打开连接 objSqlConnection.Open(); // 执行插入语句 objSqlCommand.ExecuteNonQuery(); MessageBox.Show(“已成功添加记录"); // 启用和禁用按钮 this.btnModify.Enabled = true; this.btnDelete.Enabled = true; this.btnAdd.Enabled = false; this.txtFlightCode.Enabled = false; } catch(SqlException ex) { MessageBox.Show(ex.Message); } finally { // 关闭连接 objSqlConnection.Close(); } } // 更新记录代码 private void btnModify_Click(object sender, System.EventArgs e) { modCmd = "update FlightDetails set Airline = '“ +this.txtAirline.Text+"', Destination = '“ +this.txtDestination.Text+"', Source = '"+this.txtSource.Text+"', Arrival = '"+this.txtArrival.Text+"', Departure = '"+this.txtDeparture.Text+"', TotalSeats = "+this.cboSeats.SelectedItem.ToString()+" where FlightCode like '"+this.txtFlightCode.Text+"'"; objSqlCommand = new SqlCommand(modCmd, objSqlConnection); try { objSqlConnection.Open(); objSqlCommand.ExecuteNonQuery(); MessageBox.Show(“已成功更新记录"); } catch(SqlException ex) { MessageBox.Show(ex.Message); } finally { objSqlConnection.Close(); } } // 删除记录代码 private void btnDelete_Click(object sender, System.EventArgs e) { delCmd = "delete from FlightDetails where FlightCode like '“ +this.txtFlightCode.Text+"'"; MessageBox.Show(delCmd); // 初始化 command 对象 objSqlCommand = new SqlCommand(delCmd, objSqlConnection); try { // 初始化 DialogResult DialogResult objDialogResult = MessageBox.Show(“您确定要删除当前记录吗?", “确认", MessageBoxButtons.YesNo); // 确定用户的响应 if (objDialogResult.Equals(DialogResult.Yes)) { objSqlConnection.Open(); objSqlCommand.ExecuteNonQuery(); MessageBox.Show(“已删除记录"); btnModify.Enabled = false; } } 小结  NET framework 中的 ADO.NET 是一组类,允许应用程序与数据库交互,以便检索和更新信 息  DataSet 和 .NET 数据提供程序是 ADO.NET 的两个主要组件  每种 .NET 数据提供程序都是由以下四个对象组成: 1. Connection 2. Command 3. DataAdapter 4. DataReader  Connection 对象用于在应用程序和数据库之间建立连接  Command 对象允许向数据库传递请求、检索和操纵数据库中的数据  事务处理是一组数据操作,这些操作要么必须全部成功,要么必须全部失败,以保证数据 的一致性和完整性 第八章 数据库编程:检索和操作数据 本章主要目标 通过本章的学习,主要把握以下内容:  理解并使用数据集对象  使用 DataAdapter 对象  使用 DataReader 对象  了解 DataGridView 控件常见的属性和方法  掌握 DataGridView 的数据绑定  掌握在 DataGridView 控件中插入、更新和删除数据  掌握定制 DataGridView 界面 本章重点 使用 DataSet 访问数据库中的数据,了解如何使用 DataAdapter 和 DataReader 对象 DataGridView 的数据绑定和对 DataGridView 控件中插入、更新和删除数据以及定制 DataGridView 界面。 本章难点 如何使用 DataSet、DataAdapter 和 DataReader 对象 数据绑定、DataGridView 定制 8.1、数据集 DataSet 数据集是一个用于存储从数据库检索到的数据的对象,它由数据行和列、约束和有关表对象中 数据关系的信息组成的 0 个或多个表对象的集合。这些数据缓存在本地机上,不需要与数据库持续 连接。 图 1 描述了 DataSet 类的层次结构。 图 1 DataSet 类的层次结 DataTableCollection:包含特定数据集的所有 DataTable 对象 DataTable :表示数据集中的一个表 DataColumnCollection:表示 DataTable 对象的结构 DataRowCollection :表示 DataTable 对象中的实际数据行 DataColumn :表示 DataTable 对象中列的结构 DataRow :表示 DataTable 对象中的一个数据行 数据集工作原理 图 2 数据集工作原理 先向服务器发出请求 服务器用 DataAdapter 将所要求的数据存储到数据集中。 将数据集发给客户端 客户端修改数据集中的数据时,并不是立即修改数据库中的数据,而是等修改完毕后,统 一将修改过的数据更新到数据库。 数据集的类型 类型化:它是一个生成类,继承了基类 DataSet 的所有方法、事件和属性。如: string employeeName; employeeName = dsEmployees.Emp[0].EmpName; 该代码表示获取数据集的 Emp 表第一行 EmpName 的值 非类型化:表和列只能以集合的形式公开,而不能用于借助 XML 结构文件派生新类 string employeeName; employeeName = dsEmployees.Tables["Emp"].Rows[0]["EmpName"]; 如何创建: 数据集实例是由 DataSet 构造函数创建的,数据集的名称是可选的,不需要指定,如 果没有指定名称,则以默认名称 NewDataSet 创建数据集。 如:DataSet empDS = new DataSet("EmployeeDetails"); 数据集的属性和方法 属性 DataSetName:用于获取或设置当前数据集的名称 Tables :用于检索数据集中包含的表集合 方法: Clear:清除数据集中包含的所有表的所有行 HasChanges :返回一个布尔值,指示数据集是否更改了 DataTable、DataColumn 和 DataRow DataTable:数据集中的数据以 DataTable 对象的形式存储,DataTable 类属于 System.Data 命名空间。 属性、事件和方法 表 1 DataTable 属性、事件和方法 属性 说明 Columns 表示列的集合或 DataTable 包含的 DataColumn Constraints 表示特定 DataTable 的约束集合 DataSet 表示 DataTable 所属的数据集 PrimaryKey 表示作为 DataTable 主键的字段或 DataColumn Rows 表示行的集合或 DataTable 包含的 DataRow HasChanges 返回一个布尔值,指示数据集是否更改了 方法 说明 AcceptChanges 提交对该表所做的所有修改 NewRow 添加新的 DataRow 事件 说明 ColumnChanged 修改该列中的值时激发该事件 RowChanged 成功编辑行后激发该事件 RowDeleted 成功删除行时激发该事件 以下代码演示如何使用多个 DataTabel 对象实例,并将其添加到 Tables 集合中。 DataSet studentDS = new DataSet(); DataTable objStudentTable = studentDS.Tables.Add("Students"); DataColumn:定义 DataTable 的列属性 表 2 DataColumn 属性 属性 说明 AllowDBNull 表示一个值,指示对于该表中的行,此列是否允许 null 值 ColumnName 表示指定 DataColumn 的名称 DataType 表示指定 DataColumn 对象中存储的数据类型 DefaultValue &, amp;, lt;, SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">表示新建行 时该列的默认值 Table 表示 DataColumn 所属的 DataTable 的名称 Unique 表示 DataColumn 的值是否必须是唯一的 以下代码演示如何使用多个 DataColumn 对象创建 DataTabel. DataTable objStudentTable = new DataTable("Students"); DataColumn bjStuNumber= objStudentTable.Columns.Add (" StudentNo ",typeof(Int32)); objStuNumber.AllowDBNull = false; objStuNumber.DefaultValue = 25; objStudentTable. .Columns.Add(objStuNumber); objStudentTable.Columns.Add("StudentName",typeof(Int32)); objStudentTable.Columns.Add("StudentMarks",typeof(Double)); DataRow:表示 DataTable 中的实际数据 表 3 DataRow 属性和方法 属性 说明 Item 表示 DataRow 的指定列中存储的值 RowState 表示行的当前状态 Table 表示用于创建 DataRow 的 DataTable 的名称 方法 说明 AcceptChanges 用于提交自上次调用了 AcceptChanges 之后对该行所做的所 有修改 Delete Deletes the DataRow 用于删除 DataRow RejectChanges 用于拒绝自上次调用了 AcceptChanges 之后对 DataRow 所做 的所有修改 以下代码演示如何使用 DataTabel 对象创建新的 DataRow。 //定义表结构,为 Students 表添加学号、姓名、分数三列 DataTable objStudentTable = new DataTable("Students"); DataColumn objStudentNumber = new DataColumn(); objStudentNumber.DataType = objStudentTable.Columns.Add (" StudentNo ",typeof(string)); objStudentNumber.AllowDBNull = false; objStudentNumber.DefaultValue = 25; objStudentTable.Columns.Add("StudentName",typeof(string)); objStudentTable.Columns.Add("StudentMarks",typeof(Double)); //向表中填充数据 DataRow objStudentRow; objStudentRow= objStudentTable.NewRow(); objStudentRow["StudentNo"]=101; objStudentRow[“StudentName”]=“张三"; objStudentRow["StudentMarks"]=55; objStudentTable.Rows.Add(objStudentRow); 定义主键:表中的主键用于对记录进行唯一标识,DataTable 的 PrimaryKey 属性接受 含有一个或多个 DataColumn 对象的数组。 设置单个列为 DataTable 的主键 objStudentTable.PrimaryKey = new DataColumn[]{objStudentTable.Columns["StudentNo"]}; 为 DataTable 对象设置复合主键 objStudentTable.PrimaryKey = new DataColumn[] {objStudentTable.Columns["StudentNo"], bjStudentTable.Columns["StudentName"]}; DataTable 的约束:是对表中数据施加的限制或规则集,决定表中可以存储的数据。主要有 ForeignKeyConstraint(外码约束)和 UniqueConstraint(主键约束)两种。 DataView 用作 DataTable 中存储的数据的表示层,提供对 DataTable 进行排序、筛选和搜索的自 定义视图,允许 WinForms 控件进行数据绑定,可用于查看 DataTable 中存储的数据的子集。 属性和方法 表 4 DataView 属性和方法 属性 说明 Item 用于从指定的表中获取一行数据 RowFilter 用于获取或设置表达式,该表达式用于筛选可以在 DataView 中查看的行 RowStateFilter 用于获取 DataView 的行状态筛选器 Table 用于表示源 DataTable 方法 说明 AddNew 向 DataView 添加新行 Delete 用于删除指定索引处的 以下代码演示如何创建 DataView 并对视图应用某种筛选器。 DataView objStudentView = new DataView(objStudentTable); objStudentView.RowFilter = "StudentMarks > 60"; for(int ctr =0; ctr < objStudentView.Count; ctr++) {MessageBox.Show(objStudentView[ctr]["StudentNo"].ToString());} 8.2、DataAdapter 对象 DataAdapter 是与数据集一起使用的对象,它包括在和一个数据库连接后用于填充数据集和更 新数据源的一组数据命令。它主要用于管理与数据库的连接、执行命令并向数据集返回数据。不同 的.NET 提供程序提供不同的 DataAdapter 类,如下表所示: 表 5 不同的 DataAdapter .NET Framework 数据提供程序 Connection 类 SQL 数据提供程序 SqlDataAdapter OLE DB 数据提供程序 OleDbDataAdapter Oracl, e 数据提供程序 Orac, leDataAdapter ODBC 数据提供程序 OdbcDataA, dapter 常用的属性和方法 属性 AcceptChangesDuringFill:决定在把行复制到 DataTable 中时对行所做的修改是否可以接受 TableMappings:容纳一个集合,该集合提供返回行和数据集之间的主映射 方法 Fill:用于添加或刷新数据集,以便使数据集与数据源匹配 FillSchema :用于在数据集中添加 DataTable,以便与数据源的结构匹配 Update:将 DataSet 里面的数值存储到数据库服务器上 OLEDBDataAdapter:用于访问任何由 OleDb 提供程序公开的数据源,用作数据集和数据源之间 的桥梁,以便检索和存储数据,与 OleDbConnection 和 OleDbCommand 一起使用以提高性能。 常用属性: InsertCommand:表示用于在数据库中插入新记录的 SQL 语句或存储过程 UpdateCommand:表示用于在数据库中更新记录的 SQL 语句或存储过程 DeleteCommand :表示用于从数据库中删除记录的 SQL 语句或存储过程 SelectCommand :表示用于从数据库中选择记录的 SQL 语句或存储过程 事件 RowUpdated:在对数据源执行更新命令之后的过程中激发该事件 RowUpdating:在对数据源执行命令更新之前的过程中激发该事件 示例: 使用 OLEDBDataAdapter 对象,将从 access 数据库中检索记录到数据集中。 OleDbConnection objOleConnection = new OleDbConnection(); objOleConnection.ConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source = D:\\Students.mdb"; objOleConnection.Open(); string query ="SELECT * from Student"; DataSet objDataSet = new DataSet(); OleDbDataAdapter objOleAdapter = new OleDbDataAdapter(); objOleAdapter.SelectCommand = new OleDbCommand(query,objOleConnection); objOleAdapter.Fill(objDataSet,"Students"); SQLDataAdapter:它设计为使用 Microsoft SQL Server 7 或更高版本提供最佳通信, 该适配器在数据集和 Microsoft SQL Server 之间起桥梁作用,提供用于保存和检索数据的接口, 与 SqlConnection 和 SqlCommand 相互配合使用 示例:使用 SQLDataAdapter,从 sql server 数据库中检索数据到数据集中。 SqlConnection objSqlConnection = new SqlConnection ("SERVER=MYSERVER;database=Students;uid=sa;password=playware"); SqlDataAdapter objSqlAdapter = new SqlDataAdapter("SELECT * from Student",objSqlConnection); objSqlConnection.Open(); DataSet objDataSet = new DataSet(); objSqlAdapter.Fill(objDataSet,"Students"); 8.3、DataReader DataReader 是.NET Framework 数据提供程序的另一个轻量级组件,一次只返回一行数据,并且 是只读的。比较适合于优化的只读数据访问。不同的.NET 数据提供程序有不同的 DataReader 类, 如下表所示: 表 6 不同的 DataReader .NET Framework 数据提供程序 DataReader 类 SQL 数据提供程序 SqlDataReader OLE DB 数据提供程序 OleDbDataReader Oracle 数据提供程序 OracleDataReader ODBC 数据提供程序 OdbcDataReader DataReader 常用属性和方法 属性: FieldCount :返回当前行中的列数 HasRows :容纳一个指示读取器是否含有一行或多行的值 IsClosed :表示 DataReader 是否关闭 RecordsAffected :表示执行 SQL 语句之后修改、插入或删除的行数 方法: Close :用于关闭 DataReader 对象 GetBoolean :用于获取特定列的布尔值 GetInt32 :用于返回列的整型值 GetString :用于获取特定列的 String 值 GetValue :用于返回本机格式的特定列的值 Read:使 DataReader 前移到下一个记录 示例:下列代码将演示执行给定查询、从数据流中检索行,并将结果集绑定到 DataReader 类的给定实例中。 SqlConnection objSqlConnection = new SqlConnection("SERVER=MYSERVER; database=Students; uid=sa;password=playware"); string query = "SELECT * from Student"; SqlCommand objSqlCommand = new SqlCommand(query,objSqlConnection); objSqlConnection.Open(); SqlDataReader objSqlReader = objSqlCommand.ExecuteReader(); while(objSqlReader.Read()) {MessageBox.Show(“学号: "+objSqlReader.GetValue(0));} 8.4、用于查询和检索数据的 Windows 应用程序示例  创建一个名为“Example 1”的 Windows 应用程序  将 Form1.cs 更改为 frmPassenger.cs  设计窗体,如图 3 所示 图 3 “乘客详细信息”窗体  命名控件  将以下命名空间包含在项目中 using System.Data; using System.Data.SqlClient;  在类声明部分声明以下变量 private SqlConnection objSqlConnection; private SqlDataAdapter objDataAdapter;  将以下代码添加到 frmPassenger 的 Load 事件中。 private void frmPassenger_Load(object sender, System.EventArgs e) {this.cboSex.Items.Add(“男") this.cboSex.Items.Add(“女"); objSqlConnection = new SqlConnection("server=MYSERVER;database=Flights; uid=sa; pwd = playware;"); }  在 btnAdd 按钮的 Click 事件中添加以下代码。 private void btnAdd_Click(object sender, System.EventArgs e) {try {objSqlConnection.Open(); DataSet objDataSet = new DataSet(); objDataAdapter = new SqlDataAdapter("Select * from Passenger",objSqlConnection); objDataAdapter.Fill(objDataSet,"Passenger"); objDataAdapter.InsertCommand =objSqlConnection.CreateCommand(); objDataAdapter.InsertCommand.CommandText = "INSERT INTO Passenger(FlightCode,PassportNo,Name,SeatNo, Sex,Age) " +"VALUES('"this.txtFlightCode.Text+"','“this.txtPassport.Text+"',’" +this.txtName.Text+"',“+this.txtSeatNo.Text+",'“ +this.cboSex.SelectedItem.ToString()+"',“ +this.txtAge.Text+")"; DataTable objDataTable = new DataTable(); objDataSet.Tables.Add(objDataTable); DataRow objDataRow = objDataSet.Tables[0].NewRow(); objDataRow["FlightCode"]=this.txtFlightCode.Text; objDataRow["PassportNo"]=this.txtPassport.Text; objDataRow["Name"]=this.txtName.Text; objDataRow["SeatNo"]=Convert.ToInt32(this.txtSeatNo.Text); objDataRow["Sex"]=this.cboSex.SelectedText; objDataRow["Age"]=Convert.ToInt32(this.txtAge.Text); objDataSet.Tables[0].Rows.Add(objDataRow); objDataAdapter.Update(objDataSet,"Passenger"); Application.DoEvents(); MessageBox.Show(“已插入行"); this.ClearFields();} catch(SqlException ex){ . . .}}  在 btnDelete 按钮的 Click 事件中添加下列代码。 private void btnDelete_Click(object sender, System.EventArgs e) {try {objSqlConnection.Open(); DataSet objDataSet = new DataSet(); objDataAdapter = new SqlDataAdapter("Select * from Passenger",objSqlConnection); objDataAdapter.Fill(objDataSet,"Passenger"); objDataAdapter.DeleteCommand = objSqlConnection.CreateCommand(); objDataAdapter.DeleteCommand.CommandText = "DELETE from Passenger where FlightCode='"+ this.txtFlightCode.Text+"'"; DataTable objDataTable = new DataTable(); objDataSet.Tables.Add(objDataTable); foreach(DataRow dr in objDataSet.Tables[0].Rows) {if(dr["FlightCode"].Equals(this.txtFlightCode.Text)) {DataRow objDataRow = dr; this.txtFlightCode.Text = objDataRow["FlightCode"].ToString(); this.txtPassport.Text = objDataRow["PassportNo"].ToString(); this.txtName.Text = objDataRow["Name"].ToString(); this.txtSeatNo.Text = objDataRow["SeatNo"].ToString(); this.cboSex.SelectedText = objDataRow["Sex"].ToString(); this.txtAge.Text = objDataRow["Age"].ToString(); objDataRow.Delete(); objDataAdapter.Update(objDataSet,"Passenger"); objDataSet.AcceptChanges(); MessageBox.Show(“已删除记录"); ClearFields(); break;} else {MessageBox.Show(“未找到记录"); break;}}} catch(SqlException ex) . . . }  在项目中添加 ClearFields() 方法 private void ClearFields() {this.txtName.Text=""; this.txtFlightCode.Text=""; this.txtAge.Text=""; this.txtPassport.Text=""; this.txtSeatNo.Text=""; this.cboSex.Text="";} • 在 btnCancel 按钮的 Click 事件中添加代码 private void btnCancel_Click(object sender, System.EventArgs e) {this.Close();} 8.5、DataGridView 控件 在数据库编程中,DataGridView 控件是.NET 中提供的最通用、功能最强和最灵活的数据绑定控 件,它以表的形式显示数据,并可以根据需要对数据进行插入、删除、排序、分页、更新等操作。 DataGridView 常用属性、方法和事件 属性 AllowNavigation:是否允许导航 AllowSorting:是否允许排序 AllowPaging:是否允许分页 AllowCustomPaging:是否允许定制 CurrentCell:当前单元格 DataBindings:获, 取或设置控件的数据绑定 DataSource:数据源 Item:特定单元格的值 方法: Focus():获得焦点 IsSelected():是否被选定 Select():选定特定行 事件: CurrentCellChanged:修改 CurrentCell 时触发 Click:单击时 绑定数据源 数据源可以是 DataSet、DataView、DataViewManager、数组或列表。 示例代码:绑定表<, /SPAN>Passenger: dbgPassenger.DataSource = objDataSet.Tables["Passenger"].DefaultView; 或者,也可以写成: <, SPAN lang=EN-US>dbgBooks.DataSource = obj, DataSet; dbgBooks.DataMember = " Passenger "; 还可以写成:dbgBooks.SetDataBinding(objDataSet," Passenger "); 插入、更新和删除记录 用户修改了 DataGridView 中的数据后,DataGridView 会自动更新数据集中的记录。其更新流 程如图 4 所示。当执行 Update()方法后,会自动调用相应的命令 Insert、update 和 delete 命令来 执行数据库操作。 图 4 插入、更新和删除记录过程 由于代码应具有封装性,有些值不好确定,所以可以通过 SqlParameter 类来传递参数值给 SqlCommand,如: SqlParameter objSqlParameter; objSqlParameter = objSqlDataAdapter.Parameters.Add("@StudentNo", SqlDbType.Int, 3); //创建变量标识符,并用 @ 符号作为前缀 objSqlParameter.SourceColumn = "ColumnName"; objSqlParameter.SourceVersion = DataRowVersion .Current; 应用程序示例:设计一个图 5 所示的界面: 图 5 界面 主要代码: private void frmPassenger_Load(object sender, System.EventArgs e) {// 初始化连接 objSqlConnection = new SqlConnection("Server = MYSERVER; Database = Flights; uid = sa; pwd = playware"); // 初始化 DataAdapter objSqlDataAdapter = new SqlDataAdapter("select * from Passenger", objSqlConnection); // 填充数据集 objSqlDataAdapter.Fill(objDataSet, "Passenger"); // 将 Passenger 表设置为 DataGridView 的默认视图 dbgPassenger.DataSource = objDataSet.Tables[0].DefaultView; // 如果行数为零,则禁用“更新”和“删除”按钮 if (objDataSet.Tables["Passenger"].Rows.Count == 0) {btnUpdate.Enabled = false; btnDelete.Enabled = false;}} //插入记录 private void btnInsert_Click(object sender, System.EventArgs e) {// 插入语句 string insCmd = "Insert into Passenger values(@FlightCode, @PassportNo, @Name, @SeatNo, @Sex, @Age)"; // 初始化 InsertCommand objSqlDataAdapter.InsertCommand = new SqlCommand(insCmd, objSqlConnection); // 声明参数并指定数据库的列和版本 objSqlParameter = objSqlDataAdapter.InsertCommand.Parameters.Add("@FlightCode", SqlDbType.VarChar); objSqlParameter.SourceColumn = "FlightCode"; objSqlParameter.SourceVersion = DataRowVersion.Current; objSqlParameter = objSqlDataAdapter.InsertCommand.Parameters.Add("@PassportNo", SqlDbType.VarChar); objSqlParameter.SourceColumn = "PassportNo"; objSqlParameter.SourceVersion = DataRowVersion.Current; // 如果数据集已更改,则插入记录 if (objDataSet.HasChanges()) {try {// DataAdapter 的 Update() 方法将调用 InsertCommand objSqlDataAdapter.Update(objDataSet, "Passenger"); MessageBox.Show(“已插入记录"); btnUpdate.Enabled = true; btnDelete.Enabled = true;} catch(SqlException ex) {// 显示错误消息 MessageBox.Show(ex.Message);}} else MessageBox.Show(“提供详细信息以添加新记录");} //删除记录 private void btnDelete_Click(object sender,System.EventArgs e) {string delCmd = "Delete from Passenger where FlightCode = @FlightCode)"; objSqlDataAdapter.DeleteCommand = new SqlCommand(delCmd, objSqlConnection); objSqlParameter = objSqlDataAdapter.DeleteCommand.Parameters.Add("@FlightCode ", SqlDbType.VarChar); objSqlParameter.SourceColumn = "FlightCode "; objSqlParameter.SourceVersion = DataRowVersion.Original; objDialogResult = MessageBox.Show(“您确定要删除当前记录吗?”,” 确认 ",MessageBoxButtons.YesNo, MessageBoxIcon.Question); if ( objDialogResult == DialogResult.Yes) {try { // 确定选定的行 objDataSet.Tables[0]. Rows[dbgPassenger.CurrentRowIndex].Delete(); // DataAdapter 的 Update() 方法将调用 DeleteCommand objSqlDataAdapter.Update(objDataSet, "Passenger"); MessageBox.Show(“已删除记录");} catch(SqlException ex) {MessageBox.Show(ex.Message);}} else return; // 如果行数为零,则禁用“删除”和“更新”按钮 if (objDataSet.Tables["Passenger"].Rows.Count == 0) {btnUpdate.Enabled = false; btnDelete.Enabled = false;}} //更新记录 private void btnUpdate_Click(object sender, System.EventArgs e) {// 更新语句 string updCmd = "Update Passenger set FlightCode = @FlightCode, PassportNo = @PassportNo, Name = @Name, SeatNo = @SeatNo, Sex = @Sex, Age = @Age where FlightCode = @FlightCode"; // 初始化 UpdateCommand objSqlDataAdapter.UpdateCommand = new SqlCommand(updCmd, objSqlConnection); // 声明参数并指定数据库的列和版本 objSqlParameter = objSqlDataAdapter.UpdateCommand.Parameters. Add("@FlightCode", SqlDbType.VarChar); objSqlParameter.SourceColumn = "FlightCode"; objSqlParameter.SourceVersion = DataRowVersion.Current; // 如果数据集已更改,则更新记录 if (objDataSet.HasChanges()) { // 初始化 DialogResult objDialogResult = MessageBox.Show("您想要保存这些更改吗?“, ” 确认", MessageBoxButtons.YesNo, MessageBoxIcon.Question); if (objDialogResult == DialogResult.No) return; else { // DataAdapter 的 Update() 方法将调用 UpdateCommand objSqlDataAdapter.Update(objDataSet, "Passenger"); MessageBox.Show(“数据已更新");}} else MessageBox.Show(“未做任何修改");} catch(SqlException ex) { // 显示错误消息 MessageBox.Show(ex.Message);}} 定制 DataGridView 样式层次结构 图 6 样式层次结构 定制成 CheckBox 型,如图 6、7 所示 图 7 DataGridViewTableStyle 集合编辑器 图 8 DataGridViewColumnStyle 集合编辑器 定制 ComboBox 型 假如要将 DataGridView 表中的学历字段,能作成一个下拉列表框,以便于用户选择,可采用以 下代码来实现: //定制整个网格的样式 DataGridViewTableStyle dgdtblStyle = new DataGridViewTableStyle(); gdtblStyle.MappingName =tbSourceTable.TableName; dbgNewGrid.TableStyles.Add(dgdtblStyle); dgdtblStyle.RowHeadersVisible = false; dgdtblStyle.HeaderBackColor =Color.LightSteelBlue; dgdtblStyle.AllowSorting = false; „„.. dbgNewGrid.BackgroundColor = Color.White; //实例化所需要的控件 ComboBox dgComboBoxColumn = new ComboBox(); dgComboBoxColumn.Items.AddRange(new object[]{"本科","硕士","博士"}); dgComboBoxColumn.Cursor = Cursors.Arrow; dgComboBoxColumn.DropDownStyle= ComboBoxStyle.DropDownList; dgComboBoxColumn.Dock = DockStyle.Fill; dgComboBoxColumn.SelectionChangeCommitted += new EventHandler dgComboBoxColumn_SelectionChangeCommitted); //将定制好的控件添加到选定列的 TextBox 的 Cont, rols 集合里面 DataGridViewTextBoxColumn dgTextBoxColumn = (DataGridViewTextBoxColumn)dbgNewGrid. TableStyles[0].GridColumnStyles[1]; dgTextBoxColumn.TextBox.Controls. Add(dgComboBoxColumn); 小结  在 DataSet 对象内表示的数据是数据库的部分或全部的断开式内存副本  DataAdapter 对象用来填充数据集和用更新集到数据库,这样方便了数据库和数据集之间 的交互  类型化数据集对象是 DataSet 类的派生类的实例,这些类都基于 XML 结构 DataTable 表示一个内存数据表,而 DataColumn 表示 DataTable 中列的结构 DataView 是 DataTable 中存储的数据的表示层 DataReader 对象提供只进、只读和连接式数据访问,并要求使用专用的数据连接 DataReader 对象提供检索强类型化数据的方法  在数据库编程中使用数据绑定控件时, DataGridView 控件是 Visual Studio .NET 中提 供的最通用、最强大和最灵活的控件  DataGridView 控件以表的形式显示数据,并根据需要支持数据编辑功能,如插入、更新、 删除、排序和分页  使用 DataSource 属性为 DataGridView 控件设置一个有效的数据源  调用 Update() 方法来执行相应的插入、更新和删除操作时,将执行 DataAdapter 的 InsertCommand、 UpdateCommand 和 DeleteCommand 属性  定制 DataGridView 界面 第九章 C#高级编程 本章主要目标 通过本章的学习,主要把握以下内容:  理解继承  在 C# 中使用继承  在 C#中使用接口  在 C#中使用方法的重写  实现委托  定义和触发事件  组件与程序集  反射 本章重点 继承、接口、方法重写、委托、事件 本章难点 实现抽象方法和非抽象方法 方法重写 接口实现 委托和事件 9.1、继承 继承是允许重用现有类去创建新类的过程,使用继承的好处是我们无需从头开始创建新类,便 可以在现有类的基础上添加新的属性、方法和事件。创建新类所依据的类称为父类或基类,新建的 类称为子类。可以这么说,子类是父类的具体化,父类是子类的一般化。 在 C#中,不支持多重继承,但可以借用接口这个概念来实现多重继承。 语法: class 子类:父类{} 示例: //父类 public class Person{ private string _name; private uint _age; public void GetInfo() { Console.WriteLine("请输入您的姓名和年龄"); _name = Console.ReadLine(); _age = uint.Parse(Console.ReadLine()); } public void DispInfo() { Console.WriteLine("尊敬的 {0},您的年龄为 {1} ", _name, _age); } } //派生类 public class Student:Person{ private string _school; private uint _eng; private uint _math; private uint _sci; public void GetMarks() { Console.WriteLine(“请输入学校名称"); _school = Console.ReadLine(); Console.WriteLine("请分别输入英语、数学和自然科学的分数。"); _eng = uint.Parse(Console.ReadLine()); _math = uint.Parse(Console.ReadLine()); _sci = uint.Parse(Console.ReadLine()); Console.WriteLine(“所得总分为:{0}",_eng+_math+_sci); } } public class Exercise{ static void Main(string[] args){ Student objStudent = new Student(); objStudent.GetInfo(); objStudent.DispInfo(); objStudent.GetMarks(); } } 说明: C#类始终继承一个基类(若未指定一个基类):Sytem.Object 类 子类继承父类的所有成员(私有成员除外) 可以利用 base 关键字来访问基类成员和被重写的方法。 在创建子类实例时,可以用 base 来调用基类的构造函数,base 只能访问基类的构造函数、实 例方法或实例属性,不能访问基类的静态方法。 示例 public class Person { public string _name; public uint _age; public Person(string name, uint age) { this._name = name; this._age = age; Console.WriteLine(_name); Console.WriteLine(_age); } } public class Student:Person { private uint _id; public Student(string name, uint age, uint id):base(name, age) { this._id = id; Console.WriteLine(_id); } static void Main(string[] args) { //构造 Student Student objStudent = new Student("XYZ", 45, 001); } } 9.2、方法重写 重写基类方法就是修改它的实现或者说在派生类中对它进行重新实现。 使用 override 关键字用于修改方法, override 修饰的方法是对基类中同名方法的新实现,基 类中的同名方法必须声明为 virtual 或 abstract 类型。默认情况下,方法并非 virtual 方法,因此 不能重写。基类和派生类同名方法的访问修饰符相同。 new、static 和 virtual 不能与 override 同时修饰一个方法。 Class Base { // 成员变量 int basevar; // 成员函数 virtual void GetInfo() {// 定义 } } Class Derived : Base { // 成员变量 int derivedvars; // 成员函数 override void GetInfo() {// 定义} } 使用 virtual 关键字用于将方法定义为多态。virtual 修饰的方法称为虚拟方法,子类可以用 override 来自由实现各自版本的虚拟方法。 virtual 不能与 static、override 同时修饰一个方法。 语法 [访问修饰符] virtual [返回类型] 方法名( [参数列表] ) { ... // Virtual 方法实现 ...} 示例 class Employee { public virtual void EmpInfo() { Console.WriteLine(“此方法显示职员信息"); } } class DervEmployee: Employee { public override void EmpInfo() { base.EmpInfo(); Console.WriteLine(“此方法重写 base 方法"); } } static void Main(string[] args) { DervEmployee objDervEmployee = new DervEmployee(); objDervEmployee.EmpInfo(); Employee objEmployee = objDervEmployee; objEmployee.EmpInfo(); } 关键字 new 用于显式隐藏继承之基类的方法,即如果子类方法与父类方法同名,new 会将派生类识别为一 个全新的成员。new 不能与 override 同时使用。 9.3、抽象类和抽象方法 不能实例化的类称为抽象类,抽象类是派生类的基础。通过不实现或部分实现,来创建模板, 以派生子类。 定义 abstract class ClassOne {//类实现} 说明 abstact 修饰的方法,只存放原型,无具体实 现)和非抽象方法。 override)。 示例 1 using System; namespace Example_5 { abstract class ABC { public abstract void AFunc(); public void BFunc() { Console.WriteLine(“这是一个非抽象方法!"); } } class Derv : ABC { public override void AFunc() { Console.WriteLine(“这是一个抽象方法! "); } static void Main(string[] args) { Derv objB = new Derv(); objB.AFunc(); objB.BFunc(); } } } 示例 2 abstract class MyAbs { public abstract void AbMethod(); } //派生类 class MyClass : MyAbs { public override void AbMethod() { Console.WriteLine(“在 MyClass 中实现的抽象方法"); } //派生自 MyClass 的子类 class SubMyClass:MyClass { public void General() { //未实现 AbMethod 抽象方法 Console.WriteLine("在 SubMyClass 中未实现的抽象方法 "); } } 9.4、接口 一个接口相当于一个抽象类,但比抽象类更抽象,它不能包含非抽象方法。接口的每个方法必 须在派生类中实现。 接口的作用是提供一个模板,指明实现此特定接口的类必须实现该接口列出的所有成员。 定义 interface 接口名 , { //只有方法声明,无实现 void method1(); int method2(); int method3(float); }    public,不能显式指定。  override 关键字。  I 开头。 示例 1: public interface IPict { int DeleteImage(); void DisplayImage(); } public class MyImages : IPict { //第一个方法的实现 public int DeleteImage() { Console.WriteLine(“DeleteImage 实现!"); return(5); } //第二个方法的实现 public void DisplayImage() { Console.Wr, iteLine("DisplayImage 实现!"); } static void Main(string[] args) { MyImages objM = new MyImages(); objM.DisplayImage(); int t = objM.DeleteImage(); Console.WriteLine(t); } } 示例 2:一个类既可以继承一个类,也可以实现接口,但这个基类必须先写入到基类列表中。 public class BaseIO { public void Open() { Console.WriteLine("BaseIO 的 Open 方法"); } } public interface IPict { int DeleteImage(); void DisplayImage(); } public class MyImages : BaseIO, IPict { public int DeleteImage() { Console.WriteLine(“DeleteImage 实现!"); return(5); } public void DisplayImage() { Console.WriteLine(“DisplayImage 实现!"); } void Main(string[] args){ MyImages objM = new MyImages(); objM.DisplayImage(); int val = objM.DeleteImage(); Console.WriteLine(val); objM.Open(); } } 多重接口实现:一个类不能继承多个类,但可以实现多个接口,如下例中的 MyImages 类。 public interface IPictManip {void ApplyAlpha();} //第二个接口 public interface IPict { int DeleteImage(); void DisplayImage(); } public class BaseIO { public void Open() { Console.WriteLine(“BaseIO 的 Open 方法"); } } //一个类和两个接口的实现 public class MyImages:BaseIO,IPict, IPictManip { public int int DeleteImage() { Console.WriteLine(“DeleteIamge 实现!”); } public void DisplayImage() { Console.WriteLine(“DisplayImage 实现!”); } public void ApplyAlpha() { Console.WriteLine(“ApplyAlpha 实现!”); } } class Test { static void Main(string[] args) { MyImages objM = new MyImages(); objM.DisplayImage(); objM.DeleteImage(); objM.Open(); objM.ApplyAlpha(); } } 显式接口实现 在 C# 中,只要不发生命名冲突,就完全可以允许多重接口实现,但如果两个接口中都有同一 个方法签名,则应使用接口名来显式指定。 如: public interface IPictManip { void ApplyAlpha(); void DisplayImage(); } public interface IPict { int DeleteImage(); void DisplayImage(); } public class BaseIO { public void Open() { Console.WriteLine(“BaseIO 的 Open 方法"); } } public class MyImages : BaseIO, IPict, IPictManip { public int DeleteImage() { Console.WriteLine(“DeleteImage 实现!"); return(5); } public void ApplyAlpha() { Console.WriteLine(“ApplyAlpha 实现!"); } void IPict.DisplayImage() { Console.WriteLine(“DisplayImage 的 IPict 实现"); } void IPictManip.DisplayImage() { Console.WriteLine(“DisplayImage 的 IPictManip 实现"); } } 接口继承:C#中,允许一个接口有多个父接口,实现该接口的类必须实现所有接口中的抽象方 法。 示例 public interface IPict {int DeleteImage();} public interface IPictManip { void ApplyAlpha(); void DisplayImage(); } //继承多重接口 public interface IPictAll:IPict, IPictManip {void ApplyBeta();} public class MyImages:IPictAll { public int DeleteImage() { Console.WriteLine(“DeleteImage 实现!"); return(5); } public void ApplyAlpha() {Console.WriteLine(“ApplyAlpha 实现!");} public void ApplyBeta() {Console.WriteLine(“ApplyBeta 实现!");} public void DisplayImage() {Console.WriteLine(“DisplayImage 实现!");} static void Main(string[] args) { MyImages objM = new MyImages(); objM.DisplayImage(); int val = objM.DeleteImage(); Console.WriteLine(val); objM.ApplyAlpha(); objM.ApplyBeta(); } } 9.5、委托 使用委托可以在运行时动态设定要访问的方法,即使不知道方法名称,也可以调用 方法,执行一个委托将执行该委托引用的方法。 定义委托 [访问修饰符] delegate 返回类型 委托名(); 其定义与方法类似,但必须和方法具有相同的签名(参数相同、返回类型相同)。 使其指向具体的方法。例如: class Delegates {public delegate int Call(int num1, int num2); //定义委托 class Math {public int Multiply(int num1, int num2) {// 实现} public int Divide(int num1, int num2) {// 实现}} class TestDelegates {static void Main() {Call objCall; Math objMath = new Math(); objCall = new Call(objMath.Multiply); //实例委托}} 调用委托意味着使用委托对方法进行实例化。其形式与方法类似,只是调用委托是 调用与委托关联的方法的实现代码。例如: class Delegates {// 委托定义 public delegate int Call(int num1, int num2); //定义委托 class Math {// 乘法方法 public int Multiply(int num1, int num2) {return num1*num2;} // 除法方法 public int Divide(int num1, int num2) {return num1/num2;} } static void Main(string[] args) {// 委托的对象 Call objCall; // Math 类的对象 Math objMath = new Math(); // 将方法与委托关联起来 objCall = new Call(objMath.Multiply); //实例委托 // 将委托实例化 int result = objCall(5, 3); //调用委托 System.Console.WriteLine("结果为 {0}", result); } } 9.6、事件 以电视节目上的抢答为例,主持人一开始宣布“请听题”,则参赛人会认真倾听主持人的问题。 从程序员的角度来看,当听到“请听题”时,相当于触发了一个事件,告之参赛者应认真听题。在 C#中,事件允许一个对象将发生的事件通知给其他对象。发出事件通知的称为发行者,对象可以订 阅事件,该对象称为订阅者,一个事件可以有多个订阅者。 定义事件 [访问修饰符] event 委托名 事件名;如: public delegate void delegateMe(); //定义委托 private event delegateMe eventMe; //定义一个事件 订阅事件 eventMe += new delegateMe(objA.Method); // objA、objB 都订阅了事件 eventMe eventMe += new delegateMe(objB.Method); 引发事件:通知所有订阅该事件的对象 if(condition) {eventMe();} 示例 class Delegate {// 定义委托 public delegate void MeDelegate(); // 定义事 public event MeDelegate NotifyEveryOne; public void Notify() {// 如果事件不为 null if(NotifyEveryOne != null) {Console.WriteLine("触发事件:"); // 触发事件 NotifyEveryOne(); } } } class ClassA { public void DispMethod() { Console.WriteLine(“Class A 已接到 NotifyEveryOne 事件的通知!"); } } // 第二个类 class ClassB { public void DispMethod() { Console.WriteLine(“Class B 已接到 NotifyEveryOne 事件的通知! "); } } class TestEvents {[STAThread] static void Main(string[] args) {// 委托的对象 Delegate objDelegate = new Delegate(); // ClassA 的对象 ClassA objClassA = new ClassA(); // ClassB 的对象 ClassB objClassB = new ClassB(); // 订阅该事件 objDelegate.NotifyEveryOne += new Delegate.MeDelegate(objClassA.DispMethod); objDelegate.NotifyEveryOne += new Delegate.MeDelegate(objClassB.DispMethod); objDelegate.Notify(); } } 小结  继承是获得现有类的功能的过程  创建新类所根据的基础类称为基类或父类,新建的类则称为派生类或子类  base 关键字用于从派生类中访问基类成员  override 关键字用于修改方法、属性或索引器。  new 访问修饰符用于显式隐藏继承自基类的成员抽象类是指至少包含一个抽象成员(尚未 实现的方法)的类。抽象类不能实例化  重写方法就是修改基类中方法的实现。virtual 关键字用于修改方法的声明  显式接口实现是用于在名称不明确的情况下确定成员函数实现的是哪一个接口  委托包含对方法而不是方法名称的引用  C# 中的事件允许一个对象将发生的事件或修改通知其他对象 第十章 数组、集合对象、泛型 本章主要目标 通过本章的学习,主要把握以下内容:  System.Array  System.Collections  ArrayList  泛型 本章重点  使用 System.Array 对象  理解集合对象的特点和优点  使用 System.ArrayList 对象  使用哈希表对象 本章难点 泛型集合 10.1、System.Array 介绍 Array 是抽象的基类,提供 CreateInstance 方法来创建数组。 Array obj = Array.CreateInstance(typeof(string),10); System.Array 的属性和方法 static void Main(string[] args) { //构建 objNames 数组 Array objNames = Array.CreateInstance(typeof(string),5); //初始化值 objNames.SetValue(“A",0); objNames.SetValue(“B",1); objNames.SetValue(“C",2); objNames.SetValue(“D",3); objNames.SetValue(“E",4); Console.WriteLine(“数组值"); for(int ctr = 0 ; ctr < 5; ctr++) { Console.WriteLine(“元素 {0}: {1}",ctr+1,objNames.GetValue(ctr)); } Console.WriteLine(“\n 数组中元素的总数是{0}", objNames.Length.ToString()); //输出数组秩 Console.WriteLine("\n 数组秩是 {0}",objNames.Rank.ToString()); //反转数组并输出 Array.Reverse(objNames); Console.WriteLine(“\n 反转数组后"); for(int ctr = 0 ; ctr < 5; ctr++) { Console.WriteLine(“元素 {0}: {1}",ctr+1, objNames.GetValue(ctr)); } } 10.2、System.Collections 介绍  HashTable 类 .NET 集合类提供了 Hashtable 类可以实现使用字符串来标识集合中的元素。我们把标识集合 重的元素的字符串称为关键字或者键。Hashtable 类集合的元素都是以键-值对应的形式存在的, 也就是说,再给该集合添加元素的时候要给每个元素指定一个键值(或者说关键词)。Hashtable 类的方法的定义如下: Hashtable 类 Add 方法用于将带有指定键和值的元素添加到 Hashtable 中 public virtual void Add(object key,object value); 其中 key 表示该元素的键值(或者说关键词),alue 表示该元素的值。 Hashtable 类还提供了类似数组的形式访问集合中的元素,如果有一个 数组 students 保存了 学生的年龄,那么我们可以通过 student[0]的形式获得第一个学生的年龄。Hashtable 提供的访问 集合中元素的方法和数组类似,但是比数组更加直观,数组访问元素的方法为:数组元素名[元素索 引]; 而 Hashtable 类方法特级和中元素的值的方法为:Hashtable 集合对象名[元素关键词] 从上面的语法可以看到,Hashtable 集合提供了通过关键词的形式访问元素的值,通过关键词 访问元素的值的方法将大大提高程序的直观性。 Hashtable 类常见的属性和方法: Count 属性 获取包含在 Hashtable 中的键值对的数目。 10.3、ArrayList 类  ArrayList 的特点: Array 类的容量或元素数是固定的,而 ArrayList 类的容量可以根据需要动态扩展。通过设置 ArrayList.Capacity 的值可以重新分配内存和复制元素 使用 ArrayList 提供的方法可以同时添加、插入或移除一个范围内的元素  数组的灵活性 可以设置数组的下界 数组可以有多个维 许多需要使用数组的实例都可以使用 ArrayList 简单的例子: ArrayList List = new ArrayList(); //给数组增加 10 个 Int 元素 for( int i=0;i<10;i++ ) List.Add(i); //..程序做一些处理 //将第 6 个元素移除 List.RemoveAt(5); //再增加 3 个元素 for( int i=0;i<3;i++ ) List.Add(i+20); //返回 ArrayList 包含的数组 Int32[] values = (Int32[])List.ToArray(typeof(Int32)); 10.4、泛型  什么是泛型集合 泛型最常见的用途是创建集合类 泛型集合可以约束集合内的元素类型 典型泛型集合 List,Dictionary  List的使用 引入命名空间:System.Collections.Generic List students = new List(); 利用 List存储班级集合  Dictionary概述 Dictionary具有 List相同的特性 约束集合中元素类型 编译时检查类型约束 无需装箱拆箱操作 与哈希表类似存储 Key 和 Value 的集合  泛型的重要性 泛型集合与传统集合相比类型更安全 泛型集合无需装箱拆箱操作 泛型是未来五年的主流技术之一 解决了很多需要繁琐操作的问题 提供了更好的类型安全性 CLR 支持泛型 小结  哈希表如何获取一个元素的 Value?  ArrayList 与哈希表存取对象需要什么操作?  List中的 T 表示什么?  泛型集合与传统集合获取元素时的区别? 第十一章 WinForms 高级编程 本章主要目标 通过本章的学习,主要把握以下内容:  了解 MDI 应用程序和 SDI 应用程序  了解菜单和掌握菜单控件的使用  掌握 ImageList 控件  掌握 ToolBar 控件和 StatusBar 控件  掌握 Timer 控件  掌握 TreeView 控件  掌握 ListView 控件 本章重点 应用 MDI 相关的属性创建多文档窗体 在应用程序中使用 ImageList、ToolBar 控件、Timer 、TreeView 、StatusBar 等控件 本章难点 菜单的使用 一些特殊控件的使用 11.1、SDI 和 MDI 简介 SDI SDI 应用程序是由一个窗口组成,一次只能打开一个文件,如 windows 环境下的记事本。 MDI MDI 应用程序由一个父窗口和若干个子窗口构成,如 excel 应用程序就是一个 MDI 程序(图 1)。 图 1 excel 的 MDI 界面 MDI 父窗体是 MDI 应用程序的基础,它可以包含多个子窗体,子窗体是用户和 MDI 应用程序 进行交互的副窗口。 MDI 的特点 启动一个 MDI 应用程序时,首先显示父窗体 它是应用程序中所有其他窗口的容器 每个应用程序界面都只能有一个 MDI 父窗体 在任何指定的时间都可以打开多个子窗体 任何 MDI 子窗体都不能移出 MDI 框架区域 任何 MDI 子窗体都不能移出 MDI 框架区域 关闭 MDI 父窗体则自动关闭所有打开的 MDI 子窗体 MDI 的属性和事件 表 1 MDI 的属性和事件 属性 说明 MdiChildren 用于获取表示多文档界面 (MDI) 子窗体的窗体数组 MdiParent 用于获取或设置当前多文档界面 (MDI) 父窗体 ActiveMdiChild 用于获取当前活动的多文档界面 (MDI) 子窗体 方法 说明 ActivateMdiChild 用于激活子窗体 LayoutMdi 排列 MDI 父窗体中的多文档界面 (MDI) 子窗体 事件 说明 Closed 由用户或窗体的 Close 方法关闭窗体后,发生该事件 Closing 正在关闭窗体时,发生该事件 MdiChildActivate 在 MDI 应用程序中激活或关闭多文档界面 (MDI) 子窗体时,触发该事 件 注意:如果窗体为 MDI 父窗体,则在触发 MDI 父窗体的 Closing 事件之前,将触发所有 MDI 子窗体的 Closing 事件。另外,在触发 MDI 父窗体的 Closed 事件之前,将触发所有 MDI 子窗体 的 Closed 事件 创建 MDI 窗体 将 IsMdiContainer 属性设置为 True 选择“项目”→“添加 Windows 窗体” 选择“项目”→“添加 Windows 窗体” 添加给定的代码,将其他窗体设置为子窗体。下面代码演示如何将 TestForm 设置为当 前窗体的子窗体。 TestForm objChild = new TestForm(); objChild.MdiParent = this; objChild.Show(); 激活窗体:this.ActivateMdiChile(<子窗体>); 排列窗体:使用 MdiLayout 枚举的成员。 ArrangeIcons :在 MDI 父窗体的客户端区内排列所有 MDI 子窗体的图标 Cascade:在 MDI 父窗体的客户端区内层叠所有 MDI 子窗口 TileHorizontal :在 MDI 父窗体的客户端区内水平平铺所有 MDI 子窗口 TileVertical:在 MDI 父窗体的客户端区内垂直平铺所有 MDI 子窗口 11.2、菜单 菜单是程序中显示一个选项列表的图形元素,它提供了将命令分组的一致方法,一个菜单可以 带有若干子菜单,便于用户访问,同时也支持快捷键。 主要有两类菜单:主菜单和弹出式菜单。菜单类 Menu 位于 System.Windows.Forms 命名空间, 其层次结构如图 2 所示。 图 2 菜单的层次结构 主菜单:由 MainMenu 控件来创建。利用 MainMenu 控件可以新建菜单、菜单栏以及在现 有的菜单上添加新菜单项。 MainMenu 控件的常用属性和方法如表 2 所示。 表 2 MainMenu 控件的常用属性和方法 属性 说明 IsParent 指定菜单是否包含菜单项,如果此属性的值为 True,指 定菜单上则有菜单项 MdiListItem 表示菜单项,它列出 MDI 窗体的子窗体 MenuItems 检索属于指定菜单的菜单项集合 方法 说明 GetContextMenu 检索包含指定菜单的上下文菜单的名称,默认值为 null 引用 GetForm 检索包含指定菜单控件的窗体的名称 GetMainMenu 检索包含指定菜单的主菜单的名称,如果此菜单不在 菜 单内,则该属性返回 null 引用 MergeMenu 将指定菜单的菜单项合并到当前活动的菜单中 当然,也可以用 MainMenu 类来实现,如: MainMenu mnuMainMenu1 = new MainMenu(); this.Menu = mnuMainMenu1; mnuMainMenu1.MenuItems.Add ("文件") 在向菜单添加菜单项时,设计器自动创建一个 MenuItem 对象实例。表 3 列出了 MenuItem 类的 常用属性和方法。 表 3 MenuItem 类的常用属性和方法 属性 说明 Checked 是否在指定菜单项的文本旁边显示复选标记,如果此属性设置为 True,则会在指定的菜单项旁边显示复选标记,默认值为 False Index 指定菜单项在当前活动菜单中的索引值,此索引为赋给给每个菜单项 的正整数,它从 0 开始 Mnemonic 获取一个指示与此菜单项关联的助记符的值,如果在菜单项的文本中 没有指定助记符,则此属性返回字符 ‘0’ Parent 指定此菜单项所在菜单的名称 Text 菜单项显示的文本信息 方法 说明 PerformClick 触发菜单项的 Click 事件,而不需要用户实际上单击此菜单项 事件 说明 Click 按下为菜单项指定的快捷键或访问键,或者用鼠标单击菜单项时,将 触发该事件 下面代码演示如何利用 MenuItem 类的实例来创建菜单项: MenuItem mnuFileOpen; mnuFileOpen = new MenuItem("打开"); this.mnuMainMenu1.MenuItems.Add(mnuFileOpen); 弹出式菜单(上下文菜单) 表 4 列出了上下文菜单的属性、方法和事件 表 4 上下文菜单的属性、方法和事件 属性 说明 SourceControl 用于获取正在显示快捷菜单的控件 方法 说明 Show 在指定位置显示快捷菜单 事件 说明 Popup 在显示可快捷菜单前触发该事件 下面代码用于在运行时如何创建上下文菜单。 ContextMenu mnuContextMenu1 = new ContextMenu(); this.ContextMenu = mnuContextMenu1; 示例程序:演示如何在运行时创建和移除菜单。 MainMenu mnuMainMenu1 = new MainMenu();//添加菜单和菜单项 MenuItem mnuAddItem = new MenuItem(); MenuItem mnuRemoveItem = new MenuItem(); mnuAddItem.Text = “添加(&A)"; //添加键盘快捷方式助记符,可以用 alt+相应字母启 动该菜单 mnuRemoveItem.Text = “移除(&R)"; mnuMainMenu1.MenuItems.Add(mnuAddItem); mnuMainMenu1.MenuItems.Add(mnuRemoveItem); this.Menu = mnuMainMenu1; DialogResult preference = new DialogResult(); preference = MessageBox.Show(“是否要添加新菜单项?”,“添加 ",MessageBoxButtons.YesNo); if(Convert.ToString(preference) == “是") { mnuMainMenu1.MenuItems.Add(“新菜单"); DialogResult preference1 = new DialogResult(); preference1 = MessageBox.Show(“是否要移除新菜单项?”, 移除 ",MessageBoxButtons.YesNo); if(Convert.ToString(preference1) == “是") { mnuMainMenu1.MenuItems.RemoveAt(2); } } 11.3、ImageList 控件 位于 Systems.Windows.Forms 命名空间内,可以包含一个或多个图像。运行时不能查看 ImageList 控件,但可以将起附加到如 TreeView 或 Button 等控件中来查看。 ImageList 控件的属性和方法 属性 Images :表示图像列表中包含的图像的集合。 ImageSize:表示图像的大小,默认高度和宽度为 16 x 16,最大大小为 256 x 256。 方法 Draw:绘制指定图像。 示例:往 imgImageList1 添加图片,并将图像附加到 picMyPicture 图片框中。 imgImageList1.Images.Add(Image.FromFile("picture.gif")); this.picMyPicture.Image = this.imgImageList1.Images[0]; 11.4、ToolBar 控件 创建 windows 工具栏,需要用到两个类:ToolBar 和 ToolBarButton。表 5 列出了这两个 类的常用属性和事件。 表 5 ToolBar 和 ToolBarButton 的常用属性和事件 ToolBar 属性 说明 Buttons 工具栏按钮控件的集合 ShowToolTips 鼠标移到各工具栏按钮上时,是否显示相应的工具提示, 如果该属性的值设置为 True,则显示工具提示 事件 说明 ButtonClick 单击工具栏按钮时,将触发该事件 ToolBarButton 属性 说明 ImageIndex 为工具栏按钮指定的图像在图像列表中的索引值 Parent 指定工具栏按钮所属的 ToolBar 控件 Style 工具栏按钮的样式,其中包括 DropDownButton(下拉按 钮)、Separator(分隔符) 和 ToggleButton(切换按 钮) ToolTipText 表示工具栏按钮的工具提示文本 示例:要创建如下图所示的工具栏 主要步骤: 创建名为 Example 1 的 Windows 应用程序。 将 Form1.cs 更改为 frmToolBarExample.cs。 将 ToolBar 控件拖动到窗体上,并将它命名为 tbrToolBar。 选择 Buttons 属性,并单击按钮以显示“ToolBarButton 集合编辑器”窗口。 添加三个按钮 添加相应的代码,如点“打开”时,显示一个消息框: private void tbrToolBar_ButtonClick(object sender,System.Windows.Forms.ToolBarButtonClickEventArgs e) { if(e.Button == this.tbrToolBar.Buttons[0]) { MessageBox.Show(“已单击 “打开”按钮"); } } 11.5、StatusBar 控件 显示在底部,主要是向用户提供有关应用程序状态的信息。状态栏可以划分为多个区,每个 区称为一个窗格,每个窗格又可包含文本或图片。 StatusBar 控件的属性有: Panels:表示 StatusBar 控件的所有面板 ShowPanels:用于指定是否显示状态栏的面板 StatusBar 控件显示的每个面板都是 StatusBarPanel 类的一个实例。 StatusBarPanel 类的属性: Text:用于获取或设置状态栏面板的文本 ToolTipText:用于获取或设置与状态栏面板关联的工具提示文本。 例如,要在状态栏的第二个面板上显示当前日期。 sbrStatusBar1.Panels[1].Text=Convert.ToString(DateTime.Today); 11.6、应用程序示例 设计一个如图 3 所示的应用程序。 图 3 主界面 主要的控件属性如表 6 所示 表 6 窗体中的控件 控件 属性 Form Name: frmMdiApplication Text: 图书管理系统 Menu: mnuBookManage IsMdiContainer: True Menu Name: mnuBookManage MenuItem Name: mnuEnterStore Text: 新书入库(&N) MenuItem Name: mnuSearchBook Text: 查询书目(&S) MenuItem Name: mnuConManage Text: 菜单管理(&M) MenuItem MenuItemName: mnuExitText: 退出系统(&E) MenuItem Name:mnuEnginerrBook Text:工程类图书 MenuItem Name:mnuForeignBook Text:外文类图书 MenuItem Name:mnuChineseBook Text:中文类图书 ToolBar Name: tbrBookBar StatusBar Name: sbrBookStatus 主要步骤:  创建名为 BookManagement 的 Windows 应用程序。  将名称 Form1.cs 更改为 frmMdiApplication.cs。  Menu、StatusBar 、ToolBar 和 ImageList 控件拖动到窗体上。  ToolBar 控件,单击 ToolBar 控件的 Buttons 属性,此时会出现 “ToolBarButton 集合编辑器”窗口,向其中添加 3 个按钮,并分别命名为 trbNewBooks 、 trbBookSearch 和 trbExit。分别在 Text 属性处输入入库、查询和退出  StatusBar 控件,并将 ShowPanels 属性设置为 True  Panels 属性,并添加两个面板,命名为 sbrTimeBar 和 sbrNameBar  frmNewBooks.cs,当鼠标单击“工程类图书”菜单选项时弹出此 窗体 将下列代码添加到 frmMdiApplication 的 Load 事件中 private void frmMdiForm_Load(object sender, System.EventArgs e) { this.sbrBookStatus.Panels[0].Text = DateTime.Now.ToShortDateString(); } 将下列代码添加到 mnuEnginerrBook 的 Click 事件中 private void mnuEnginerrBook_Click(object sender, System.EventArgs e) { frmNewBooks newBooks=new frmNewBooks(); newBooks.MdiParent=this; newBooks.WindowState=FormWindowState.Maximized; newBooks.Show(); this.sbrBookStatus.Panels[1].Text=this.ActiveMdiChild.Text.ToString(); } 将下列代码添加到 mnuConManage “菜单管理”的 Click 事件中 private void mnuConManage_Click(object sender, System.EventArgs e) { //创建上下文菜单 ContextMenu mnuContextMenu = new ContextMenu(); this.ContextMenu = mnuContextMenu; mnuContextMenu.MenuItems.Add("新书入库"); mnuContextMenu.MenuItems.Add("查询书目"); mnuContextMenu.MenuItems.Add("退出系统"); } 将下列代码添加到 mnuExit 的 Click 事件中 private void mnuExit_Click(object sender, System.EventArgs e) { this.Close(); } 将以下代码添加到 tbrBookBar 工具栏按钮的 ButtonClick 事件中 private void tbrBookBar_ButtonClick(object sender,System.Windows.Forms.ToolBarButtonClickEventArgs e) { if(e.Button == this.tbrBookBar.Buttons[0]) { this.mnuEnginerrBook_Click(sender,e); } else if(e.Button == this.tbrBookBar.Buttons[1]) { MessageBox.Show("书籍查询系统正在建设中„„ "); } else if(e.Button==this.tbrBookBar.Buttons[2]) { this.mnuExit_Click(sender,e); } } 11.7、Timer 控件 Timer 控件提供了一种可在程序运行时操控时间的机制。它是一种非可视化控件,不向用户提 供用户界面,因此在运行时不会显示在界面上。它类似与时钟,在指定的时间间隔不断记时,时间 一到即触发事件,执行预设的动作。 Timer 控件的主要属性 Enabled:是否可用 Interval:设置时钟周期,单位 ms 事件 Tick:设定的时间到,触发该事件 方法 Start();启动时钟进行计时。 Stop():停止时钟 示例:要求显示计算机运行的时间长度和应用程序运行的时间长度,界面如图 4 所示 图 4 界面 主要代码 public class frmTickCounter : System.Windows.Forms.Form { private int compuTime; //为窗体声明一个私有整型变量,跟踪经过的时间。 „ private void frmTickCounter_Load(object sender, System.EventArgs e) { compuTime = Environment.TickCount; } private void tmrTickTimer_Tick(object sender, System.EventArgs e) { long curTickValue = Environment.TickCount; long difference = curTickValue - compuTime; long computerHours, computerMinutes, computerSeconds; long applicationHours, applicationMinutes, applicationSeconds; //将毫秒转换成小时、分钟和秒 computerHours = (curTickValue / (3600 * 999)) % 24; computerHours = (curTickValue / (3600 * 999)) % 24; computerMinutes = (curTickValue / (60 * 999)) % 60; computerSeconds = (curTickValue / 999) % 60; applicationHours = (difference / (3600 * 999)) % 24; applicationMinutes = (difference / (60 * 999)) % 60; applicationSeconds = (difference / 999) % 60; this.lblComputer.Text = String.Format(“这台计算机已经开机{0}小时,{1}分钟{2} 秒",computerHours.ToString(),computerMinutes. ToString(),computerSeconds.ToString()); this.lblApplication.Text = String.Format(“这个程序已经运行了 {0}小时, {1} 分 钟 {2} 秒", applicationHours.ToString(), applicationMinutes.ToString(),applicationSeconds.ToString()); } private void btnClose_Click(object sender,System.EventArgs e) { this.Close(); } } 11.8、ListView 控件 用于以特定的样式或视图显示列表项,如 windows 资源管理器。 ListView 控件模式:大图标、小图标、列表和详细资料 属性和方法:见表 7 表 7 ListView 属性和方法 属性 说明 Items 所有项的集合 MultiSelect 是否允许多选 SelectedItems 选定的项 Sorting 排序方式:Ascending,Descending 和 None TopItem 置顶的项 View 显示方式:LargeIcon、SmallIcon、Details、List。默认为 LargeIcon 方法 说明 Clear() 移除所有项 Sort() 排序 GetItemAt() 获取指定项 Columns 集合和 Columns 对象 ListView 控件的 Columns 属性表示控件中出现的所有列标题的集合 列标题是 ListView 控件中包含标题文本的一个项 ColumnHeader 对象定义在控件的 View 属性设置为“Details”值时,作为 ListView 控件的 一部分显示的那些列 如果 ListView 控件没有没有任何列标题,并且 View 属性设置为 Details,则 ListView 控 件不显示任何项 添加控件列有两种方法 ColumnHeader objHeader=new ColumnHeader(); objHeader.Text="姓名"; objHeader.TextAlign=HorizontalAlignment.Center ; objHeader.Width=100; lvPerson.Columns.Add(objHeader); 方法二: lvPerson.Columns.Add ( "姓名" , 60 , HorizontalAlignment.Right ) ; 项集合和项对象 ListView 控件的 Items 属性表示包含控件中所有项的集合 该属性返回 ListView.ListViewItemCollection,可以用于 ListView 中添加新项、删除项或计 算可用项数,写法如下: ListViewItem lstItem = new ListViewItem ( ) ; lstItem.SubItems.Clear ( ) ; lstItem.SubItems[0].Text = reader["name"].ToString ( ) ; //第 0 列 lstItem.SubItems.Add ( reader["HomePhone"].ToString ( ) ) ; //第 1 列 „„ lstItem.SubItems.Add ( reader["Email"].ToString ( ) ) ; //第 n 列 lvPerson.Items.Add(lstItem) 11.9、TreeView 控件 此控件用于以节点方式显示文本或数据,这些节点按层次结构排列,如资源管理器中的浏览窗 格中的树型结构就是一个 TreeView 控件。 节点集和节点对象 TreeView 控件的 Nodes 属性表示为 TreeView 控件指定的树节点集 树节点集中的每个树节点对象可包括它本身的树节点集 树节点集中 Add()、Remove() 和 RemoveAt() 方法使开发人员可添加和移动集中的单个树 节点 添加、修改和删除节点 TreeView 控件以层次结构方式显示节点 在将新节点添加到现有 TreeView 时,重要的是注意新节点所添加到的父节点 可用设计器模式或用户界面在 TreeView 上添加或删除节点 当然,也可以用代码实现,如: TreeNode chNode = new TreeNode("Text for new node"); tvwTree1.currNode.Nodes.Add (chNode); //添加节点 ... tvwTree1.Nodes.Remove(tvwTree1.currNode); //删除选择的节点,如果没有选定要删除 的节点,则会删除根节点 // 清除所有节点 tvwTree1.Nodes.Clear(); 事件 AfterCheck:选择树节点旁的复选框触发 AfterCollapse:折叠时 AfterExpand:展开时 AfterSelect:选择时 BeforeCheck:选择树节点旁的复选框前触发 BeforeCollapse:折叠前 BeforeExpand:展开前 BeforeSelect:选择前 示例:创建如图 5 的文件资源管理器 图 5 文件资源管理器 主要代码: 添加盘符 private void FilltvwDirectory() { string[] drives = Environment.GetLogicalDrives(); //获取当前设备盘符,并添加到数组 drivers[]l 里面 for (int i = 0; i < drives.Length; i++) { TreeNode cRoot = new TreeNode(drives[i]); //这里可以写成 //TreeNode cRoot=new TreeNode(); //cRoot.Text=drivers[i]; tvwDirectory.Nodes.Add(cRoot); AddDirectories(cRoot); //添加该盘符下的文件夹 } } 添加下一级目录 private void AddDirectories(TreeNode node) { try { DirectoryInfo dir = new DirectoryInfo(GetPathFromNode(node)); DirectoryInfo[] e = dir.GetDirectories(); for (int i = 0; i < e.Length; i++) { string name = e[i].Name; if (!name.Equals(".") && !name.Equals("..")) //判断是否是上级目录 {//若文件夹不是返回上级目录标志则添加到 TreeView 里面 node.Nodes.Add(new TreeNode(name)); } } } catch (Exception e) { MessageBox.Show(e.Message); } } 展开节点 private void tvwDirectory_BeforeExpand(object source, TreeViewCancelEventArgs e) { TreeNode nodeExpanding = (TreeNode)e.Node; AddSubDirectories(nodeExpanding); } //调用 AddDirectories 将该节点的字节点添加到树图中该节点下 private void AddSubDirectories(TreeNode node) { for (int i = 0; i < node.Nodes.Count; i++) { AddDirectories(node.Nodes[i]); } } 排序节点 private void chkOrder_Click(object source, EventArgs e) { this.tvwDirectory.Sorted = chkOrder.Checked; //树图排序 for (int i = 0; i < tvwDirectory.Nodes.Count; i++) { Refresh(tvwDirectory.Nodes[i]); //调用刷新树图方法,重新添加节点 } } private void Refresh(TreeNode node) //刷新树图 { if (node.Nodes.Count > 0) { if (node.IsExpanded) { string[] tooBigExpandedNodes = new string[node.GetNodeCount(true)]; int iExpandedNodes = Refresh_GetExpanded(node, tooBigExpandedNodes,0); string[] expandedNodes = new string[iExpandedNodes]; Array.Copy(tooBigExpandedNodes, 0, expandedNodes, 0, iExpandedNodes); node.Nodes.Clear(); AddDirectories(node); AddSubDirectories((TreeNode)node); node.Expand(); for (int j = 0; j < node.Nodes.Count ; j++) { if (node.Nodes[j].Nodes.Count > 0) {Refresh_Expand(node.Nodes[j], expandedNodes); } } } else { node.Nodes.Clear();AddDirectories(node); } } else { node.Nodes.Clear(); AddDirectories(node); } } 获取展开节点 private int Refresh_GetExpanded(TreeNode Node, string[] ExpandedNodes, int StartIndex) { if (StartIndex < ExpandedNodes.Length) { if (Node.IsExpanded) { ExpandedNodes[StartIndex] = Node.Text; StartIndex++; for (int i = 0; i < Node.Nodes.Count; i++) { StartIndex = Refresh_GetExpanded(Node.Nodes[, i],ExpandedNodes,StartIndex); } } return StartIndex; } return -1; } 展开原来已经展开的节点 private void Refresh_Expand(TreeNode Node, string[] ExpandedNodes) { for (int i = ExpandedNodes.Length - 1; i >= 0; i--) { if (ExpandedNodes[i] == Node.Text) { AddSubDirectories((TreeNode) Node); Node.Expand(); for (int j = 0; j < Node.Nodes.Count; j++) { Refresh_Expand(Node.Nodes[j], ExpandedNodes); } return; } } } 小结  单文档界面的某一时刻只能打开一个文档,多文档界面允许同时打开多个文档  MDI 应用程序由一个 MDI 父窗体和一个或多个子窗体构成  菜单是程序中显示一个选项列表的图型元素,它提供将命令分组的方法和用户对其访问 的简单途径  上下文菜单用于使用户通过单击鼠标右键访问常用的命令  ImageList 控件是一种图形存储控件,可以包含单个图像或图像集合  工具栏包含工具栏按钮,这些按钮提供对应用程序中最常用的菜单命令的快速访问  状态栏通常显示在窗体的底部,向用户提供有关应用程序状态的信息  Timer 控件为开发人员提供了一种在指定时刻或指定的周期执行任务的控件  Timer 控件的 Interval 属性表示时钟的周期,单位为毫秒  ListView 控件用于以特定样式或视图类型显示列表项,其 Items 集合对象提供了对其 列表项的操作  TreeView 控件用于以节点形式显示文本或数据,这些节点按层次结构顺序排列  TreeView 控件的 Nodes 集合对象提供了对树型节点的操作 第十二章 简单设计模式及应用 本章主要目标 通过本章的学习,主要把握以下内容:  什么是设计模式  应用简单工厂设计模式和抽象工厂设计模式 本章重点  简单设计模式和抽象工厂设计模式 本章难点  抽象工厂设计模式 12.1、设计模式简介 什么是设计模式 模式就是得到很好研究的范例 设计模式是软件开发过程中经验的积累 特定问题的经过实践检验的特定解决方法 设计模式的原则 1、"开-闭"原则 此原则是由"Bertrand Meyer"提出的。原文是:"Software entities should be open for extension,but closed for modification"。就是说模块应对扩展开放,而对修改关闭。 2、里氏代换原则 里氏代换原则是由"Barbara Liskov"提出的。如果调用的是父类的话,那么换成子类也完全可 以运行。 3、合成复用原则 就是说要少用继承,多用合成关系来实现。 4、依赖倒转原则 抽象不应该依赖与细节,细节应当依赖与抽象。 要针对接口编程,而不是针对实现编程。 传递参数,或者在组合聚合关系中,尽量引用层次高的类。 5、接口隔离原则 定制服务的例子,每一个接口应该是一种角色,不多不少,不干不该干的事,该干的事都要干。 6、抽象类 抽象类不会有实例,一般作为父类为子类继承,一般包含这个系的共同属性和方法。 注意:好的继承关系中,只有叶节点是具体类,其他节点应该都是抽象类,也就是说具体类是 不被继承的。将尽可能多的共同代码放到抽象类中。 7、迪米特法则 最少知识原则。 12.2、简单工厂设计模式 简单工厂模式的原理 工厂类:担任这个角色的是工厂方法模式的核心,含有与应用紧密相关的商业逻辑。工厂 类在客户端的直接调用下创建产品对象,它往往由一个具体的类实现。 抽象产品角色:担任这个角色的类是由工厂方法模式所创建的对象的父类,或她们共同拥有的 接口。一般由接口或抽象类实现。 具体产品角色:工厂方法模式所创建的任何对象都是这个角色的实例,由具体类实现。 简单工厂模式优缺点 模式的核心是工厂类,这个类负责产品的创建,而客户端可以免去产品创建的责任,这实现 了责任的分割。但由于工厂类集中了所有产品创建逻辑的,如果不能正常工作的话会对系统造成很 大的影响。如果增加新产品必须修改工厂角色的源码。 12.3、抽象工厂设计模式的应用  抽象工厂角色:担任这个角色的是工厂方法模式的核心,它是与应用系统的商业逻辑无关 的。通常使用接口或抽象类实现。  具体工厂角色:这个角色直接在客户端的调用下创建产品的实例。这个角色含有选择合适的 产品对象的逻辑,而这个逻辑是与应用系统的商业逻辑紧密相关的。通常使用具体的类实现。  抽象产品角色:担任这个角色的类是抽象工厂方法模式所创建的对象的父类,或它们共同拥 有的接口。通常使用接口或抽象类实现这一角色。  具体产品角色:抽象工厂模式所创建的任何产品对象都是某一具体产品类的实例。这是客户 端最终需要的东西。 通常使用具体类实现这个角色。 1、提供一系列相互依赖对象的创建工作 2、封装对象常规的创建方法(new) 3、提供统一调用数据访问方法的方式 4、避免调用数据访问方法和具体对象创建工作的紧耦合 使用抽象工厂模式  一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有形态的工厂 模式都是重要的。  这个系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。  同属于同一个产品族的产品是在一起使用的,这一约束必须在系统的设计中体现出来。  系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于实现。 如何使用抽象工厂设计模式 1、用抽象工厂生产抽象产品 2、用实体工厂生产实体产品 3、用抽象产品提供实体产品访问接口 4、用实体产品实现自己的功能 小结  什么是面向对象设计模式?  什么是抽象工厂设计模式? 第十三章 文件和注册表操作 本章主要目标 通过本章的学习,主要把握以下内容:  System.IO 命名空间  读写文本文件  读写二进制文件  读写内存流  path 类和 directory 类  读写注册表 本章重点  读写文本文件  读写注册表 本章难点  读写二进制文件  读写内存流  读写注册表 13.1、System.IO 命名空间 System.IO 命名空间包含允许读写文件和数据流的类型以及提供基本文件和目录支持的类型。 其中用于文件 I/O 的类主要有以下几个: Directory 提供通过目录和子目录进行创建、移动和枚举的静态方法。DirectoryInfo 类提供 实例方法。 DirectoryInfo 提供通过目录和子目录进行创建、移动和枚举的实例方法。Directory 类提供 静态方法。 DriveInfo 提供访问有关驱动器的信息的实例方法。 File 提供用于创建、复制、删除、移动和打开文件的静态方法,并协助创建 FileStream。FileInfo 类提供实例方法。 FileInfo 提供用于创建、复制、删除、移动和打开文件的实例方法,并协助创建 FileStream。 File 类提供静态方法。 FileStream 支持通过其 Seek 方法随机访问文件。默认情况下,FileStream 以同步方式打开 文件,但它也支持异步操作。File 包含静态方法,而 FileInfo 包含实例方法。 FileSystemInfo 是 FileInfo 和 DirectoryInfo 的抽象基类。 Path 提供以跨平台的方式处理目录字符串的方法和属性。 用于从流读取和写入流的类: BinaryReader 和 BinaryWriter 从 Streams 读取或向 Streams 写入编码的字符串和基元数据 类型。 StreamReader 通过使用 Encoding 进行字符和字节的转换,从 Streams 中读取字符。 StreamReader 具有一个构造函数,该构造函数根据是否存在专用于 Encoding 的 preamble(例如一 个字节顺序标记)来尝试确定给定 Stream 的正确 Encoding 是什么。 StreamWriter 通过使用 Encoding 将字符转换为字节,向 Streams 写入字符。 TextReader 是 StreamReader 和 StringReader 的抽象基类。抽象 Stream 类的实现用于字节 输入和输出,而 TextReader 的实现用于 Unicode 字符输出。 TextWriter 是 StreamWriter 和 StringWriter 的抽象基类。抽象 Stream 类的实现用于字节 输入和输出,而 TextWriter 的实现用于 Unicode 字符输出。 通用 I/O 流类: BufferedStream 是向另一个 Stream(例如 NetworkStream)添加缓冲的 Stream。((FileStream 内部已具有缓冲,MemoryStream 不需要缓冲。)BufferedStream 可以围绕某些类型的流来构成以 提高读写性能。缓冲区是内存中的字节块,用于缓存数据,从而减少对操作系统的调用次数。 MemoryStream 是一个非缓冲的流,可以在内存中直接访问它的封装数据。该流没有后备存储, 可用作临时缓冲区。 13.2、读写文本文件 File 类 File 类提供用于创建、复制、删除、移动和打开文件的静态方法,并协助创建 FileStream 对 象。 将 File 类用于典型的操作,如复制、移动、重命名、创建、打开、删除和追加到文件。也可 将 File 类用于获取和设置文件属性或有关文件创建、访问及写入操作的 DateTime 信息。 许多 File 方法在创建或打开文件时返回其他 I/O 类型。可以使用这些其他类型进一步操作文 件。 由于所有的 File 方法都是静态的,所以如果只想执行一个操作,那么使用 File 方法的效率比 使用相应的 FileInfo 实例方法可能更高。所有的 File 方法都要求当前所操作的文件的路径。 File 类的静态方法对所有方法都执行安全检查。如果打算多次重用某个对象,可考虑改用 FileInfo 的相应实例方法,因为并不总是需要安全检查。 默认情况下,将向所有用户授予对新文件的完全读/写访问权限。 下表描述了用于自定义各种 File 方法的行为的枚举。 枚举 说明 FileAccess 指定对文件的读取和写入访问。 FileShare 为已在使用中的文件指定允许的访问级别。 FileMode 指定是保留还是改写现有文件的内容,并指定创建现有文件的请求是否会 导致异常。 注意:在接受路径作为输入字符串的成员中,路径必须是格式良好的,否则将引发异 例如,如果路径是完全限定的但以空格开头,则路径在类的方法中不会被修剪。因此,路径的 格式不正确,并将引发异常。同样,路径或路径的组合不能被完全限定两次。例如,“c:\temp c:\windows”在大多数情况下也将引发异常。在使用接受路径字符串的方法时,请确保路径是格式 良好的。 在接受路径的成员中,路径可以是指文件或仅是目录。指定路径也可以是相对路径或者服务器 和共享名称的统一命名约定(UNC) 路径。例如,以下都是可接受的路径: C# 中的“c:\\MyDir\\MyFile.txt”或 Visual Basic 中的“c:\MyDir\MyFile.txt”。 C# 中的“c:\\MyDir”或 Visual Basic 中的“c:\MyDir”。 C# 中的“MyDir\\MySubdir”或 Visual Basic 中的“MyDir\MySubDir”。 C# 中的“\\\\MyServer\\MyShare”或 Visual Basic 中的“\\MyServer\MyShare”。 FileInfo 类 提供创建、复制、删除、移动和打开文件的实例方法,并且帮助创建 FileStream 对象。无法 继承此类。 例子: using System; using System.IO; class Test { public static void Main() {string path = Path.GetTempFileName(); FileInfo fi1 = new FileInfo(path); if (!fi1.Exists) { //Create a file to write to. using (StreamWriter sw = fi1.CreateText()) {sw.WriteLine("Hello"); sw.WriteLine("And"); sw.WriteLine("Welcome"); } } //Open the file to read from. using (StreamReader sr = fi1.OpenText()) { string s = ""; while ((s = sr.ReadLine()) != null) { Console.WriteLine(s); } } try { string path2 = Path.GetTempFileName(); FileInfo fi2 = new FileInfo(path2); //Ensure that the target does not exist. fi2.Delete(); //Copy the file. fi1.CopyTo(path2); Console.WriteLine("{0} was copied to {1}.", path, path2); //Delete the newly created file. fi2.Delete(); Console.WriteLine("{0} was successfully deleted.", path2); } catch (Exception e) { Console.WriteLine("The process failed: {0}", e.ToString()); } } } FileStream 类 使用 FileStream 类对文件系统上的文件进行读取, 、写入、打开和关闭操作,并对其他与文 件相关的操作系统句柄进行操作,如管道、标准输入和标准输出。读写操作可以指定为同步或异步 操作。FileStream 对输入输出进行缓冲,从而提高性能。 FileStream 对象支持使用 Seek 方法对文件进行随机访问。Seek 允许将读取/写入位置移动到文件 中的任意位置。这是通过字节偏移参考点参数完成的。字节偏移量是相对于查找参考点而言的,该 参考点可以是基础文件的开始、当前位置或结尾,分别由 SeekOrigin 类的三个属性表示。 注意: 磁盘文件始终支持随机访问。在构造时,CanSeek 属性值设置为 true 或 false,具体取决于基 础文件类型。具体地说,就是当基础文件类型是 FILE_TYPE_DISK(如 winbase.h 中所定义)时, CanSeek 属性值为 true。否则,CanSeek 属性值为 false。 虽然同步方法 Read 和 Write 以及异步方法 BeginRead、BeginWrite、EndRead 和 EndWrite 在 同步或异步模式下都可以工作,但模式会影响这些方法的性能。FileStream 默认情况下以同步方式 打开文件,但提供 FileStream 和 FileStream 构造函数来异步打开文件。 如果进程因文件的一部分锁定而终止或者关闭具有未解除锁定的文件,则行为是未定义的。 在磁盘空间有限的环境中,如果在 FileStream 完成之前不调用 Dispose 方法,则执行 IO 操作 可能引发异常。 13.3、读写二进制文件 要使用 BinaryReader 和 BinaryWriter 类,这两个对象都需要在 FileStream 上创建 FileStream filestream = new FileStream(Filename, FileMode.Create); BinaryWriter objBinaryWriter = new BinaryWriter(filestream); BinaryReader 和 BinaryWriter 类的主要方法 BinaryReader BinaryWriter Close() Close() Read() Flush() ReadDecimal() Write() ReadByte() ReadInt16() ReadInt32() ReadString() 读写二进制文件举例 public static void Main(String[] args) { Console.WriteLine("输入文件名:"); string Filename = Console.ReadLine(); FileStream filestream = new FileStream(Filename, FileMode.Create); BinaryWriter objBinaryWriter = new BinaryWriter(filestream); for (int index = 0; index < 20; index++) { objBinaryWriter.Write((int) index); } Console.WriteLine("\二进制数据已写入文件"); objBinaryWriter.Close(); filestream.Close(); } public static void Main(String[] args) { Console.WriteLine("输入文件名:"); string file = Console.ReadLine(); if (!File.Exists (file)) { Console.WriteLine("文件不存在!"); } else { FileStream filestream = new FileStream(file, FileMode.Open, FileAccess.Read); BinaryReader objBinaryReader = new BinaryReader(filestream); try { while(true) { Console.WriteLine(objBinaryReader.ReadInt32()); } } catch(EndOfStreamException eof) { Console.WriteLine(“已到文件末尾"); } } } 13.4、读写内存流 BufferedStream 类构造函数 public BufferedStream(Stream StName); public BufferedStream(Stream StName, int bsize); 通过缓冲区交换数据举例 public st, atic void Main( ) { Console.WriteLine (“请输入文件名:"); string name = Console.ReadLine(); Console.WriteLine (“请输入备份文件名:"); string backup = Console.ReadLine(); if(File.Exists(name)) { Stream inputStream = File.OpenRead(name); Stream outputStream = File.OpenWrite(backup); BufferedStream bufferedInput =new BufferedStream(inputStream); BufferedStream bufferedOutput =new BufferedStream(outputStream); byte[] buffer = new Byte[sizeBuff]; int bytesRead; while ((bytesRead =bufferedInput.Read(buffer,0,sizeBuff)) > 0 ) { bufferedOutput.Write(buffer,0,bytesRead); } Console.WriteLine(); Console.WriteLine("给定备份的文件已创建"); bufferedOutput.Flush( ); bufferedInput.Close( ); bufferedOutput.Close( ); } else { Console.WriteLine ("文件不存在"); } } 13.5、path 类和 directory 类 path 类 .NET Framework 不支持通过由设备名称构成的路径(如“\\.\PHYSICALDRIVE0”)直接访问物 理磁盘。 路径是提供文件或目录位置的字符串。路径不必指向磁盘上的位置;例如,路径可以映射到内 存中或设备上的位置。路径的准确格式是由当前平台确定的。例如,在某些系统上,路径可以驱动 器号或卷号开始,而此元素在其他系统中是不存在的。在某些系统上,文件路径可以包含扩展名, 扩展名指示在文件中存储的信息的类型。文件扩展名的格式是与平台相关的;例如,某些系统将扩 展名的长度限制为 3 个字符,而其他系统则没有这样的限制。当前平台还确定用于分隔路径中各元 素的字符集,以及确定在指定路径时不能使用的字符集。因为这些差异,所以 Path 类的字段以及 Path 类的某些成员的准确行为是与平台相关的。 路径可以包含绝对或相对位置信息。绝对路径完整指定一个位置:文件或目录可被唯一标识, 而与当前位置无关。相对路径指定部分位置:当定位用相对路径指定的文件时,当前位置用作起始 点。若要确定当前目录,请调用 Directory.GetCurrentDirectory。 Path 类的大多数成员不与文件系统交互,并且不验证路径字符串指定的文件是否存在。修改路 径字符串的 Path 类成员(例如 ChangeExtension)对文件系统中文件的名称没有影响。但 Path 成 员确实验证指定路径字符串的内容;并且如果字符串包含在路径字符串中无效的字符(如 InvalidPathChars 中的定义),则引发 ArgumentException。例如,在基于 Windows 的桌面平台上, 无效路径字符可能包括引号(")、小于号(<)、大于号(>)、管道符号(|)、退格(\b)、空(\0) 以及从 16 到 18 和从 20 到 25 的 Unicode 字符。 Path 类的成员可以快速方便地执行常见操作,例如确定文件扩展名是否是路径的一部分,以及 将两个字符串组合成一个路径名。 Path 类的所有成员都是静态的,因此无需具有路径的实例即可被调用。 directory 类 将 Directory 类用于典型操作,如复制、移动、重命名、创建和删除目录。也可将 Directory 类 用于获取和设置与目录的创建、访问及写入操作相关的 DateTime 信息。 由于所有的 Directory 方法都是静态的,所以如果只想执行一个操作,那么使用 Directory 方 法的效率比使用相应的 DirectoryInfo 实例方法可能更高。大多数 Directory 方法要求当前操作的 目录的路径。 Directory 类的静态方法对所有方法都执行安全检查。如果打算多次重用某个对象,可考虑改 用 DirectoryInfo 的相应实例方法,因为并不总是需要安全检查。 13.6、读写注册表 要访问注册表,可以使用 Microsoft.Win32 命名空间中的两个类 Registry 和 RegistryKey。 RegistryKey 实例表示一个注册表项,这个类的方法可以浏览子键、创建新键、读取或修改键 中的值。换言之,该类可以完成对注册表项进行的所有操作(除了设置键的安全级别之外)。 RegistryKey 类可以用于完成对注册表的所有操作。Registry 是不能实例化的一个类。它的作用只 是提供表示顶级键的 RegistryKey 实例(不同的巢),以便开始在注册表中浏览。Registry 是通过静 态属性来提供这些实例的,这些属性共有 7 个,分别是 ClassesRoot、CurrentConfig、CurrentUser、 DynData、LocalMachine、PerformanceData 和 Users。 例如,要获得一个表示 HKLM 键的 RegistryKey 实例,可以编写下面的代码: RegistryKey hklm = Registry.LocalMachine; 获得 RegistryKey 对象引用的过程,视为打开一个键。 用户可能会认为,因为注册表的层次结构类似于文件系统,所以 RegistryKey 的方法类似于 DirectoryInfo 的方法,但实际上并非如此。访问注册表的方式通常不同于使用文件和文件夹的方 式,RegistryKey 执行的方法可以反映这种不同。 最明显的区别是如何在注册表的给定位置上打开一个注册表项。Registry 类没有用户可以使用 的公共构造函数,也没有任何可以直接通过键的名称来访问键的方法。但可以在相关的巢中从上至 下浏览该键。如果要实例化一个 RegistryKey 对象,惟一的方式是从 Registry 的静态属性开始,向 下浏览。例如,要读取 HKLM/Software/Microsoft 键中的一些数据,可以使用下面的代码获得它的 一个引用: RegistryKey hklm = Registry.LocalMachine; RegistryKey hkSoftware = hklm.OpenSubKey("Software"); RegistryKey hkMicrosoft = hkSoftware.OpenSubKey("Microsoft"); 以这种方式访问注册表项是只读访问。如果要写入该键(包括写入其值,或创建和删除其子键), 就需要使用 OpenSubKey 的另一个重写方法,该方法的第二个参数是 bool 类型,表示是否要对该键 进行读写访问。例如,如果要修改 Microsoft 键(并假定用户是一个系统管理员,有修改该键的许可), 就应编写如下代码: RegistryKey hklm = Registry.LocalMachine; RegistryKey hkSoftware = hklm.OpenSubKey("Software"); RegistryKey hkMicrosoft = hkSoftware.OpenSubKey("Microsoft", true); 因为这个键包含 Microsoft 应用程序使用的信息,在大多数情况下,就不应修改这个特定键。 如果这个键已经存在,就应调用 OpenSubKey()方法。如果这个键不存在,就返回一个空引用。如果 要创建一个键,就应使用 CreateSubKey()方法(该方法会通过返回的引用,自动提供该键的读写访 问): RegistryKey hklm = Registry.LocalMachine; RegistryKey hkSoftware = hklm.OpenSubKey("Software"); RegistryKey hkMine = hkSoftware.CreateSubKey("MyOwnSoftware"); CreateSubKey()工作的方式非常有趣:如果键不存在,它就创建这个键。但如果键已经存在, 它就会返回一个表示该键的 RegistryKey 实例。这个方法采用这样的工作方式,其原因是用户总是 可以使用这个键。注册表包含长期数据,例如 Windows 和各种应用程序的配置信息。因此用户并不 需要经常显式地创建键。 更常见的是,应用程序需要确保某些数据在注册表中是存在的。换言之,如果这些数据不存在, 就要创建相关的键,但如果它们存在,就不需要做任何事。CreateSubKey()就可以完成这项任务。 与 FileInfo.Open()的情况不同,CreateSubKey()不会删除任何数据。如果要删除注册表项,就需 要显式调用 RegistryKey.Delete()方法,因此注册表对于 Windows 是非常重要的。如果删除了一些 重要的键,就会中断 Windows 的执行,此时就需要调试 C#注册表调用了。 定位了要读取或修改的注册表项后,就可以使用 SetValue() 或 GetValue()方法设置或获取该 键中的值。这两个方法的参数都是一个字符串,其中字符串给出了值的名称,SetValue()还需要一 个包含值的信息的对象引用。这个参数定义为对象引用,实际上可以是任何一个类的引用。SetValue() 根据所提供的类的类型,确定把值设置为 REG_SZ、REG_DWORD,还是 REG_BINARY。例如: RegistryKey hkMine = HkSoftware.CreateSubKey("MyOwnSoftware"); hkMine.SetValue("MyStringValue", "Hello World"); hkMine.SetValue("MyIntValue", 20); 这段代码设置键包含两个值:MyStringValue 的类型是 REG_SZ,而 MyIntValue 的类型是 REG_DWORD,这里只考虑这两种类型,在后面的示例中会使用它们。 RegistryKey.GetValue()的工作方式也是这样。它返回一个对象引用,如果该方法检测到值的 类型为 REG_SZ,就返回一个字符串引用,如果值的类型为 REG_DWORD,就返回一个 int 型值。 string stringValue = (string)hkMine.GetValue("MyStringValue"); int intValue = (int)hkMine.GetValue("MyIntValue"); 最后,完成了读取或修改数据后,应关闭该键: hkMine.Close(); 小结  File 是静态对象,提供对文件的创建、拷贝、移动和删除等一系列操作  File.Create(文件名)可以创建新的文件,并结合 FileS, tream 对象来进行读写操作  FileStream 和 BinaryReader、BinaryWriter 对象结合起来可对二进制数据进行操作  在 C#中指明文件名的时候,要使用转义字符“\\”  内存流提供无法调整大小的数据流视图,而且只能向其写入  BufferedStream 对象对缓冲区进行读写
还剩228页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 15 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

Speed2109

贡献于2012-07-21

下载需要 15 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf