C# 篇基础知识1——编译、进制转换、内存单位、变量
编译:C#语言要经过两次编译,程序员编写好源代码后进行第一次编译,将源代码编译为微软中间语言(MSIL),生成可以发布的应用软件;当用户使用软件时,MSIL代码会在首次载入内存后进行第二次编译,中间语言被编译为机器语言,以供计算机执行。对于同一段MSIL代码,第二次编译只在代码首次载入内存时发生,编译结果被暂时保存起来,以供重复利用。并且第二次编译是按需编译的,即用到那段就编译那段,不用到的不编译,所以这种需要时才临时编译的方式被称为即时编译(Just-in-time,JIT)。
进制转换:将十进制转换为二进制,一般使用除2取余法,也可以用类似的办法把十进制转换为k进制,称为除k取余法。
只需把得到的余数从下到上排列即可,例如89=(1011001)2,与其它进制的转换类似。二进制转换为八进制,因为8是2的三次方,所以二进制转换为八进制非常简单,只需将二进制串划分成每三位一组(如果需要的话,在前面补零)。二进制转换为十六进制,因为16是2的四次方,所以二进制转换为十六进制也非常简单,只需将二进制串划分成每四位一组(如果需要的话,在前面补零)。
内存单位:内存由千千万万个具有两个状态的电子开关组成,电子开关打开时代表1,闭合时代表0,每个电子开关可代表一位二进制数,这些电子开关称为比特(bit,也叫位),是最小存储单位。为方便存取信息,把这些电子开关有机地组织起来,一般用8个二进制位组成一个字节。比特用小写字母b,字节用大写字母B表示,因此有1 B = 8 b。计算机内存就是由很多排列整齐的字节组成,为管理方便,每个字节都有相应的编号,这个编号就是这个字节的地址,通过地址可以找到内存中任何一个字节的内容。如果只考虑正数,1个字节可表示最大数为28-1=255,因此可以表示0~255之间的整数,共256个数。国际单位制(SI)规定1kB = 1000 B(10的3次方),通常在标示储存媒介的储存容量时使用。但按照IEC(国际电工委员会)标准,计算机使用二进制表示计算机、电子信息数据容量的量纲,基本单位为字节B,1KB=1024 B(2的10次方),字节向上分别为KB、MB、GB、TB,每级为前一级的1024倍(2的10次方),比如1KB=1024B,1M=1024KB。因为210=1024最接近于1000,比较贴近10进制表示习惯,所以使用它。
变量:
程序的核心是处理数据,数据的表现形式是各种变量。变量有很多类型,比如自然数、整数、有理数、实数等,复杂一点的有复数、向量、矩阵等。
(1)整型变量
int 型变量,占用4 字节空间,共32 位。为了表示负数,把最高位定义为符号位,0 表示正数,1 表示负数,后面31 位用来表示数值大小。int 型变量的取值范围是–231~231–1。
short型变量,占用2 个字节,取值范围是–215~215–1。
long 型变量,占用8 个字节,取值范围是–263~263–1。
int 型变量取值范围可达正负21 亿,一般情况下足够用了,long 型虽能表示更大范围数据,但会占用较多内存,除非确实需要,就不要随便使用long 型。
年龄、人口等问题中,涉及的全部是正整数,并不需要负数。这时可以使用uint型变量(u 是unsigned 的缩写,无符号的),无符号位,其表示范围是0~232–1,相应地,也有ushort 和ulong 型变量。8种整数类型如表1-1所示。
表 1‑1 8种整数类型
类型 |
占用字节 |
取值范围 |
备注 |
sbyte |
1 |
-27~27-1 |
有符号字节型 |
byte |
1 |
0~28-1 |
无符号字节型 |
short |
2 |
-215~215-1 |
有符号短整型 |
ushort |
2 |
0~216-1 |
无符号短整型 |
int |
4 |
-231~231-1 |
有符号整数型 |
uint |
4 |
0~232-1 |
无符号整数型 |
long |
8 |
-263~263-1 |
有符号长整型 |
long |
8 |
0~264-1 |
无符号长整型 |
数据溢出,int 型变量所能容纳的最大整数为2147483647,如果再加1,则变成-2147483648,这种情况编译器为报错,这个情况称为数据溢出。溢出也可能发生在int、long 等数据类型上,因此在选择数据类型时,首先要估算数据大小,选择恰当的数据类型,既不要太大浪费空间,也不要太小而发生溢出。
实际上,在计算机中,整数均是以补码形式存储的。在n位的机器数中,最高位为符号位,该位为0表示为正,为1表示为负;其余n-1位为数值位,各位的值可为0或1。当整数真值为正时,原码、反码、补码数值位完全相同;当真值为负时,反码的数值位是原码数值位的各位取反,补码则是反码的最低位加一。注意,求机器数反码补码过程中符号位始终不变。补码的补码即为原码。
例如机器数10111111表示-65,对于这种最左侧位为1的机器数,首先即应判定其为负数,而求负数数值位的值,需要先将其补码转换为原码。补码加法,例如X=+0110011,Y=-0101001,求[X+Y]补。[X]补=00110011, [Y]补=11010111,[X+Y]补 = [X]补 + [Y]补 = 00110011+11010111=00001010(因为计算机中运算器的位长是固定的(定长运算),上述运算中产生的最高位进位将丢掉)。补码减法,[X-Y]补 = [X]
补 - [Y]补 = [X]补 + [-Y]补。移码(又叫增码)是符号位取反的补码,即[X]移与[X]补的关系是符号位互为相反数(仅符号位不同)。一般用指数的移码减去1来做浮点数的阶码,引入的目的是为了保证浮点数的机器零为全0。
(2)实数变量
除了整数以外,通常还会用到带小数的实数,实数也称为浮点数,在C#中有3种实数型变量类型:float、double 和decimal,如表1-2 所示。
表 1‑2 3种实数类型
类型 |
字节 |
取值范围(0为特殊值) |
有效数字 |
说明 |
|
float |
4 |
-2^127*(2-2^-23)~-2^(-126)*1 2^(-126)*1~2^127*(2-2^-23)= -3.4E38~-1.175E-38 1.175E-38~3.4E38 |
7位 (2^23-1) =16777215 |
单精度实数 7位有效能保证 |
|
double |
8 |
-2^1023(2-2^-52)~-2^(-1022) *1 2^(-1022) *1~2^1023(2-2^-52)= -1.7E308~A A~1.7E308 |
16位/15位 (2^53-1) =9007199254740991 |
双精度实数 15位有效能保证 |
|
decimal |
16 |
-7.9*10^28~-1.0*10^-28 1.0*10^-28~7.9*10^28 |
28 |
金融货币 28位能有效保证 |
在 IEEE 标准中,浮点数是将特定长度的连续字节的所有二进制位分割为特定宽度的符号域,指数域和尾数域三个域,其中保存的值分别用于表示给定二进制浮点数中的符号,指数和尾数。这样,通过尾数和可以调节的指数(所以称为"浮点")就可以表达给定的数值了。IEEE754标准规定一个实数V可以用: V=(-1)s×M×2^E的形式表示,说明如下:
①符号s(sign)决定实数是正数(s=0)还是负数(s=1),对数值0的符号位特殊处理。
②有效数字M是二进制小数,M的取值范围在1≤M<2或0≤M<1。
③指数E(exponent)是2的幂,它的作用是对浮点数加权。
表 1‑3 IEEE关于几类实数的机器表示标准
|
长度 |
符号 |
指数 |
尾数 |
有效位数 |
指数偏移(+) |
说明 |
单精度 |
32位 |
1 |
8 |
23(1.M) |
24 |
127 |
含1个隐含位 |
双精度 |
64位 |
1 |
11 |
52(1.M) |
53 |
1023 |
含1个隐含位 |
为了强制定义一些特殊值,IEEE标准通过指数将表示空间划分成了三大块:
①最小值指数(所有位全置0)用于定义0和弱规范数
②最大指数(所有位全值1)用于定义±∞和NaN(Not a Number)
③其他指数用于表示常规的数。
这样一来,最大(指绝对值)常规数的指数不是全1的,最小常规数的指数也不是0,而是1。一般如1.001001*2EValue,包括3个参数:尾数(Mantissa ),基数(底数Base),和指数Evalue,即:M * BE= 尾数 * 底数指数,标准表示为:x=(-1)S*(1+Fraction)*2(Exponent-Bias),因为IEEE标准写法中前面的1.总是省略(IEEE规定小数点左侧的 1 是隐藏的),Fraction = 尾数 – 1,因此Fraction= 0.001001。如上图所示,第一个域为符号域。0 表示正数,而 1 则表示负数,第二个域为指数域通常使用移码表示(移码和补码只有符号位相反,其余都一样),对应于之前介绍的二进制科学计数法中的指数部分。
对于指数域,单精度数为 8 位,双精度数为 11 位,8位二进制指数理论上可表示0~255间指数值,11位理论可表示0~2047间指数值。但指数既可为正,也可为负,为处理负指数的情况,在机器中表示时,将指数实际值加上一个偏差值(Bias)作为保存在指数域中的值。单精度数的Bias为127(0111 1111),而双精度数的Bias为1023(011 1111 1111),例如单精度数的指数域中保存值为127,则实际值为0,而指数域中64则表示-63,偏差值的引入使单精度数,实际可表示的指数值范围变成-127~128。
实际的指数值-127(保存为0),变换为指数域保存值过程:首先,得到-127原码1111 1111的补码1000 0001,然后,加上单精度偏差值0111 111 ,即得指数域保存值0000 0000,即指数域全0表示:-127。
实际的指数值+128(保存为全1),变换为指数域保存值过程:首先,得到+128原码‘1’000 0000的补码‘1’000 0000,然后加上单精度偏差值0111 1111,即得指数域保存值‘1’-111 1111,即指数域全1表示:+128。
这些特殊值保留用来处理特殊值,如此,实际可以表示的有效指数范围间于 -127 和 128之间。在IEEE 754标准关于单精度及双精度数的定义中,有一些特殊约定,
①当P(阶码)=0,M(尾数)=0时,表示0;
②当P=255,M=0时,表示无穷大,用符号位来确定是正无穷大还是负无穷大;
③当P=255,M≠0时,表示NaN(Not a Number,不是一个数)。
Decimal,表示 128 位数据类型。同浮点型相比,decimal 类型具有更高的精度和更小的范围,这使它适合于财务和货币计算。 decimal 类型的大致范围和精度如下表所示。decimal也是浮点数,不过它的底数是10,它是十进制浮点数,它也包含符号位、指数部分以及尾数部分。decimal包含16个字节,共126位,它这126的结构如下所示:
1~4号字节: mmmm mmmm mmmm mmmm mmmm mmmm mmmm mmmm
5~8号字节: mmmm mmmm mmmm mmmm mmmm mmmm mmmm mmmm
9~12号字节: mmmm mmmm mmmm mmmm mmmm mmmm mmmm mmmm
13~16号字节: 0000 0000 0000 0000 000e eeee 0000 000s
从它的组成结构,可以看到decimal的尾数部分有96位(12字节),而指数部分有效的只有5位,符号位自然只有1位。decimal的尾数部分事实上是一个整数,而尾数所表示范围即为:0~2^96-1。换算为十进制便是0~79228162514264337593543950335,一个29位的数字(最高位的值最多到7)。
指数部分也是一个整数,但是观察指数部分的形式(000e eeee),只有5位有效,这是因为它的最大值只能到28。为何如此?因为decimal指数的底数是10,而尾数部分表示的是一个29位或者28位的整数(29位的数值最高位只能到7,只有28位的值是可以任意设置的)。假设有一个29位的十进制数,此时decimal的指数部分,控制的便是要在这个29位整数的哪一位点上小数点,因此指数最大值只能到28。decimal的实际表示形式为:符号位*尾数/10^指数,非0尾数最小为1,所以decimal的最小绝对值为1.0/10^28,而最大绝对值为7.9*10^28。
综上,实数常量默认情况下是双精度的,为了把实数常量赋给单精度变量,需要添加后缀f或F,将其标志为单精度实数,比如12.34567f、3.1415926F 等。由于float与double的精度通常不能得到保证,因此涉及财务计算时,应使用 decimal 型变量,decimal取值范围比float和double小,但其精度能保证28位。由于实数常量默认情况下为double型数据,要将其赋给decimal 型变量,需要添加后缀m 或M,将其标记为decimal 型数字。
字符和字符串:
计算要要处理的字符包括0~9 数字符、英文字母、希腊字母、各种特殊符号以及汉字等。字符型变量用来存储一个字符,用关键字char声明,占用2个字节。字符串变量用来存储一条字符串,用关键字string声明。控制台接收输入与打印输出字符和字符串的方法为,Console.Read()、Console.ReadLine()、Console.Write(X)、Console.WriteLine(Y)。
ASCII编码(American Standard Code for Information Interchange,美国信息交换标准码)早期标准ASCII 码使用7 位二进制数来表示128 个字符,包括英文字母、数字和常用符号以及各种控制符号,后来人们扩展了ASCII 码,使用8 位二进制数表示256 个字符。存储一个字符,只需存储它的编码即可,实际上编码在内存中是以二进制形式存储的,字符串在存储介质中就是一串连续的二进制编码。
Unicode编码,ASCII可表示256个字符,表示本文字母语言编码足够了,全对于中文、日文、韩文、台湾繁体字等方块字却出现了问题,中国1981年制定了GB-2312编码标准,用2个字节,可存储6万多字符,,日文、韩文、阿拉伯文、台湾地区繁体中文(BIG–5)等,这些编码均称为多字节字符集Charset。但由于各个国家和地区定义的字符集编码不统一,会造成严重的兼容性问题,为统一编码,人们制定了世界通用的Unicode编码, Unicode为全世界每一个字符提供统一的编码,同一个字符,不论在什么平台上、不论在什么软件中,也不论在什么语言环境中,都有相同的编码,Unicode编码完全兼容ASCII编码,相同的字符具有相同的编码。
转义字符,
Console.WriteLine(" What’s your name?\n My name is Jack. ");
会分两行显示,这里的\ n不会直接输出,而是起到换行作用,像这种控制文本格式的特殊字符称为转义字符,它们均以\开头,C#定义的转义字符有:
表 1‑4 8种整数类型
转义字符 |
功能 |
说明 |
\' |
单引号 |
输出单引号' |
\" |
双引号 |
输出双引号" |
\\ |
反斜杠 |
输出反斜杠 |
\0 |
空 |
常放在字符串末尾 |
\a |
警告 |
产生嘀的一声蜂鸣 |
\b |
退格 |
光标向前移动一个位置 |
\f |
换页 |
将当前位置移到下一页开头 |
\n |
换行 |
将当前位置移到下一行开头 |
\r |
回车 |
将当前位置移到本行开头 |
\t |
水平制表符 |
跳到下一个tab位置 |
\v |
垂直制表符 |
把当前行移动到下一个垂直tab位置 |
@控制符,
Console.WriteLine(@"Good \n Better \t best! An other line.") ;
这里字符串用了\,但不会出错,会原原本本地输出字符串的内容,因为这里在字符串前面加了前缀@,字符串就变成了原义字符串,不会再理会其中的转义字符。添加前缀@后,如果字符串里需要引号本身,可以用连续两个引号表示。
@"Say ""Hello"" to Heaven";
+运算符,
两个字符串可以用“+”运算符连接起来,字符串也可以和其他类型的变量连接。程序先将变量的值转化为字符串,然后与字符串连接,最终合并成新字符串。
(4)var关键字,
C#3.0 带来了一个新的定义变量的方式——隐式类型,不管什么类型的变量,都可用var关键字定义,编译器会根据初始化的内容推断变量的类型,使用var关键字时必须同时初始化变量,因为不初始化,编译器无法推断变量的类型。
(5)变量的格式化输出
int i = 360;int j = 60;
Console.WriteLine("{0} + {1} = {2}", i, j, i + j);
用占位符方式的好处是可以在占位符{}中添加各种格式控制信息:
① 控制数值位数
格式为{i, w},其中i 是参数索引,w 是宽度值(位数),正值表示右对齐,负值表示左对齐,例如Console.WriteLine("{0,6}\n +{1,3}\n------\n{2,6}", i, j, i+j);
② 控制数值格式和小数位数
一个数值可以有多种书写形式,比如0.05 用科学记数法表示为5.0×10–2,用百分数表示为5%,用货币格式表示为¥0.05,添加格式字符串和精度值可以控制数值的显示格式和小数位数。Console.WriteLine("{0,8:C2}\n+{1,7:C2}\n---------\n{2,8:C2}",i,j,i+j);
其中C 称为金融格式字符串。我们知道,美国的金融符号用$表示,中国的金融符号用¥表示,用格式字符串C 后,系统会根据计算机上的地区设置显示相应的符号,在英文环境中会显示$,在中文环境中会显示¥,语言环境不同,显示的符号也不同。C 后的2为精度说明符,表示显示结果保留2 位小数。宽度值和格式字符串之间用冒号隔开。
表 1‑5格式字符串
格式字符串 |
说明 |
C |
本地货币格式 |
D |
把二进制、八进制等转化为十进制格式 |
E |
科学计数法格式,默认为6 位小数,用E 后的精度说明符控制小数位数 |
F |
固定点格式,精度说明符控制小数位数,可以为0 |
G |
普通格式,使用E 还是F 格式取决于哪种格式最简单 |
N |
数字格式,用逗号表示千分符,例如1,245,678.125 |
P |
百分数格式 |
X |
16 进制格式 |
注意除 e/E 外,格式字符串无论是大写还是小写,对结果无影响。
③#占位符
一个技巧是,可以使用占位符来代替这些格式字符串,例如
double pi = 3.1415926;
Console.WriteLine("{0:#.00}", pi);
Console.WriteLine("{0:#.000}", pi);
Console.WriteLine("{0:#.0000}", pi);
(6)变量的命名规则
给变量命名必须遵循如下规则:变量的第一个字符必须是字母、下划线或@;其后的字符可以是字母、数字或下划线;变量不能和关键字重名。要注意的是,C#区分大小写。.Net 推荐用camelCasing(驼峰式)方式给变量命名,该命名方法要求变量名称的第一个单词全部小写,从第二个单词开始,每个单词的首字母大写。如firstName、displyTextBox。
(7)常量
C#中的常量也分为不同的类型。如数值常量,1 默认是int 型常量,而1.0 默认是double 型常量。其他类型常量需要添加后缀,如89L 是long 型,3.14f 为float 型,如表2-6 所示。
表 1‑6常量的后缀
类型 |
后缀 |
示例 |
int |
无 |
10,100,–10,–100 |
uint |
U或u |
1u,100u,2007U |
long |
L或l |
10l,100L,88888888L,–99999999L |
ulong |
Ul或ul |
100000ul,200000000UL |
float |
F或f |
1.0f,3.14f,1.414F |
double |
D或d或无 |
1.0,1.0d,3.14159265358979D |
decimal |
M或m |
1000.00m,123456789.987654321M |
const常量,即符号常量,如果一个较大程序中许多地方使用到一个数值常量,最好将其设置为符号常量,这样当此常量需要修改时,就很简单,且不易出错,const 常量只能在声明的时候赋值,在程序运行过程中不能改变它的值。例如:const double PI=3.14;double area = PI*r*r;。