抽丝剥茧,C#面向对象快速上手
文章目录
- switch和字典
- 类、成员、方法
- 是时候规范一下写法了
- 继承
- 枚举
- 构造函数和方法重载
- 运算符重载
基础必读: 超快速成,零基础快速掌握C#开发中最重要的概念
源码地址: C#面向对象初步 源代码
switch和字典
前文提到过,有个游戏里面有个著名的屎山,就是跑了19亿次if
,把玩家憋得不行。而解决这个问题其实非常简单,只需用到switch
就可以了。
比如打牌的时候,正常只有2-10是数字,1是A,11是J,12是Q,13是K,如果要用if...else if
这种方法来判断,那么遇到K
的时候需要判断好多次才行,switch
则只需一次
void cardName(int cardNum)
{switch (cardNum){case 1: Console.WriteLine("A"); break;case 11: Console.WriteLine("J"); break;case 12: Console.WriteLine("Q"); break;case 13: Console.WriteLine("K"); break;default: Console.WriteLine(cardNum); break;}
}
C#中的switch
语句,除了用break
可以跳出switch
之外,还可以用goto case xx
来跳转到第xx
个case
,这个特性还挺有意思的,所以在这里多提一嘴,但初学者其实只要有switch case
这个概念就可以了。
switch case
语句之所以在性能上优于if...else
,极有可能是用了哈希表,通过计算输入的方法,来快速链接到执行程序的入口,达到常数级别的时间复杂度。
在C#
中,提供了字典这种数据结构,可以实现类似于switch case
的效果。所谓字典,就是一组键值对,通过键值的一一对应关系,达到通过键来索引值的目的,其定义方式如下
Dictionary<int, string> card = new Dictionary<int, string>
{{1,"A" },{11, "J" },{12, "Q" },{13, "K" }
};
Dictionary
为数据类型的名字,<int, string>
表示其键为整型,值为字符串。后面的new
表示创建新对象,这个在数组的时候就已经学过了,最后花括号中的四行代码,用于对Dictionary
进行初始化。
有了这个,就可以更简洁地实现抽牌功能
Console.WriteLine(card[1]);
//命令行中显示 A
那么这个时候可能有人问了,那2-10
这9张牌咋办?是需要加一个if
来判断吗?
答案是当然不用,毕竟字典是一种动态的数据结构,内部元素是可以增长的,只需跑个循环将其填充上就行了
for (int i = 2; i < 11; i++)
{card.Add(i, i.ToString());
}
其中,card.Add
就是添加元素的方法,i.ToString()
可以将整型的i
转化为字符串。
这样,就有了从A
到K
的扑克牌。
类、成员、方法
扑克牌除了面值之外,还要看花色的。换言之,用数字是没法完全描述扑克牌的所有属性的。
当然,这种属性其实可以用字典来实现,例如现在有一个红桃K,可以表示为
Dictionary<string, string> 红桃K = new Dictionary<string, string>
{{"花色", "红桃" },{"数值", "13" },{"名字", "K" }
};
首先,看到红桃K千万不要害怕,在C#中,中文也是可以当作变量名的。
其次,字典毕竟不太方便,因为C#中的字典要求指定数据类型,数值这个键对应的值,按理说应该是整型才比较合理,但无奈之下,只能是字符串。
面对这种痛点,就得看Class
来大显身手了。
class Card
{public int Id { get; set; }public string Name { get; set; }public string Color { get; set; }
}
其中,class
表示,Card
是一个类,后面的花括号里就是这个类的成员变量。
public
表示,这是个公开的属性,可以被别人调用;{get; set;}
表示这个属性既可以被调用,也可以被赋值。
需要注意的是,目前我们写下的所有代码,都是在.NET6中所定义的顶级语句。这种顶级语句,并不符合以往C#代码的规范,由此也会导致一些问题,即顶级语句必须写在所有类定义的前面。
所以,如果想创建一个Card
类的实例,需要在class Card
之前调用,这个很反直觉,但习惯了就好。
Card 红桃A = new Card { Id = 1, Name = "A", Color = "红桃" };
接下来遇到了一个很尴尬的问题,的确是新建了一个红桃A,然后呢?
首先,可以通过.
来调用类的属性,例如
Console.WriteLine(红桃A.Name);
其次,可以在类中添加成员方法,然后调用一下
class Card
{public int Id { get; set; }public string Name { get; set; }public string Color { get; set; }public void introduce(){Console.WriteLine($"我的名字是{Color}{Name}");}
}
调用仍然要在class Card
这行的前面,
红桃A.introduce();
得到的结果为
我的名字是红桃A
是时候规范一下写法了
顶级语句用起来虽然很爽,但,至少在目前看来,最适合的应用场景是算法原理的快速验证,而非做开发。因为开发要涉及到团队合作,涉及到大家按照相同的规范去分块做不同的内容,为了团队和整体的效率,不得不牺牲局部代码的简洁性,所以作为C#程序员,还是要习惯那种类似Java风格的完全的面向对象写法。
重新建一个控制台应用,这次在选择框架的界面,勾选上不使用顶级语句,这回看到的就是这样的一个结果
namespace MySecondCS
{internal class Program{static void Main(string[] args){Console.WriteLine("Hello, World!");}}
}
就是前面提到的,Hello World外面有一层Main函数,Main函数外面有一个class,class外面有一个命名空间。
然后把之前写过的Card类写在Program
前面,并将Main函数中的内容改为
static void Main(string[] args)
{Card card = new Card() { Id = 1, Name = "A", Color = "红桃" };card.introduce();
}
这样启动命令行,会得到上一节同样的结果。
将类写在一个文件中当然没什么问题,但随着所开发的应用越来越复杂,涉及到的类也会越来越多,全部堆在一个文件中,缺乏有效的组织,显然是不成的。
正所谓晴天带伞、居安思危,全堆一个文件不行,那就把类写在另一个文件中就是了。右键解决方案中的项目名,选择添加类,如下图
类名取为Card
,然后项目中除了Program.cs
之外,还会出现一个Card.cs
,其内容为
namespace MySecondCS
{internal class Card{}
}
其中,namespace
为命名空间,在同一个命名空间中的类可以互相调用。internal
是一个用于修饰类的关键字,是对可访问性的一种限制,这个限制并不强,只要在一个程序集中,就可以访问。
这种访问限制,在前面第一次创建Card
的时候就有提过,Card
类中,修饰成员变量用到的public
也是用于访问限制的。
接下来把之前写好的Card
代码剪切进internal class Card
中,在启动命令行,程序仍然是可以跑通的。
继承
之所以要有继承这个概念,是因为纸牌的玩法太多了,比如我小的时候就喜欢搜集小当家的水浒卡,梁山好汉108将刚好是两幅扑克牌,但是Card
类中并没有额外给梁山好汉提供位置。
这个时候就会遇到两难问题,若直接把Card
改成小当家水浒卡,那么打牌的人会觉得这玩意没啥用,只会白白地浪费内存;若是另起炉灶重新写一个类,那老板会觉得你同样的内容写两遍,是不是欺负我不懂技术?然后说不定就扣工资了。
所以,继承就比较好地解决了这个问题,就像这个名字暗示的,在C#中,可以新建一个水浒卡的类,可以在继承Card
类中的各种成员之外,添加自己独有的成员。
接下来在Card
类的下面,新建一个类,就叫UniCard
,表示统一小当家水浒卡,如下所示
class UniCard : Card
{public int Order { get; set; }public string PersonName { get; set; }
}
其中,UniCard : Card
就表示,前者是对后者的继承,所有在Card
中public
的功能,都可以在UniCard
中无痛调用。
接下来在UniCard
中实现一个功能,即根据排名确定其对应的纸牌面额。纸牌大小排序是大小王,然后是AkQJ
,再然后是10
到2
。
那么2副扑克牌中,有4个大小王,其他诸如AKQJ之类的都有8张。现令大小王是0
,A是1
,那么其对应的排序就是0->1->13->12->...->2
。
也就是说,排名1, 2, 3, 4
对应扑克牌中的大小王,面额为0;5-12
对应扑克牌中的A
,面额为1
,然后接下来,每新增八位,其面额就加1。其函数实现为
int order2Id(uint order){if(order <= 4)return 0;else if (order <= 12)return 1;elsereturn 14 - (order-4) / 8;
}
这个函数是非常简单的,但接下来要将其嵌入到UniCard
类中,实现通过Order
自动生成Id
这样的功能
class UniCard : Card
{public int Order { get; set; }public string PersonName { get; set; }public void getIdName(){if (this.Order <= 4)this.Id = 0;else if (this.Order <= 12)this.Id = 1;elsethis.Id = 14 - (this.Order - 4) / 8;switch (this.Id){case 0: this.Name = "Joker"; break;case 1:this.Name = "A"; break;case 13:this.Name = "K"; break;case 12:this.Name = "Q";break;case 11:this.Name = "J"; break;default: this.Name = this.Id.ToString(); break;}}
}
其中,this
表示当前的这个class
,在不引起歧义的情况下是可以省略的。所谓引起歧义,就是假如这个class
外面已经有了一个Name
,那么在这个class
里面如果非常突兀地来一个Name=1
,可能会导致程序不知道这个Name
到底指向谁。
另外,如果if
后面跟着的程序块中只有一行代码,那么花括号可以省略。
除此之外,上面的代码稍微长了一点,但并没有新的知识点,只是相当于复习了一下switch case
。
接下来,在Main
函数中创建一个UniCard
,并调用其继承的自我介绍的函数。
static void Main(string[] args)
{UniCard uniCard = new UniCard();uniCard.Order = 6;uniCard.Color = "红桃";uniCard.getIdName();uniCard.introduce();
}
运行之后,命令行输出我的名字是红桃A
。
6号如果我没记错的话,是豹子头林冲,结果现在变成了红桃A,看来这个继承还是比较成功的。
枚举
扑克牌的花色只有四种,红桃、黑桃、草花、方片,如果把数据类型限制为字符串,保不准有人会把牌的花色定义为“五彩斑斓黑”之类的,为了做一个限制,目前想到比较好的方案是用字典
Dictionary<int, String> CARD_COLOR = new Dictionary<int, string>
{{0, "黑桃" },{1, "红桃" },{2, "草花" },{3, "方片" }
};
然后再把花色定义为整型,想要看花色的时候以CARD_COLOR[0]
这种形式调用。
这样一来思路就打开了,甚至可以将花色封装成字符串数组
String[] CARD_COLOR = new string[] { "黑桃", "红桃", "草花", "方片" };
然而在C#中,其实有更加优雅的解决方案,这个方案就是枚举
public enum COLOR { 黑桃, 红桃, 草花, 方片};
上面这行代码可以写在internal class Card
的外面,然后在Card
类中可以把花色定义为
public COLOR Color { get; set; }
枚举这种数据类型的好处是,既有字符串的特点,又有整型的特点,以COLOR
这种类型为例,黑桃对应的是0,红桃对应的是1,以此类推。
这样一来,getIdName
这个函数,除了可以通过排名来算牌的面额,还可以据此计算牌的花色。
public void getIdName()
{//...//写在switch case后面if (this.Order <= 2)this.Color = COLOR.黑桃;else if (this.Order <= 4)this.Color = COLOR.红桃;elsethis.Color = (COLOR)((this.Order - 5) % 8 / 2);
}
其中COLOR.黑桃
是常用的枚举类型的调用方法,而后面的(COLOR)
相当于把其后面的(this.Order - 5) % 8 / 2
这个整数,强制转化为枚举类型。
改完这些之后,就会发现Main
函数中的uniCard.Color = "红桃";
出现了红色的下划波浪线,说明出现了错误。原因也很简单,现在的Color
是枚举类型,并不能赋值一个字符串。
将这行删掉之后,再运行程序,命令行输出为
我的名字是黑桃A
说明introduce
中的$"我的名字是{Color}{Name}"
仍然发生了作用,枚举类型,通过666这个数值,计算得到了COLOR.黑桃
这个结果,最后又通过$
字符串转化成了字符串。
这就是前文所言,枚举类型,既有整型,又有字符串。
构造函数和方法重载
现在回顾一下Main
函数,发现UniCard
的创建过程未免太过繁琐。
static void Main(string[] args)
{UniCard uniCard = new UniCard();uniCard.Order = 6;uniCard.getIdName();uniCard.introduce();
}
Main函数中的4行代码中,如果只保留第一行和最后一行,那就完美了,比如写成这种
UniCard uniCard = UniCard(6);
uniCard.introduce();
在这个过程中,UniCard
变成了一个函数,通过输入一个排名,便可以初始化花色、牌额等内容,这个函数就叫做构造函数,想要实现,只需在UniCard
中添加
public UniCard(int order)
{Order = order;getIdName();
}
需要注意,在Order=order
中,前面的Order
为类成员,其实可以写为this.Order
,getIdName
也可以写为this.getIdName
,由于不会引起歧义,所以将this
省略了。
这时会发现,Main
函数中又出现了错误:
UniCard uniCard = new UniCard();
这时因为,我们已经为UniCard
设定了唯一的构造函数,这个构造函数必须要输入一个整型才能执行,UniCard()
的参数却空空如也,这不报错才怪,解决方法也很简单,只需将其改为我们喜闻乐见的形式就行了
static void Main(string[] args)
{UniCard uniCard = new UniCard(6);uniCard.introduce();
}
这个时候有人说了,那我就想生成一个啥也没有的UniCard
,你这么改来改去把我想要的改没了,你还我UniCard()
。
这个需求也是可以满足的,这就是所谓的重载。所谓重载,就是在C#中,允许创建一些同名函数,这些同名函数可以有着不同的输入参数,所以只需在public UniCard(int order)
前面或者后面添加下面的代码,就可以既满足带参数的构造函数,又满足不带参数的构造函数了。
public UniCard(){}
运算符重载
无论是是打牌,还是梁山好汉,都是有排名的。有排名就可以比大小,比大小,就涉及到了大于号等于号小于号之类的东西。
正如字符串可以把加号更改为拼接的意思,Card
也应该有重新定义运算符的能力,这种能力就叫做运算符重载。
对于已经建立起函数重载这种概念的人来说,运算符重载并不存在理解上的困难,毕竟运算符也是一种函数。
下面针对UniCard
这种数据类型,对比较运算符进行重载,
public static bool operator< (UniCard a, UniCard b){return a.Order > b.Order;
}
public static bool operator>(UniCard a, UniCard b){return a.Order < b.Order;
}
public static bool operator==(UniCard a, UniCard b){return a.Order == b.Order;
}
public static bool operator!=(UniCard a, UniCard b){return a.Order != b.Order;
}
非常直观,其中static
为静态类的修饰符,所谓静态类型,表示在类尚未实例化时就可以调用,所有运算符重载函数都必须是static
的。
operator<
表示重新定义运算符<
,bool
类型标识作为运算结果的数据类型。
在这种排名中,肯定是数越小的人地位越高,所以排名第一大于排名第5,从而其Order
大的反而小。
在进行了这些运算符重载之后,就可以在Main函数中进行调用了
static void Main(string[] args)
{UniCard a = new UniCard(15);UniCard b = new UniCard(25);if (a > b)Console.WriteLine($"{a.Color}{a.Name} > {b.Color}{b.Name}");elseConsole.WriteLine($"{a.Color}{a.Name} <= {b.Color}{b.Name}");
}
运行之后,命令行输出为
红桃K > 草花Q
抽丝剥茧,C#面向对象快速上手相关推荐
- Python学习六大路线,教你快速上手
最近几年随着互联网的发展学习Python人越来越多,Python的初学者总希望能够得到一份Python学习路线图,小编经过多方面汇总,总结出比较全套Python学习路线,快速上手.对于一个零基础的想学 ...
- 0基础入门,如何快速上手Python?
0基础入门,如何快速上手pythpn 新的改变 因为清晰易读的风格,广泛的适用性,python已经成为最受欢迎的编程语言之一,在TIOBE排行榜位居第四,是名副其实的人工智能第一语言. python ...
- python编程快速上手-----让繁琐工作自动化_每周一书《Python编程快速上手 让繁琐工作自动化》分享!...
内容简介 如今,人们面临的大多数任务都可以通过编写计算机软件来完成.Python是一种解释型.面向对象.动态数据类型的高级程序设计语言.通过Python编程,我们能够解决现实生活中的很多任务. 本书是 ...
- Python程序员的圣经——《Python编程快速上手:让繁琐工作自动化》尾末附下载地址
一.前言 如今,人们面临的大多数任务都可以通过编写计算机软件来完成.Python是一种解释型.面向对象.动态数据类型的高级程序设计语言.通过Python编程,我们能够解决现实生活中的很多任务. 今天给 ...
- 《Iphone SDK3开发快速上手》
<Iphone SDK3开发快速上手> --iPhone SDK 3 Visual QuickStart Guide 译者:(美)Duncan Campbell 著 刘红伟 等译 IS ...
- python编程快速上手自动化_《Python编程快速上手 让繁琐工作自动化》完整版PDF...
image.png <Python编程快速上手 让繁琐工作自动化>完整版PDF 提取码:7qm4 3.jpg 有关本书 累计销售超过10万册 零基础编程者的不二之选 基于Python3编写 ...
- Java开发快速上手
Java开发快速上手 前言 1.我的大学 2.对初学者的建议 3.大牛的三大特点 4.与他人的差距 第一章 了解Java开发语言 前言 基础常识 1.1 什么是Java 1.1.1 跨平台性 1.2 ...
- 【Python五篇慢慢弹】快速上手学python
快速上手学python 作者:白宁超 2016年10月4日19:59:39 摘要:python语言俨然不算新技术,七八年前甚至更早已有很多人研习,只是没有现在流行罢了.之所以当下如此盛行,我想肯定是多 ...
- objective-c 2.0编程语言,Objective-C 2.0编程快速上手 EXE版[12MB]
Objective-C 2.0编程快速上手 内容简介: <Objective-C 2.0编程快速上手>是介绍Objective-C编程的基础教程.全书采用实例讲解.按部就班的方式,全面详细 ...
最新文章
- 初识Tcl(五):Tcl 循环
- linux c 文件操作
- 巧用watch命令执行循环操作,来解放我们的双手
- 【数据科学】鱼水说竞赛:如何做好「特征工程」?
- R与量化(part1)--量化概述
- NGINX SSL配置之设置HTTPS服务器
- 不输3000元旗舰!红米Note 7 Pro堆料有点狠
- Timeline的Animation Track详解
- MITRE 发布“2021年最重要的硬件弱点”榜单
- 开课吧Java课堂:是什么是比较函数?
- php 循环左移,PHP运算符、PHP分支结构和循环、模板语法在模板中的应用
- 模拟电路与数字电路基础知识点总结
- 【UCSC Genome Browser】Genes and Gene Predictions - GENCODE
- 2021牛客寒假算法基础集训营1 C 无根树问题的处理策略 前序后序遍历 奇偶匹配 DFS
- android 定位 指南针,Android 实现指南针效果
- 《人月神话》(The Mythical Man-Month)4概念一致性:专制、民主和系统设计(System Design)...
- 派工单系统 源码_「VIP报修云」报修工单进度通知方法
- html多图轮播淡入淡出js,原生JS实现图片轮播与淡入效果的简单实例
- Vue 设置图片不转为base64
- SQLServer 连接不上 找不到网络路径