C# 语言练习题(15 题)
学了语法得动手写才算真的会。这一页给 15 道精心挑选的练习题,按难度分三级——基础 6 题(变量 / 运算 / 控制流 / 方法)、进阶 5 题(LINQ / 异常处理)、实战 4 题(综合应用)。每题都含题目描述、提示、可折叠的参考答案,答案都是完整的、本地 dotnet run 一跑就能看到输出的程序。
使用建议:先别急着展开答案。读题后自己用本地 .NET 项目(或 dotnetfiddle 在线 C#)试着写,卡住了再看提示,实在写不出再对照参考答案。C# 的 LINQ 是它的杀手锏——能把 20 行的循环代码压成一行,做完进阶组你会深有体会。
基础(6 题):变量 / 运算 / 控制流 / 方法
这一组覆盖最基础的语言元素,目标是训练"现代 C#"的肌肉记忆——用 var 类型推导、string.Join 拼接、foreach 遍历,而不是 C 风格的 for 下标循环。
第 1 题:翻转字符串
题目:写一个翻转字符串的函数。比如 "hello" → "olleh"。要求给出两种写法:LINQ 版本与手写版本。
提示:LINQ 版 new string(s.Reverse().ToArray()) 一行搞定(需要 using System.Linq;);手写版把字符串转成 char[],用元组解包交换首尾 (arr[i], arr[j]) = (arr[j], arr[i])——C# 7+ 的元组交换,无需临时变量。
参考答案
using System;
using System.Linq;
// 方法 1:LINQ Reverse + ToArray(最简洁)
string Reverse1(string s) => new string(s.Reverse().ToArray());
// 方法 2:char 数组下标交换(理解原理)
string Reverse2(string s) {
char[] arr = s.ToCharArray();
for (int i = 0; i < arr.Length / 2; i++)
(arr[i], arr[arr.Length - 1 - i]) = (arr[arr.Length - 1 - i], arr[i]);
return new string(arr);
}
Console.WriteLine(Reverse1("hello")); // olleh
Console.WriteLine(Reverse2("你好世界")); // 界世好你第 2 题:判断素数
题目:写一个函数 bool IsPrime(int n),判断正整数 n 是否为素数。
提示:小于 2 的都不是素数;只需检查从 2 到 sqrt(n) 的整除性——超过平方根的因子必然配对一个更小的、已经检查过的因子。用 (int)Math.Sqrt(n) 取平方根;返回类型用 bool(C# 原生支持)。
参考答案
using System;
bool IsPrime(int n) {
if (n < 2) return false;
for (int i = 2; i <= (int)Math.Sqrt(n); i++)
if (n % i == 0) return false;
return true;
}
Console.WriteLine(IsPrime(7)); // True
Console.WriteLine(IsPrime(10)); // False
Console.WriteLine(IsPrime(1)); // False
Console.WriteLine(IsPrime(2)); // True第 3 题:FizzBuzz
题目:用 Console.WriteLine 打印 1 到 15。3 的倍数打印 Fizz,5 的倍数打印 Buzz,同时是 3 和 5 的倍数打印 FizzBuzz,其余打印数字本身。
提示:if / else if / else 的判断顺序很关键——必须先判"同时是 3 和 5 的倍数"(即 15 的倍数),否则会被前面的分支截胡。
参考答案
using System;
for (int i = 1; i <= 15; i++) {
if (i % 15 == 0) // 必须先判 15
Console.WriteLine("FizzBuzz");
else if (i % 3 == 0)
Console.WriteLine("Fizz");
else if (i % 5 == 0)
Console.WriteLine("Buzz");
else
Console.WriteLine(i);
}
// 输出:
// 1
// 2
// Fizz
// 4
// Buzz
// Fizz
// 7
// 8
// Fizz
// Buzz
// 11
// Fizz
// 13
// 14
// FizzBuzz第 4 题:数组最大值(不用 LINQ,遍历)
题目:不使用 LINQ 的 .Max(),写一个方法 int FindMax(int[] arr),返回数组中的最大值。
提示:这是"打擂台"算法——变量初始化为数组首元素,从下标 1 开始遍历,遇到更大的就更新。完成后可以对比一下 arr.Max()(LINQ 版)有多简洁。
参考答案
using System;
int FindMax(int[] arr) {
int m = arr[0];
for (int i = 1; i < arr.Length; i++)
if (arr[i] > m) m = arr[i];
return m;
}
int[] a = { 3, 1, 4, 1, 5, 9, 2, 6 };
Console.WriteLine(FindMax(a)); // 9
// 提示:实际项目可用 a.Max()(System.Linq)第 5 题:数组求和
题目:写一个方法 int SumOf(int[] arr),返回数组所有元素之和。
提示:用 foreach (int x in arr) 累加;实际项目里 LINQ 的 arr.Sum() 一行搞定,但理解原理仍重要——LINQ 底层就是这么实现的。
参考答案
using System;
int SumOf(int[] arr) {
int total = 0;
foreach (int x in arr) total += x;
return total;
}
int[] a = { 1, 2, 3, 4, 5 };
Console.WriteLine(SumOf(a)); // 15
// 提示:实际项目可用 a.Sum()(System.Linq)第 6 题:计算阶乘
题目:写方法 long Factorial(int n),返回 n!。要求 Factorial(0) == 1。同时用循环和递归两种写法。
提示:int 装不下 13!,所以返回类型用 long;递归版可用表达式体方法 long FactorialRec(int n) => n <= 1 ? 1 : n * FactorialRec(n - 1);——C# 的方法定义比 Java 简洁得多。
参考答案
using System;
// 方法 1:循环
long Factorial(int n) {
long r = 1;
for (int i = 2; i <= n; i++) r *= i;
return r;
}
// 方法 2:递归(用三元表达式,更紧凑)
long FactorialRec(int n) => n <= 1 ? 1 : n * FactorialRec(n - 1);
Console.WriteLine(Factorial(5)); // 120
Console.WriteLine(Factorial(0)); // 1
Console.WriteLine(FactorialRec(6)); // 720进阶(5 题):LINQ / 异常处理
这一组是 C# 区别于 Java/C++ 的最大杀手锏——LINQ(Language Integrated Query)。同一套 Where / Select / GroupBy / OrderBy 算子既能查对象集合,也能查数据库(Entity Framework)、XML。学完进阶组你会感觉别的语言都"啰嗦"。
第 7 题:LINQ Where 筛选偶数
题目:给定 int[] nums,用 LINQ 筛出所有偶数并打印。
提示:加上 using System.Linq; 即可启用 LINQ 扩展方法;nums.Where(n => n % 2 == 0) 返回 IEnumerable<int>(延迟求值);string.Join(", ", evens) 把集合拼成字符串。
参考答案
using System;
using System.Linq;
int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8 };
// Where 是"过滤":保留满足条件的元素,返回 IEnumerable<int>
var evens = nums.Where(n => n % 2 == 0);
Console.WriteLine(string.Join(", ", evens));
// 输出: 2, 4, 6, 8
// 提示:lambda 表达式 n => n % 2 == 0 读作"n 真则 n 除以 2 余 0"。第 8 题:LINQ Select 投影
题目:给定一个匿名类型的学生数组(Name、Score),用 LINQ 取出所有学生的 Name 字段。
提示:Select(s => s.Name) 把 Student 投影成 string——相当于 SQL 的 SELECT name FROM students;匿名类型 new { Name = "...", Score = 90 } 让你不用先定义类。
参考答案
using System;
using System.Linq;
var students = new[] {
new { Name = "小明", Score = 90 },
new { Name = "小红", Score = 85 },
new { Name = "小刚", Score = 78 }
};
// Select 是"映射":每个 Student 投影成它的 Name 字段
var names = students.Select(s => s.Name);
Console.WriteLine(string.Join(", ", names));
// 输出: 小明, 小红, 小刚第 9 题:LINQ GroupBy 分组
题目:给定一个员工列表(姓名 + 部门),按部门分组并打印每个部门的人员名单。
提示:GroupBy(u => u.Dept) 返回 IEnumerable<IGrouping<TKey, T>>;每个分组有 .Key(分组键)和它自己(可迭代);插值字符串 $"...{g.Key}: ..." 让格式化比 string.Format 直观。
参考答案
using System;
using System.Linq;
var users = new[] {
new { Name = "小明", Dept = "技术部" },
new { Name = "小红", Dept = "市场部" },
new { Name = "小刚", Dept = "技术部" }
};
// GroupBy 返回 IEnumerable<IGrouping<TKey, T>>,每组有 Key 和组内元素
var groups = users.GroupBy(u => u.Dept);
foreach (var g in groups)
Console.WriteLine($"{g.Key}: {string.Join(", ", g.Select(u => u.Name))}");
// 输出:
// 技术部: 小明, 小刚
// 市场部: 小红第 10 题:LINQ OrderBy + Average 聚合
题目:给定一组分数,用 LINQ 升序排序并打印,再用 .Average() 算平均分。
提示:OrderBy(s => s) 升序、OrderByDescending 降序;.Average() 直接返回平均值(double);插值 $"平均: {avg:F1}" 中的 :F1 表示保留 1 位小数。其他聚合算子还有 Sum / Min / Max / Count。
参考答案
using System;
using System.Linq;
int[] scores = { 78, 95, 62, 88, 70 };
var sorted = scores.OrderBy(s => s); // 升序(Descending 倒序)
double avg = scores.Average(); // Average 直接算平均值
Console.WriteLine($"升序: {string.Join(", ", sorted)}");
Console.WriteLine($"平均: {avg:F1}");
// 输出:
// 升序: 62, 70, 78, 88, 95
// 平均: 78.6
// 提示:其他聚合算子 — Sum / Min / Max / Count。第 11 题:try/catch 处理除零
题目:写一个 Divide(int a, int b) 方法,当 b == 0 时捕获 DivideByZeroException 并打印错误,而不是让程序崩溃。
提示:C# 中整数除以 0 会抛 DivideByZeroException(浮点除零则返回 Infinity);try { ... } catch (DivideByZeroException ex) { ... };ex.Message 是系统给的错误描述。
参考答案
using System;
Console.WriteLine(Divide(10, 2)); // 5
Console.WriteLine(Divide(10, 0)); // 捕获异常,打印错误并返回 0
int Divide(int a, int b) {
try {
return a / b; // 整数除法,b==0 时抛 DivideByZeroException
} catch (DivideByZeroException ex) {
Console.WriteLine($"错误: {ex.Message}");
return 0;
}
}实战(4 题):综合应用
这一组每题都会综合用到多个知识点——面向对象、LINQ、JSON、枚举与模式匹配——做出一个接近真实场景的小程序。建议每题都完整跑一遍,再想想怎么扩展。
第 12 题:学生管理(List<Student> + LINQ 排序/分组)
题目:定义 record Student(姓名 + 数学 + 语文 + 算均分的方法),用 List<Student> 存储;用 LINQ 按均分降序排名,再按"数学是否≥85"分组。
提示:用 record(C# 9+)替代 class,一行定义主构造 record Student(string Name, int Math, int Chinese);OrderByDescending(s => s.Average()) 排序;GroupBy(s => s.Math >= 85) 分组——key 是 bool。
参考答案
using System;
using System.Collections.Generic;
using System.Linq;
var students = new List<Student> {
new("小明", 90, 80),
new("小红", 95, 88),
new("小刚", 70, 100)
};
// 1. 按均分降序排序
Console.WriteLine("按均分排名:");
foreach (var s in students.OrderByDescending(s => s.Average()))
Console.WriteLine($" {s.Name} 均分 {s.Average():F1}");
// 2. 按数学是否>=85分组
Console.WriteLine("数学>=85分组:");
foreach (var g in students.GroupBy(s => s.Math >= 85))
Console.WriteLine($" {g.Key}: {string.Join(",", g.Select(s => s.Name))}");
record Student(string Name, int Math, int Chinese) {
public double Average() => (Math + Chinese) / 2.0;
}
// 输出:
// 按均分排名:
// 小红 均分 91.5
// 小明 均分 85.0
// 小刚 均分 85.0
// 数学>=85分组:
// True: 小明,小红
// False: 小刚第 13 题:词频统计(LINQ GroupBy + OrderByDescending)
题目:统计一段英文文本中每个单词的出现次数,输出频次最高的 Top 3(频次相同按字典序)。
提示:用 LINQ 链式调用——Split(' ') 分词 → GroupBy(w => w) 分组 → Select(g => (g.Key, g.Count())) 投影 → OrderByDescending(x => x.Count) 主排序 → ThenBy(x => x.Word) 次排序 → Take(3) 取前 N。这是 LINQ 的招牌写法。
参考答案
using System;
using System.Linq;
string text = "the quick brown fox the lazy dog the fox brown dog dog";
var top = text.Split(' ')
.GroupBy(w => w) // 按单词分组
.Select(g => (Word: g.Key, Count: g.Count())) // 投影成 (单词, 次数)
.OrderByDescending(x => x.Count) // 次数降序
.ThenBy(x => x.Word) // 次数相同按字典序
.Take(3); // 只取前 3
foreach (var item in top)
Console.WriteLine($" {item.Word}: {item.Count}");
// 输出:
// dog: 3
// the: 3
// brown: 2
// 提示:LINQ 链式调用把"过滤/映射/分组/排序/取前 N"压缩成 5 行,这是 C# 的招牌。第 14 题:JSON 文件读写(System.Text.Json 序列化)
题目:定义一个 Person 类,用 System.Text.Json 把对象序列化成 JSON 写到文件,再从文件反序列化回来打印。
提示:JsonSerializer.Serialize(obj, opt) 序列化,Deserialize<T>(json) 反序列化;JsonSerializerOptions { WriteIndented = true } 让输出带缩进易读;类的属性必须同时有 get/set,否则反序列化无法写入。
参考答案
using System;
using System.IO;
using System.Text.Json;
var p = new Person { Name = "小明", Age = 28 };
// 序列化(WriteIndented = true 让输出带缩进,易读)
var opt = new JsonSerializerOptions { WriteIndented = true };
string json = JsonSerializer.Serialize(p, opt);
File.WriteAllText("person.json", json);
// 反序列化(string -> 对象)
Person? p2 = JsonSerializer.Deserialize<Person>(json);
Console.WriteLine($"反序列化: {p2?.Name}, {p2?.Age} 岁");
class Person {
public string Name { get; set; } = ""; // set 必须有,反序列化才能写入
public int Age { get; set; }
}
// 输出(屏幕):
// 反序列化: 小明, 28 岁
// 文件 person.json:
// {
// "Name": "小明",
// "Age": 28
// }第 15 题:简易计算器(枚举 Op + switch 表达式)
题目:用 枚举 Op + switch 表达式实现一个简易计算器,支持 + - * /;除数为 0 返回 NaN;未知运算抛异常。
提示:C# 8+ 的 switch 表达式用 => 分支和 _(丢弃模式,等同 default);比传统 switch 语句更紧凑、有返回值;枚举让调用方不能传非法字符串——编译期就帮你查错。
参考答案
using System;
enum Op { Add, Sub, Mul, Div }
// C# 8+ switch 表达式:模式匹配,一行写完所有分支
double Calc(double a, Op op, double b) => op switch {
Op.Add => a + b,
Op.Sub => a - b,
Op.Mul => a * b,
Op.Div => b == 0 ? double.NaN : a / b,
_ => throw new ArgumentException("未知运算")
};
Console.WriteLine(Calc(10, Op.Add, 3)); // 13
Console.WriteLine(Calc(10, Op.Sub, 3)); // 7
Console.WriteLine(Calc(10, Op.Mul, 3)); // 30
Console.WriteLine(Calc(10, Op.Div, 4)); // 2.5
Console.WriteLine(Calc(10, Op.Div, 0)); // NaN
// 提示:_ 是丢弃模式(类似 default);switch 表达式必须穷尽所有可能或加 _。练习建议
不要只看答案。把每道题在本地或在线环境亲手敲一遍,改一改输入试试边界情况(空数组、负数、超长字符串、null)。C# 是强类型语言——编译器是你的朋友,多数错误在编译阶段就被揪出来了,看懂红色波浪线提示是 C# 程序员的硬通货。
试着扩展:做完第 12 题学生管理,能不能改成"按单科排名并打印 Top N"?做完第 13 题词频统计,能不能从真实文本文件读?(用 File.ReadAllText)做完第 15 题计算器,能不能解析字符串表达式(比如 "1+2*3")?每个扩展都是一次小型的真实开发。
更多练习资源:推荐 LeetCode 用 C# 刷算法(LINQ 在面试里是亮点);官方文档 Microsoft Learn 的 C# 教程有完整的中文指南和交互式练习。
← 上一篇 C# 实战项目
返回 C# 教程目录