镇江网站建设教程,宁波室内设计公司排名,公司网站建设费属于什么费用,中国建筑行业网站一、C#程序
1.1 基础程序
using System; //引入命名空间namespace CsharpTest //将以下类定义在CsharpTest命名空间中
{internal class TestProgram //定义TestProgram类{public void Test() { }//定义Test方法}
}方法是C#中的诸多种类的函数之一。另一种函数*#xff0c;还…一、C#程序
1.1 基础程序
using System; //引入命名空间namespace CsharpTest //将以下类定义在CsharpTest命名空间中
{internal class TestProgram //定义TestProgram类{public void Test() { }//定义Test方法}
}方法是C#中的诸多种类的函数之一。另一种函数*还有构造器属性事件索引器终结器。 public unsafe static void Test2(int *p1,int p2){int * p p1 p2;}1.2 编译
C#编译器将一系列.cs扩展名的源代码文件编译成程序集。程序集是.NET中的最小打包和部署单元。程序集可以是一个应用程序或者是一个库。应用程序是一个.exe文件包含一个Main方法。库是一个.dll文件。C#的编译器是csc.exe。
二、语法
c#的语法基于c和c语法。
2.1 标识符
标识符是程序员为类、方法、变量等选择的名字。 例 System Test Main x Console WeiteLine
2.2 关键字
关键字是对编译器有特殊意义的名字。 例 Using class static void int
其它关键字
Column 1Column 2Column 3Column 4Column 5abstractdoinprotectedtrueasdoubleintpublictrybaseelseinterfacereadonlytypeofboolenuminternalrefuintbreakeventisreturnulongbyteexplicitlocksbyteuncheckedcaseexternlongsealedunsafecatchfalsenamespaceshortushortchatfinallynewsizeofusingchecedfixednullstackallocvirtualclassfloatobjectstaticvoidconstforoperatorstringvolatitlecontinueforeachoutstructwhiledecinmalgotooverrideswitchdefaultifparamsthisdelegateimplicitprivatethrow
2.2.1 避免冲突
如果希望用关键字作为标识符需要在关键字前面加上前缀如 public void class() { }2.2.2 上下文关键字
一些关键字是上下文相关的它们有时不用添加前缀就可以用作标识符。
Column 1Column 2Column 3Column 4Column 5adddynamicinorderbyvarascendingequalsintopatrialwhenasyncfromjoinremovewhereawaitgetletselectyieldbyglobalnameofsetdescendinggrouponvalue
2.2.3 字面量、标点、运算符
字面量是静态的嵌入程序的原始数据。1230 标点 {} 运算符 . *
2.2.4 注释
单行注释 // 多行注释 /*开始 */结束
2.3 类型基础
类型是值的蓝图。
int x 2 * 3;变量表示一个存储位置,其中的值可能会不断的变换。与之对应常量总是表示同一个值。
const int y 360;C#的所有值都是一种类型的实例。值或者变量所包含的可能取值均由器类型决定。
2.3.1 预定义类型示例
预定义类型是指那些由编译器特别支持的类型。 如 int string bool
2.3.2 自定义类型示例
如自己定义一个类Person
Person person new Person();2.3.3 类型成员
类型包含数据成员和函数成员。 数据成员如字段 函数成员如构造器和方法
2.3.4构造器和实例化
构造器 如下代码中
internal class Person{string name;public Person(string name){this.name name;}}构造器部分为
public Person(string name)
{this.name name;
}实例化
Person xiao_ming new Person(小明);2.3.5 实例与静态成员
由类型的实例操作的数据成员和函数成员称为实例成员。 那些不是由类型的实例操作而是由类型本身操作的数据成员和函数成员必须标记为static。Console.WeiteLine就是静态方法。Console类是一个静态类它的所有成员都是静态的由于Console类型无法实例化因此控制台将在整个应用程序内共享使用。 例 类 internal class Panda
{public string name;public static int population;public Panda(string name){this.name name;population 1;}
}调用
Panda xm new Panda(xm);
Panda xz new Panda(xz);
Console.WriteLine(Panda.population);如果试图xm.population 或者 Panda.Name会出现编译错误。
2.3.6 转换
C#可以转换兼容类型的实例。转换始终会根据一个已经存在的值创建一个新的值。转换可以是隐式或显式的隐式转换自动发生而显示转换需要强制转换。我们将int隐式转换为long类型其存储位数是int的两倍并将一个int显示转换为一个short类型其存储位数是int的一半
int x 123;
long y x;
short z (short)x;隐式转换 ~编译器能确保转换总能成功 ~没有信息在转换过程中丢失
显式转换 ~编译器不能保证转换总是成功 ~信息在转换过程中有可能丢失
2.4 值类型与引用类型
所有的C#类型可以分为以下几类 ~值类型 ~引用类型 ~泛型参数 ~指针类型
值类型包含大多数的内置类型(具体包括所有数值类型、char类型和bool类型)以及struct和enum。 引用类型包含所有的类、数组、委托和接口包括自定义的string类型。 引用类型和值类型最根本的不同在于它们在内存中的处理方式。
2.4.1值类型
值类型的变量或常量仅仅是一个值。
Point结构体XY
public struct Point
{public int X;public int Y;
}值类型实例的赋值总是会进行实例的复制。例如
Point p1 new Point();
p1.X 7;
Point p2 p1;
p1.X 9;Console.WriteLine(p1.X);
Console.WriteLine(p2.X);p1和p2拥有不同的空间 #mermaid-svg-sNMEORrBsSKvJwUq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-sNMEORrBsSKvJwUq .error-icon{fill:#552222;}#mermaid-svg-sNMEORrBsSKvJwUq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-sNMEORrBsSKvJwUq .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-sNMEORrBsSKvJwUq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-sNMEORrBsSKvJwUq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-sNMEORrBsSKvJwUq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-sNMEORrBsSKvJwUq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-sNMEORrBsSKvJwUq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-sNMEORrBsSKvJwUq .marker.cross{stroke:#333333;}#mermaid-svg-sNMEORrBsSKvJwUq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-sNMEORrBsSKvJwUq g.classGroup text{fill:#9370DB;fill:#131300;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-sNMEORrBsSKvJwUq g.classGroup text .title{font-weight:bolder;}#mermaid-svg-sNMEORrBsSKvJwUq .nodeLabel,#mermaid-svg-sNMEORrBsSKvJwUq .edgeLabel{color:#131300;}#mermaid-svg-sNMEORrBsSKvJwUq .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-sNMEORrBsSKvJwUq .label text{fill:#131300;}#mermaid-svg-sNMEORrBsSKvJwUq .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-sNMEORrBsSKvJwUq .classTitle{font-weight:bolder;}#mermaid-svg-sNMEORrBsSKvJwUq .node rect,#mermaid-svg-sNMEORrBsSKvJwUq .node circle,#mermaid-svg-sNMEORrBsSKvJwUq .node ellipse,#mermaid-svg-sNMEORrBsSKvJwUq .node polygon,#mermaid-svg-sNMEORrBsSKvJwUq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-sNMEORrBsSKvJwUq .divider{stroke:#9370DB;stroke:1;}#mermaid-svg-sNMEORrBsSKvJwUq g.clickable{cursor:pointer;}#mermaid-svg-sNMEORrBsSKvJwUq g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-sNMEORrBsSKvJwUq g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-sNMEORrBsSKvJwUq .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-sNMEORrBsSKvJwUq .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-sNMEORrBsSKvJwUq .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-sNMEORrBsSKvJwUq .dashed-line{stroke-dasharray:3;}#mermaid-svg-sNMEORrBsSKvJwUq #compositionStart,#mermaid-svg-sNMEORrBsSKvJwUq .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-sNMEORrBsSKvJwUq #compositionEnd,#mermaid-svg-sNMEORrBsSKvJwUq .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-sNMEORrBsSKvJwUq #dependencyStart,#mermaid-svg-sNMEORrBsSKvJwUq .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-sNMEORrBsSKvJwUq #dependencyStart,#mermaid-svg-sNMEORrBsSKvJwUq .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-sNMEORrBsSKvJwUq #extensionStart,#mermaid-svg-sNMEORrBsSKvJwUq .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-sNMEORrBsSKvJwUq #extensionEnd,#mermaid-svg-sNMEORrBsSKvJwUq .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-sNMEORrBsSKvJwUq #aggregationStart,#mermaid-svg-sNMEORrBsSKvJwUq .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-sNMEORrBsSKvJwUq #aggregationEnd,#mermaid-svg-sNMEORrBsSKvJwUq .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-sNMEORrBsSKvJwUq .edgeTerminals{font-size:11px;}#mermaid-svg-sNMEORrBsSKvJwUq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Point int X int Y P1 [X 9] - 9 [Y 0] - 0 P2 [X 9] - 7 [Y 0] - 0 运行结果
赋值操作复制了值类型的实例。
2.4.2 引用类型
引用类型比值类型复杂它由两部分组成对象和对象引用。引用类型变量和常量中的内容是一个含值对象的引用。 内存中的引用类型的实例 #mermaid-svg-oOnt0bi9Dh6oNbXL {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-oOnt0bi9Dh6oNbXL .error-icon{fill:#552222;}#mermaid-svg-oOnt0bi9Dh6oNbXL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-oOnt0bi9Dh6oNbXL .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-oOnt0bi9Dh6oNbXL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-oOnt0bi9Dh6oNbXL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-oOnt0bi9Dh6oNbXL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-oOnt0bi9Dh6oNbXL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-oOnt0bi9Dh6oNbXL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-oOnt0bi9Dh6oNbXL .marker.cross{stroke:#333333;}#mermaid-svg-oOnt0bi9Dh6oNbXL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-oOnt0bi9Dh6oNbXL g.classGroup text{fill:#9370DB;fill:#131300;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-oOnt0bi9Dh6oNbXL g.classGroup text .title{font-weight:bolder;}#mermaid-svg-oOnt0bi9Dh6oNbXL .nodeLabel,#mermaid-svg-oOnt0bi9Dh6oNbXL .edgeLabel{color:#131300;}#mermaid-svg-oOnt0bi9Dh6oNbXL .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-oOnt0bi9Dh6oNbXL .label text{fill:#131300;}#mermaid-svg-oOnt0bi9Dh6oNbXL .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-oOnt0bi9Dh6oNbXL .classTitle{font-weight:bolder;}#mermaid-svg-oOnt0bi9Dh6oNbXL .node rect,#mermaid-svg-oOnt0bi9Dh6oNbXL .node circle,#mermaid-svg-oOnt0bi9Dh6oNbXL .node ellipse,#mermaid-svg-oOnt0bi9Dh6oNbXL .node polygon,#mermaid-svg-oOnt0bi9Dh6oNbXL .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-oOnt0bi9Dh6oNbXL .divider{stroke:#9370DB;stroke:1;}#mermaid-svg-oOnt0bi9Dh6oNbXL g.clickable{cursor:pointer;}#mermaid-svg-oOnt0bi9Dh6oNbXL g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-oOnt0bi9Dh6oNbXL g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-oOnt0bi9Dh6oNbXL .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-oOnt0bi9Dh6oNbXL .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-oOnt0bi9Dh6oNbXL .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-oOnt0bi9Dh6oNbXL .dashed-line{stroke-dasharray:3;}#mermaid-svg-oOnt0bi9Dh6oNbXL #compositionStart,#mermaid-svg-oOnt0bi9Dh6oNbXL .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-oOnt0bi9Dh6oNbXL #compositionEnd,#mermaid-svg-oOnt0bi9Dh6oNbXL .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-oOnt0bi9Dh6oNbXL #dependencyStart,#mermaid-svg-oOnt0bi9Dh6oNbXL .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-oOnt0bi9Dh6oNbXL #dependencyStart,#mermaid-svg-oOnt0bi9Dh6oNbXL .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-oOnt0bi9Dh6oNbXL #extensionStart,#mermaid-svg-oOnt0bi9Dh6oNbXL .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-oOnt0bi9Dh6oNbXL #extensionEnd,#mermaid-svg-oOnt0bi9Dh6oNbXL .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-oOnt0bi9Dh6oNbXL #aggregationStart,#mermaid-svg-oOnt0bi9Dh6oNbXL .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-oOnt0bi9Dh6oNbXL #aggregationEnd,#mermaid-svg-oOnt0bi9Dh6oNbXL .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-oOnt0bi9Dh6oNbXL .edgeTerminals{font-size:11px;}#mermaid-svg-oOnt0bi9Dh6oNbXL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 引用 对象 X[值/实例] Y[值/实例] 注结构体的命名和类的命名在同一命名空间下不能相同。以下改写为Point类
public class Point
{public int X;public int Y;
}给引用类型变量赋值只会复制引用而不是实例。这允许不同变量指向同一个对象而值类型通常不会出现这种情况。如果Point是一个类那么按照之前Point结构体那样操作p1的操作就会影响p2:
Point p1 new Point();
p1.X 7;
Point p2 p1;
p1.X 9;Console.WriteLine(p1.X);
Console.WriteLine(p2.X);运行结果 #mermaid-svg-mLLBajqw1f6L6bJ3 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-mLLBajqw1f6L6bJ3 .error-icon{fill:#552222;}#mermaid-svg-mLLBajqw1f6L6bJ3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mLLBajqw1f6L6bJ3 .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-mLLBajqw1f6L6bJ3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mLLBajqw1f6L6bJ3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mLLBajqw1f6L6bJ3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mLLBajqw1f6L6bJ3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mLLBajqw1f6L6bJ3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mLLBajqw1f6L6bJ3 .marker.cross{stroke:#333333;}#mermaid-svg-mLLBajqw1f6L6bJ3 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mLLBajqw1f6L6bJ3 g.classGroup text{fill:#9370DB;fill:#131300;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-mLLBajqw1f6L6bJ3 g.classGroup text .title{font-weight:bolder;}#mermaid-svg-mLLBajqw1f6L6bJ3 .nodeLabel,#mermaid-svg-mLLBajqw1f6L6bJ3 .edgeLabel{color:#131300;}#mermaid-svg-mLLBajqw1f6L6bJ3 .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-mLLBajqw1f6L6bJ3 .label text{fill:#131300;}#mermaid-svg-mLLBajqw1f6L6bJ3 .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-mLLBajqw1f6L6bJ3 .classTitle{font-weight:bolder;}#mermaid-svg-mLLBajqw1f6L6bJ3 .node rect,#mermaid-svg-mLLBajqw1f6L6bJ3 .node circle,#mermaid-svg-mLLBajqw1f6L6bJ3 .node ellipse,#mermaid-svg-mLLBajqw1f6L6bJ3 .node polygon,#mermaid-svg-mLLBajqw1f6L6bJ3 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-mLLBajqw1f6L6bJ3 .divider{stroke:#9370DB;stroke:1;}#mermaid-svg-mLLBajqw1f6L6bJ3 g.clickable{cursor:pointer;}#mermaid-svg-mLLBajqw1f6L6bJ3 g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-mLLBajqw1f6L6bJ3 g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-mLLBajqw1f6L6bJ3 .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-mLLBajqw1f6L6bJ3 .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-mLLBajqw1f6L6bJ3 .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-mLLBajqw1f6L6bJ3 .dashed-line{stroke-dasharray:3;}#mermaid-svg-mLLBajqw1f6L6bJ3 #compositionStart,#mermaid-svg-mLLBajqw1f6L6bJ3 .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-mLLBajqw1f6L6bJ3 #compositionEnd,#mermaid-svg-mLLBajqw1f6L6bJ3 .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-mLLBajqw1f6L6bJ3 #dependencyStart,#mermaid-svg-mLLBajqw1f6L6bJ3 .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-mLLBajqw1f6L6bJ3 #dependencyStart,#mermaid-svg-mLLBajqw1f6L6bJ3 .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-mLLBajqw1f6L6bJ3 #extensionStart,#mermaid-svg-mLLBajqw1f6L6bJ3 .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-mLLBajqw1f6L6bJ3 #extensionEnd,#mermaid-svg-mLLBajqw1f6L6bJ3 .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-mLLBajqw1f6L6bJ3 #aggregationStart,#mermaid-svg-mLLBajqw1f6L6bJ3 .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-mLLBajqw1f6L6bJ3 #aggregationEnd,#mermaid-svg-mLLBajqw1f6L6bJ3 .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-mLLBajqw1f6L6bJ3 .edgeTerminals{font-size:11px;}#mermaid-svg-mLLBajqw1f6L6bJ3 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} p1引用 对象 X[值/实例] Y[值/实例] p2引用 赋值操作复制了引用。
2.4.3 Null
引用可以赋值为字面量null表示它并不指向任何对象
Point p null;值类型则不行例如结构体Point用上面的写法会直接编译报错 C#中也有一种代表值类型的null结构称为可空类型nullable。
Point? p null;如上所示结构体类型采用可空类型写法编译就可以通过。
2.4.4 储存开销
值类型实际占用的内存大小是储存其字段的内存。例如结构体Point需要8字节的内存
public struct Point
{public int X;//4 Bytespublic int Y;//4 Bytes
}技术上,CLR采用整数倍大小最大到8个字节来分配内存地址。因此下面定义的对象实际会占用16字节的内存则第一个字段的7个字节被“浪费了”
public struct P
{public byte b;//1 Bytes CLR采用整数倍大小 最大 8 bytespublic long l;//8 Bytes
}这种行为可以通过StructLayout重写。 [StructLayout(LayoutKind.Explicit)]internal struct P{[FieldOffset(0)] public byte b;//1 Bytes[FieldOffset(1)] public long l;//偏移1个字节取值}这样的Struct占用9个字节0。
引用类型要求为引用和对象单独分配内存。对象除占用了个字段一样的字节数外还需要额外的管理空间开销。管理开销的精确值本质上属于.NET运行时实现的细节但最少也需要8个字节来存储该对象的类型的键以及一些诸如多线程锁的状态是否可以被垃圾回收器固定等信息。根据.NET运行时是工作在32位或64位平台上。每一个对象的引用都需要额外的4到8个字节。
2.4.5 预定义类型分类
C#预定义类型
值类型 ⊶ \origof ⊶数值 ⊸ \multimap ⊸有符号整数sbyte、short、int、long ⊸ \multimap ⊸无符号整数byte、ushort、uint、ulong ⊸ \multimap ⊸实数float、double、decimal ⊶ \origof ⊶逻辑值bool ⊶ \origof ⊶字符char
引用类型 ⊶ \origof ⊶字符串string) ⊶ \origof ⊶对象 (object)
C#的预定义类型又称为框架类型它们都在System命名空间下。下面语句仅在拼写方面不同
int i 1;
System.Int32 i 1;在CRL中除了decimal外的一系列预定义值类型称为基元类型。之所以称为基元类型是因为它们在编译过程中有直接的命令支持。而这种指令通常翻译为底层处理器直接支持的指令。如
int i 0
bool b true;
char c A;
float f 0.2f;System.IntPtr以及System.UIntPtr类型也是基元类型。
2.5 数值类型
C#中的预定义数值类型
C#类型系统后缀容量数值范围整数有符号sbyteSbyte8 位 − 2 − 7 -2^{-7} −2−7~ 2 7 − 1 2^{7}-1 27−1shortInt1616 位 − 2 − 15 -2^{-15} −2−15~ 2 15 − 1 2^{15}-1 215−1intInt3232 位 − 2 − 31 -2^{-31} −2−31~ 2 31 − 1 2^{31}-1 231−1longInt64L64 位 − 2 − 63 -2^{-63} −2−63~ 2 63 − 1 2^{63}-1 263−1整数无符号byteByte8 位 0 0 0~ 2 8 − 1 2^{8}-1 28−1ushortUInt1616 位 0 0 0~ 2 16 − 1 2^{16}-1 216−1uintUInt32U32 位 0 0 0~ 2 32 − 1 2^{32}-1 232−1ulongUInt64UL64 位 0 0 0~ 2 64 − 1 2^{64}-1 264−1实数floatSingleF32 位 ∓ \mp ∓( 1 0 − 45 10^{-45} 10−45~ 1 0 38 10^{38} 1038 )doubleSoubleD64 位 ∓ \mp ∓( 1 0 − 324 10^{-324} 10−324~ 1 0 308 10^{308} 10308 )decimalDecimalM128 位 ∓ \mp ∓( 1 0 − 28 10^{-28} 10−28~ 1 0 28 10^{28} 1028 )
在整数类型中 int 和 long 是最基本的类型C#和运行时对其有良好的支持。其他的整数类型通常用于实现互操作性或存储空间使用效率要求更高的情况。
在实数类型中 float 和 double 称为浮点类型并通常用于科学和图形计算。decimal 类型通常用于金融计算这种十进制下的高精度算术运算。
2.5.1 数值字面量
数值字面量的任意位置加下划线以方便阅读
int million 1_000_000;16进制以 0x 前缀
long y 0x7F;2进制以 0b 前缀:
var b 0b1001_0000;实数字面量可以用小数或指数表示
double d 1.5;
double million 1E06;2.5.1.1 数值字面量类型接口
默认情况下编译器将数值字面量推断为double类型或是整数类型。 ⊶ \origof ⊶如果这个字面量包含小数点或者指数符号E那么他就是double. ⊶ \origof ⊶否则这个字面量的类型就是下列能满足这个字面量的第一个类型int、uint、long、ulong如
Console.WriteLine(1.3.GetType());
Console.WriteLine(1E06.GetType());
Console.WriteLine(1.GetType());
Console.WriteLine(0xF0000000.GetType());
Console.WriteLine(0x100000000.GetType());运行结果
2.5.1.2 数值后缀
数值后缀显示定义了字面量的类型。后缀可以是大写或小写字母
种类C#类型例子Ffloatfloat f 1.0F;Ddoubledouble d 1D;Mdecimaldecimal d 1.0M;Uuintuint 1U;Llonglong i 1L;ULulongulong i 1UL
一般 U 和 L 后缀是很少需要的。因为uint、long和ulong总是可以推断出来或者从int类型隐式转换过来
long i 5;浮点数类型,后缀D是多余的编译器数值字面量默认推断为 double 类型。因此可以不用加后缀D:
double i 1.2;后缀F和M是最有用的应该在指定 float 或 decimal 字面量是使用。下面语句不能在没有后缀 F 时进行编译。这是因为4.5会认定为 double 而 double 无法隐式转换为 float:
float f 4.5F;同理 double :
decimal d 1.23M;2.5.2 数值转换
2.5.2.1 整数类型到整数类型的转换
整数类型转换在能够表示源类型的所有可能值时是隐式转换否则是显示转换。如
int x 123;隐式转换
long y x;显示转换
short z (short)x;2.5.2.2 浮点类型到浮点类型的转换
double 能表示所有可能的 float 的值因此 float 能隐式转换为 double。反之则必须是显示转换。
2.5.2.3 浮点类型到整数类型的转换
所有整数类型都可以隐式转换为浮点类型
int i 1;
float f i;反之则必须是显示转换
int i2 (int)f;2.5.2.4 decimal类型转换
所有的整数类型都能隐式转换成 decimal 类型。这是因为 decimal 可以表示所有可能的C#整数类型值。其他所有的数值类型转换为 decimal 或从 decimal 类型转换都必须是显示转换。
2.5.3 算术运算符
算术运算符、-、*、/、%可用于除8位和16位的整数类型之外的所有数值类型。
2.5.4 自增和自减运算符
自增和自减运算符分别给数值类型加1或者减1。具体要将其放在变量之前还是之后取决于需要得到变量在自增/自减之前的值还是之后的值。 int x 0, y 0;Console.WriteLine(x);Console.WriteLine(y);运行结果
2.5.5 特殊整数类型运算
2.5.5.1 整数除法
整数类型的除法运算总是会截断余数向0舍入。用一个值为0的变量做除数将产生运行时错误DivideByZeroException):
int a 2 / 3; //0
int b 0;
int c 5/ b;//thow DivideByZeroException2.5.5.2 整数溢出
在运行时执行整数类型的算术可能会造成溢出。默认情况下溢出会默默的发生而不会抛出任何异常且其溢出行为是“循环”的。就像是运算发生在更大的整数类型上而超出的部分就被丢弃了。例如减小最小的整数值将产生最大的整数值
int a int.MinValue;
a--;
Console.WriteLine(a int.MaxValue);//True
int b int.MaxValue;
b;
Console.WriteLine(b int.MinValue);//True2.5.5.3 整数运算溢出检查运算符
checked运算符的作用是在运行时当整数类型表达式或语句超过相应类型的算术限制时不再默默地溢出而是抛出 OverflowException。 checked运算符对double和float没有作用它们的溢出为特殊的“无限”值对decimal也没有作用这种类型总是会进行溢出检查。
checked运算符能和表达式或语句块结合使用
int a 1000000;
int b 1000000;
int c checked(a * b);
checked
{c a * b;
}如果你想禁止指定表达式或语句的溢出检查。用unchecked运算符
int x int.MaxValue;
int y unchecked(x1);
unchecked
{int z unchecked(x1);
}2.5.5.4 常量表达式的溢出检查
无论是否用了/checked 命令开关编译时的表达式计算总会检查溢出除非应用了uchecked运算符。
以下语句编译报错
int x int.MaxValue 1;//编译报错解决方案
int x unchecked(int.MaxValue 1);2.5.5.4 位运算符
~ 按位取反 运算规则二进制上的每一位上的数0变为11变为0 例如 10的8位二进制 10 的二进制 00001010 取反得负二进制 11110101 减一 11110100 取反 00001011 二进制转十进制 11 负二进制转十进制 − 11 \def\arraystretch{1.5} \begin{array}{c} 10的二进制 00001010 \\ \hline 取反得负二进制 11110101\\ \hline 减一 11110100\\ \hline 取反 00001011\\ \hline 二进制转十进制 11 \\ \hline 负二进制转十进制 -11 \end{array} 10的二进制取反得负二进制减一取反二进制转十进制负二进制转十进制0000101011110101111101000000101111−11 相应的负二进制转换技巧请查看1.5 负数的二进制 所以 ~10 的结果为 -11
代码示例
Console.WriteLine(~10); //-11按位与 运算规则若两个二进制数同一位上的数都为1时该位上的结果为1若两个二进制数同一位上的数其中一个数为0该位上的结果为0 以1113为例 11 的二进制 1011 13 的二进制 1101 与运算结果 1001 转化为十进制 9 \def\arraystretch{1.5} \begin{array}{c} 11的二进制 1011 \\ \hline 13的二进制 1101\\ \hline 与运算结果 1001 \\ \hline 转化为十进制 9 \end{array} 11的二进制13的二进制与运算结果转化为十进制1011110110019 所以 1113的结果为 9
代码示例
int a 0b1011; //11
int b 0b1101; //13
int c 0b1001; //9
Console.WriteLine(a);
Console.WriteLine(b);
Console.WriteLine(a b); //9
Console.WriteLine(11 13); //9
Console.WriteLine(c);| 按位或 运算规则若两个二进制数同一位上的数其中一个数为1则该位上的结果为1否则为0 以3|9为例 3 的二进制 0011 9 的二进制 1001 或运算结果 1011 转化为十进制 11 \def\arraystretch{1.5} \begin{array}{c} 3的二进制 0011 \\ \hline 9的二进制 1001\\ \hline 或运算结果 1011 \\ \hline 转化为十进制 11 \end{array} 3的二进制9的二进制或运算结果转化为十进制00111001101111 所以 3|9 的结果为 11
代码示例
int a 0b0011; //3int b 0b1001; //9int c 0b1011; //11Console.WriteLine(a);Console.WriteLine(b);Console.WriteLine(a | b); //11Console.WriteLine(3 | 9); //11Console.WriteLine(c);^ 按位异或 运算规则若两个二进制数同一位上的数其中一个数为1,另一个数为0则该位上的结果为1否则为0 以3^9为例 3 的二进制 0011 9 的二进制 1001 异或运算结果 1010 转化为十进制 10 \def\arraystretch{1.5} \begin{array}{c} 3的二进制 0011 \\ \hline 9的二进制 1001\\ \hline 异或运算结果 1010 \\ \hline 转化为十进制 10 \end{array} 3的二进制9的二进制异或运算结果转化为十进制00111001101010 所以 3^9 的结果为 10
代码示例 int a 0b0011; //3int b 0b1001; //9int c 0b1010; //10Console.WriteLine(a);Console.WriteLine(b);Console.WriteLine(a ^ b); //10Console.WriteLine(3 ^ 9); //10Console.WriteLine(c);按位左移 以32为例,将3的二进制数做移2个位置。 3 的二进制 00000011 左移 2 个位置 00001100 转化为十进制 12 \def\arraystretch{1.5} \begin{array}{c} 3的二进制 00000011\\ \hline 左移2个位置 00001100 \\ \hline 转化为十进制 12 \end{array} 3的二进制左移2个位置转化为十进制000000110000110012 代码示例
int a 0b00000011;//3
int b 0b00001100;//1
Console.WriteLine(a 2);//12
Console.WriteLine(3 2);//12
所以32的结果为12按位右移 以93为例将9的二进制数右移3个位置。 9 的二进制 00001001 右移 3 个位置 00000001 转化为十进制 1 \def\arraystretch{1.5} \begin{array}{c} 9的二进制 00001001\\ \hline 右移3个位置 00000001 \\ \hline 转化为十进制 1 \end{array} 9的二进制右移3个位置转化为十进制00001001000000011 代码示例 int a 0b00001001;//9int b 0b00000001;//1Console.WriteLine(a 3);//9Console.WriteLine(9 3);//1所以93的结果为1
2.5.6 8位和16位整数类型
8位和16位整数类型指的是byte、sbyte、short、ushort。这些类型自己并不具备算术运算所以C#隐式的将它们转换为所需的更大一些的类型。当试图把运算结果赋给一个小的整数类型时会产生编译错误 以上情况x和y会隐式转换成int以便进行加法运算。因此结果也是int它不能隐式转换回short因为这可能造成数据丢失。我们必须显示转换才能令其通过编译
short z (short)(x y);2.5.7 特殊的float和double值
不同于整数类型浮点类型包含某些特定运算需要特殊对待的值。这些特殊值是NaN(Not a NUmber,非数字)、 ∞ \infin ∞、 − ∞ -\infin −∞和-0。float和double类型包含表示NaN、 ∞ \infin ∞、 − ∞ -\infin −∞值的常量。其它常量还有MaxValue、MinBalue、Epsilon。例
Console.WriteLine(double.NegativeInfinity);double和float特殊值常量表
特殊值double类型常量float类型常量NaNdouble.NaNfloat.NaN ∞ \infin ∞double.PositiveInfinityfloat.PositiveInfinity − ∞ -\infin −∞double.NegativeInfinityfloat.NegativeInfinity-0-0.0-0.0f
非零值除以零结果是无穷大。例
Console.WriteLine(1.0/0.0);
Console.WriteLine(-1.0 / 0.0);
Console.WriteLine(1.0 / -0.0);
Console.WriteLine(-1.0 / -0.0);零除以零或无穷大减去无穷大的结果NaN。例如
Console.WriteLine(0.0 / 0.0);
Console.WriteLine((1.0 / 0.0) - (1.0 / 0.0));使用比较运算符()时,一个NaN的值永远也不等于其他值甚至不等于其他NaN的值
Console.WriteLine((0.0 / 0.0) double.NaN); //False必须使用float.IsNaN或double.IsNaN方法来判断一个值是否为NaN:
Console.WriteLine(double.IsNaN(0.0 / 0.0)); //True
Console.WriteLine(float.IsNaN(0.0f / 0.0f)); //True但使用object.Equal时两个NaN确实相等的
Console.WriteLine(object.Equals(0.0 / 0.0, double.NaN)); //TrueNaN在表示特殊值时很有用在WPF中double.NaN表示值为“Automatic”自动。另一种表示方法是可空类型nullable。还可以使用一个包含数值类型和一个额外字段的自定义结构体。
float和double遵循IEEE 754格式类型规范。处理器原生支持此规范。
2.5.8 double和decimal的对比
double类型在科学计算例如计算空间坐标时很有用。decimal在金融计算和计算那些“人为”的而非真实世界度量的结果时很有用。两种类型对比
doubledecimal内部表示基数为2基数为10精度16为有效数字29位有效数字范围 ∓ \mp ∓( 1 0 − 324 10^{-324} 10−324~ 1 0 308 10^{308} 10308 ) ∓ \mp ∓( 1 0 − 28 10^{-28} 10−28~ 1 0 28 10^{28} 1028 ))特殊值NaN、 ∞ \infin ∞、 − ∞ -\infin −∞和-0无速度处理器原生支持非处理器原生支持速度大约比double慢十倍
2.5.9 实数的舍入误差
float和double在内部都是基于2来表示数值的。因此只有基于2表示数值才能够精确表示。事实上这意味着大多数有小数字部分的字面量它们都基于10将无法精确表示。例如
float tenth 0.1001f;
float one 1f;
Console.WriteLine(one - tenth * 10f);理论上结果应该为-0.001。
而运行结果为 同样的如果将关键字float改为decimal
decimal t 0.1001M;
decimal o 1M;
Console.WriteLine(o - t * 10M);运行结果为 这就是为什么float和double不适合做金融计算。相反decimal基于10,它能够精确表示基于10的数值也包括它的因数基于2和基于5的数值。因为实数的字面量都是基于10的所以decimal能够精确表示像0.1这样的数。然而double和decimal都不能精确表示那些基于10的循环小数
decimal m 1M / 6M;
double d 1.0 / 6.0;
Console.WriteLine(m); //0.1666666666666666666666666667
Console.WriteLine(d); //0.16666666666666666这样会导致累计性的误差
decimal wholeM m m m m m m;
double wholeD d d d d d d;
Console.WriteLine(wholeM);//1.0000000000000000000000000002
Console.WriteLine(wholeD);//0.9999999999999999这也将影响相等和比较操作
Console.WriteLine(wholeM 1M);//False
Console.WriteLine(wholeD 1.0);//True2.6 布尔运算和运算符
C#中的boolSystem.Boolean类型是能赋值为True和false字面量的逻辑值。 尽管布尔类型的值仅需要1位的存储空间但是运行时却需要1字节内存空间。这是因为字节是运行时和处理器能够有效使用的最小单位。为避免使用数组时的空间浪费.NET framework在System.Collections命令空间下提供了BitArray类其中的每一个布尔值仅占用一位。
BitArray s new BitArray(new bool[10]);
s.Set(0, true);2.6.1 布尔运算和运算符
bool类型不能转换为数值类型反之亦然。
2.6.2 相等和比较运算符
和用于判断任意类型相等与不等并总是返回一个bool.
int x 1;
int y 2;
int z 1;
Console.WriteLine(x y); //False
Console.WriteLine(z x); //True对于引用类型默认情况下相等说基于引用的而不是底层对象的实际值 Person p1 new Person(Wan);Person p2 new Person(Li);Console.WriteLine(p1 p2);//FalsePerson p3 p1;Console.WriteLine(p1 p3);//True相等和比较运算符、!、、、、可用于所有的数值类型但是用于实数类型要特别注意参考前面。比较运算符也可以用于枚举类型enum类型的成员他比较的是表示枚举成员的整数值。
public enum EnumValue
{Zero,One,Two
}Console.WriteLine(EnumValue.Two EnumValue.One);//True
Console.WriteLine(EnumValue.Zero EnumValue.One);//False
Console.WriteLine(EnumValue.Two.GetHashCode() 1);//True
Console.WriteLine(((int)EnumValue.Two) 1);//True其中 语句
Console.WriteLine(EnumValue.Two.GetHashCode() 1);比语句
Console.WriteLine(((int)EnumValue.Two) 1);执行速度快一倍。
2.6.3 条件运算符 和 ||运算符用于判断与和条件。它们常常与代表“非”的运算符一起使用。在下面例子中UseUmberalla方法在下雨或阳光充足雨伞可以保护我们不会经受日晒雨淋以及无风因为雨伞在有风的时候true:
static bool UseUmbrella(bool rainy, bool sunny, bool windy)
{return !windy (sunny || rainy);
}和 ||运算符会在可能的情况下执行短路计算。如上例子中如果刮风windytrue则(sunny || rainy)将不会计算。
短路计算在某些表达式中是非常必要的它可以允许如下表达式允许而不会抛出NullReferenceException异常
if(sb null ** sb.Length 0)
{
}和 **|**运算符也可用于判断与和或条件
return !windy (sunny | rainy);不同之处是和|运算符不支持短路计算。因此它们很少用于替代条件运算符。
三元条件运算符 三元条件运算符由于它是唯一一个使用三个操作数的运算符因此也简称为三元运算符使用q ? a:b的形式。它在q为真时计算a否则计算b。例如
static int Max(int a, int b)
{return (a b) ? a : b;
}条件运算符在LINGQ语句中尤其有用。
2.7 字符串和字符
C# char(System.Char)类型表示一个Unicode字符并占用两个字节。char字面量应位于两个单引号之间 char c ‘A’; 转义字符指那些不能用字面量表示或解释的字符。转义字符由反斜线和一个表示特殊含义的字符组成例如
char newLine \n;
Console.WriteLine(a newLine b);运行结果 char backSlash \\;Console.WriteLine(backSlash);运行结果 转义字符序列表
字符含义值\’单引号0x0027\双引号0x0022\\斜线0x005C\0空(null)0x0000\a警告0x0007\ b退格0x0008\f走纸0x000C\n换行0x000A\r回车0x000D\t水平制符表0x0009\v垂直制符表0x000B
\u(或\x)转义字符通过4位十六进制代码来指定任意Unicode字符
char copyrighySymbol \u00A9;
char omegasymbol \u03A9;
char newLine \u000A;2.7.1 char转换
从chat类型到数值类型的隐式转换只在这个数值类型可以容纳无符号 short 类型时有效。对于其它数值类型则需要显示转换。
2.7.2 字符串类型
C#中字符串类型(System.String)表示不可变的Unicode字符序列。字符串字面量应位于两个引好”之间
string a hahaha;string类型是引用类型而不是值类型。但是它的相等运算符却遵守值类型的语义。
string x test;
string y test;
Console.WriteLine(x y);//True对char字面量有效的转义字符在字符串中同样有效
string a Here s a tab:\tmini;
Console.WriteLine(a);这意味着当需要一个反斜杠时需要写两次才可以
string a1 \\\\server\\fileshare\\selloworld.cs;
Console.WriteLine(a1);//\\server\fileshare\selloworld.cs为避免这种情况C#引入了原意字符串字面量。原意字符串字面量要加前缀它不支持转义字符。下面的原意字符串和之前的字符串是一样的。
string a2 \\server\fileshare\selloworld.cs;愿意字符串可以贯穿多行 string escaped Frist Line\r\nSecond Line;string verbatim Frist Line
Second Line;Console.WriteLine(escaped verbatim);//True愿意字符串中需要两个双引号来表示一个双引号字符
string xml customer id 123/customer;
Console.WriteLine(xml);//customer id 123/customer2.7.2 .1连接字符串
运算符可连接两个字符串
string s a b;
Console.WriteLine(s);//ab如果操作数之一是非字符串值则会调用其 ToString 方法例如
string s a 5;
Console.WriteLine(s);//a5重复使用运算符来构建字符串是低效的。更好的解决方案是使用 System.Text.StringBuilder 类型。
2.7.2.2字符串插值
以$字符为前缀的字符串称为插值字符串。插值字符串可以在大括号内包含表达式:
int x 4;
Console.WriteLine($Square has {x} sides);//Square has 4 sides大括号内可以是任意类型的合法C#表达式。C#会调用其 ToString 方法或等价方法将表达式转换成字符串。若要更改表达式格式可以使用冒号:并继以格式字符串
var x2 0xFF;
string s $255 in hex is {byte.MaxValue:x2};
Console.WriteLine(s);//255 in hex is ff插值字符串只能是在单行内声明除非使用意愿字符串运算符。需要注意$ 运算符必须在 运算符之前 编译错误 加意愿字符解决
int x 2;
string s $this spans {x} lines;
Console.WriteLine(s);若要在插值字符中表示大括号只需要写两个大括号字符即可。
2.7.2.3字符串比较
string 类型不支持 和 的比较。必须使用字符串的 CompareTo 方法。 编译报错 比较用法 例如 StrA.CompareTo(StrB) 如果StrA在StrB排序顺序之前返回值小于0
string a a;
string b c;
Console.WriteLine(a.CompareTo(b));//-1如果StrA在StrB排序顺序一样返回值为0
string a a;
string b a;
Console.WriteLine(a.CompareTo(b));//0如果StrA在StrB排序顺序一后或者StrB为null返回值大于0。
string a c;
string b a;
Console.WriteLine(a.CompareTo(b));//12.8 数组
数组是固定数量的特定类型的集合。为了实现高效的访问数组中的元素总是储存在连续的内存块中。 C#里面的数组用元素类型后加方括号表示。例
char[] chars new char[3];方括号也可用于检索数组数组的索引从零开始通过位置访问特定元素
chars[0] a;
chars[1] b;
chars[2] c;
Console.WriteLine(chars[1]);//b我们可以使用 for 循环语句来遍历数组的每一个元素
for (int i 0; i chars.Length; i)
{Console.Write(chars[i]);
}//abc数组的Length属性返回数组中的元素数目。一旦数组创建完毕它的长度将不能更改。System.Collection命名空间和子命名空间提供了可变长数组和字典等高级数据结构。
数组初始化表达式可以让你一次性声明并填充数组
char[] chars new char[] { a, b, c };简化写法
char[] chars { a, b, c };所有数组都继承自System.Array类。它为所有数组提供了通用服务。包括与数组类型无关的获取和设定数组元素的方法。
2.8.1 默认数组元素初始化
创建数组时其元素总会用默认值初始化类型的默认值是按位取0的内存表示值。例如若定义一个整数数组由于int是值类型因此该操作会在连续的内存块中分配1000个整数。每一个元素的默认值都是0
int[] a new int[1000];
Console.WriteLine(a[111]);//0值类型和引用类型的区别 数组元素的类型是值类型还是引用类型对其性能有重要的影响。若元素类型是值类型每个元素的值将作为数组的一部分进行分配例如
public struct Point
{public int X;public int Y;
}Point[] a new Point[1000];
int x a[500].X; //0若Point是类创建数组则仅仅分配了1000空引用
public class Point
{public int X;public int Y;
}Point[] a new Point[1000];
int x a[500].X; //编译报错NullReferenceException为了避免这个错误我们必须在实例化数组之后显式实例化1000个Point实例
Point[] a new Point[1000];
for (int i 0;i a.Length; i)
{a[i] new Point();
}
int x a[500].X; //0无论元素是何种类型数组本身总是引用类型对象。例如下面语句是合法的
int[] a null;2.8.2 多维数组
多维数组分为两种类型矩形数组和锯齿形数组。矩形数组代表n维的内存块而锯齿形数组则是数组的数组。
2.8.2.1矩形数组
矩形数组声明时用逗号分隔每个维度。下面语句声明了一个矩形二维数组它的维度是3x3
int[,] matrix new int[3,3];数组的 GetLength 方法返回给定维度的长度
for (int i 0; i matrix.GetLength(0); i)
{for(int j 0; j matrix.GetLength(1); j){matrix[i, j] i * 3 j;}
}矩形数组可以按照如下方式进行初始化以下示例创建了一个和上例一样的数组
int[,] matrix new int[,]
{{ 0, 1, 2 },{ 3, 4, 5 },{ 6, 7, 8 }
};2.8.2.2锯齿形数组
锯齿形数组在声明时用一对方括号对表示每个维度。以下例子声明了一个最外层是3的二维锯齿数组
int[][] matrix new int[3][];这里是 new int[3][] 而非 new int[][3] 。
对不同于矩形数组锯齿形数组内层维度在声明时并未指定。每个内层数组都可以是任意长度。每一个内层数组都隐式初始化为null而不是一个空数组因此都需要手动创建。
for (int i 0; i matrix.GetLength(0); i)
{matrix[i] new int[3];for (int j 0; j matrix.GetLength(1); j){matrix[i][j] i * 3 j;}
}锯齿形数组可以按照如下方式进行初始化以下示例创建了一个和上例一样的数组
int[][] matrix new int[][]
{new int[] {0,1,2},new int[] {3,4,5},new int[] {6,7,8,9},
};2.8.3简化数组初始化表达式
有两种方式可以简化数组初始化表达式。
第一种是省略new运算符和类型限制条件
char[] vowels { a, b, c, d, e };int[,] rectangularMatrix {{0, 1, 2},{3, 4, 5},{6, 7, 8}};int[][] jaggedMatirx {new int[]{0, 1, 2},new int[]{3, 4, 5},new int[]{6, 7, 8}};第二种是使用var关键字使编译器隐式确定局部变量类型
var i 3;//implicitly int
var s sssss;//implicitly stringvar rectangularMatrix new int[,] //implicitly int[,]
{{0, 1, 2},{3, 4, 5},{6, 7, 8}
};var jaggedMatirx new int[][]//implicitly int[][]
{new int[]{0, 1, 2},new int[]{3, 4, 5},new int[]{6, 7, 8}
};数组类型可以进一步应用隐式类型转换规则可以直接在 new 关键字之后忽略类型限定符而由编译器推断数组的类型
var vowels new[]{ a, b, c, d, e };为了使上述机制工作数组中的所有元素必须能够隐式转换为一种类型。例
var x new[] { 1, 1000000000 }; //all convertible to long2.8.4边界检查
运行时会为所有数组的索引操作进行边界检查。如果是用了不合法的索引值就会抛出 IndexOutOfRangeException 异常。
int[] arr new int[3];
arr[3] 1;//thow IndexOutOfRangeException数组边界检查对类型安全和调试简化都是非常必要的。 通常边界检查的性能开销很小且JIT(即时编译器)也会对其优化。例如在进入循环之前预先确保所有的索引操作的安全性来避免每次循环中都进行检查。另外C#还提供了 unsafe 代码来显式绕过边界检查。
2.9 变量和参数
变量表示存储着可变值的存储位置。变量可以是局部变量、参数value、ref或out、字段实例或静态以及数组元素。
2.9.1 栈和堆
栈和堆是存储变量和常量的地方。它们分别具有不同生命周期语义。
2.9.1.1 栈
栈是存储局部变量和参数的内存块。逻辑上栈会在函数进入和退出时增加或减少。考虑下面方法为了避免干扰该范例省略了输入参数检查
static int Factorial(int x)
{if (x 0) return 1;return x * Factorial(x - 1);
}这个方法是递归的即调用它自身。每一次进入这个方法的时候就在栈上分配一个新的 int 而每一次离开这个方法就会释放一个 int。
2.9.1.2 递归的风险
递归调用在深度上不可预测层数过多不断压栈可能会引起栈溢出而崩溃。 1.死循环
static int Factorial(int x)
{return x * Factorial(x - 1);
}运行结果 2.栈不够用 例如一个局部变量buffer为10M如果你的栈默认也为10M,这就会发生溢出因为其他东西会占掉少量栈空间局部变量能用的空间肯定小于10M就会跳出 stack overflow异常
3.调用层数太深 在计算机中函数调用是通过栈stack这种数据结构实现的每当程序执行进入一个函数调用栈就会加一层栈帧每当函数返回栈就会减一层栈帧。由于栈的大小不是无限的所以递归调用的次数过多就会导致栈溢出。函数的参数是通过stack栈来传递的在调用中会占用线程的栈资源。递归调用在到达最后的结束点后函数才能依次退栈清栈如果递归调用层数过多就可能导致占用的栈资源超过线程的最大值从而导致栈溢出程序异常退出。 例
static int Factorial(int x)
{if (x 0) return 1;return x * Factorial(x - 1);
}Console.WriteLine(Factorial(100000));运行结果 3.1递归层数太深解决方法 3.1.1将递归改为循环
static int FactorialWhile(int x)
{int num 1;while(x 0){num * x;x--;}return num;
}3.1.2 加线程 定义
static int count 0;
static int total 1;
static void FactorialEnd(int i)
{count;//Console.WriteLine(count);if (i 0){Console.WriteLine(total: total);return;}if (count % 10 0){Thread thread new Thread(delegate(){total * i;FactorialEnd(i - 1);});thread.Start();return;}total * i;FactorialEnd(i - 1);
}调用
FactorialEnd(20000);2.9.1.2 堆
堆是保存对象例如引用类型的实例的内存块。新创建的对象会分配在堆上并返回其引用。程序执行过程中堆就会被新创建的对象不断填充。.NET运行时的垃圾回收器会定时从堆上释放对象因此应用程序不会内存不足。只要对象没有被”存活“的对象引用它就可以被释放。
下面例子中我们创建了一个 StringBuilder 对象并将其引用赋值给 ref1 变量之后在其中写入内容。StringBuilder 对象在后续没有使用的情况下可立即被垃圾回收器释放。
之后我们创建另一个 StringBuilder 对象赋值给 ref2 再将引用复制给 ref3 。虽然 ref2 之后便不再使用但是由于 ref3 保持着同一个 StringBuilder 对象的引用因此在 ref3 使用完毕之前它不会被垃圾回收器回收。
StringBuilder ref1 new StringBuilder(object1);
Console.WriteLine(ref1);StringBuilder ref2 new StringBuilder(object2);
StringBuilder ref3 ref2;
Console.WriteLine(ref3);值类型的实例和对象的引用就存储在变量声明的地方。如果声明为类的字段或数组的元素则该实例会存储在堆上。C#中无法像C那样显示的删除对象。未引用的对象最终将被垃圾回收器回收。
静态字段也会存储在堆上。与分配在堆的对象可以被垃圾回收器回收不同这些变量一直存活直至应用程序域结束。
2.9.2 明确赋值
C#强制执行明确赋值策略。实践中这意味着在 unsafe 上下文之外无法访问未初始化的内存。明确赋值有三种含义 1.局部变量在读取之前必须赋值。 2.调用方法时必须提供函数的实际参数。 3.运行时将自动初始化其他变量例如字段和数组元素。
例如以下示例将产生编译错误
字段和数组会自动初始化为其类型的默认值。以下代码输出0就是因为数组元素会隐式赋值为默认值
int[] ints new int[2];
Console.WriteLine(ints[0]);//0以下代码输出0因为字段会隐式赋值为默认值
static int x;
static void main()
{Console.WriteLine(x);//0
}2.9.3 默认值
所有类型的实例都有默认值。预定义类型的默认值是按位取0的内存表示的值。
类型默认值所有引用类型null所有数字和枚举类型0char类型‘\0’bool类型false
default 关键字可用于获得任意类型的默认值这对泛型非常有用
decimal d default(decimal);
Console.WriteLine(d);//0自定义值类型例如struct的默认值等同于每一个字段都取其默认值。
2.9.4 参数
方法可以有一连串参数。在调用方法时必须为这些参数提供实际值。
方法
static void Foo(int p)
{p p 1;Console.WriteLine(p);
}调用
Foo(10);使用 ref 和 out 可以控制参数的传递方式
参数修饰符传递类型必须明确赋值的变量无按值传递传入ref按引用传递传入out按引用传递传出
2.9.4.1 按值传递参数
默认情况下C#中的参数默认按值传递这是最常用的方式。这意味着在将参数值传递给方法的时将创建一份参数值的副本。
方法
static void Foo(int p)
{p p 1;Console.WriteLine(p);
}调用
int x 8;
Foo(x);//make a copy of x
Console.WriteLine(x); //8为p赋一个新的值并不会改变x的值因为p和x分别存储在不同的内存位置中。 按值传递引用类型参数复制的是引用本身而非对象本身。下例中Foo方法中的StringBuilder对象和Main方法中实例同化的是同一个对象但是它们的引用不同。换句话说变量sb个fooSB是引用同一个StringBuilder对象的不同变量。
方法
static void Foo(StringBuilder fooSB)
{fooSB.Append(test);fooSB null;
}Main方法调用
StringBuilder sb new StringBuilder();
Foo(sb);
Console.WriteLine(sb.ToString());//test由于fooSB是引用的一份副本因此将它赋值为null并不会把sb也赋值为null然而如果在声明和调用fooSB时使用ref修饰符则sb会变成null。
2.9.4.2 ref修饰符
在C#中若按引用传递参数则应使用ref参数修饰符。下面例子中p和x指向同一块内存位置
static void Foo(ref int p)
{p p 1;Console.WriteLine(p);
}int x 8;
Foo(ref x);
Console.WriteLine(x);现在给p赋新值将改变x的值。注意 ref 修饰符在声明和调用时都是必须的。
ref 修饰符对于实现交换方法是必要的。
static void Swap(ref string a,ref string b)
{string temp a;a b;b temp;
}string x Panny;
string y Teller;
Swap(ref x, ref y);
Console.WriteLine(x);//Teller
Console.WriteLine(y);//Panny无论参数是引用类型还是值类型都可以按引用传递或值传递。
2.9.4.3 out修饰符
out参数和ref参数类似但在以下几点上不同 1.不需要在传入函数前赋值。 2.必须在函数结束之前赋值。
out修饰符通常用于获得方法的多个返回值例如
static void Split(string name, out string firstNames,out string lastName)
{int i name.LastIndexOf( );firstNames name.Substring(0,i);lastName name.Substring(i1);
}string a, b;
Split(Stevie Ray Vaughan,out a,out b);
Console.WriteLine(a);//Stevie Ray
Console.WriteLine(b);//Vaughan与ref参数一样out参数按引用传递。
2.9.4.4 out变量及丢弃变量
从C#7开始允许调用含有 out 参数的方法时直接声明变量。因此我们可以将前面的例子简化为
Split(Stevie Ray Vaughan,out string a,out string b);
Console.WriteLine(a);//Stevie Ray
Console.WriteLine(b);//Vaughan当调用含有多个out参数方法时若我们并非关注所有参数值那么可以用下划线来”丢弃“不感兴趣的参数
Split(Stevie Ray Vaughan,out string a,out _);
Console.WriteLine(a);//Stevie Ray此时编译器会将下划线认定为一个特殊的符号称为丢弃符号。一次调用可以引入多个丢弃符号。假设SomeBigMethod定义了7个out参数除第4个之外的其他全部被丢弃。
static void SomeBigMethod(out int a1, out int a2, out int a3, out int a4, out int a5, out int a6, out int a7)
{a1 1;a2 2;a3 3;a4 4;a5 5;a6 6;a7 7;
}SomeBigMethod(out _, out _, out _, out int x, out _, out _, out _);
Console.WriteLine(x);//42.9.4.5 按引用传递的含义
按引用传递参数是为了现存变量的存储位置起了一个别名而不是创建了一个新的存储位置。下面的例子中x和y代表相同的实例
方法
static int x;
static void Yoo(out int y)
{Console.WriteLine(x);//0y 1;Console.WriteLine(x);//1
}调用
Yoo(out x);2.9.4.6 params修饰符
params参数修饰符只能修饰方法中的最后一个参数它能够使方法接受任意数量的指定类型参数。参数类型必须声明为数组。例如
static int Sum(params int[] ints)
{int sum 0;for(int i 0;iints.Length;i){sum ints[i];}return sum;
}int total Sum(1,2,3,4);
Console.WriteLine(total);//10也可以将普通数组提供给params参数。和上例方法等价
int total Sum(new int[] { 1, 2, 3, 4 });
Console.WriteLine(total);//102.9.4.7 可选参数
从C#4.0开始方法构造器和索引器中都可以声明可选参数。只要在参数声明中提供默认值这个参数就是可选参数
static void Xoo(int x 23)
{Console.WriteLine(23);
}可选参数在调用方法时可以省略
Xoo();默认参数23实际上传递给了可选参数x编译器在调用端将值23传递到编译好的代码中。上例中调用Xoo的代码语义上等价于
Xoo(23);这是由于编译器在用到可选参数的地方使用默认值代替可选参数而造成的。
若public方法对其他程序集可见则在添加可选参数时双方均需要重新编译就像参数是必须提供的一样。
2.9.4.8 命名参数
除了用位置确定参数外还可以用名称来确定参数例如
static void Test()
{Foo(x: 1, y: 2);//1:2
}static void Foo(int x, int y)
{Console.WriteLine(x : y);
}命名参数能够以任意顺序出现。下面两种调用Foo方式在语义上是一样的
Foo(x: 1, y: 2);//1:2
Foo(y: 2, x: 1);//1:2上述写法的不同之处是参数表达式将按调用端参数出现的顺序计算。通常这种不同只出现在非独立的拥有副作用的表达式中。例如下面的代码将输出1:0
static void Foo(int x, int y)
{Console.WriteLine(x : y);
}int a 0;
Foo(y: a, x: a--);命名参数和可选参数可以混合使用
Foo(1, y: 2);当然这里有一个限制按位置传递的参数必须出现在命名参数之前。因此不能这样调用Foo方法
static void Foo(int y, int x)
{Console.WriteLine(x : y);
}编译错误 命名参数在和可选参数混合使用时特别有效。例
void Bar(int a 0, int b 0, int c 0, int d 0){}我们可以用以下方式在调用它的时候仅提供d的值
Bar(d:3);2.9.5 引用局部变量
C#7中添加了一个令人费解的特性即定义了一个用于引用数组中某一个元素或对象中某一个字段的局部变量
int[] numbers { 0, 1, 2, 3, 4 };
ref int numRef ref numbers[2];
numRef 100;
Console.WriteLine(numbers[2]);//100在这个例子中。numRef是numbers[2]的引用。当我们更改numRef的值时也相应更改了数组中的元素值。
引用局部变量的目标只能是数组的元素对象字段或者局部变量而不能是属性。引用局部变量适用于在特定的场景下进行小范围优化并通常和引用返回值合并使用。
2.9.6 引用返回值
从方法中返回的引用局部变量称为引用返回值ref return
声明
static string X Old value;
static ref string GetX() ref X;调用
ref string xRef ref GetX();
xRef New Value;
Console.WriteLine(X);//New Value2.9.7 var引用类型局部变量
我们通常会在一步中完成变量的声明和初始化。如果编译器能够从初始化表达式中推断出变量的类型就能够使用var关键字来代替类型声明例如
var x hello;
var y new System.Text.StringBuilder();
var z (float)Math.PI;完全等价于
string x hello;
System.Text.StringBuilder y new System.Text.StringBuilder();
float z (float)Math.PI;因为完全等价所以隐式类型变量仍是静态的。 编译报错
var x 5;
x hello;//编译报错必须使用var的情况匿名类型
2.10 表达式和运算符
表达式的本质是值。最简单的表达式是常量和变量。表达式能够用运算符进行转换和组合。运算符用一个或者多个输入操作数来输出一个新的表达式。
常量表达式例子
12可以用 * 运算符来组合两个操作数。
12*13由于操作数本身可以是表达式使用可以创建出更复杂的表达式。例如(12 * 13)是下面表达是中的操作数。
1 (12 * 13)C#中的运算符分为一元运算符、二元运算符和三元运算符这取决于它们使用的操作数数量1、2或3。二元运算符总是使用中缀发表示在两个操作数直间。
一元运算符 示例
a--,a,a,--a二元运算符 示例
ab,a-b,a*b,a/b,a%b,aba^b,a|b由于操作数本身可以是表达式则如下为二元运算符
1 (12 * 13)三元运算符 示例
ab-c,ab*c.....条件运算符也是三元运算符
ab ? a:b,ab ? a:b,多元运算符 总之看操作数或者条件运算符的嵌套层数嵌套n层则为n3元运算符。
2.10.1 基础表达式
基础表达式由C#内置的基础运算符表达式组成例如
Math.Log(1)这个表达式由两个基础表达式构成第一个表达式执行成员查找用.运算符而第二个表达式执行方法调用用运算符。
2.10.2 空表达式
空表达式是没有值的表达式例如
Console.WriteLine(1);2.10.3 赋值表达式
赋值表达式用 运算符将另一个表达式的值赋值给变量例如
x x * 5;赋值表达式不是一个空表达式它的值即是被赋予的值。因此赋值表达式可以和其他表达式组合。下面例子中将2赋给x并将10赋给y:
y 5 * (x 2);这种类型也可以用于初始化多个值
a b c d 0复合赋值运算是由其他运算符组合而成的简化运算符。例如
x *2
x 1这条规则的例外是事件event。它的和-运算符会特殊对待并映射至事件的add和remove访问器上。
2.10.4 运算符优先级和结合性
当表达式包含多个运算符时运算符优先级和结合性决定了计算的顺序。优先级高的运算符先于优先级低的运算符执行。如果运算符的优先级相同那么运算符的结合性决定计算的顺序。
2.10.4.1 优先级
以下表达式
1 2 * 3由于*的优先级高于因此它将按下面的方式计算
1 (2 * 3)2.10.4.2 左结合运算符
二元运算符除了赋值运算符、Lambda表达式、null合并运算符是左结合运算符。换句话说它们是从左往右计算。例如
8 / 4 / 2由于左结合性将按如下的方式计算
(8 / 4) / 2 //1插入括号可以改变实际的计算顺序
8 / (4 / 2) //42.10.4.3 右结合运算符
除了赋值运算符、Lambda表达式、null合并运算符和条件运算符是右结合的。换句话说它们是从右往左计算。右结合性允许多重赋值例如
x y 3;首先将3赋值给y,之后再将表达式(3)的结果赋值给x.
2.10.5 运算符表
C#的运算符按照优先级顺序分类
类别运算符符号运算符名称示例是否可重载基础.成员访问x.y否-不安全代码结构体指针x-y否( )函数调用x()否[ ]数组/索引a[x]通过索引器后自增x是- -后自减x- -是new创建实例new Fo()否stackalloc不安全的栈空间分配stackalloc(10)否typeof从标识符中获得类型typeof(int)否nameof从标识符中获取名称nameof(x)否checked检测整数溢出checked(x)否unchecked不检测整数溢出unchecked(x)否default默认值default(char)否await等待异步操作await myTask否sizeof获得结构体大小sizeof(int)否?.null条件运算符x?.y否正数x是-负数-x是!非!x是~按位取反~x是前自增x是- -前自减- -x是( )转换(int) x否*(不安全代码)取地址中的值*x否(不安全代码)取值的地址x否乘法*乘x * y是/除x / y是%取余x % y是加法加x y是-减x - y是位移左移x1是右移x1是关系小于x y是大于x y是小于等于x y是大于等于x y是is类型是/是子类x is y否as类型转换x as y否相等x y是!不相等x ! y是逻辑与与x y是逻辑异或^异或x ^ y是逻辑或|或x | y是条件与条件与x y通过条件或||条件或x || y通过|null合并??null合并运算符x ?? y否条件?:条件运算符ab ? a:b否赋值与Lambda赋值x y否*自身乘x * 2通过*/自身除x / 2通过/自身加x 2通过-自身减x - 2通过-自身左移x 2通过自身右移x 2通过自身与x 2通过^自身异或x ^ 2通过^|自身或x | 2通过 |Lambdax x 1否
2.11 null运算符
C#提供了两个简化null处理的运算符null合并运算符和null条件运算符。
2.11.1 null合并运算符
null合并运算符写作??。它的意思是如果操作数不是null则结果为操作数否则结果为一个默认的值。例如
string s1 null;
string s2 s1 ?? nothing;
Console.WriteLine(s2);//nothing如果左侧的表达式不是null,则右侧的表达式将不会进行计算。null合并运算符同样适用于可空的值类型。
2.11.2 null条件运算符
C#6中引入了?.运算符称为null条件运算符或者Elvis运算符。该运算符可以像标准的“.”运算符那样访问成员以及调用方法。当运算符的左侧为null的时候该表达式的运算结果也是null而不会抛出 NullReferenveException 异常。
StringBuilder sb null;
string s sb?.ToString();
Console.WriteLine(s);//上述代码的第二行等价于
string s (sb null ? null : sb.ToString());当遇到null时Elvis运算符将直接略过表达式的其余部分。在接下来的例子中即使ToString()和ToUpper()方法使用的是标准的.运算符s的值仍然为null。
StringBuilder sb null;
string s sb?.ToString().ToUpper();仅当直接的左侧运算数有可能为null的时候才有必要重复使用Elvis运算符。因此下述表达式在x和y都为null时依然是健壮的
x?y?.z它等价于唯一不同在于x.y只执行了一次
x null ? null : (x.y null ? null : x.y.x)需要注意最终的表达式必须能够处理null,因此下面的范例是不合法的
StringBuilder sb null;
int length sb?.ToString().Length;//int cannot be null我们可以使用可空类型来修正这个错误
StringBuilder sb null;
int? length sb?.ToString().Length;//int? can be null我们也可以使用null运算符调用返回值为void的方法
someObject?.SomeVoidMethod();如果someObject为null,则表达式将不执行指令而不会抛出 NullReferenceException 异常
null条件运算符可以和第3章介绍的常用类型成员一起使用包括方法、字段、属性和索引器。而且它也可以和null合并运算符一起使用。
StringBuilder sb null;
string s sb?.ToString() ?? nothing;
Console.WriteLine(s);//nothing2.12 语句
函数是语句构成的。语句按照出现的字面量顺序执行。语句块则是在大括号({})中的一系列语句。
2.12.1 声明语句
声明语句可以声明新的变量并可以用表达式初始化变量。声明语句以分号结束。可以用逗号分隔的列表声明多个同类型的变量。例如
bool rich true, famous false;常量的声明和变量类似但是它的值无法在声明之后改变并且变量初始化必须和声明同时进行
const double c 2.12314;
c 10;//编译时报错局部变量 局部变量和常量的作用范围在当前语句块中。当前语句块或者嵌套的语句块中声明一个同名的局部变量是不行的例如
int x;
{int y;int x;//报错xx已经定义
}
{int y;
}
Console.WriteLine(y);//报错y在范围外变量的作用范围是它所在的整个代码块向前向后都包含。这意味着虽然变量或常量声明之前引用它是不合法的但即使实例中的x初始化移动的 方法的末尾我们也会得到相同的错误这个规则与c不同。
2.12.2 表达式语句
表达式语句既是表达式也是合法的语句。表达式语句必须改变状态或者执行某些可能改变状态的调用。状态改变本质上是改变一个变量的值。可能的表达式语句有 1.赋值表达式包括自增和自减表达式0 2.有返回值和没有返回值方法调用表达式 3.对象实例化表达式
string s;
int x, y;
StringBuilder sb;//Expreddion statements
x 1 2; //Assignment expression
x; //Increnment expression
y Math.Max(x, 5); //Assignment expression
Console.WriteLine(y); //Method call expression
sb new StringBuilder();//Assignment expression
new StringBuilder(); //Object instantiation expression当调用有返回值的构造器或方法时并不一定要使用它的返回值。因此除非构造器或方法改变了某些状态否则以下这些语句完全没有用处
new StringBuilder();
new string(c, 3);
x.Equals(y);2.12.3 选择语句
C#使用以下几种机制来有效的控制程序的执行流 1.选择语句if、switch 2.条件语句?: 3.循环语句while、do…while、for和foreach
2.12.3.1 if语句
if 语句在bool表达式为真时执行的语句。例如
if(5 2 * 3)Console.WriteLine(true);if 语句可以时代码块
if(5 2 * 3)
{Console.WriteLine(true);Console.WriteLine(lets move on);
}2.12.3.2 if语句
if语句之后可以紧跟else子句
if (2 2 5)Console.WriteLine(Does not compute);
elseConsole.WriteLine(False);在else语句中能嵌套另一个if语句
if (2 2 5)Console.WriteLine(Does not compute);
else if(2 2 4)Console.WriteLine(compute);2.12.3.3 用大括号改变执行流
else子句总是与它之前的语句块中紧邻的未配对的if语句结合。例如
if (true)if (false)Console.WriteLine();elseConsole.WriteLine(executes);可以通过改变大括号的位置来改变执行流
if (true)
{if (false)Console.WriteLine();
}
elseConsole.WriteLine(does not executes);大括号可以明确表示结构这能提高嵌套if的可读性。需要特别指出的是下面模式
static void TellChatMethod(int age)
{if (age 35)Console.WriteLine(You can be persident!);else if (age 21)Console.WriteLine(You can drink!);else if (age 18)Console.WriteLine(You can vote);elseConsole.WriteLine(You can wait!);
}这里参照其他语言的elseif结构以及C#的预处理指令来安排if和else语句。Visual Studio 自动识别这个模式并保持代码缩进。紧跟着每一个if语句的else语句从功能上都是嵌套在else子句中的。
2.12.3.4 switch语句
switch语句可以根据变量可能的取值来转移程序的执行。switch语句可以拥有比嵌套if语句更加简洁的代码。例如
static void ShowCard(int carNumber)
{switch (carNumber){case 0:Console.WriteLine(King);break;case 1:Console.WriteLine(Queen);break;case 2:Console.WriteLine(Jack);break;case -1:goto case 2;default: Console.WriteLine(carNumber); break;}
}这个例子演示了针对常量的 switch 。当指定常量时只能指定内置的整数类型、bool、char、enum、string类型。
每一个case子句结束时必须使用某种跳转指令显式指定下一个执行点除非你的代码是一个无限循环。这些跳转指令有 1.break(跳转到switch语句的最后) 2.goto case x跳转到另外一个case子句 3.goto default跳转到default子句 4.其他跳转语句return、throw、continue、goto label 当多个值要执行相同的代码时可以按照顺序列出共同的case条件
switch (cardNumber)
{case 0:case 1:case 2:Console.WriteLine(a card);break;case -1:goto case 2;default:Console.WriteLine(card);break;
}switch语句比多个if-else更简洁
2.12.3.5 带有模式的switch语句
C#7 开始支持按类型switch
static void TellMeTheType(object x)
{switch (x){case int:Console.WriteLine(Its an int!);break;case string s:Console.WriteLine(Its a string);break;default:Console.WriteLine(I dont know what x is);break;}
}object 类型允许其变量未任何类型。
每一个case子句都指定了一种需要匹配的类型和一个变量如果类型匹配成功就对变量赋值。和常量不同对于类型的使用并没有任何限制。
还可以用when关键字对case进行预测例如
static void TheType(object x)
{switch (x){case bool b when b true:Console.WriteLine(True!);break;case bool b:Console.WriteLine(False!);break;}
}case子句的顺序会影响类型的选择。如果交换case的顺序则上述示例可以得到完全不同的结果。但default子句是一个例外不论它出现在什么地方都会在最后执行。
堆叠多个case子句也是没有问题的。Console.WriteLine会在任何浮点类型的值大于1000时执行
static void ValueType(object x)
{switch (x){case float f when f 1000:case double d when d 1000:case decimal m when m 1000:Console.WriteLine(We can refer to x here but bot);break;}
}when 子句中使用模式变量 f,g和m。当调用 Console.WriteLine 时我们并不清楚到底三个模式变量中的哪一个会被赋值因而编译器会将它们放在作用域之外。
除此之外还可以混合使用常量选择和模式选择甚至可以选择 null 值
case null:break;2.12.4 迭代语句
C#中可以使用while、do while、for、和foreach语句重复执行一系列语句。
2.12.4.1 while和do-while循环
while循在其bool表达式为true的情况下重复执行循环体中的代码。这个表达式在循环体执行之前进行检测。例如
int i 0;
while(i 3)
{Console.WriteLine(i);i;
}do-while 循环在功能上不同于while循环的地方是它在语句块执行之后才检查表达式的值保证语句块至少执行过一次。以下例子重新用do-while循环重新书写了一遍
int i 0;
do
{Console.WriteLine(i);i;
}
while (i 3);2.12.4.2 for循环
for循环就像有特殊子句的while循环。这些特殊子句用于初始化和迭代循环变量。for循环有三个子句
for(initiation-clause;condition-clause;iteration-clause)
statement-or-statement-block1.初始化子句在循环之前执行初始化一个或多个迭代变量。 2.条件子句它是一个bool表达式当其为true时将执行循环体。 3.迭代子句在每次语句块迭代之后执行通常用于更新迭代变量。
例如下面例子将打印 0-2
for (int i 0; i 3; i)
{Console.WriteLine(i);
}下面的代码将打印前10个斐波那契数每一个数都是前面两个数和
for (int i 0,prevFib 1,curFib 1; i 10; i)
{Console.WriteLine(prevFib);int newFib prevFib curFib;prevFib curFib;curFib newFib;
}for语句的这三个部分都可以省略因而可以通过下面的代码来实现无限循环也可以用while(true)来代替
for(;;)Console.WriteLine(Log);2.12.4.3 foreach循环
foreach语句遍历可枚举对象的每一个元素。大多数C#和.NET Framework中表示集合或元素列表的类型都是可枚举的。例如数组和字符串都是可枚举的。例
foreach(char c in beer)Console.WriteLine(c);2.12.5 跳转语句
C#中的跳转语句有break、continue、goto、return和throw。 跳转语句仍然遵守try语句的可靠性规则。这意味着
1.到try语句块之外的跳转总是在达到目标之前执行try语句的finally语句块。 2.跳转语句不能从finally语句块跳到快外除非使用throw。
2.12.5.1 break语句
break 语句用于结束迭代或switch语句的执行
int x 0;
while (true)
{if (x 5)break;
}2.12.5.2 continue语句
continue语句放弃循环体中其后的语句继续下一轮迭代。例如以下循环跳过了偶数
for (int i 0; i 10; i)
{if (i % 2 0)continue;Console.Write(i );
}2.12.5.3 goto语句
goto语句将执行点转移到语句块的指定标签处。格式如下
goto statement-label;或者用于switch语句内
goto case case-constant;标签语句仅仅是代码块中的占位符位于语句之前用冒号后缀表示。下面的代码模拟for循环来遍历从1到5的数子
int i 1;
startLoop:
if(i 5)
{Console.Write(i );i;goto startLoop;
}goto case case-constant 会将执行点转移到switch语句块的另一个条件上。
2.12.5.4 return语句
return语句用于退出方法。如果这个方法有放回值则必须返回方法指定返回类型的表达式。
static decimal AsPercentage(decimal d)
{decimal p d * 100m;return p;
}return 语句能够出现在方法的任意位置除finally块中。
2.12.5.5 throw语句
throw语句抛出异常来表示有错误发生
Person p null;
if (p null)throw new ArgumentNullException(P 未实例化);2.12.6 其他语句
using 语句用一种优雅的语法在finally块中调用实现了 IDisposable 接口对象的 Dispose方法。 C#重载了using关键字使它在不同上下文有不同的含义。特别注意using指令和using语句是不同的 lock语句是调用mintor类型的Enter和Exit方法的简化写法。
2.13 命名空间
命名空间是一系列类型名称的领域。通常情况下类型组织在分层的命名空间里既避免了命名冲突又更容易查找。例如处理公钥加密的RSA类型就定义在如下的命名空间下
System.Security.Cryptography.RSA命名空间组成了类型名的基本部分。例下面代码调用了RSA类型的Create方法
System.Security.Cryptography.RSA rsa System.Security.Cryptography.RSA.Create();拓展
string s 123小明是木头;
byte[] data Encoding.UTF8.GetBytes(s);
Console.WriteLine(初始字节);
for (int i 0; i data.Length; i)
{Console.Write(data[i] );
}
Console.WriteLine();
System.Security.Cryptography.RSA rsa System.Security.Cryptography.RSA.Create();
byte[] rasEncryptData rsa.Encrypt(data, RSAEncryptionPadding.OaepSHA256);Console.WriteLine(rasEncryptData rasEncryptData.Length);
for (int i 0; i rasEncryptData.Length; i)
{Console.Write(rasEncryptData[i] );
}byte[] rawData rsa.Decrypt(rasEncryptData, RSAEncryptionPadding.OaepSHA256);
Console.WriteLine(rawData rawData.Length);
for (int i 0; i rawData.Length; i)
{Console.Write(rawData[i] );
}
Console.WriteLine();
string rawStr Encoding.UTF8.GetString(rawData);
Console.WriteLine(原来的值为 rawStr);命名空间是独立于程序集的程序集是像.exe或者.dll一样的部署单元。命名空间并不影响成员的public、internal、private的可见性。
namespace 关键字为其中定义了命名空间。例如
namespace In.Middle.Inner
{class Class1 { }class Class2 { }
}命名中的“.”表明了嵌套命名空间的层次结构。下面的代码在语义上和上一个例子是等价的
namespace In
{namespace Middle{namespace Inner{class Class1 { }class Class2 { }}}
}如果类型没有在任何命名空间中定义则它存在于全局命名空间中。全局命名空间也包含了顶级命名空间如例子中的 In 命名空间。
2.13.1 using指令
using指令用于导入命名空间。这是避免使用完全限定名称来指代某种类型的快捷方法。例如
using In.Middle.Inner;
public class Test1
{static void Main() {Class1 c1;}
}在不同命名空间中定义相同类型名称是合法的。然而这种做法通常出现在开发者不会同时导入两个命名空间时。
2.13.2 using static指令
从C#6开始我们不仅可以导入命名空间还可以使用 using static 指令导入特定的类型。这样就可以直接使用类型的静态成员而不需要指定类型名称了。调用 Console 类的静态方法WriteLine:
using static System.Console;
public class Test
{static void Main() {WriteLine(hello);}
}using static 指令将类型的可访问的静态成员包括字段、方法、属性以及嵌套类型全部导入进来。同时该指令也支持导入枚举类型的成员。因此如果导入枚举类型后可能会出现相同的枚举的情况多个静态类型导致二义性时会发生编译错误。
2.13.3 命名空间中的规则
2.13.3.1 名称范围
外层命名空间中声明的名称能够直接在内层命名空间中使用。以下示例中的Class1在Inner中不需要限定名称:
namespace Outer
{class Class1 { }namespace Inner {class Class2 : Class1{ }}
}使用同一命名分层结构中不同分支的类型需要使用部分限定名称。在下面的例子中SalesReport类继承Common.ReportBase:
namespace Common
{class ReportBase{}namespace ManagementReporting{class SalesReport : Common.ReportBase { }}
}2.13.3.2 名称隐藏
如果相同名称同时出现在内层和外层的命名空间中则内层类型优先。如果要使用外层命名空间中的类型必须使用它的完全限定名称。
namespace Outer
{class Foo { }namespace Inner {class Foo { }class Test{Foo f1;//Outer.Inner.FooOuter.Foo f2;//Outer.Foo}}
}所有的类型名在编译时都会转换为完全限定名称。中间语言IL代码不包含非限定名称和部分限定名称。
2.13.3.3 重复的命名空间
只要命名空间内的类型名称不冲突就可以重复声明同一个命名空间
namespace In.Middle.Inner
{class Class1 { }
}namespace In.Middle.Inner
{class Class2 { }
}上述例子也可以分为两个不同的源文件并将每一个类都编译到不同的程序集中。
源文件1
namespace In.Middle.Inner
{class Class1 { }
}源文件2
namespace In.Middle.Inner
{class Class2 { }
}2.13.3.4 嵌套的using指令
我们能够在命名空间中嵌套使用using指令这样可以控制using指令在命名空间声明中的作用范围。在以下例子中Class1在一个命名空间中可见但是在另一个命名空间中不可见
namespace N1
{class Class1 { }
}
namespace N2
{using N1;class Class2 : Class1 { }
}namespace N2
{class Class3 : Class1 { }//Compile-time error
}2.13.4 类型和命名空间别名
导入命名空间可能导致类型名称的冲突因此可以只导入需要的特定类型而不是整个命名空间并给它们创建别名。例如
using PropertyInfo2 System.Reflection.PropertyInfo;
class Program
{PropertyInfo2 p;
}下面代码为整个命名空间创建别名
using R System.Reflection;
class Program2
{R.PropertyInfo p;
}2.13.5 高级命名空间
2.13.5.1 外部别名
使用外部别名就可以引用两个完全限定的名称相同的类型例如命名空间和类型名称都相同。这种情况只在两种类型来自不同的程序集时才会出现。 程序库1 // csc target:library / out:Widgets1.dll widgetsv1.cs
namespace widgets
{public class Widget{}
}程序库2 // csc target:library / out:Widgets2.dll widgetsv2.cs
namespace widgets
{public class Widget{}
}应用程序 // csc r:Widgets1.dll / r:Widgets2.dll application.cs
using widgets;
class Test
{static void Main{Widget w new Widget();}
}这个应用程序无法编译因为Widget类型具有二义性。外部别名可以消除应用程序中的二义性 // csc r:Widgets1.dll / r:Widgets2.dll application.cs
extern alias W1;
extern alias W2;
class Test
{static void Main{Widget w new Widget();Widget w new Widget();}
}补 1.首先新建两个类库Widget1Widget2 2.项目引入 3.更改别名以W1为例
4.上代码
extern alias W1;
extern alias W2;
W1.Widget.Widgets.Widget();//Widget1
W2.Widget.Widgets.Widget();//Widget22.13.5.2 命名空间别名限定符
之前提到的内层命名空间中的名称隐藏外层命名空间中的名称。但是有时即使使用类型的完全限定名也无法解决冲突。请考虑下面的例子
namespace N
{class A{public class B{}static void main(){new A.B();}}
}namespace A
{class B{}
}Main方法会实例化嵌套类B或命名空间A中的类B。编译器总是给当前命名空间中的标识符以更高的优先级;在这种情况下将会实例化嵌套类B。
要解决这样的冲突可以使用如下的发式限定命名空间的名称 1.全局命名空间即所有命名空间的根命名空间由上下文关键字global指定 2.一系列外部别名 “ :: ”用于限定命名空间别名。下面的例子中我们使用了全局命名空间这通常出现在自动生成的代码中以避免冲突
新建一个A.cs文件代码如下
namespace N
{public class A{public class B{public B(){Console.WriteLine(N.A.B);}}public static void main(){new A.B();new global::A.B();}}
}namespace A
{public class B {public B(){Console.WriteLine(A.B);}}
}main方法调用 N.A.main();以下例子使用了别名限定符
extern alias W1;
extern alias W2;
W1::Widget.Widgets.Widget();//Widget1
W2::Widget.Widgets.Widget();//Widget2