描述
开 本: 32开纸 张: 胶版纸包 装: 平装-胶订是否套装: 否国际标准书号ISBN: 9787302454076丛书名: 21世纪高等学校计算机专业实用规划教材
全书讲解过程中配备了大量示例,示例简短精炼,融知识性趣味性于一体。为了给读者释疑解惑,也为了给部分学有余力的读者提供深入学习的窗口,在每章都安排了问与答环节,讲解了一些容易混淆的问题或者扩展一些课堂内的知识。练习方面,也是分层递进,注重梯度,按课堂练习→课堂思考→课后思考与练习→实战任务,逐层深入,难度逐步提升,符合一般的学习规律。另外,在实战任务或者思考与练习中设计了不少具有实用价值的编程练习,使读者在学习的过程中不会感到编程枯燥无趣,体会到用C#编程其乐无穷。
本书适合大中专院校、培训机构的学生及.NET爱好者使用,可用作C#面向对象程序设计、.NET Framework程序设计、WinForm应用开发、.NET下的数据库应用开发等课程的教材。
目录
第1章概述
1.1.NET
1.2C#
1.3VS开发环境
1.4编程初试
1.4.1控制台程序
1.4.2WinForm程序
1.5问与答
1.5.1学习.NET相关技术,将来能干什么
1.5.2何谓注释,C#中的注释有几种
1.5.3使用VS.NET时有什么技巧
1.5.4VS太大,是否有更小巧的C#学习开发环境
1.6思考与练习
1.7实战任务
第2章数据类型与运算符
2.1常量与变量
2.1.1常量
2.1.2变量
2.1.3变量的命名
2.1.4变量的命名法
2.2简单数据类型
2.2.1bool类型
2.2.2整型类型
2.2.3char类型
2.2.4小数类型
2.3枚举类型
2.4结构类型
2.5隐式类型变量
2.6运算符
2.6.1算术运算符
2.6.2赋值运算符
2.6.3关系运算符与逻辑运算符
2.6.4位运算符
2.6.5条件运算符
2.6.6自增与自减
2.6.7运算符的优先级
2.7转换
2.7.1隐式转换
2.7.2显式转换
2.7.3Type.Parse
2.7.4Convert类
2.7.5装箱与拆箱
2.7.6as & is
2.8问与答
2.8.1数值类型那么多,怎样记忆各类型的取值范围
2.8.2如何知道数值类型占用多大存储空间
2.8.3数值运算中,除数不能为零吗
2.8.40/0.0=?
2.8.5NaN和Infinity参与计算时,结果如何
2.8.6定义枚举类型时,个枚举对应的数值必须为0吗
2.8.7定义枚举类型时,各个枚举项对应的数值必须连续吗
2.8.8如何更改枚举类型元素的数据类型
2.8.9各种类型的默认值分别是什么
2.8.10枚举类型的位操作是什么意思
2.9思考与练习
2.10实战任务
第3章程序控制
3.1选择语句
3.1.1if语句
3.1.2switch语句
3.2循环语句
3.2.1for语句
3.2.2while语句
3.2.3do…while语句
3.3跳转语句
3.3.1break语句
3.3.2continue语句
3.3.3goto语句
3.3.4return语句
3.3.5throw语句
3.4问与答
3.4.1if和switch分别应用于什么场合
3.4.2if和switch的各个分支的书写顺序有影响吗
3.4.3如何避免太深的嵌套
3.4.4for、while、do…while分别应用于什么场合
3.4.5如何知道程序执行耗费的时间
3.4.6如何产生随机数
3.4.7什么叫程序集
3.5思考与练习
3.6实战任务
第4章面向对象基础
4.1类与对象
4.2字段
4.3属性
4.3.1常规属性
4.3.2自动属性
4.4索引器
4.5方法
4.5.1方法的定义与使用
4.5.2变量作用域
4.5.3方法重载
4.5.4参数的个数不定问题——params
4.5.5ref与out
4.5.6this
4.6Main()函数
4.7构造函数
4.8static
4.9析构函数
4.10委托
4.10.1委托使用三步曲
4.10.2多播委托
4.11匿名方法
4.12Lambda表达式
4.13事件
4.13.1事件使用三步曲
4.13.2三类事件
4.14继承
4.14.1继承的实现
4.14.2抽象类及抽象方法
4.14.3类的密封
4.14.4继承过程中构造函数的执行顺序及调用
4.14.5protected修饰符
4.15多态
4.16接口
4.17匿名类型
4.18结构
4.18.1DateTime
4.18.2TimeSpan
4.19object类
4.19.1相等问题
4.19.2GetType()
4.19.3ToString()
4.20问与答
4.20.1什么是命名空间
4.20.2readonly与const究竟有何区别
4.20.3什么是分部类
4.20.4密封类的扩展——扩展方法
4.20.5is和as——兼谈如何让singer不要调用基类方法
4.20.6重写与重载
4.20.7抽象方法和虚方法
4.20.8接口、抽象类、类与结构
4.20.9接口中有重名的方法该如何办
4.20.10base与this
4.20.11什么是运算符重载
4.20.12如何给自定义的结构定义相等逻辑
4.21思考与练习
4.22实战任务
第5章数组
5.1声明及初始化
5.2访问与遍历
5.3Array
5.4聪明的数组——索引器
5.5问与答
5.5.1如何使用Array.Sort()来排序对象数组
5.5.2数组的大小真的没法调整吗
5.5.3如何查找数组中具有特定特征的元素
5.5.4索引器的参数类型一定要为int吗
5.5.5如何不计算即可获得值、小值、和值、平均值
5.6思考与练习
5.7实战任务
第6章字符串
6.1字符串及其转义符
6.1.1字符串及其构造
6.1.2字符转义
6.2常用方法
6.2.1string类的方法
6.2.2字符串对象的方法
6.3StringBuilder
6.4编码
6.5问与答
6.5.1s=null,s=string.Empty与s
=””
6.5.2字符串与数组之间如何互相转化
6.5.3字符串与字节数组之间的转换有何意义
6.5.4各种编码之间如何转换
6.6思考与练习
6.7实战任务
第7章WinForm初步
7.1窗体
7.2控件常用操作及其键盘和鼠标事件
7.2.1控件常用操作
7.2.2键盘事件处理
7.2.3鼠标事件处理
7.3基本控件
7.3.1Label控件
7.3.2Button控件
7.3.3RadioButton控件
7.3.4CheckBox控件
7.3.5TextBox控件
7.3.6ListBox控件
7.3.7ComboBox控件
7.3.8PictureBox控件
7.3.9NumericUpDown控件
7.3.10ProgressBar控件
7.3.11HScrollBar控件和VScrollBar控件
7.3.12TrackBar控件
7.3.13ToolTip控件
7.3.14GroupBox控件
7.3.15Panel控件
7.3.16MonthCalendar控件
7.4常用组件
7.4.1Timer组件
7.4.2ImageList组件
7.5菜单
7.5.1MenuStrip
7.5.2ContextMenuStrip
7.5.3ToolStrip
7.5.4StatusStrip
7.6对话框
7.6.1OpenFileDialog
7.6.2SaveFileDialog
7.6.3FontDialog
7.6.4ColorDialog
7.6.5FolderBrowserDialog
7.7高级控件
7.7.1RichTextBox控件
7.7.2CheckedListBox控件
7.7.3TabControl控件
7.7.4ListView控件
7.7.5TreeView控件
7.7.6WebBrowser控件
7.8COM组件
7.8.1Shockwave Flash Object组件
7.8.2Windows Media Player组件
7.9MDI
7.10问与答
7.10.1键盘事件KeyDown、KeyUp和KeyPress有何关系
7.10.2Click和MouseClick有何关系
7.10.3多种鼠标事件有何关系
7.10.4如何获取应用程序的运行环境信息
7.10.5如何获取应用程序的运行目录
7.10.6如何实现拖放
7.10.7关于剪贴板
7.10.8如何动态构建控件树
7.10.9如何实现窗体间的数据交互
7.11思考与练习
7.12实战任务
第8章文件
8.1文件系统
8.1.1驱动器访问
8.1.2目录访问
8.1.3文件访问
8.1.4路径
8.2文件处理流
8.2.1FileStream
8.2.2StreamReader与StreamWriter
8.2.3BinaryReader与BinaryWriter
8.3问与答
8.3.1如何创建临时文件
8.3.2如何比较两个文件是否一样
8.4思考与练习
8.5实战任务
第9章集合
9.1普通集合
9.1.1ArrayList
9.1.2Queue
9.1.3Stack
9.1.4Hashtable
9.1.5SortedList
9.1.6BitArray
9.2泛型
9.3泛型集合
9.3.1ListT
9.3.2QueueT和StackT
9.3.3DictionaryK,V和KeyValuePairK,
V
9.3.4SortedListK,V
9.3.5HashSetT
9.4问与答
9.4.1集合中的元素应该如何正确删除
9.4.2如何使用内置排序器来实现ArrayList排序——IComparer
9.4.3如何完全自定义排序规则来排序
9.4.4IEnumerable和IEnumerator有什么作用和特性
9.4.5什么是可空类型
9.4.6什么是Tuple
9.4.7泛型变量的默认值是多少
9.4.8针对如下泛型方法,下面的调用代码可行吗
9.4.9泛型的比较问题
9.4.10HashSetT的扩展方法
9.4.11集合的运算
9.5思考与练习
9.6实战任务
第10章GDI
10.1概述
10.2辅助绘图对象
10.2.1Point结构
10.2.2Size结构
10.2.3Rectangle结构
10.2.4Color结构
10.2.5Font类
10.2.6Graphics类
10.3基本绘图工具
10.3.1Pen
10.3.2Brush
10.4图像处理
10.4.1绘制直线
10.4.2绘制矩形
10.4.3绘制多边形
10.4.4绘制曲线
10.4.5绘制椭圆
10.4.6绘制图像
10.5常见应用
10.5.1格式转换
10.5.2水印
10.5.3灰化
10.5.4底片
10.5.5浮雕
10.5.6文本打印
10.6问与答
10.6.1如何实现网页颜色与Color的转换
10.6.2Math类
10.7思考与练习
10.8实战任务
第11章多线程
11.1进程
11.2多线程基础操作
11.2.1创建线程
11.2.2启动线程
11.2.3终止线程
11.2.4暂停线程
11.2.5合并线程
11.3线程同步
11.3.1lock
11.3.2Monitor
11.3.3Mutex
11.3.4ContextBoundObject
11.3.5ManualResetEvent
11.3.6AutoResetEvent
11.4线程池
11.5跨线程的控件访问
11.6问与答
11.6.1如何使用匿名方法来创建线程
11.6.2如何使用Lambda表达式来创建线程
11.6.3如何向线程方法传递多个参数
11.6.4如何通过手动同步事件给多个线程加锁
11.7思考与练习
11.8实战任务
第12章序列化
12.1二进制序列化
12.2SOAP序列化
12.3XML序列化
12.4问与答
12.4.1采用二进制序列化时,从序列化后的文件能看到什么
12.4.2如何序列化到内存流
12.4.3反序列化时想使用被禁止序列化的字段该如何办
12.4.4属性在序列化时遵从什么样的规律呢
12.5思考与练习
12.6实战任务
第13章压缩与解压
13.1DeflateStream
13.2GZipStream
13.3问与答
13.3.1using的作用
13.3.2如何实现多文件的压缩解压
13.4思考与练习
13.5实战任务
第14章SQL
14.1数据库基本概念
14.2SQL学习环境及基本操作
14.2.1Microsoft SQL Server Management
Studio
14.2.2数据库与表的基本SQL操作
14.3Insert
14.4Select
14.4.1查询数据
14.4.2查询指定字段
14.4.3排序
14.4.4过滤
14.4.5查询前n条记录
14.4.6模糊查询
14.4.7统计
14.4.8分组
14.4.9空值查询
14.5Update
14.6Delete
14.7问与答
14.7.1如果表名或者字段名中有空格该如何办
14.7.2如何只返回不重复的记录
14.7.3如何指定结果的列名
14.7.4如何对查询到的结果进行一定的组合或者运算后呈现
14.7.5如何使用between关键字
14.7.6如何使用in关键字
14.7.7如何使用[]、[^]通配符
14.7.8compute子句如何使用
14.7.9什么是联合查询
14.7.10什么是嵌套查询
14.8思考与练习
14.9实战任务
第15章ADO.NET
15.1Connection
15.2Command
15.3DataReader
15.4DataAdapter
15.5DataSet
15.6参数化查询
15.7数据绑定
15.7.1相关控件与组件
15.7.2简单控件绑定
15.7.3复杂控件绑定
15.7.4数据绑定示例
15.8问与答
15.8.1记不住连接字符串的写法如何办
15.8.2Access数据库的连接字符串是怎样的
15.8.3连接Excel工作簿的连接字符串如何写
15.8.4如何使用App.config文件
15.9思考与练习
15.10实战任务
附录A异常处理与调试
A.1异常处理
A.1.1异常处理的几种形式
A.1.2异常的引发
A.1.3自定义异常
A.2调试
参考文献
前言
C#作为.NET Framework下的语言,是一种简洁优雅、多用途、面向对象的现代化语言,它兼具C语言的语法特征、Visual Basic的快速开发特征、Java的虚拟机运行特征,可谓集百家之长。目前开设.NET相关课程的高校越来越多,相关的课程主要涉及如下几个方面: C#面向对象程序设计、.NET Framework程序设计、WinForm应用开发、ASP.NET Web应用开发、WPF程序设计、Silverlight开发、Windows Phone开发、.NET下的数据库应用开发等。虽然目前市面上关于C#的教材很多,不过在我们近几年的教学过程中,却发现这些教材或多或少存在一些缺陷和不足之处,总结如下: (1) 内容陈旧。有些教材在内容安排上过于陈旧,仍然只讲C#2.0的知识。(2) 讲解方法不合理。有的教材在讲解C#的基础知识时,大量使用庞大的示例,动辄好几页的代码; 而有的教材则喜欢使用数据结构的知识来讲解C#的基础知识。我们认为,目前有一部分学生(包括很多IT从业人员),数据结构方面的知识并没完全理解,在这种情况下,使用数据结构的例子来讲解C#新的基础知识,对学生无疑是雪上加霜。这样容易导致学生学习重点转移,甚至有可能打击学生的学习兴趣。(3) 讲解抽象或者死板。不少教材讲解太抽象,讲解多而实例少,学生学习效果不佳。有的教材甚至从MSDN上复制不少内容,虽然MSDN的内容权威,但是MSDN上的很多叙述拗口、让人费解。(4) 概念性错误。少数教材在基本概念性知识方面存在错误,如DateTime、TimeSpan是典型的结构,好几套教材都称之为类,这样基本性的错误容易误导学生对这两种数据类型的理解。(5) 示例多、讲解少。有些教材或者书籍,具有大量的示例,但却缺少基础的讲解,仅仅只是大量示例的罗列而已,缺少对本质内容的讲解,学生也因此理解不到位,终只会些花招而内功不足。这些书籍可以作为教材的有益补充,用作课后练习之用。鉴于以上一些原因,我们编著了本书。本书以通俗易懂的语言、生动有趣的示例来讲解C#多方面的知识,内容安排兼顾广度、深度,紧跟C#发展动向,知识新颖,内容丰富。本书代码开发工具使用Visual Studio 2015,内容既囊括了数据类型、运算符、程序控制、数组、字符串等传统内容,还涵盖了面向对象、Windows Form程序设计、文件、集合、泛型、GDI 、多线程、序列化、SQL、ADO.NET、实用类库等。本书具有如下特点: (1) 语言通俗易懂。在写作本书的过程中,在内容的讲解上力求通俗易懂,但这样做的风险很大。因为通俗易懂往往和精确是一对矛盾。虽然从MSDN复制内容过来既轻松又权威,不用担心出错,不过如果这样,也就没必要写这本书了。我们的目的,就是在尽量保证准确的前提下,把知识讲解得易懂。(2) 示例简短精炼。我们认为,学习新知识时,不是缺少长篇累牍的代码,而是缺少针对性强的精炼小示例。全书配套大量精选示例,帮助读者理解所学知识。(3) 示例融知识性和趣味性于一体。本书中很多的示例、思考与练习、实战任务等都来自于我们长期的教学积累,不少示例生动、有趣而又具有实用价值,学生在学习的过程中不会感到编程枯燥无趣,当经过认真思考并动手实践得到正确结果后,会充满成就感而觉得用C#编程其乐无穷。(4) 内容新颖全面。除了上述所讲的内容外,细节知识方面涉及到隐式类型变量、匿名方法、Lambda表达式、可空类型、字符编码、扩展方法、Tuple、图像算法及应用、压缩解压等诸多新颖或实用的知识,还融入了不少编程经验体会。(5) 重点、难点突出。本书内容全面充实,重点、难点突出,如对面向对象花了大篇幅进行全面细致的讲解,这是整个课程体系的基础核心所在。(6) 思考练习层层递进,注重梯度。练习方面,也是分层递进,注重梯度,按课堂练习→课堂思考→课后思考与练习→实战任务,逐层深入,难度逐步提升,符合一般的学习规律,逐步加强学习效果。(7) 问答环节设计。在每章都安排了问与答环节,讲解了一些容易混淆的问题或者扩展一些课堂内的知识,为学有余力的读者开了一扇扇去学习更多知识的窗口。后提及一下教材的使用。对于课时较少的院校,可以上学期安排第1~7章的授课内容,而下学期安排第8~15章的内容。对于多课时的院校,则可以根据自身情况选择需要的章节学习。本书可以用作下述课程的教材: C#面向对象程序设计。 .NET Framework程序设计。 WinForm应用开发。 .NET下的数据库应用开发。另外也可以作为如下课程的入门教材: ADO.NET入门。 SQL入门。 多线程入门。 GDI 入门。限于时间精力和水平,书中难免存在诸多值得推敲的地方,甚至会有内容的疏漏和错误。各位专家、老师和读者在使用过程中,如果发现任何问题,欢迎不吝赐教。联系邮箱: [email protected],[email protected]。感谢诸多领导、老师的指导与鼓励支持,也感谢在教材使用过程中为本书提出建议的所有学生和老师。
编者2016年7月
一个合理实用的程序,不可能是从头至尾一行一行地依次顺序执行的。程序的执行顺序在更多的场合应该适当地进行改变。这种改变程序从行依次顺序执行到后一行的机制即称为程序控制。程序控制语句有3类: 选择语句、循环语句和跳转语句。这3类语句构成了由简单语句搭建程序大厦的基石。3.1选 择 语 句选择语句有if语句和switch语句,其中尤其以if语句为常见。3.1.1if语句if语句是基本常见的程序流程控制语句。if可以配合else或者else if来无限扩展选择执行的分支,当然在实际编码过程中,不会写很多的if和else if。if语句可能会有如下几种使用形式。无论采用下面的哪种方式,即使分支再多,多也只会有一个分支获得运行。(1) 一个分支: if (条件) {语句序列; }。(2) 两个分支: if (条件) {语句序列; } else {语句序列; }。(3) 多分支: if (条件) {语句序列; } else if {语句序列; }…else {语句序列; }。(4) 嵌套: if (条件){ if语句序列;} else { if语句序列;}。其执行机制是: 判断各个条件,哪个条件成立则执行哪个分支相应的语句序列。若所有的条件都不成立,则直接执行整个if块后的语句。例如,若有一语音播报程序,当获知客户的性别为男时,可以输出“先生,你好!”,反之如果是女士时,则输出“女士,你好!”。则可能的参考代码如下。
Console.Write(“请输入您的性别: “);
string sSex=Console.ReadLine();
if (sSex==”男”)
Console.WriteLine(“先生,你好!”);
else
Console.WriteLine(“女士,你好!”);
上面的程序即采用的形式(2),如果能按照预期输入,程序运行自然良好。程序执行结果如图31所示。但是若用户随便输入,只要不输入“男”,则上面的程序都将把客户视为女性,自然不合理。例如如图32所示的输出。
图31两分支的if语句——正常执行
图32两分支的if语句——不正常执行
所以可以稍加修改,改善后的代码如下:
Console.Write(“请输入您的性别: “);
string sSex=Console.ReadLine();
if (sSex==”男”)
Console.WriteLine(“先生,你好!”);
else if(sSex==”女”)
Console.WriteLine(“女士,你好!”);
else
Console.WriteLine(“对不起,你是人妖吧?!”);
此代码即形式(3)。另外,为了给客户的提醒更具体点,比如根据当前时间,显示早上好、中午好、下午好、晚上好等比较具体的问候语,则可以编写如下代码:
//如下仅考虑上午好和下午好两种问候; 并且如下关于时间的判断并不对,此处仅为演示之用,聪明的您可以将其修改的更加合理。
//其中DateTime.Now.Hour是用来获取当前时间的小时部分
Console.Write(“请输入您的性别: “);
string sSex=Console.ReadLine();
if (sSex==”男”)
{
if(DateTime.Now.Hour>12)
Console.WriteLine(“先生,下午好!”);
else
Console.WriteLine(“先生,上午好!”);
}
else if(sSex==”女”)
{
if(DateTime.Now.Hour>12)
Console.WriteLine(“女士,下午好!”);
else
Console.WriteLine(“女士,上午好!”);
}
else
Console.WriteLine(“对不起,输入有误!”);
观察上面的代码,不难发现,此即形式(4),即if的嵌套使用。 课堂练习: 请编写一个程序,根据用户输入的分数,来输出其分数是优秀、良好、中等、及格或者不及格(分级可以根据平时百分制的常规分级认定)。3.1.2switch语句switch语句与if语句一样,也是在众多分支中选择一个匹配的分支来执行。然而两者并不是完全一样,并且在更多的情况下,对程序编码人员来说,用if语句会更习惯些。其语法形式如下:
switch (表达式)
{
case 值1:
语句序列;
break;
case 值2:
语句序列;
break;
…
case 值n:
语句序列;
break;
default:
语句序列;
break;
}
其执行机制是: 根据表达式的值,在各个case中寻找匹配的,如果找到匹配的case,则执行相应的语句序列直到遇到break,否则执行default分支,当然前提是在default分支存在的情况下。但是,使用时需要注意如下事项。(1) switch的表达式的值只能是整型(byte、short、int、char等)、字符串或枚举(其实枚举可以视为整型的特例)。(2) 各个case下的break不可或缺; 但是若某几个case共用一段语句序列时,break可以不要。(3) switch语句同if语句一样,也可以嵌套。仍以3.1.1节的示例为例进行讲解说明。
Console.Write(“请输入您的性别: “);
string sSex=Console.ReadLine();
switch (sSex)
{
case “男”:
Console.WriteLine(“先生,你好!”);
break;
case “女”:
Console.WriteLine(“女士,你好!”);
break;
default:
Console.WriteLine(“泰国人妖!”);
break;
}
若某些case对应的语句块相同,则break可以省略。例如,上例根据用户输入的性别,若用户输入“男”或者“女”,程序输出“性别正常”,否则输出“性别不正常”。
Console.Write(“请输入您的性别: “);
string sSex=Console.ReadLine();
switch (sSex)
{
case “男”:
case “女”:
Console.WriteLine(“性别正常”);
break;
default:
Console.WriteLine(“性别不正常”);
break;
}
两种合法的性别对应的case块,共用一个输出,此时可以采用上述这种写法。 课堂练习: 请编写一个程序,要求使用switch语句完成。根据用户输入的分数,来输出其分数是优秀、良好、中等、及格或者不及格(分级可以根据平时百分制的常规分级认定)。3.2循 环 语 句循环语句分3类,分别为for语句、while语句和do…while语句。3.2.1for语句for语句是一种使用极其灵活的循环语句。其一般形式如下:
for (初始化语句; 条件测试语句;迭代语句)
{
循环语句序列//循环体,该处的语句序列会被反复执行直至循环结束
}
其中,初始化语句通常用于给循环变量赋初值,此处的循环变量往往就是计数器; 而条件测试语句往往用来判断循环是否需要继续执行,当此处为true时循环继续,否则不再继续; 而迭代语句往往用来实现对循环变量值的更改,正是该更改使得循环变量的值向使循环结束的趋势变化。另外,需要指出的是,for循环的上述3个部分并非必不可少,可以有选择性地去除某几个部分,甚至可以把3个部分全部去除。这样就可以得到for循环的多种变体形式。其执行机制是: 首先执行初始化语句,其次执行条件测试语句,当条件测试语句返回true时,接着执行循环语句序列,后执行迭代语句,这样次循环即结束。除次循环需要执行初始化语句,其他时刻不会再执行。从第二次循环开始,每次首先执行条件测试语句,如果成立则执行循环语句序列,再执行迭代语句; 然后又进入下一轮循环的条件测试语句判断,直至该语句不成立时循环结束。
上面的一般形式,一般也可以改写为while语句,其对应的while语句代码如下:
初始化语句;
while (条件测试语句)
{
//do sth.
迭代语句;
}
下面看示例。
//100以内等差数列的输出 1 2 3 4…
for(int i=1;i<100;i )
Console.WriteLine(i);
上面的迭代语句部分虽然用自增表达式常见,然而却并不是必须这么做。例如:
//100以内奇数等差数列的输出 1 3 5 7…
for(int i=1;i<100;i =2)
Console.WriteLine(i);
//100以内等比数列的输出 1 2 4 8…
for(int i=1;i<100;i*=2)
Console.WriteLine(i);
for循环的变体很多,此处不一一说明,仅给出两个简单示例,有兴趣的读者可以参看其他书籍,或者自行测试。
//100以内奇数等差数列的输出1 3 5 7…
for(int i=1;i<100;)
{
Console.WriteLine(i);
i =2;
}
//100以内等比数列的输出1 2 4 8…
int i=1;
for(;i<100;)
{
Console.WriteLine(i);
i*=2;
}
在上面的两个小示例中,个示例取消了迭代语句部分,而第二个示例则将初始化语句部分和迭代部分都取消了,然而程序仍能正确执行。如果将3个部分都取消,只留下循环语句序列部分,则构成一个死循环。读者可以根据for循环的执行机制分析上述两段小程序。
当循环变量仅仅用于循环计数而无其他作用时,好将循环变量i的作用域限制在for循环的结构内部,即:
for(int i=0;i
而不应该这么写:
int i=0;
for(i=0;i
由于条件测试表达式会反复执行,因此如果该表达式来自于一个费时的函数,且该函数与循环变量无关,则应注意优化写法。例如:
int GetMaxLength()
{
System.Threading.Thread.Sleep(2000);//模拟一个耗时的操作
return 100;
}
for(int i=0;i{
//do sth.
}
这样,虽然GetMaxLength()的返回值与循环变量i无关,但每次循环都会执行GetMaxLength(),白白浪费了大量的时间。所以可以做如下改写:
int max=GetMaxLength();
for(int i=0;i{
//do sth.
}
修改后,这个耗时的操作将只会执行一遍。在WinForm或者ASP.NET中不少读者容易犯类似的错误,例如:
for(int i=0;i < Convert.ToInt32(textBox1.Text);i )
{
//do sth.
}
此外还有另外一类问题,就是使用循环来做某种匹配,当匹配到时即退出循环。典型的应用如在ListBox中添加不重复项的情况,此时比较通用的做法是定义一个bool类型的标记变量flag,然后在循环结束后通过flag的值来决定将要做何后续操作。3.2.2while语句while语句是另外一种常见的循环形式,其一般形式如下:
while (条件表达式)
{
循环语句序列;
}
其执行机制是: 首先执行条件表达式,若为真则执行循环语句序列,接着再执行条件表达式,直到条件表达式不成立退出循环为止,继而执行循环体之外的语句。当条件表达式次就不成立时,此时循环语句序列不会获得任何执行机会。仍以3.2.1节中输出100以内的奇数等差数列为例进行说明,代码如下:
//100以内奇数等差数列的输出 1 3 5 7…
int i=1;
while (i<100)
{
Console.WriteLine(i);
i =2;
}
3.2.3do…while语句do…while语句是另外一种常见的循环形式,与while语句基本完全一样。其一般形式如下:
do
{
循环语句序列;
} while (条件表达式);
其执行机制是: 首先执行循环语句序列,然后执行条件表达式,若为真则接着执行循环语句序列,接着再执行条件表达式,直到条件表达式不成立退出循环而执行循环之外的语句。从上面的叙述可以看到,do…while语句中的循环语句序列至少将获得一次执行机会。这也是do…while与while的不同之处。仍以3.2.1节中的输出100以内的奇数等差数列为例进行说明,代码如下:
//100以内奇数等差数列的输出 1 3 5 7…
int i=1;
do{
Console.WriteLine(i);
i =2;
}
while (i<100);
对比3.2.2节的while语句,也许看不到差别,但是若将i的初值赋为不小于100(例如1000)的整数,然后再执行上面两段程序,将会看到: while语句对应的程序不会有任何输出,而do…while语句则会输出1000。3.3跳 转 语 句跳转语句有break、continue、goto、return、throw,这几个语句都能够改变程序的执行流程。其中尤其以break语句、return语句为常见,throw语句则用于异常处理,而goto语句一般都不推荐使用,因为它可能导致程序难以阅读、维护,给人混乱的感觉。3.3.1break语句break语句除了用于switch语句中,它更广的用途是用于退出循环,即将程序的执行流程从循环内转到循环外的条语句。它的使用频率很高。例如:
for (int i=1;i<10;i )
{
if (i % 3==0)
break;
Console.WriteLine(i);
}
程序的执行结果如图33所示。可见,当i=3时,由于满足if的条件,故执行break语句,导致程序跳出了循环,后续的数值无法输出。当存在多层循环嵌套时,break语句仅仅从它所在的循环跳出,而不是跳转到所有循环的外面。例如:
for(int j=1;j<4;j )
{
for (int i=1;i<10;i )
{
if (i % 3==0)
break;
Console.WriteLine(i);
}
Console.WriteLine(“内层循环结束”);
}
程序的执行结果如图34所示。
图33break语句
图34多层循环下的break语句
从结果明显地看到,虽然在内层循环中使用break语句退出了循环,但外层循环不受影响,仍然执行了3次。3.3.2continue语句continue语句容易与break语句混淆,它也是一个用于循环控制的语句,其作用不是退出整个循环,只是将程序的执行流程提前跳转到下一次循环,执行流程仍然在循环内。这一点与break语句不一样,break语句使得程序的执行流程从循环内跳转到了循环外。例如:
for (int i=1;i<10;i )
{
if (i % 3==0)
continue;
Console.WriteLine(i);
}
图35continue语句
程序的执行结果如图35所示。从执行结果可以看到,凡是满足被3整除的数值都没有被输出,其他数值都被正常输出,表明程序遇到continue,并未跳到循环外,只是略过了某些满足条件的循环而已。读者可以仔细对照上面的示例程序,体会continue语句与break语句的不同。
3.3.3goto语句goto关键字一般不推荐使用。不过有时使用goto可以大大简化程序代码,例如从嵌套层次很深的代码块中直接跳转到外层。goto在使用时需要配合一个行标签,即表明其跳转的目的位置。下面以使用goto语句实现循环为例来说明其用法,具体如下。
int i = 1;
begin://行标签
if (i<10)
{
Console.WriteLine(i);
i = 2;
goto begin;
}
图36使用goto实现循环
程序的执行结果如图36所示。分析程序,不难看到,每当程序执行到goto begin; 时,程序的执行流程跳转到了if语句处开始执行,从而实现了循环。3.3.4return语句这是一个使用频率极其高的关键词,用于从函数退出或者返回值,详见4.5节内容。3.3.5throw语句这是一个用于异常处理的语句。当发生异常时,可以借助该关键字改变程序的正常执行流程,详见附录A.1。3.4问与答3.4.1if和switch分别应用于什么场合
if比switch更加灵活强大,可以这么认为,凡是能使用switch的场合,肯定可以使用if来完成,但反过来却不一定。但在如下场合可以优先考虑switch。(1) 测试表达式的值为离散值,而非连续值; 且是取值个数不太多的场合。例如整型数据、枚举、字符等,当取值个数不多时都符合该条件。(2) 测试表达式的值本身为连续值,但经过某种处理可以转化为离散值的场合。例如经常对分数按照某几个段来划分等级,此时可以将分数与10作整除运算即转换为离散值。
此处所说的离散不是数学上严谨的离散的意义。例如1,2,…在数学上是离散的,但当判断成绩分数的等级时,显然可以认为它们是连续的,而认为60,70,…才是离散的。3.4.2if和switch的各个分支的书写顺序有影响吗 虽然if和switch的各个分支是平行关系,其书写顺序对程序的结果不会有影响,但是其书写顺序对程序的执行效率是有影响的。一般而言,应该将可能性,即有可能匹配的分支放到前面,而将不可能的分支放到后面,这样可以避免很多不必要的判断和计算。例如,需要针对当前大学的学生做某项测试,年龄分段标准为13~18(少年班的大学生年龄都会落在该区间)、19~24(一般大学本科生即在该区间)、25~30(研究生则落在该区间)。则一种可能的代码如下(下面仅为表意,代码是不可执行的,也不符合C#语法):
if (13-18& DoIt())
{
//…
}
else if (19-24& DoIt())
{
//…
}
else if (25-30& DoIt())
{
//…
}
else
{
//…
}
DoIt()
{
//模拟一个比较耗时的操作
System.Threading.Thread.Sleep(2000);
return true;
}
假如有10000个测试对象,其中6000人介于19~24岁,3000人介于25~30岁,1000人介于13~18岁。在这里不详细比较,仅大概计算,若按上面的代码,10000次判断中只有1000次匹配个分支,但由于将13~18放在个分支,所以即使不匹配该分支的场合也要执行测试,也就是另外9000次都白白去执行了一次DoIt(),也就是无谓地浪费了9000×2s=18000s。而如果做如下调整:
if (19-24 & DoIt())
{
//…
}
else if (25-30 & DoIt())
{
//…
}
else if (13-18 & DoIt())
{
//…
}
则耗时将大大减少。请读者自行比较两种方式下的耗时情况。
上述代码只是为了说明不同写法所耗费的时间,至于存在的诸多不合理,读者不必在此深究,理解了在合适的场合使用合适的方式书写代码即可。
上文所说的将匹配可能性的分支放到前面,此规则也适用于switch。
上述规则也并不是任何情况下都应该遵从的,当执行各个分支的计算耗时不多时,也没有必要这么做,因为这样违背人们习惯的排序方式,会让代码变得不易维护。例如将100以内的数值以10间隔,分为10段,假如不按人们的认知顺序来书写,会让读代码的人感觉无所适从。3.4.3如何避免太深的嵌套 无论是if,还是for等循环语句,都应该避免太深的嵌套。具体如何避免该问题,当然要根据具体情况来分析。下面仅以一种常见的if嵌套方式来说明该问题,希望读者活学活用。看如下嵌套代码:
if(A)
{
if(B)
{
if(C)
{
//do sth.
}
}
}
这里仅写了三层嵌套,实际应用中很多代码编写人员会写出更深的嵌套层次。这应该是极力避免的。看下面一种可能的改造方式。
if(!A)
{
return;
}
if(!B)
{
return;
}
if(!C)
{
return;
}
//do sth.
3.4.4for、while、do…while分别应用于什么场合 首先需要交待的是,3种语句在很多情况下其实是通用的,只要愿意,可以使用任何一种。不过一般情况下,可以依据下述原则来选择,仅供参考。(1) 若循环的次数是已知的,选用for循环语句。(2) 若循环次数未知,但可以确保至少会执行一次,则可以使用do…while循环语句。(3) 若循环次数完全未知,可以使用while循环语句。除了此处给出的三种循环,后文将介绍另外一种循环语句——foreach。3.4.5如何知道程序执行耗费的时间要想实现该功能,首先需要引用命名空间System.Diagnostics,在该命名空间下有一个类Stopwatch,可以利用该类完成计时的功能。该类常用的属性和方法分别如表31和表32所示。
表31Stopwatch的常用属性
属性说明
Elapsed已经历了多久,TimeSpan类型ElapsedMilliseconds已经历的毫秒数,long型ElapsedTicks已经历的Tick数,long型IsRunningStopwatch是否仍然在工作
表32Stopwatch的常用方法
方法说明
Reset()重置计时,即将上表中的属性置0Restart()重启计时Start()启动计时Stop()停止计时
若要统计某段程序的执行耗时情况,一种简单的使用方式是,在该代码块的前面调用Stopwatch实例对象的Start()方法,而在代码块的后面调用该实例对象的Stop()方法,此时再读取其ElapsedMilliseconds属性即可知道程序执行耗费了多少毫秒。例如:
static void Main(string[] args)
{
Stopwatch sw = new Stopwatch();
Console.WriteLine(“开始计时”);
sw.Start();
int s = 0;
for (int i = 0; i < 10000000; i )
s = i;
sw.Stop();
Console.WriteLine(“执行完毕,停止计时。程序执行耗时{0}毫秒”,sw.ElapsedMilliseconds);
}
程序执行结果如图37所示。
图37Stopwatch演示
3.4.6如何产生随机数产生随机数是一个很常用的功能。下面讲解如何利用System.Random来产生随机数,其实例化对象主要有以下3种方法。(1) NextBytes();用于批量生成随机数。(2) NextDouble();用于生成0~1.0之间的随机数,即[0.0,1.0)。(3) Next();有3种重载方式,分别如下。 Next() 默认,产生非负随机整数。 Next(int max) 用于生成介于0~max之间的随机整数,即[0,max)。 Next(int min,int max) 生成介于min~max之间的随机整数,即[min,max)。
Random rnd = new Random();
int i = rnd.Next(100);//随机产生一个介于0~100之间的随机整数,无法取100
int j = rnd.Next(60, 100); //随机产生一个介于60~100之间的随机整数,无法取100
for (int k = 0; k< 20; k )
Console.Write(rnd.Next(100) “\t”);
3.4.7什么叫程序集 程序集根据不同的分类标准,可能有多种叫法,如可以分为单程序集和多文件程序集,可以分为共享程序集和私有程序集。此处暂时不在该概念细节上深究。为了帮助读者理解,这里仅以一个狭隘而又直观的观点来认识程序集: 在VS中写代码终得到的exe文件和dll文件就是一个程序集。3.5思考与练习(1) 从键盘上输入两个整数,由用户回答它们的和、差、积、商和取余运算结果,并统计出正确答案的个数。(2) 求出1~10000之间的所有能被9整除的数,并输出每5个数的和。(3) 找出所有介于10~10000之间的所有素数(请使用for、while、do…while 3种循环分别实现)。(4) 父子年龄问题。设计一个程序,指定父子两人当前年龄,由程序完成两个任务的计算: ①目前父亲年龄是儿子年龄的多少倍; ②计算多少年后,父亲年龄变为儿子年龄的二倍。
3.6实 战 任 务(1) 编写一段程序,运行时向用户提问“你今年多少岁?(1~100)”,接受输入后判断其属于何种人生状态(婴儿、童年、少年、青年、中年、老年; 各个年龄段如何分级请自行确定),并要求在用户输入非法数据时给予适当提示,整个程序在用户输入exit时才退出,否则应反复循环上述问题让用户作答。(2) 完成一个猜数字游戏,要求实现以下功能。 根据用户输入的两个正整数求乘积M(应保证乘积>100)。 再根据用户输入的另外一个值(N)来确定一个猜数游戏范围(介于M-N~M N之间,且0
Random i = new Random();
int iResult=i.Next(min, max);
则iResult即是一个介于min~max之间的一个随机整数,不能取到max。
评论
还没有评论。