1 / 11
函数与逻辑
自在学
分类课程AI导师创意工坊价格
分类课程AI导师创意工坊价格
编程C#C# 入门

C# 入门

本部分将系统性地介绍 C# 编程语言,帮助读者从零基础逐步掌握其基本用法与核心概念。课程内容涵盖开发环境的搭建、首个项目的创建、基础语法的讲解及典型用例示范。 无论有无编程经验,你都可以通过循序渐进的学习,理解每个关键点,真正掌握 C# 项目开发的流程与方法。

C#语言


C# 是什么,它能做什么

先说几个关键词:现代、强类型、面向对象、跨平台。C# 由微软设计,和 .NET 平台一起工作。今天的 C# 可以在 Windows、macOS、Linux 上运行; 可以写控制台程序、桌面软件、Web 后端、小游戏,甚至移动应用(Xamarin/Maui)。

你会经常看到两个名字:C# 和 .NET。简单理解:C# 是语言,就像“中文”; .NET 是运行时和标准库,就像“字典+工具箱”。我们用 C# 说话,用 .NET 提供的工具去做事。


安装与准备

要写 C#,我们需要两个东西:

  1. .NET SDK:让我们能够创建、编译、运行 C# 程序。
  2. 一个编辑器:VS Code 或 Visual Studio 都可以。我们推荐 VS Code + C# Dev Kit 扩展,轻量又好用。

安装完成后,打开终端(Windows 上可以用 PowerShell),输入:

|
dotnet --version

如果能看到一个版本号,比如 8.x 或 7.x,说明 .NET 安装成功。

接着,我们来创建第一个控制台项目。选择一个空文件夹,在终端执行:

|
dotnet new console -n HelloCSharp cd HelloCSharp dotnet run

你应该会看到屏幕上打印出:

|
Hello, World!

我们已经把“第一个音”发出来了。下面,我们不急着往下跑,先把这个小小的“Hello, World!”拆开看看。

你的第一个 C# 程序

创建出来的项目里,有一个文件 Program.cs。它的内容大概是这样(不同版本模板略有差别):

|
// Program.cs using System; // 引入 System 命名空间(提供控制台、基础类型等常用功能) // 这是主程序入口点。对顶级语句模板而言,编译器会为我们生成 Main 方法。 Console.WriteLine("Hello, World!"); // 输出一行文本到控制台

如果你看到的是带 namespace 和 class Program 的版本,也没关系,原理一样。新模板为了简洁,省略了那些“礼仪性的开场白”,让我们可以专注在核心逻辑上。 我们后面会讲“命名空间”“类”和“Main 方法”到底是什么。先记住两点:

第一,Console.WriteLine(...) 是输出一行文字;第二,代码一行行从上往下执行,遇到输出就把内容打印到屏幕上。


程序结构

很多书一上来就把“程序结构”讲得很复杂,我们只抓关键:

  • using:像“把工具箱搬进来”。using System; 就是在说:我要用 System 里的东西。
  • 命名空间(namespace):像“文件夹”。把相关代码放到同一个“命名空间”里,避免重名冲突,也让结构更清晰。
  • 类(class):像“蓝图”。C# 是面向对象语言,类是组织代码的一种重要方式,我们会在后续深入。
  • Main 方法:程序从哪里开始跑?答案是 Main。顶级语句模板把它藏起来了,但它仍然存在。

为了让你看到“完整版”,我们写一个带 Main 的最小示例:

|
// Program.cs(显式 Main 版本) using System; // 引入基础工具箱 namespace GettingStarted // 命名空间,像文件夹名 { internal class Program // 定义一个类 Program,internal 先不用纠结 { // Main 是程序入口。string[] args 是命令行参数(暂时不用) static void Main(string[] args) { Console.WriteLine("你好,C# 的世界!"); } } }

逐行理解:我们先搬进 System 工具箱,然后声明一个命名空间 GettingStarted,里面放一个 Program 类。类里有一个 Main 方法,程序从这里开始,执行输出语句,结束。


变量与数据类型

程序和现实一样,需要“记住东西”。变量就是“带名字的盒子”,数据类型规定了盒子里能放什么:整数、浮点数、文本、真假等等。

常见基础类型:

  • 整数:int(32位整数)、long(更大)
  • 浮点数:double(常用浮点)、float(更小精度)、decimal(高精度常用于金额)
  • 文本:string(字符串)、char(单个字符)
  • 真假:bool(true 或 false)

我们写一个小程序:记录今天学习的时长,然后打印出来。

|
// 变量示例:study-time.cs using System; class StudyTimeDemo { static void Main() { // 声明一个整型变量,表示今天学习了多少小时 int hours = 2; // 我们先赋值 2,后面可以改 // 声明一个浮点型变量,记录分钟(小数) double minutes = 30.5; // 30.5 分钟 // 声明一个字符串变量,记录学习主题 string topic = "C# 入门"; // 声明一个布尔变量,记录今天是否达成目标 bool goalReached = true; // 输出这些信息 Console.WriteLine("今天学习主题: " + topic); Console.WriteLine("学习时长(小时): " + hours); Console.WriteLine("学习时长(分钟): " + minutes); Console.WriteLine("是否达成目标: " + goalReached); } }

运行后你会看到相应的输出。注意 + 在拼接字符串时的作用:把多个片段连成一段文本。也可以用插值字符串的写法,更优雅:

|
Console.WriteLine($"今天学习主题: {topic}");

插值字符串里,花括号 {} 就像“窗口”,可以把变量的值展示出来。

命名规范与可读性

变量名请用有意义的英文或拼音英文化,推荐使用小驼峰:studyHours、targetReached。我们要写给“未来的自己”和“团队伙伴”看,越清晰越好。

类型转换:别把方块塞进圆孔

有时候需要把一种类型变成另一种,比如把字符串变成整数,或者让整数参与小数计算。最常见的是这几种方式:

|
int a = 5; double b = a; // 隐式转换:int 可以自动转 double double c = 3.14; int d = (int)c; // 显式转换(强制类型转换):小数到整数需要手动,可能丢失小数部分 string s = "123"; int num = int.Parse(s); // 解析字符串为整数(若 s 非法会抛异常) // 更安全:TryParse,不抛异常,用返回值表示是否成功 bool ok = int.TryParse(s, out int value);

TryParse 像是“温柔型转换器”,失败也不生气,返回 false,并把输出变量设为默认值。


输入与输出:和用户对话

Console.WriteLine 是“我们说话”;Console.ReadLine 是“我们倾听”。我们做一个小例子:计算 BMI(体质指数),并且把每一步解释清楚。

|
// bmi.cs using System; class BmiCalculator { static void Main() { // 提示并读取身高(米) Console.Write("请输入你的身高(米): "); string? heightInput = Console.ReadLine(); // 可能为 null,用 ? 表示可空 // 提示并读取体重(千克) Console.Write("请输入你的体重(kg): "); string? weightInput = Console.ReadLine(); // 尝试把输入解析为 double bool hOk = double.TryParse(heightInput, out double height); bool wOk = double.TryParse(weightInput, out double weight); if (!hOk || !wOk || height <= 0 || weight <= 0) { Console.WriteLine("输入不合法,请输入正数,例如 身高: 1.72,体重: 65.5"); return; // 早退出,避免继续计算 } // 计算 BMI = 体重 / 身高^2 double bmi = weight / (height * height); Console.WriteLine($"你的 BMI: {bmi:F2}"); // :F2 保留两位小数 // 简单分类 if (bmi < 18.5) { Console.WriteLine("偏瘦,注意营养均衡和力量训练"); } else if (bmi < 24) { Console.WriteLine("正常,保持良好作息和运动"); } else if (bmi < 28) { Console.WriteLine("偏重,建议控制饮食+有氧运动"); } else { Console.WriteLine("偏高,建议咨询专业人士制定计划"); } } }

这个程序用到了输入、解析、条件判断、格式化输出。我们也见识了“早返回”的写法:当输入不对,直接 return,让代码更干净。


运算符与表达式:算数、比较、逻辑

C# 提供了丰富的运算符来处理数据和逻辑:

算术运算符:

  • +(加法)、-(减法)、*(乘法)、/(除法)、%(取余)
  • 注意:整数除法会截断小数部分,如 5 / 2 结果是 2,而 5.0 / 2 结果是 2.5
  • 取余运算常用于判断奇偶:n % 2 == 0 表示偶数

比较运算符:

  • ==(等于)、!=(不等于)
  • <(小于)、<=(小于等于)、>(大于)、>=(大于等于)
  • 返回 bool 类型的结果

逻辑运算符:

  • &&(逻辑与,短路求值)、||(逻辑或,短路求值)、!(逻辑非)
  • 短路求值指:a && b 当 a 为 false 时不会计算 b;a || b 当 a 为 true 时不会计算 b

运算符优先级:算术 > 比较 > 逻辑,但复杂表达式建议用括号明确意图,让代码更易读。比如 (age >= 18) && (score > 60) 比 age >= 18 && score > 60 更清晰。

我们写一个“目标检查器”:

|
// goal-checker.cs using System; class GoalChecker { static void Main() { int dailyTarget = 60; // 每天目标学习 60 分钟 Console.Write("请输入今天学习分钟数: "); string? input = Console.ReadLine(); bool ok = int.TryParse(input, out int minutes); if (!ok || minutes < 0) { Console.WriteLine("请输入非负整数,如 45 或 120。"); return; } bool reached = minutes >= dailyTarget; // 比较运算 int diff = minutes - dailyTarget; // 算术运算 Console.WriteLine($"是否达成目标: {reached}"); Console.WriteLine(diff >= 0 ? $"超出目标 {diff} 分钟,干得漂亮!" : $"还差 {-diff} 分钟,明天加油!"); } }

注意三元表达式 条件 ? 值1 : 值2。虽然方便,但不要滥用,复杂情况用 if/else 更清晰。


条件分支:if/else 与 switch

程序需要根据不同情况做出不同的行为,这就是"分支控制"。就像我们在现实生活中做决定一样:如果天气晴朗就去公园,如果下雨就在家看书。

在 C# 中,最常用的分支结构是 if/else,它让程序能够"看情况行事"。当我们需要处理多个条件时,switch 语句(特别是 C# 8+ 的 switch 表达式)能让代码更加简洁清晰。

下面我们写一个成绩评级器,同时演示 if/else 和 switch 两种写法的区别:

|
// grade.cs using System; class GradeClassifier { static void Main() { Console.Write("请输入成绩(0-100): "); string? input = Console.ReadLine(); // 检查输入是否合法 if (!int.TryParse(input, out int score) || score < 0 || score > 100) { Console.WriteLine("请输入 0 到 100 之间的整数"); return; } // if/else 版本:处理多个条件 string level; if (score >= 90) level = "A"; else if (score >= 80) level = "B"; else if (score >= 70) level = "C"; else if (score >= 60) level = "D"; else level = "E"; Console.WriteLine($"评级(If/Else): {level}"); // switch 版本(C# 8+ 支持 switch 表达式) string level2 = score switch { >= 90 => "A", >= 80 => "B", >= 70 => "C", >= 60 => "D", _ => "E" // 下划线表示其它情况 }; Console.WriteLine($"评级(Switch): {level2}"); } }

switch 表达式常常让代码更紧凑。记住:清晰第一,紧凑第二。


循环:重复,直到完成

当我们需要重复做某件事,编程语言提供了四种主要的循环方式:

  • for 循环:当我们知道明确的次数时使用,比如"从 1 数到 10"或"重复 5 次"
  • while 循环:当我们有一个条件,满足就继续,不满足就停止,比如"用户还没输入正确答案就继续问"
  • do...while 循环:至少执行一次,然后根据条件决定是否继续,比如"先问一次,如果用户说继续就再问"
  • foreach 循环:专门用来遍历集合(数组、列表等),比如"把购物车里每一件商品都打印出来"

我们通过一个"猜数字游戏"来实际体验这几种循环。这个游戏的规则是:程序想一个数字,用户有限次机会猜,每次猜错会提示"大了"或"小了",猜对了就胜利。

|
// guess.cs using System; class GuessNumber { static void Main() { // 目标数字(真实项目里我们会用随机数,这里为了讲解固定为 42) int secret = 42; // 最多尝试 5 次 int maxTries = 5; // for 循环控制次数 for (int attempt = 1; attempt <= maxTries; attempt++) { Console.Write($"第 {attempt}/{maxTries} 次,请猜一个 1-100 的数: "); string? input = Console.ReadLine(); if (!int.TryParse(input, out int guess)) { Console.WriteLine("请输入整数"); attempt--; // 不计入无效输入 continue; // 继续下一轮 } if (guess == secret) { Console.WriteLine("猜对了!"); break; // 提前结束循环 } else if (guess < secret) { Console.WriteLine("小了,再试试"); } else { Console.WriteLine("大了,再试试"); } if (attempt == maxTries) { Console.WriteLine($"机会用完,正确答案是 {secret}"); } } // while 示例:从 5 倒数到 1 int n = 5; while (n > 0) { Console.WriteLine(n); n--; // 别忘记更新条件,否则会成“死循环” } // do...while:至少执行一次 int x = 0; do { Console.WriteLine($"do...while 第一次执行: x = {x}"); x++; } while (x < 0); // 条件为假,但循环体仍执行了一次 // foreach:遍历集合 int[] scores = { 95, 82, 76 }; foreach (int s in scores) { Console.WriteLine($"分数: {s}"); } } }

循环的关键是“循环条件”和“更新状态”。当你调试循环问题时,多打印变量,像打手电筒一样照亮黑箱。


数组和 List:装一排数据

数组就像是固定座位数的教室——你提前订好 10 个位置,就只能坐 10 个人,不能临时加椅子。声明时必须指定长度:int[] scores = new int[5];,这就预留了 5 个整数的空间。

相比之下,List<T> 更像是可以随时扩容的自助餐厅——开始可能只摆 3 张桌子,人多了就加桌子,人少了也能收起来。我们可以用 Add() 添加元素,用 Remove() 删除元素,用 Count 属性查看当前有多少项。

两者各有适用场景:如果你明确知道要存储的数据个数(比如一周 7 天的气温),数组足够简单高效;如果数据量会动态变化(比如用户逐个输入的购物清单),List<T> 更灵活方便。

下面我们用它们做一个小型"成绩统计器",看看实际使用中的差别:

|
// scores.cs using System; using System.Collections.Generic; // 使用 List 所需的命名空间 class ScoreStats { static void Main() { // 用数组存三次测验的成绩 int[] exams = new int[3]; exams[0] = 85; exams[1] = 92; exams[2] = 78; // 用 List 存可变数量的作业成绩 List<int> homeworks = new List<int> { 100, 90, 95, 80 }; // 计算数组平均分 double examAvg = Average(exams); Console.WriteLine($"测验平均分: {examAvg:F1}"); // 计算列表平均分 double hwAvg = Average(homeworks); Console.WriteLine($"作业平均分: {hwAvg:F1}"); // 合并后整体平均 List<int> all = new List<int>(); all.AddRange(exams); all.AddRange(homeworks); Console.WriteLine($"总体平均分: {Average(all):F1}"); } // 方法重载:支持数组 static double Average(int[] numbers) { if (numbers == null || numbers.Length == 0) return 0; int sum = 0; foreach (int n in numbers) sum += n; return (double)sum / numbers.Length; } // 方法重载:支持 List static double Average(List<int> numbers) { if (numbers == null || numbers.Count == 0) return 0; int sum = 0; foreach (int n in numbers) sum += n; return (double)sum / numbers.Count; } }

我们刚才见到了"方法重载"的实际应用:两个同名的 Average 方法,但参数类型不同——一个接受 int[] 数组,另一个接受 List<int> 列表。 这是 C# 的一个强大特性,让我们能用同一个方法名表达相同的逻辑概念,而编译器会根据我们传入的参数类型自动选择合适的版本。

想象一下,如果没有方法重载,我们可能需要写成 AverageArray() 和 AverageList() 这样的名字。 但"求平均数"本质上是同一个动作,不管容器是什么类型。方法重载让代码更简洁、更符合人类思维:我们说"计算平均值",系统自动判断用哪种实现方式。


注释与代码风格

注释是给“未来的我们”看的。好的注释解释“为什么”,而不是“怎么写”。

  • 用行注释 // 标注关键意图。
  • 使用有意义的命名,减少对注释的依赖。
  • 控制函数长度,超过屏幕一页就考虑拆分。

示例:

|
// 计算应付款:在原价基础上应用折扣,然后加上税费 static decimal CalculatePayable(decimal price, decimal discountRate, decimal taxRate) { decimal discounted = price * (1 - discountRate); decimal taxed = discounted * (1 + taxRate); return taxed; }

即便没有中文注释,也能从命名看出意图。这就是“写给未来的我们”。

项目结构与构建

当你 dotnet new console 时,会生成一个 .csproj 文件。它记录了项目名称、目标框架、依赖等信息。一个典型的控制台项目结构:

|
HelloCSharp/ HelloCSharp.csproj Program.cs

常用命令:

|
dotnet restore # 还原依赖(首次/变更依赖时) dotnet build # 构建项目(编译) dotnet run # 运行(会自动构建) dotnet publish # 发布可部署的产物

练习

  1. 在C#中,从控制台读取用户输入应该使用?
  1. 在C#中,用于控制台输出的方法包括?
  1. C#中的循环结构包括?
  1. C#中数组的特点是什么?
  1. List<T>集合的常用方法包括?
  1. 在C#中,允许定义多个同名但参数不同的方法,这称为?
  1. 摄氏度转华氏度的公式是?
  1. 在C#中,存储(string, int)元组的列表可以使用?

9. 温度转换器练习

编写一个温度转换程序,实现以下功能:

  • 使用 Console.Write() 或 Console.WriteLine() 提示用户输入摄氏度
  • 使用 Console.ReadLine() 读取用户输入的字符串
  • 使用 double.Parse() 或 Convert.ToDouble() 将字符串转换为浮点数
  • 使用公式 F = C × 9/5 + 32 计算华氏度
  • 使用 Console.WriteLine() 输出结果,格式为 $"{fahrenheit:F1}°F",保留1位小数
  • 注意:如果输入无效,程序可能会抛出异常,可以添加 try-catch 处理(可选)
|
using System; class TemperatureConverter { static void Main() { Console.Write("请输入摄氏度: "); string input = Console.ReadLine(); double celsius = double.Parse(input); double fahrenheit = celsius * 9.0 / 5.0 + 32; Console.WriteLine($"{fahrenheit:F1}°F"); } }
|
请输入摄氏度: 25 77.0°F

说明:

  • Console.Write() 输出提示信息,不换行
  • Console.ReadLine() 读取用户输入的一行文本
  • double.Parse() 将字符串转换为浮点数
  • 9.0 / 5.0 使用浮点数除法,避免整数除法
  • {fahrenheit:F1} 格式化输出,保留1位小数
  • 可以添加异常处理来提高程序的健壮性

10. 倒计时器练习

编写一个倒计时程序,实现以下功能:

  • 使用 Console.Write() 提示用户输入倒计时的秒数
  • 使用 Console.ReadLine() 读取用户输入的字符串
  • 使用 int.Parse() 或 Convert.ToInt32() 将字符串转换为整数
  • 使用 while 循环,从输入的秒数开始倒数到1
  • 在循环中,每秒打印当前剩余秒数(使用 Console.WriteLine())
  • 可以使用 Thread.Sleep(1000) 让程序暂停1秒(需要 using System.Threading;)
  • 循环结束后,打印 "时间到"
  • 注意:如果输入无效,程序可能会抛出异常,可以添加 try-catch 处理(可选)
|
using System; using System.Threading; class CountdownTimer { static void Main() { Console.Write("请输入倒计时秒数: "); string input = Console.ReadLine(); int seconds = int.Parse(input); while (seconds > 0) { Console.WriteLine(seconds); Thread.Sleep(1000); // 暂停1秒 seconds--; } Console.WriteLine("时间到"); } }
|
请输入倒计时秒数: 5 5 4 3 2 1 时间到

说明:

  • while 循环先判断条件,再执行循环体
  • Thread.Sleep(1000) 让程序暂停1000毫秒(1秒)
  • seconds-- 递减操作,每次循环减少1
  • 循环条件 seconds > 0 确保倒数到1后停止
  • 循环结束后打印 "时间到"

11. 打卡提醒练习

编写一个学习打卡程序,实现以下功能:

  • 创建一个 List<(string topic, int minutes)> 来存储学习记录
  • 使用循环让用户输入多组学习记录(主题和分钟数)
  • 每次循环中:
    • 提示用户输入学习主题(使用 Console.Write() 和 Console.ReadLine())
    • 提示用户输入学习分钟数(使用 Console.Write() 和 Console.ReadLine())
    • 将主题和分钟数作为元组添加到列表中(使用 Add() 方法)
    • 询问用户是否继续输入(如输入 "y" 继续,其他字符退出)
  • 输入结束后,使用 OrderByDescending() 方法按分钟数从大到小排序
  • 使用 foreach 循环遍历排序后的列表,打印每条记录
  • 注意:需要 using System.Linq; 来使用 OrderByDescending() 方法
|
using System; using System.Collections.Generic; using System.Linq; class StudyTracker { static void Main() { List<(string topic, int minutes)> records = new List<(string, int)>(); while (true) { Console.Write("请输入学习主题: "); string topic = Console.ReadLine(); Console.Write("请输入学习分钟数: "); int minutes = int.Parse(Console.ReadLine()); records.Add((topic, minutes)); Console.Write("是否继续输入?(y/n): "); string choice = Console.ReadLine(); if (choice != "y" && choice != "Y") { break; } } // 按分钟数从大到小排序 var sortedRecords = records.OrderByDescending(r => r.minutes); Console.WriteLine("\n学习记录(按分钟数从大到小):"); foreach (var record in sortedRecords) { Console.WriteLine($"{record.topic}: {record.minutes} 分钟"); } } }
|
请输入学习主题: C#基础 请输入学习分钟数: 60 是否继续输入?(y/n): y 请输入学习主题: 算法练习 请输入学习分钟数: 45 是否继续输入?(y/n): y 请输入学习主题: 项目实践 请输入学习分钟数: 90 是否继续输入?(y/n): n 学习记录(按分钟数从大到小): 项目实践: 90 分钟 C#基础: 60 分钟 算法练习: 45 分钟

说明:

  • List<(string, int)> 使用元组语法,存储主题和分钟数
  • while (true) 创建无限循环,通过 break 退出
  • OrderByDescending() 按指定字段降序排序
  • foreach 遍历排序后的集合
  • 元组访问使用 record.topic 和 record.minutes
  • 需要 using System.Linq; 来使用 LINQ 方法
  • C# 是什么,它能做什么
  • 安装与准备
  • 你的第一个 C# 程序
  • 程序结构
  • 变量与数据类型
    • 命名规范与可读性
    • 类型转换:别把方块塞进圆孔
  • 输入与输出:和用户对话
  • 运算符与表达式:算数、比较、逻辑
  • 条件分支:if/else 与 switch
  • 循环:重复,直到完成
  • 数组和 List:装一排数据
  • 注释与代码风格
  • 项目结构与构建
  • 练习

目录

  • C# 是什么,它能做什么
  • 安装与准备
  • 你的第一个 C# 程序
  • 程序结构
  • 变量与数据类型
    • 命名规范与可读性
    • 类型转换:别把方块塞进圆孔
  • 输入与输出:和用户对话
  • 运算符与表达式:算数、比较、逻辑
  • 条件分支:if/else 与 switch
  • 循环:重复,直到完成
  • 数组和 List:装一排数据
  • 注释与代码风格
  • 项目结构与构建
  • 练习
自在学

© 2025 自在学,保留所有权利。

公网安备湘公网安备43020302000292号 | 湘ICP备2025148919号-1

关于我们隐私政策使用条款

© 2025 自在学,保留所有权利。

公网安备湘公网安备43020302000292号湘ICP备2025148919号-1