C#基礎操作符詳解(上)
- 2019 年 10 月 27 日
- 筆記
本節內容:
1.操作符概覽;
2.操作符的本質;
3.操作符與運算順序
4.操作符詳解。
1.操作符概覽:
操作符(Operator)也譯為”運算符”
操作符是用來操作數據的,被操作符操作的數據稱為操作數(Operand)
表格從上往下優先順序遞減,同一行運算符的優先順序一樣一般按從左到右算,
“=”賦值操作符,是先運算右邊的值再運算左邊的值,所以是最後運算的。
2.操作符的本質
①操作符的本質是函數(即演算法)的”簡記法”
假如沒有發明”+”只有Add函數,算式3+4+5將可以寫成Add(Add(3,4),5)
假設沒有發明”*”只有Mul函數,那麼算式3+4*5將只能寫成Add(3,Mul(4,5))
可見有操作符可讀性更強。
②操作符不能脫離與它關聯的數據類型(比如double數據類型的除法與int類型的除法相同數據結果不同)
可以說操作符就是與固定數據相關聯的一套基本演算法的簡記法。
示例:為自定義的數據類型創建操作符。(格式為把方法名字改為”operator 想要定義的操作符”如:”operator +”)如下例子進一步說明了C#裡面的操作符就是方法,也就是函數的一個簡記法。
class Person { public string Name; //public static List<Person>GetMary(Person p1, Person p2)(一般方法自定義操作符之前) public static List<Person>operator +(Person p1, Person p2) { List<Person> people = new List<Person>(); people.Add(p1); people.Add(p2); for (int i = 0; i < 11; i++) { Person child = new Person(); child.Name = p1.Name + "&" + p2.Name + "'s child"; people.Add(child); } return people; } }
3.操作符與運算順序
①操作符的優先順序
可以使用圓括弧提高被括起來的表達式的優先順序。
圓括弧可以嵌套。
不像數學裡面有方括弧和花括弧,在C#語法中”[]”與”{}”有專門的用途。
②同優先順序操作符的運算順序
除了帶有賦值功能的操作符,同優先順序操作符都是有左到右進行運算,
帶有賦值功能的操作符的運算順序是由右到左(比如賦值運算符”=”),
與數學運算不同,電腦語言的同優先順序運算沒有”結合率”:
3+4+5只能理解為Add(Add(3,4),5)不能理解為Add(3,Add(4,5)。
- 操作符詳解
4.1基本操作符
①(成員訪問操作符)”.”操作符(上表中寫為X.Y):四種功能;
*訪問命名空間當中的子集命名空間;
*訪問名稱空間當中的類型;
*訪問類型的靜態成員(靜態成員隸屬於類本身故用類可以訪問,而用類的對象不能訪問類的靜態成員);
*訪問對象的成員(包括數據成員和方法);
②方法調用操作符”()”即方法後面跟著的那對圓括弧(上表寫為f(x))。
調用方法一定要加圓括弧,但是:
Action myAction = new Action(c.PrintHello);//把PrintHello方法交給委託對象myAction管理
myAction();//這樣在委託對象後面加個圓括弧就相當於調用了被它管理的方法了,
這個時候PrintHello方法可以不帶圓括弧。
③元素訪問操作符”[]”
int[] myIntArray = new int[13];//創建int數組的實例13個元素
int[] myIntArray2 = new int[]{1,2,3,4,5};//也可以在後面加花括弧輸入相應值,這對或括弧叫做“初始化器“,[]里不寫數組大小會根據初始化器自動賦值…
myIntArray[0]=2;//訪問的是第一個數組元素,訪問數組元素,[]里寫的是偏移量,從0開始。
Dictionary<string, Student> stuDic = new Dictionary<string, Student>();//一個類名後面,跟著一個尖括弧表示這個類是泛型
//泛型是不完整的類如Dictionary<string, Student>在尖括弧里要說明索引的類型(string)與值的類型(Student)(順帶一提Dictionary是一個字典類型)
*總結元素訪問操作符”[]”裡面放的是索引裡面不一定是整數,如以下舉例。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Dictionary<string, Student> stuDic = new Dictionary<string, Student>();//一個類名後面,跟著一個尖括弧表示這個類是泛型 6 //泛型是不完整的類如Dictionary<string, Student>在尖括弧里要說明索引的類型(string)與值的類型(Student)(順帶一提Dictionary是一個字典類型) 7 for (int i = 1; i < 100; i++) 8 { 9 Student stu = new Student(); 10 stu.Name = "s_" + i.ToString(); 11 stu.Score = 100+i; 12 stuDic.Add(stu.Name, stu);//把stu放進字典裡面,所以為stu.Name,值為stu 13 } 14 Student number6 = stuDic["s_6"];//說明了[]里不一定是整數,而一定是索引 15 Console.WriteLine(number6.Score); 16 } 17 } 18 class Student 19 { 20 public string Name; 21 public int Score; 22 }
④x–與x++:叫做後置的加加和後置的減減:
Int x=100; int y=x++;結果為x=101;y=100;因為x++是先賦值再進行自增;
–x與++x:叫做前置的加加和前置的減減:先進行自增或自減後進行賦值。
⑤typeof()操作符和default()操作符
*typeof操作符的作用為查看變數的種類:
Type t = typeof(int);
Console.WriteLine(t.Namespace);
Console.WriteLine(t.FullName);
Console.WriteLine(t.Name);
*Default操作符使操作數取默認值:數值型為0,引用型為null,
int x=default(int);//default操作的類型為結構體類型即數值類型時就返回記憶體塊當中為0的值:
Console.WriteLine(x);
輸出為0;
Form myForm = default(Form);//default操作數的類型為引用類型時就返回記憶體塊當中為0的值即為null
Console.WriteLine(myForm==null);
輸出為true;
當為枚舉型enum時: Level level=default(Level);
Console.WriteLine(level);
enum Level
{
Mid,
Low,
High
}
結果為Mid,如果把Mid的位置和Low互換則結果為Low,這是因為當default操作符遇到枚舉類型會把它當做數值型來處理,即第一個元素為0,後面的依次+1;
如果這樣寫:
enum Level
{
Mid=1,
Low=0,
High=2
}則返回值為Low。當用default獲取枚舉值的時候要小心,如果這樣寫:
enum Level
{
Mid=1,
Low=3,
High=2
}返回值為0,出錯了,所以在設置枚舉值時最好給元素一個0的整數值。
先說明:關鍵字var:幫助生成隱式類型變數:
int x;//顯式變數,明確的告訴了編譯器x屬於什麼數據類型;
var y;//隱式變數,告訴編譯器y的類型暫時不知道,當我賦值的時候看著辦
C#是強類型語言變數一旦確定數據類型就不可以變更。
⑥new操作符:
*幫助我們在記憶體當中創建一個類型的實例並且立刻調用這個實例的實例構造器(所謂的構造函數),並取得的實例地址….
new Form();//調用默認實例構造器
創建這個實例之後如果沒有任何變數去引用它,訪問它,過一會垃圾收集就把這個實例所佔用的堆記憶體當做垃圾給收回來了。
*除了創建實例和調用實例構造器之外還能把new取得的實例地址通過賦值符號交給負責訪問這個實例的變數。這樣就在變數和實例之間構成了引用關係。有了這個引用關係之後就可以通過這個變數來訪問實例。如: Form myForm=new Form();//調用默認實例構造器
myForm.Text = “Hello!”;//通過變數來訪問實例
*上面為主要功能,以下為附加功能:調用實例的初始化器:
Form myForm = new Form() {Text=”Hello!” };在實例後面加花括弧裡面加屬性的值。
可以初始化多個屬性,中間逗號隔開。
還有:有的時候用實例只是一次性的沒必要創建一個引用變數去初始化它,可以採用這時初始化器就發揮作用了:new Form(){Text=”Hello!”}.ShowDialog();只是由於沒有引用變數引用(沒有小孩牽著這個氣球,氣球一會就飛走了)所以一段時間後,垃圾回收器把它的堆記憶體回收。
a、錯覺:要創建類的實例就一定要使用new操作符,錯誤的。如string Name = “Hello!”;
String是一個類,創建實例時不用new操作符,這種方式叫做C#的”語法糖衣”,原因為為了統一使string與int的書寫格式,而把string類的new操作符隱藏起來了,string可以用new但平常不這麼用。類似的還有數組:
用new操作符:
int[] myArray = new int[10];//由於int的實例構造器有點特殊不用圓括弧調用;
不用new操作符時:
int[] myArray = { 1,2,3,4};
b、new操作符特殊用法:為匿名類型創建實例,
Form myForm=new Form(){Text=”Hello!”};當為非匿名類型創建實例時new後面要加類型名,
當為匿名類型創建實例時:如:
Var person=new {Name=”Mr li”,Age=34};//new操作符後面不跟類型,直接用初始化器初始化實例,什麼類型?讓編譯器根據初始化內容自行判斷,不過該實例一定要有引用變數引用,不知道類型?用var隱式變數即可。那到底是什麼類型呢?
Console.WriteLine(Person.GetType().Name);
輸出為:<>f__AnonymousType0`2
“<>f__AnonymousType”為約定的前綴,0表示我在程式中創建的第一個,’2表示這個類型為泛型類,構成這個類型的時候你需要兩個類型來構成它,哪兩個類型呢?就是初始化器裡面的一個是string,一個是int。這是在創建匿名類型時編譯器自己識別的類型。
這裡才真正體現出var類型(全部)功能的強大之處與重要性。因為如上一種情況就算你想寫出它的類型也不知道叫什麼名字。
*記住new操作符與var隱式變數組合的使用方法:是為匿名對象創建對象並且用隱式類型變數來引用這個實例。
c、new操作符有危險性(功能強大伴隨的濫用風險)一旦在某個類裡面(比如main函數隸屬的Program類)用new操作符創建了某個類的實例(比如在main函數中創建Form類),那麼這個類(Form)就與主類(Program)緊緊耦合在一起,Pragram類就緊緊依賴於Form類,一旦某個類(Form)出現問題,整個耦合體都無法正常運行。即new操作符會造成緊耦合。那怎麼解決?在軟體工程有項非常重要和實用的技術叫做”設計模式”,在”設計模式”當中有一種非常重要的模式叫做”依賴注入”(dependenty injection),該模式就是幫助我們把緊耦合變成相對松的耦合。有概念即可:new操作符有風險慎用,大型程式中為了避免有緊耦合的情況我們有一種叫做”依賴注入”的設計模式可以使用,實現不必關注。
*程式設計追求”高內聚低耦合”
d、new關鍵字的多用性(不是操作符而是關鍵字):如
class Student
{
public void Report()
{
Console.WriteLine(“I’m a student”);
}
}
class CsStudent:Student
{
new public void Report()//這叫子類對父類方法的隱藏,這裡的new便不是操作符而是修飾符用來修飾new後面的方法的。(並不常見)
{
Console.WriteLine(“I’m a Cstudent”);
}
}
則 Student stu = new Student();
stu.Report();
CsStudent csStu = new CsStudent();
csStu.Report();時分別調用各自的Report()方法。
⑥checked()和unchecked()操作符:用來檢查()內的值在記憶體中是否有溢出:(Overflow)
C#是強類型語言,任何一個變數它在記憶體裡面都有數據類型,而數據類型有個非常重要的作用就是表示這種數據類型的實例在記憶體當中能夠佔多大的空間,一個值在記憶體空間所佔的大小決定了這個值能夠表達的範圍,一旦超出這個範圍這個值就產生了溢出。Checked就是告訴我們要去檢出溢出,unchecked則告訴我們不用:
uint x = uint.MaxValue;
Console.WriteLine(x);
string binStr = Convert.ToString(x, 2);
Console.WriteLine(binStr);
try
{
uint y = checked(x + 1);//檢測x+1是否溢出,溢出後去catch捕獲異常
Console.WriteLine(y);
}
catch (OverflowException ex)
{
Console.WriteLine(“There is overflow”);
}
Unchecked()操作符表示不用檢查,C#中默認該種方式。Checked也有其他用法:
Checked
{
try
{
uint y = checked(x + 1);//檢測x+1是否溢出,溢出後去catch捕獲異常
Console.WriteLine(y);
}
catch (OverflowException ex)
{
Console.WriteLine(“There is overflow”);
}
}
直接判斷整個語句塊中所有語句是否有溢出。
⑦delegate操作符(關鍵字)最主要的作用為聲明一種叫委託的數據類型,委託是C#非常重要的概念。本節主要講其作為操作符的作用(非常稀有因為拉姆達表達式(Lambda Expressions)的出現就是來替代delegate當做操作符的場景):使用delegate生成匿名方法:
this.myButton.Click +=delegate (object sender, RoutedEventArgs e)//使用delegate聲明了一個匿名方法
{
this.myTextBox.Text = “Hello World!”;
};
程式原本應為:this.myButton.Click += myButton_Click;
void myButton_Click(object sender, RoutedEventArgs e)
{
this.myTextBox.Text = “Hello World!”;
}
現在替代這用用法的拉姆達表達式:
this.myButton.Click += (sender, e)=>
{
this.myTextBox.Text = “Hello World!”;
};
語法的演變可見C#語法越來越簡潔,功能越來越強大。
⑧sizeof()操作符:
a、只能獲取結構體類型在記憶體中所佔位元組數,默認情況下:sizeof只能去獲取基本數據類型他們的實例在記憶體當中所佔的位元組數,基本數據類型:比如int、uint…說白了就是C#關鍵字裡面那些除了string和object的數據類型:因為這兩個為引用類。
b、在非默認的情況下可以使用sizeof去獲取自定義的結構體類型的實例它在記憶體中占的位元組數,但是需要把它放在不安全的上下文當中:
unsafe
{
int x=sizeof(Student);
}
Decimal數據類型精確度比double高占16個位元組;
⑨最後一個”基本操作符”:”->”
*類(class)屬於引用類型,結構體(struct)屬於值類型。C#中有嚴格的規定像指針操作,取地址操作,用指針去訪問成員的操作,只能用來操作結構體類型,不能用它們去操作引用體類型。(Class)
要運行不安全程式碼除了要把它放在unsafe{}裡面,還要再項目->最後一項(相應項目屬性)->生成->勾選”允許生成不安全程式碼”,所謂雙重保險。
使用該操作符時要在unsafe情況下使用:
unsafe
{
Student stu;
stu.ID = 1;
stu.Score = 99;
Student*pStu=&stu;
pStu->Score = 100;
Console.WriteLine(stu.Score);
}