深入浅出C#中文版 图文皆译第八章.docx
- 文档编号:9615085
- 上传时间:2023-02-05
- 格式:DOCX
- 页数:82
- 大小:5.46MB
深入浅出C#中文版 图文皆译第八章.docx
《深入浅出C#中文版 图文皆译第八章.docx》由会员分享,可在线阅读,更多相关《深入浅出C#中文版 图文皆译第八章.docx(82页珍藏版)》请在冰豆网上搜索。
深入浅出C#中文版图文皆译第八章
HeadFirstC#中文版第八章
连载地址:
SWPU崔鹏飞翻译,欢迎提出其中的错误。
(下载源码就到源码网:
)
不鸣则已,一鸣惊人。
在真实的世界中,你不会只处理少量的数据。
数据会成堆、成群地向你汹涌而来。
你需要有有力的工具来处理数据,这时集合就派上用场了。
集合可以存储、排序、管理你的程序需要的数据。
这样,你只需写程序来操作数据,而数据的存储就交给集合吧。
不要总是用字符串来存储分类的数据
假设Worker类表示工蜂。
你会如何给它写一个构造方法来接受代表工作的参数呢?
如果你用字符串来代表工作,你的代码有可能会写成下面这样:
或许你可以在Woker类的构造方法中添加代码来检查代表工作类型的字符串。
但是如果你要让蜜蜂掌握一些新工作,那你就需要修改检查代码并重新编译Woker类。
这是相当短视的解决方案。
如果有别的类也需要检查蜜蜂工作的类型怎么办呢?
那就会造成冗余代码,此路不通。
我们需要一种方式来表达出这样的意思:
“嘿,只有某些特定值在这儿才是合法的。
”我们需要枚举出可以使用的值。
(下载源码就到源码网:
)
枚举类型让你可以枚举出有效值
枚举是一种只允许某些特定值的数据类型。
所以我们可以定义一个叫做Jobs的枚举,并定义蜜蜂可以做的工作:
现在你可以这样使用枚举了:
你不可以给枚举胡乱编一个值。
如果你那么做,程序会无法编译。
枚举让你可以给数字取一个名字来代表它
如果数字有名字的话会更容易操作。
你可以把枚举中的值赋为数字并用名字来引用数字值。
这样你的代码中就不会出现一大堆意义不明的数字了。
下面是一个用来给狗狗比赛计分的枚举。
它处于DogCompetition类内部,所以如果你要在类外部使用它的话,就要称它作DogComptition.TrickScore。
你可以把枚举转型为数字并用它来做计算,或者你也可以用ToString()方法来把它当做字符串来对待。
如果你没有给某个名称分配任何值,枚举中的项将会获得默认值。
第一项会被赋值为0,第二个是1,等等。
但是如果你想要给某个枚举项赋值为一个很大的数字会怎么样呢?
枚举中数字的默认类型是int,所以你需要用冒号来这样指明类型:
用你学过的关于枚举的知识来创建一个扑克牌的程序。
①创建一个新项目并添加一个Card类
你会需要两个公有字段:
Suit(代表花色,可以是Spades,黑桃;Clubs,梅花;Diamonds,方片;Hearts,红桃)。
还有Value(代表牌的大小,可以是A,2,3......10,J,Q,K)。
你还需要一个只读属性Name(它的值可以是黑桃A,方片5)。
②用两个枚举来代表花色和牌的大小
确保(int)Card.Suits.Spades的值是0,然后是Clubs等于1,Diamonds等于2,Hearts等于3。
让Value的值等于牌面的大小:
(int)Card.Values.Ace等于1,Two等于2,Three等于3,等等。
Jack等于11,Queen等于12,King等于13。
③给牌的名字添加一个属性
Name应该是一个只读属性。
它的get访问器会返回一个描述牌的字符串。
下面的代码将会在窗体中调用Card类的Name属性,并把它显示出来:
(下载源码就到源码网:
)
④在窗体上添加一个按钮,让它弹出一个消息框来显示随机一张牌
你可以通过把一个0到3的随机值转型为Cards.Suits,还把一个1到13之间的随机值转型为Cards.Values来创建一张随机的牌。
你可以通过内建的Random类来做这件事,你有三种方式来调用它的Next()方法。
问:
等一下。
当我键入上面的代码的时候,我注意到一个智能感应窗口在我用Random.Next()方法的时候弹出来说什么“3of3”。
这是什么意思?
答:
这就是重载了。
一个方法有多种被调用的方式就叫做重载。
使用重载方法的时候,IDE会给出你所有可能的选择。
在这儿,Random的Next()方法有三种调用方式。
你一旦键入“random.Next(”,IDE就会弹出智能感应窗口来提示你。
现在不用太关心重载的问题,本章后面我们会讲的。
一副扑克是可以证明受限的值的重要性。
没人会希望翻过牌来发现是一张梅花王,或者是一张红桃13。
我们如下写Card类。
我们可以用数组来创建一副牌...
如果你想要创建一个类来代表一副牌怎么办?
那就需要存储一副牌中的每一张牌,还需要知道牌的顺序。
数组可以做到这一点--一副牌中顶上的位序是0,下一张是1,等等。
下面开始--52张牌全在的一副扑克。
...如果想要做更多的事情怎么办呢?
想想你会用一副牌做什么呢?
如果你在打扑克,通常都会需要洗牌,在牌堆里取出牌或者放牌进去。
用数组来做这些并不是很简单。
数组不易操作
数组适合存储一系列固定的值和引用。
但是一旦你需要移动元素或者添加的元素大于数组的长度,情况就不妙了。
①每个数组都有长度,你需要知道长度才可以操作数组。
你可以用空引用来把某些数组元素保持为空:
②你需要知道保存了多少张牌。
所以你需要一个int类型的字段,把它叫做topCard,它保存着数组中最后一张牌的位序。
所以我们的只有三张牌的数组长度为7,但是我们要把topCard赋值为3。
③现在情况复杂了。
添加一个返回最上面的牌的引用的Peek()(偷窥)方法很简单--这样你就可以偷看牌堆中最上面的牌了。
但是如果想要添加一张牌怎么办呢?
如果topCard小于数组的长度,你可以把一张牌放进数组去并给topCard加1。
但是如果数组满了,你就需要创建一个新的,更大的数组并把原数组中的牌复制进去。
移除牌是很简单的--但是给topCard减1之后,你需要把被移除的牌原来所在的位序设置为空。
而如果需要在数组中间某个位置移除一张牌又怎么办呢?
如果你想要移除第四张牌,那你就要把第五张牌向前移来填补空白,然后前移第六张牌,然后第七张...噢,这太麻烦了!
用List来存储成组的...任何东西都很好
.NETFramework中有很多集合类可以处理你的类似添加和移除数组元素的问题。
最常见的一个集合类就是List。
创建了List对象,添加元素、从列表的任意位置移除元素、浏览元素、移动元素的位置就都很容易了。
列表是如下工作的:
①首先创建List类的实例
数组都是有类型的--int数组,或者Card数组,等等。
List也是一样。
你需要在用new关键字创建实例时通过把类型写在尖括号<>中来指明类型。
②现在你可以向其中添加元素了
创建List对象之后,你可以向其中放入任意数量的元素(只要放入的元素的类型和你声明List对象时指明的一样就行)。
List比数组更灵活
List类是内建在.NETFramework中的,它让你可以做很多用简单老式的数组做不到的事情。
看看你用List可以做的事情吧。
①你可以创建一个List对象
List
②向其中添加东西
Eggx=newEgg();
myCarton.Add(x);
③再次向其中添加东西
Eggy=newEgg();
myCarton.Add(y);
④查明自身中包含多少元素
InttheSize=myCarton.Count;
⑤查明是否包含某个特定项
BoolIsin=myCarton.Contains(x);
⑥找出特定元素的位置
Intidx=myCarton.IndexOf(y);
⑦查明列表可以包含多少元素
Intlimit=myCarton.Capacity;
⑧从中取出元素
myCarton.Remove(y);
观察左侧的List相关的代码并在右侧补全用数组实现一样功能的代码。
我们并不期望你可以写的完全正确,尽力做就好了。
观察左侧的用List的代码,在右侧写出用数组实现同样功能的代码来把表格补全。
List会自动的增大或者缩小
List的一个优点就是你无须在创建它的对象时指定它的大小。
它会自动的增大或者缩小来适应其中存储的内容。
下面的例子证明了使用List的一些方法会比使用数组更简单:
List对象可以存储任何类型
你已经看见List可以存储字符串或者是Shoe对象。
你也可以创建整数列表,或者任何你可以创建的类型的对象都可以包含到列表中。
这就叫做泛型集合。
创建List对象的时候,你可以把它绑定到一个特定的类型:
你可以创建int列表,string列表,或者是Shoe对象的列表。
这使得操作列表变得简单--一旦创建了列表,你就知道其中存储的数据的类型了。
.NETFramework有很多泛型接口,它们使得集合类可以操作任何类型。
List实现了这些接口,所以你可以创建一个int列表,并以和操作Shoe列表类似的方式来操作它。
自己检查一下:
在IDE中键入List,右键点击它并选择“转到定义”。
这会转到List类的定义。
它实现了一些接口。
你能够给这些代码片段重新排序来使之组成一个可以运行的Windows窗体吗?
要让它在点击按钮的时候弹出下面列出的消息框。
问:
我何时会用枚举代替List呢?
它们不是解决一样的问题吗?
答:
你可以把枚举看做存储多个常量的简便方法。
它们可以使你的代码易读。
List中可以存储任何东西。
其中的每个元素都有自己的方法和属性。
而枚举只可以存储某些数字类型。
所以枚举不可用来存储引用类型。
枚举也不可以动态改变大小,不可以包含方法不可以实现接口。
可以看出枚举和List有很多不同点。
但是它们都有各自不同的作用。
问:
List看起来很强大。
那么我为什么还要用数组呢?
答:
数组占用的内存和CPU比较少。
如果你的程序对性能要求很严格,比如需要成千次的做同一个操作,你会发现List会使得你的程序变慢。
很幸运,你可以通过List的ToArray()方法来把List转化为数组,也可以用List的一个重载的构造方法来把数组转化为List。
问:
我不懂“泛型”这个名字。
为什么叫做泛型集合呢?
数组为什么不是泛型呢?
答:
泛型集合是用来存储某种特定类型的集合对象。
问:
好的,你解释了“集合”。
但是“泛型”呢?
答:
无论List<>中存储的是什么类型,它的行为都是一样的。
所以无论列表中是什么类型的数据,你总是可以添加、移除、插入...“泛型”这个术语是指虽然一个List的实例只可以存储一种特定类型,但是List类是可以普遍的操作任何类型的。
你可以用
但是List类是可以运用于任何类型的。
问:
存在没有类型的列表吗?
答:
没有。
任何列表--其实,是任何集合类型--必须和一种特定类型绑定。
C#中确实有可以存储任何对象的非泛型列表,它叫做ArrayList。
但是由于泛型集合的强大,很少有人用非泛型集合了。
所以我们不讨论非泛型列表。
创建List对象的时候,总是要指定某种类型--这告诉C#该List对象将要存储哪种数据。
它可以存储值类型,也可以存储引用类型。
集合初始化器和对象初始化器的工作方式基本一样
在你需要创建一个列表并立即添加一些项进去的时候,C#给你提供了一个捷径让你可以少打些字。
创建List对象的时候,你可以用集合初始化器来给它一列初始项。
列表创建好初始项也就添加进去了。
通过允许你把创建列表的语句和向列表中添加初始项的语句结合起来,集合初始化器使得你的代码更加紧凑。
我们来创建一个鸭子的列表
这儿有一个鸭子类,它保存你广泛的鸭子收藏品。
(你收藏鸭子,对吧?
)
下面是初始化器
我们有六只鸭子,所以要创建一个List
每个new语句用对象初始化器设置鸭子对象的Size和Kind字段。
列表很好用,但是排序不易处理
想要给数字或者字母排序并不难。
但是依据什么给对象排序呢?
尤其是对象有多个字段的情况。
有时你会想要根据name字段的值给对象排序,而有时又可以根据高度或者生日来给对象排序。
给事物排序的方式很多,List支持所有这些方式。
列表知道怎么给自己排序
每一个列表都有一个可以给内部元素重新安排顺序来使之有序的Sort()方法。
列表已经知道如何排序内建的类了,要教给它如何排序你自己的类也不难。
给鸭子排序的两种方式
List.Sort()方法知道如何给实现了IComparable接口的类排序。
这个接口只有一个成员--一个叫做CompareTo()的方法。
Sort()方法用一个对象的CompareTo()方法来把该对象和另一个对象进行比较,然后用返回值(一个int值)来决定哪一个对象排在前面。
但是有时你需要给没有实现IComparaBle接口的对象排序,.NET有另一个接口来解决这种状况。
你可以给Sort()方法传递一个实现了IComparer接口的类的实例。
这个接口也有一个方法。
List的Sort()方法使用要比较的对象的Compare()方法来比较成对的对象,以此来搞清楚哪个对象应该在先。
一个对象的CompareTo()方法把它自身与另一个对象比较
可以通过修改Duck类为实现IComparable接口来让List对象可以给鸭子排序。
要这样做,就要添加一个接收Duck引用作为参数的CompareTo()方法。
如果要比较的鸭子应该排在当前鸭子的后面,CompareTo()方法会返回一个负值。
下面是一个根据大小来排序的Duck类:
你可以通过让一个类实现IComparable接口并给它添加CompareTo()方法来让List的Sort()方法可以给它排序。
使用IComparable来告诉List如何排序
.NETFramework中有一个内建的特殊接口来让你构建自己的排序逻辑。
通过实现IComparer接口,编写其中定义的Compare()方法,你可以告诉List如何去给你的对象排序。
Compare()方法接收x和y两个参数,并返回一个int值。
如果x小于y,返回负值。
如果x和y相等,返回0。
如果x大于y,返回正值。
下面是一个比较鸭子的大小的例子:
你实现IComparer的方式决定List如何给你的对象排序。
给你的比较器创建一个实例
想要用IComparer来排序,就要创建一个实现了该接口的类的实例。
这个实例的存在只是为了--让List的Sort()方法知道如何排序。
但是像任何其他(非静态)类一样,在使用它之前先要实例化它。
多种IComparer实现,多种排序方式
你可以根据不同的需要来创建多种IComparer的实现类。
然后你需要某种特定排序的时候,你就可以调用它。
下面是另一种鸭子比较器的实现:
IComparer可以做复杂的比较
创建一个单独的类来给鸭子排序的一个好处就是你可以在其中构建更复杂的逻辑--你可以向其中添加一些成员来帮助决定列表如何排序。
随机创建五张扑克然后给它们排序。
①写代码来生成一堆乱七八糟的扑克
向窗体添加一个按钮来创建五个Card对象。
创建每个扑克对象之后,用内建的Console.WriteLine()方法来把扑克的名字输出。
你可以在程序运行的时候选择视图中的输出来查看输出。
②创建一个实现IComparer接口的类来给扑克排序
下面是一个好机会可以试试IDE实现接口的捷径:
PublicclassCardComparer_byValue:
IComparer
然后点击IComparer并用鼠标在字母I上面悬停。
下面会出现一个小框,点击小框,IDE将会弹出一个窗口:
如果你点击“实现IComparer
在这个特例下,IDE将会创建一个空的Comparer()方法来比较两张扑克,x和y。
自己写方法让方法在x比y大的时候返回1,反之返回-1,如果一样大就返回0。
在这个例子中,确保K在J之后,而J在4之后,4在A之后。
③确保输出正确
下面是你的窗体在按钮被点击后看起来的样子:
随机创建五张扑克然后给它们排序。
用字典存储键值对
列表就好像是写满了名字的一大页长纸。
但是如果你想要给每个名字配一个地址怎么办呢?
或者是车库中的每辆车需要一条详细描述呢?
你需要dictionary(字典)。
字典让你可以把一个特殊值--key(键)和一些数据--value(值)联系起来起来。
还有一点:
同一个键在一个字典中只可以出现一次。
下面是在C#中声明一个Dictionary的方法:
下面是一个Dictionary的运行实例:
字典的功能概要
字典和列表很是相似。
它们两个在处理多种类型上都很灵活,而且都有很多内建功能。
下面是Dictionary的基本方法:
★添加一项
你可以通过给字典的Add()方法传递一个键和一个值来向字典添加一项。
Dictionary
myDictionary.Add("somekey","somevalue");
★用键来查找它对应的值
你可以用字典做的最重要的一件事就是查找值--这很有道理,因为你把值存在字典里就是为了用其对应的唯一键来查找它。
StringlookupValue=myDictionary["somekey"];
★移除一项
像列表一样,在字典中你也可以通过Remove()方法来移除一项。
只需要把键传递给Remove()方法就可以把键和值同时移除。
★获取键的集合
你可以用KeyCollection从字典中获取所有键的集合并用foreach遍历它。
通常这样用KeyCollection:
★获取值的集合
你可以用ValueCollection从字典中获取所有值的集合。
大多数情况下,你也会把foreach和ValueCollection拿来一起用:
键和值的类型也可以不同
字典的用途很广,它可以存储几乎是任何类型的数据,字符串、数字、对象皆可。
下面的例子中字典存储的数据就是整数作为键,Duck对象作为值。
Duck对象有一个Size字段和一个Types枚举,它们在构造方法中得以初始化。
①创建一个窗体来在两副扑克之间移动牌
你已经创建了一个Card类了。
现在该是时候创建一个类来包含任意数量的牌了,我们把它叫做Deck(一副牌)。
现实中一副牌有52张,但是Deck类可以包含任意数量的牌--或者其中没有牌也可以。
然后你要创建一个窗体来显示两个Deck对象的内容。
启动程序的时候,第一个Deck对象最多有10张随机的牌,第二个Deck对象是完整的一副52张牌,它们都是先依据花色后依据牌面值来排序的--而且你可以用两个Reset按钮把两副牌重设为初始状态。
窗体上还有标记为“<<”和“>>”的按钮来在两副牌之间移动牌。
除了六个按钮的事件处理方法之外,你还需要给窗体添加两个方法。
首先添加一个ResetDeck()方法,它把一副牌重设到初始状态。
它接受一个int参数:
如果传入1,它把第一副牌重设为含有十张随机牌的初始状态;如果传入2,它把第二副牌重设为含有52张牌的一整副牌。
再然后添加下面这个方法:
②创建Deck类
下面是Deck类的框架。
我们已经给你写好了一些成员。
你需要编写Shuffle()方法和GetCardNames()方法来完成它,你还需要让Sort()方法可以工作。
我们重载两个很实用的构造方法:
一个创建52张一整副的扑克,另一个接受一个Card数组并把其中的扑克加载进来。
创建一个类来存储一副牌,还有一个使用这个类的窗体。
你自己也可以创建重载方法
你已经用过重载方法了,甚至还用过.NETFramework的内建类的重载构造方法,所以你应该知道重载是多么实用了。
要是你可以在自己的类中写重载方法岂不是很酷?
好吧,你可以做到--而且很简单!
你需要做的只是写两个或更多个同名但是接收参数不同的方法。
①创建一个新项目并把Card类添加进去
这很容易,在项目上右击,再在“添加”中选择“现有项”。
IDE将会复制Card类并把它添加进本项目。
类文件中的命名空间还是其出处的名字,所以你要到Card.cs类文件的顶部去把它修改为与当前项目一致。
②给Card类添加一些重载方法。
创建两个staticDoesCardMatch()方法。
第一个检查牌的花色。
第二个检查牌的面值。
只有当牌匹配的时候它们才会返回true。
③向窗体添加一个按钮来使用新方法
给按钮添加下面的代码:
CardcardToCheck=newCard(Card.Suits.Clubs,Card.Values.Three);
booldoesItMatch=Card.DoesCardMatch(cardToCeck,Card.Suits.Hearts);
你键入“DoesCardMatch(”之后,IDE的反应就说明了你的确是写出了重载的方法:
使用一下这两个方法,熟悉一下重载。
创建一个钓鱼游戏!
你可以和电脑对玩这个游戏。
这次练习有点不同...
有可能你是因为想要找一份专业开发的工作所以才在学习C#。
你在团队中工作的时候,不太可能自始至终创建一个完整的程序,一般你会创建一个大程序中的一部分。
所以我们这次给你一道题目,其中一部分已经完成了。
窗体的代码在下一页的③中。
你只需要把这些代码输入就可以了--这样你就有了一个很好的开头,但是这也意味着你自己写的类需要和已存在的代码相契合。
这是个挑战!
①从规格说明开始
每个专业的软件项目都是从一个规格说明开始,这个也不例外。
你要创建一个经典的扑克游戏GoFish(钓鱼)!
不同的人玩这个游戏的方式不太一样,所以下面是对于游戏规则的重述:
★游戏由一整副52张牌开始。
每人发五张牌。
剩下的牌叫做stock。
玩家依次叫牌(“你有7吗?
”)。
其他有这张牌的人必须把牌移交出来。
如果没人有这张牌,叫牌的玩家就要去“钓鱼”了,也就是抓一张牌。
★这个游戏是要“作书”的,“书”指的是四张同面值的牌。
游戏最后持有最多的“书”的人赢。
集齐了一本“书”,就要把它亮出来让别人看得到。
★一个玩家把手里的“书”亮出来之后有可能手里就没有牌了。
这种情况下,他就要去再去抓五张牌。
如果剩下的牌没有五张了,他就把所有牌都拿走。
剩下的牌取光,游戏也就结束了。
这时持有最多的“书”的人赢。
★这个电脑版的钓鱼有两个电脑玩家,一个用户玩家。
每轮开始,用户玩家从手中的牌里选一张,显示出来,这代表他要叫这张牌。
然后两个电脑玩家叫牌。
每轮的结果都显示出来。
这样反复循环,直到产生赢家。
★游戏将会管理牌的交换并会把成“书”的牌挑出来。
产生赢家之后,游戏结束。
游戏将会显示赢家的名字(也或许是多个玩家平局)。
没有其他可做--玩家要重启程序来开启一局新游戏。
如果你在开始之前不知道自己要做什么,那你如何知道什么时候算是做完了呢?
所以多数专业的软件项目都是从规格说明开始,它告诉你你要做什么。
②创建窗体
创建钓鱼游戏的窗体。
窗体上应该有一个ListBox控件代表玩家手里的牌,要有两个TextBox控件代表游戏的过程,还有一个按钮让玩家可以叫牌。
要玩这个游戏,玩家要从手里选择一张牌并点击按
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 深入浅出C#中文版 图文皆译第八章 深入浅出 C# 中文版 图文 第八