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

变量

时长 6:38授课计划第 2 周

讲义

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

1变量是什么

变量就是程序里一块存储区的名字。比如 int i = 100;:可以把它想成台面上的一个格位,铭牌上刻着名字 i,格位的宽窄代表类型 int,格位里只放一块值 100。

类型决定这块存储有多大、怎么存,以及能对它做哪些操作:int 占 4 字节,double 占 8 字节,格位就宽一倍。格位只是比喻,内存里并没有一个个格子,变量名是给一段存储起的称呼。

2基本值类型

前面讲过的基本值类型大致分成这几类:

  • 整数类型:sbyte、byte、short、ushort、int、uint、long、ulong,char 也算在整数里(存的是字符,底下其实是一个 16 位的编号)。
  • 浮点型:float、double;十进制类型:decimal。
  • 布尔类型:bool,只有 true 和 false 两个值。
  • 可空类型:能多存一个 null,表示没有值。

string 是字符串,它其实是引用类型,不算值类型。enum 这样的值类型、class 这样的引用类型,后面的章节再讲。

3dynamic 类型

C# 4.0 加了 dynamic 类型,变量是什么类型要到运行时才确定。下面用的是顶级语句写法(不写类和 Main,代码从第一行开始执行):先给它一句字符串,再改成 100,各打印一次类型。

C#
1dynamic dynamicVariable = "This can be any type";// dynamic:类型先不定,这时装的是字符串
2Console.WriteLine(dynamicVariable.GetType());// GetType() 取到它现在的类型,输出 System.String
3dynamicVariable = 100;// 改成整数 100,不报错
4Console.WriteLine(dynamicVariable.GetType());// 类型跟着变了,输出 System.Int32
运行结果
System.String
System.Int32

换成普通的 int,同样改成字符串,就编译不过:

C#
1int n = 100;// 普通的 int 变量,类型定死了
2n = "This can be any type";// 想把字符串放进 int 变量
编译报错
Program.cs(2,5): error CS0029: 无法将类型“string”隐式转换为“int”

这就是静态类型(编译时就定死类型)的好处:错误在运行前就查出来,跑得也更快。所以平时都用静态类型,dynamic 留给特殊场合。

4定义变量

格式是:数据类型,后面跟变量列表,最后一个分号。列表里可以只有一个名字,也可以有好几个,用逗号隔开。

C#
1int i, j, k;// 一行开出三个 int 变量 i、j、k
2char c, ch;// 两个 char 变量
3float f, salary;// 两个 float 变量
4double d;// 一个 double 变量

这些变量现在都还没有值。也可以在定义的同时给初值:

C#
1int i = 100;// 定义 int 变量 i,同时给初值 100

5命名规则

名字不能乱起,有四条规则:

  1. 名字里可以有字母、数字和下划线。
  2. 必须以字母或下划线开头。
  3. 区分大小写。
  4. 关键字(C# 自己占用的单词,如 int、class)不能直接当名字。
C#
1int myVariable = 10;// 字母开头,可以
2string _userName = "John";// 下划线开头,也可以
3int age = 18;// 小写的 age
4int Age = 30;// 大写的 Age,和 age 是两个不同的变量
5Console.WriteLine("{0} {1}", myVariable, _userName);// {0} {1} 是占位符,依次换成后面两个变量的值
6Console.WriteLine("{0} {1}", age, Age);// 输出 18 30
运行结果
10 John
18 30

用数字开头,编译器提示“应输入标识符”(标识符就是名字):

C# · 第 1 行
1int 2count = 10;// 2count 以数字开头,不能当名字
编译报错
Program.cs(1,5): error CS1001: 应输入标识符
Program.cs(1,5): error CS1003: 语法错误,应输入“,”

拿关键字 class 当名字,同样报“应输入标识符”,后面还会连带出一串错:

C# · 第 1 行
1int class = 10;// class 是关键字,不能直接当名字
编译报错
Program.cs(1,5): error CS1001: 应输入标识符
Program.cs(1,5): error CS1002: 应输入 ;
Program.cs(1,11): error CS1001: 应输入标识符
Program.cs(1,11): error CS1514: 应为 {
Program.cs(1,11): error CS1513: 应输入 }
Program.cs(1,11): error CS8803: 顶级语句必须位于命名空间和类型声明之前。
Program.cs(1,11): error CS1525: 表达式项“=”无效

前面加个 @ 才能编译,但这种名字读着别扭,最好还是避开关键字:

C#
1int @class = 10;// 加了 @,class 就能当变量名
2Console.WriteLine(@class);// 用的时候也要带 @,输出 10
运行结果
10

6赋值

赋值写成 变量名 = 值;,声明时就初始化再在前面加上类型:类型 变量名 = 值;。这个等号念“赋值为”,把右边的值放进左边的变量,它不是“等于”,不是在比较两边。

一行可以初始化好几个。先猜猜,把它们和 i 一起打出来是什么:

C#
1int i = 100;// i 赋值为 100
2int d = 3, f = 5; /* 初始化 d 和 f. */// 一行初始化两个:d 是 3,f 是 5;/* */ 也是注释
3byte z = 22; /* 初始化 z. */// z 是 22
4double pi = 3.14159; /* 声明 pi 的近似值 */// pi 是 3.14159
5char x = 'x'; /* 变量 x 的值为 'x' */// 字符要用单引号,x 的值是字母 x
6Console.WriteLine("{0} {1} {2} {3} {4} {5}", i, d, f, z, pi, x);// 六个占位符依次换成六个变量的值
运行结果
100 3 5 22 3.14159 x

等号右边不一定是常量,也可以是算式,下面的完整例子就会看到。

7先赋值再使用

给变量初值是个好习惯,在 C# 里更是硬规定。声明了 int i,没赋值就直接打印:

C#
1int i;// 只声明,没给值
2Console.WriteLine(i);// 想读一个还没有值的变量
编译报错
Program.cs(2,19): error CS0165: 使用了未赋值的局部变量“i”

局部变量(在方法里声明的变量)没有值可读,编译就通不过。

8完整的例子

先声明三个变量 short a、int b、double c(都还没有值),然后赋值。读 a、b 去算只是复制一份值,它们自己不变:

C# · 第 13-16 行
13a = 10;// a 赋值为 10
14b = 20;// b 赋值为 20
15c = a + b;// 右边是算式:10 + 20 = 30,放进 c
16Console.WriteLine("a = {0}, b = {1}, c = {2}", a, b, c);// 输出 a = 10, b = 20, c = 30
查看完整代码(20 行)
C#
1using System;// 引入 System 命名空间(Console 就在里面)
2namespace VariableDefinition// 定义命名空间 VariableDefinition
3{// 命名空间 VariableDefinition 开始
4 class Program// 定义 Program 类
5 {// Program 类开始
6 static void Main(string[] args)// 程序入口 Main 方法
7 {// Main 开始
8 short a;// 声明 short 变量 a,还没有值
9 int b ;// 声明 int 变量 b(分号前多个空格不影响)
10 double c;// 声明 double 变量 c
11
12 /* 实际初始化 */// /* */ 中间的是注释,不会执行
13 a = 10;// a 赋值为 10
14 b = 20;// b 赋值为 20
15 c = a + b;// a + b 得 30;c 是 double,结果自动转成 double 放进 c
16 Console.WriteLine("a = {0}, b = {1}, c = {2}", a, b, c);// 依次填进 a、b、c 的值
17 Console.ReadLine();// 等待回车(现在的 VS 不写也会停住)
18 }// Main 结束
19 }// Program 类结束
20}// 命名空间 VariableDefinition 结束
运行结果
a = 10, b = 20, c = 30

9一个变量只有一个值

一个变量同一时刻只有一个值。很多人以为第二次赋值会加上去变成 30,运行看看:

C#
1int b = 10;// b 赋值为 10
2Console.WriteLine("b = {0}", b);// 输出 b = 10
3b = 20;// 再赋值为 20:旧值 10 被覆盖掉,不是 10 + 20
4Console.WriteLine("b = {0}", b);// 输出 b = 20
运行结果
b = 10
b = 20

10让用户输入

Console.ReadLine() 读用户敲的一行字,但它读到的永远是字符串。输入 25 直接加 1,是把 1 接在后面;先用 Convert.ToInt32 转成 int 再加 1,才是 26。

C#
1Console.WriteLine(Console.ReadLine() + 1);// 读到字符串 "25",字符串 + 1 是拼接,输出 251
2int num;// 声明 int 变量 num
3num = Convert.ToInt32(Console.ReadLine());// 再读一行 "25",Convert.ToInt32 把它转成整数 25
4Console.WriteLine(num + 1);// 整数相加:25 + 1,输出 26
运行结果
25
251
25
26

第 1、3 行的 25 是用户输入的内容。要是不转,直接交给 int 变量,编译就报错:

C#
1int num = Console.ReadLine();// 把读到的字符串直接放进 int 变量
编译报错
Program.cs(1,11): error CS0029: 无法将类型“string”隐式转换为“int”

11lvalue 和 rvalue

能放在赋值号左边的叫 lvalue,变量就是;只能放在右边的叫 rvalue,比如数值 20。

C#
1int g = 20;// g 是变量(lvalue),20 放进去

反过来写 10 = 20;,10 只是一个值,没有存储,20 没地方放:

C#
110 = 20;// 10 是数值(rvalue),不能放在赋值号左边
编译报错
Program.cs(1,1): error CS0131: 赋值号左边必须是变量、属性或索引器

12小结

  • 变量有名字、有类型,一个变量同一时刻只放一个值。
  • 声明是开出存储,赋值是换值,旧值被覆盖;用之前必须先赋值。
  • Console.ReadLine() 读到的是字符串,要用 Convert.ToInt32 转成数才能算。
  • 赋值号左边必须是变量,10 = 20; 编译不过。

下一节:变量是不是在哪儿都能用?出了大括号还在不在?

检查题

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

  1. 1int b = 10; b = 20; 之后 b 是?

  2. 2哪个能当变量名?

  3. 3输入 25,Console.WriteLine(Console.ReadLine() + 1); 输出?

  4. 4int i; Console.WriteLine(i); 会?

  5. 510 = 20; 会?