什么是变量?
想象一下,我们在做数学题的时候,是不是会有x和y,这个x和y就是数学题中的变量,用于记录中间结果。在程序中,变量就像一个带有标签的盒子,他能装入你想要保存的数据,你可以通过标签随时找到这个盒子,并随时查看并更换盒子里面的物体。
变量让我们的程序能够:
- 存储信息: 比如用户的年龄、计算结果、一段文字等。
- 操作信息: 我们可以读取变量里的值进行计算、比较或显示。
- 重复使用信息: 定义一次,可以在程序的不同地方多次使用。
数据类型:盒子能装什么?
光有盒子还不够,我们还需要规定这个盒子能装什么类型的东西。你不能把水装进一个纸盒子,对吧?
数据类型 (Data Type) 就是用来告诉计算机,这个变量(盒子)准备存储哪种类型的数据,以及它大概需要占用多少内存空间。
C#中有许多内置的数据类型,我们先学习几个最常用的:
- int (整数类型): 用于存储没有小数部分的整数。
- 例子:10, -5, 0, 10000
- 范围:大约 -21亿 到 +21亿 (足够大多数日常计数)。
- 内存占用:通常 4 个字节。
- double (双精度浮点数类型): 用于存储带有小数部分的数字。它是 C# 中最常用的表示小数的类型。
- 例子:3.14, -0.5, 123.456
- 精度:很高,大约 15-16 位小数。
- 内存占用:通常 8 个字节。
- 注意: 当你直接在代码里写一个小数时 (如 3.14),C# 默认会把它当作 double 类型。
- float (单精度浮点数类型): 也用于存储带有小数部分的数字,但精度比 double 低。
- 例子:3.14f, -0.5f (注意:需要在数字后面加上 f 或 F 后缀,告诉编译器这是 float 类型)。
- 精度:大约 6-9 位小数。
- 内存占用:通常 4 个字节。
- 何时使用? 当内存非常有限,或者明确知道不需要 double 的高精度时(比如在某些图形计算或游戏开发中)。通常我们优先使用 double。
- decimal (十进制类型):特别适用于金融或货币计算。它能非常精确地表示小数,避免 double 和 float 可能出现的微小误差(二进制浮点数表示法的固有问题)。
- 例子:10.99m, 500.00m (注意:需要在数字后面加上 m 或 M 后缀)。
- 精度:非常高,大约 28-29 位有效数字。
- 内存占用:通常 16 个字节。
- 何时使用? 任何涉及金钱或需要极高精度的计算。
- string (字符串类型): 用于存储一串文本(零个或多个字符)。
- 例子:"你好,世界!", "QcrTiMo", "" (空字符串), "123" (注意,这是文本字符串 "123",不是数字 123)。
- 注意: 字符串的值必须用双引号 " 包起来。
- char (字符类型): 用于存储单个字符。
- 例子:'A', '男', '!', ' ' (空格也是一个字符)。
- 注意: 字符的值必须用单引号 ' 包起来。
- bool (布尔类型): 用于存储真 (true) 或 假 (false) 这两种逻辑值。常用于表示条件判断的结果。
- 例子:true, false
- 注意: 只有这两个值,没有其他。
- Enums(枚举):枚举是一组命名的整数常量,能让你的代码更清晰、更易读、更易于维护,尤其是在处理一组相关的固定常量时。
声明、初始化和使用变量
前几天看到网友发的一张图片,觉得很有趣:

变量也和函数一样,必须要先声明变量,告诉编辑器你要创建一个什么类型的变量,以及它的名字是什么,我们才能让编辑器去使用它。
dataType variableName;
//基本语法
/*dataType 是上面讲的数据类型之一 (如 int, string, double)。
variableName 是你给变量起的名字(需要符合命名规则)。
分号 ; 结尾!*/
下面我给出几个例子:
int age; // 声明一个名为 age 的整数变量
string name; // 声明一个名为 name 的字符串变量
double salary; // 声明一个名为 salary 的 double 变量
bool isStudent; // 声明一个名为 isStudent 的布尔变量
此时此刻,我们只是让编辑器知道有这个变量的存在,但是我们没有给变量放入任何有意义的值。(或者说是一个默认值,比如 int 的默认值是 0,string 是 null,bool 是 false)
variableName = value;
//赋值基本语法
//= 是赋值运算符 (Assignment Operator),表示把右边的值赋给左边的变量。注意:它不是数学上的“等于”!
下面我也给出几个常见例子:
age = 30; // 把 30 赋给 age
name = "李四"; // 把 "李四" 赋给 name
salary = 5000.50; // 把 5000.50 赋给 salary
isStudent = false; // 把 false 赋给 isStudent</pre>
我们也可以在声明变量的时候就进行初始化,在声明变量的同时就给它赋予一个初始值。
dataType variableName = value; //基本语法
例子:
int age = 30;
string name = "王五";
double price = 99.9;
char grade = 'A';
bool isActive = true;
我们在给变量声明并赋值之后,就可以在代码中使用变量了。
例子:
int score = 100;
Console.WriteLine(score); // 输出变量 score 的值,即 100
string message = "欢迎学习 C#";
Console.WriteLine(message); // 输出变量 message 的值
// 变量的值可以改变
score = 95;
Console.WriteLine(score); // 输出 95
// 可以用变量的值给其他变量赋值
int finalScore = score;
Console.WriteLine(finalScore); // 输出 95
变量命名规范 (非常重要!)
良好的命名能让你的代码更加容易阅读和理解,而且在很长一段时间内你都能依靠命名知道这个变量或者函数的作用以及功能是什么。C#社区有着一些普遍遵循的约定:
- 必须以字母或下划线 _ 开头。 (虽然下划线开头合法,但不推荐用于普通局部变量)。
- 后面可以跟字母、数字、下划线。
- 不能包含空格或特殊符号 (如 +, -, *, /, @ 等)。
- 区分大小写。 age 和 Age 是两个不同的变量。
- 不能使用 C# 的关键字 (保留字)。 比如 int, string, class, namespace, if, else, while 等不能作为变量名。(Visual Studio 编辑器通常会用蓝色标出关键字)。
- 推荐使用 "驼峰命名法" (camelCase): 第一个单词首字母小写,后续每个单词的首字母大写。
- 例如:firstName, totalAmount, isComplete, studentAge
- 名字要有意义: 变量名应该能清晰地表达它存储的是什么数据。避免使用像 a, b, c, temp, data 这样过于笼统的名字(除非是在非常小的、临时的作用域内)。
反例 (不要这样做):
// int 1age; // 不能以数字开头
// string first name; // 不能包含空格
// double static; // 不能使用关键字
// bool _; // 不推荐(虽然合法)
// int a; // 名字无意义 (除非上下文非常清晰)
var 关键字 (隐式类型推断)
有时候,你很懒,不想自己写变量数据类型,你希望编辑器从赋值的表达式就能明显看出变量应该是什么类型。C# 提供了 var 关键字,让编译器自动推断变量的类型。
var variableName = value; //基本语法
var age = 30; // 编译器推断 age 是 int 类型
var name = "赵六"; // 编译器推断 name 是 string 类型
var salary = 8000.0; // 编译器推断 salary 是 double 类型 (因为 8000.0 是 double 字面量)
var price = 19.95m; // 编译器推断 price 是 decimal 类型 (因为有 m 后缀)
var isActive = true; // 编译器推断 isActive 是 bool 类型
使用 var 的注意点:
- 必须在声明时就初始化。 不能写 var x;,编译器不知道 x 是什么类型。
- 类型是固定的。 一旦编译器推断出类型,这个变量的类型就确定了,不能再赋其他类型的值。例如,var age = 30; age = "hello"; 这会报错。var 只是省去了你手动写类型名的麻烦,它不是动态类型。
- 什么时候用? 当变量类型从右边的赋值语句很明显能看出来时,使用 var 可以让代码更简洁。但如果类型不明显,或者显式写出类型能让代码更清晰,就不要用 var。
常量 (const)
如果你希望规定银行存钱汇率保持不变,或者在制作游戏中,你希望重力不随环境变化而变化,也不允许改变重力,那你应该把代表重力和汇率的变量声明为常量 (Constant)。
const dataType CONSTANT_NAME = value; //基本语法
/*使用 const 关键字。
常量必须在声明时就初始化。
常量的值一旦设定,就不能再修改。
习惯上,常量名使用帕斯卡命名法 (PascalCase,又被成为驼峰命名法) 或 全大写加下划线 (ALL_CAPS_SNAKE_CASE)(C# 更倾向于 PascalCase,但全大写也常见)。*/
const double Pi = 3.14159;
const double g = 9.8;
const int MaxLoginAttempts = 3;
const string DefaultErrorMessage = "发生未知错误";
// Pi = 3.14; // 错误!不能修改常量的值
Console.WriteLine($"圆周率是:{Pi}");
使用常量的好处:
- 提高可读性: 名字本身就说明了值的意义。
- 便于维护: 如果这个固定值需要在多处使用,将来只需要修改常量定义处即可,所有引用的地方都会自动更新。
- 防止意外修改: 编译器会保证它的值不被改变。
枚举 (Enums) - 让常量更有意义
什么是枚举 (Enum)?
想象一下,你在程序中需要表示一周中的某一天。你可以用整数来表示:0 代表星期一,1 代表星期二…… 6 代表星期日。
// 使用整数表示星期
int today = 0; // 0 代表星期一
if (today == 0)
{
Console.WriteLine("今天是星期一");
}
else if (today == 6) // 6 代表星期日
{
Console.WriteLine("今天是星期日");
}
// ... 其他判断
这种做法有什么问题呢?
- 可读性差: 数字 0, 1, 6 本身并没有明确的含义。过段时间再看代码,或者别人看你的代码,可能需要查注释或者回忆才能知道这些数字代表什么。我们称之为**“魔术数字 (Magic Numbers)”**,应该尽量避免。
- 容易出错: 你可能会不小心写成 if (today == 7),或者把星期一记成了 1。编译器不会发现这种逻辑错误。
- 维护困难: 如果以后决定用 1-7 而不是 0-6 来表示星期,你需要修改所有用到这些数字的地方。
枚举 (Enum) 就是为了解决这些问题而生的。枚举允许你为一组紧密相关的整数常量定义一套友好的、具有描述性的名字。
你可以把枚举看作是一种自定义的、特殊的整数类型,它的值只能是你在定义时列出的那几个命名常量 (枚举成员)。
使用枚举的好处:
- 提高可读性: 用有意义的名字(如 DayOfWeek.Monday)代替神秘的数字,代码意图一目了然。
- 增强类型安全: DayOfWeek 是一种类型,你不能随便把一个不相关的整数赋给它(虽然可以强制转换,但不推荐)。编译器可以检查类型错误。
- 易于维护: 如果需要修改,只需要修改枚举定义本身。代码中使用枚举成员的地方会自动更新。
- 便于协作: 团队成员都能理解枚举成员的含义。
- 与 switch 完美配合: 让 switch 语句更清晰
如何定义枚举 (enum 关键字)
枚举通常定义在命名空间 (namespace) 下,或者类 (class) 的内部。如果在类内部定义,访问它需要通过类名。通常建议定义在命名空间下,除非它与某个特定类紧密相关。
基本语法:
[访问修饰符] enum EnumName // 枚举名称,使用 PascalCase
{
Member1, // 枚举成员,使用 PascalCase
Member2,
Member3,
// ... 其他成员
// 可以显式指定整数值
MemberX = 10,
MemberY, // 如果不指定,会是前一个成员的值 + 1 (即 11)
MemberZ = 20
}
- [访问修饰符]: 和类一样,可以是 public, internal (默认) 等。public 表示可以在任何地方访问。
- enum: 关键字,表示正在定义一个枚举类型。
- EnumName: 你给枚举起的名字,遵循 PascalCase 命名规范。名字应该是单数形式,表示一个成员代表该类型的一个实例,例如 DayOfWeek, OrderStatus, Color。
- { ... }: 大括号内是枚举的成员列表。
- Member1, Member2, ...: 枚举的成员,也叫枚举常量。它们是这个枚举类型的有效值。命名也遵循 PascalCase。
- 每个成员代表一个底层的整数值 (underlying value)。
- 默认情况下:
- 第一个成员 (Member1) 的值是 0。
- 后续每个成员的值是前一个成员的值加 1。
- 你可以显式地为成员指定整数值,使用 = 赋值。
- 如果某个成员指定了值,后面未指定值的成员会在此基础上递增。
- 不同的成员可以拥有相同的整数值(虽然不太常见,除非有特殊意图)。
- 枚举的底层类型默认是 int。你也可以显式指定其他整数类型(如 byte, short, long),方法是在枚举名后面加冒号和类型:enum Status : byte { ... }。
代码示例:定义表示方向和订单状态的枚举
// 定义在命名空间下
namespace EnumsDemo
{
// 公开的枚举,表示方向
public enum Direction
{
North, // 默认值 0
East, // 默认值 1
South, // 默认值 2
West // 默认值 3
}
// 公开的枚举,表示订单状态,并显式指定值
public enum OrderStatus : byte // 底层类型指定为 byte
{
Pending = 1, // 待处理
Processing = 5, // 处理中
Shipped = 10, // 已发货
Delivered = 15, // 已送达
Cancelled = 20, // 已取消
Returned = 20 // 已退回 (可以有相同的值)
}
internal class Program
{
// ... Main 方法 ...
}
}
如何使用枚举
声明枚举类型的变量:
// 声明 Direction 类型的变量
Direction myDirection;
// 声明 OrderStatus 类型的变量并初始化
OrderStatus currentStatus = OrderStatus.Processing; // 直接使用 枚举名.成员名
将枚举成员赋值给变量:
myDirection = Direction.North;
Console.WriteLine(myDirection); // 输出 "North" (默认调用 ToString() )
比较枚举变量:
if (currentStatus == OrderStatus.Processing)
{
Console.WriteLine("订单正在处理中...");
}
if (myDirection != Direction.South)
{
Console.WriteLine("当前方向不是南方。");
}
在 switch 语句中使用枚举 (非常常用):
这能极大地提高代码可读性。
// 根据 currentStatus 执行不同操作
switch (currentStatus)
{
case OrderStatus.Pending:
Console.WriteLine("订单待处理,请尽快付款。");
break;
case OrderStatus.Processing:
Console.WriteLine("订单处理中,请耐心等待。");
break;
case OrderStatus.Shipped:
Console.WriteLine("订单已发货,请注意查收。");
break;
case OrderStatus.Delivered:
Console.WriteLine("订单已送达,欢迎再次光临!");
break;
case OrderStatus.Cancelled:
case OrderStatus.Returned: // 处理已取消或已退回
Console.WriteLine("订单已取消或退回。");
break;
default: // 虽然理论上 OrderStatus 变量只应包含定义的值,但以防万一
Console.WriteLine("未知的订单状态。");
break;
}
看到没?比起 switch (statusInt) 然后 case 1:, case 5:, case 10:,使用枚举的 switch 清晰多了!
枚举与整数之间的转换
虽然枚举的主要目的是使用有意义的名字,但有时你可能需要在枚举成员和其底层的整数值之间进行转换。
获取枚举成员的整数值 (显式转换):
int northValue = (int)Direction.North; // northValue 会是 0
byte shippedValue = (byte)OrderStatus.Shipped; // shippedValue 会是 10 (因为 OrderStatus 底层是 byte)
Console.WriteLine($"Direction.North 的整数值是: {northValue}");
Console.WriteLine($"OrderStatus.Shipped 的整数值是: {shippedValue}");
将整数值转换为枚举成员 (显式转换):
这种转换不会检查该整数值是否对应一个有效的、已定义的枚举成员!如果整数值无效,转换不会报错,但得到的枚举变量可能不是你期望的。
int directionCode = 2;
Direction calculatedDirection = (Direction)directionCode; // calculatedDirection 会是 Direction.South
Console.WriteLine($"整数 2 对应的方向是: {calculatedDirection}");
byte statusValue = 15;
OrderStatus orderStatusFromValue = (OrderStatus)statusValue; // orderStatusFromValue 会是 OrderStatus.Delivered
Console.WriteLine($"整数 15 对应的订单状态是: {orderStatusFromValue}");
// 尝试转换一个无效的值
int invalidDirectionCode = 5;
Direction invalidDirection = (Direction)invalidDirectionCode;
Console.WriteLine($"整数 5 对应的方向是: {invalidDirection}"); // 输出 5 (因为它没有对应的名字)
// 在使用转换后的枚举前,最好验证其有效性
if (Enum.IsDefined(typeof(Direction), invalidDirection))
{
Console.WriteLine("这是一个有效的方向。"); // 这行不会执行
}
else
{
Console.WriteLine($"{invalidDirection} 不是一个已定义的有效方向!");
}
Enum.IsDefined(typeof(EnumType), value) 是一个静态方法,用于检查某个值是否是指定枚举类型的有效成员。
枚举与字符串之间的转换
获取枚举成员的名称 (ToString() 或 Enum.GetName):
string northName = Direction.North.ToString(); // northName 会是 "North"
string processingName = Enum.GetName(typeof(OrderStatus), OrderStatus.Processing); // processingName 会是 "Processing"
Console.WriteLine($"Direction.North 的名称是: {northName}");
Console.WriteLine($"OrderStatus.Processing 的名称是: {processingName}");
ToString() 是最常用的方法。
将字符串转换为枚举成员 (Enum.Parse 或 Enum.TryParse):
这在处理用户输入或从配置文件读取设置时很有用。
- Enum.Parse(typeof(EnumType), stringValue, ignoreCase):
- 尝试将字符串解析为指定枚举类型的成员。
- 如果解析失败(字符串无效),会抛出 ArgumentException 异常。
- ignoreCase (bool): 是否忽略大小写进行匹配 (可选,默认为 false)。
- 返回值是 object 类型,需要强制转换为你需要的枚举类型。
- Enum.TryParse<TEnum>(stringValue, ignoreCase, out TEnum result): (泛型版本,更推荐)
- 尝试将字符串解析为指定枚举类型 TEnum 的成员。
- TEnum: 你要解析成的枚举类型 (如 Direction, OrderStatus)。
- 如果解析成功,返回 true,并将结果存入 out 参数 result。
- 如果解析失败,返回 false,result 会被设为该枚举的默认值 (通常是值为 0 的那个成员,或者如果没有值为 0 的成员,就是 0 本身)。不会抛出异常,更安全。
- ignoreCase (bool): 是否忽略大小写 (可选)。
string directionInput = "East";
string statusInput = "shipped"; // 小写
string invalidInput = "Up";
// 使用 Enum.Parse (需要处理异常)
try
{
Direction parsedDirection = (Direction)Enum.Parse(typeof(Direction), directionInput);
Console.WriteLine($"使用 Parse 解析 \"{directionInput}\": {parsedDirection}"); // 输出 East
// 忽略大小写解析
OrderStatus parsedStatus = (OrderStatus)Enum.Parse(typeof(OrderStatus), statusInput, true); // true 表示忽略大小写
Console.WriteLine($"使用 Parse (忽略大小写) 解析 \"{statusInput}\": {parsedStatus}"); // 输出 Shipped
// 尝试解析无效输入 (会抛异常)
// Direction invalidParse = (Direction)Enum.Parse(typeof(Direction), invalidInput);
}
catch (ArgumentException ex)
{
Console.WriteLine($"解析时发生错误: {ex.Message}");
}
Console.WriteLine("--- 使用 TryParse (更安全) ---");
// 使用 Enum.TryParse<TEnum>
Direction tryParsedDirection;
if (Enum.TryParse<Direction>(directionInput, out tryParsedDirection)) // 尝试解析 "East"
{
Console.WriteLine($"使用 TryParse 解析 \"{directionInput}\" 成功: {tryParsedDirection}"); // 输出 East
}
else
{
Console.WriteLine($"使用 TryParse 解析 \"{directionInput}\" 失败。");
}
OrderStatus tryParsedStatus;
// 尝试忽略大小写解析 "shipped"
if (Enum.TryParse<OrderStatus>(statusInput, true, out tryParsedStatus))
{
Console.WriteLine($"使用 TryParse (忽略大小写) 解析 \"{statusInput}\" 成功: {tryParsedStatus}"); // 输出 Shipped
}
else
{
Console.WriteLine($"使用 TryParse 解析 \"{statusInput}\" 失败。");
}
Direction tryInvalidDirection;
if (Enum.TryParse<Direction>(invalidInput, out tryInvalidDirection)) // 尝试解析 "Up"
{
Console.WriteLine($"使用 TryParse 解析 \"{invalidInput}\" 成功: {tryInvalidDirection}");
}
else
{
Console.WriteLine($"使用 TryParse 解析 \"{invalidInput}\" 失败。"); // 这行会执行
}
动手实践:编写代码
现在我已经带领你学完了C#基本变量和数据类型,该你实践了!
现在打开你的Visual Studio和你刚刚创建的新项目。
修改Program.cs文件,尝试以下代码:
// 使用顶级语句 (如果你的 .NET 版本支持)
using System; // 仍然建议保留 using System; 因为会用到 Console
// --- 变量声明和初始化 ---
string studentName = "小明";
int studentAge = 18;
double studentHeight = 175.5; // 单位:厘米
char gender = '男';
bool isGraduated = false;
// 使用 var 声明变量
var className = "三年二班";
var tuitionFee = 5000.50m; // 使用 m 后缀表示 decimal
// --- 常量定义 ---
const string SchoolName = "阳光中学";
const int PassingScore = 60;
// --- 使用变量和常量 ---
// 使用 Console.WriteLine 输出变量信息
Console.WriteLine("学生信息:");
Console.WriteLine("姓名:" + studentName); // 传统字符串拼接
Console.WriteLine("年龄:" + studentAge);
Console.WriteLine("身高:" + studentHeight + " 厘米");
Console.WriteLine("性别:" + gender);
Console.WriteLine("是否毕业:" + isGraduated);
Console.WriteLine("班级:" + className);
Console.WriteLine("学费:" + tuitionFee + " 元");
Console.WriteLine("----------"); // 分隔线
// 使用字符串插值 (String Interpolation) - 更推荐的方式!
// 在字符串前面加上 $ 符号,然后可以直接在 {} 中嵌入变量名
Console.WriteLine($"学校:{SchoolName}");
Console.WriteLine($"及格分数线:{PassingScore}");
Console.WriteLine($"欢迎 {className} 的 {studentName} 同学!");
Console.WriteLine($"他的身高是 {studentHeight}cm,学费是 {tuitionFee:C}。"); // :C 是格式化输出为货币
// --- 修改变量的值 ---
studentAge = 19; // 小明长大了一岁
isGraduated = true; // 小明毕业了
Console.WriteLine("---------- 更新后 ----------");
Console.WriteLine($"姓名:{studentName}, 年龄:{studentAge}, 是否毕业:{isGraduated}");
// 尝试修改常量 (会报错,可以取消注释试试)
// SchoolName = "月光中学"; // 取消这行注释,编译会失败
// 让控制台暂停,方便查看输出
Console.WriteLine("\n按任意键退出...");
Console.ReadKey(); // 等待用户按键
运行结果:

代码解释:
- 我们声明了各种基本类型的变量 (string, int, double, char, bool) 并初始化。
- 我们使用 var 声明了 className 和 tuitionFee,注意 decimal 类型需要 m 后缀。
- 我们定义了两个常量 SchoolName 和 PassingScore。
- 我们演示了两种输出变量值的方式:
- 传统的字符串拼接 (+):将字符串和变量的值连接起来。
- 字符串插值 ($""):在字符串前加 $,然后在 {} 中直接写变量名,更简洁易读。强烈推荐使用这种方式!
- 在字符串插值中,还可以对变量进行格式化,比如 {tuitionFee:C} 会将 decimal 值格式化为本地货币样式。
- 我们修改了 studentAge 和 isGraduated 的值,并再次输出,展示了变量的可变性。
- 最后用 Console.ReadKey() 让程序暂停,直到用户按键才退出,方便我们在控制台窗口看到所有输出。
运行代码 (Ctrl + F5),看看输出结果是否符合预期。尝试修改变量的初始值,或者添加你自己的变量,然后重新运行。
Comments NOTHING