第 5 节 · 第一部分 基础语法

数据类型

时长 5:47授课计划第 2 周

讲义

跟着视频的顺序写。每行代码都有注释,代码和运行结果都是实际运行得到的。

1三种类型

上一节 Rectangle 类的长和宽都写成了 double,它就是一种数据类型。C# 的变量分三种类型:值类型、引用类型和指针类型。

2值类型

值类型直接装着数据:声明一个 int,系统就分出一块内存,把值直接放进去。值类型都是从 System.ValueType 派生出来的(基类就是它上一层的类型,后面会讲)。本节的例子都是顶级语句写法(不写类和 Main,代码从第一行开始执行):

C#
1Console.WriteLine(typeof(int).BaseType);// typeof 取到类型,.BaseType 是它的基类
2Console.WriteLine(typeof(object));// object 其实是 System.Object
3Console.WriteLine(typeof(string));// string 其实是 System.String
4Console.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。

C#
1Console.WriteLine(default(bool));// default(类型):取这个类型的默认值,False
2Console.WriteLine(default(int));// int 的默认值 0
3Console.WriteLine(default(long));// long:0(教程写作 0L)
4Console.WriteLine(default(float));// float:0(教程写作 0.0F)
5Console.WriteLine(default(double));// double:0(教程写作 0.0D)
6Console.WriteLine(default(decimal));// decimal:0(教程写作 0.0M)
7Console.WriteLine((int)default(char));// char 的默认值转成编号输出:0
查看完整输出(7 行)
运行结果
False
0
0
0
0
0
0

5放不下就报错

byte 放不下 256,int 只装整数,编译器直接报错,程序根本跑不起来:

C# · 第 9 行
9byte b = 256;// byte 最大只到 255
编译报错
Program.cs(9,22): error CS0031: 常量值“256”无法转换为“byte”
C# · 第 9 行
9int a = 3.5;// 3.5 是 double,int 装不下小数
编译报错
Program.cs(9,21): error CS0266: 无法将类型“double”隐式转换为“int”。存在一个显式转换(是否缺少强制转换?)

6sizeof

想知道一个类型占几个字节,用 sizeof 运算符,结果以字节为单位。

C# · 第 9-10 行
9Console.WriteLine("Size of int: {0}", sizeof(int));// sizeof(int) 算出 int 占几个字节,填进 {0}
10Console.ReadLine();// 等待回车(现在的 VS 不写也会停住)
运行结果
Size of int: 4
查看完整代码(13 行)
C#
1using System;// 引入 System 命名空间
2
3namespace 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 行)
C#
1Console.WriteLine($"bool {sizeof(bool)}");// $"..." 里的 {} 会换成算出来的值:1
2Console.WriteLine($"byte {sizeof(byte)}");// 1
3Console.WriteLine($"sbyte {sizeof(sbyte)}");// 1
4Console.WriteLine($"char {sizeof(char)}");// 2
5Console.WriteLine($"short {sizeof(short)}");// 2
6Console.WriteLine($"ushort {sizeof(ushort)}");// 2
7Console.WriteLine($"int {sizeof(int)}");// 4
8Console.WriteLine($"uint {sizeof(uint)}");// 4
9Console.WriteLine($"float {sizeof(float)}");// 4
10Console.WriteLine($"long {sizeof(long)}");// 8
11Console.WriteLine($"ulong {sizeof(ulong)}");// 8
12Console.WriteLine($"double {sizeof(double)}");// 8
13Console.WriteLine($"decimal {sizeof(decimal)}");// 16

7引用类型

引用类型的变量里放的是一个引用,好比一张取货牌,指向数据放在哪儿;两个变量可以拿着指向同一份数据的牌。

C# · 第 1-9 行
1int a = 1;// 值类型 a = 1
2int b = a;// b = a:复制了一份值
3b = 2;// 改的是 b 自己那份
4Console.WriteLine(a);// a 还是 1
5
6Rectangle r1 = new Rectangle();// 造一个矩形对象,r1 拿着指向它的引用
7Rectangle r2 = r1;// r2 = r1:复制的是引用,两个变量指向同一个对象
8r2.length = 9.5;// 通过 r2 改 length
9Console.WriteLine(r1.length);// r1 看到的是同一个对象:9.5
运行结果
1
9.5

内置的引用类型有三个:object、dynamic 和 string。

8object

object 是所有类型的终极基类,是 System.Object 的别名,所以什么值都能赋给它。把值类型的值放进 object 叫装箱,从 object 里取回值类型叫拆箱。

C#
1object obj;// 声明一个 object 变量
2obj = 100; // 这是装箱// 自动装箱,不用写转换
3int n = (int)obj; // 这是拆箱// 拆箱要写 (int),把 obj 转回 int
4Console.WriteLine(obj);// 输出 100
5Console.WriteLine(n);// 输出 100
运行结果
100
100

拆箱不写 (int) 就编译不过:

C# · 第 3 行
3int n = obj;// 少了 (int)
编译报错
Program.cs(3,9): error CS0266: 无法将类型“object”隐式转换为“int”。存在一个显式转换(是否缺少强制转换?)

9dynamic

dynamic 是动态类型,也能存任何类型的值,写法是 dynamic 加变量名再赋值。

C#
1dynamic d = 20;// d 先装整数 20
2Console.WriteLine(d + 1);// 20 + 1,输出 21
3d = "Hello";// 改装字符串 "Hello"
4Console.WriteLine(d.Length);// .Length 是字符串的长度,输出 5
运行结果
21
5

它和 object 的区别在于什么时候检查类型。同样去取整数的 Length,object 在编译时就报错:

C#
1object o = 20;// object 装整数 20
2Console.WriteLine(o.Length);// 编译时就查:object 没有 Length
编译报错
Program.cs(2,21): error CS1061: “object”未包含“Length”的定义,并且找不到可接受第一个“object”类型参数的可访问扩展方法“Length”(是否缺少 using 指令或程序集引用?)

dynamic 能编译通过,到运行时才报错:

C#
1dynamic d = 20;// dynamic 装整数 20
2Console.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.String 的别名,也是从 object 派生的(见本节开头的输出)。赋值可以用引号,也可以用 @ 引号。普通引号里反斜杠是转义符(和后面一个字符合起来表示一个特殊字符),\W 认不出来就报错:

C#
1string s = "C:\Windows";// 普通引号里 \W 认不出来
编译报错
Program.cs(1,15): error CS1009: 无法识别的转义序列

前面加 @ 就成了逐字字符串,反斜杠只是普通字符,所以这两种写法完全相等:

C#
1string a = @"C:\Windows";// 逐字字符串:\ 就是普通的反斜杠
2string b = "C:\\Windows";// 普通字符串:\\ 才表示一个反斜杠
3Console.WriteLine(a);// 输出 C:\Windows
4Console.WriteLine(b);// 也输出 C:\Windows
5Console.WriteLine(a == b);// == 比较两个字符串是否相等:True
运行结果
C:\Windows
C:\Windows
True

逐字字符串还能直接换行,换行和缩进的空格都算在里面;里面要写一个引号,就连写两个 ""。下面把一段 4 行的网页代码写进 @"...",开头写成 @"<script type=""text/javascript"">,输出时换行、缩进原样保留,"" 变成一个 ":

运行结果
<script type="text/javascript">
    <!--
    -->
</script>

class、interface 和 delegate 是自己定义的引用类型,后面再讲。

11指针类型

指针存的是另一个值的内存地址,写法是类型加星号再加名字。在 C# 里,指针只能用在不安全的代码里,直接写就报错,后面“不安全代码”一节再讲。

C#
1char* cptr;// char 类型的指针 cptr
2int* iptr;// int 类型的指针 iptr
编译报错
Program.cs(1,1): error CS0214: 指针和固定大小缓冲区只能在不安全的上下文中使用
Program.cs(2,1): error CS0214: 指针和固定大小缓冲区只能在不安全的上下文中使用

12小结

  • 值类型自己装着数据,大小由类型定死(1、2、4、8、16 字节五档)。
  • 引用类型拿着一张牌,指向别处的数据;r2 = r1 后两个变量指向同一个对象。
  • 装箱自动,拆箱要写 (int);object 编译时查类型,dynamic 运行时才查。
  • @"C:\Windows" 和 "C:\\Windows" 相等;指针只能在不安全代码里用。

下一节:3.5 非要放进 int,该怎么办?

检查题

5 道,选完马上告诉你对不对。

  1. 1sizeof(long) 输出几?

  2. 2byte b = 256; 会怎样?

  3. 3Rectangle r2 = r1; r2.length = 9.5; 之后 r1.length 是?

  4. 4同样写 x.Length,x 是装着 20 的 object 和装着 20 的 dynamic,各在什么时候报错?

  5. 5@"C:\Windows" 和哪个相等?