C# 基础知识
一、变量和常量
在 C# 中,变量和常量是程序中存储和使用数据的重要组成部分
(一) 变量(Variables)
- 定义:变量是用于存储和表示数据值的一种标识符。在 C# 中,变量必须先声明后使用。
- 类型:变量具有特定的数据类型,例如整数、浮点数、布尔值、字符等。在声明变量时,必须指定其类型。
- 赋值:变量可以通过赋值操作来存储数据。赋值操作使用等号(=)将数据值分配给变量。
- 作用域:变量具有作用域,即其可访问的范围。通常,变量的作用域由其声明的位置决定,可以是全局的或局部的。
- 生命周期:变量的生命周期是指其存在的时间范围。局部变量的生命周期通常在其声明所在的块结束时结束,而全局变量的生命周期则在整个程序运行期间。
- 变量的本质是代表了一段可操作的内存空间。我们可以将变量视为内存的符号化表示,当程序需要使用内存时,我们可以定义某种类型的变量。编译器会根据变量的数据类型分配一定大小的内存空间,程序可以通过变量名来访问这些内存
// 示例:定义和使用变量 int age; // 声明一个名为 age 的整数型变量 age = 25; // 赋值操作,将 25 存储到 age 变量中 Console.WriteLine(age); // 输出变量的值 // 变量作用域示例 { int localVar = 10; Console.WriteLine(localVar); // 局部变量在其声明的块内可见 } // Console.WriteLine(localVar); // 这里将会引发编译错误,因为 localVar 在此处不可见
(二) 常量(Constants)
- 定义:常量是一种特殊的变量,其值在程序执行期间保持不变。
- 声明:在 C# 中,常量通过 const 关键字声明,并且必须在声明时进行初始化,初始化后无法修改。
- 作用域:常量的作用域与变量类似,可以是全局的或局部的。
- 命名规则:通常,常量的命名采用大写字母,并使用下划线分隔单词,以提高可读性。
// 示例:定义和使用常量 const double PI = 3.14159; // 声明一个名为 PI 的常量,并初始化为 3.14159 Console.WriteLine(PI); // 输出常量的值 // 常量作用域示例 { const int HoursInDay = 24; Console.WriteLine(HoursInDay); // 常量在其声明的块内可见 } // Console.WriteLine(HoursInDay); // 这里将会引发编译错误,因为常量在此处不可见
(三) 对比
特性 |
变量 |
常量 |
可变性 |
可变的,值可以在程序执行期间改变 |
不可变的,值在初始化后不能修改 |
赋值 |
值可以多次赋给变量 |
值只能在初始化时赋给常量 |
类型 |
类型可以是任意数据类型 |
类型可以是任意数据类型 |
作用域 |
可以具有全局或局部作用域 |
可以具有全局或局部作用域 |
生命周期 |
生命周期取决于作用域 |
生命周期取决于作用域 |
内存空间 |
代表一段可操作的内存空间 |
代表一段可操作的内存空间 |
值的确定时机 |
值可以在运行时确定 |
值必须在编译时确定 |
编译时行为 |
编译器不会对变量的值进行优化或检查 |
编译器会在编译时对常量的值进行优化或检查 |
使用场景 |
用于存储和操作可变的数据 |
用于表示固定的、不变的数据或值 |
二、成员变量、局部变量、全局变量
特性 |
成员变量 |
局部变量 |
全局变量 |
声明位置 |
在类或结构体中声明 |
在方法或代码块内部声明 |
在命名空间或文件范围内声明 |
作用域 |
整个类或结构体 |
仅在声明的方法或代码块内可见 |
整个命名空间或文件 |
访问权限 |
通常具有 private、protected、public 等 |
无法从定义它们的代码块之外直接访问 |
可以在命名空间或文件中的任何地方访问 |
生命周期 |
与类或结构体的实例相同 |
仅在声明的方法或代码块的执行期间有效 |
整个程序的执行期间 |
默认初始值 |
取决于类型,默认为 null 或对应类型的零值 |
不确定,未赋值的局部变量不具有默认值 |
取决于类型,默认为 null 或对应类型的零值 |
使用场景 |
用于表示类或结构体的属性和状态 |
用于临时存储方法或代码块内部的数据 |
用于在整个程序中共享的数据 |
注意: C# 中虽然可以在命名空间或文件范围内声明变量,这些变量可以在声明它们的命名空间或文件的范围内被访问,但它们并不是严格意义上的全局变量。这是因为在 C# 中,并没有一种特定的语言结构或关键字来定义全局变量,它们所声明的变量仍然受限于其所在的命名空间或文件的范围。
C# 中的静态变量(static variables)可能会被误认为是全局变量,因为它们在整个类的范围内都可见和可访问。但实际上,静态变量是与类相关联的,而不是整个程序。它们仍然受到类的封装性的影响,并且需要通过类名来访问。
此外,C# 中还有一种可以在整个应用程序中共享的数据存储机制,那就是使用静态类和静态成员。静态类是一种特殊的类,它们不能被实例化,而静态成员是在静态类中声明的成员,它们在整个应用程序的生命周期内保持不变。虽然静态成员可以在整个应用程序中使用,但它们仍然是受到类的封装性的限制,并且需要通过类名来访问。
因此,尽管 C# 中没有严格意义上的全局变量,但可以通过静态变量、静态类和静态成员来实现在整个应用程序范围内共享的数据。
(一) 全局变量
C++ 中存在全局变量。全局变量是在函数外部声明的变量,在整个程序的范围内可见和可访问。与局部变量不同,全局变量的生命周期从程序的启动到程序的结束,即它们在程序的整个执行期间都存在。
全局变量可以在程序的任何地方使用,包括函数内部和外部。如果在函数内部声明了与全局变量同名的局部变量,则局部变量将会遮盖全局变量,使得在该函数内部引用该变量时使用的是局部变量而不是全局变量。
全局变量的使用应谨慎,因为它们会增加程序的复杂性,并且可能导致代码维护和调试的困难。通常情况下,推荐使用局部变量和类的成员变量来代替全局变量,以提高代码的可维护性和可读性。
三、成员变量与静态变量
特性 |
成员变量 |
静态变量 |
声明位置 |
在类或结构体中声明 |
在类中使用 static 关键字声明 |
作用域 |
整个类或结构体 |
整个类或结构体 |
访问权限 |
通常具有 private、protected、public 等 |
与访问修饰符相关 |
生命周期 |
与类或结构体的实例相同 |
整个程序的执行期间 |
默认初始值 |
取决于类型,默认为 null 或对应类型的零值 |
取决于类型,默认为 null 或对应类型的零值 |
共享性 |
每个实例都有一份副本 |
所有实例共享同一份副本 |
访问方式 |
通过类的实例访问 |
可以通过类名直接 |
四、流程控制
- 条件语句:
- if语句
- switch语句
- 循环语句:
- for循环
- while循环
- do-while循环
- foreach循环
- 跳转语句:
- break语句
- continue语句
- return语句
- goto语句
- 异常处理:
- try-catch块
- finally块
- throw语句
- 锁定语句:
- lock语句
- 其他控制结构:
- using语句:用于资源管理,例如自动释放资源。
- checked和unchecked语句:用于控制整数溢出检查的行为。
五、枚举
在C#中,枚举(Enum)是一种用于定义命名常量集合的数据类型。枚举允许您为一组相关的常量分配友好的名称,使得代码更易读和维护。
(一) 优点
- 易读性和可维护性: 枚举提供了一种将常量与易于理解的名称关联的方法,使得代码更易读、更易理解和更易维护。
- 类型安全性: 枚举是类型安全的,因为它们限制了可以赋给变量的值,只能是在枚举中事先定义的值。
- 编译时检查: 编译器可以在编译时检查枚举值的有效性,这有助于捕获一些常见的错误,例如拼写错误或不正确的值。
- 自动赋值: 默认情况下,枚举值会自动赋予递增的整数值,使得代码更加简洁。
- 更好的可读性: 使用枚举可以让代码更清晰地表达意图,避免使用硬编码的数字或字符串。
(二) 缺点
- 限制: 枚举只能包含固定的预定义值,这可能会限制其在某些情况下的灵活性。如果需要支持动态的、可变的选项集合,枚举可能不适用。
- 类型转换: 有时候在不同的数据类型之间进行枚举值的转换可能会显得有些繁琐,需要进行显式的类型转换。
- 不利于扩展: 如果需要向枚举中添加新值,可能会涉及到更改现有的代码,特别是如果枚举类型被用于外部API或持久化存储。
- 可能导致紧耦合: 当枚举类型用于方法参数、返回值或成员变量时,可能导致类之间的紧耦合,使得代码难以维护和测试。
六、NameSpace
C# 中的命名空间(Namespace)是一种用于组织和管理代码的机制。它们提供了一种将相关的类、接口、结构体和其他类型组织在一起的方式,以便更好地管理项目的结构和避免命名冲突。
通过命名空间,您可以将相关的代码组织在一起,从而使代码更易于理解、维护和重用。使用命名空间可以将代码划分为逻辑上相关的模块,使其更易于组织和管理。
在 C# 中,可以使用关键字 namespace 来定义命名空间。例如:
namespace MyNamespace { // 在这里定义相关的类、接口、结构体等 class MyClass { // 类的成员和方法 } }
在其他文件中,可以使用 using 关键字来引用特定的命名空间,从而在代码中使用其中定义的类型,而不必在每个使用时都完全限定其名称。例如:
using MyNamespace; class AnotherClass { void Method() { MyClass obj = new MyClass(); // 使用 MyNamespace 中的 MyClass } }
通过合理使用命名空间,可以更有效地组织和管理代码,从而提高代码的可读性和可维护性。
注意:命名空间在 C# 中是一种逻辑结构,而不是物理结构。它们用于逻辑上组织和管理代码,而不涉及文件系统或物理文件的结构。
在 C# 中,可以将一个或多个命名空间的定义放在一个或多个文件中,也可以将多个命名空间的定义放在同一个文件中。
七、Main() 方法
在C#中,Main() 方法是程序的入口点(Entry Point),也是程序执行的起点。当程序运行时,CLR(Common Language Runtime)会首先查找并执行名为Main()的方法。
Main() 方法具有以下特点:
- 静态方法:Main() 方法必须声明为静态方法,这是因为CLR在调用程序的入口点时,不需要创建类的实例,而是直接调用该类的静态方法。
- 特定签名:Main() 方法必须带有特定的签名,即可以是以下两种签名之一:
- static void Main():表示Main()方法不接收任何参数。
- static int Main(string[] args):表示Main()方法接收一个字符串数组参数,通常用于接收命令行参数。
- 返回类型:在.NET Core和.NET 5及以后的版本中,Main() 方法的返回类型可以是int,用于指示程序的退出状态码。通常情况下,返回 0 表示程序正常退出,而其他值表示程序异常退出。
- 命令行参数:如果Main()方法接受命令行参数,那么这些参数将会作为字符串数组传递给args参数。
- 程序的唯一入口点:每个C#应用程序都必须有且只能有一个Main()方法,它标志着程序的唯一入口点。如果有多个Main()方法,编译器会报错。
八、使用注释
在C#中,注释用于向代码添加说明、文档和备注,以提高代码的可读性和可维护性。C#支持三种类型的注释:
- 单行注释:以双斜杠(//)开头,用于注释单行代码或单行注释的一部分。
// 这是单行注释 int x = 10; // 这是对变量赋值的注释
- 多行注释:以斜杠加星号(/)开头,以星号加斜杠(/)结尾,用于注释多行代码或多行注释的一部分。
/* 这是多行注释 这是多行注释的第二行 */
- XML文档注释:以三个连续的斜杠(///)开头,用于编写XML文档注释,通常用于为类、方法、属性等成员添加文档说明。
/// <summary> /// 这是一个方法的文档注释 /// </summary> /// <param name="x">参数x的说明</param> /// <param name="y">参数y的说明</param> /// <returns>返回值的说明</returns> int Add(int x, int y) { return x + y; }
注释对编写清晰、易读的代码非常重要。它们不仅可以帮助其他开发人员理解代码的用途和工作方式,还可以在文档生成工具中自动生成文档。因此,在编写C#代码时,应该养成良好的注释习惯。
九、C# 预处理器指令
C#预处理器指令(Preprocessor Directives)用于在编译时根据条件包含或排除代码块,以及定义符号等操作。预处理器指令始终以井号(#)开头,放置在代码文件的最顶部。以下是一些常用的C#预处理器指令:
- #define 和 #undef:
- #define symbol: 定义一个符号,用于条件编译。
- #undef symbol: 取消定义一个符号。
- #if、#elif、#else 和 #endif:
- #if condition: 如果条件为真,则编译下面的代码。
- #elif condition: 如果前面的条件为假且当前条件为真,则编译下面的代码。
- #else: 如果前面的条件都为假,则编译下面的代码。
- #endif: 结束条件编译块。
- #warning 和 #error:
- #warning message: 在编译时生成一个警告消息。
- #error message: 在编译时生成一个错误消息,并终止编译过程。
- #line 和 #region:
- #line number "file": 修改编译器的行号和文件名指示。
- #region name 和 #endregion: 将代码块标记为可折叠的区域。
- #pragma:
- #pragma warning disable: 禁用指定的警告。
- #pragma warning restore: 恢复指定的警告。
这些预处理器指令可以让开发人员在编写代码时根据条件进行编译和排除,以及对编译过程中的警告和错误进行控制。这在实现跨平台兼容性、调试和代码维护时非常有用。
十、C# 编程准则
编写清晰、可读和可维护的C#代码是每个C#开发人员应该追求的目标。以下是一些常见的C#编程准则,可帮助您编写高质量的代码:
- 命名规范:
- 使用有意义且描述性的名称来命名变量、方法、类和其他成员。
- 遵循PascalCase(首字母大写)命名约定来命名类、方法和属性,例如MyClass、MyMethod。
- 遵循camelCase(首字母小写)命名约定来命名变量和参数,例如myVariable、myParameter。
- 避免使用缩写和简写,除非是广为人知的缩写。
- 代码布局:
- 使用缩进和空格来增强代码的可读性。
- 使用一致的代码格式,例如统一的大括号位置和缩进规则。
- 每行代码长度适中,避免过长的行,通常建议不超过80或120个字符。
- 注释和文档:
- 使用注释来解释代码的目的、逻辑和重要细节。
- 使用XML文档注释来为公共API添加文档注释,以便自动生成文档。
- 将注释保持最新和一致,确保它们与代码保持同步。
- 异常处理:
- 使用try-catch块捕获和处理异常,以确保程序的稳定性和可靠性。
- 只捕获和处理需要处理的异常,避免捕获过于宽泛的异常。
- 在catch块中记录异常信息,以便诊断和调试。
- 代码重用:
- 遵循DRY原则(Don't Repeat Yourself),避免重复编写相似的代码。
- 将通用的功能封装成方法、类或库,以便在多个地方重用。
- 使用继承、接口和组合等面向对象的技术来实现代码的重用。
- 性能优化:
- 在必要时进行性能优化,但不要过早优化。
- 使用性能高效的数据结构和算法。
- 避免频繁的内存分配和释放,尽量重用对象和资源。
- 测试和调试:
- 编写单元测试来验证代码的正确性和可靠性。
- 使用调试工具和日志记录来诊断和调试问题。
- 定期进行代码审查,以确保代码质量和一致性。
- 版本控制:
- 使用版本控制系统(如Git)来管理代码的版本历史和变更记录。
- 提交代码时编写有意义的提交信息,以便他人理解和追踪代码变更。
遵循这些编程准则可以帮助您编写更好的C#代码,提高代码的质量、可维护性和可读性,从而更轻松地开发和维护应用程序。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)