1三种类型
上一节 Rectangle 类的长和宽都写成了 double,它就是一种数据类型。C# 的变量分三种类型:值类型、引用类型和指针类型。
2值类型
值类型直接装着数据:声明一个 int,系统就分出一块内存,把值直接放进去。值类型都是从 System. 派生出来的(基类就是它上一层的类型,后面会讲)。本节的例子都是顶级语句写法(不写类和 Main,代码从第一行开始执行):
| 1 | Console.WriteLine(typeof(int).BaseType); | // typeof 取到类型,.BaseType 是它的基类 |
| 2 | Console.WriteLine(typeof(object)); | // object 其实是 System.Object |
| 3 | Console.WriteLine(typeof(string)); | // string 其实是 System.String |
| 4 | Console.WriteLine(typeof(string).BaseType); | // string 的基类是 System.Object |
System.ValueType System.Object System.String System.Object
3十三种值类型
按占几个字节,可以摆成五排:
- 1 字节:
byte是 8 位无符号整数,只能放 0 到 255;sbyte的 s 表示有符号,能放负数,范围 -128 到 127;bool只有 True 和 False 两个值。 - 2 字节:
short有正有负,ushort(u 表示无符号)从 0 到 65,535;char装一个 16 位 Unicode(给世界各种文字统一编号的标准)字符,编号从 U+0000 到 U+FFFF。 - 4 字节:
int大约正负二十一亿,uint去掉负数,最大约四十二亿;float是 32 位单精度浮点型(浮点数就是带小数点的数)。 - 8 字节:
long能放十九位数,大到九百多亿亿;ulong是无符号的 long;double是 64 位双精度,比 float 范围更大、更精确。 - 16 字节:
decimal,128 位精确的十进制数,有 28 到 29 位有效数字,算钱的时候常用它。
4默认值
像 Rectangle 的成员变量,没赋值时取的就是默认值:数字类型都是 0,bool 是 False,char 是编号为 0 的字符。教程表里 0 后面跟的 M、D、F、L 是写法后缀,分别表示 decimal、double、float 和 long,值还是 0。
| 1 | Console.WriteLine(default(bool)); | // default(类型):取这个类型的默认值,False |
| 2 | Console.WriteLine(default(int)); | // int 的默认值 0 |
| 3 | Console.WriteLine(default(long)); | // long:0(教程写作 0L) |
| 4 | Console.WriteLine(default(float)); | // float:0(教程写作 0.0F) |
| 5 | Console.WriteLine(default(double)); | // double:0(教程写作 0.0D) |
| 6 | Console.WriteLine(default(decimal)); | // decimal:0(教程写作 0.0M) |
| 7 | Console.WriteLine((int)default(char)); | // char 的默认值转成编号输出:0 |
查看完整输出(7 行)
False 0 0 0 0 0 0
5放不下就报错
byte 放不下 256,int 只装整数,编译器直接报错,程序根本跑不起来:
| 9 | byte b = 256; | // byte 最大只到 255 |
Program.cs(9,22): error CS0031: 常量值“256”无法转换为“byte”
| 9 | int a = 3.5; | // 3.5 是 double,int 装不下小数 |
Program.cs(9,21): error CS0266: 无法将类型“double”隐式转换为“int”。存在一个显式转换(是否缺少强制转换?)
6sizeof
想知道一个类型占几个字节,用 sizeof 运算符,结果以字节为单位。
| 9 | Console.WriteLine("Size of int: {0}", sizeof(int)); | // sizeof(int) 算出 int 占几个字节,填进 {0} |
| 10 | Console.ReadLine(); | // 等待回车(现在的 VS 不写也会停住) |
Size of int: 4
查看完整代码(13 行)
| 1 | using System; | // 引入 System 命名空间 |
| 2 | | |
| 3 | namespace DataTypeApplication | // 命名空间 DataTypeApplication |
| 4 | { | // DataTypeApplication 命名空间开始 |
| 5 | class Program | // Program 类 |
| 6 | { | // Program 类开始 |
| 7 | static void Main(string[] args) | // 程序入口 Main 方法 |
| 8 | { | // Main 开始 |
| 9 | Console.WriteLine("Size of int: {0}", sizeof(int)); | // 输出 Size of int: 4 |
| 10 | Console.ReadLine(); | // 等待回车(现在的 VS 不写也会停住) |
| 11 | } | // Main 结束 |
| 12 | } | // Program 类结束 |
| 13 | } | // DataTypeApplication 命名空间结束 |
把十三种都测一遍,和上面五排的宽度完全对得上。这些大小是 C# 规定死的,换台电脑也一样:
查看完整输出(13 行)
bool 1 byte 1 sbyte 1 char 2 short 2 ushort 2 int 4 uint 4 float 4 long 8 ulong 8 double 8 decimal 16
查看完整代码(13 行)
| 1 | Console.WriteLine($"bool {sizeof(bool)}"); | // $"..." 里的 {} 会换成算出来的值:1 |
| 2 | Console.WriteLine($"byte {sizeof(byte)}"); | // 1 |
| 3 | Console.WriteLine($"sbyte {sizeof(sbyte)}"); | // 1 |
| 4 | Console.WriteLine($"char {sizeof(char)}"); | // 2 |
| 5 | Console.WriteLine($"short {sizeof(short)}"); | // 2 |
| 6 | Console.WriteLine($"ushort {sizeof(ushort)}"); | // 2 |
| 7 | Console.WriteLine($"int {sizeof(int)}"); | // 4 |
| 8 | Console.WriteLine($"uint {sizeof(uint)}"); | // 4 |
| 9 | Console.WriteLine($"float {sizeof(float)}"); | // 4 |
| 10 | Console.WriteLine($"long {sizeof(long)}"); | // 8 |
| 11 | Console.WriteLine($"ulong {sizeof(ulong)}"); | // 8 |
| 12 | Console.WriteLine($"double {sizeof(double)}"); | // 8 |
| 13 | Console.WriteLine($"decimal {sizeof(decimal)}"); | // 16 |
7引用类型
引用类型的变量里放的是一个引用,好比一张取货牌,指向数据放在哪儿;两个变量可以拿着指向同一份数据的牌。
| 1 | int a = 1; | // 值类型 a = 1 |
| 2 | int b = a; | // b = a:复制了一份值 |
| 3 | b = 2; | // 改的是 b 自己那份 |
| 4 | Console.WriteLine(a); | // a 还是 1 |
| 5 | | |
| 6 | Rectangle r1 = new Rectangle(); | // 造一个矩形对象,r1 拿着指向它的引用 |
| 7 | Rectangle r2 = r1; | // r2 = r1:复制的是引用,两个变量指向同一个对象 |
| 8 | r2.length = 9.5; | // 通过 r2 改 length |
| 9 | Console.WriteLine(r1.length); | // r1 看到的是同一个对象:9.5 |
1 9.5
内置的引用类型有三个:object、dynamic 和 string。
8object
object 是所有类型的终极基类,是 System. 的别名,所以什么值都能赋给它。把值类型的值放进 object 叫装箱,从 object 里取回值类型叫拆箱。
| 1 | object obj; | // 声明一个 object 变量 |
| 2 | obj = 100; // 这是装箱 | // 自动装箱,不用写转换 |
| 3 | int n = (int)obj; // 这是拆箱 | // 拆箱要写 (int),把 obj 转回 int |
| 4 | Console.WriteLine(obj); | // 输出 100 |
| 5 | Console.WriteLine(n); | // 输出 100 |
100 100
拆箱不写 ( 就编译不过:
| 3 | int n = obj; | // 少了 (int) |
Program.cs(3,9): error CS0266: 无法将类型“object”隐式转换为“int”。存在一个显式转换(是否缺少强制转换?)
9dynamic
dynamic 是动态类型,也能存任何类型的值,写法是 dynamic 加变量名再赋值。
| 1 | dynamic d = 20; | // d 先装整数 20 |
| 2 | Console.WriteLine(d + 1); | // 20 + 1,输出 21 |
| 3 | d = "Hello"; | // 改装字符串 "Hello" |
| 4 | Console.WriteLine(d.Length); | // .Length 是字符串的长度,输出 5 |
21 5
它和 object 的区别在于什么时候检查类型。同样去取整数的 Length,object 在编译时就报错:
| 1 | object o = 20; | // object 装整数 20 |
| 2 | Console.WriteLine(o.Length); | // 编译时就查:object 没有 Length |
Program.cs(2,21): error CS1061: “object”未包含“Length”的定义,并且找不到可接受第一个“object”类型参数的可访问扩展方法“Length”(是否缺少 using 指令或程序集引用?)
dynamic 能编译通过,到运行时才报错:
| 1 | dynamic d = 20; | // dynamic 装整数 20 |
| 2 | Console.WriteLine(d.Length); | // 编译通过,运行到这里才发现 int 没有 Length |
Unhandled exception. Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 'int' does not contain a definition for 'Length' at CallSite.Target(Closure, CallSite, Object) at System.Dynamic.UpdateDelegates.UpdateAndExecute1[T0,TRet](CallSite site, T0 arg0) at Program.<Main>$(String[] args) in 数据类型/dyn-err/Program.cs:line 2
10string
string 是字符串,System. 的别名,也是从 object 派生的(见本节开头的输出)。赋值可以用引号,也可以用 @ 引号。普通引号里反斜杠是转义符(和后面一个字符合起来表示一个特殊字符),\W 认不出来就报错:
| 1 | string s = "C:\Windows"; | // 普通引号里 \W 认不出来 |
Program.cs(1,15): error CS1009: 无法识别的转义序列
前面加 @ 就成了逐字字符串,反斜杠只是普通字符,所以这两种写法完全相等:
| 1 | string a = @"C:\Windows"; | // 逐字字符串:\ 就是普通的反斜杠 |
| 2 | string b = "C:\\Windows"; | // 普通字符串:\\ 才表示一个反斜杠 |
| 3 | Console.WriteLine(a); | // 输出 C:\Windows |
| 4 | Console.WriteLine(b); | // 也输出 C:\Windows |
| 5 | Console.WriteLine(a == b); | // == 比较两个字符串是否相等:True |
C:\Windows C:\Windows True
逐字字符串还能直接换行,换行和缩进的空格都算在里面;里面要写一个引号,就连写两个 ""。下面把一段 4 行的网页代码写进 @"...",开头写成 @"<script type=""text/,输出时换行、缩进原样保留,"" 变成一个 ":
<script type="text/javascript">
<!--
-->
</script>class、interface 和 delegate 是自己定义的引用类型,后面再讲。
11指针类型
指针存的是另一个值的内存地址,写法是类型加星号再加名字。在 C# 里,指针只能用在不安全的代码里,直接写就报错,后面“不安全代码”一节再讲。
| 1 | char* cptr; | // char 类型的指针 cptr |
| 2 | int* iptr; | // int 类型的指针 iptr |
Program.cs(1,1): error CS0214: 指针和固定大小缓冲区只能在不安全的上下文中使用 Program.cs(2,1): error CS0214: 指针和固定大小缓冲区只能在不安全的上下文中使用
12小结
- 值类型自己装着数据,大小由类型定死(1、2、4、8、16 字节五档)。
- 引用类型拿着一张牌,指向别处的数据;
r2 = r1后两个变量指向同一个对象。 - 装箱自动,拆箱要写
(;object 编译时查类型,dynamic 运行时才查。int) @"C:\Windows"和"C:\\Windows"相等;指针只能在不安全代码里用。
下一节:3.5 非要放进 int,该怎么办?
