预处理数据类型
值类型和引用类型
C#把数据类型分为两种:值类型和引用类型。两者的区别是:
值类型直接存储其值,引用类型存储对值的引用。
值类型存储在堆栈中,引用类型存储在托管堆内。
看个例子:
int i = 1;
int j = i;
这两条语句将在内存的两个位置存储1。
再看一个例子:
代码分析:
执行A a,b;,系统为A创建了引用a和引用b,
执行a = new A();,则实例化了引用a,此时a是存储在堆上一个对象的引用,
执行a.value = 30;,则是对该对象的字段赋值30,此时该对象的字段的值是30,
执行b = a;表明b也成为了该对象的引用,即a和b都是该对象的引用,
因此第一个Console.WriteLine($"b.value = {b.value}");,是显示该对象的value的值,即30,
执行b.value = 50;,则是对该对象的字段赋值50,此时该对象的字段的值是50,
因此第二个Console.WriteLine($"a.value = {a.value}");,是显示该对象的value的值,即50。
.NET类型
数据类型的C#关键字(如int、short和string)从编译器映射到.NET数据类型。例如,C#声明一个int型数据时,声明的实际上是一个.NET struct:System.Int32的一个实例,这意味着在语法上可以把所以的数据类型看成支持某些方法的类,例如把int i转换成string类型,可以编写以下代码:
string s = i.ToString();
这种便利语法的背后,类型实际上仍被存储为基本类型,基本类型在概念上用结构表示,没有性能损失。
下面将看一下C#的预定义类型,预定义类型有15种,13个是值类型,两个是引用类型。
预定义的值类型
预定义的值类型包括整型、浮点型、字符型和布尔类型。
整型
名称 | .NET类型 | 说明 | 范围 |
sbyte | System.SByte | 8位有符号整数 | -2^7 ~ 2^7 - 1 |
short | System.Int16 | 16位有符号整数 | -2^15 ~ 2^15 - 1 |
int | System.Int32 | 32位有符号整数 | -2^31 ~ 2^31 - 1 |
long | System.Int64 | 64位有符号整数 | -2^63 ~ 2^63 - 1 |
byte | System.Byte | 8位无符号整数 | 0 ~ 2^8 - 1 |
ushort | System.UInt16 | 16位无符号整数 | 0 ~ 2^16 - 1 |
uint | System.UInt32 | 32位无符号整数 | 0 ~ 2^32 - 1 |
ulong | System.UInt64 | 64位无符号整数 | 0 ~ 2^64 - 1 |
注意,C#认为byte类型和char类型完全不同,它们之间的转换必须显式请求。
所有的都能被赋予十进制或者十六进制的值,十六进制的值需要0x或者0X后缀。
如果对一个int、uint、long或ulong类型的整数值没有显式声明,那么该变量默认为int类型。为了把输入的值指定为其他整数类型,可以在数字后面加上如下字符:
U或u表示uint类型的数值,L或l表示long类型的数值,Ul、UL、ul或者uL表示ulong类型的字符。
数字分隔符
C# 7提供了数字分隔符,这些分隔符有助于提高可读性,且并不添加任何功能。例如:
作为分隔符的下划线将会被编译器忽略,因此添加下划线可以便于程序的阅读。
二进制值
C# 7还可以把二进制分配给整数类型,在变量值前加入0b或0B即可,加入0b或0B后只允许使用0和1。例如:
当使用八进制计数时,每三位使用一个分隔符将会方便代码的阅读。例如
uint i = 0b111_110_101_100_011_010_001_000;
浮点类型
名称 | .NET类型 | 说明 | 位数 | 范围 |
float | System.Single | 32位单精度浮点数 | 7 | -+1.5*10^245 ~ -+3.4*10^38 |
double | System.Double | 64位双精度浮点数 | 15/16 | -+5.0*10^2324 ~ -+1.7*10^308 |
代码对某个非整数值编码时,一般假定该变量是double类型,要指定该变量为float类型,可以在数值后加上F或f。
decimal类型
decimal类型表示精度更高的浮点数。
名称 | .NET类型 | 说明 | 位数 | 范围 |
decimal | System.Deciaml | 128位高精度十进制表示法 | 28 | -+1.0*10^228 ~ -+7.9*10^28 |
要把数字指定为decimal类型而不是double、float类型,可以在数字后面加上后缀M或m。
bool类型
名称 | .NET类型 | 说明 | 位数 | 范围 |
bool | System.Boolean | 表示true或者false | NA | true或者false |
bool和整数值不能相互隐式转换,如果变量声明为bool类型,只能使用true或者false,用0表示false和用非0值表示true都会出错。
字符类型
名称 | .NET类型 | 值 |
char | System.Char | 表示一个16位的(Unicode)字符 |
char类型的字面量是用但引号括起来的,如'A',如果把字符用双引号括起来的话,编译器将把它看成字符串从而出现错误。除了把char表示为字符型常量外,还可以使用4位十六进制的Unicode值如'\u0041'、带有强制类型转换的整数值(char)65或者十六进制数'x0041'表示它,他们还可以用转义序列表示。
转义序列 | 字符 |
\' | 单引号 |
\" | 双引号 |
\\ | 反斜杠 |
\0 | 空 |
\a | 报警(响铃) |
\b | 退格 |
\f | 换页 |
\n | 换行 |
\r | 回车 |
\t | 水平制表符 |
\v | 垂直制表符 |
数字的字面值
字面值 | 位置 | 说明 |
U | 后缀 | unsigned int |
L | 后缀 | long |
UL | 后缀 | unsigned long |
F | 后缀 | float |
M | 后缀 | 十进制 |
0x | 前缀 | 十六进制数字,允许使用0 ~ F |
0b | 前缀 | 二进制数字,只能使用0和1 |
true | NA | 布尔值 |
false | NA | 布尔值 |
预定义的引用类型
C#支持两种预定义的引用类型:string和object。
名称 | .NET类型 | 说明 |
object | System.Object | 根类型,其他类型都是由它派生来的(包括值类型) |
string | System.String | Unicode字符串 |
object类型
在C#类型中,object类型是最终的父类型,所有的内置类型和用户自定义的类型都是由它派生而来的。object类型可以用于两个目的:
可以使用object引用来绑定任何特定子类型的对象,我们可以使用object类型把堆栈中的值对象装箱,再移动到堆中。object引用也可以用于反射,此时必须有代码来处理类型未知的对象。
object类型定义了许多一般用途的方法,用户定义的类需要使用一种面向对象技术——重写,来提供一些方法的替代实现代码。例如重写ToString()时,要给类提供一个方法,给出类本身的字符串表示。如果类没有提供这些方法的实现代码,编译器就会使用object类型中的实现代码,它们在类的上下文执行不一定正确。
string类型
对于代码
string str1 = "Hello ";
string str2 = "World";
string str3 = str1 + str2;//str3 = "Hello World";
虽然这是一个值类型的赋值,但由于string是一个引用类型。string对象被分配到堆上,而不是栈上。因此把一个字符串变量赋给另一个字符串时,会得到对内存同一字符串的两个引用。但是string和引用的常见行为有一些区别,例如字符串是不可改变的,修改一个字符串,就会创建一个新的string对象而另一个字符串不会发生变化。
改变str1对str2没有影响,这实际上是运算符重载的结果。
在WriteLine()方法中写入反斜杠时,往往需要两个反斜杠\\表示。C#提供了替代方式,可以在字符串前面加上@,@后所有的字符都会看成原来的含义——它们不会解释成转义字符。
C#定义了一种新的字符串插值格式,用$前缀表示,加上$后允许在花括号内放入一个变量或代码表达式,变量和代码表达式的结果将会放在花括号所在的位置。