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

运算符

时长 11:43授课计划第 3 周

讲义

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

1运算符是什么

运算符是告诉编译器做哪种数学或逻辑操作的符号,C# 的运算符分六类:算术、关系、逻辑、位、赋值和其他;最后再讲一个式子里谁先算。视频把它们比成一张运算台上的计算器、天平、开关和灯、翻牌、格位、量尺和道岔,这只是比喻,真正干活的是编译器和 CPU。

2算术运算符

+ - * / % 是加、减、乘、除、取余,++ 让整数加 1,-- 让整数减 1。A++ 这个式子的值是旧值 10,之后 A 才变成 11。下面是顶级语句写法(不写类和 Main,代码从第一行开始执行):

C#
1int A = 10;// A 赋值 10
2int B = 20;// B 赋值 20
3Console.WriteLine(A + B);// 加:10 + 20,输出 30
4Console.WriteLine(A - B);// 减:10 - 20,输出 -10
5Console.WriteLine(A * B);// 乘:10 × 20,输出 200
6Console.WriteLine(B / A);// 除:20 ÷ 10,输出 2
7Console.WriteLine(B % A);// 取余:20 ÷ 10 刚好除尽,余数 0
8Console.WriteLine(A++);// 打印 A++ 这个式子的值:是旧值 10,打印完 A 才变成 11
9Console.WriteLine(A);// 再打印 A,这回是 11
查看完整输出(7 行)
运行结果
30
-10
200
2
0
10
11

教程实例里 a 是 21、b 是 10,七行输出依次是 31、11、210、2、1、22、21。两个整数相除只留商,想要小数就让一边是小数:

C# · 第 19-21 行
19c = a / b;// 两个整数相除只留商:21 ÷ 10 = 2,小数部分直接丢掉
20Console.WriteLine("Line 4 - c 的值是 {0}", c);// 输出 2,不是 2.1
21c = a % b;// 取余:21 ÷ 10 商 2 余 1
C# · 第 9-10 行
9Console.WriteLine(5 / 2);// 两边都是整数:只留商,输出 2
10Console.WriteLine(5.0 / 2);// 有一边是小数 5.0:结果带小数,输出 2.5

C# 里 % 叫余数运算符,负数时余数的符号跟着被除数走:

C#
1Console.WriteLine(7 % 3);// 7 ÷ 3 商 2 余 1,输出 1
2Console.WriteLine(-7 % 3);// 被除数是负数,余数也是负的:输出 -1
3Console.WriteLine(7 % -3);// 除数是负数,余数跟着被除数 7 走,还是正的:输出 1

3自增和自减

c = a++ 先把 a 的旧值交给 c,a 再加 1;c = ++a 是 a 先加 1,再把新值交给 c,-- 同理。教程实例里 a 每次都重新赋值为 1,再试四种写法:

C# · 第 12-15 行
12// a++ 先赋值再进行自增运算
13b = a++;// 后置 ++:a 的旧值 1 先交给 b,a 再加 1 变成 2
14Console.WriteLine("a = {0}", a);// 输出 a = 2
15Console.WriteLine("b = {0}", b);// 输出 b = 1

换成 ++a,a、b 都是 2;a-- 得 a 是 0、b 是 1;--a 两个都是 0。每组后面的 Console.ReadLine() 会停下等用户输入一行,实测时直接按的回车就是输出里的空行。

4关系运算符

关系运算符比较两个值,结果是 bool(只有真、假两种值,打印成 True、False)。一个等号 = 是赋值,两个等号 == 才是比较。A 是 10,B 是 20:

C# · 第 3-8 行
3Console.WriteLine(A == B);// == 判断相等:10 等于 20 吗?输出 False
4Console.WriteLine(A != B);// != 判断不相等:输出 True
5Console.WriteLine(A > B);// 10 > 20?输出 False
6Console.WriteLine(A < B);// 10 < 20?输出 True
7Console.WriteLine(A >= B);// 10 >= 20?输出 False
8Console.WriteLine(A <= B);// 10 <= 20?输出 True
查看完整输出(6 行)
运行结果
False
True
False
True
False
True

教程实例用 if(下一章细讲)按比较结果打印一行结论,比如 a 是 21、b 是 10 时打印“a 不等于 b”“a 大于 b”。

5逻辑运算符

&&(并且)两边都为真结果才为真,||(或者)有一边为真就为真,!(非)把真变假、假变真。A 为 true、B 为 false 时,A && B 为假,A || B 为真,!(A && B) 为真。教程实例里 a 改成 false、b 是 true 后:

C# · 第 31 行
31if (!(a && b))// 括号里 a && b 为假,! 再一翻就为真

C# 的 &&、|| 只能用在布尔值上(“非零就是真”是 C 语言的规矩),整数用会报错:

C# · 第 3 行
3Console.WriteLine(x && y);// 想对两个整数用 &&
编译报错
Program.cs(3,19): error CS0019: 运算符“&&”无法应用于“int”和“int”类型的操作数

6位运算符

位运算直接对二进制的每一位动手:& 两位都是 1 才得 1,| 有一个 1 就得 1,^ 两位不同才得 1,~ 把 0 变 1、1 变 0。<< 往左挪、右边补 0,>> 往右挪、挪出去的丢掉。

int 有 32 位,~60 时前面那些 0 也全翻成了 1,最高位是 1,所以是负数 -61:

C#
1int A = 60;// A 赋值 60
2int B = 13;// B 赋值 13
3Console.WriteLine(Convert.ToString(A, 2).PadLeft(8, '0'));// 把 A 写成二进制,左边补 0 补够 8 位:00111100
4Console.WriteLine(Convert.ToString(B, 2).PadLeft(8, '0'));// B 的二进制:00001101
5Console.WriteLine(Convert.ToString(A & B, 2).PadLeft(8, '0'));// A & B 的二进制:0000 1100,就是 12
6Console.WriteLine(Convert.ToString(~A, 2));// ~A 的完整 32 位二进制,就是 -61
运行结果
00111100
00001101
00001100
11111111111111111111111111000011

教程实例对 60 和 13 依次做 & | ^ ~ << >>,六行输出是 12、61、49、-61、240、15。

7赋值运算符

赋值把右边的值放进左边的变量,旧值被换掉。C += A 等于 C = C + A,-= *= /= %= <<= >>= &= ^= |= 都是这个套路。教程实例里 c 依次得 21、42、21、441、21、11、44、11,此时 c 是 11(二进制 1011):

C# · 第 37-41 行
37c &= 2;// c = c & 2:1011 & 0010 = 0010,就是 2
38Console.WriteLine("Line 9 - &= c 的值 = {0}", c);// 输出 2
39
40c ^= 2;// c = c ^ 2:0010 ^ 0010 = 0000,就是 0
41Console.WriteLine("Line 10 - ^= c 的值 = {0}", c);// 输出 0

8其他运算符

sizeof 返回类型占几个字节:int 是 4,short 是 2,double 是 8。? : 叫条件运算符(三元运算符):条件为真取冒号前的值,为假取冒号后的值。教程实例里 a 是 10:

C# · 第 19-22 行
19b = (a == 1) ? 20 : 30;// b 放结果:a == 1 为假,取冒号后面的 30
20Console.WriteLine("b 的值是 {0}", b);// 输出 30
21
22b = (a == 10) ? 20 : 30;// a == 10 为真,取冒号前面的 20

typeof 取到类型对象(描述这个类型的对象)。教程打印 string 的完整名字;任何类型都能用,typeof(int).FullName 输出 System.Int32。

C# · 第 7-8 行
7Type type = typeof(string);// 取到 string 的类型对象,存进 Type 类型的变量 type
8Console.WriteLine(type.FullName);// FullName 是类型的完整名字,输出 System.String

& 取变量的地址,* 通过指针(存地址的变量)取值,只能写在 unsafe(不安全代码)里:

C#
1int a = 5;// a 赋值 5
2int* p = &a;// int* 是“指向 int 的指针”,&a 取 a 的地址
3Console.WriteLine(*p);// *p 通过指针读出 a 的值
编译报错
Program.cs(2,1): error CS0214: 指针和固定大小缓冲区只能在不安全的上下文中使用
Program.cs(2,10): error CS0214: 指针和固定大小缓冲区只能在不安全的上下文中使用
Program.cs(3,20): error CS0214: 指针和固定大小缓冲区只能在不安全的上下文中使用

包进 unsafe 块,并在项目文件里加 <AllowUnsafeBlocks>true</AllowUnsafeBlocks> 允许不安全代码,就能读出 5:

C#
1unsafe// unsafe 块:里面才允许用指针
2{// unsafe 块开始
3 int a = 5;// a 赋值 5
4 int* p = &a;// 指针 p 指向 a(存的是 a 的地址)
5 Console.WriteLine(*p);// *p 读出 p 指着的值:5
6}// unsafe 块结束

is 判断对象是不是某种类型;as 尝试转换,失败不报错,得到空值 null:

C# · 第 3-10 行
3object obj = new StringReader("Hello");// object 能装任何对象;StringReader 是读字符串的类
4Console.WriteLine(obj is StringReader);// obj 是 StringReader 吗?输出 True
5Console.WriteLine(obj is string);// obj 是 string 吗?输出 False
6
7StringReader r = obj as StringReader;// as 转成 StringReader:成功,r 拿到这个对象
8Console.WriteLine(r != null);// r 不是空值,输出 True
9string s = obj as string;// as 转成 string:失败,不报错,s 得到空值 null
10Console.WriteLine(s == null);// s 是空值,输出 True

C# 区分大小写,if 必须小写,不能写成 If。

9运算符优先级

一个式子里有好几个运算符,谁先算由优先级决定:

C#
1int x = 7 + 3 * 2;// 乘号优先级高:先算 3 * 2 = 6,再 7 + 6 = 13
2Console.WriteLine(x);// 输出 13

从高到低依次是:后缀、一元、乘除、加减、移位、关系、相等、位与、位异或、位或、&&、||、条件 ?:、赋值。同一级的大多从左往右算(叫结合性),一元、条件和赋值从右往左。口诀:先括号,后乘除再加减,然后移位再关系,逻辑完了是条件。

口诀和表最后都有逗号,但 C# 没有逗号运算符,括号里写两个值会变成元组:

C#
1int x = (1, 2);// (1, 2) 是元组(一对值),不能放进 int
编译报错
Program.cs(1,9): error CS0029: 无法将类型“(int, int)”隐式转换为“int”

教程实例里 a、b、c、d 是 20、10、15、5,(a + b) * c / d 等三种写法都是 90,这一种是 50:

C# · 第 24 行
24e = a + (b * c) / d; // 20 + (150/5)// e 放结果:10 × 15 = 150,150 ÷ 5 = 30,20 + 30 = 50

再看三个片段。a、b、c 是 10、5、2 时,a + b * c 得 20,(a + b) * c 得 30:

C# · 第 8-9 行
8a += b * c; // 先计算乘法,再执行加法赋值// 赋值优先级最低:先算 5 × 2 = 10,再 a = 10 + 10 = 20
9Console.WriteLine(a); // 输出20// 输出 20

&& 比 || 先算,三种写法都打印 True:

C# · 第 1-3 行
1bool a = true, b = false, c = true;// a 为 true,b 为 false,c 为 true
2bool result = a || b && c; // 先计算与运算,再计算或运算// 先算 b && c 得 false,再 true || false 得 true
3Console.WriteLine(result); // 输出true// 实际打印 True(首字母大写)
运行结果
True
True
True

a 是 10、b 是 5,?: 比 > 低:

C# · 第 2-3 行
2string result = a > b ? "a大于b" : "a不大于b"; // 先判断大小关系,再执行条件语句// 先比 a > b 为真,再由 ?: 选出 "a大于b"
3Console.WriteLine(result); // 输出"a大于b"// 输出 a大于b

10小结

  • 算术算数(整数相除只留商),关系比大小,逻辑组合真假;a++ 先给旧值,++a 先加 1。
  • 位运算动二进制的位,赋值把结果放回变量,其他运算符各管一件事。
  • 式子复杂时按优先级算;拿不准就加括号,不容易算错,别人也读得懂。

下一节:有了真和假,程序怎么根据条件,走不同的路?

检查题

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

  1. 1int a = 21, b = 10; 那么 a / b 和 a % b 是?

  2. 2int a = 1; int b = a++; 之后?

  3. 3int x = 1, y = 0; Console.WriteLine(x && y); 会?

  4. 4int c = 200; c %= 21; c <<= 2; 之后 c 是?

  5. 5int x = 7 + 3 * 2; x 是?