home commit with new plugins

This commit is contained in:
2026-06-21 09:51:55 +08:00
parent ebb9e385c5
commit 3d067fe75c
85 changed files with 10794 additions and 1443 deletions
-793
View File
@@ -1,793 +0,0 @@
# CSharp 复习题库(分层练习)
> 使用方式:每题都在“你的作答”填写答案;“批改区”由我批改后填写。
---
## 入门(16题)
### 入门-1 [选择]
C# 源文件常见扩展名是:A. `.cs` B. `.csharp` C. `.dotnet` D. `.clr`
**你的作答**
A
**批改区**
- 结果:✅ 正确
- 参考答案:A
### 入门-2 [选择]
`Main` 方法作为入口,正确签名之一是:A. `void main()` B. `static void Main(string[] args)` C. `public Main()` D. `int Main(void)`
**你的作答**
B
**批改区**
- 结果:✅ 正确
- 参考答案:B
### 入门-3 [选择]
下列哪个是值类型:A. `string` B. `object` C. `int` D. `Array`
**你的作答**
C
**批改区**
- 结果:✅ 正确
- 参考答案:C
### 入门-4 [选择]
`var x = 10;``x` 的编译期类型是:A. `dynamic` B. `object` C. `int` D. `long`
**你的作答**
C
**批改区**
- 结果:✅ 正确
- 参考答案:C
### 入门-5 [选择]
输出到控制台应使用:A. `Console.ReadLine` B. `Console.WriteLine` C. `Console.Open` D. `Console.Print`
**你的作答**
B
**批改区**
- 结果:✅ 正确
- 参考答案:B
### 入门-6 [填空]
用于单行注释的符号是 `____`
**你的作答**
`//`
**批改区**
- 结果:✅ 正确
- 参考答案:`//`
### 入门-7 [填空]
`if` 条件表达式的结果类型必须是 `____`
**你的作答**
`bool`
**批改区**
- 结果:✅ 正确
- 参考答案:`bool`
### 入门-8 [填空]
将字符串 `"123"``int`,可用 `int.Parse("123")``____("123", out n)`
**你的作答**
`Convert.Int32("123", out n)`
**批改区**
- 结果:❌ 错误
- 参考答案:`int.TryParse("123", out n)`
- 说明:`Convert.ToInt32` 没有 `out` 参数;带 `out` 的是 `TryParse`
### 入门-9 [判断]
`const` 字段的值可以在运行时修改。对/错
**你的作答**
**批改区**
- 结果:✅ 正确
- 参考答案:错
### 入门-10 [判断]
`string` 在 C# 中是引用类型。对/错
**你的作答**
**批改区**
- 结果:✅ 正确
- 参考答案:对
### 入门-11 [代码结果]
写出输出:
```csharp
int a = 5;
int b = 2;
Console.WriteLine(a / b);
Console.WriteLine(a / 2.0);
```
**你的作答**
`2`
`1.0`
**批改区**
- 结果:❌ 错误
- 参考答案:
```text
2
2.5
```
- 说明:`a / b` 是整数除法,结果为 `2`
### 入门-12 [代码改错]
修复下面代码使其可编译:
```csharp
Console.Writeline("Hello")
```
**你的作答**
```csharp
Console.Writeline("Hello");
```
**批改区**
- 结果:❌ 错误
- 参考答案:
```csharp
Console.WriteLine("Hello");
```
- 说明:`WriteLine``L` 需要大写。
### 入门-13 [简答]
`==``Equals` 的常见区别是什么(1-2句话)
**你的作答**
`Equals` 可以用于更复杂判断,`==` 只比较值。
**批改区**
- 结果:⚠️ 部分正确
- 参考要点:
- `==` 是运算符,可被重载;不同类型语义可能不同。
- `Equals` 是方法(实例/静态),常用于值相等判断。
-`string` 来说,`==` 比较的是内容(被重载),不是引用地址。
### 入门-14 [编程]
从控制台读取姓名和年龄,输出:`你好,张三,你明年 19 岁。`
**你的作答**
```csharp
string name = Console.Readline();
int age = int.Parse(Console.Readline());
Console.WriteLine($"你好,{name},你明年 {age} 岁。");
```
**批改区**
- 结果:❌ 错误
- 参考答案:
```csharp
string name = Console.ReadLine();
int age = int.Parse(Console.ReadLine()!);
Console.WriteLine($"你好,{name},你明年 {age + 1} 岁。");
```
- 说明:`ReadLine` 大小写错误,且“明年”应输出 `age + 1`
### 入门-15 [编程]
输入一个整数,判断奇偶并输出 `odd/even`
**你的作答**
```Csharp
int value = int.Parse(Console.ReadLine());
if( value%2 != 0 )
{
Console.WriteLine("odd");
}
else
{
Console.WriteLine("even");
}
```
**批改区**
- 结果:❌ 错误
- 主要问题:`int.TryParse(...)` 返回 `bool`,不能直接赋给 `int`
- 参考答案:
```csharp
if (int.TryParse(Console.ReadLine(), out int value))
Console.WriteLine(value % 2 == 0 ? "even" : "odd");
```
### 入门-16 [编程]
输入三个整数,输出最大值
**你的作答**
```Csharp
int[] value;
int maxValue = 0;
value[0] = int.Parse(Console.ReadLine());
value[1] = int.Parse(Console.ReadLine());
value[2] = int.Parse(Console.ReadLine());
for(int i = 0;i<2;i++)
{
if(value[i]>=value[i+1])
{
maxValue = value[i];
}
}
Console.WriteLine(maxValue);
```
**批改区**
- 结果:❌ 错误
- 主要问题:数组未初始化
- 参考答案:
```csharp
int a = int.Parse(Console.ReadLine()!);
int b = int.Parse(Console.ReadLine()!);
int c = int.Parse(Console.ReadLine()!);
int maxValue = Math.Max(a, Math.Max(b, c));
Console.WriteLine(maxValue);
```
---
## 基础(18题)
### 基础-1 [选择]
`for` 循环最适合:A. 已知次数迭代 B. 无限循环 C. 异常处理 D. 命名空间管理
**你的作答**
A
**批改区**
- 结果:✅ 正确
- 参考答案:A
### 基础-2 [选择]
`break` 在循环中的作用是:A. 跳过本次 B. 结束当前循环 C. 结束程序 D. 重启循环
**你的作答**
B
**批改区**
- 结果:✅ 正确
- 参考答案:B
### 基础-3 [选择]
`continue` 的作用是:A. 结束方法 B. 退出程序 C. 跳过本次迭代 D. 抛出异常
**你的作答**
C
**批改区**
- 结果:✅ 正确
- 参考答案:C
### 基础-4 [选择]
数组声明正确的是:A. `int arr = new int[3]` B. `int[] arr = new int[3]` C. `array<int> arr` D. `int arr[] = 3`
**你的作答**
B
**批改区**
- 结果:✅ 正确
- 参考答案:B
### 基础-5 [选择]
`List<T>` 位于命名空间:A. `System.Text` B. `System.Collections.Generic` C. `System.Linq.Expressions` D. `System.IO`
**你的作答**
B
**批改区**
- 结果:✅ 正确
- 参考答案:B
### 基础-6 [填空]
定义一个可空整型变量:`int____ x = null;`
**你的作答**
?
**批改区**
- 结果:✅ 正确
- 参考答案:`?`
### 基础-7 [填空]
声明并初始化字典:`Dictionary<string,int> d = new ____();`
**你的作答**
`Dictionary<string,int>
**批改区**
- 结果:✅ 正确
- 参考答案:`Dictionary<string,int>`
- 说明:你填的类型名是对的,只是 Markdown 里的反引号没闭合。
### 基础-8 [填空]
捕获异常的关键字是 `____`
**你的作答**
try和catch
**批改区**
- 结果:⚠️ 部分正确
- 参考答案:`catch`
- 说明:`try` 用来包裹可能抛异常的代码,真正负责捕获异常的是 `catch`。
### 基础-9 [填空]
释放非托管资源常用模式:实现 `____` 接口
**你的作答**
(待填写)
**批改区**
(待批改)
### 基础-10 [代码结果]
写出输出:
```csharp
for (int i = 0; i < 5; i++)
{
if (i == 2) continue;
if (i == 4) break;
Console.Write(i);
}
```
**你的作答**
(待填写)
**批改区**
(待批改)
### 基础-11 [代码结果]
写出输出:
```csharp
string s = null;
Console.WriteLine(s?.Length ?? -1);
```
**你的作答**
(待填写)
**批改区**
(待批改)
### 基础-12 [改错]
找出并修复问题:
```csharp
int[] nums = {1,2,3};
Console.WriteLine(nums[3]);
```
**你的作答**
(待填写)
**批改区**
(待批改)
### 基础-13 [简答]
`throw;` 和 `throw ex;` 的差异是什么
**你的作答**
(待填写)
**批改区**
(待批改)
### 基础-14 [编程]
用 `while` 计算 `1..n` 的和(n 从输入读取)
**你的作答**
(待填写)
**批改区**
(待批改)
### 基础-15 [编程]
统计一个字符串中元音字母个数(忽略大小写)
**你的作答**
(待填写)
**批改区**
(待批改)
### 基础-16 [编程]
手写一个方法 `bool IsPrime(int n)`
**你的作答**
(待填写)
**批改区**
(待批改)
### 基础-17 [编程]
使用 `List<int>` 去重并升序输出
**你的作答**
(待填写)
**批改区**
(待批改)
### 基础-18 [编程]
使用 `Dictionary<string,int>` 统计一句话中每个单词出现次数
**你的作答**
(待填写)
**批改区**
(待批改)
---
## 核心(18题)
### 核心-1 [选择]
面向对象三大特性不包括:A. 封装 B. 继承 C. 多态 D. 事务
**你的作答**
(待填写)
**批改区**
(待批改)
### 核心-2 [选择]
`virtual` + `override` 用于:A. 字段隐藏 B. 运行时多态 C. 命名空间引用 D. 泛型约束
**你的作答**
(待填写)
**批改区**
(待批改)
### 核心-3 [选择]
`abstract` 类:A. 可直接实例化 B. 不能包含实现 C. 不能被继承 D. 不能直接实例化
**你的作答**
(待填写)
**批改区**
(待批改)
### 核心-4 [选择]
`interface` 成员默认:A. private B. protected C. public 合同 D. internal
**你的作答**
(待填写)
**批改区**
(待批改)
### 核心-5 [选择]
泛型约束 `where T : class` 的含义是:A. 值类型 B. 引用类型 C. 必须有无参构造 D. 必须实现 IDisposable
**你的作答**
(待填写)
**批改区**
(待批改)
### 核心-6 [填空]
声明一个自动属性:`public int Age { ____; ____; }`
**你的作答**
(待填写)
**批改区**
(待批改)
### 核心-7 [填空]
只读自动属性常见写法:`public string Id { get; ____; }`
**你的作答**
(待填写)
**批改区**
(待批改)
### 核心-8 [填空]
委托声明关键字是 `____`
**你的作答**
(待填写)
**批改区**
(待批改)
### 核心-9 [填空]
事件订阅语法:`publisher.SomeEvent ____ Handler;`
**你的作答**
(待填写)
**批改区**
(待批改)
### 核心-10 [代码阅读]
输出是什么:
```csharp
class A { public virtual void M() => Console.Write("A"); }
class B : A { public override void M() => Console.Write("B"); }
A x = new B();
x.M();
```
**你的作答**
(待填写)
**批改区**
(待批改)
### 核心-11 [代码阅读]
输出是什么:
```csharp
int Add(int a, int b = 10) => a + b;
Console.WriteLine(Add(5));
```
**你的作答**
(待填写)
**批改区**
(待批改)
### 核心-12 [改错]
指出并修复:
```csharp
interface IRun { void Go() { Console.WriteLine("go"); } }
```
**你的作答**
(待填写)
**批改区**
(待批改)
### 核心-13 [简答]
`override` 与 `new` 隐藏成员的行为区别
**你的作答**
(待填写)
**批改区**
(待批改)
### 核心-14 [简答]
什么时候用接口,什么时候用抽象类(简述判断标准)
**你的作答**
(待填写)
**批改区**
(待批改)
### 核心-15 [编程]
设计 `Shape` 抽象类,派生 `Circle/Rectangle`,实现 `GetArea()`
**你的作答**
(待填写)
**批改区**
(待批改)
### 核心-16 [编程]
实现泛型方法 `Swap<T>(ref T a, ref T b)`
**你的作答**
(待填写)
**批改区**
(待批改)
### 核心-17 [编程]
定义一个 `event EventHandler<DataEventArgs>`,在数据变化时触发
**你的作答**
(待填写)
**批改区**
(待批改)
### 核心-18 [编程]
实现一个只读集合包装(对外不可修改,内部可维护)
**你的作答**
(待填写)
**批改区**
(待批改)
---
## 进阶(18题)
### 进阶-1 [选择]
`async/await` 中,`await` 作用是:A. 创建线程 B. 异步等待任务完成 C. 阻塞 UI 线程 D. 强制并行
**你的作答**
(待填写)
**批改区**
(待批改)
### 进阶-2 [选择]
表示“一个无返回值异步操作”的常用返回类型:A. `void` B. `Task` C. `object` D. `Result`
**你的作答**
(待填写)
**批改区**
(待批改)
### 进阶-3 [选择]
LINQ 中用于投影的操作符:A. `Where` B. `Select` C. `Any` D. `Count`
**你的作答**
(待填写)
**批改区**
(待批改)
### 进阶-4 [选择]
延迟执行是 LINQ 的常见特性之一,以下通常会立即执行的是:A. `Where` B. `Select` C. `ToList` D. `OrderBy`
**你的作答**
(待填写)
**批改区**
(待批改)
### 进阶-5 [选择]
`using` 语句主要用于:A. 导入命名空间 B. 自动释放资源 C. 定义别名 D. 启动任务
**你的作答**
(待填写)
**批改区**
(待批改)
### 进阶-6 [填空]
声明取消令牌源:`var cts = new ____();`
**你的作答**
(待填写)
**批改区**
(待批改)
### 进阶-7 [填空]
正则匹配常用类型:`System.Text.RegularExpressions.____`
**你的作答**
(待填写)
**批改区**
(待批改)
### 进阶-8 [填空]
线程安全递增可用:`Interlocked.____(ref x);`
**你的作答**
(待填写)
**批改区**
(待批改)
### 进阶-9 [填空]
文件全部文本读取:`File.____(path)`
**你的作答**
(待填写)
**批改区**
(待批改)
### 进阶-10 [代码结果]
写出输出:
```csharp
var nums = new[] {1,2,3,4,5};
var q = nums.Where(x => x % 2 == 1).Select(x => x * x);
Console.WriteLine(string.Join(",", q));
```
**你的作答**
(待填写)
**批改区**
(待批改)
### 进阶-11 [代码结果]
判断是否死锁风险并说明一句原因:
```csharp
Task.Run(async () => { await Task.Delay(100); }).Wait();
```
**你的作答**
(待填写)
**批改区**
(待批改)
### 进阶-12 [简答]
`Task.WhenAll` 与顺序 `await` 的区别
**你的作答**
(待填写)
**批改区**
(待批改)
### 进阶-13 [简答]
`IEnumerable<T>` 与 `IQueryable<T>` 的核心差异
**你的作答**
(待填写)
**批改区**
(待批改)
### 进阶-14 [编程]
异步读取文本文件并统计行数,支持取消(`CancellationToken`
**你的作答**
(待填写)
**批改区**
(待批改)
### 进阶-15 [编程]
用 LINQ 完成:给定学生列表,按班级分组后求每组平均分并降序输出
**你的作答**
(待填写)
**批改区**
(待批改)
### 进阶-16 [编程]
实现一个简单缓存 `MemoryCache<TKey,TValue>`(带过期时间)
**你的作答**
(待填写)
**批改区**
(待批改)
### 进阶-17 [编程]
写一个扩展方法 `IsNullOrEmptyEx(this string? s)`
**你的作答**
(待填写)
**批改区**
(待批改)
### 进阶-18 [编程]
设计一个可并发调用的计数器类,要求线程安全
**你的作答**
(待填写)
**批改区**
(待批改)
@@ -0,0 +1,871 @@
# CSharp 综合练习 01
## 说明
- 本题单依据你当前已有笔记整理,范围主要来自:
- `基本概念`
- `入门:常量、转义字符、格式转换、字符串拼接、三目运算符`
- `基础:值类型和引用类型、ref 和 out、变长参数、枚举`
- `核心:继承、静态成员、成员属性、索引器`
- 你可以直接在每道题下方的 `你的作答` 区域填写答案。
- 我后续会在 `批改区` 直接给你批改和点评。
---
## 入门
### 1. [填空题] 面向对象基础概念
根据你的笔记,完成下面填空:
1. 面向对象三大特性分别是:`____``____``____`
2. “对扩展开放,对修改关闭”指的是 `____原则`
3. “优先使用组合,而不是为了复用就滥用继承”指的是 `____原则`
**你的作答**
1.继承、封装、多态
2.开闭原则
3.合成复用原则
**批改区**
判定:正确。
点评:三大特性、开闭原则、合成复用原则都答对了。第 1 小题的顺序不影响正确性,更常见的写法是“封装、继承、多态”。
### 2. [简答题] const
根据 `常量.md`,回答下面问题:
1. `const` 声明的变量有哪些核心特点?
2. 请写出两个适合使用 `const` 的场景。
**你的作答**
1.不能修改、必须初始化
2.定义PI、重力加速度等需要某个常量的时候
**批改区**
判定:正确。
点评:你抓住了 `const` 的两个核心点:必须初始化、之后不能修改;场景举例也合适,比如 `PI`、重力加速度这类固定值。
### 3. [代码阅读题] 转义字符
阅读下面代码,写出三次输出的大致结果,并说明分别用到了哪些转义字符。
```csharp
string title = "\"C#入门\"";
string path = "C:\\Users\\Kister";
string text = "第一行\n第二行";
Console.WriteLine(title);
Console.WriteLine(path);
Console.WriteLine(text);
```
**你的作答**
"C#入门"
C:\Users\Kister
第一行
第二行
\"把"保留在字符串里了
\\把\保留在字符串里了
\n是换行
**批改区**
判定:正确。
点评:输出结果和三个转义字符的作用都写对了:`\"` 保留双引号,`\\` 保留反斜杠,`\n` 表示换行。
### 4. [代码阅读题] 格式转换
阅读下面代码,并回答问题:
```csharp
int a = 10;
double b = a;
int c = (int)12.8;
string s = 123.ToString();
```
问题:
1. 哪一行发生了隐式转换?
2. 哪一行发生了显式转换?
3. 哪一行把其他类型转成了 `string`
4. 为什么 `Parse` 方法通常更适合“字符串转数字”这类场景?
**你的作答**
1.第2行
2.第3、4行
3.第四行
4.因为转不了数字对象会报错
**批改区**
判定:部分正确。
点评:
- 第 1 问答对了,隐式转换是第 2 行:`double b = a;`
- 第 2 问应主要写第 3 行:`int c = (int)12.8;`
- 第 4 行 `123.ToString()` 更适合归类为“转成字符串”,不算这里重点考的强制类型转换
- 第 4 问你的回答有点偏了。更准确地说:`Parse` 本来就是专门把字符串内容解析成目标数字类型的方法,所以特别常用于“字符串转数字”
### 5. [代码阅读题] 字符串拼接和三目运算符
写出下面变量的最终值,并说明原因。
```csharp
string str1 = "" + 1 + 2 + 3;
string str2 = 1 + 2 + 3 + "";
string result = 5 > 3 ? "真" : "假";
```
**你的作答**
str1为"123"""让string知道这是在字符串拼接,123会作为字符串拼起来
str2为"6"""在最后,所以前面做的是运算而非拼接
result为"真"
**批改区**
判定:正确。
点评:`str1``"123"``str2``"6"``result``"真"`,你的原因分析也对,说明你已经能区分“先做数值运算”还是“先做字符串拼接”了。
---
## 基础
### 6. [判断题] 值类型和引用类型
请判断下面说法是对还是错,如果错了请改正:
1. `int` 是引用类型
2. `string` 是引用类型
3. 数组是值类型
4. 值类型赋值时通常复制的是数据本身
5. 引用类型变量保存的是对象的引用
**你的作答**
1.错,int是典型值类型
2.对
3.错,数组存放的是值,本身是引用类型
4.对
5.对
**批改区**
判定:基本正确。
点评:整体判断对了。第 3 小题建议表述成“数组本身是引用类型,数组元素可以是值类型也可以是引用类型”,这样会更严谨,不要把“数组存放的是值”说得太绝对。
### 7. [代码阅读题] 传值和传引用
阅读下面代码,写出最终输出结果,并解释原因。
```csharp
static void ChangeA(int x)
{
x = 100;
}
static void ChangeB(int[] arr)
{
arr[0] = 100;
}
int a = 1;
int[] b = { 1 };
ChangeA(a);
ChangeB(b);
Console.WriteLine(a);
Console.WriteLine(b[0]);
```
**你的作答**
1
100
a作为值类型传递值本身而不是地址,函数内部更改的是函数内部x地址的值,所以外部还是1
数组b作为引用类型传递的是地址,函数内部修改的是b引用地址上的值,所以外部也改变了
**批改区**
判定:正确。
点评:输出 `1``100` 都对,核心原因也说对了。更严谨一点可以记成:
- `int` 传入方法时改的是副本
- 数组传入方法时传的是引用的副本,但这个副本仍然指向同一个数组对象,所以能改到内部元素
### 8. [简答题] ref 和 out
根据你的笔记,总结 `ref``out` 的区别,至少写 3 点。然后再补充一个你认为适合用 `out` 的使用场景。
**你的作答**
1.ref在使用时必须在外部赋值,out在传入前不能赋值,或赋值和传出值无关
2.相反out在函数内部必须赋值,ref可以不
3.out流向只输出,ref是双向
out的应用场景可以是TryParse,将某个类型转换为int类型而int默认情况下(不加?修饰)不能为null,所以必须用out在TryParse方法体内返回一个值出来
**批改区**
判定:部分正确。
点评:
- `ref` 传入前必须先初始化,这点你答对了
- `out` 传入前是“可以不初始化”,不是“不能初始化”
- `out` 在方法结束前必须赋值,这点你答对了
-`out` 理解成“额外带回一个结果”也对,`TryParse` 的例子很合适
补充:这里的关键不是“`int` 不能为 `null`”,而是方法除了返回“是否成功”之外,还要再返回一个解析出来的值,所以适合用 `out`
### 9. [找错题] out 参数
下面代码能否通过编译?如果不能,请指出原因。
```csharp
void Test(ref int a, out int b)
{
a++;
}
int x = 1;
int y;
Test(ref x, out y);
```
**你的作答**
不能,out传递的参数在函数结束时没有赋值
**批改区**
判定:正确。
点评:问题抓得很准,`out int b` 在方法结束前没有赋值,所以代码不能通过编译。
### 10. [应用题] 变长参数
请根据你的笔记,回答下面两个问题:
1. `params` 变长参数有哪些规则?
2. 请写出一个方法签名,使它能够接收任意个 `int` 参数,并返回它们的和。只写方法签名也可以,愿意的话也可以顺手把方法体写出来。
**你的作答**
1.只能作用于数组对象,必须放在方法参数列表的末尾
2.访问修饰符 static int Sum(params int[] arr){}
具体里面是一个循环了arr.Length次的数组,逐次取出并累加到一个变量里并最终将他返回
**批改区**
判定:部分正确。
点评:你答到了两条关键规则:
- `params` 后面必须是数组
- `params` 参数必须放在参数列表末尾
还差一条:
- 一个方法里最多只能有一个 `params` 参数
你写的方法签名 `static int Sum(params int[] arr)` 是正确的。
### 11. [代码阅读题] 枚举
阅读下面代码并回答问题:
```csharp
enum E_PlayerState
{
Idle,
Move,
Attack = 10,
Die
}
```
问题:
1. `Idle``Move``Attack``Die` 对应的整数值分别是多少?
2. 为什么笔记里说枚举和 `switch` “天生一对”?
3. 为什么一般不推荐把枚举量写成中文?
**你的作答**
1.0、1、10、11
2.因为switch的case可以识别传递的枚举类型,或直接用枚举的"下标"值作为对象
3.中文在编译时容易报错,兼容性不好
**批改区**
判定:基本正确。
点评:
- 第 1 问数值全对:`0、1、10、11`
- 第 2 问意思基本对,可以再补一句:用枚举配合 `switch` 时,`case` 可以直接按状态名写,代码比直接写数字更清晰
- 第 3 问建议改成“命名规范、可读性和兼容性更好”,而不是说“中文一定会编译报错”
---
## 核心
### 12. [简答题] 继承和 base
根据你的笔记,回答下面问题:
1. `Dog : Animal` 表达的是什么关系?
2. 为什么子类里通常可以访问父类的 `protected` 成员,但不能直接访问 `private` 成员?
3. `base(name)` 的作用是什么?
4. 为什么说“构造函数不会被继承”?
**你的作答**
1.Dog继承Animal的关系
2.因为private修饰的成员只有当前类自己才能访问,而protect修饰的成员是当前或继承该类的子类都可以访问
3.base(name)意思是继承的父类的name成员
4.因为首先违反了命名规则,如果子类继承了父类的构造函数,那么函数名和子类对不上,其次重名之后程序会不知道调用的是子类的构造函数还是父类的构造函数
**批改区**
判定:部分正确。
点评:
- 第 1 问和第 2 问基本对
- ==第 3 问答错了,`base(name)` 的作用是“调用父类构造函数,并把 `name` 传给父类==”
- 第 4 问的更准确说法是:构造函数属于各自类型,不会被子类直接继承,子类只是可以通过 `base(...)` 去调用父类构造函数
==补一句你这题最该记住的话:`base(name)` 不是“继承父类成员”,而是“调用父类构造函数”。==
### 13. [代码阅读题] 静态成员
阅读下面代码,写出输出结果,并解释为什么这里更适合用 `Player.Count` 来访问。
```csharp
class Player
{
public static int Count;
public string Name;
public Player(string name)
{
Name = name;
Count++;
}
}
Player p1 = new Player("A");
Player p2 = new Player("B");
Console.WriteLine(Player.Count);
```
然后再补充回答:
1. 静态成员和实例成员最核心的区别是什么?
2. `const``static readonly` 的区别是什么?
**你的作答**
因为static int了count成员后,所有player类实例化对象在操作count时只会操作同一个对象,这样可以统一一个值,如果不是static的话每个player实例化对象都会操作自己的count,反而对不上总人数了。
1.静态成员首先在程序调用到时会自动创建内存并一直存在直到程序结束不会被GC清理,实例成员如果没有引用后可以被GC清理,静态成员只有唯一一个地址/标识,但实例成员可以是被实例化成很多地址/成员
2.const和static readonly的区别是const必须在声明时赋值,static readonly可以先声明,后续在构造函数中赋值
**批改区**
判定:部分正确。
点评:
- 你对“为什么适合用 `Player.Count`”的理解是对的:它是所有对象共享的一份数据
- 但这题少写了最直接的一问:输出结果是 `2`
- “静态成员一直存在直到程序结束不会被 GC 清理”这个说法太绝对了,先不要这么记
更稳的记法:
- 静态成员属于类型本身
- 实例成员属于具体对象
- `const` 必须声明时赋值,属于编译期常量
- `static readonly` 可以在声明时赋值,也可以在静态构造函数中赋值,属于运行期确定
### 14. [代码编写题] 成员属性
请补全下面这个类中的 `Age` 属性,要求:
1. 外部可以读和写
2. 当赋值小于 0 时,内部实际保存为 0
```csharp
class Person
{
private int age;
// 请在这里补全 Age 属性
}
```
**你的作答**
```csharp
class Person
{
private int age;
public int Age
{
get
{
return age;
}
set
{
if(value < 0) age = 0;
age = value;
}
}
}
```
**批改区**
判定:有错误,需要修改。
点评:你的 `get` 写对了,但 `set` 里有逻辑漏洞:
```csharp
if(value < 0) age = 0;
age = value;
```
这样即使 `value < 0`,下一行还是会把负数重新赋给 `age`,所以最终并不会变成 `0`
更正后可以写成:
```csharp
public int Age
{
get
{
return age;
}
set
{
if (value < 0)
{
age = 0;
return;
}
age = value;
}
}
```
### 15. [代码编写题] 索引器
请根据你的笔记,给下面这个类补一个索引器,让外部可以通过 `team[0]` 这种方式读写成员名。
```csharp
class Team
{
private string[] members = new string[3];
// 请在这里补全索引器
}
```
补充回答:
1. 为什么索引器的名字固定写成 `this`
2. 为什么说它“很像属性,但访问器带参数”?
**你的作答**
```csharp
class Team
{
private string[] members = new string[3];
public string this[int index]{get{return members[index];}}
}
```
1.表示该类本身
2.因为它可以get或set,但访问需要下标
**批改区**
判定:部分正确。
点评:你已经把索引器最核心的语法写出来了,也知道它为什么像属性。但题目要求“可以读写成员名”,所以这里只写 `get` 还不够,还需要补 `set`
参考写法:
```csharp
public string this[int index]
{
get
{
return members[index];
}
set
{
members[index] = value;
}
}
```
### 16. [综合设计题] 小型角色类
请你自己设计一个简单的 `Player``Student` 类,要求至少用到下面 4 个知识点中的 3 个:
- 枚举
- 继承
- 静态成员
- 属性
- 索引器
要求:
1. 写出核心代码
2. 简单说明你每一部分分别用到了什么知识点
3. 如果你不想写完整代码,也可以写类结构和关键成员
**你的作答**
```Csharp
using System;
namespace Exercise2
{
/// <summary>
/// 学生年级枚举。
/// </summary>
enum StudentLevel
{
Freshman,
Sophomore,
Junior,
Senior
}
/// <summary>
/// 表示三门课程成绩的值类型。
/// </summary>
struct Score
{
public int Chinese;
public int Math;
public int English;
/// <summary>
/// 三门成绩总分。
/// </summary>
public int Total
{
get { return Chinese + Math + English; }
}
/// <summary>
/// 初始化成绩。
/// </summary>
public Score(int chinese, int math, int english)
{
Chinese = chinese;
Math = math;
English = english;
}
/// <summary>
/// 重载 + 运算符,用于合并两个成绩对象。
/// </summary>
public static Score operator +(Score left, Score right)
{
return new Score(
left.Chinese + right.Chinese,
left.Math + right.Math,
left.English + right.English
);
}
}
/// <summary>
/// 学生基类。
/// </summary>
class Student
{
public const int PassLine = 180;
private int _id;
private string _name;
private int _age;
private Score _score;
/// <summary>
/// 统计已创建的学生对象总数。
/// </summary>
public static int StudentCount { get; private set; }
public int Id
{
get { return _id; }
}
public StudentLevel Level { get; private set; }
/// <summary>
/// 学生姓名属性。
/// </summary>
public string Name
{
get { return _name; }
set
{
_name = string.IsNullOrWhiteSpace(value) ? "未命名" : value;
}
}
/// <summary>
/// 学生年龄属性,年龄不能小于 0。
/// </summary>
public int Age
{
get { return _age; }
set
{
if (value < 0)
{
throw new ArgumentException("年龄不能小于 0。");
}
_age = value;
}
}
/// <summary>
/// 学生成绩属性,三门成绩都不能小于 0。
/// </summary>
public Score Score
{
get { return _score; }
set
{
if (value.Chinese < 0 || value.Math < 0 || value.English < 0)
{
throw new ArgumentException("成绩不能小于 0。");
}
_score = value;
}
}
/// <summary>
/// 初始化学生对象。
/// </summary>
public Student(int id, string name, int age, Score score, StudentLevel level)
{
_id = id;
Name = name;
Age = age;
Score = score;
Level = level;
StudentCount++;
}
/// <summary>
/// 输出学生信息。
/// </summary>
public virtual void PrintInfo()
{
Console.WriteLine("============================================");
Console.WriteLine($"学号:{Id}");
Console.WriteLine($"姓名:{Name}");
Console.WriteLine($"年龄:{Age}");
Console.WriteLine($"年级:{Level}");
Console.WriteLine($"语文:{Score.Chinese}");
Console.WriteLine($"数学:{Score.Math}");
Console.WriteLine($"英语:{Score.English}");
Console.WriteLine($"总分:{Score.Total}");
Console.WriteLine(Score.Total >= PassLine ? "结果:及格" : "结果:不及格");
}
}
/// <summary>
/// 研究生类,继承自 Student。
/// </summary>
class GraduateStudent : Student
{
public string ResearchDirection { get; set; }
/// <summary>
/// 初始化研究生对象,并调用父类构造函数。
/// </summary>
public GraduateStudent(
int id,
string name,
int age,
Score score,
StudentLevel level,
string researchDirection)
: base(id, name, age, score, level)
{
ResearchDirection = string.IsNullOrWhiteSpace(researchDirection) ? "未定方向" : researchDirection;
}
/// <summary>
/// 输出研究生信息。
/// </summary>
public override void PrintInfo()
{
base.PrintInfo();
Console.WriteLine($"研究方向:{ResearchDirection}");
}
}
/// <summary>
/// 班级管理类。
/// </summary>
class ClassRoom
{
private const int MaxStudentCount = 40;
private readonly Student[] _students = new Student[MaxStudentCount];
private int _count = 0;
/// <summary>
/// 当前班级已添加学生数量。
/// </summary>
public int Count
{
get { return _count; }
}
/// <summary>
/// 索引器:通过下标访问学生对象。
/// </summary>
public Student this[int index]
{
get
{
if (index < 0 || index >= _count)
{
throw new IndexOutOfRangeException("索引超出范围。");
}
return _students[index];
}
}
/// <summary>
/// 一次添加多个学生。
/// </summary>
public void AddStudents(params Student[] students)
{
for (int i = 0; i < students.Length; i++)
{
if (_count >= MaxStudentCount)
{
Console.WriteLine("班级已满,无法继续添加学生。");
return;
}
_students[_count] = students[i];
_count++;
}
}
/// <summary>
/// 按学号查找学生。
/// </summary>
public bool FindStudent(int id, out Student result)
{
for (int i = 0; i < _count; i++)
{
if (_students[i].Id == id)
{
result = _students[i];
return true;
}
}
result = null!;
return false;
}
/// <summary>
/// 给某门成绩加分,演示 ref 的使用。
/// </summary>
public static void AddBonus(ref int score, int bonus)
{
score += bonus;
}
}
/// <summary>
/// 程序入口。
/// </summary>
class Program
{
static void Main(string[] args)
{
Student s1 = new Student(
1,
"小周",
18,
new Score(90, 95, 85),
StudentLevel.Freshman
);
Student s2 = new Student(
2,
"小王",
19,
new Score(80, 85, 90),
StudentLevel.Sophomore
);
GraduateStudent g1 = new GraduateStudent(
3,
"小李",
24,
new Score(88, 90, 92),
StudentLevel.Senior,
"人工智能"
);
ClassRoom classRoom = new ClassRoom();
classRoom.AddStudents(s1, s2, g1);
Console.WriteLine("索引器访问第 2 个学生:");
Console.WriteLine(classRoom[1].Name);
Console.WriteLine();
Console.WriteLine("按学号查找学生:");
if (classRoom.FindStudent(2, out Student result))
{
result.PrintInfo();
}
else
{
Console.WriteLine("没找到该学生。");
}
Console.WriteLine();
Console.WriteLine("给小周的语文成绩加 10 分:");
Score updatedScore = s1.Score;
ClassRoom.AddBonus(ref updatedScore.Chinese, 10);
s1.Score = updatedScore;
Console.WriteLine($"加分后语文:{s1.Score.Chinese}");
Console.WriteLine($"加分后总分:{s1.Score.Total}");
Console.WriteLine();
Console.WriteLine($"当前已创建的学生对象总数:{Student.StudentCount}");
Console.WriteLine();
Score totalScore = s1.Score + s2.Score;
Console.WriteLine("s1 + s2 的成绩合并结果:");
Console.WriteLine($"语文合计:{totalScore.Chinese}");
Console.WriteLine($"数学合计:{totalScore.Math}");
Console.WriteLine($"英语合计:{totalScore.English}");
Console.WriteLine($"总分合计:{totalScore.Total}");
Console.WriteLine();
Console.WriteLine("普通学生信息:");
s1.PrintInfo();
Console.WriteLine();
Console.WriteLine("研究生信息:");
g1.PrintInfo();
Console.WriteLine();
Console.WriteLine("说明:");
Console.WriteLine("Score 适合用 struct,因为它只是一个小型成绩数据组合,拷贝成本低,语义上更像值。");
Console.WriteLine("Student 适合用 class,因为学生包含身份信息,通常需要被多个地方共享和持续修改。");
}
}
}
```
**批改区**
判定:优秀。
点评:这题完成度很高,已经明显超出“只要能覆盖 3 个知识点”的要求了。你实际用到了:
- 枚举
- 继承
- 静态成员
- 属性
- 索引器
- 甚至还额外用了 `struct`、运算符重载、`params``ref``out`
优点:
- 类之间职责比较清楚
- `Student` / `GraduateStudent` 的继承关系自然
- `StudentCount`、属性封装、索引器都写得比较完整
- 能把多个知识点串起来,说明你不是只会背概念,而是在尝试组织代码结构
小建议:
- 如果你想让 `ClassRoom` 的索引器更完整,也可以再补一个 `set`
- `null!`、运算符重载这些已经超出当前这套题的核心范围了,不过写出来说明你有主动扩展
这道题我给高评价,思路和完成度都很好。
@@ -0,0 +1,795 @@
# CSharp 综合练习 02
## 说明
- 本题单基于你当前已经完成的 C# 笔记整理。
- 本次重点覆盖:
- 上一套练习里的易错点回收
- `多态`
- `抽象类和抽象方法`
- `接口`
- `密封类`
- `密封函数`
- `命名空间`
- `抽象类和接口的区别`
- 你可以直接在每道题下方的 `你的作答` 区域填写答案。
- 我后续会继续在 `批改区` 直接给你批改和点评。
---
## 第一部分:错题回收
### 1. [分类题] 格式转换再辨析
请把下面每一行代码分别归类为:
- 隐式转换
- 显式强制转换
- 转成字符串
- 以上都不是
```csharp
int a = 10;
double b = a;
int c = (int)12.8;
string s = 123.ToString();
bool ok = int.TryParse("123", out int value);
```
要求:
1. 给第 2~5 行分别分类
2. 用一句话说明 `Parse` / `TryParse` 为什么常用于“字符串转数字”
**你的作答**
```
2.隐式转换
3.显式强制转换
4.转成字符串
5.以上都不是
`Parse` 本来就是专门把字符串内容解析成目标数字类型的方法,所以特别常用于“字符串转数字”
```
**批改区**
判定:正确。
点评:第 2~5 行的分类都对,`Parse / TryParse` 的用途说明也准确。你这题已经把“强转”“转字符串”“解析字符串”区分开了。
### 2. [简答题] ref 和 out 精准区分
请至少写出 `ref``out` 的 3 个区别,并额外回答下面两个判断:
1. `out` 参数在传入前能不能已经有值?
2. `TryParse` 这种写法为什么更适合用 `out` 而不是 `ref`
**你的作答**
ref和out的区别
1.ref必须在传入前赋值
2.out必须在传出前赋值
1.out传入前可以有值
2.因为out传出前必须赋值,更符合TryParse的应用情景
**批改区**
判定:部分正确。
点评:
- 你答对了两点:`ref` 传入前必须先赋值,`out` 在方法结束前必须赋值
- `out` 传入前“可以有值”,这点你也答对了
- 但题目要求至少写 3 个区别,你这里只写出了 2 个核心点
建议补上这一句:
- `ref` 更像“把已有变量传进去继续读写”
- `out` 更像“额外带回一个结果”
`TryParse` 适合 `out` 的原因你答到了主线:它除了返回“是否成功”,还要把解析结果再带出来。
### 3. [应用题] params 规则回收
根据你的笔记,回答下面问题:
1. `params` 参数有哪些规则?
2. 下面两个方法签名,哪个是合法的?为什么?
```csharp
void Test1(params int[] arr, int x)
void Test2(int x, params int[] arr)
```
3. 一个方法里能不能出现两个 `params` 参数?
**你的作答**
1.params的规则有1.只能修饰数组类型对象,2.必须放在函数的参数列表末尾,3.一个函数只能有一格变长参数
2.第二个是合法的,因为第一个没有把变长数组放在参数列表的末尾
3.不能,这样的写法是不合法的
**批改区**
判定:正确。
点评:三条规则都答对了:
- `params` 后面必须是数组
- 必须放在参数列表末尾
- 一个方法里最多只能有一个 `params`
`Test2` 合法、`Test1` 不合法,这里判断没有问题。
### 4. [代码阅读题] base 的真正作用
阅读下面代码并回答问题:
```csharp
class Animal
{
public string Name;
public Animal(string name)
{
Name = name;
Console.WriteLine("Animal 构造");
}
}
class Dog : Animal
{
public Dog(string name) : base(name)
{
Console.WriteLine("Dog 构造");
}
}
Dog d = new Dog("旺财");
Console.WriteLine(d.Name);
```
问题:
1. `base(name)` 的作用是什么?
2. 代码执行时,哪一句先输出?
3. 为什么说这里不是“继承了父类的构造函数”?
**你的作答**
1.base(name)是复用父对象的构造函数来给name对象初始化
2.先输出父对象的cw
3.没有为什么,就不是继承,而是调用父类构造函数
**批改区**
判定:部分正确。
点评:
- 你知道 `base(name)` 和父类构造函数有关,这个方向是对的
- 但更准确的说法是:`base(name)` 是“调用父类构造函数,并把 `name` 传过去”
- 这里先输出的是 `Animal 构造`
第 3 问你结论对,但解释太短了。更完整的记法是:
- 构造函数不会被继承
- 子类只是可以在自己的构造函数里用 `base(...)` 调用父类构造函数
### 5. [代码阅读题] 静态成员本质
阅读下面代码,写出输出结果,并回答问题:
```csharp
class Player
{
public static int Count;
public string Name;
public Player(string name)
{
Name = name;
Count++;
}
}
Player p1 = new Player("A");
Player p2 = new Player("B");
Console.WriteLine(Player.Count);
```
问题:
1. 最终输出什么?
2. 为什么这里更适合通过 `Player.Count` 访问,而不是让每个对象自己保存一份人数?
3. 静态成员和实例成员最核心的区别是什么?
**你的作答**
1.2
2.自己保管自己的容易出现数量不一样的问题,都通过一个对象管理更统一
3.静态成员有唯一性,实例成员可以有很多
**批改区**
判定:正确。
点评:输出 `2`,原因分析也对。这里最核心的一句可以记成:
- 静态成员属于类本身,所有对象共享
- 实例成员属于具体对象,每个对象各有一份
### 6. [改错题] 属性的 set 逻辑
下面这个属性写法有逻辑错误。请指出错误,并改成正确版本。
```csharp
public int Age
{
get
{
return age;
}
set
{
if (value < 0)
{
age = 0;
}
age = value;
}
}
```
**你的作答**
set最终会被赋值为value,if的逻辑被忽略了
改正版本就是往里加个return
**批改区**
判定:部分正确。
点评:你已经抓到错误本质了,问题确实在于最后还是执行了 `age = value;`,把前面的判断覆盖掉了。
不过这题要求“改成正确版本”,所以最好把代码完整写出来,例如:
```csharp
public int Age
{
get
{
return age;
}
set
{
if (value < 0)
{
age = 0;
return;
}
age = value;
}
}
```
### 7. [代码编写题] 索引器补全
请补全下面类中的索引器,要求外部既可以读,也可以写:
```csharp
class Team
{
private string[] members = new string[3];
// 请补全索引器
}
```
补充回答:
1. 为什么索引器名字固定写 `this`
2. 为什么它“很像属性,但访问器带参数”?
**你的作答**
```Csharp
public string this[int index]
{
get
{
return members[index];
}
set
{
memebers[index] = value;
}
}
```
1.因为规定的语法就是这么写的,别再问这么傻逼的问题了,或者说代表自己这个类
2.因为他要给别人用所以必须带参数不带参数怎么知道要访问的是哪个
**批改区**
判定:部分正确。
点评:
- 结构写对了,`get / set` 也都补上了
- 但代码里有一个拼写错误:`memebers` 应该是 `members`,不然会编译报错
补充回答里,比较稳的表述可以记成:
- 索引器名字固定写 `this`,这是语法规定,表示当前对象支持 `[]` 访问
- 它像属性,是因为也有 `get / set`
- 它又和普通属性不同,因为访问时要带索引参数,比如 `team[0]`
---
## 第二部分:核心专题
### 8. [简答题] 多态的核心句子
根据你的笔记,用自己的话解释下面几件事:
1. 什么是多态?
2. `virtual``override``base` 分别起什么作用?
3. “编译时看变量类型,运行时看实际对象” 这句话是什么意思?
**你的作答**
1.父对象在子对象中,同样的行为有不同的表现
2.virtual表示这是个虚方法,可以在子类中被重写,override表示这是个重写的方法,base表示访问父类成员
3.如果我有两个类Person和Player是继承关系,都有SayHi方法,如果我声明时用父类,装父类,用父类方法出来的是父类方法,子类也是同理,但是父类装子类时,再用SayHi就会用父类方法而不是具体的子类方法,重写能规避这个问题
**批改区**
判定:部分正确。
点评:
- 你知道多态是在“同样调用下表现不同”,方向是对的
- `virtual / override / base` 的作用也基本对
但第 3 问你那句“父类装子类时,再用 SayHi 就会用父类方法”是错的。
如果父类方法是 `virtual`,子类正确 `override` 了,那么父类引用指向子类对象时,调用的是子类重写后的版本。
这题最稳的记法是:
- 编译时看变量类型:决定你“能调用哪些成员”
- 运行时看实际对象:决定你“真正执行哪个重写版本”
### 9. [代码阅读题] override 和 new 的区别
阅读下面两段代码,分别写出输出结果,并说明为什么不同。
第一段:
```csharp
class Father
{
public virtual void Say()
{
Console.WriteLine("Father");
}
}
class Son : Father
{
public override void Say()
{
Console.WriteLine("Son");
}
}
Father f = new Son();
f.Say();
```
第二段:
```csharp
class Father
{
public void Say()
{
Console.WriteLine("Father");
}
}
class Son : Father
{
public new void Say()
{
Console.WriteLine("Son");
}
}
Father f = new Son();
f.Say();
```
**你的作答**
1.Son因为重写了
2.Father因为没重写,且容器是父类所以还是在用父类方法
**批改区**
判定:正确。
点评:第一段输出 `Son`,第二段输出 `Father`,原因你也说到了:
- `override` 是真正的多态重写
- `new` 只是隐藏父类同名成员,不是多态重写
### 10. [判断题] 密封类和密封函数
请判断下面说法是对还是错,如果错了请改正:
1. `sealed class` 表示这个类不能再被继承
2. `sealed override` 会让当前类也不能再被继承
3. 普通方法可以直接写成 `sealed void Test()`
4. 密封类不能创建对象
5. 密封类和抽象类不能同时使用
**你的作答**
1.T
2.F,只是方法不能被重写了,不会不让类被继续继承
3.F,都没重写,不能直接加sealed
4.F,可以创建对象,只是不能再被继承了
5.T
**批改区**
判定:正确。
点评:五个判断都对。你已经把:
- `sealed class`
- `sealed override`
- 普通方法不能直接 `sealed`
- 密封类仍可创建对象
- `sealed``abstract` 不能同时用
这几个点区分清楚了。
### 11. [代码阅读题] sealed override
阅读下面代码,判断哪些地方合法,哪些地方不合法,并说明原因。
```csharp
class Animal
{
public virtual void Speak()
{
Console.WriteLine("动物叫");
}
}
class Dog : Animal
{
public sealed override void Speak()
{
Console.WriteLine("狗叫");
}
}
class Husky : Dog
{
public override void Speak()
{
Console.WriteLine("哈士奇叫");
}
}
```
补充回答:
1. `sealed override``sealed class` 的区别是什么?
2. `Dog` 类本身还能不能被继承?
**你的作答**
Husky类中父类已经把Speak Sealed了,不能继续重写
1.前者只是保证该方法不会被继续重写,后者是保证整个类不被继续继承
2.可以,类本身没被Sealed
**批改区**
判定:正确。
点评:你抓住了关键点:`Husky` 里那段 `override Speak()` 不合法,因为 `Dog.Speak()` 已经被 `sealed override` 封住了。
补充两问你也答对了:
- `sealed override` 只封成员
- `sealed class` 封整个类
- `Dog` 本身还能继续被继承
### 12. [简答题] 抽象类和抽象方法
根据你的笔记,回答下面问题:
1. 抽象类为什么不能直接 `new`
2. 抽象方法和普通方法最核心的区别是什么?
3. 抽象类里能不能有构造函数、普通方法、字段、属性?
4. 普通子类继承抽象类后,必须做什么?
**你的作答**
1.因为规则就是这样的,abstract类可以被理解为模板,是抽象的,不是具体的
2.普通类是具体的可以直接实例化对象,抽象类必须先通过重写才能有实例
3.可以有,只要子类去实现具体实例就可以了
4.必须重写抽象类中声明的构造函数、方法、字段等
**批改区**
判定:部分正确。
点评:
- 第 3 问答对了,抽象类里可以有构造函数、普通方法、字段、属性
- 第 1 问你的理解接近了,但更准确地说:抽象类是不完整类型,可能还包含没实现的抽象成员,所以不能直接 `new`
- 第 2 问答偏了。题目问的是“抽象方法和普通方法”,不是“抽象类和普通类”
- 第 4 问答错了。普通子类必须实现的是“所有抽象成员”,不是“重写构造函数、字段等”
建议你把这题压缩记成:
- 抽象方法没有方法体
- 普通方法有方法体
- 普通子类继承抽象类后,必须 `override` 所有抽象成员
### 13. [代码编写题] 抽象类
请根据你的笔记,补全下面代码:
要求:
1. `Animal` 是抽象类
2. `Speak()` 是抽象方法
3. `Dog` 继承 `Animal`
4. `Dog` 正确实现 `Speak()`
```csharp
// 请补全
```
**你的作答**
```Csharp
abstract class Animal
{
public abstract void Speak();
}
class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("Woof!");
}
}
```
**批改区**
判定:正确。
点评:代码满足题目要求:
- `Animal` 是抽象类
- `Speak()` 是抽象方法
- `Dog` 继承 `Animal`
- `Dog` 正确 `override``Speak()`
这题写得干净,没问题。
### 14. [简答题] 接口
根据你的笔记,回答下面问题:
1. 接口主要表达的是什么?
2. 为什么接口更适合表示“能力”而不是“身份”?
3. 一个类能继承几个类?能实现几个接口?
4. 为什么实现接口成员时通常要写 `public`
**你的作答**
1.表达某个可以实现的功能,或者如你2中所说的能力
2.身份通常是唯一的,能力可以是多重的,如果把“能唱歌”当成身份(`Singer`作为抽象类),那么某个学生毕业成为程序员后可能不再是`Singer`,但实际他依然能唱歌。用接口则灵活保持这个能力。
3.一个类只能继承一个类,而接口没有数量限制
4.写成private外部无法调用,且违背原则编译错误
**批改区**
判定:正确。
点评:这题答得比较完整。
- 你对“接口表达能力而不是身份”的理解是到位的
- 第 3 问也对:类只能继承一个父类,但可以实现多个接口
- 第 4 问也对:实现接口成员时访问级别不能比接口契约更低
### 15. [代码编写题] 接口实现
请完成下面代码,让 `Bird``Plane` 都能通过同一个方法被调用:
要求:
1. 定义一个 `IFlyable` 接口
2. `Bird``Plane` 都实现它
3. 写一个 `LetItFly(IFlyable flyable)` 方法
4. 在方法里调用飞行行为
```csharp
// 请补全
```
```Csharp
using System.Security.Cryptography.X509Certificates;
interface IFlyable
{
public void LetItFly()
{
Console.WriteLine("Flying...");
}
}
public class Bird : IFlyable
{
public Bird()
{
}
public void LetItFly()
{
Console.WriteLine("Bird Flying... ");
}
}
public class Plane : IFlyable
{
public Plane()
{
}
public void LetItFly()
{
Console.WriteLine("Plane Flying... ");
}
}
class Program
{
public static void LetItFly(IFlyable flyable)
{
flyable.LetItFly();
}
static void Main(string[] args)
{
IFlyable f1 = new Bird();
IFlyable f2 = new Plane();
LetItFly(f1);
LetItFly(f2);
}
}
```
**批改区**
判定:部分正确。
点评:
- 你的代码确实达成了“Bird 和 Plane 能通过同一个方法统一调用”的目标
- `LetItFly(IFlyable flyable)` 这部分也写出来了
但按这套笔记的学习范围,更标准的写法应该是:
- 接口里只声明方法,不写方法体
- 接口方法名通常写成 `Fly()`,外部统一调用方法再写成 `LetItFly(IFlyable flyable)`
也就是说,更推荐的结构是:
```csharp
interface IFlyable
{
void Fly();
}
```
然后在 `Bird` / `Plane` 里实现 `Fly()`
你这题功能上基本成立,但接口的“契约”味道还可以再更清晰一点。
### 16. [代码阅读题] 接口多实现
阅读下面代码,并回答问题:
```csharp
interface IMovable
{
void Move();
}
interface IAttackable
{
void Attack();
}
class Player : IMovable, IAttackable
{
public void Move()
{
Console.WriteLine("玩家移动");
}
public void Attack()
{
Console.WriteLine("玩家攻击");
}
}
```
问题:
1. 这段代码说明了接口的什么特点?
2. 如果 `Player` 只实现了 `Move()`,没实现 `Attack()`,会怎么样?
3. 为什么说接口可以弥补“类只能单继承”的限制?
**你的作答**
1.面向能力编程
2.报错,继承了必须实现
3.因为接口让你给一个类“附加”多种不相关的行为能力,而这些行为无法(或不适合)放进单条继承链里。
**批改区**
判定:正确。
点评:三问都答对了。
- 这段代码体现了“一个类可以实现多个接口”
- 少实现一个接口成员会直接报错
- 接口可以给一个类补充多种能力,这是它对单继承限制的重要补充
### 17. [简答题] 命名空间
根据你的笔记,回答下面问题:
1. `namespace` 的作用是什么?
2. `using` 的作用是什么?
3. 如果 `Game``Tool` 两个命名空间里都有 `Player`,该怎么区分使用?
4. 命名空间和访问修饰符是不是一回事?为什么?
**你的作答**
1.将程序划分为不同的区域,防止命名冲突
2.using的作用就和include一样,引用命名空间
3.Game.Player和Tool.Player
4.命名空间用于组织代码逻辑分组(解决命名冲突),访问修饰符用于控制成员的可访问性(封装);前者是“在哪”,后者是“谁能用”。
**批改区**
判定:正确。
点评:这题比较稳。
- `namespace` 负责组织代码、避免重名
- `using` 负责引入命名空间,简化书写
- 同名类型可以用完整限定名区分
- 命名空间和访问修饰符不是一回事
你最后那句“前者是在哪,后者是谁能用”总结得很好。
### 18. [对比题] 抽象类和接口的区别
请根据你的笔记,从下面几个角度对比抽象类和接口:
1. 它们分别更适合表达什么?
2. 在“共同代码复用”方面,谁更适合?
3. 在“一个类拥有多种能力”方面,谁更适合?
4. 什么情况下更适合用抽象类,什么情况下更适合用接口?
**你的作答**
1.一个是用来表达某个东西的通用模板,一个用来表达一个通用的方法
2.抽象类
3.接口
4.打个比方:鸟和飞机都能飞,但他们不属于一类东西,飞这个功能是都有的,所以用接口。人和猩猩都是猿,且都有猿类独有的特征如直立行走等所以可以用猿类抽象类
**批改区**
判定:基本正确。
点评:
- 第 2、3、4 问都答到了重点
- 第 1 问再严谨一点可以写成:
- 抽象类更适合表达“是什么”
- 接口更适合表达“能做什么 / 具备什么能力”
你举的“鸟和飞机都会飞”这个例子是合适的,已经能说明接口为什么更适合表达能力。
### 19. [综合设计题] 角色系统小练习
请你自己设计一个简单的角色系统,要求至少用到下面 6 个知识点中的 4 个:
- 继承
- 多态
- 抽象类
- 接口
- 静态成员
- 属性
- 索引器
- 密封函数
- 命名空间
要求:
1. 写出核心代码
2. 标出你分别用到了哪些知识点
3. 简单说明为什么这里用抽象类,为什么这里用接口
4. 如果你愿意,也可以顺手加上一个你自己的小扩展
**你的作答**
(待填写)
**批改区**
判定:良好。
点评:我按你提供的 `D:\Visual Studio\Program\ReStudy CSharp\Restudy\Exercise3` 代码一起批改了。
这套代码里你实际用到了很多要求内的知识点:
- 继承:`Player : Character``Enemy : Character``Car : Vehicle`
- 多态:`Character[]` 中装 `Player``Enemy`,统一调用 `Move()` / `Attack()`
- 抽象类:`Character``Vehicle`
- 接口:`IDrivable`
- 静态成员:`playerCount``enemyCount`
- 属性:`maxPassenger``passengers``location`
- 索引器:`Vehicle.this[int index]`
- 密封函数:`Car` 中的 `sealed override Drive(...)`
- 命名空间:`Program``Character``Vehicle``Vector`
优点:
- 结构已经有“小项目”味道了,不只是单题代码
- 你能把抽象类、多态、接口、静态成员、索引器串起来用,这点很好
- `Character[]` 统一调度角色行为,这个设计是对路的
还可以继续改进的点:
- 你在题目里没有把“为什么这里用抽象类、为什么这里用接口”写成文字说明,这一部分算缺失
- `IAttackable``IMovable` 文件目前没有真正接入 `Character / Player / Enemy` 体系,像是还没用上的草稿接口
- `IDrivable.Drive(Player player)` 这个接口写得稍微有点窄,如果以后不是只有 `Player` 能开车,这个接口会不够通用
综合看,这题已经明显达到要求,而且超出了“只用 4 个知识点”的下限。解释部分如果补得更完整,这题会更强。