基礎部分被我分為了2篇,因為實在太多了,但是每一個知識點我都不舍得洗掉,所以越寫越多,這一篇博客整理了4個夜晚,內容有點多建議慢慢看,本章涵蓋以下主題:
-
介紹C#
-
理解C#的基礎知識
-
使用變數
-
處理空值
下一章進一步探索控制臺應用程式,
2.1) 介紹C#
1.C#1.0
C#1.0于2002年發布,其中包括了靜態型別的面向物件編程語言的所有重要特性,本書第一大部分所有章節都會介紹這些特性,
2.C#2.0
C#2.0是在2005年發布的,重點是使用泛型實作強資料型別,以提高代碼性能、減少型別錯誤,其中包含的主題如表2.1所示,
表2.1 C#2.0中包含的主題
| 功能 | 涉及的章節 | 主題 |
|---|---|---|
| 可空的值型別 | 第2章 | 使值型別為空 |
| 泛型 | 第6章 | 使型別與泛型更加可重用 |
3.C#3.0
C#3.0是2007年發布的,重點是使用語言集成查詢(LINQ)以及匿名型別和lambda運算式等支持宣告式編程,其中包含的主題如表2.2表示,
表2.2 C#3.0中包含的主題
| 功能 | 涉及的章節 | 主題 |
|---|---|---|
| 隱式型別的區域變數 | 第2章 | 推斷區域變數的型別 |
| LINQ | 第12章 | 所有的主題詳見第12章 |
4.C#4.0
C#4.0是在2010年發布的,重點是利用F#和Python等動態語言改進互操作性,其中包含的主題如表2.3所示:
表2.3 C#4.0中包含的主題
| 功能 | 涉及的章節 | 主題 |
|---|---|---|
| 動態型別 | 第2章 | dynamic型別 |
| 命名/可選引數 | 第5章 | 可選引數和命名引數 |
5.C#5.0
C#5.0發布于2012年,重點是簡化異步操作支持,從而在撰寫類似于同步陳述句的陳述句時自動實作復雜的狀態機,其中包括的主題如表2.4所示,
表2.4 C#5.0中包含的主題
| 功能 | 涉及的章節 | 主題 |
|---|---|---|
| 簡化異步任務 | 第13章 | 理解async和await |
6.C#6.0
C#6.0于2015年發布,專注于對語言的細微改進,其中包括的主題如表2.5所示,
表2.5 C#6.0中包含的主題
| 功能 | 涉及的章節 | 主題 |
|---|---|---|
| 靜態匯入 | 第2章 | 簡化了控制臺的使用 |
| 內插字串 | 第2章 | 向用戶顯示輸出 |
| 運算式體成員 | 第5章 | 定義只讀屬性 |
7.C#7.0
C#7.0是在2017年3月發布的,重點是添加功能語言特性,如元組和模式匹配,還對語言做了細微改進,其中包含的主題如表2.6所示,
表2.6 C#7.0中包含的主題
| 功能 | 涉及的章節 | 主題 |
|---|---|---|
| 二進制字面量和數字分隔符 | 第2章 | 存盤整數 |
| 模式匹配 | 第3章 | 利用if陳述句進行模式匹配 |
| out變數 | 第5章 | 控制引數的傳遞方式 |
| 元組 | 第5章 | 將多個值于元組組合在一起 |
| 區域函式 | 第6章 | 定義區域函式 |
8C#7.1
C#7.1是在2017年8月發布的,重點是對語言做了細微改進,其中包含的主題如表2.7所示,
表2.7 C#7.1中包含的主題
| 功能 | 涉及的章節 | 主題 |
|---|---|---|
| 默認字面量運算式 | 第5章 | 使用默認字面量設定欄位 |
| 推斷元組元素的名稱 | 第5章 | 推斷元組名稱 |
| async Main | 第13章 | 改進對控制臺應用程式的回應 |
9.C#7.2
C#7.2是在2017年11月發布的,重點是對語言做了席位改進,其中包含的主題如表2.8所示,
表2.8 C#7.2中包含的主題
| 功能 | 涉及的章節 | 主題 |
|---|---|---|
| 數字字面量中的前導下劃線 | 第2章 | 存盤整數 |
| 非追蹤的命名引數 | 第5章 | 可選引數和命名引數 |
| 私有保護訪問修飾符 | 第5章 | 理解訪問修飾符 |
| 可以使用元組型別測驗==和!= | 第5章 | 比較元組 |
10.C#7.3
C#7.3于2018年5月發布,主要關注性能導向型的安全代碼,并且改進了ref變數、指標和stackalloc,這些都是高級功能,對于大多數開發人員來說很少使用,因此不涉及它們,
如果感興趣可以通過C#7.3網址訪問,
11.C#8.0
C#8.0于2019年9月發布,主要關注與空處理相關的語言的重大變化,其中包含的主題如表2.9所示,
表2.9 C#8.0中包含的主題
| 功能 | 涉及的章節 | 主題 |
|---|---|---|
| 可空參考型別 | 第2章 | 使參考型別為空 |
| switch運算式 | 第3章 | 使用switch運算式簡化switch陳述句 |
| 默認的介面方法 | 第6章 | 了解默認的介面方法 |
2.1發現C#編譯器版本
在C#7.x中,微軟決定加快語言的發布節奏-發布次要版本號,也成為點發布,
.NET 語言編譯器(對于C#、Visual Basic和F#也稱為Roslyn)是作為.NET Core SDK的一部分發布的,要使用特定版本的C#,就必須至少安裝對應版本的.NET Core SDK, 如表2.10所示,
表2.10 不同C#版本對應的.NET Core SDK版本
| .NET Core SDK版本 | Roslyn 版本 | C#版本 |
|---|---|---|
| 1.04 | 2.0~2.2 | 7.0 |
| 1.1.4 | 2.3和2.4 | 7.1 |
| 2.1.2 | 2.6和2.7 | 7.2 |
| 2.2.200 | 2.8~2.10 | 7.3 |
| 3.0 | 3.0~3.3 | 8.0 |
通過以下鏈接查看版本串列,
查看可用的C#編譯器版本:
1)啟動Visual Studio Code,
2)導航到View|Terminal,
3)要確定可以使用哪個版本的.net Core SDK,輸入
dotnet --version
2.1.3啟用特定的語言版本編譯器
如果要使用特定的版本,就需要在專案檔案中添加配置元素,比如微軟發布了C# 8.1的編譯器,并且希望使用C#8.1的新語言特性,那就必須在專案中編輯.csproj并添加
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<LangVersion>8.1</LangVersion>
</PropertyGroup>
</Project>
2.2)理解C#的基礎知識,
為了學習C#,你需要創建一些簡單的應用程式,為了避免過快地提供過多的資訊,第一大章主要使用最簡單的程式型別:控制臺應用程式,本章將創建多個控制臺應用程式,每個控制臺程式顯示C#語言的一個特性,執行以下步驟:
(1)如果已經完成了第一章,那么我們在Code檔案夾下由Chapter01子檔案夾,如果沒用,需要創建Code檔案夾,
(2)創建名為Chapter02的子檔案夾,在其中再創建名為Basics的子檔案夾,
(3)啟動Visual Studio Code 并打開Chapter02/Basics檔案夾
(4)在Visual Studio Code 中導航到View|Terminal,輸入以下命令:
dotnet new console
(5)在資源管理器中單擊Program.cs檔案,然后單擊右下角的Yes以添加缺少的必須資產,
2.2.1)了解C#語法,
C#語法包括陳述句和塊,要記錄代碼,可以使用注釋,
1.陳述句
在英語中,人們使用句點來表示句子的結束,句子可以由多個單詞和短語組成,單詞的順序是語法的一部分,例如在英陳述句子the black cat中,形容詞black在名詞cat之前;而在法語中,含義相同的句子為le chat noir,形容詞noir跟在名詞chat的后面,從這里可以看出,單詞的順序很重要,
C#用分號表示陳述句的結束,C#陳述句可以由多個變數和運算式組成,例如,在下面的C#陳述句中,totalPrice是變數,而subtotal+salesTax是運算式,
var totalPrice=subtotal+salesTax;
以上運算式由一名為sybtotal的運算元、運算子+和另一個名為salesTax的操作陣列成,運算元和運算子的順序很重要,
2.注釋
在撰寫代碼時,可以使用雙斜杠//添加注釋以解釋代碼,通過插入//,編譯器將忽略//后面的所有內容,直到行尾,如下所示:
// sales tax must be added to the subtotal
var totalPrice = subtotal+ salesTax;
如果按Ctrl+K+C組合鍵來添加注釋或按Ctrk+K+U組合鍵來洗掉注釋,那么Visual Studio Code 將在當前選中行的開頭添加或洗掉注釋用的雙斜杠,在macOS中,對應的方法時按Cmd鍵二不是Ctrl鍵,
要撰寫多行注釋,請在注釋的開頭使用/,在結尾使用/,如下所示:
/*
The is a multi-line
comment.
*/
3.塊
C#使用花括號{}表示代碼塊(簡稱塊),塊以宣告開頭,以指示正在定義什么,例如,塊可以定義名稱空間、類、方法或陳述句,稍后詳細介紹,
在當前專案中,請注意C#語法是使用dotnet CLI工具撰寫的,在專案模板的陳述句中添加一些注釋,如下所示:
using System;
namespace Basics
{
class Program
{
static void Main(string[] args)
{
// the start of a block
Console.Writeline("Hello World!"); //a statement
// the end of a block
}
}
}
2.2.2) 了解C#詞匯表
C#詞匯表由關鍵字、符號字符和型別組成,
我們看到一些預定義的保留關鍵字包括using、namespace、class、static、int、string、double、bool、if、switch、break、while、do、for和foreach,符號字符可能包括"、'、+、-、*、/、%、@和$,
默認情況下,Visual Stuodio Code以藍色顯示C#關鍵字,以便于其他代碼區分開來,Visual Studio Code 允許自定義配色方案,執行以下步驟:
(1)在Visual Studio Code中導航到Code|Preferences|color Theme,
(2)選擇一種顏色主題,作為參考,
還有一些其他的背景關系關鍵字,他們只在特定的背景關系中具有特定的含義,然而,這仍然意味著C#語言中只有大概100個實際的C#關鍵字,
英語有超過250 000個不同的單詞,那么C#怎么可能只有大概100個關鍵字呢?此外,如果C#僅為英語的0.04%,那么為什么C#會如此難學呢?
人類語言和編程語言之間的關鍵區別是:開發人員需要能夠定義具有新含義的新“單詞”,除了C#語言中的大約100個關鍵詞之外,本書還將介紹其他開發人員定義的數十萬個“單詞”中的一些,你將學習如何 定義自己的“單詞”,
PS:全世界的程式員都必須學習英語,因為大多數編程語言使用的都是英語單詞,比如Namespace和CLass,有些編程語言使用其他人類語言,如阿拉伯語,但他們很少見,
1.如何撰寫正確的代碼
像記事本這樣的純文本編輯器并不能幫助你寫出正確的英語,同樣,記事本也不能幫助寫出正確的C#代碼,
微軟的Word軟體可以幫助你寫英語,Word軟體會使用紅色波浪線來強調拼寫錯誤,比如icecream應該是ice-cream或ice cream;而用藍色波浪線強調語法錯誤,比如句子應該使用大寫的首字母,
類似的,Visual Studio Code 的C#擴展可通過突出顯示拼寫錯誤(比如方法名WriteLine中的L應該大寫)和語法錯誤(陳述句必須分號結尾)來幫助你撰寫C#代碼,
C#擴展不斷地監視輸入的內容,并通過彩色的波浪線高亮顯示問題來提供反饋,這與Word軟體類似,
下面看看具體是如何運作的,
(1)在Program.cs中,將WriteLine方法中的L改為小寫,
(2)洗掉陳述句末尾的分號,
(3)導航到View|Problems,注意紅色的波浪線出現在錯誤代碼下方,具體細節顯示在Problems網格中,
(4)修復編碼錯誤,
2.動詞表示方法
在英語中,動詞時動作或行動,例如run和Jump,在C#中,動作或行動被稱為方法,C#由成千上萬個方法可用,在英語中,動詞的寫法取決于動作發生的事件,例如,jump的過去進行時是was jumping ,現在時是jumps,過去時是jumped,未來時是will jump,
在C#中,像WriteLine這樣的方法會根據操作的細節改變呼叫或執行的方式,這稱為多載,第5章詳細討論這個問題,但現在,考慮以下示例:
Console.WriteLine();
Console.WriteLine("Hello Ahmed");
Console.WriteLine("Temperature on {0:D} is {1}° C.",DateTime.Today,23.4);
另一個不同的類比是:有些單詞的拼寫相同,但根據背景關系有不同的含義,
3.名詞表示型別、欄位和變數
在英語中,名詞是指事物的名稱,例如Fido是一只狗的名字,
在C#中,等價物是型別、欄位和變數,例如,Animal和Car是型別;也就是說它們是用來對事物進行分類的名詞,Head和Engine是欄位,他們是屬于Animal和Car的名詞,Fido和Bob是變數,也就是說,它們是指代特定事物的名詞,
C#由成千上萬種可用的型別,但是注意,這里并沒有說“C#中由成千上萬中型別”,這種差別很細微,但很重要,C#語言只有一些型別關鍵字,比如string和int,嚴格來說C#沒用定義任何型別,類似于string的關鍵字是別名,他們表示運行在C#平臺所提供的型別,
你要知道,C#不能單獨存在;畢竟,C#是一種運行在不同.NET變體上的語言,理論上,可以為C#撰寫使用不同平臺和底層型別的編譯器,實際上,C#的平臺是.NET,.NET為C#提供了成千上萬種型別,包括System.Int32以及許多更復雜的型別,如System.XmlLinq.XDocument.
注意,術語type(型別)和class(類)很容易混淆.你有沒有玩過室內游戲《二十個問題》?在這個游戲中,任何東西都可以歸類為動物、素菜或者礦物.在C#中,每種型別都可以歸類為類、結構、列舉、介面或者委托,C#關鍵字string 是類,而int是結構體,因此最好使用術語type指代它們兩者,
4.揭示C#詞匯表的范圍
我們知道C#中大約100個關鍵字,但是由多少型別呢,我們撰寫以下代碼以便找出簡單的控制臺應用程式中有但是型別(及方法)可用于C#,
現在不用擔心代碼是如何作業的,這里使用了一種叫做反射的技術,執行以下步驟,
(1)首先在Program.cs中頂部添加以下代碼:
using System.Linq;
using System.Reflection;
(2)在Main方法內洗掉用于寫入Hello World!的陳述句,并將它們替換為以下代碼:
static void Main(string[] args)
{
foreach(var r in Assembly.GetEntryAssembly().GetReferencedAssemblies())
{
var a=Assembly.Load(new AssemblyName(r.FullName));
int methodCount=0;
foreach(var t in a.DefinedTypes)
{
methodCount+=t.GetMethods().Count();
}
Console.WriteLine("{0:N0} types with {1:N0} methods in {2} assembly.",arg0:a.DefinedTypes,arg1:methodCount,arg2:r.Name);
}
}
(5)運行上述命令后,你就能看到在最簡單的應用程式中可用的型別和方法的實際數量,這里顯示的型別和方法的數量可能會根據使用的作業系統而有所不同,
PS D:\Code\Chapter02\Basics> dotnet run
System.RuntimeType[] types with 325 methods in System.Runtime assembly.
System.RuntimeType[] types with 1,068 methods in System.Linq assembly.
System.RuntimeType[] types with 638 methods in System.Console assembly.
(6)在Main方法的頂部添加陳述句以宣告一些變數,如下所示:
static void Main(string[] args)
{
System.Data.DataSet dataSet;
System.Net.Http.HttpClient client;
foreach(var r in Assembly.GetEntryAssembly().GetReferencedAssemblies())
{
var a=Assembly.Load(new AssemblyName(r.FullName));
int methodCount=0;
foreach(var t in a.DefinedTypes)
{
methodCount+=t.GetMethods().Count();
}
Console.WriteLine("{0:N0} types with {1:N0} methods in {2} assembly.",arg0:a.DefinedTypes,arg1:methodCount,arg2:r.Name);
}
}
System.RuntimeType[] types with 325 methods in System.Runtime assembly.
System.RuntimeType[] types with 6,763 methods in System.Data.Common assembly.
System.RuntimeType[] types with 4,167 methods in System.Net.Http assembly.
System.RuntimeType[] types with 1,068 methods in System.Linq assembly.
System.RuntimeType[] types with 638 methods in System.Console assembly.
通過宣告要在其他程式集中使用型別的變數,應用程式將加載這些程式集,從而允許代碼查看其中的所有型別和方法,編譯器會警告存在未使用的變數,但這不會 阻止代碼的運行,
現在你可以更好的理解為什么學習C#是一大挑戰,因為有太多的型別和方法需要學習,方法知識型別可以擁有的成員的類別,而其他程式員正在不斷地定義新成員!
2.3)使用變數
所有應用程式都要處理資料,資料都是先輸入,在處理,最后輸出,資料通常來自檔案、資料庫或用戶輸入,可以臨時放入變數中,這些變數存盤在運行程式的記憶體中,當程式結束時,記憶體中的資料會丟失,資料通常輸出到檔案和資料庫中,抑或輸出到螢屏或列印機,當使用變數時,首先應該考慮它在記憶體中占用了多少空間,其次考慮它的處理速度有多塊,
變數可通過選擇合適的型別來控制,可以將簡單的常見型別(如int和double)視為不同大小的存盤盒,其中較小的存盤和占用的記憶體少,但處理速度可能沒用那么快;例如,在64位系統中,添加16位數字的速度,可能不如添加64位數字的速度快,這些盒子有的可能堆放在附近,有的可能被扔到更遠的一大堆盒子里,
2.3.1 命名和賦值
事物都有命名約定,最好遵循這些約定,如表2.12所示,
表2.12命名約定
| 命名約定 | 示例 | 適用場合 |
|---|---|---|
| 駝峰樣式 | Cost、orderDetail、dateOfBirth | 區域變數、私有欄位 |
| 標題樣式 | String、Int32、Cost、DateOfBirth、Run | 型別、非私有欄位以及其他成員(如方法) |
遵循一組一致的命名約定,將使代碼更容易被其他開發人員理解以及將來的自己理解,
以下代碼塊顯示了一個宣告已命名的區域變數并使用=符號為之賦值的示例,注意,可以使用C#6.0中引入的關鍵字nameof來輸出變數的名稱,
double heightInMetres=1.88;
Console.WriteLine($"The variable {nameof(heightInMetres)} has the value {heightInMetres}.");
字面值
在給變數賦值時,賦予的經常(但不總是)是字面值,什么是字面值呢?字面值是表示固定值的符號,資料型別的字面值有不同的表示法,接下來的內容中包含了使用字面符號為變數賦值的示例,
2.3.2 存盤文本
對于一些文本,比如單個字母(如A),可存盤為char型別,并在字面值的兩邊使用單引號來賦值,也可以直接賦予函式呼叫的回傳值,如下所示:
char letter='A';
char dight='1';
char symbol='$';
char userChoice=GetKeystroke();
private static char GetKeystroke()
{
return 'S';
}
//對于另外一些文本,比如多個字母(如Bob),可存盤為字串型別,并在字面值的兩邊使用雙引號進行賦值,同時可也直接賦予為函式呼叫的回傳值,如下所示:
string firstNmae="Bob";
=string lastName="Smith";
string phoneNumber="(215) 555-4256";
string address=GetAddressFromDatabase(id:563);
private static string GetAddressFromDatabase(int id)
{
return "Beijing ChangPing Beiqijia";
}
理解逐字字串
在字串變數中存盤文本時,可以包括轉義序列,轉義序列使用反斜杠表示特殊字符,如制表符和新行,如下所示:
string fullNameWithTabSeparator="Bob\tSmith";
//但是如果要將路徑存盤到檔案中,并且路徑中有檔案夾的名稱以t開頭,如下所示:
string filePaht="C:\televisions\sony\bravia.txt";
//編譯器就會把\t轉換成制表符,這顯然是錯誤的,解決辦法時逐字串必須加上@符號作為前綴,如下所示:
string filePathTwo=@"c:\televisions\sony\bravia.txt";
下面進行總結,
1)字面字串:用雙引號括起來的一些字符,它們可以使用轉義字符\t作為制表符
2)逐字字串:以@為前綴的字面字串,以禁用轉義字符,因此反斜杠就是反斜杠,
3)內插字串:以$為前綴的字面字串,以支持嵌入式的格式化變數,
2.3.3 存盤數字
數字時細微進行算術計算的資料,例如,電話號碼不是數字,要決定是否應該將變數存盤為數字,請考慮是需要對數字執行算術運算,還是資料應包含圓括號或連字符等非數字字符,以便將數字格式化為(414)555-1234. 在本例中,數字是字符序列,因此應該存盤為字串,
數字可以是自然數,如42,用于計數;它們也可以是負數,如-42(也稱之為整數);或者,它們可以是實數,例如3.9(帶小數部分),在計算中稱為單精度浮點數或雙精度浮點數,
下面探討數字,執行以下步驟:
(1)在Chapter02檔案夾中創建一個名為Numbers的新檔案夾,
(2)在Visual Studio Code 中打開Numbers檔案夾,
(3)在終端使用dotnet new console命令創建一個新的控制臺應用程式,
(4)在Main方法內部輸入以下陳述句,以使用不同的資料型別宣告一些數字變數:
uint naturalNumber=23;
int integerNumber=-23;
float realNumber=2.3F;
double anotherRealNumber=2.3;
1.存盤整數
計算機把所有東西都存盤為位,位的值不是0就是1,這就是所謂的二進制數字系統,人類使用的是十進制數字系統,
十進制數字系統也稱為以10為基數的系統,但一些其他的數字基數系統在科學、工程和計算領域也很受歡迎,二進制數字系統以2為基數,也就是說只有兩個基數:0和1,
? ####表2.13計算機如何存盤數字10
| 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
|---|---|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 |
十進制數字10在二進制中表示為00001010,
C#7.0及更高版本中的兩處改進是使用下劃線_作為數字分隔符以及支持二進制字面值,可以在數字字面值(包括十進制、二進制和十六進制表示法)中插入下劃線,以提高可讀性,例如可以將十進制數字100 000 寫成1_000_000,
二進制記數法以2為基數,只使用1和0,數字字面值的開頭是0b,十六進制計數法以16為基數,使用的是09和AF,數字字面值的開頭是0x,
下面在Main方法的底部輸出如下陳述句,使用下劃線分割符宣告一些數字變數:
int decimalNotation=2_000_000;
int binaryNotation=0b_0001_1110_1000_0100_1000_0000;
int hexadecimalNotation=0x_001E_8480;
Console.WriteLine($"{decimalNotation==binaryNotation}");
Console.WriteLine($"{decimalNotation==hexadecimalNotation}");
結果表明三個數字是相同的,輸出結果是:
True
True
計算機總是可以使用int型別以及其他兄弟型別(如long和short)精確地表示整數,
2.存盤實數
計算機并不能總是精確地表示浮點數,float和double型別使用單精度和雙精度浮點數存盤實數,
大多數編程語言都實作了IEEE浮點運算標準,IEEE754是電氣和電子工程師協會(IEEE)與1985年建立的浮點運算技術標準,
浮點數入門教程,
表2.14顯示了計算機如何用二進制計數法表示數字12.75,
2.14 計算機如何存盤數字12.75
| 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 | . | 1/2 | 1/4 | 1/8 | 1/16 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | . | 1 | 1 | 0 | 0 |
所以十進制數字12.75 在二進制中表示為00001100.1100,可以看到,數字12.75可以用位精確的表示,然而,有些數字不能用位精確的表示,稍后探討這個問題,
3.撰寫代碼以探索數字的大小
C#提供的名為sizeof()的運算子可回傳型別在記憶體中使用的位元組數,有些型別又名為MinValue和MaxValue的成員,它們回傳可以存盤在型別變數中的最小值和最大值,現在我們將使用這些特性創建一個控制臺應用程式來研究數字型別,
(1)在Main方法的內部輸入如下陳述句,顯示三種數字資料型別的大小:
Console.WriteLine($"int uses {sizeof(int)} bytes and can store numbers in the range {int.MinValue:N0} to {int.MaxValue:N0}.");
Console.WriteLine($"double uses{sizeof(double)} bytes and can store number in the range{double.MinValue:N0} to {double.MaxValue:N0}.");
Console.WriteLine($"decimal uses {sizeof(decimal)} bytes and can store number in the range{decimal.MinValue:N0} to {decimal.MaxValue:N0}.");
int變數使用4位元組的記憶體,可以存盤正數和負數,double變數使用8個位元組的記憶體,因而可以存盤更大的值!decimal變數使用16位元組的記憶體,雖然可以存盤較大的數字,但卻不像double型別那么大,我們比較double和decimal型別,
現在來撰寫一段代碼比較double和decimal的值,盡管代碼并不難理解,但我們現在不要擔心語法,
(1)在前面的陳述句中,宣告兩個double 變數,將它們相加并與預期結果進行比較,然后將結果寫入控制臺,如下所示:
double a=0.1;
double b=0.2;
if(a+b==0.3)
{
Console.WriteLine($"{a}+{b} equals 0.3");
}
else
{
Console.WriteLine($"{a}+{b} does Not equal 0.3");
}
(2)運行控制臺應用程式并查看結果,如下所示:
0.1+0.2 does NOT equal 0.3
double型別不能保證值是精確的,因為有些數字不能表示為浮點數,
關于為什么0.1不存在于浮點數中的原因,點擊這個地址,或者看我下面的分析,
Why 0.1 Does Not Exist In Floating-Point - Exploring Binary
根據經驗,應該只在準確性不重要時使用double型別,特別是在比較兩個數字的相等性時,例如,當測量一個人的身高時,
上述問題可以通過計算機如何存盤數字0.1或0.1的倍數來說明,要用二進制表示0.1,計算機需要在1/16列存盤1、在1/32列存盤1、在1/256列存盤1、在1/512列存盤1,以此類推,于是小數中的數字0.1是0.00011001100110011……
表2.15 數字0.1的存盤
| 4 | 2 | 1 | . | 1/2 | 1/4 | 1/8 | 1/16 | 1/32 | 1/64 | 1/128 | 1/256 | 1/512 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 0 | . | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 |
永遠不要使用==比較兩個double值,在第一次海灣戰爭期間,美國愛國者導彈系統在計算時使用了double值,這種不精確性導致導彈無法跟蹤和攔截來襲的伊拉克飛毛腿導彈,
(3)復制并粘貼之前撰寫的陳述句(使用了double變數),
(4)修改陳述句,使用decimal并將變數重命名為c和d,如下所示:
decimal c=0.1M;
decimal d=0.2M;
if(c+d==0.3M)
{
Console.WriteLine($"{c}+{d} equal 0.3");
}
else
{
Console.WriteLine($"{c}+{d} does NOT equal 0.3");
}
(5)運行控制臺應用程式并查看結果,輸出如下所示:
0.1+0.2 equals 0.3
decimal型別是精確的,因為這種型別可以將數字存盤為大的整數并移動小數點,例如,可以將0.1存盤為1,然后將小數點左移一位,再如,可以將12.75存盤為1275,然后將小數點左移兩位,
對整數使用int型別存盤,而對不會與其他值比較的實數使用double型別進行存盤,decimal型別適合用于貨幣、CAD繪圖、一般工程學以及任何對實數的準確性要求較高的場合,
double型別有一些有用的特殊值:double.NaN表示不是數字,double.Epsilon是可以存盤在double里的最小正數,double.Infinity意味著無限大的值,
2.3.4 存盤布林值
布林值只能是如下兩個字面值中的一個:true 或false,
2.3.5使用Visual Studio Code 作業區
在創建更多專案之前,下面先討論一下作業區,
盡管可以繼續為每個專案創建和打開單獨的檔案夾,但同時打開多個檔案夾可能很有用,在Visual Studio中,名為作業區的特性可以實作這一點,
下面本章到目前為止創建的兩個專案創建作業區,
(1)在Visual Studio Code 中導航到File|Save Workspace As...,
(2)輸入Chapter02作為作業區的名稱,更改到Chapter02檔案夾,然后單擊Save按鈕
(3)導航到File|Add Folder to Workspace...,
(4)選擇Basics檔案夾,單擊Add按鈕,注意Basics和Numbers檔案夾現在是Chapter02作業區的一部分,
在使用作業區時,在終端輸入命令時要小心,在輸入可能具有破壞性的命令之前,請確保處于正確的檔案夾中!
2.3.6 存盤任何型別的物件
有一種名為object的特殊型別,這種型別可以存盤任何資料,但這種靈活性是以混亂的代碼和可能較差的性能為代價的,由于這兩個原因,你應該盡可能避免使用object型別,
(1)創建一個名為Variables的新檔案夾,并將其添加到Chapter02作業區中,
(2)導航到選單欄的Terminal(不是View下的Terminal)|New Terminal,
(3)選擇Variables專案,
(4)輸入如下用來創建新控制臺應用程式的命令:dotnet new console,
(5)導航到View|Command Palette,
(6)輸入并選擇OmniSharp:Select Project,
(7)選擇Variables專案,在Variables專案中打開Program.cs,
(8)在資源管理器中,在Variables 專案中打開Program.cs
(9)在Main方法中添加宣告陳述句,并通過object型別來使用一些變數,如下所示:
object height= 1.88;
object name="Amir";
Console.WriteLine($"{name}is {height} metres tall.");
int length1=name.Length;
int length2=((string)name).Length;
Console.WriteLine($"{name} has {length2} characters.");
Console.WriteLine("Hello World!");
(10)在終端輸入dotnet run 執行代碼,注意第四條陳述句不能編譯,因為編譯器不知道name變數的資料型別,
(11)將注釋用的雙斜杠加到想要注釋掉的陳述句的開頭,
(12)在終端輸入dotnet run以執行代碼,注意,如果程式員明確告訴編譯器,object變數包含字串,那么編譯器可以訪問字串的長度,如下所示:
Amiris 1.88 metres tall.
Amir has 4 characters.
object型別自從C#的第一個版本就已經可用了,但是C#2.0及其后期版本有了更好的選擇——泛型,參見6章,泛型可提供我們想要的靈活性,但沒有性能開銷,
2.3.7 動態存盤型別
還有一種特殊型別名為dynamic,可用于存盤任何型別的資料,并且靈活性相比object型別更強,代價是性能下降了,dynamic關鍵字是在C#4.0中引入的,但是,與object變數不同的是,存盤在dynamic變數中的值可以在沒用顯示進行強制轉換的情況下呼叫成員,
(1)在Main方法中添加如下陳述句,宣告一個dynamic變數并為它賦予一個字串值:
dynamic anotherName="Ahmed";
(2)添加如下陳述句以獲得這個字串的長度:
int length=anotherName.Length;
dynamic型別存在的限制是,Visual Studio Code 不能顯示只能感知來幫助撰寫代碼,這是因為編譯器在編譯期間不能檢查型別是什么,相反,CLR會在運行期間檢查成員,如果缺少成員,則拋出例外,
例外是指示出錯的一種方式,第3章將詳細介紹它們,并且說明如何處理它們,
2.3.8宣告區域變數
區域變數是在方法中宣告的,它們只在方法執行期間存在,一旦方法回傳,分配給任何區域變數的記憶體都會被釋放,
嚴格地說,值型別都會被釋放,而參考型別必須等待垃圾收集,第6章將介紹值型別和參考型別之間的區別,
指定和推斷區域變數的型別
下面進一步探討使用特定型別宣告的區域變數并使用型別推斷,
(1)在Main方法中輸入如下陳述句,使用特定的型別宣告一些區域變數并賦值:
int population=66_000_000;
double weight=1.88;
decimal price=4.99M;
string fruit="Apples";
char letter='Z';
bool happy=true;
Visual Studio Code 將在每個變數名稱的下方顯示綠色波浪線,以警告這個變數雖然已經分配了,但卻從未使用過它的值,
可以使用Var關鍵字來宣告區域變數,編譯器將從你在賦值運算子=之后賦予的值推斷型別,
沒用小數點的字面數字可推斷為int型別,除非添加L后綴,這種情況下,則會推斷為long型別,
帶有小數點的字面數字可推斷為double型別,除非添加M后綴(在這種情況下,可推斷為decimal型別)或F后綴(在這種情況下,則推斷為float型別),雙引號用來指示字串變數,單引號用來知識char變數,true和false值則被推斷為bool型別,
(2)修改前面的陳述句以使用var 關鍵字,
var population=66_000_000;
var weight=1.88;
var price=4.99M;
var fruit="Apples";
var letter='Z';
var happy=true;
雖然使用var 關鍵字很方便,但有些開發人員卻總是想避免使用它,以便讀者更容易理解代碼中使用的型別,就我個人而言,我只在型別明顯的時候才使用var關鍵字,例如在下面的代碼中,第一條陳述句與第二條陳述句都清楚地說明了變數的型別,但是第一條陳述句明顯更短些,另外,第三條陳述句表述不清楚,所以第四條陳述句更好些,
var xal1=new XmlDocument();
XmlDocument xml2=new XmlDocument();
var file=File.CreateText(@"C:\something.txt");
StreamWriter file2=File.CreateText(@"c:\something.txt");
2.3.9 獲取型別的默認值
除了string之外,大多數基本型別都是值型別,這意味著它們必須有值,可以使用default()運算子確定型別的默認值,
string型別是參考型別,這意味著string變數包含值的記憶體地址而不是值本身,參考型別的變數可以有控制,空值是字面值,便是變數尚未參考任何東西,空值是所有參考型別的默認值,
第6章將介紹更多關于值型別和參考型別的知識,
下面看默認值,
(1)在Main方法中添加如下陳述句以顯示int、bool、DateTime和string型別的默認值:
default(int)=0
default(bool)=False
default(DateTime)=0001/1/1 0:00:00
default(string)=
2.3.10 存盤多個值
當需要存盤同一型別的多個值時,可以宣告陣列,例如,當需要在string陣列中存盤四個名稱時,就可以這樣做,
下面的代碼可用來為存盤四個字串的陣列分配記憶體,首先在索引位置0~3存盤字串值(陣列時從0開始計數的,因此最后一項比陣列長度小1).然后使用for陳述句回圈遍歷陣列中的每一項,詳見第3章,
(1)在Chapter02檔案夾中創建一個名為Arrays的檔案夾,
(2)將Arrays檔案夾添加到Chapter02作業區,
(3)為Arrays專案創建一個新的終端視窗,
(4)在Arrays檔案夾中創建一個新的控制臺應用程式專案,
(5)選擇Arrays作為OmniSharp的當前專案,
(6)在Arrays專案中,在Program.cs的Main方法中添加如下陳述句,以宣告和使用字串值陣列:
string[] names;
names=new string[4];
names[0]="Kate";
names[1]="Jack";
names[2]="Rebecca";
names[3]="Tom";
for(int i=0;i<names.Length;i++)
{
Console.WriteLine(names[i]);
}
(7)運行結果為:
Kate
Jack
Rebecca
Tom
在分配記憶體的時候,陣列的大小總是固定的,因此需要在實體化之前確定陣列要存盤多少項,
陣列對于臨時存盤多個項很有用,但是在動態添加和洗掉項時,集合時更靈活的選擇,現在不需要擔心集合,第8章會討論它們,
2.4處理空值
前面介紹了如何在變數中存盤數字之類的基本值,但是,如果變數沒有值呢?怎么表示呢?C#有空值的概念,空值可以用來指示變數沒有賦值,
2.4.1 使值型別為null
默認情況下,像int和DateTime這樣的值型別必須總是有值,但有時,例如,當讀取存盤在資料庫中允許的空值或缺失值時,允許值型別為null是很方便的,我們稱之為可空值型別,
通過在宣告變數時將問號作為后綴添加到型別中來啟用這一功能,下面來看一個例子,
(1)在Chapter02檔案夾中創建一個名為NullHandling的新檔案夾,
(2)將NullHandling 檔案夾添加到Chapter02作業區,
(3)為NullHandling專案創建一個新的終端視窗,
(4)在NullHandling檔案夾中創建一個新的控制臺應用程式專案,
(5)在NullHandling作為OmniSharp的當前專案,
(6)在NullHandling專案中,在Program.cs的Main方法中添加如下陳述句,以宣告int變數并賦值(包括null);
int thisCannotBeNull=4;
thisCannotBeNull=null; //compile error
int? thisCouldBeNull=null;
Console.WriteLine(thisCouldBeNull);
Console.WriteLine(thisCouldBeNull.GetValueOrDefault());
thisCouldBeNull=7;
Console.WriteLine(thisCouldBeNull);
Console.WriteLine(thisCouldBeNull.GetValueOrDefault());
(7)注釋掉出現編譯錯誤的陳述句,
(8)運行控制臺應用程式并查看結果,輸出以下所示:
0
7
7
第一行是空的,因為輸出的是空值!
理解可空參考型別
在許多編程語言中,空值的使用是如此普遍,以至于許多有經驗的程式員從不懷疑空值的存在,但是在很多情況下,如果不允許變數有空值,就可以撰寫更好、更簡單的代碼,
C#8.0語言中最重要的變化是引入了可空參考型別和不可空參考型別,“但是等一下!”你可能會想,“參考型別以及可以為空了!”
你可能是對的,但是在C#8.0中,可以通過設定檔案級或專案級選項來啟用這一有用的新特性,從而將參考型別配置為不再允許空值,因此這對C#來說一個巨大的變化,所以微軟決定選擇加入這個特性,
這個新的C#語言特性需要幾年的時間才能產生影響,因為有成千上萬的現有庫包和應用程式仍保持舊的行為,甚至微軟也沒有時間在所有的.NET核心包中完全實作這個新特性,在過渡期間,你可以為自己的專案選擇如下幾種方案,
-
保持默認:不需要更改,不支持不可空參考型別,
-
opt-in project,opt-out files:在專案級別啟用這一特性,對于需要與舊行為保持兼容和任何檔案,選擇退出,這是微軟內部使用的方案,同時請更新自己的包以使用這個新特性,
-
Opt-in files:僅為單個檔案啟用這一特性,
2.4.2啟用可空參考型別和不可空參考型別
要在專案級別啟用這一特性,請將以下內容添加到專案檔案中:
<PropertyGroup> <Nullable>enable</Nullable> </PropertyGroup>要在檔案級別禁用這一特性,請在代碼檔案的頂部添加以下內容:
nullable disable
要在檔案級別啟用這一特性,請在代碼檔案的頂部添加以下內容:
nullable enable
2.4.3宣告不可為空的變數和引數
如果啟用了可空參考型別,并希望為參考型別分配空值,那么使用的語法必須與使值型別為null的相同:在型別宣告后面添加?符號,
那么,可空參考型別是如何作業的呢?下面看一個例子,在存盤關于地址的資訊時,可能希望強制存盤街道、城市和地區資訊,但建筑物資訊可以留空(為null),
(1)在NullHadnling.csproj中添加一個元素來再用可空參考型別,如下所示:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>.NETCoreapp3.0</TargetFramework> <Nullable>enable</Nullable> </PropertyGroup> </Project>(2)在Program.cs檔案的頂部添加如下陳述句以啟用可空參考型別:
nullable enable
(3)在Program.cs中,在Program類上方的NullHandling 名稱空間中添加如下陳述句,以宣告包含四個欄位的Address類:
class Address { public string? Building; public string Street; public string City; public string Region; }(4)在Terminal輸入dotnet run 觀察輸出結果,會看到很多警告,
(5)將控制付出值分配給不可空的三個欄位,如下所示:
public string Street=string.Empty; public string City=string.Empty; public string Region=string.Empty;(6)在Main方法中添加如下陳述句以實體化Address并設定其屬性:
var address=new Address(); address.Building=null; address.Street=null; address.City="London"; address.Region=null;(7)在Terminal輸入dotnet run 觀察警告內容,
2.4.4 檢查null
檢查可空參考型別變數或可空值型別變數當前是否包含空值非常重要,因為如果不包含空值,就可能會拋出例外NullReferenceException,從而導致錯誤,應該在使用可控變數之前檢查空值,如下所示:
string thisCouldBeNullStr=string.Empty; if(thisCouldBeNullStr!=null) { int length=thisCouldBeNullStr.Length; }如果試圖使用可能為空的變數的成員,請使用空條件運算子?.,如下所示:
if(thisCouldBeNullStr!=null) { int length=thisCouldBeNullStr.Length; } //如果試圖使用可能為空的變數的成員,請使用空條件運算子?.如下所示: string authorName=null; int x=authorName.Length; int? y=authorName?.Length;有時,你希望為結果分配一個變數或者使用另一個值,比如3(假設變數為null),為此,可以使用空合并運算子??,如下所示:
var result =authorName?.Length ??3; Console.WriteLine(result);這章就寫這么多啦,這也太長了,實在不行了,這一篇就寫了我4個晚上,基礎部分拆成2個把,太長了沒法看啊,
我創建了一個C#相關的交流群,用于分享學習資料和討論問題,歡迎有興趣的小伙伴:QQ群:542633085
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/280475.html
標籤:.NET Core
