C#運算符 內置了豐富的運算符操作類型,使用方便,極大的簡化了編碼,同時還支持多種運算符重載機制,讓自定義的類型也能支持運算符行爲。
01、運算符概覽
運算符分類 | 描述 |
---|---|
數學運算 | 基礎的加減乘除,及++、-- |
賦值運算 | =,及各種複合賦值op= ,x+=20; 等效於x=x+20; |
比較運算 | 比較相等、大小,內置類型大多支持,自定類型需要自己重載運算符才能支持 |
邏輯運算符 | 常用的就是非! 、短路邏輯與&& 、短路邏輯或 ||。 |
位運算 | 二進制位運算,適當使用可極大提高數據處理性能 |
類型相關運算符 | 類型判斷is 、類型轉換as 、typeof ... |
指針操作運算符 | 指針相關運算符:*、&、-> |
其他運算符 | ^ 、.. 範圍運算、nameof 、default (默認值)、await /async ... |
運算符重載 | public static Point operator +(Point p1, Point p2) {} |
隱式轉換 | public static implicit operator int(Point p1){} |
顯示轉換 | public static explicit operator string(Point p){} |
02、運算符彙總🔣
2.1、算數運算
運算符 | 描述 | 示例 |
---|---|---|
+ | 加,字符串加爲字符串連接,推薦字符內插$"{var}" |
|
- | 減,同+ 可用於委託,也可表示負數-100 |
|
* | 乘 | |
/ | 除 | |
% | 取餘數 | 6%3; //=0 |
++ | 自加1,符號在前面的先運算,再賦值,在後面的反之 | x=++y; //y=1+y; x=y; x=y++; //x=y; y=1+y; |
-- | 自減1,同上 |
- 整數除以0會引發 DivideByZeroException,浮點數不會,結果爲無窮
∞/Infinity
。 - 整數運算超出範圍,默認情況下不會引發異常,會被自動截斷(循環),除非使用檢查語句(checked),此時超出範圍會引發OverflowException。而常量表達式默認檢查。
- 浮點數
float
和double
不會出現超出範圍異常問題。 - decimal 類型溢出始終會已發異常 OverflowException,被零除總是引發 DivideByZeroException。
- 整數除以整數,結果依然是整數,向下(截斷)取整。
int a = 3;
int b = a +int.MaxValue; //-2147483646
int c = checked(a + int.MaxValue); //System.OverflowException
2.2、賦值運算
運算符 | 描述 | 示例/備註 |
---|---|---|
= | 賦值運算符,一個神聖的儀式 | |
+= | 加法賦值 | x+=y //x=x+y |
-= | 減法賦值 | x-=y //x=x-y |
*= | 乘法賦值 | x*=y //x=x*y |
/= | 除法賦值 | x/=y //x=x/y |
%= | 取餘賦值 | a%=3 //a=a%3 |
op= | 複合賦值x op = y = x = x op y |
上面這些都是複合賦值 |
= ref | 按引用賦值,可看做是別名,是同一個地址的別名 | ref int b = ref a; |
?? ??= |
Null 合併賦值,爲null 時就用後面的表達式值。 ??= 可用來設置默認值,或爲null 時拋出異常 |
c = b1 ?? 5; name??= "sam"; _= sender ??throw... |
2.3、比較運算
運算符 | 描述 | 示例/備註 |
---|---|---|
== | 相等比較,值類型比較值,應用類型比較地址 | if(x == 0){} |
!= | != 不等於比較,與== 對應 |
if(x != 1){} |
>,< | 大於,小於 | |
>=,<= | 大於等於,小於等於 |
🔸對於相等(==、!=)比較:
- 值類型比較的是數據值是否相等。
- 引用類型
==
比較的是其引用地址(對象),同 Object.ReferenceEquals。 - 字符串比較的字符串值,雖然他是引用類型(一個特殊的引用類型)。
- 用戶定義的 struct 類型默認情況下不支持 == 運算符,需要自己重載運算符。可以用
Equals()
方法,會box裝箱。 - C# 9.0 中的值類型 記錄類型
record struct
支持 == 和 != 運算符,比較的是其內部值。這是因爲record
內部實現了相等的運算符重載,比較了每個屬性的值。 - 委託,如果其內部列表長度相同,且每個位置的元素相同則二者相等。
🔸對於大小比較:
- char 比較的是字符碼值。
2.4、邏輯運算符
運算符 | 描述 | 示例/備註 |
---|---|---|
! | ! 邏輯非運算符,取反,顛掉黑白! |
!false //true |
& | 邏輯與,都爲true 結果爲true ,都會計算,前面false 還是會計算後面的 |
true & false //false |
^ | 邏輯異或,不同則爲ture ,相同false |
true ^ false //true |
| | 邏輯或,只要有一個true ,則爲true ,都會計算 |
true | false //true |
&& | 條件邏輯與,也叫短路運算符,都爲true 才爲true ,先遇到false 就返回了 |
效果同& ,推薦用&& |
|| | 條件邏輯或,也是短路的,只要有true 就返回true ,先遇到true 就返回了 |
效果同"|",推薦用"||" |
?: | 三元表達式:判斷條件 ?條件爲真 :條件爲假 |
age = a > 2 ? 4 : 2 |
- 上面運算符除了
&&
、||
都支持可空類型的bool?
,只是結果可能爲null
。 - 一般邏輯與、邏輯或都用支持短路的
&&
、||
,可提前返回,減少運算。
void Main()
{
Console.WriteLine(F()&T()); // f t False 都運算了
Console.WriteLine(F()&&T()); // f False 發現fasle就返回了,後面就不運算了
Console.WriteLine(T()|F()); // t f True 都運算了
Console.WriteLine(T()||F()); // t True 發現true就返回true了,後面就不運算了
Console.WriteLine(true^true); // False
Console.WriteLine(true^false); // True 不同則爲true
Console.WriteLine(false^false); // False
}
bool T()
{
Console.Write("t ");
return true;
}
bool F()
{
Console.Write("f ");
return false;
}
2.5、位運算
運算符 | 描述 | 示例/備註 |
---|---|---|
~ | 按位求補,每位(所有位)反轉 申明終結器(析構函數), ~className(){} |
uint b = ~a; |
<< | 左移位,向左移動右側操作數定義的位數 | uint y = 1 << 5; ,//32(1*2的5次方) |
>> | 右移位,向右移動右側操作數定義的位數 | 8>>2 //2(8除以2的2次方) |
>>> | 無符號右移 | 0b100>>>2 //1 |
& | 邏輯與,計算每一位的邏輯與 | 0b100 & 0b111 //100 |
| | 邏輯或,計算每一位的邏輯或 | 0b100 | 0b111 //111 |
^ | 邏輯異或,計算每一位的邏輯異或 | 0b100 ^ 0b111 //11 |
左移位
<< n
,相當於乘以2的n次方,同樣的,右移位意味着除以2的n次方。移位運算比乘法、除法運算效率要高很多!所以在合適的場景可考慮使用位運算。
Console.WriteLine(123456*Math.Pow(2,6)); //7901184
Console.WriteLine(123456<<6); //7901184
用左移位給Flags
枚舉賦值:
[Flags]
public enum Interest
{
None = 0, //0
Study = 1, //1
Singing = 1 << 1, //2
Dancing = 1 << 2, //4
Sports = 1 << 3, //8
Games = 1 << 4, //16
}
2.6、類型、成員運算符
運算符 | 描述 | 示例/備註 |
---|---|---|
Object. prop |
成員訪問表達式,命名空間、類、成員訪問 | user.name |
?. 、?[] |
Null 條件運算符,僅非null 時才繼續,否則返回null |
簡化null 判斷,arr?[1]??1 |
new | 🔸 new Object() 創建對象實例,C#9可省略後面的類型。🔸 重新實現父類的方法、屬性。 🔸 創建匿名實例 new {n=1} 。 |
User u1 = new(); var a = new {Name="sam"} |
is (E is T) |
類型兼容性檢查,如果兼容則爲True,否則false。支持可空泛型、裝箱的兼容轉換。is 支持各種模式匹配,參考《C#模式匹配》 |
if(obj is int i) //True |
as (E as T) |
顯式轉換類型,如果轉換失敗則返回null ,不會報錯。只支持引用類型、可空類型、裝箱拆箱。 |
int b =i as User |
(T)E | 強制類型轉換,如果不匹配會引發異常 | var b = (int)b1 |
typeof |
獲取類型System.Type,參數只能是類型、T,不支持dynamic、string?,可用Object.GetType代替。 | typeof(int?) b1?.GetType() |
sizeof |
獲取類型所需大小(字節數),只能用於值類型、不安全類型 | sizeof(int) //4 |
() |
🔸 調用表達式,調用方法,執行委託/方法,new構造函數。 🔸創建一個Tuple 🔸 強制類型轉換 (Type)v ,轉換失敗拋出異常 |
🔸Foo(); 🔸 var b = (1,2,"ss"); 🔸 var b = (int)a |
[] |
🔸 數組下標,必須爲整數 🔸 索引器,可任意類型,如Dictionary <TKey,TValue> 🔸 特性Attribute的使用 |
🔸arr[0] =1; 🔸 dict["one"] = 1; 🔸 [Serializable] |
2.7、其他運算符
運算符 | 描述 | 示例/備註 |
---|---|---|
[value]! |
在表達式後面,告訴編譯器不會爲空,不要在提示告警了 | p.Message!.Length |
^ |
範圍運算符,從末尾開始索引,倒數 | arr[^1] // 倒數第一個 |
.. |
範圍運算符,.NET Standard 2.1/C#8,表達式 a..b 屬於 System.Range 類型,表示a 到b 的範圍。a.. 等效於 a..^0 |
arr[1..3] // 索引1到3的值 |
with |
C#9非破壞性創建+修改,創建一個副本,並修改特定屬性,只支持record 、struct 類型。 |
n = o with { age = 2 } |
nameof |
獲取變量、類型、成員的名稱作爲字符串常量,編譯時獲取 | nameof(numbers.Add) |
stackalloc |
堆棧上分配內存塊,用於System.Span<T> 、unsafe 代碼 |
arr = stackalloc int[10] |
(p) => {} |
Lambda表達式創建匿名函數,左側爲參數,右側爲表達式 | var f = ()=>x=100; |
default | 默認值,獲取類型的默認值,T initialValue = default 數值類型默認0、false,引用類型默認null |
default(int) //0 int x = default; |
:: |
命名空間別名的使用,using t = System.Text; t::Json. |
|
await、async | 異步編程,async 標記方法爲異步方法 |
|
&、* | 指針操作:* 獲取指針指向的變量,& 獲取變量地址 int n = 100; int* n1 = &n; int n2 = *n1; |
在unsafe 上下文中運行 |
03、運算符優先級
運算符優先級,最簡單的方法就是加括號()
。
04、operator 運算符重載
上面這麼多運算符,有些是可以自定義重載實現的,讓自定義的類型(class、struct)也支持運算符操作,比如讓自定的類型支持相加、相減、大小比較。
void Main()
{
var p1 =new Point(1,2);
var p2 =new Point(3,4);
var p = p1+p2; //4,6
p +=p; //8,12
}
public class Point //struct也是一樣的
{
public int X { get; set; }
public int Y { get; set; }
public Point(int x, int y)
{
X = x;
Y = y;
}
public static Point operator +(Point p1, Point p2)
{
return new Point(p1.X + p2.X, p1.Y + p2.Y);
}
}
上面的示例是重載實現+
運算符,實現運算符重載的基本語法:public static T operator +(T arg)
- 方法必須是
public
+static
的,然後operator
關鍵字,“方法名”就是運算符,後面就是正常的方法參數。 - 所以不難看出重載的運算符本質上就是靜態方法調用,下面是上面示例的
IL
代碼。
4.1、可重載的運算符
運算符 | 說明 |
---|---|
+x, -x, !x, ~x, ++, --, true, false | true和 false 運算符必須一起重載(比較少見)。 |
x + y, x - y, x * y, x / y, x % y, | |
x & y, x | y, x ^ y, | |
x << y, x >> y, x >>> y | |
x == y, x != y, x < y, x > y, x <= y, x >= y | 必須按如下方式成對重載: == 和 !=、 < 、 ><= 和 >= |
還有些不可重載運算符
- 成對重載:有些運算符的重載要求需成對實現,如
==
和!=
、<
和>
、<=
和>=
。 - Equals 和 GetHashCode:如果重載了相等運算符,則也應該重載Equals、GetHashCode方法才更完整、合理。
- IComparable:如果重載了大小比較運算符,則應當實現
IComparable
接口。
4.2、顯式和隱式轉換運算
隱式轉換 顧名思義就是自動匹配類型並完成轉換,顯示類型轉換就是要指定轉換的類型T(T)value
。
- 隱式轉換多用於無損、必定轉換成功的場景,如
int
轉double
、long
,short
轉換爲int
。 - 強制轉換則可能存在信息損失,或轉換可能存在不確定性,如上面示例的相反方向轉換。
void Main()
{
int a =100;
float f = a; //int隱式轉換爲了float
char c1 = a; //報錯,不支持隱式轉換
char c2 =(char)a; //強制轉換
Console.WriteLine(c2); //d
}
自定義實現隱式、顯示類型轉換主要是下面兩個關鍵字:
- implicit (/ɪmˈplɪsɪt/ 隱式),隱式類型轉換運算,用於無損數據類型轉換,一般用於“小”數據類型轉換爲“大”數據類型,如
int
轉換爲long
、float
。 - explicit (/ɪkˈsplɪsɪt/ 顯示),顯示類型轉換運算,可能會導致數據丟失的轉換。
void Main()
{
Point p1 = 2; //2,2
int a = p1; //2
string pstr = (string)p1; //(2,2)
}
public class Point
{
public int X { get; set; }
public int Y { get; set; }
public Point(int x, int y)
{
X = x;
Y = y;
}
public static implicit operator int(Point p1) //Point隱式轉換爲int
{
return (p1.X + p1.Y) / 2;
}
public static implicit operator Point(int v) //int隱式轉換爲Point
{
return new Point(v, v);
}
public static explicit operator string(Point p)//Point顯示轉換爲string
{
return $"({p.X},{p.Y})";
}
}
📢 一般建議強相關的類型可實現顯示、隱式轉換,對於弱相關類型的轉換則建議編寫專門的轉換函數,如果
ToXXX
,Parse
方法。
參考資料
©️版權申明:版權所有@安木夕,本文內容僅供學習,歡迎指正、交流,轉載請註明出處!原文編輯地址-語雀