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,各打印一次类型。
| 1 | dynamic dynamicVariable = "This can be any type"; | // dynamic:类型先不定,这时装的是字符串 |
| 2 | Console.WriteLine(dynamicVariable.GetType()); | // GetType() 取到它现在的类型,输出 System.String |
| 3 | dynamicVariable = 100; | // 改成整数 100,不报错 |
| 4 | Console.WriteLine(dynamicVariable.GetType()); | // 类型跟着变了,输出 System.Int32 |
System.String System.Int32
换成普通的 int,同样改成字符串,就编译不过:
| 1 | int n = 100; | // 普通的 int 变量,类型定死了 |
| 2 | n = "This can be any type"; | // 想把字符串放进 int 变量 |
Program.cs(2,5): error CS0029: 无法将类型“string”隐式转换为“int”
这就是静态类型(编译时就定死类型)的好处:错误在运行前就查出来,跑得也更快。所以平时都用静态类型,dynamic 留给特殊场合。
4定义变量
格式是:数据类型,后面跟变量列表,最后一个分号。列表里可以只有一个名字,也可以有好几个,用逗号隔开。
| 1 | int i, j, k; | // 一行开出三个 int 变量 i、j、k |
| 2 | char c, ch; | // 两个 char 变量 |
| 3 | float f, salary; | // 两个 float 变量 |
| 4 | double d; | // 一个 double 变量 |
这些变量现在都还没有值。也可以在定义的同时给初值:
| 1 | int i = 100; | // 定义 int 变量 i,同时给初值 100 |
5命名规则
名字不能乱起,有四条规则:
- 名字里可以有字母、数字和下划线。
- 必须以字母或下划线开头。
- 区分大小写。
- 关键字(C# 自己占用的单词,如
int、class)不能直接当名字。
| 1 | int myVariable = 10; | // 字母开头,可以 |
| 2 | string _userName = "John"; | // 下划线开头,也可以 |
| 3 | int age = 18; | // 小写的 age |
| 4 | int Age = 30; | // 大写的 Age,和 age 是两个不同的变量 |
| 5 | Console.WriteLine("{0} {1}", myVariable, _userName); | // {0} {1} 是占位符,依次换成后面两个变量的值 |
| 6 | Console.WriteLine("{0} {1}", age, Age); | // 输出 18 30 |
10 John 18 30
用数字开头,编译器提示“应输入标识符”(标识符就是名字):
| 1 | int 2count = 10; | // 2count 以数字开头,不能当名字 |
Program.cs(1,5): error CS1001: 应输入标识符 Program.cs(1,5): error CS1003: 语法错误,应输入“,”
拿关键字 class 当名字,同样报“应输入标识符”,后面还会连带出一串错:
| 1 | int 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: 表达式项“=”无效前面加个 @ 才能编译,但这种名字读着别扭,最好还是避开关键字:
| 1 | int @class = 10; | // 加了 @,class 就能当变量名 |
| 2 | Console.WriteLine(@class); | // 用的时候也要带 @,输出 10 |
10
6赋值
赋值写成 变量名 = 值;,声明时就初始化再在前面加上类型:类型 变量名 = 值;。这个等号念“赋值为”,把右边的值放进左边的变量,它不是“等于”,不是在比较两边。
一行可以初始化好几个。先猜猜,把它们和 i 一起打出来是什么:
| 1 | int i = 100; | // i 赋值为 100 |
| 2 | int d = 3, f = 5; /* 初始化 d 和 f. */ | // 一行初始化两个:d 是 3,f 是 5;/* */ 也是注释 |
| 3 | byte z = 22; /* 初始化 z. */ | // z 是 22 |
| 4 | double pi = 3.14159; /* 声明 pi 的近似值 */ | // pi 是 3.14159 |
| 5 | char x = 'x'; /* 变量 x 的值为 'x' */ | // 字符要用单引号,x 的值是字母 x |
| 6 | Console.WriteLine("{0} {1} {2} {3} {4} {5}", i, d, f, z, pi, x); | // 六个占位符依次换成六个变量的值 |
100 3 5 22 3.14159 x
等号右边不一定是常量,也可以是算式,下面的完整例子就会看到。
7先赋值再使用
给变量初值是个好习惯,在 C# 里更是硬规定。声明了 int i,没赋值就直接打印:
| 1 | int i; | // 只声明,没给值 |
| 2 | Console.WriteLine(i); | // 想读一个还没有值的变量 |
Program.cs(2,19): error CS0165: 使用了未赋值的局部变量“i”
局部变量(在方法里声明的变量)没有值可读,编译就通不过。
8完整的例子
先声明三个变量 short a、int b、double c(都还没有值),然后赋值。读 a、b 去算只是复制一份值,它们自己不变:
| 13 | a = 10; | // a 赋值为 10 |
| 14 | b = 20; | // b 赋值为 20 |
| 15 | c = a + b; | // 右边是算式:10 + 20 = 30,放进 c |
| 16 | Console.WriteLine("a = {0}, b = {1}, c = {2}", a, b, c); | // 输出 a = 10, b = 20, c = 30 |
查看完整代码(20 行)
| 1 | using System; | // 引入 System 命名空间(Console 就在里面) |
| 2 | namespace 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,运行看看:
| 1 | int b = 10; | // b 赋值为 10 |
| 2 | Console.WriteLine("b = {0}", b); | // 输出 b = 10 |
| 3 | b = 20; | // 再赋值为 20:旧值 10 被覆盖掉,不是 10 + 20 |
| 4 | Console.WriteLine("b = {0}", b); | // 输出 b = 20 |
b = 10 b = 20
10让用户输入
Console. 读用户敲的一行字,但它读到的永远是字符串。输入 25 直接加 1,是把 1 接在后面;先用 Convert. 转成 int 再加 1,才是 26。
| 1 | Console.WriteLine(Console.ReadLine() + 1); | // 读到字符串 "25",字符串 + 1 是拼接,输出 251 |
| 2 | int num; | // 声明 int 变量 num |
| 3 | num = Convert.ToInt32(Console.ReadLine()); | // 再读一行 "25",Convert.ToInt32 把它转成整数 25 |
| 4 | Console.WriteLine(num + 1); | // 整数相加:25 + 1,输出 26 |
25 251 25 26
第 1、3 行的 25 是用户输入的内容。要是不转,直接交给 int 变量,编译就报错:
| 1 | int num = Console.ReadLine(); | // 把读到的字符串直接放进 int 变量 |
Program.cs(1,11): error CS0029: 无法将类型“string”隐式转换为“int”
11lvalue 和 rvalue
能放在赋值号左边的叫 lvalue,变量就是;只能放在右边的叫 rvalue,比如数值 20。
| 1 | int g = 20; | // g 是变量(lvalue),20 放进去 |
反过来写 10 = 20;,10 只是一个值,没有存储,20 没地方放:
| 1 | 10 = 20; | // 10 是数值(rvalue),不能放在赋值号左边 |
Program.cs(1,1): error CS0131: 赋值号左边必须是变量、属性或索引器
12小结
- 变量有名字、有类型,一个变量同一时刻只放一个值。
- 声明是开出存储,赋值是换值,旧值被覆盖;用之前必须先赋值。
Console.读到的是字符串,要用ReadLine() Convert.转成数才能算。ToInt32 - 赋值号左边必须是变量,
10 = 20;编译不过。
下一节:变量是不是在哪儿都能用?出了大括号还在不在?
