一文帶你吃透C#中面向?qū)ο蟮南嚓P(guān)知識(shí)
基礎(chǔ)必讀: 超快速成,零基礎(chǔ)快速掌握C#開發(fā)中最重要的概念
switch和字典
前文提到過,有個(gè)游戲里面有個(gè)著名的屎山,就是跑了19億次if,把玩家憋得不行。而解決這個(gè)問題其實(shí)非常簡(jiǎn)單,只需用到switch就可以了。
比如打牌的時(shí)候,正常只有2-10是數(shù)字,1是A,11是J,12是Q,13是K,如果要用if...else if這種方法來判斷,那么遇到K的時(shí)候需要判斷好多次才行,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來跳轉(zhuǎn)到第xx個(gè)case,這個(gè)特性還挺有意思的,所以在這里多提一嘴,但初學(xué)者其實(shí)只要有switch case這個(gè)概念就可以了。
switch case語句之所以在性能上優(yōu)于if...else,極有可能是用了哈希表,通過計(jì)算輸入的方法,來快速鏈接到執(zhí)行程序的入口,達(dá)到常數(shù)級(jí)別的時(shí)間復(fù)雜度。
在C#中,提供了字典這種數(shù)據(jù)結(jié)構(gòu),可以實(shí)現(xiàn)類似于switch case的效果。所謂字典,就是一組鍵值對(duì),通過鍵值的一一對(duì)應(yīng)關(guān)系,達(dá)到通過鍵來索引值的目的,其定義方式如下
Dictionary<int, string> card = new Dictionary<int, string>
{
{1,"A" },
{11, "J" },
{12, "Q" },
{13, "K" }
};
Dictionary為數(shù)據(jù)類型的名字,<int, string>表示其鍵為整型,值為字符串。后面的new表示創(chuàng)建新對(duì)象,這個(gè)在數(shù)組的時(shí)候就已經(jīng)學(xué)過了,最后花括號(hào)中的四行代碼,用于對(duì)Dictionary進(jìn)行初始化。
有了這個(gè),就可以更簡(jiǎn)潔地實(shí)現(xiàn)抽牌功能
Console.WriteLine(card[1]); //命令行中顯示 A
那么這個(gè)時(shí)候可能有人問了,那2-10這9張牌咋辦?是需要加一個(gè)if來判斷嗎?
答案是當(dāng)然不用,畢竟字典是一種動(dòng)態(tài)的數(shù)據(jù)結(jié)構(gòu),內(nèi)部元素是可以增長(zhǎng)的,只需跑個(gè)循環(huán)將其填充上就行了
for (int i = 2; i < 11; i++)
{
card.Add(i, i.ToString());
}
其中,card.Add就是添加元素的方法,i.ToString()可以將整型的i轉(zhuǎn)化為字符串。
這樣,就有了從A到K的撲克牌。
類、成員、方法
撲克牌除了面值之外,還要看花色的。換言之,用數(shù)字是沒法完全描述撲克牌的所有屬性的。
當(dāng)然,這種屬性其實(shí)可以用字典來實(shí)現(xiàn),例如現(xiàn)在有一個(gè)紅桃K,可以表示為
Dictionary<string, string> 紅桃K = new Dictionary<string, string>
{
{"花色", "紅桃" },
{"數(shù)值", "13" },
{"名字", "K" }
};
首先,看到紅桃K千萬不要害怕,在C#中,中文也是可以當(dāng)作變量名的。
其次,字典畢竟不太方便,因?yàn)镃#中的字典要求指定數(shù)據(jù)類型,數(shù)值這個(gè)鍵對(duì)應(yīng)的值,按理說應(yīng)該是整型才比較合理,但無奈之下,只能是字符串。
面對(duì)這種痛點(diǎn),就得看Class來大顯身手了。
class Card
{
public int Id { get; set; }
public string Name { get; set; }
public string Color { get; set; }
}
其中,class表示,Card是一個(gè)類,后面的花括號(hào)里就是這個(gè)類的成員變量。
public表示,這是個(gè)公開的屬性,可以被別人調(diào)用;{get; set;}表示這個(gè)屬性既可以被調(diào)用,也可以被賦值。
需要注意的是,目前我們寫下的所有代碼,都是在.NET6中所定義的頂級(jí)語句。這種頂級(jí)語句,并不符合以往C#代碼的規(guī)范,由此也會(huì)導(dǎo)致一些問題,即頂級(jí)語句必須寫在所有類定義的前面。
所以,如果想創(chuàng)建一個(gè)Card類的實(shí)例,需要在class Card之前調(diào)用,這個(gè)很反直覺,但習(xí)慣了就好。
Card 紅桃A = new Card { Id = 1, Name = "A", Color = "紅桃" };
接下來遇到了一個(gè)很尷尬的問題,的確是新建了一個(gè)紅桃A,然后呢?
首先,可以通過.來調(diào)用類的屬性,例如
Console.WriteLine(紅桃A.Name);
其次,可以在類中添加成員方法,然后調(diào)用一下
class Card
{
public int Id { get; set; }
public string Name { get; set; }
public string Color { get; set; }
public void introduce()
{
Console.WriteLine($"我的名字是{Color}{Name}");
}
}
調(diào)用仍然要在class Card這行的前面,
紅桃A.introduce();
得到的結(jié)果為
我的名字是紅桃A
是時(shí)候規(guī)范一下寫法了
頂級(jí)語句用起來雖然很爽,但,至少在目前看來,最適合的應(yīng)用場(chǎng)景是算法原理的快速驗(yàn)證,而非做開發(fā)。因?yàn)殚_發(fā)要涉及到團(tuán)隊(duì)合作,涉及到大家按照相同的規(guī)范去分塊做不同的內(nèi)容,為了團(tuán)隊(duì)和整體的效率,不得不犧牲局部代碼的簡(jiǎn)潔性,所以作為C#程序員,還是要習(xí)慣那種類似Java風(fēng)格的完全的面向?qū)ο髮懛ā?/p>
重新建一個(gè)控制臺(tái)應(yīng)用,這次在選擇框架的界面,勾選上不使用頂級(jí)語句,這回看到的就是這樣的一個(gè)結(jié)果
namespace MySecondCS
{
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
}
}
}
就是前面提到的,Hello World外面有一層Main函數(shù),Main函數(shù)外面有一個(gè)class,class外面有一個(gè)命名空間。
然后把之前寫過的Card類寫在Program前面,并將Main函數(shù)中的內(nèi)容改為
static void Main(string[] args)
{
Card card = new Card() { Id = 1, Name = "A", Color = "紅桃" };
card.introduce();
}
這樣啟動(dòng)命令行,會(huì)得到上一節(jié)同樣的結(jié)果。
將類寫在一個(gè)文件中當(dāng)然沒什么問題,但隨著所開發(fā)的應(yīng)用越來越復(fù)雜,涉及到的類也會(huì)越來越多,全部堆在一個(gè)文件中,缺乏有效的組織,顯然是不成的。
正所謂晴天帶傘、居安思危,全堆一個(gè)文件不行,那就把類寫在另一個(gè)文件中就是了。右鍵解決方案中的項(xiàng)目名,選擇添加類,如下圖

類名取為Card,然后項(xiàng)目中除了Program.cs之外,還會(huì)出現(xiàn)一個(gè)Card.cs,其內(nèi)容為
namespace MySecondCS
{
internal class Card
{
}
}
其中,namespace為命名空間,在同一個(gè)命名空間中的類可以互相調(diào)用。internal是一個(gè)用于修飾類的關(guān)鍵字,是對(duì)可訪問性的一種限制,這個(gè)限制并不強(qiáng),只要在一個(gè)程序集中,就可以訪問。
這種訪問限制,在前面第一次創(chuàng)建Card的時(shí)候就有提過,Card類中,修飾成員變量用到的public也是用于訪問限制的。
接下來把之前寫好的Card代碼剪切進(jìn)internal class Card中,在啟動(dòng)命令行,程序仍然是可以跑通的。
繼承
之所以要有繼承這個(gè)概念,是因?yàn)榧埮频耐娣ㄌ嗔耍热缥倚〉臅r(shí)候就喜歡搜集小當(dāng)家的水滸卡,梁山好漢108將剛好是兩幅撲克牌,但是Card類中并沒有額外給梁山好漢提供位置。
這個(gè)時(shí)候就會(huì)遇到兩難問題,若直接把Card改成小當(dāng)家水滸卡,那么打牌的人會(huì)覺得這玩意沒啥用,只會(huì)白白地浪費(fèi)內(nèi)存;若是另起爐灶重新寫一個(gè)類,那老板會(huì)覺得你同樣的內(nèi)容寫兩遍,是不是欺負(fù)我不懂技術(shù)?然后說不定就扣工資了。
所以,繼承就比較好地解決了這個(gè)問題,就像這個(gè)名字暗示的,在C#中,可以新建一個(gè)水滸卡的類,可以在繼承Card類中的各種成員之外,添加自己獨(dú)有的成員。
接下來在Card類的下面,新建一個(gè)類,就叫UniCard,表示統(tǒng)一小當(dāng)家水滸卡,如下所示
class UniCard : Card
{
public int Order { get; set; }
public string PersonName { get; set; }
}
其中,UniCard : Card就表示,前者是對(duì)后者的繼承,所有在Card中public的功能,都可以在UniCard中無痛調(diào)用。
接下來在UniCard中實(shí)現(xiàn)一個(gè)功能,即根據(jù)排名確定其對(duì)應(yīng)的紙牌面額。紙牌大小排序是大小王,然后是AkQJ,再然后是10到2。
那么2副撲克牌中,有4個(gè)大小王,其他諸如AKQJ之類的都有8張?,F(xiàn)令大小王是0,A是1,那么其對(duì)應(yīng)的排序就是0->1->13->12->...->2。
也就是說,排名1, 2, 3, 4對(duì)應(yīng)撲克牌中的大小王,面額為0;5-12對(duì)應(yīng)撲克牌中的A,面額為1,然后接下來,每新增八位,其面額就加1。其函數(shù)實(shí)現(xiàn)為
int order2Id(uint order){
if(order <= 4)
return 0;
else if (order <= 12)
return 1;
else
return 14 - (order-4) / 8;
}
這個(gè)函數(shù)是非常簡(jiǎn)單的,但接下來要將其嵌入到UniCard類中,實(shí)現(xiàn)通過Order自動(dòng)生成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;
else
this.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表示當(dāng)前的這個(gè)class,在不引起歧義的情況下是可以省略的。所謂引起歧義,就是假如這個(gè)class外面已經(jīng)有了一個(gè)Name,那么在這個(gè)class里面如果非常突兀地來一個(gè)Name=1,可能會(huì)導(dǎo)致程序不知道這個(gè)Name到底指向誰。
另外,如果if后面跟著的程序塊中只有一行代碼,那么花括號(hào)可以省略。
除此之外,上面的代碼稍微長(zhǎng)了一點(diǎn),但并沒有新的知識(shí)點(diǎn),只是相當(dāng)于復(fù)習(xí)了一下switch case。
接下來,在Main函數(shù)中創(chuàng)建一個(gè)UniCard,并調(diào)用其繼承的自我介紹的函數(shù)。
static void Main(string[] args)
{
UniCard uniCard = new UniCard();
uniCard.Order = 6;
uniCard.Color = "紅桃";
uniCard.getIdName();
uniCard.introduce();
}
運(yùn)行之后,命令行輸出我的名字是紅桃A。
6號(hào)如果我沒記錯(cuò)的話,是豹子頭林沖,結(jié)果現(xiàn)在變成了紅桃A,看來這個(gè)繼承還是比較成功的。
枚舉
撲克牌的花色只有四種,紅桃、黑桃、草花、方片,如果把數(shù)據(jù)類型限制為字符串,保不準(zhǔn)有人會(huì)把牌的花色定義為“五彩斑斕黑”之類的,為了做一個(gè)限制,目前想到比較好的方案是用字典
Dictionary<int, String> CARD_COLOR = new Dictionary<int, string>
{
{0, "黑桃" },
{1, "紅桃" },
{2, "草花" },
{3, "方片" }
};
然后再把花色定義為整型,想要看花色的時(shí)候以CARD_COLOR[0]這種形式調(diào)用。
這樣一來思路就打開了,甚至可以將花色封裝成字符串?dāng)?shù)組
String[] CARD_COLOR = new string[] { "黑桃", "紅桃", "草花", "方片" };
然而在C#中,其實(shí)有更加優(yōu)雅的解決方案,這個(gè)方案就是枚舉
public enum COLOR { 黑桃, 紅桃, 草花, 方片};
上面這行代碼可以寫在internal class Card的外面,然后在Card類中可以把花色定義為
public COLOR Color { get; set; }
枚舉這種數(shù)據(jù)類型的好處是,既有字符串的特點(diǎn),又有整型的特點(diǎn),以COLOR這種類型為例,黑桃對(duì)應(yīng)的是0,紅桃對(duì)應(yīng)的是1,以此類推。
這樣一來,getIdName這個(gè)函數(shù),除了可以通過排名來算牌的面額,還可以據(jù)此計(jì)算牌的花色。
public void getIdName()
{
//...
//寫在switch case后面
if (this.Order <= 2)
this.Color = COLOR.黑桃;
else if (this.Order <= 4)
this.Color = COLOR.紅桃;
else
this.Color = (COLOR)((this.Order - 5) % 8 / 2);
}
其中COLOR.黑桃是常用的枚舉類型的調(diào)用方法,而后面的(COLOR)相當(dāng)于把其后面的(this.Order - 5) % 8 / 2這個(gè)整數(shù),強(qiáng)制轉(zhuǎn)化為枚舉類型。
改完這些之后,就會(huì)發(fā)現(xiàn)Main函數(shù)中的uniCard.Color = "紅桃";出現(xiàn)了紅色的下劃波浪線,說明出現(xiàn)了錯(cuò)誤。原因也很簡(jiǎn)單,現(xiàn)在的Color是枚舉類型,并不能賦值一個(gè)字符串。
將這行刪掉之后,再運(yùn)行程序,命令行輸出為
我的名字是黑桃A
說明introduce中的$"我的名字是{Color}{Name}"仍然發(fā)生了作用,枚舉類型,通過6 66這個(gè)數(shù)值,計(jì)算得到了COLOR.黑桃這個(gè)結(jié)果,最后又通過$字符串轉(zhuǎn)化成了字符串。
這就是前文所言,枚舉類型,既有整型,又有字符串。
構(gòu)造函數(shù)和方法重載
現(xiàn)在回顧一下Main函數(shù),發(fā)現(xiàn)UniCard的創(chuàng)建過程未免太過繁瑣。
static void Main(string[] args)
{
UniCard uniCard = new UniCard();
uniCard.Order = 6;
uniCard.getIdName();
uniCard.introduce();
}
Main函數(shù)中的4行代碼中,如果只保留第一行和最后一行,那就完美了,比如寫成這種
UniCard uniCard = UniCard(6); uniCard.introduce();
在這個(gè)過程中,UniCard變成了一個(gè)函數(shù),通過輸入一個(gè)排名,便可以初始化花色、牌額等內(nèi)容,這個(gè)函數(shù)就叫做構(gòu)造函數(shù),想要實(shí)現(xiàn),只需在UniCard中添加
public UniCard(int order)
{
Order = order;
getIdName();
}
需要注意,在Order=order中,前面的Order為類成員,其實(shí)可以寫為this.Order,getIdName也可以寫為this.getIdName,由于不會(huì)引起歧義,所以將this省略了。
這時(shí)會(huì)發(fā)現(xiàn),Main函數(shù)中又出現(xiàn)了錯(cuò)誤:
UniCard uniCard = new UniCard();
這時(shí)因?yàn)?,我們已?jīng)為UniCard設(shè)定了唯一的構(gòu)造函數(shù),這個(gè)構(gòu)造函數(shù)必須要輸入一個(gè)整型才能執(zhí)行,UniCard()的參數(shù)卻空空如也,這不報(bào)錯(cuò)才怪,解決方法也很簡(jiǎn)單,只需將其改為我們喜聞樂見的形式就行了
static void Main(string[] args)
{
UniCard uniCard = new UniCard(6);
uniCard.introduce();
}
這個(gè)時(shí)候有人說了,那我就想生成一個(gè)啥也沒有的UniCard,你這么改來改去把我想要的改沒了,你還我UniCard()。
這個(gè)需求也是可以滿足的,這就是所謂的重載。所謂重載,就是在C#中,允許創(chuàng)建一些同名函數(shù),這些同名函數(shù)可以有著不同的輸入?yún)?shù),所以只需在public UniCard(int order)前面或者后面添加下面的代碼,就可以既滿足帶參數(shù)的構(gòu)造函數(shù),又滿足不帶參數(shù)的構(gòu)造函數(shù)了。
public UniCard(){}
運(yùn)算符重載
無論是是打牌,還是梁山好漢,都是有排名的。有排名就可以比大小,比大小,就涉及到了大于號(hào)等于號(hào)小于號(hào)之類的東西。
正如字符串可以把加號(hào)更改為拼接的意思,Card也應(yīng)該有重新定義運(yùn)算符的能力,這種能力就叫做運(yùn)算符重載。
對(duì)于已經(jīng)建立起函數(shù)重載這種概念的人來說,運(yùn)算符重載并不存在理解上的困難,畢竟運(yùn)算符也是一種函數(shù)。
下面針對(duì)UniCard這種數(shù)據(jù)類型,對(duì)比較運(yùn)算符進(jìn)行重載,
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為靜態(tài)類的修飾符,所謂靜態(tài)類型,表示在類尚未實(shí)例化時(shí)就可以調(diào)用,所有運(yùn)算符重載函數(shù)都必須是static的。
operator<表示重新定義運(yùn)算符<,bool類型標(biāo)識(shí)作為運(yùn)算結(jié)果的數(shù)據(jù)類型。
在這種排名中,肯定是數(shù)越小的人地位越高,所以排名第一大于排名第5,從而其Order大的反而小。
在進(jìn)行了這些運(yùn)算符重載之后,就可以在Main函數(shù)中進(jìn)行調(diào)用了
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}");
else
Console.WriteLine($"{a.Color}{a.Name} <= {b.Color}{b.Name}");
}
運(yùn)行之后,命令行輸出為
紅桃K > 草花Q
以上就是一文帶你吃透C#中面向?qū)ο蟮南嚓P(guān)知識(shí)的詳細(xì)內(nèi)容,更多關(guān)于C#面向?qū)ο蟮馁Y料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C#集合根據(jù)對(duì)象的某個(gè)屬性進(jìn)行去重的代碼示例
當(dāng)根據(jù)對(duì)象的Name屬性進(jìn)行去重時(shí),你可以使用以下三種方法:使用Distinct方法和自定義比較器、使用LINQ的GroupBy方法,以及使用HashSet,下面給大家介紹C#集合根據(jù)對(duì)象的某個(gè)屬性進(jìn)行去重的代碼示例,感興趣的朋友一起看看吧2024-03-03
C#實(shí)現(xiàn)簡(jiǎn)單計(jì)算器功能
給大家分享用C#寫出一個(gè)計(jì)算機(jī)功能的全部代碼分享,有興趣的朋友可以跟著做一下。2018-03-03
unity實(shí)現(xiàn)虛擬搖桿控制Virtual Joystick
這篇文章主要為大家詳細(xì)介紹了unity實(shí)現(xiàn)虛擬搖桿控制Virtual Joystick,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-04-04
C#使用CefSharp實(shí)現(xiàn)內(nèi)嵌網(wǎng)頁詳解
這篇文章主要介紹了C# WPF里怎么使用CefSharp嵌入一個(gè)網(wǎng)頁,并給出一個(gè)簡(jiǎn)單示例演示C#和網(wǎng)頁(JS)的交互實(shí)現(xiàn),感興趣的小伙伴可以了解一下2023-04-04
C#中面向?qū)ο缶幊虣C(jī)制之繼承學(xué)習(xí)筆記
這篇文章主要介紹了C#中面向?qū)ο缶幊虣C(jī)制之繼承學(xué)習(xí)筆記,本文給出一個(gè)簡(jiǎn)單子實(shí)例講解C#中的繼承,并講解了一些C#繼承的知識(shí)技巧,需要的朋友可以參考下2015-01-01
C#實(shí)現(xiàn)窗體中動(dòng)態(tài)按鈕的設(shè)計(jì)方法
在窗體界面中,通常以按鈕來代替菜單欄的功能,這種形式雖然給用戶一種直觀、界面風(fēng)格各異的感覺,但通常按鈕都是以靜止的形式顯示,所以本文給大家介紹了C#實(shí)現(xiàn)窗體中動(dòng)態(tài)按鈕的設(shè)計(jì)方法,感興趣的朋友可以參考下2024-04-04
c# Linq distinct不會(huì)調(diào)用Equals方法詳解
這篇文章主要介紹了c# Linq distinct不會(huì)調(diào)用Equals方法詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-12-12

