【IT168 技术讲解】处理器不能直接解释一个程序集.程序集采用的是另一种语言,即公共中间语言(Common Intermediate Language, CIL),或者简称为中间语言(IL).
注意 CIL的另一种说法即MSIL, 即Microsoft IL.
C#编辑器只是将C#源代码文件转换成为CIL.为了将CIL代码转换成处理器能够理解的机器码,还要执行一个额外的步骤(该步骤通常是在执行时完成的).这个步骤涉及C#执行中的一个重要元素:VES(Virtual Execution System,虚拟执行系统).VES也被人称为“运行时”,它根据需要来编译CIL代码,这个过程称为即时编译或者JIT编译(just-in-time compilation).
托管代码(managed code) :代码在"运行时"这样一个"代理"的上下文中执行.
托管执行(managed execution):在"运行时"的控制下执行的过程.
非托管代码(unmanaged code):在执行过程中不需要"运行时"的代码.
CLI(common Languageinfrastructure,公共语言基础结构)
VES规范被包含在一个包容面更广的规范中,即CLI(common Languageinfrastructure,公共语言基础结构)规范.作为一个国际行标准,CLI包含了以下几方面的规范.
VES或"运行时".
CIL
为语言互操作性提供支持的一个类型系统,成为CTS(Common Type System,公共类型系统).
如何编写能通过各种CLI兼容语言访问的库的指导原则,这部分内容具体放在公共语言规范(Common Language Specification CLS)中.
市各种服务能被CLI(包括程序集的布局或文件格式规范)识别的元数据
一个公共编程框架,称为基类库(Base Class Library, BCL),所有语言的开发者都能利用它.
语言互操作性:不同源语言之间的互操作性.语言编译器将每一种源语言转换成相同的中间语言(CIL),从而实现这种互操作性.
类型安全:检查类型间的转换,确保只有相互兼容的类型才会进行转换.这有助于防止发生缓冲区溢出.造成安全问题的一个主要诱因.
代码访问安全性:程序集开发者的代码有权在计算机上执行的证明
垃圾回收
平台可移植性
BCL
基本数据类型
C#数据类型大体有:基本数据类型(整数/浮点/decimal类型)、布尔类型、字符类型、字符串类型
C#共有8种整数类型,可以选择恰当的一种数据类型来存放数据,避免浪费资源. 下面对每种整数类型进行总结:
C#基本字符串类型为string(C#中类型区分大小写),BCL名称为System.string.
在这里讲一点string比较深入的内容.
length属性
length()方法,主要用于获取数据类型的长度,而在string类型中,length实际只是string的一个属性,C#语法允许像访问一个成员变量那样访问一个属性.为了判断一个字符串长度,可以使用string的length成员,我们将这个特定的成员称为一个只读属性.所以不能设置它,调用它也不需要任何参数.
代码清单 使用string的length成员
static void Main()
{
string palindrome;
System.Console.Write("Enter a palindrome: ");
palindrome = System.Console.ReadLine();
System.Console.WriteLine("The palindrome, \"{0}\" is {1}
characters.",palindrome,palindrome.length);
}
}
字符串是不可变的
string类型的关键特征在于它是不可变的。可以为一个string变量赋一个全新的值,但出于对性能的考虑,没有机制可供修改一个字符串的内容.
代码清单 错误,string是不可变的
{
static void Main()
{
string text;
System.Console.Write("Enter text: ");
text = System.Console.ReadLine();
text.ToUpper();
System.Console.WriteLine(text);
}
}
输出
Enter text:This is a test of the emergency broadcast system.
System.Text.string 和 System.Text.StringBuilder 区别?
如果有大量字符串进行修改,比如要经历多个步骤来构造一个长字符串,那么应当使用System.Text.StringBuilder.string的原理是在栈中新建一个内存地址,把原有的对象复制到新的地址,然后删除原来的对象.这一来二去的操作很麻烦,效率也不高.这个时候可以改用System.Text.StringBuilder,StringBuilder提供了Append()、AppendFormat()、Insert()、Remove()和Replace()这样的方法.但两者的关键区别在于,在System.Text.StringBuilder上,这些方法会修改变量中的数据,而不是返回一个新字符串.
null和void
与类型有关的两个额外的关键字是null和void.null是一个字面值,用来表示数据类型(具体就是引用类型)未被赋予任何值.void用来表示没有类型,或者没有任何值.
null值只能赋给引用类型,不能赋给值类型.引用类型包含一个指针、一个地址、或者一个引用,它指向内存中的一个特定位置.
必须注意的是,和根本不进行赋值相比,将null赋给一个引用类型完全是不同的概念.换言之,赋值为null的变量已被设置,而未进行赋值的变量未被设置.所以假如在赋值前使用变量,通常会造成一个编译时错误.
null意味着变量无任何值,而""意味着变量有一个值,一个空字符串.
void本质上并不是一个数据类型,它只是用于指出没有数据类型这一事实.
C#中的数组声明,使用方括号来声明数组变量.首先要指定数组中数据项的类型,后跟一对方括号,再输入变量名.显然,数组声明的第一部分标识了数组中存储的元素的类型.作为声明的一部分,方括号指定了数组的秩(rank),或者说维数.
代码清单 数组的声明
数组的实例化和赋值
// | |
//-------+--------+-------
// | |
//-------+--------+-------
// | |
int[,] languages;
声明数组之后,可以立即为其填充值.方法是在一对大括号中,使用一个以逗号分隔的数据向项列表.
代码清单 声明数组的同时进行赋值
方式一:
方式二:
languages ={"c#","cobol","java","c++","Visual Basic","j#","Lisp"};
数组长度
C#中数组方括号内的索引从0开始,但length长度获取的是数组大小.
为了将length作为一个索引来使用,有必要在它上面减去1,以避免越界错误.
代码清单 在数组索引中使用length-1
for(int i=0;i<languages.length-1;i++)
{
//循环体
}
以下引用自MSDN:
数组具有以下属性:
数组可以是一维、多维或交错的。
数值数组元素的默认值设置为零,而引用元素的默认值设置为 null。
交错数组是数组的数组,因此,它的元素是引用类型,初始化为 null。
数组的索引从零开始:具有 n 个元素的数组的索引是从 0 到 n-1。
数组元素可以是任何类型,包括数组类型。
数组类型是从抽象基类型 Array 派生的引用类型。由于此类型实现了 IEnumerable 和 IEnumerable,因此可以对 C# 中的所有数组使用 foreach 迭代。
控制流控制流语句
还有while语句.do while语句.for语句.foreach语句.continue语句.switch语句.break语句.goto语句.
在这里不做详细介绍,太多了 如果不了解的自己复制关键字去谷歌.MSDN上查.
作用域
作用域(scope)是指出一个代码块或者语言结构约束起来的分层上下文.C#禁止在同一作用域内出现两个同名的声明.例如,在一个代码块中,不能定义两个同名的局部变量,代码块限定了作用域.类似地,不可以在同一个类中定义两个名为Main()的方法.
作用域是分层的.假定现在有一个方法,方法内部有一个if语句块,那么不可以先在方法这一层上定义一个局部变量,再在if语句块中定义一个同名的新变量.换言之,你声明的第一个变量的作用域跨越了方法内定义的所有代码块的作用域.然而,if块中声明的变量与else块中声明的变量不在同一个作用域中.除此之外,由于方法限定了局部变量的作用域,所以同一个局部变量名称可以在另一个方法中使用.
作用域限制了可访问性.例如,一个局部变量不可以在定义它的方法外部访问.类似的,如果一个变量是在if块内定义的,就不能在if块的外部访问,即使是从同一个方法中访问.
通常将运算符分为3类:一元运算符、二元运算符、三元运算符。他们对应的操作数分别是1个、2个和3个。
一元运算符主要用于指定负值(decimal a = -2234234234234234.72M)
二元运算符要求两个操作数,分别称为左操作数和右操作数。二元运算符还要求代码对结果进行赋值,以避免丢失结果值。二元运算符分为加(+)、减(-)、乘(*)、除(/)、和取余(%,有时也称为取模运算符)。
二元运算符优先级如下:
1).*、/和% 2).+和- 3).=
如果将二元运算符中的加法运算符用于字符串,便起到一个拼接字符串的作用。
在算术运算中使用字符
虽然char类型存储的是字符而不是数字,但它是一个整数类型。它可以和其他整数类型一起参与算术运算。然而,如何解释char类型的值,并不是基于存储在其中的字符,而是基于它的基础值。例如,数字3包含一个Unicode值0×33(十六进制),换算成十进制值是51。数字4包含Unicode值0×34,或者十进制的52。
浮点类型造成非预期的不相等
浮点类型float和double有一些特殊性,比如它们处理精度的方式。
一个float具有7位精度,能容纳值1234567和0.1234567。然而,如果将这两个float值加到一起,结果会被取整为1234567,因为小数部分超过了一个float能够容纳的7位有效数字。这种类型的取整有时是致命的,尤其是在执行重复性计算或者检查相等性的时候。
在比较两个值是否相等的时候,浮点类型的不准确性可能造成非常严重的后果。有的时候,本来应该相等的值被错误地判断为不相等。
浮点类型还有其他一些特殊性。例如,一个整数除以零,理论上来说应造成一个错误。对于精确的数据类型来说,这一点是成立的。然后,float和double允许一些特殊的值。浮点数除以零,显示“非数字”
输出:
圆括号运算符
圆括号运算符允许组合操作数和运算符,使它们能一起求值。这一点相当重要,因为它提供了超越默认优先级的途径。开发者应主动使用圆括号来增强代码的可读性,显示地消除表达式的歧义,而不是依赖运算符的优先级。
递增和递减运算符
C#提供了特殊的运算符来实现计数器的递增和递减。递增运算符(++)每次使一个变量递增1。递减运算符(--)每次使一个变量递减1。
对比下前缀和后缀递增运算符
{
public static void Main()
{
int x;
x = 1;
System.Console.WriteLine("{0},{1},{2}",x++,x++,x);
System.Console.WriteLine("{0}.{1},{2}",++x,++x,x);
}
}
输出:
4,5,5
由此可以看出,前缀递增/递减运算符返回的是对操作数进行递增/递减之后的结果,而后缀递增/递减运算符返回的是对操作数进行递增/递减之前的结果.
线程安全的递增和递减
使用由System.Threading.Interlocked类提供的线程安全的方法Increment()和Decrement()。这两个方法依赖于处理器的功能来执行快速的、线程安全的递增和递减运算。
常量表达式
根据定义,常量表达式是C#编译器能在编译时完成求值的表达式。
const关键字会在编译时将值锁定。之后,代码中任何修改或锁定的值的尝试都会造成一个编译错误。
{
const int a = 60*60*24;//常量表达式
const int b = a*7;
}
注意,赋给b的值也是一个常量表达式,因为表达式中的操作数也是常量,所以编译器能确定结果。
逻辑运算符
逻辑运算符有一个或两个布尔操作数,并返回一个布尔结果。逻辑运算符允许你合并多个布尔表达式,以构成其他布尔表达式。逻辑运算符包括||、&&和^,分别对应于OR、AND、XOR。
1.OR运算符(||)
||运算符对两个布尔表达式进行求值,如果其中任何一个为true,就返回true
2.AND运算符(&&)
布尔AND运算符&&要求在两个操作数求值都为true的前提下才返回true。任何一个操作数为false,就会返回false。
3.XOR运算符(^)
^符号是异或运算符。若应用于两个布尔操作数,那么只有在两个操作数中仅有一个为true的前提下,XOR运算符才会返回true。
与布尔AND和OR不同,布尔XOR运算符不支持短路运算。 它始终都要检查两个操作数,因为除非确切地知道两个操作数的值,否则不能判定最终结果。
逻辑求反运算符
逻辑求反运算符(!)有时也称为NOT运算符,它的作用是反转一个bool数据类型的值,这是一个一元运算符,只需要一个操作数。
条件运算符
可以使用条件运算符来取代if语句。条件运算符是一个问号(?),其常规格式如下:
条件运算符是三元运算符,因为它需要3个操作数:a、1、2。
如果a求值为true,则条件运算符返回1,否则返回2。
while和do/while循环
while语句的常规形式:while(booLean-expression) //括号里为一个布尔表达式
statement
只要布尔表达式求值为true,计算机就会反复执行语句statement。如果求值为false,就从statement之后的那一行语句执行。
do/while循环与while循环非常相似,只是它用于从1到n的数字循环,而且n在循环开始之前无法确定.do/while的一个典型应用就是反复提示用户输入.
do/while循环的常规形式: do
statement
while(booLean-expression);
与while循环不同的是,do/while不管while循环里的布尔表达式为true还是false,都至少对循环体执行一次.
for循环
递增和递减运算符经常在for循环中使用。与while循环相似,for循环反复执行一个代码块,直到满足一个指定的条件。区别在于,for循环有一套内建的语法规定了如何初始化、递增以及测试一个计数器的值。
下面代码使用for循环以二进制形式显示一个整数
for循环的常规形式如下
for(initial;booLean-expression;Loop)
statement
下面对for循环的各个部分进行了解释
initial表达式执行第一次循环之前的操作。initial表达式不一定非要声明一个新变量。例如,可以在之前声明好变量,在for循环中只是初始化它。然而,在这里声明的变量的作用域只在for语句之内。
for循环的booLean-expression部分指定了循环结束条件。一旦该条件为false,就会终止循环,这和while循环是相似的。并且仅当布尔表达式求值为true时,for循环会一直重复。在上例中,循环会在count递增到64的时候退出。
loop表达式会在每次循环之后执行。loop表达式一般表示为对initial表达式中的变量递增或递减
statement部分是在条件表达式保持为true的时候执行的代码。
for语句的圆括号内允许省略任何元素。for(;;){....}是完全有效的,只要你有办法从循环中退出。类似地,initial和loop表达式可以是由多个子表达式构成的一个复杂表达式。
选择for和while循环
虽然两种语句可以替换使用,但假如涉及某种形式的计数器,而且在循环初始化的时候就知道循环次数,那么一般使用for循环。相反,假如循环不基于计数器,或者在刚开始的时候无法确定循环次数,那么一般使用while循环。
foreach循环
C#语言的最后一个循环语句是foreach。foreach用于遍历一个数据项集合,设置一个标识符来依次表示其中的每一项。在循环期间,可以对数据项执行指定的操作。foreach循环的特点在于:不会出现计数错误,也不可能越过集合边界。
foreach语句的常规形式
foreach(type identifier in collection)
statement;
下面对foreach语句的各个部分进行解释
type为代表collection中的每个数据项的identifier声明一个数据类型。
identifier是一个只读变量,foreach结构会自动将collection中的下一项指派给它。identifier的作用域限制在foreach循环以内。
collection是代表多个数据项的一个表达式,比如数组。
statement是每一次foreach循环时执行的代码
执行到foreach语句时,它将cells数组中的第一项,也就是值'1'赋给cell变量。然后执行foreach语句块内的代码。if语句判断cell的值是否等于'0'或'x',如果两者都不是,就在控制台输出cell的值。下一次循环时,将下一个数组值赋给cell,依次类推。
要记住的一个重点是,foreach循环期间,编译器禁止修改identifier变量(这里是cell)