C# 试题 04:运算符与表达式
61.后置自增与赋值覆盖:x = x++
难度: 实战
int x = 1;
x = x++;
Console.WriteLine(x);
- A.
2 - B.
1 - C.
0 - D. 编译失败
查看答案与解析
正确答案B
解析: x++ 先返回旧值 1 再自增(此时 x 为 2),随后赋值把 2 覆盖回 1,最终 x 是 1。
关键点: x = x++ 是经典陷阱,结果不是 2。
62.前置与后置自增的求值顺序
难度: 实战
int i = 0;
int r = i++ + ++i;
Console.WriteLine(r);
- A.
1 - B.
2 - C.
3 - D. 编译失败
查看答案与解析
正确答案B
解析: 操作数从左到右求值:左 i++ 得 0(i=1),右 ++i 得 2(i=2),0 + 2 = 2。
关键点: 自增副作用发生在各自操作数求值时刻,不要按“先算完整表达式”理解。
63.运算符优先级与操作数求值顺序
难度: 实战
static int A() { Console.Write("A"); return 1; }
static int B() { Console.Write("B"); return 2; }
Console.Write(A() + B() * 3);
- A.
BA7 - B.
AB9 - C.
AB7 - D.
BA9
查看答案与解析
正确答案C
解析: 优先级决定的是“谁先算”,不是“谁先求值”:A() 先求值打印 A,再求值 B() 打印 B,最后 1 + 2*3 = 7,输出 AB7。
关键点: 优先级 ≠ 求值顺序;副作用顺序按操作数从左到右。
64.复合赋值中的索引只求值一次
难度: 实战
int[] arr = { 1, 2 };
int index = 0;
arr[index++] += 10;
Console.WriteLine(arr[0] + "," + arr[1] + "," + index);
- A.
11,2,1 - B.
11,2,0 - C.
1,12,1 - D.
12,2,1
查看答案与解析
正确答案A
解析: arr[index++] += 10 中索引 index++ 只求值一次:目标为 arr[0],index 变为 1,arr[0] = 1 + 10 = 11。
关键点: 复合赋值不会“两次取索引”,这既是优点也是容易误判的点。
65.&& 与 || 的短路求值
难度: 进阶
static bool F() { Console.Write("F"); return false; }
static bool T() { Console.Write("T"); return true; }
Console.WriteLine(F() && T());
Console.WriteLine(T() || F());
- A. 第一行
FFalse,第二行TTrue - B. 两行都执行所有调用
- C. 第一行
FFalse,第二行TFTTrue - D. 编译失败
查看答案与解析
正确答案A
解析: && 左侧为 false 时不求值右侧;|| 左侧为 true 时不求值右侧。F() 打印 F 后返回 false,WriteLine(false) 打印 False。
关键点: 把有副作用的调用放在 &&/|| 右侧,可能永远不会执行。
66.int 移位计数的取模规则
难度: 实战
int x = 1;
Console.WriteLine(x << 31);
Console.WriteLine(x << 32);
- A.
-2147483648和0 - B.
2147483647和1 - C. 编译失败
- D.
-2147483648和1
查看答案与解析
正确答案D
解析: int 移位时移位计数对 32 取模:1 << 31 把符号位置 1,得 int.MinValue;1 << 32 等价 1 << 0,得 1。
关键点: 超出位宽的移位不会置零,而是取模,容易导致诡异结果。
67.string 的值比较与引用比较
难度: 进阶
string a = "hello";
string b = "hello";
string c = new string("hello".ToCharArray());
Console.WriteLine(a == b);
Console.WriteLine(a == c);
Console.WriteLine(ReferenceEquals(a, c));
- A.
True、False、False - B.
True、True、False - C.
True、True、True - D.
False、True、False
查看答案与解析
正确答案B
解析: string 重载了 ==,比较内容是否相等;ReferenceEquals 比较引用,new string(...) 创建了新对象,与常量引用不同。
关键点: 字符串的 == 是值语义,但跨 object 引用比较时可能意外。
68.object 引用比较与字符串驻留
难度: 实战
object a = "ab";
object b = "a" + "b";
Console.WriteLine(a == b);
- A.
True - B.
False - C. 运行时抛出异常
- D. 编译失败
查看答案与解析
正确答案A
解析: "a" + "b" 是编译期常量表达式,结果 "ab" 与字面量一样被驻留,a、b 引用同一对象;object 的 == 是引用比较,因此为 True。
关键点: 依赖字符串驻留写业务判断很危险,应改用 Equals/StringComparison。
69.as 与可空值类型的失败转换
难度: 实战
object o = "text";
Console.WriteLine(o as string);
Console.WriteLine(o as int?);
- A. 两行都输出
text - B. 第二行抛
InvalidCastException - C. 两行都输出空行
- D. 第一行输出
text,第二行输出空行
查看答案与解析
正确答案D
解析: as 只用于引用类型和可空值类型,失败时返回 null;"text" 不是 int,o as int? 为 null,WriteLine(null) 输出空行。
关键点: as 失败是 null 而不是异常;对不可空值类型不能使用 as。
70.nameof 对泛型类型与成员的处理
难度: 进阶
Console.WriteLine(nameof(List<int>));
Console.WriteLine(nameof(int.MaxValue));
- A.
List<int>和MaxValue - B.
List和MaxValue - C.
List和int.MaxValue - D. 编译失败
查看答案与解析
正确答案B
解析: nameof 返回标识符的名字:泛型类型只保留类型名(去掉类型参数),成员返回成员名(去掉类型前缀),所以是 List 和 MaxValue。
关键点: nameof 常用于反射/日志键,输出形式与直觉可能不同。
71.加法与移位的运算符优先级
难度: 进阶
int x = 1 + 2 << 3;
Console.WriteLine(x);
- A.
9 - B.
33 - C.
24 - D.
16
查看答案与解析
正确答案C
解析: 优先级从高到低:加减高于移位,1 + 2 << 3 解析为 (1 + 2) << 3 = 3 << 3 = 24。
关键点: 混淆移位与算术优先级是经典低级错误,可读性优先用括号。
72.赋值目标与右侧表达式的求值顺序
难度: 实战
int[] arr = { 10, 20 };
int i = 0;
arr[i] = arr[++i];
Console.WriteLine(arr[0] + "," + arr[1] + "," + i);
- A.
10,20,1 - B.
20,10,1 - C.
20,20,0 - D.
20,20,1
查看答案与解析
正确答案D
解析: 赋值表达式的左操作数先求值:目标确定是 arr[0](此时 i=0),再求右侧 arr[++i](i 变 1,值 20),最后 arr[0] = 20。
关键点: 赋值目标在右侧求值之前就被锁定,i 的副作用不会改目标。
73.循环中的 string += 与 StringBuilder
难度: 实战
- A. 原地修改现有字符串,单次成本为 O(1)
- B. 反复创建新字符串,累计复制成本约为 O(n²)
- C. 编译器始终改写为
StringBuilder - D. 超过长度阈值后自动切换为
StringBuilder
查看答案与解析
正确答案B
解析: string 不可变,+= 每次分配新对象并复制原内容,n 次拼接累计 O(n²);StringBuilder 用可变缓冲区把成本降到接近 O(n)。
关键点: 编译器的拼接优化只对常量/少量拼接有效,不适用于循环内动态拼接。
74.装箱值的 == 与 Equals
难度: 进阶
object a = 5;
object b = 5;
Console.WriteLine(a == b);
Console.WriteLine(a.Equals(b));
- A.
False和True - B.
True和True - C.
False和False - D.
True和False
查看答案与解析
正确答案A
解析: 两个装箱后的 int 是不同对象,object 的 == 比较引用为 False;Equals 是虚方法,Int32.Equals 按值比较为 True。
关键点: 装箱后忘记值语义是高频 Bug 来源。
75.C# 运算符的可重载范围
难度: 进阶
- A.
&&、||、?:均可直接重载 - B. 赋值运算符
=可以重载 - C.
+、-、*、==可重载,?:、is、as、=不可重载 - D. 只有
==和!=可以重载
查看答案与解析
正确答案C
解析: 可重载运算符是有明确清单的(算术、比较、移位等);&&/|| 不能直接重载,但重载 &/| 与 true/false 后编译器会合成短路版本;=、?:、is、as、?. 等不可重载。
关键点: “能重载什么”是面试常考点,不要凭感觉。
76.switch 模式匹配的分支顺序
难度: 实战
static string Describe(object? o) => o switch
{
int and > 100 => "big",
int => "small",
null => "none",
_ => "other"
};
Console.WriteLine(Describe(50));
Console.WriteLine(Describe(null));
Console.WriteLine(Describe("x"));
- A.
small、other、other - B.
small、none、other - C.
big、none、other - D. 编译失败
查看答案与解析
正确答案B
解析: 开关表达式自上而下匹配:50 不满足 int > 100,命中 int;null 命中 null 分支;"x" 落到 _。分支顺序决定结果。
关键点: 更具体的模式(关系/类型)要放在一般模式之前。
77.?? 链式表达式的短路求值
难度: 进阶
static int? Get() { Console.Write("G"); return null; }
var r = Get() ?? Get() ?? 1;
Console.WriteLine(r);
- A.
G和1 - B.
GGG和1 - C.
GG和1 - D. 编译失败
查看答案与解析
正确答案C
解析: ?? 左结合、逐个短路:第一次 Get() 得 null,再调第二次 Get() 得 null,最后取 1;Get 被调用两次,输出 GG 和 1。
关键点: 链式 ?? 不是“全部求值再选”,是逐个短路。
78.string 默认比较规则与 StringComparison
难度: 进阶
- A.
==与string.Equals(string)默认都进行区分大小写的序号比较 - B.
==是引用比较 - C.
Equals总是忽略大小写 - D. 默认比较会忽略大小写
查看答案与解析
正确答案A
解析: string 的 == 和 Equals(string) 默认使用 StringComparison.Ordinal(区分大小写);要忽略大小写或按文化比较,必须显式指定比较方式。
关键点: 字符串比较“默认区分大小写”是常见认知缺口。
79.属性复合赋值中的 getter/setter 调用次数
难度: 实战
var c = new Counter();
c.V += 1;
Console.Write(c.V);
class Counter
{
private int _v;
public int V
{
get { Console.Write("G"); return _v; }
set { Console.Write("S"); _v = value; }
}
}
- A.
GSG1 - B.
GS1 - C.
G1 - D.
GG1
查看答案与解析
正确答案A
解析: c.V += 1 展开为“读一次 getter(G)+ 算 + 写一次 setter(S)”;随后 Console.Write(c.V) 再读一次 getter(G)并输出 1,合计 GSG1。
关键点: 复合赋值对属性只读一次、只写一次,但后续访问会再次触发 getter。
80.复合乘法赋值的右侧表达式优先级
难度: 实战
int x = 3;
x *= 2 + 3;
Console.WriteLine(x);
- A.
9 - B.
11 - C. 编译失败
- D.
15
查看答案与解析
正确答案D
解析: 复合赋值 x *= 2 + 3 等价于 x = x * (2 + 3),右侧表达式整体参与运算,3 * 5 = 15;不是 x * 2 + 3。
关键点: 复合赋值的右侧是“完整表达式”,优先级陷阱常在这里出现。