首页 / C# 入门教程 / 变量与常量

C# 入门教程

变量与常量

本教程共 100 篇 · 第 8 篇 · 更新于 2026-07-31 · 约 9 分钟阅读

C#C# 入门教程变量常量varconst

8. 变量与常量

本节目标:分清变量、隐式类型 var、编译期常量 const 和只读字段 readonly,知道什么时候用哪个。

程序说白了就是”数据 + 处理数据”。数据存在哪?存在变量(variable)里。这一章先把”装数据的盒子”讲透。

什么是变量

变量是一块带名字的内存,用来存一个值。你给它起名、规定类型、存入数据,之后随时取用或改动。

namespace CSharpDemo;

int age = 18;
age = 19;            // 可以改
Console.WriteLine(age);

这里 age 是变量名,int 是类型,18 是初值。第二行把它改成 19,说明变量的值可变。

为什么需要”变量”这个概念?因为程序要处理的数据在运行前往往不知道。比如一个订单的金额、一个用户的年龄,只有在运行时才确定。如果所有值都写死,程序就只能算同一道题。变量让同一段代码能适配不同数据——这是程序”通用”的根基。你可以把变量想象成快递柜的格子:格子有编号(变量名),里面暂存一件包裹(值),随时取走换一件。

显式类型声明

上面就是显式声明:你明确写出类型 int。编译器据此分配合适的内存并检查类型是否匹配。常见基础类型有 int(整数)、double(小数)、string(文字)、bool(真假)。

显式声明的好处是清楚:读者一眼知道 age 装的是整数。在类型不那么一目了然、或者你想强调”这里就是 int”时,显式写最稳妥。

Note

C# 是**静态类型(static typing)**语言:每个变量一旦声明了类型,就只能装该类型的值。age = "十八"; 这种把字符串塞给 int 的写法会直接编译报错。这层检查在写代码时就帮你挡掉大量低级错误,而不是等运行到才崩。

变量怎么起名

起名不是小事。好的名字让代码自己会说话,差的名字让人猜半天。C# 的约定(也是本教程统一口径):

  • 局部变量、参数用 camelCase(首字母小写,如 agepeopleCount)。
  • 类名用 PascalCase(首字母大写,如 PersonCalculator)。
  • 用有意义的词,避免 atmpx1 这种天书。
int userAge = 18;          // 好:一眼知道是用户年龄
int a = 18;                // 差:a 是什么?
Tip

命名的一条心法:名字要回答”它装的是什么”,而不是”它是什么类型”。ageint1 好,priced 好。类型编译器管,名字是写给人看的。

var 隐式类型

如果你嫌写类型麻烦,且编译器能从右边推出来,可用 var 让编译器自己判断:

var name = "小明";      // 编译器推断为 string
var price = 9.99m;     // 推断为 decimal
var count = 10;        // 推断为 int

var 不是”没有类型”,而是”类型交给编译器猜”。上面三行,编译器分别认定它们是 stringdecimalint,跟手写一模一样。

var 是 C# 3.0 引入的,本意是配合”类型名很长”或”右边一看就知类型”的场景,减少噪音、提升可读性。但它从没有改变类型系统的本质:推断出来的类型从那一刻起就焊死了,你不能把别的东西赋给它。

Tip

var 的两个场景:一是右边写法已明显暴露类型(如 new List<string>());二是类型名太长,写了反而碍眼。但读起来类型不清时,宁可显式写,别为了省事牺牲可读性。

Note

var 声明时必须立刻赋值,否则编译器无从推断。写 var x; 会直接报错。

Warning

别在”右边也看不出类型”的复杂表达式上用 var,例如 var result = Compute(x);——读者得跳进 Compute 才知道 result 是什么。这种情况显式写类型更友好。滥用 var 是新手常见的可读性陷阱。

const 编译期常量

有些值永远不变,且写代码时就确定了,比如圆周率、税率。用 const 定义为编译期常量(compile-time constant)

const double Pi = 3.14159;
const int MaxRetry = 3;
// Pi = 3.14;  错误:常量不能重新赋值

const 有两个硬约束:第一,声明时必须给初值;第二,之后任何地方都不能改。它的价值在于”语义”——看到 const,你就知道这是铁板钉钉的定值,也防止别人手滑改掉。

Note

const 的值在编译时就要确定。所以 const 只能用于能直接写死的类型(数字、字符串、枚举等),不能把一个要运行时才算出的结果标成 const

为什么要有 const,而不直接用普通变量写死?因为普通变量随时可能被改,而 const 把”不可变”这件事刻进了类型系统。编译器还会把 const 直接内联到使用处,连读取字段的开销都没有。更关键的是:它向读代码的人(包括未来的你)宣告”这个值亘古不变”,省去一堆猜测。

readonly 只读字段

readonly(只读)和 const 都”不能改”,但区别关键:

  • const:编译期就定死,必须写死字面量。
  • readonly:运行期才能定,但一旦构造完就再也改不了。

readonly 用在类的字段(field)上,不能在方法里的局部变量用:

namespace CSharpDemo;

class BankAccount
{
    public readonly string AccountId;   // 开户后不可改
    public decimal Balance { get; set; }

    public BankAccount(string id)
    {
        AccountId = id;                 // 构造函数里可赋值
    }
}

var acc = new BankAccount("A1001");
// acc.AccountId = "B2002";  错误:构造完就锁死

账号一旦开户就不该变,用 readonly 正合适。它能接收运行时传进来的值(如参数 id),这点 const 做不到。

readonly 解决的是这样一类需求:值不是写死的,要在创建对象时根据外界确定,但确定之后终身不变。const 做不到(它必须写死),而普通字段又太松(谁都能改)。readonly 正好卡在中间,是”构造后锁死”的语义担当。

三者怎么选

需求写法
值会变,类型明显var x = ... 或显式 int x
值永远不变,且写代码时已知const
值构造后不变,但需运行时确定readonly 字段
Tip

经验法则:默认用普通变量;确定是全局定值用 const;类里”创建后不可改”的成员用 readonly。别一上来全写 var 然后把常量也写成变量,后期容易被人误改。

变量作用域:它在哪能用

变量不是”全局通行”的。它只在声明它的那对大括号(代码块)里有效,出了块就消失。这叫作用域(scope)

void Demo()
{
    int outer = 1;
    if (outer > 0)
    {
        int inner = 2;      // inner 只在这对 if 括号里存在
        Console.WriteLine(outer);
    }
    // Console.WriteLine(inner);  报错:inner 已超出作用域
}

理解作用域能帮你避免”变量哪来的”混乱,也让内存该释放时释放。局部变量在函数结束时随栈帧一起回收,这也是值类型常驻栈的原因之一(见第 9 章)。

一个综合示例

namespace CSharpDemo;

class Product
{
    public readonly int Id;
    public string Name { get; set; }
    public decimal Price { get; set; }

    public Product(int id, string name, decimal price)
    {
        Id = id;
        Name = name;
        Price = price;
    }
}

const decimal TaxRate = 0.13m;
var p = new Product(1, "笔记本", 9.99m);
var finalPrice = p.Price * (1 + TaxRate);
Console.WriteLine($"{p.Name} 含税价:{finalPrice}");

TaxRate 是编译期常量;Id 是构造后只读;pfinalPrice 是可变变量。三种各司其职。

变量不赋值能用吗

不能。C# 要求” definite assignment(明确赋值)“:局部变量在使用前必须先赋值,否则编译报错。这是 C# 比某些语言安全的地方——不会读到内存里的随机垃圾值。

int x;
Console.WriteLine(x);   // 报错:使用了未赋值的局部变量

字段(类里的变量)则不同:不赋值也有默认值(int 默认 0,bool 默认 false,引用默认 null)。但局部变量没有默认值,必须你给。

Tip

这个规则帮你避开一大类 bug。写变量时顺手给个初值(哪怕 int count = 0;),既是好习惯,也让编译器闭嘴。

const 能修饰所有类型吗

不能。const 只能用于”编译期就能确定的常量”:基础数值类型、string、枚举、null。你不能写 const DateTime Now = DateTime.Now;,因为 Now 要运行时才算。这类”运行期定值、之后不变”的需求,请用 static readonly

class Config
{
    public static readonly DateTime StartTime = DateTime.Now;
}

static readonly 在第一次用时初始化,之后锁死,正好补上 const 的短板。

命名常量,干掉”魔法数字”

3.140.13 这类直接写在公式里的数叫魔法数字(magic number):读的人不知道它代表啥。用 const 给它起个名字,语义立刻清晰:

const decimal TaxRate = 0.13m;
decimal total = price * (1 + TaxRate);   // 一眼看懂:含税
Tip

凡是会在多处用到、或含义不直观的固定值,优先定义成命名常量。这是几乎零成本的代码可读性提升。

常见疑问解答

问:const 和 readonly 到底选哪个? 一句话:值写死在代码里、永远不变 → const(如圆周率);值要运行时才确定、但构造后不变 → readonly(如账号 ID)。拿不准就问”这个值编译时知道吗”,知道用 const,不知道用 readonly。

问:var 会不会让类型变模糊、难维护? 只有当你滥用在”右边也看不出类型”的复杂表达式上才会。规范用法是右边明显(如 new List<int>() 或字面量),此时 var 反而更干净。类型不清的地方,老老实实显式写。

问:局部变量能写成 readonly 吗? 不能。readonly 只修饰类的字段。局部变量要”不可改”,C# 没有 readonly 局部;若需常量语义且编译期已知,用 const(但 const 局部也只能用于编译期常量)。

要点速记

  • 变量存可变数据;var 让编译器推断类型。
  • const 编译期定值,声明即赋值且不可改。
  • readonly 是构造后锁死的字段,可运行期赋值。
  • 三者按”是否要变、何时确定”来选。
  • 局部变量须先赋值,字段有默认值。

动手练一练

  1. 定义一个 Product 类,用 readonly int Id 存商品编号、decimal Price 存价格,并在构造函数里给 Id 赋值,验证构造后无法再改。
  2. 写一个 const decimal TaxRate = 0.13m;,然后用它计算 9.99m 含税价,体会 const 在公式里的用法。
  3. 故意把 var x; 单独写一行,看编译器报什么错,理解 var 必须立刻赋值的约束。

小结与下一步

变量装可变数据,var 让编译器推断类型,const 是写死的定值,readonly 是构造后锁死的字段。命名常量化解魔法数字,作用域决定变量在哪能用。分清楚这些,代码才既灵活又安全。

下一章深入一点:为什么同样是”变量”,有的赋值像复印、有的像共享?这就要讲值类型与引用类型。