本文将介绍变量和常量(之前介绍过标识符,变量和常量就是标识符的用途之一)。好了,我们开始吧!
1. 变量
什么是变量呢?顾名思义,变量中的“变”字表示“变化”,说明它所代表的值不是恒定不变的;和变量相对应的就是常量,常量意味着一旦赋值就不再变化,关于常量我们会在第2节详述。因此,变量其实是一个占位符,它引用了一块内存地址,但它存储的值是可以变化的。举个例子说明,假设有一系列的盒子,每个盒子上贴有一个标签。那么,盒子本身相当于变量,而标签相当于变量名,而盒子里的东西则相当于该变量所代表的值。当我们给一个盒子贴了一个标签后,标签名就是相对固定了的,但盒子里的东西却是经常变的,比如我们拿走一支笔,放进去一个墨水瓶。如图4-1所示。
1.1 给变量命名
就像给盒子贴标签一样,标签上的文字一定要言简意赅,让人一眼就能知道盒子中的内容。另外,如果盒子有很多,那么相应的标签也会很多,这时就需要为标签的命名制定一个规则,比如“01-生活用品-化妆品”、“02-生活用品-洗漱类”就是一种不错的规则;反之,诸如“物品类1”、“物品类2”则是不好的命名规则,“物品”这个词本身过于宽泛,不能起到好的说明和描述作用。
和标签命名一样,变量名也要含义清晰,不易混淆,要能够描述变量所代表的用途,要避免词不达意。
下面是一些好的命名:
-
TaskItem
-
ItemName
-
PersonName
下面是一些不好的命名:
-
var1
-
someVar
1.2 声明变量
声明一个变量的语法为:数据类型 变量名;
要注意不要漏掉最后的分号(;
)。
例如,声明3个变量,分别是string
、int
、bool
类型,如下所示:
string yourName;
int booksNumber;
bool isRegister;
声明多个同类型的变量的语法如下:
string x, y, z;
如果不是同类型,则不能一起声明,下面的代码就是错误的:
string x, int y, double z;
编译时编译器会报如下错误:
应输入标识符;\"double\"是关键字
应输入标识符;\"int\"是关键字
因为编译器认为int
、double
和x
一样,都是标识符,且都被声明为string
类型,但编译器很快就发现问题了,因为int
、double
都是关键字,因此编译器不允许编译通过。
要改正这个问题也很简单,每一个都使用单独的语句即可,如下所示:
string name = "Tom";
bool success = true;
int height = 10;
声明变量的目的是使用变量存储数据,那如何给变量赋值呢?和很多其他语言一样,在C#中使用的是=
运算符为变量赋值。也可以声明变量后立即为它赋值,如下所示:
bool isSuccess;
isSuccess = true;
string name = "Tom";
也可以在同时声明多个同变量时有选择的赋值,如下所示:
int a = 1, b, c = 0;
1.3 变量的初始化
基于安全性考虑,C#对变量的初始化有一定要求:
-
所有的局部变量在被显式地初始化之前,都会被编译器当作未初始化,然后抛出编译期异常;
-
所有的字段级变量被编译器初始化为所属类型中等价于0的值。如布尔型的被初始化为
false
,数值型的被初始化为0
或者0.0
,所有的引用类型都被初始化为null
。举例说明,如代码清单4-1所示。
代码清单4-1 未赋值的局部变量
using System;
namespace ProgrammingCSharp4
{
class Person
{
private int Age;
public void SayHello()
{
string message = "Hello!";
Console.WriteLine(message);
}
}
}
上述代码中有两个变量,一个字段级变量Age
,一个局部变量message
,当我们编译这段代码的时候会产生如下错误:
使用了未赋值的局部变量\"message\"
这是因为我们没有给局部变量message
赋值初始化,而Age
变量是字段级变量,可以被编译器自动初始化为0
。知道了错误的原因,我们对上述代码进行修改,如代码清单4-2所示。
代码清单4-2 已赋值的局部变量
using System;
namespace ProgrammingCSharp4
{
class Person
{
private int Age;
public void SayHello()
{
string message = "My age is {0}";
Console.WriteLine(message, Age);
}
static void Main(string[] args)
{
Person person = new Person();
person.SayHello();
}
}
}
在上述代码中,我们使用字符串"My age is {0}"
初始化了局部变量message
,然后把message
的值和Age
变量的值一起输出到了控制台,这里的{0}
是个占位符,意味着它的位置会被Age
的值所取代。代码清单4-2运行的结果如下:
My age is 0
这也印证了字段级的变量会被编译器自动初始化,它被初始化为该类型的初始值0
。
提示:可以使用int myInt = new int();
来初始化局部变量,它等同于int myInt = 0;
表4-1显示了各种数据类型的默认值。
1.4 类型推断
从C# 3.0开始,C#引入了一个新的关键字var
,它表示一种隐式类型推断,编译器可以通过它的初始值来判断变量的具体类型。尤其需要注意的是,var
只能用于局部变量的声明,不能用于字段级变量的声明,并且使用var
声明的变量必须有初始值,这样编译器有判断其是否是真实变量的依据。
如代码清单4-3所示,如下两种功能是一样的。
代码清单4-3 使用var关键字
var i = 10; // 隐式类型
int i = 10; // 显式类型
我们将代码清单4-2使用var
关键字进行改写,如代码清单4-4所示。
代码清单4-4 使用var关键字改写代码清单4-2
using System;
namespace ProgrammingCSharp4
{
class Person
{
private int Age;
public void SayHello()
{
var message = "My age is {0}";
Console.WriteLine(message, Age);
}
static void Main(string[] args)
{
var person = new Person();
person.SayHello();
}
}
}
message
变量的初始值为一个字符串类型,因此编译器可以推断其类型为string
类型;person
变量的初始值为Person
类型,同理编译器可以断定person
变量为Person
类型。
1.5 变量的作用域
我们从中国移动的一句经典广告语开始:我的地盘我做主。变量的作用域即广告语中所说的“地盘”,出了变量的作用域便相当于离开了它的地盘,也就意味着该变量将变得不再“可见”,当然也就无法对它执行读取、赋值等操作了,实际上,从栈的角度来看,变量离开了作用域后,该变量将被弹出栈。我们来研究下这个“地盘”是如何确定的,确定“地盘”的规则如下:
-
类的字段所处的作用域等同于该字段所属类所在的作用域;
-
局部变量的作用域仅限于声明它的方法或循环体内部,以大括号
{}
为界。
变量的作用域如下图所示。
下面,我们来看一个例子,如代码清单4-5所示。
代码清单4-5 变量的作用域
using System;
namespace ProgrammingCSharp4
{
class AboutVariableScope
{
public string fieldVar; // 作用域和它所属的类的作用域相同
public void DoSomething()
{
// someVar 变量作用域仅限在 DoSomething() 方法内部,以 {} 为界
var someVar = "some value";
// someVars 变量作用域同 someVar 变量
int[] someVars = { 1, 2, 3, 4, 5, 6 };
// item 的作用域仅限 foreach 循环体内部,以 {} 为界
foreach (var item in someVars)
{
Console.WriteLine(item);
}
// 这里将无法访问 foreach 循环体内部定义的 item 变量,编译器会报错
Console.WriteLine(item); // 编译错误
// sampleObject 对象的 sampleValue 字段的作用域与 sampleObject 对象的作用域相同
SampleObject sampleObject = new SampleObject();
sampleObject.sampleValue = "sampleValue's scope";
}
}
class SampleObject
{
public string sampleValue;
}
}
代码中以//
开始的语句称为注释,主要是起说明和备注作用,注释语句会被编译器忽略,并不会被编译。
代码清单4-5中各变量的作用域情况已经在注释中说明了,建议大家亲自动手实践一下。
接下来,我们探讨作用域的重叠问题。从层次的角度来看C#代码,就是一层层的{}
嵌套,那么必然会存在变量作用域重叠的问题,这时候我们就说两者的作用域发生了冲突。冲突主要分为两种情况:
-
同一个作用域内,存在两个同名的变量,这里不关心变量的类型是否相同。如果存在这种情况,编译器将会报错,编译将无法继续;
-
局部变量和字段级变量同名,那么局部变量会将同名的字段级变量隐藏,就是说在局部变量的作用域内,局部变量的值覆盖了字段级变量的值。
针对以上两种情况,我们来看一下示例代码,如代码清单4-6所示。
代码清单4-6 作用域冲突
using System;
namespace ProgrammingCSharp4
{
class AboutVariableScope
{
public string fieldVar = "你好,世界!";
public void DoSomething()
{
var someVar = "some value";
var fieldVar = "我是 DoSomething() 方法内的局部变量";
int[] someVars = { 1, 2, 3, 4, 5, 6 };
foreach (var item in someVars)
{
string item = "作用域冲突了!";
Console.WriteLine(item);
}
Console.WriteLine(fieldVar);
Console.WriteLine(this.fieldVar);
}
}
}
我们来分析上面这段代码,第17行的item
变量位于foreach
循环体内,位于第15行的foreach
循环中已定义了变量item
,并且它和第17行的item
作用域相同,这符合前面说的第一种冲突条件,属于语法错误,编译器会及时产生错误异常,如下所示:
不能在此范围内声明名为\"item\"的局部变量,因为这样会使\"item\"具有不同的含义,而它已在"父级或当前"范围中表示其他内容了。
这类冲突编译器可以协助我们检查出来,但接下来的一种情况,则属于逻辑错误,并且很难发现。我们来看第7行,这里定义了一个字段级别的变量fieldVar
,它的值为:“你好,世界!”,但在另一个地方——DoSomething()
方法内部(即第11行),我们又定义了一个相同名称的局部变量fieldVar
,其值为:“我是DoSomething()
方法内的局部变量”,那么我们来看第21行——输出fieldVar
的值,它会输出字段变量fieldVar
还是局部变量fieldVar
的值呢?由于局部变量隐藏了字段变量,因此这里输出的是:“我是DoSomething()
方法内的局部变量”。如果要使用字段变量fieldVar
的值,则要像第22行那样使用this
关键字。this
关键字代表当前类的实例,可以看作当前类实例的别名,比如这里的this.fieldVar
代表的是字段变量fieldVar
,其值为:“你好,世界!”。
关于变量,我们就介绍到这里,其他章节还会对它做相应的补充,接下来我们一起看看变量的“对立面”:常量。
2. 常量
前面我们讲了变量,那么相对的就是常量了。顾名思义,常量就是一旦声明并且初始化就不再改变的数据。我们使用了一把锁来代表盒子里一旦放了东西就无法再改变了,如图4-3所示。
2.1 常量的特征及其作用
下面我们先来看看常量的特征及其作用。常量一般具有如下的特征:
-
常量必须在声明的时候就立即初始化,其值在初始化后将无法再进行更改;
-
常量必须使用显式类型声明,不能使用类型推断关键字
var
; -
常量可以在类、结构、接口中进行声明;
-
常量可以作为类、结构以及接口的字段,也可以是定义在类、结构中的方法内部的局部变量,事实上常量永远是静态的,虽然并没有使用
static
关键字(也不允许); -
常量无法接受变量的赋值,哪怕该变量是
static
并且是readonly
也不行,常量在初始化时只能使用另一个常量为它赋值,当然直接赋予一个具体的值更好;
为什么要有常量呢?它的作用如何?下面我们就来探讨这些问题。
-
常量一般用作某个具体值的替代物,只需修改常量的值,那么所有用到该常量的地方都不需要修改;
-
常量让一个值具有了具体意义,提高了代码的可读性和可维护性。
2.2 常量的声明
在C#中,常量是一个在程序运行时不可更改的值。常量在声明时必须初始化,并且一旦初始化后,不能再改变其值。常量通常使用const
关键字进行声明。
代码清单4-7是一个C#常量的例子:
代码清单4-7 常量的用法
using System;
class Program
{
// 声明常量
const double Pi = 3.14159;
const int MaxUsers = 100;
static void Main()
{
// 使用常量
Console.WriteLine("Pi的值是: " + Pi);
Console.WriteLine("系统最大用户数: " + MaxUsers);
// 你不能修改常量的值,例如:
// Pi = 3.14; // 编译错误
// MaxUsers = 200; // 编译错误
}
}
解释:
-
const double Pi = 3.14159;
:声明了一个常量Pi
,它的值是3.14159
。常量的值是不可修改的。 -
const int MaxUsers = 100;
:声明了一个常量MaxUsers
,其值为100
,表示系统允许的最大用户数。 -
在
Main
方法中,常量被用来输出其值。
如果尝试修改常量的值(如Pi = 3.14;
),编译器会报错,因为常量值是不可变的。常量通常用于存储那些在程序执行过程中不会改变的值,例如数学常数、配置设置、固定参数等。