C#基础语法完全详解 编程小白也能轻松上手的实用指南
1. 引言
C#(读作”C Sharp”)是一种由微软开发的现代、类型安全的面向对象编程语言。它是.NET框架的一部分,设计目标是结合Visual Basic的易用性和C++的强大功能。C#广泛用于开发桌面应用程序、Web应用、游戏(尤其是使用Unity引擎的游戏)、移动应用等。
对于编程初学者来说,C#是一个很好的入门选择,因为它的语法清晰、结构严谨,同时又有庞大的社区支持和丰富的学习资源。本指南将带你从零开始,逐步掌握C#的基础语法,即使你没有任何编程经验,也能轻松上手。
2. 开发环境搭建
在开始编写C#代码之前,你需要安装一个集成开发环境(IDE)。对于C#开发,最常用的IDE是Visual Studio。
安装Visual Studio
- 访问Visual Studio官网:https://visualstudio.microsoft.com/
- 下载Visual Studio Community版本(免费且功能齐全)
- 运行安装程序,在”工作负载”选项卡中,选择”.NET桌面开发”
- 点击安装按钮,等待安装完成
创建你的第一个C#项目
- 打开Visual Studio
- 点击”创建新项目”
- 选择”控制台应用”模板
- 为项目命名(例如”HelloWorld”),选择保存位置
- 点击”创建”按钮
现在,你已经准备好开始编写C#代码了!
3. C#程序的基本结构
让我们从一个经典的”Hello World”程序开始,了解C#程序的基本结构。
using System; namespace HelloWorld { class Program { static void Main(string[] args) { Console.WriteLine("Hello, World!"); } } }
代码解析
using System;
- 引入System命名空间,包含基本的输入输出功能namespace HelloWorld
- 定义一个名为HelloWorld的命名空间,用于组织代码class Program
- 定义一个名为Program的类,C#程序的基本组成单位static void Main(string[] args)
- 程序的入口点,程序从这里开始执行static
- 表示这个方法属于类本身,而不是类的实例void
- 表示这个方法不返回任何值Main
- 方法名,是C#程序的入口点string[] args
- 参数,用于接收命令行参数
Console.WriteLine("Hello, World!");
- 在控制台输出文本
运行程序
在Visual Studio中,你可以通过以下方式运行程序:
- 按F5键(开始调试)
- 点击工具栏上的”开始”按钮(绿色三角形)
- 使用菜单:调试 -> 开始调试
你将在控制台窗口看到输出:”Hello, World!”
4. 变量与数据类型
在编程中,变量是用来存储数据的容器。每个变量都有一个特定的数据类型,决定了它可以存储什么类型的数据以及如何操作这些数据。
基本数据类型
C#提供了多种基本数据类型,以下是最常用的几种:
数据类型 | 描述 | 示例 | 范围 |
---|---|---|---|
int | 整数 | int age = 25; | -2,147,483,648 到 2,147,483,647 |
double | 双精度浮点数 | double price = 19.99; | ±5.0 × 10^−324 到 ±1.7 × 10^308 |
float | 单精度浮点数 | float temperature = 36.6f; | ±1.5 × 10^−45 到 ±3.4 × 10^38 |
char | 单个字符 | char grade = ‘A’; | U+0000 到 U+ffff |
bool | 布尔值(真/假) | bool isPassed = true; | true 或 false |
string | 字符串 | string name = “John”; | 任意长度的字符序列 |
变量声明与初始化
在C#中,你可以先声明变量,然后再赋值,也可以在声明的同时初始化:
// 先声明,后赋值 int age; age = 25; // 声明的同时初始化 string name = "John Doe"; double height = 175.5; bool isStudent = true;
变量命名规则
在C#中,变量命名需要遵循以下规则:
- 变量名必须以字母、下划线(_)或@符号开头
- 变量名可以包含字母、数字、下划线
- 变量名不能是C#关键字(如int, class, void等)
- 变量名区分大小写(age和Age是不同的变量)
良好的命名习惯:
- 使用有意义的名称(如studentAge而不是a)
- 使用驼峰命名法(第一个单词小写,后续单词首字母大写,如firstName)
- 避免使用缩写(除非是广泛接受的缩写)
类型推断
C#允许使用var
关键字让编译器自动推断变量的类型:
var age = 25; // 编译器推断age为int类型 var name = "John"; // 编译器推断name为string类型 var price = 19.99; // 编译器推断price为double类型
注意:使用var
时必须在声明的同时初始化,因为编译器需要根据初始值来确定类型。
常量
如果你有一个值在程序运行期间不会改变,可以将其声明为常量:
const double PI = 3.14159; const int DAYS_IN_WEEK = 7;
常量一旦声明就不能修改,尝试修改常量值会导致编译错误。
类型转换
有时,你需要将一种数据类型的值转换为另一种数据类型。C#中有两种类型的转换:隐式转换和显式转换。
隐式转换
当转换是安全的(不会导致数据丢失)时,C#会自动进行隐式转换:
int intValue = 100; long longValue = intValue; // int可以隐式转换为long,因为long的范围更大 float floatValue = 10.5f; double doubleValue = floatValue; // float可以隐式转换为double
显式转换(强制转换)
当转换可能导致数据丢失时,需要进行显式转换:
double doubleValue = 10.8; int intValue = (int)doubleValue; // intValue将为10,小数部分被截断 char charValue = 'A'; int asciiValue = (int)charValue; // asciiValue将为65
使用Convert类进行转换
C#提供了Convert
类,可以在各种基本数据类型之间进行转换:
string strNumber = "123"; int number = Convert.ToInt32(strNumber); string strDouble = "19.99"; double price = Convert.ToDouble(strDouble); int age = 25; string strAge = Convert.ToString(age);
使用Parse方法
许多数据类型提供了Parse
方法,可以将字符串转换为该类型:
string strNumber = "123"; int number = int.Parse(strNumber); string strDouble = "19.99"; double price = double.Parse(strDouble);
注意:如果字符串格式不正确,Parse
方法会抛出异常。为了避免这种情况,可以使用TryParse
方法:
string strNumber = "abc"; int number; if (int.TryParse(strNumber, out number)) { Console.WriteLine("转换成功: " + number); } else { Console.WriteLine("转换失败"); }
5. 运算符
运算符是用于执行操作的符号。C#提供了多种运算符,包括算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符等。
算术运算符
算术运算符用于执行基本的数学运算:
运算符 | 描述 | 示例 |
---|---|---|
+ | 加法 | int sum = 5 + 3; // 结果为8 |
- | 减法 | int difference = 5 - 3; // 结果为2 |
* | 乘法 | int product = 5 * 3; // 结果为15 |
/ | 除法 | int quotient = 15 / 3; // 结果为5 |
% | 取模(求余) | int remainder = 10 % 3; // 结果为1 |
++ | 自增 | int i = 5; i++; // i变为6 |
– | 自减 | int i = 5; i–; // i变为4 |
自增和自减运算符可以放在变量的前面(前缀)或后面(后缀),它们的效果不同:
int x = 5; int y = x++; // y为5,x为6(先赋值,后自增) int a = 5; int b = ++a; // b为6,a为6(先自增,后赋值)
关系运算符
关系运算符用于比较两个值,结果是一个布尔值(true或false):
运算符 | 描述 | 示例 |
---|---|---|
== | 等于 | bool result = (5 == 3); // 结果为false |
!= | 不等于 | bool result = (5 != 3); // 结果为true |
> | 大于 | bool result = (5 > 3); // 结果为true |
< | 小于 | bool result = (5 < 3); // 结果为false |
>= | 大于等于 | bool result = (5 >= 3); // 结果为true |
<= | 小于等于 | bool result = (5 <= 3); // 结果为false |
逻辑运算符
逻辑运算符用于组合多个条件,结果也是一个布尔值:
运算符 | 描述 | 示例 |
---|---|---|
&& | 逻辑与 | bool result = (true && false); // 结果为false |
|| | 逻辑或 | bool result = (true || false); // 结果为true |
! | 逻辑非 | bool result = !true; // 结果为false |
短路求值:&&
和||
运算符具有短路特性,即如果第一个操作数已经可以确定整个表达式的结果,就不会计算第二个操作数。
int x = 5; bool result = (x > 10) && (x++ < 20); // x > 10为false,所以不会计算x++ < 20,x仍为5
位运算符
位运算符直接对整数的二进制位进行操作:
运算符 | 描述 | 示例 |
---|---|---|
& | 按位与 | int result = 5 & 3; // 结果为1 |
| | 按位或 | int result = 5 | 3; // 结果为7 |
^ | 按位异或 | int result = 5 ^ 3; // 结果为6 |
~ | 按位取反 | int result = ~5; // 结果为-6 |
<< | 左移 | int result = 5 << 1; // 结果为10 |
>> | 右移 | int result = 5 >> 1; // 结果为2 |
赋值运算符
赋值运算符用于给变量赋值:
运算符 | 描述 | 示例 |
---|---|---|
= | 简单赋值 | int x = 5; |
+= | 加法赋值 | int x = 5; x += 3; // x变为8 |
-= | 减法赋值 | int x = 5; x -= 3; // x变为2 |
*= | 乘法赋值 | int x = 5; x *= 3; // x变为15 |
/= | 除法赋值 | int x = 15; x /= 3; // x变为5 |
%= | 取模赋值 | int x = 10; x %= 3; // x变为1 |
&= | 按位与赋值 | int x = 5; x &= 3; // x变为1 |
|= | 按位或赋值 | int x = 5; x |= 3; // x变为7 |
^= | 按位异或赋值 | int x = 5; x ^= 3; // x变为6 |
<<= | 左移赋值 | int x = 5; x <<= 1; // x变为10 |
>>= | 右移赋值 | int x = 5; x >>= 1; // x变为2 |
运算符优先级
当一个表达式中有多个运算符时,运算符的优先级决定了计算的顺序。以下是C#中运算符的优先级(从高到低):
- 括号:()
- 自增/自减:++, –(作为前缀)
- 乘法/除法/取模:*, /, %
- 加法/减法:+, -
- 关系运算符:<, >, <=, >=
- 相等性运算符:==, !=
- 按位与:&
- 按位异或:^
- 按位或:|
- 逻辑与:&&
- 逻辑或:||
- 赋值运算符:=, +=, -=, *=, /=, %=等
可以使用括号改变运算的优先级:
int result = 5 + 3 * 2; // 结果为11,因为*优先级高于+ int result = (5 + 3) * 2; // 结果为16,因为括号改变了优先级
6. 控制流语句
控制流语句允许你根据条件控制程序的执行路径。C#提供了多种控制流语句,包括条件语句和循环语句。
条件语句
if语句
if
语句用于在条件为true时执行一段代码:
int age = 18; if (age >= 18) { Console.WriteLine("你是成年人"); }
if-else语句
if-else
语句在条件为true时执行一段代码,为false时执行另一段代码:
int age = 16; if (age >= 18) { Console.WriteLine("你是成年人"); } else { Console.WriteLine("你是未成年人"); }
if-else if-else语句
当有多个条件需要判断时,可以使用if-else if-else
语句:
int score = 85; if (score >= 90) { Console.WriteLine("优秀"); } else if (score >= 80) { Console.WriteLine("良好"); } else if (score >= 60) { Console.WriteLine("及格"); } else { Console.WriteLine("不及格"); }
switch语句
switch
语句用于根据变量的值执行不同的代码块,特别适合多分支情况:
char grade = 'B'; switch (grade) { case 'A': Console.WriteLine("优秀"); break; case 'B': Console.WriteLine("良好"); break; case 'C': Console.WriteLine("及格"); break; case 'D': Console.WriteLine("不及格"); break; default: Console.WriteLine("无效的成绩"); break; }
C# 7.0及以上版本支持switch表达式,可以更简洁地实现相同的功能:
char grade = 'B'; string message = grade switch { 'A' => "优秀", 'B' => "良好", 'C' => "及格", 'D' => "不及格", _ => "无效的成绩" }; Console.WriteLine(message);
三元运算符
三元运算符?:
是if-else
语句的简洁形式,适用于简单的条件判断:
int age = 18; string message = (age >= 18) ? "你是成年人" : "你是未成年人"; Console.WriteLine(message);
循环语句
for循环
for
循环用于重复执行一段代码特定次数:
for (int i = 1; i <= 5; i++) { Console.WriteLine("这是第 " + i + " 次循环"); }
for
循环由三部分组成:
- 初始化:
int i = 1
,设置循环变量的初始值 - 条件:
i <= 5
,每次循环前检查的条件,如果为true则继续循环 - 迭代:
i++
,每次循环后执行的操作,通常用于更新循环变量
while循环
while
循环在条件为true时重复执行一段代码:
int count = 1; while (count <= 5) { Console.WriteLine("这是第 " + count + " 次循环"); count++; }
注意:如果条件始终为true,while
循环会变成无限循环,应避免这种情况。
do-while循环
do-while
循环至少执行一次,然后在条件为true时继续执行:
int count = 1; do { Console.WriteLine("这是第 " + count + " 次循环"); count++; } while (count <= 5);
do-while
循环与while
循环的区别在于,do-while
循环先执行代码,再检查条件,因此至少执行一次。
foreach循环
foreach
循环用于遍历集合或数组中的每个元素:
string[] fruits = { "苹果", "香蕉", "橙子" }; foreach (string fruit in fruits) { Console.WriteLine(fruit); }
跳转语句
break语句
break
语句用于立即退出循环或switch语句:
for (int i = 1; i <= 10; i++) { if (i == 6) { break; // 当i等于6时退出循环 } Console.WriteLine(i); } // 输出:1 2 3 4 5
continue语句
continue
语句用于跳过当前循环的剩余部分,直接进入下一次循环:
for (int i = 1; i <= 10; i++) { if (i % 2 == 0) { continue; // 跳过偶数 } Console.WriteLine(i); } // 输出:1 3 5 7 9
goto语句
goto
语句允许程序直接跳转到指定的标签处。虽然C#支持goto
语句,但通常不推荐使用,因为它会使代码结构混乱,难以维护:
int i = 0; start: Console.WriteLine(i); i++; if (i < 5) { goto start; }
return语句
return
语句用于退出方法,并可以返回一个值(如果方法的返回类型不是void):
int Add(int a, int b) { return a + b; } void PrintMessage(string message) { Console.WriteLine(message); return; // 在void方法中,return是可选的 }
7. 数组与集合
数组和集合是用于存储多个数据项的数据结构。它们允许你以有序的方式组织和管理数据。
数组
数组是一个固定大小的序列,其中所有元素都具有相同的类型。
一维数组
声明和初始化一维数组的几种方式:
// 声明并指定大小 int[] numbers = new int[5]; // 声明并初始化 int[] numbers = new int[] { 1, 2, 3, 4, 5 }; // 简化形式 int[] numbers = { 1, 2, 3, 4, 5 };
访问数组元素:
int[] numbers = { 1, 2, 3, 4, 5 }; Console.WriteLine(numbers[0]); // 输出第一个元素:1 numbers[0] = 10; // 修改第一个元素的值 Console.WriteLine(numbers[0]); // 输出:10
遍历数组:
int[] numbers = { 1, 2, 3, 4, 5 }; // 使用for循环 for (int i = 0; i < numbers.Length; i++) { Console.WriteLine(numbers[i]); } // 使用foreach循环 foreach (int number in numbers) { Console.WriteLine(number); }
多维数组
C#支持多维数组,最常见的是二维数组:
// 声明并初始化二维数组 int[,] matrix = new int[3, 4]; // 声明并初始化二维数组(带初始值) int[,] matrix = new int[,] { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } }; // 简化形式 int[,] matrix = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } };
访问二维数组元素:
int[,] matrix = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } }; Console.WriteLine(matrix[0, 0]); // 输出第一行第一列的元素:1 matrix[0, 0] = 10; // 修改第一行第一列的值 Console.WriteLine(matrix[0, 0]); // 输出:10
遍历二维数组:
int[,] matrix = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } }; // 使用嵌套for循环 for (int i = 0; i < matrix.GetLength(0); i++) { for (int j = 0; j < matrix.GetLength(1); j++) { Console.Write(matrix[i, j] + "t"); } Console.WriteLine(); }
交错数组
交错数组是数组的数组,其中每个元素都是一个数组,这些数组的长度可以不同:
// 声明交错数组 int[][] jaggedArray = new int[3][]; // 初始化每个元素(数组) jaggedArray[0] = new int[] { 1, 2 }; jaggedArray[1] = new int[] { 3, 4, 5 }; jaggedArray[2] = new int[] { 6, 7, 8, 9 }; // 简化形式 int[][] jaggedArray = { new int[] { 1, 2 }, new int[] { 3, 4, 5 }, new int[] { 6, 7, 8, 9 } };
访问交错数组元素:
int[][] jaggedArray = { new int[] { 1, 2 }, new int[] { 3, 4, 5 }, new int[] { 6, 7, 8, 9 } }; Console.WriteLine(jaggedArray[0][0]); // 输出第一个数组的第一个元素:1 jaggedArray[0][0] = 10; // 修改第一个数组的第一个元素的值 Console.WriteLine(jaggedArray[0][0]); // 输出:10
遍历交错数组:
int[][] jaggedArray = { new int[] { 1, 2 }, new int[] { 3, 4, 5 }, new int[] { 6, 7, 8, 9 } }; // 使用嵌套循环 for (int i = 0; i < jaggedArray.Length; i++) { for (int j = 0; j < jaggedArray[i].Length; j++) { Console.Write(jaggedArray[i][j] + " "); } Console.WriteLine(); }
常用集合
除了数组,C#还提供了多种集合类,它们位于System.Collections
和System.Collections.Generic
命名空间中。
List
List<T>
是一个动态数组,可以根据需要自动调整大小:
using System.Collections.Generic; // 创建List List<string> fruits = new List<string>(); // 添加元素 fruits.Add("苹果"); fruits.Add("香蕉"); fruits.Add("橙子"); // 访问元素 Console.WriteLine(fruits[0]); // 输出:苹果 // 修改元素 fruits[0] = "葡萄"; Console.WriteLine(fruits[0]); // 输出:葡萄 // 删除元素 fruits.Remove("香蕉"); // 删除指定元素 fruits.RemoveAt(0); // 删除指定索引的元素 // 遍历List foreach (string fruit in fruits) { Console.WriteLine(fruit); } // 获取元素数量 Console.WriteLine("水果数量: " + fruits.Count);
Dictionary
Dictionary<TKey, TValue>
是一个键值对集合,通过键来访问值:
using System.Collections.Generic; // 创建Dictionary Dictionary<string, int> ages = new Dictionary<string, int>(); // 添加键值对 ages.Add("张三", 25); ages.Add("李四", 30); ages.Add("王五", 35); // 访问值 Console.WriteLine("张三的年龄: " + ages["张三"]); // 修改值 ages["张三"] = 26; Console.WriteLine("张三的年龄: " + ages["张三"]); // 检查键是否存在 if (ages.ContainsKey("张三")) { Console.WriteLine("张三的年龄: " + ages["张三"]); } // 删除键值对 ages.Remove("李四"); // 遍历Dictionary foreach (KeyValuePair<string, int> pair in ages) { Console.WriteLine(pair.Key + ": " + pair.Value); }
Queue
Queue<T>
是一个先进先出(FIFO)集合:
using System.Collections.Generic; // 创建Queue Queue<string> queue = new Queue<string>(); // 添加元素(入队) queue.Enqueue("第一个"); queue.Enqueue("第二个"); queue.Enqueue("第三个"); // 查看队首元素(不移除) Console.WriteLine("队首元素: " + queue.Peek()); // 移除并返回队首元素(出队) string item = queue.Dequeue(); Console.WriteLine("出队元素: " + item); // 遍历Queue foreach (string qItem in queue) { Console.WriteLine(qItem); }
Stack
Stack<T>
是一个后进先出(LIFO)集合:
using System.Collections.Generic; // 创建Stack Stack<string> stack = new Stack<string>(); // 添加元素(入栈) stack.Push("第一个"); stack.Push("第二个"); stack.Push("第三个"); // 查看栈顶元素(不移除) Console.WriteLine("栈顶元素: " + stack.Peek()); // 移除并返回栈顶元素(出栈) string item = stack.Pop(); Console.WriteLine("出栈元素: " + item); // 遍历Stack foreach (string sItem in stack) { Console.WriteLine(sItem); }
8. 方法的定义与使用
方法(也称为函数)是一段可重用的代码,用于执行特定的任务。方法可以提高代码的模块化和可重用性。
方法的定义
方法的基本语法如下:
访问修饰符 返回类型 方法名(参数列表) { // 方法体 return 返回值; // 如果返回类型不是void }
- 访问修饰符:如
public
、private
、protected
等,指定方法的可访问性 - 返回类型:方法返回的值的类型,如果方法不返回值,则使用
void
- 方法名:方法的名称,遵循标识符命名规则
- 参数列表:方法接收的输入参数,多个参数用逗号分隔
- 方法体:包含执行任务的代码
- return语句:返回结果并退出方法,如果返回类型是
void
,可以省略return
语句或使用return;
不带值
无参数无返回值的方法
public void SayHello() { Console.WriteLine("Hello, World!"); }
带参数无返回值的方法
public void SayHelloTo(string name) { Console.WriteLine("Hello, " + name + "!"); }
带参数有返回值的方法
public int Add(int a, int b) { return a + b; }
参数传递
C#中有两种参数传递方式:值传递和引用传递。
值传递
默认情况下,参数是通过值传递的,即方法接收的是参数的副本,对参数的修改不会影响原始值:
public void ModifyValue(int x) { x = 100; } int number = 5; ModifyValue(number); Console.WriteLine(number); // 输出:5,原始值不受影响
引用传递
使用ref
关键字可以通过引用传递参数,这样方法可以修改原始值:
public void ModifyRef(ref int x) { x = 100; } int number = 5; ModifyRef(ref number); Console.WriteLine(number); // 输出:100,原始值被修改
注意:使用ref
参数时,调用方法时也必须使用ref
关键字。
输出参数
使用out
关键字可以定义输出参数,用于从方法返回多个值:
public void Divide(int dividend, int divisor, out int quotient, out int remainder) { quotient = dividend / divisor; remainder = dividend % divisor; } int quotient, remainder; Divide(10, 3, out quotient, out remainder); Console.WriteLine("商: " + quotient + ", 余数: " + remainder); // 输出:商: 3, 余数: 1
注意:使用out
参数时,调用方法时也必须使用out
关键字,并且方法必须为out
参数赋值。
参数数组
使用params
关键字可以定义可变数量的参数:
public int CalculateSum(params int[] numbers) { int sum = 0; foreach (int number in numbers) { sum += number; } return sum; } int sum1 = CalculateSum(1, 2, 3); // 传递三个参数 int sum2 = CalculateSum(1, 2, 3, 4, 5); // 传递五个参数 int sum3 = CalculateSum(); // 不传递参数 Console.WriteLine(sum1); // 输出:6 Console.WriteLine(sum2); // 输出:15 Console.WriteLine(sum3); // 输出:0
方法重载
方法重载允许在同一个类中定义多个同名的方法,但参数列表必须不同(参数个数、类型或顺序不同):
public int Add(int a, int b) { return a + b; } public double Add(double a, double b) { return a + b; } public int Add(int a, int b, int c) { return a + b + c; } int sum1 = Add(1, 2); // 调用第一个Add方法 double sum2 = Add(1.5, 2.5); // 调用第二个Add方法 int sum3 = Add(1, 2, 3); // 调用第三个Add方法
递归方法
递归方法是指方法调用自身的情况。递归通常用于解决可以分解为相似子问题的问题:
public int Factorial(int n) { if (n == 0 || n == 1) { return 1; } else { return n * Factorial(n - 1); } } int result = Factorial(5); Console.WriteLine(result); // 输出:120
注意:递归方法必须有终止条件,否则会导致无限递归和栈溢出错误。
静态方法与实例方法
静态方法
静态方法使用static
关键字修饰,属于类本身,而不是类的实例。静态方法可以通过类名直接调用,不需要创建类的实例:
public class MathHelper { public static int Add(int a, int b) { return a + b; } } int sum = MathHelper.Add(5, 3); // 直接通过类名调用 Console.WriteLine(sum); // 输出:8
实例方法
实例方法不属于static
,必须通过类的实例来调用:
public class Calculator { public int Add(int a, int b) { return a + b; } } Calculator calculator = new Calculator(); // 创建实例 int sum = calculator.Add(5, 3); // 通过实例调用 Console.WriteLine(sum); // 输出:8
9. 类与对象
C#是一种面向对象的编程语言,类和对象是面向对象编程的核心概念。
类的定义
类是一种用户定义的数据类型,它包含数据成员(字段、属性)和函数成员(方法、构造函数等):
public class Person { // 字段 private string name; private int age; // 构造函数 public Person(string name, int age) { this.name = name; this.age = age; } // 属性 public string Name { get { return name; } set { name = value; } } public int Age { get { return age; } set { age = value; } } // 方法 public void SayHello() { Console.WriteLine("Hello, my name is " + name + " and I am " + age + " years old."); } }
对象的创建与使用
对象是类的实例,使用new
关键字创建:
// 创建Person对象 Person person = new Person("张三", 25); // 访问属性 Console.WriteLine("姓名: " + person.Name); Console.WriteLine("年龄: " + person.Age); // 修改属性 person.Name = "李四"; person.Age = 30; // 调用方法 person.SayHello();
构造函数
构造函数是一种特殊的方法,用于在创建对象时初始化对象。构造函数的名称与类名相同,没有返回类型。
默认构造函数
如果没有显式定义构造函数,C#会提供一个默认的无参构造函数:
public class Person { private string name; private int age; // 默认构造函数 public Person() { name = "Unknown"; age = 0; } } Person person = new Person(); // 使用默认构造函数
带参数的构造函数
可以定义带参数的构造函数,以便在创建对象时提供初始值:
public class Person { private string name; private int age; // 带参数的构造函数 public Person(string name, int age) { this.name = name; this.age = age; } } Person person = new Person("张三", 25); // 使用带参数的构造函数
构造函数重载
可以定义多个构造函数,只要它们的参数列表不同:
public class Person { private string name; private int age; // 默认构造函数 public Person() { name = "Unknown"; age = 0; } // 带一个参数的构造函数 public Person(string name) { this.name = name; age = 0; } // 带两个参数的构造函数 public Person(string name, int age) { this.name = name; this.age = age; } } Person person1 = new Person(); Person person2 = new Person("张三"); Person person3 = new Person("张三", 25);
构造函数链
可以使用this
关键字在一个构造函数中调用另一个构造函数:
public class Person { private string name; private int age; public Person() : this("Unknown", 0) { } public Person(string name) : this(name, 0) { } public Person(string name, int age) { this.name = name; this.age = age; } }
属性
属性是类的成员,它提供了一种灵活的机制来读取、写入或计算私有字段的值。属性使用访问器(get和set)来定义。
只读属性
只有get访问器的属性是只读属性:
public class Circle { private double radius; public Circle(double radius) { this.radius = radius; } public double Radius { get { return radius; } } public double Area { get { return Math.PI * radius * radius; } } } Circle circle = new Circle(5); Console.WriteLine("半径: " + circle.Radius); Console.WriteLine("面积: " + circle.Area); // circle.Radius = 10; // 错误,Radius是只读属性
只写属性
只有set访问器的属性是只写属性:
public class Password { private string password; public string Value { set { password = value; } } public bool Check(string input) { return input == password; } } Password pwd = new Password(); pwd.Value = "secret123"; // 设置密码 // Console.WriteLine(pwd.Value); // 错误,Value是只写属性 Console.WriteLine(pwd.Check("secret123")); // 输出:True Console.WriteLine(pwd.Check("wrong")); // 输出:False
自动实现的属性
如果属性不需要额外的逻辑,可以使用自动实现的属性:
public class Person { public string Name { get; set; } public int Age { get; set; } } Person person = new Person(); person.Name = "张三"; person.Age = 25; Console.WriteLine("姓名: " + person.Name); Console.WriteLine("年龄: " + person.Age);
属性初始化器
C# 6.0及以上版本支持属性初始化器,可以在声明属性时指定初始值:
public class Person { public string Name { get; set; } = "Unknown"; public int Age { get; set; } = 0; } Person person = new Person(); Console.WriteLine("姓名: " + person.Name); // 输出:Unknown Console.WriteLine("年龄: " + person.Age); // 输出:0
访问修饰符
访问修饰符用于指定类、字段、属性、方法等成员的可访问性:
访问修饰符 | 描述 |
---|---|
public | 可以从任何地方访问 |
private | 只能在定义它的类内部访问 |
protected | 只能在定义它的类或派生类中访问 |
internal | 只能在同一程序集内访问 |
protected internal | 可以在同一程序集或派生类中访问 |
public class Person { // 私有字段,只能在Person类内部访问 private string name; // 公共属性,可以从任何地方访问 public string Name { get { return name; } set { name = value; } } // 受保护的方法,只能在Person类或其派生类中访问 protected void Display() { Console.WriteLine("Name: " + name); } // 内部属性,只能在同一程序集内访问 internal int Age { get; set; } }
静态成员
使用static
关键字修饰的成员属于类本身,而不是类的实例。静态成员可以通过类名直接访问,不需要创建类的实例。
静态字段
public class Counter { public static int count = 0; public Counter() { count++; // 每次创建Counter对象时,count增加1 } } Counter c1 = new Counter(); Counter c2 = new Counter(); Counter c3 = new Counter(); Console.WriteLine(Counter.count); // 输出:3
静态属性
public class Circle { private static double _pi = 3.14159; public static double PI { get { return _pi; } } } Console.WriteLine("PI的值: " + Circle.PI);
静态方法
public class MathHelper { public static int Add(int a, int b) { return a + b; } public static int Multiply(int a, int b) { return a * b; } } int sum = MathHelper.Add(5, 3); int product = MathHelper.Multiply(5, 3); Console.WriteLine("和: " + sum); // 输出:8 Console.WriteLine("积: " + product); // 输出:15
静态类
使用static
关键字修饰的类只能包含静态成员,不能被实例化:
public static class ConfigurationManager { private static string _connectionString; public static string ConnectionString { get { return _connectionString; } set { _connectionString = value; } } public static void LoadConfiguration() { // 加载配置的代码 } } // 使用静态类 ConfigurationManager.ConnectionString = "Server=myServer;Database=myDB;"; ConfigurationManager.LoadConfiguration(); // ConfigurationManager config = new ConfigurationManager(); // 错误,不能实例化静态类
this关键字
this
关键字引用当前类的实例,可以用于:
- 区分类的字段和参数/局部变量
- 调用当前类的其他构造函数
- 作为参数传递给其他方法
public class Person { private string name; private int age; public Person(string name, int age) { // 使用this区分字段和参数 this.name = name; this.age = age; } public Person(string name) : this(name, 0) { // 调用其他构造函数 } public void PrintDetails() { // 将当前实例作为参数传递 Printer.Print(this); } } public static class Printer { public static void Print(Person person) { Console.WriteLine("Name: " + person.Name + ", Age: " + person.Age); } }
10. 继承与多态
继承和多态是面向对象编程的重要特性,它们允许创建基于现有类的新类,并实现”一个接口,多种方法”的编程模式。
继承
继承允许一个类(派生类)继承另一个类(基类)的字段、属性和方法。派生类可以重用基类的代码,并可以添加新的成员或重写基类的成员。
基本语法
// 基类 public class Animal { protected string name; public Animal(string name) { this.name = name; } public void Eat() { Console.WriteLine(name + " is eating."); } public virtual void Sleep() { Console.WriteLine(name + " is sleeping."); } } // 派生类 public class Dog : Animal { public Dog(string name) : base(name) { } public void Bark() { Console.WriteLine(name + " is barking."); } public override void Sleep() { Console.WriteLine(name + " is sleeping peacefully."); } }
使用继承
Dog dog = new Dog("Buddy"); dog.Eat(); // 继承自Animal类的方法 dog.Bark(); // Dog类自己的方法 dog.Sleep(); // 重写的方法
构造函数和继承
派生类的构造函数可以调用基类的构造函数,使用base
关键字:
public class Animal { protected string name; public Animal(string name) { this.name = name; } } public class Dog : Animal { private string breed; public Dog(string name, string breed) : base(name) { this.breed = breed; } }
访问基类成员
使用base
关键字可以访问基类的成员:
public class Animal { protected string name; public Animal(string name) { this.name = name; } public virtual void Sleep() { Console.WriteLine(name + " is sleeping."); } } public class Dog : Animal { public Dog(string name) : base(name) { } public override void Sleep() { base.Sleep(); // 调用基类的Sleep方法 Console.WriteLine(name + " is dreaming about chasing cats."); } }
多态
多态允许使用基类引用指向派生类对象,并在运行时调用适当的重写方法。
虚方法和方法重写
使用virtual
关键字标记的方法可以在派生类中被重写,使用override
关键字重写方法:
public class Shape { public virtual void Draw() { Console.WriteLine("Drawing a shape."); } } public class Circle : Shape { public override void Draw() { Console.WriteLine("Drawing a circle."); } } public class Rectangle : Shape { public override void Draw() { Console.WriteLine("Drawing a rectangle."); } }
使用多态
Shape[] shapes = new Shape[3]; shapes[0] = new Shape(); shapes[1] = new Circle(); shapes[2] = new Rectangle(); foreach (Shape shape in shapes) { shape.Draw(); // 根据实际对象类型调用适当的Draw方法 }
输出:
Drawing a shape. Drawing a circle. Drawing a rectangle.
抽象类和抽象方法
抽象类是不能被实例化的类,它只能作为其他类的基类。抽象方法是没有实现的方法,必须在非抽象派生类中被重写。
public abstract class Shape { public abstract void Draw(); // 抽象方法,没有实现 public void Display() { Console.WriteLine("This is a shape."); } } public class Circle : Shape { public override void Draw() { Console.WriteLine("Drawing a circle."); } } public class Rectangle : Shape { public override void Draw() { Console.WriteLine("Drawing a rectangle."); } }
使用抽象类:
// Shape shape = new Shape(); // 错误,不能实例化抽象类 Shape circle = new Circle(); Shape rectangle = new Rectangle(); circle.Draw(); // 输出:Drawing a circle. rectangle.Draw(); // 输出:Drawing a rectangle. circle.Display(); // 输出:This is a shape. rectangle.Display(); // 输出:This is a shape.
密封类和密封方法
密封类是不能被继承的类,使用sealed
关键字标记。密封方法是不能被重写的方法,也使用sealed
关键字标记。
public sealed class FinalClass { // 类的实现 } // public class DerivedClass : FinalClass { } // 错误,不能继承密封类 public class BaseClass { public virtual void Method() { Console.WriteLine("BaseClass.Method"); } } public class DerivedClass : BaseClass { public sealed override void Method() { Console.WriteLine("DerivedClass.Method"); } } public class FurtherDerivedClass : DerivedClass { // public override void Method() { } // 错误,不能重写密封方法 }
接口
接口定义了一组成员(方法、属性、事件和索引器),但不提供实现。类或结构可以实现接口,并提供接口成员的具体实现。
public interface IDrawable { void Draw(); } public interface IResizable { void Resize(int width, int height); } public class Rectangle : IDrawable, IResizable { private int width; private int height; public Rectangle(int width, int height) { this.width = width; this.height = height; } public void Draw() { Console.WriteLine("Drawing a rectangle with width " + width + " and height " + height); } public void Resize(int width, int height) { this.width = width; this.height = height; Console.WriteLine("Resized to width " + width + " and height " + height); } }
使用接口:
Rectangle rectangle = new Rectangle(10, 20); IDrawable drawable = rectangle; IResizable resizable = rectangle; drawable.Draw(); // 输出:Drawing a rectangle with width 10 and height 20 resizable.Resize(15, 25); // 输出:Resized to width 15 and height 25
显式接口实现
如果一个类实现多个接口,而这些接口有相同签名的成员,可以使用显式接口实现来解决冲突:
public interface IControl { void Paint(); } public interface ISurface { void Paint(); } public class SampleClass : IControl, ISurface { // 显式接口实现 void IControl.Paint() { Console.WriteLine("IControl.Paint"); } void ISurface.Paint() { Console.WriteLine("ISurface.Paint"); } // 普通方法 public void Paint() { Console.WriteLine("SampleClass.Paint"); } }
使用显式接口实现:
SampleClass sample = new SampleClass(); IControl control = sample; ISurface surface = sample; sample.Paint(); // 输出:SampleClass.Paint control.Paint(); // 输出:IControl.Paint surface.Paint(); // 输出:ISurface.Paint
11. 异常处理
异常是在程序执行过程中发生的事件,它会中断正常的指令流。C#提供了强大的异常处理机制,使程序能够优雅地处理错误情况。
try-catch块
try-catch
块用于捕获和处理异常:
try { // 可能引发异常的代码 int a = 10; int b = 0; int result = a / b; // 会引发DivideByZeroException Console.WriteLine("结果: " + result); } catch (DivideByZeroException ex) { // 处理特定异常 Console.WriteLine("错误: 不能除以零"); Console.WriteLine("异常信息: " + ex.Message); } catch (Exception ex) { // 处理其他异常 Console.WriteLine("发生错误: " + ex.Message); }
try-catch-finally块
finally
块包含的代码无论是否发生异常都会执行,通常用于释放资源:
try { // 可能引发异常的代码 int[] numbers = { 1, 2, 3 }; Console.WriteLine(numbers[5]); // 会引发IndexOutOfRangeException } catch (IndexOutOfRangeException ex) { // 处理异常 Console.WriteLine("错误: 数组索引超出范围"); } finally { // 无论是否发生异常都会执行的代码 Console.WriteLine("finally块已执行"); }
using语句
using
语句用于确保实现了IDisposable
接口的对象在使用后被正确释放:
using (System.IO.StreamReader reader = new System.IO.StreamReader("file.txt")) { string content = reader.ReadToEnd(); Console.WriteLine(content); } // reader.Dispose()会自动调用
throw语句
throw
语句用于显式引发异常:
public void Divide(int a, int b) { if (b == 0) { throw new DivideByZeroException("除数不能为零"); } int result = a / b; Console.WriteLine("结果: " + result); } try { Divide(10, 0); } catch (DivideByZeroException ex) { Console.WriteLine("捕获异常: " + ex.Message); }
自定义异常
可以创建自定义异常类,继承自Exception
类或其派生类:
public class InvalidAgeException : Exception { public InvalidAgeException() : base("年龄无效") { } public InvalidAgeException(string message) : base(message) { } public InvalidAgeException(string message, Exception innerException) : base(message, innerException) { } } public class Person { private int age; public int Age { get { return age; } set { if (value < 0 || value > 150) { throw new InvalidAgeException("年龄必须在0到150之间"); } age = value; } } } try { Person person = new Person(); person.Age = 200; // 会引发InvalidAgeException } catch (InvalidAgeException ex) { Console.WriteLine("捕获异常: " + ex.Message); }
常见异常类
C#提供了许多内置的异常类,以下是一些常见的异常类:
异常类 | 描述 |
---|---|
Exception | 所有异常的基类 |
ArgumentException | 方法的参数无效 |
ArgumentNullException | 参数为null |
ArgumentOutOfRangeException | 参数超出范围 |
DivideByZeroException | 尝试除以零 |
IndexOutOfRangeException | 数组索引超出范围 |
InvalidCastException | 无效的类型转换 |
InvalidOperationException | 方法调用对对象当前状态无效 |
IOException | 发生I/O错误 |
NotSupportedException | 不支持的操作 |
NullReferenceException | 尝试引用null对象 |
OverflowException | 算术运算导致溢出 |
StackOverflowException | 执行堆栈溢出 |
TimeoutException | 操作超时 |
12. 文件操作
C#提供了丰富的类和方法用于文件和目录操作,这些类主要位于System.IO
命名空间中。
文件读写
读取文本文件
using System; using System.IO; class Program { static void Main() { string filePath = "example.txt"; // 检查文件是否存在 if (File.Exists(filePath)) { // 读取所有文本 string content = File.ReadAllText(filePath); Console.WriteLine("文件内容:"); Console.WriteLine(content); // 读取所有行 string[] lines = File.ReadAllLines(filePath); Console.WriteLine("n文件行数: " + lines.Length); // 使用StreamReader逐行读取 Console.WriteLine("n使用StreamReader逐行读取:"); using (StreamReader reader = new StreamReader(filePath)) { string line; while ((line = reader.ReadLine()) != null) { Console.WriteLine(line); } } } else { Console.WriteLine("文件不存在: " + filePath); } } }
写入文本文件
using System; using System.IO; class Program { static void Main() { string filePath = "output.txt"; // 写入所有文本(会覆盖现有文件) string content = "这是第一行。n这是第二行。n这是第三行。"; File.WriteAllText(filePath, content); Console.WriteLine("已写入文件: " + filePath); // 追加文本 string additionalContent = "n这是追加的行。"; File.AppendAllText(filePath, additionalContent); Console.WriteLine("已追加内容到文件: " + filePath); // 写入所有行 string[] lines = { "第一行", "第二行", "第三行" }; File.WriteAllLines("lines.txt", lines); Console.WriteLine("已写入行到文件: lines.txt"); // 使用StreamWriter写入 Console.WriteLine("n使用StreamWriter写入:"); using (StreamWriter writer = new StreamWriter("streamwriter.txt")) { writer.WriteLine("第一行"); writer.WriteLine("第二行"); writer.WriteLine("第三行"); } Console.WriteLine("已使用StreamWriter写入文件: streamwriter.txt"); } }
二进制文件读写
using System; using System.IO; class Program { static void Main() { string filePath = "binarydata.bin"; // 写入二进制数据 using (BinaryWriter writer = new BinaryWriter(File.Open(filePath, FileMode.Create))) { writer.Write(123); // 整数 writer.Write(45.67); // 双精度浮点数 writer.Write("Hello, Binary World!"); // 字符串 writer.Write(true); // 布尔值 } Console.WriteLine("已写入二进制文件: " + filePath); // 读取二进制数据 using (BinaryReader reader = new BinaryReader(File.Open(filePath, FileMode.Open))) { int intValue = reader.ReadInt32(); double doubleValue = reader.ReadDouble(); string stringValue = reader.ReadString(); bool boolValue = reader.ReadBoolean(); Console.WriteLine("读取的整数值: " + intValue); Console.WriteLine("读取的双精度值: " + doubleValue); Console.WriteLine("读取的字符串值: " + stringValue); Console.WriteLine("读取的布尔值: " + boolValue); } } }
文件和目录操作
using System; using System.IO; class Program { static void Main() { // 创建目录 string directoryPath = "TestDirectory"; if (!Directory.Exists(directoryPath)) { Directory.CreateDirectory(directoryPath); Console.WriteLine("已创建目录: " + directoryPath); } // 创建子目录 string subDirectoryPath = Path.Combine(directoryPath, "SubDirectory"); if (!Directory.Exists(subDirectoryPath)) { Directory.CreateDirectory(subDirectoryPath); Console.WriteLine("已创建子目录: " + subDirectoryPath); } // 创建文件 string filePath = Path.Combine(directoryPath, "testfile.txt"); if (!File.Exists(filePath)) { File.WriteAllText(filePath, "这是一个测试文件。"); Console.WriteLine("已创建文件: " + filePath); } // 复制文件 string copyPath = Path.Combine(directoryPath, "testfile_copy.txt"); if (!File.Exists(copyPath)) { File.Copy(filePath, copyPath); Console.WriteLine("已复制文件到: " + copyPath); } // 移动文件 string movePath = Path.Combine(subDirectoryPath, "testfile_moved.txt"); if (!File.Exists(movePath)) { File.Move(copyPath, movePath); Console.WriteLine("已移动文件到: " + movePath); } // 删除文件 if (File.Exists(movePath)) { File.Delete(movePath); Console.WriteLine("已删除文件: " + movePath); } // 获取文件信息 FileInfo fileInfo = new FileInfo(filePath); Console.WriteLine("n文件信息:"); Console.WriteLine("文件名: " + fileInfo.Name); Console.WriteLine("完整路径: " + fileInfo.FullName); Console.WriteLine("文件大小: " + fileInfo.Length + " 字节"); Console.WriteLine("创建时间: " + fileInfo.CreationTime); Console.WriteLine("最后访问时间: " + fileInfo.LastAccessTime); Console.WriteLine("最后修改时间: " + fileInfo.LastWriteTime); // 获取目录信息 DirectoryInfo directoryInfo = new DirectoryInfo(directoryPath); Console.WriteLine("n目录信息:"); Console.WriteLine("目录名: " + directoryInfo.Name); Console.WriteLine("完整路径: " + directoryInfo.FullName); Console.WriteLine("创建时间: " + directoryInfo.CreationTime); Console.WriteLine("最后访问时间: " + directoryInfo.LastAccessTime); Console.WriteLine("最后修改时间: " + directoryInfo.LastWriteTime); // 列出目录中的文件和子目录 Console.WriteLine("n目录内容:"); Console.WriteLine("文件:"); foreach (FileInfo file in directoryInfo.GetFiles()) { Console.WriteLine(" " + file.Name + " (" + file.Length + " 字节)"); } Console.WriteLine("子目录:"); foreach (DirectoryInfo dir in directoryInfo.GetDirectories()) { Console.WriteLine(" " + dir.Name); } // 删除目录(必须为空) if (Directory.Exists(subDirectoryPath)) { Directory.Delete(subDirectoryPath); Console.WriteLine("n已删除子目录: " + subDirectoryPath); } // 递归删除目录及其内容 if (Directory.Exists(directoryPath)) { Directory.Delete(directoryPath, true); Console.WriteLine("已删除目录及其内容: " + directoryPath); } } }
文件路径处理
using System; using System.IO; class Program { static void Main() { // 获取当前目录 string currentDirectory = Directory.GetCurrentDirectory(); Console.WriteLine("当前目录: " + currentDirectory); // 组合路径 string fileName = "test.txt"; string fullPath = Path.Combine(currentDirectory, fileName); Console.WriteLine("完整路径: " + fullPath); // 获取路径信息 Console.WriteLine("n路径信息:"); Console.WriteLine("目录名: " + Path.GetDirectoryName(fullPath)); Console.WriteLine("文件名: " + Path.GetFileName(fullPath)); Console.WriteLine("不带扩展名的文件名: " + Path.GetFileNameWithoutExtension(fullPath)); Console.WriteLine("扩展名: " + Path.GetExtension(fullPath)); Console.WriteLine("根目录: " + Path.GetPathRoot(fullPath)); // 修改扩展名 string newPath = Path.ChangeExtension(fullPath, ".bak"); Console.WriteLine("n修改扩展名后的路径: " + newPath); // 获取临时文件名 string tempFileName = Path.GetTempFileName(); Console.WriteLine("临时文件名: " + tempFileName); // 获取临时路径 string tempPath = Path.GetTempPath(); Console.WriteLine("临时路径: " + tempPath); // 清理临时文件 File.Delete(tempFileName); Console.WriteLine("已删除临时文件: " + tempFileName); } }
13. 实用示例
下面是一个综合运用前面知识的实用示例:一个简单的学生管理系统。
using System; using System.Collections.Generic; using System.IO; // 学生类 public class Student { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } public double Grade { get; set; } public Student(int id, string name, int age, double grade) { Id = id; Name = name; Age = age; Grade = grade; } public override string ToString() { return $"ID: {Id}, 姓名: {Name}, 年龄: {Age}, 成绩: {Grade}"; } } // 学生管理类 public class StudentManager { private List<Student> students; private int nextId; public StudentManager() { students = new List<Student>(); nextId = 1; } // 添加学生 public void AddStudent(string name, int age, double grade) { Student student = new Student(nextId, name, age, grade); students.Add(student); nextId++; Console.WriteLine("学生添加成功: " + student); } // 查找学生 public Student FindStudent(int id) { foreach (Student student in students) { if (student.Id == id) { return student; } } return null; } // 更新学生信息 public bool UpdateStudent(int id, string name, int age, double grade) { Student student = FindStudent(id); if (student != null) { student.Name = name; student.Age = age; student.Grade = grade; Console.WriteLine("学生信息更新成功: " + student); return true; } Console.WriteLine("未找到ID为 " + id + " 的学生"); return false; } // 删除学生 public bool DeleteStudent(int id) { Student student = FindStudent(id); if (student != null) { students.Remove(student); Console.WriteLine("学生删除成功: " + student); return true; } Console.WriteLine("未找到ID为 " + id + " 的学生"); return false; } // 显示所有学生 public void DisplayAllStudents() { if (students.Count == 0) { Console.WriteLine("没有学生记录"); return; } Console.WriteLine("所有学生信息:"); foreach (Student student in students) { Console.WriteLine(student); } } // 计算平均成绩 public double CalculateAverageGrade() { if (students.Count == 0) { return 0; } double sum = 0; foreach (Student student in students) { sum += student.Grade; } return sum / students.Count; } // 保存到文件 public void SaveToFile(string filePath) { try { using (StreamWriter writer = new StreamWriter(filePath)) { foreach (Student student in students) { writer.WriteLine($"{student.Id},{student.Name},{student.Age},{student.Grade}"); } } Console.WriteLine("数据已保存到文件: " + filePath); } catch (Exception ex) { Console.WriteLine("保存文件时出错: " + ex.Message); } } // 从文件加载 public void LoadFromFile(string filePath) { try { if (File.Exists(filePath)) { students.Clear(); nextId = 1; using (StreamReader reader = new StreamReader(filePath)) { string line; while ((line = reader.ReadLine()) != null) { string[] parts = line.Split(','); if (parts.Length == 4) { int id = int.Parse(parts[0]); string name = parts[1]; int age = int.Parse(parts[2]); double grade = double.Parse(parts[3]); Student student = new Student(id, name, age, grade); students.Add(student); if (id >= nextId) { nextId = id + 1; } } } } Console.WriteLine("数据已从文件加载: " + filePath); } else { Console.WriteLine("文件不存在: " + filePath); } } catch (Exception ex) { Console.WriteLine("加载文件时出错: " + ex.Message); } } } // 主程序 class Program { static void Main() { StudentManager manager = new StudentManager(); string filePath = "students.txt"; // 尝试从文件加载数据 manager.LoadFromFile(filePath); while (true) { Console.WriteLine("n学生管理系统"); Console.WriteLine("1. 添加学生"); Console.WriteLine("2. 查找学生"); Console.WriteLine("3. 更新学生信息"); Console.WriteLine("4. 删除学生"); Console.WriteLine("5. 显示所有学生"); Console.WriteLine("6. 计算平均成绩"); Console.WriteLine("7. 保存数据"); Console.WriteLine("8. 退出"); Console.Write("请选择操作: "); string choice = Console.ReadLine(); try { switch (choice) { case "1": // 添加学生 Console.Write("请输入学生姓名: "); string name = Console.ReadLine(); Console.Write("请输入学生年龄: "); int age = int.Parse(Console.ReadLine()); Console.Write("请输入学生成绩: "); double grade = double.Parse(Console.ReadLine()); manager.AddStudent(name, age, grade); break; case "2": // 查找学生 Console.Write("请输入学生ID: "); int findId = int.Parse(Console.ReadLine()); Student student = manager.FindStudent(findId); if (student != null) { Console.WriteLine("找到学生: " + student); } else { Console.WriteLine("未找到ID为 " + findId + " 的学生"); } break; case "3": // 更新学生信息 Console.Write("请输入学生ID: "); int updateId = int.Parse(Console.ReadLine()); Console.Write("请输入新的学生姓名: "); string newName = Console.ReadLine(); Console.Write("请输入新的学生年龄: "); int newAge = int.Parse(Console.ReadLine()); Console.Write("请输入新的学生成绩: "); double newGrade = double.Parse(Console.ReadLine()); manager.UpdateStudent(updateId, newName, newAge, newGrade); break; case "4": // 删除学生 Console.Write("请输入学生ID: "); int deleteId = int.Parse(Console.ReadLine()); manager.DeleteStudent(deleteId); break; case "5": // 显示所有学生 manager.DisplayAllStudents(); break; case "6": // 计算平均成绩 double average = manager.CalculateAverageGrade(); Console.WriteLine("学生平均成绩: " + average.ToString("F2")); break; case "7": // 保存数据 manager.SaveToFile(filePath); break; case "8": // 退出 Console.Write("是否保存数据? (y/n): "); string saveChoice = Console.ReadLine(); if (saveChoice.ToLower() == "y") { manager.SaveToFile(filePath); } Console.WriteLine("感谢使用学生管理系统,再见!"); return; default: Console.WriteLine("无效的选择,请重新输入"); break; } } catch (FormatException) { Console.WriteLine("输入格式错误,请输入有效的数字"); } catch (Exception ex) { Console.WriteLine("发生错误: " + ex.Message); } } } }
这个学生管理系统演示了C#的许多基础语法和特性,包括:
- 类和对象的定义与使用
- 集合(List
)的使用 - 文件读写操作
- 异常处理
- 用户输入处理
- 控制流语句(switch, while, if等)
- 方法定义与调用
14. 学习资源与进阶路径
在线学习资源
Microsoft Learn (原Microsoft Docs)
- 网址:https://learn.microsoft.com/zh-cn/dotnet/csharp/
- 描述:微软官方的C#文档,包含完整的语言参考、教程和API文档
C# Station
- 网址:https://www.csharp-station.com/
- 描述:提供C#教程、文章和代码示例
TutorialsTeacher
- 网址:https://www.tutorialsteacher.com/csharp
- 描述:提供结构化的C#教程,从基础到高级
W3Schools C# Tutorial
- 网址:https://www.w3schools.com/cs/
- 描述:简单易懂的C#教程,适合初学者
Codecademy - Learn C#
- 网址:https://www.codecademy.com/learn/learn-c-sharp
- 描述:交互式C#课程,通过实践学习
视频教程
YouTube - C# 教程
- 频道推荐:freeCodeCamp.org, Programming with Mosh, Brackeys
- 描述:免费的C#视频教程,从基础到高级
Udemy - C# 课程
- 课程推荐:C# Basics for Beginners: Learn C# Fundamentals by Coding
- 描述:付费但质量高的C#课程,通常有折扣
Pluralsight - C# 路径
- 网址:https://www.pluralsight.com/paths/csharp
- 描述:结构化的C#学习路径,需要订阅
书籍推荐
《C# 9.0 in a Nutshell》 by Joseph Albahari and Ben Albahari
- 描述:全面的C#参考书,适合有一定基础的开发者
《Head First C#》 by Andrew Stellman and Jennifer Greene
- 描述:以视觉化和互动方式介绍C#,适合初学者
《C# 8.0 and .NET Core 3.0》 by Mark J. Price
- 描述:现代C#和.NET Core开发实践指南
《Effective C#》 by Bill Wagner
- 描述:提高C#编程技巧的最佳实践
实践项目
控制台应用程序
- 描述:创建简单的控制台应用,如计算器、待办事项列表等
Windows Forms 应用程序
- 描述:开发带有图形用户界面的桌面应用程序
ASP.NET Core Web 应用
- 描述:构建Web应用程序和API
Unity 游戏开发
- 描述:使用C#和Unity引擎开发游戏
进阶学习路径
深入学习C#语言特性
- 主题:LINQ、委托、事件、异步编程、反射等
学习.NET框架
- 主题:.NET Core/.NET 5+、ASP.NET Core、Entity Framework Core等
设计模式
- 主题:单例模式、工厂模式、观察者模式等常见设计模式
软件架构
- 主题:分层架构、微服务、领域驱动设计等
性能优化
- 主题:内存管理、算法优化、并发编程等
社区与支持
Stack Overflow
- 网址:https://stackoverflow.com/
- 描述:程序员问答社区,可以提问和查找C#相关问题
Reddit - r/csharp
- 网址:https://www.reddit.com/r/csharp/
- 描述:C#相关的讨论社区
GitHub
- 网址:https://github.com/
- 描述:查找开源C#项目,参与贡献
Microsoft Q&A
- 网址:https://learn.microsoft.com/en-us/answers/products/dotnet
- 描述:微软官方的技术问答社区
通过本指南的学习,你已经掌握了C#的基础语法和核心概念。编程是一个不断学习和实践的过程,希望你能继续深入探索C#和.NET的世界,成为一名优秀的开发者!