C#.Net築基-運算符🔣Family

image.png

C#運算符 內置了豐富的運算符操作類型,使用方便,極大的簡化了編碼,同時還支持多種運算符重載機制,讓自定義的類型也能支持運算符行爲。

01、運算符概覽

運算符分類 描述
數學運算 基礎的加減乘除,及++、--
賦值運算 =,及各種複合賦值op=x+=20; 等效於x=x+20;
比較運算 比較相等、大小,內置類型大多支持,自定類型需要自己重載運算符才能支持
邏輯運算符 常用的就是非!、短路邏輯與&&、短路邏輯或 ||。
位運算 二進制位運算,適當使用可極大提高數據處理性能
類型相關運算符 類型判斷is、類型轉換astypeof...
指針操作運算符 指針相關運算符:*、&、->
其他運算符 ^..範圍運算、nameofdefault(默認值)、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。而常量表達式默認檢查。
  • 浮點數floatdouble不會出現超出範圍異常問題。
  • 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;
}

image.png

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 類型,表示ab的範圍。a.. 等效於 a..^0 arr[1..3] // 索引1到3的值
with C#9非破壞性創建+修改,創建一個副本,並修改特定屬性,只支持recordstruct類型。 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、運算符優先級

運算符優先級,最簡單的方法就是加括號()

image.png


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代碼。

image.png

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

  • 隱式轉換多用於無損、必定轉換成功的場景,如intdoublelongshort 轉換爲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轉換爲longfloat
  • 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})";
	}
}

📢 一般建議強相關的類型可實現顯示、隱式轉換,對於弱相關類型的轉換則建議編寫專門的轉換函數,如果ToXXXParse方法。


參考資料


©️版權申明:版權所有@安木夕,本文內容僅供學習,歡迎指正、交流,轉載請註明出處!原文編輯地址-語雀

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章