1运算符是什么
运算符是告诉编译器做哪种数学或逻辑操作的符号,C# 的运算符分六类:算术、关系、逻辑、位、赋值和其他;最后再讲一个式子里谁先算。视频把它们比成一张运算台上的计算器、天平、开关和灯、翻牌、格位、量尺和道岔,这只是比喻,真正干活的是编译器和 CPU。
2算术运算符
+ - * / % 是加、减、乘、除、取余,++ 让整数加 1,-- 让整数减 1。A++ 这个式子的值是旧值 10,之后 A 才变成 11。下面是顶级语句写法(不写类和 Main,代码从第一行开始执行):
| 1 | int A = 10; | // A 赋值 10 |
| 2 | int B = 20; | // B 赋值 20 |
| 3 | Console.WriteLine(A + B); | // 加:10 + 20,输出 30 |
| 4 | Console.WriteLine(A - B); | // 减:10 - 20,输出 -10 |
| 5 | Console.WriteLine(A * B); | // 乘:10 × 20,输出 200 |
| 6 | Console.WriteLine(B / A); | // 除:20 ÷ 10,输出 2 |
| 7 | Console.WriteLine(B % A); | // 取余:20 ÷ 10 刚好除尽,余数 0 |
| 8 | Console.WriteLine(A++); | // 打印 A++ 这个式子的值:是旧值 10,打印完 A 才变成 11 |
| 9 | Console.WriteLine(A); | // 再打印 A,这回是 11 |
查看完整输出(7 行)
30 -10 200 2 0 10 11
教程实例里 a 是 21、b 是 10,七行输出依次是 31、11、210、2、1、22、21。两个整数相除只留商,想要小数就让一边是小数:
| 19 | c = a / b; | // 两个整数相除只留商:21 ÷ 10 = 2,小数部分直接丢掉 |
| 20 | Console.WriteLine("Line 4 - c 的值是 {0}", c); | // 输出 2,不是 2.1 |
| 21 | c = a % b; | // 取余:21 ÷ 10 商 2 余 1 |
| 9 | Console.WriteLine(5 / 2); | // 两边都是整数:只留商,输出 2 |
| 10 | Console.WriteLine(5.0 / 2); | // 有一边是小数 5.0:结果带小数,输出 2.5 |
C# 里 % 叫余数运算符,负数时余数的符号跟着被除数走:
| 1 | Console.WriteLine(7 % 3); | // 7 ÷ 3 商 2 余 1,输出 1 |
| 2 | Console.WriteLine(-7 % 3); | // 被除数是负数,余数也是负的:输出 -1 |
| 3 | Console.WriteLine(7 % -3); | // 除数是负数,余数跟着被除数 7 走,还是正的:输出 1 |
3自增和自减
c = a++ 先把 a 的旧值交给 c,a 再加 1;c = ++a 是 a 先加 1,再把新值交给 c,-- 同理。教程实例里 a 每次都重新赋值为 1,再试四种写法:
| 12 | // a++ 先赋值再进行自增运算 | |
| 13 | b = a++; | // 后置 ++:a 的旧值 1 先交给 b,a 再加 1 变成 2 |
| 14 | Console.WriteLine("a = {0}", a); | // 输出 a = 2 |
| 15 | Console.WriteLine("b = {0}", b); | // 输出 b = 1 |
换成 ++a,a、b 都是 2;a-- 得 a 是 0、b 是 1;--a 两个都是 0。每组后面的 Console. 会停下等用户输入一行,实测时直接按的回车就是输出里的空行。
4关系运算符
关系运算符比较两个值,结果是 bool(只有真、假两种值,打印成 True、False)。一个等号 = 是赋值,两个等号 == 才是比较。A 是 10,B 是 20:
| 3 | Console.WriteLine(A == B); | // == 判断相等:10 等于 20 吗?输出 False |
| 4 | Console.WriteLine(A != B); | // != 判断不相等:输出 True |
| 5 | Console.WriteLine(A > B); | // 10 > 20?输出 False |
| 6 | Console.WriteLine(A < B); | // 10 < 20?输出 True |
| 7 | Console.WriteLine(A >= B); | // 10 >= 20?输出 False |
| 8 | Console.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 后:
| 31 | if (!(a && b)) | // 括号里 a && b 为假,! 再一翻就为真 |
C# 的 &&、|| 只能用在布尔值上(“非零就是真”是 C 语言的规矩),整数用会报错:
| 3 | Console.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:
| 1 | int A = 60; | // A 赋值 60 |
| 2 | int B = 13; | // B 赋值 13 |
| 3 | Console.WriteLine(Convert.ToString(A, 2).PadLeft(8, '0')); | // 把 A 写成二进制,左边补 0 补够 8 位:00111100 |
| 4 | Console.WriteLine(Convert.ToString(B, 2).PadLeft(8, '0')); | // B 的二进制:00001101 |
| 5 | Console.WriteLine(Convert.ToString(A & B, 2).PadLeft(8, '0')); | // A & B 的二进制:0000 1100,就是 12 |
| 6 | Console.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):
| 37 | c &= 2; | // c = c & 2:1011 & 0010 = 0010,就是 2 |
| 38 | Console.WriteLine("Line 9 - &= c 的值 = {0}", c); | // 输出 2 |
| 39 | | |
| 40 | c ^= 2; | // c = c ^ 2:0010 ^ 0010 = 0000,就是 0 |
| 41 | Console.WriteLine("Line 10 - ^= c 的值 = {0}", c); | // 输出 0 |
8其他运算符
sizeof 返回类型占几个字节:int 是 4,short 是 2,double 是 8。? : 叫条件运算符(三元运算符):条件为真取冒号前的值,为假取冒号后的值。教程实例里 a 是 10:
| 19 | b = (a == 1) ? 20 : 30; | // b 放结果:a == 1 为假,取冒号后面的 30 |
| 20 | Console.WriteLine("b 的值是 {0}", b); | // 输出 30 |
| 21 | | |
| 22 | b = (a == 10) ? 20 : 30; | // a == 10 为真,取冒号前面的 20 |
typeof 取到类型对象(描述这个类型的对象)。教程打印 string 的完整名字;任何类型都能用,typeof( 输出 System.。
| 7 | Type type = typeof(string); | // 取到 string 的类型对象,存进 Type 类型的变量 type |
| 8 | Console.WriteLine(type.FullName); | // FullName 是类型的完整名字,输出 System.String |
& 取变量的地址,* 通过指针(存地址的变量)取值,只能写在 unsafe(不安全代码)里:
| 1 | int a = 5; | // a 赋值 5 |
| 2 | int* p = &a; | // int* 是“指向 int 的指针”,&a 取 a 的地址 |
| 3 | Console.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</ 允许不安全代码,就能读出 5:
| 1 | unsafe | // 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:
| 3 | object obj = new StringReader("Hello"); | // object 能装任何对象;StringReader 是读字符串的类 |
| 4 | Console.WriteLine(obj is StringReader); | // obj 是 StringReader 吗?输出 True |
| 5 | Console.WriteLine(obj is string); | // obj 是 string 吗?输出 False |
| 6 | | |
| 7 | StringReader r = obj as StringReader; | // as 转成 StringReader:成功,r 拿到这个对象 |
| 8 | Console.WriteLine(r != null); | // r 不是空值,输出 True |
| 9 | string s = obj as string; | // as 转成 string:失败,不报错,s 得到空值 null |
| 10 | Console.WriteLine(s == null); | // s 是空值,输出 True |
C# 区分大小写,if 必须小写,不能写成 If。
9运算符优先级
一个式子里有好几个运算符,谁先算由优先级决定:
| 1 | int x = 7 + 3 * 2; | // 乘号优先级高:先算 3 * 2 = 6,再 7 + 6 = 13 |
| 2 | Console.WriteLine(x); | // 输出 13 |
从高到低依次是:后缀、一元、乘除、加减、移位、关系、相等、位与、位异或、位或、&&、||、条件 ?:、赋值。同一级的大多从左往右算(叫结合性),一元、条件和赋值从右往左。口诀:先括号,后乘除再加减,然后移位再关系,逻辑完了是条件。
口诀和表最后都有逗号,但 C# 没有逗号运算符,括号里写两个值会变成元组:
| 1 | int x = (1, 2); | // (1, 2) 是元组(一对值),不能放进 int |
Program.cs(1,9): error CS0029: 无法将类型“(int, int)”隐式转换为“int”
教程实例里 a、b、c、d 是 20、10、15、5,( 等三种写法都是 90,这一种是 50:
| 24 | e = 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,( 得 30:
| 8 | a += b * c; // 先计算乘法,再执行加法赋值 | // 赋值优先级最低:先算 5 × 2 = 10,再 a = 10 + 10 = 20 |
| 9 | Console.WriteLine(a); // 输出20 | // 输出 20 |
&& 比 || 先算,三种写法都打印 True:
| 1 | bool a = true, b = false, c = true; | // a 为 true,b 为 false,c 为 true |
| 2 | bool result = a || b && c; // 先计算与运算,再计算或运算 | // 先算 b && c 得 false,再 true || false 得 true |
| 3 | Console.WriteLine(result); // 输出true | // 实际打印 True(首字母大写) |
True True True
a 是 10、b 是 5,?: 比 > 低:
| 2 | string result = a > b ? "a大于b" : "a不大于b"; // 先判断大小关系,再执行条件语句 | // 先比 a > b 为真,再由 ?: 选出 "a大于b" |
| 3 | Console.WriteLine(result); // 输出"a大于b" | // 输出 a大于b |
10小结
- 算术算数(整数相除只留商),关系比大小,逻辑组合真假;
a++先给旧值,++a先加 1。 - 位运算动二进制的位,赋值把结果放回变量,其他运算符各管一件事。
- 式子复杂时按优先级算;拿不准就加括号,不容易算错,别人也读得懂。
下一节:有了真和假,程序怎么根据条件,走不同的路?
