Skip to content

⭐ 1.1 整数

整数的声明

程序里包含了各种类型的数据,整数是其中一种。现在请你环顾四周,从你身边找出3样可以用整数表示的量:人数、物品的个数、某个月的天数……这样的例子还有很多!

现在,如何把整数引入我们的程序中呢?请先创建一个新项目,或者打开我们在上一节中创建的 Program.cs 文件,删除其中的全部内容。

输入以下内容:

int a;

很简单地完成了,对吧?

那么这是在干什么呢?在讲故事之前经常会介绍人物。比如

《水浒传》第七回

智深问道:“那军官是谁?”众人道:“这官人是八十万禁军枪棒教头林武师,名唤林冲。”智深道:“何不就请来厮见。”

这里引入了一个新的人物林冲,他的身份(类型)是八十万禁军枪棒教头,所以可以这样表示:

八十万禁军枪棒教头 林冲;

同样的,也可以这样介绍鲁智深:

渭州经略府提辖 鲁达;

在这种介绍方法中,我们首先说明了要介绍的人的类型(Type),然后说明这个人的名字,最后以分号;结尾,代表句子的结束。

同时,除了林冲以外,别的人也可以担任“八十万禁军枪棒教头”这个职位。因此我们说林冲只是“八十万禁军枪棒教头”这种类型的一个实例(Instance)或者说对象(Object)。这种出场前的介绍就叫做声明(Statement)。

回到我们的程序。我们用int表示整数类型(Integral Type),用a表示这个整数的名称。这样就完成了对a这个变量的声明。

等等,为什么说a是一个“变量”,那有不变的量吗?是的,之所以说a是一个变量是因为我们可以在让它代表某个整数之后改变它的值。例如用a表示用户的年龄,经过一年以后,a应该增加1。

对于那些不发生变动的值(比如物理常数、版本号等),我们可以设置为常量,防止发生改动。方法也很简单,只需要在声明的开头加上const

const int a;

为什么不能声明一个变量,然后不修改它来达到常量的效果呢?

随着对象的增加,你最终可能会忘记哪些是可变的,哪些是不可变的。或者新加入团队的开发者可能不清楚哪些是不可变的从而误修改。而如果用const声明常量,尝试修改它会引发编译错误,降低了不小心修改的风险。

命名法

总之,实际情况总是比我们想象的更复杂,因此遵循一定的规则总是有益的。后续我们将了解到更多的规则,比如:

我可以把一个变量命名为int吗?

请将Program.cs中的内容修改为:

int int;

注意到,此时产生了错误提示,说明编译器不能将第二个int正确识别为一个整数变量的名称(即标识符)。这就好像给小猫起名“小猫”,介绍时说“我有一只小猫小猫”会让人感到困惑一样。通常我们在C#中这样命名:

  • 开头是字母或下划线_,不能是数字:Number_Location可以;3num不行。
  • 剩下部分可以是字母、数字和下划线:num3num_of_stock可以;i<3u^_^=-=不行。
  • 不能是已经有专门用途的名字(叫做“保留字”):intconst这些都不行。
  • 标识符应能清楚表达意思:agea更好、num_of_stocknos更好。
  • 能用汉字做标识符吗?-可以但不推荐。开发的某个环节有可能不支持unicode从而导致奇怪的问题(锟斤拷.jpg)。
    • 而且写代码时狂切输入法把shift按冒烟不仅影响效率还容易出错。
  • 能用拼音做标识符吗?-看情况。重要的是保证易于理解和区分。如果有同音词、涉及国际化等情况下就不推荐。

有两种比较知名的命名方法:

  • 帕斯卡命名法(PascalCase)。当标识符里有多个单词时,对每个单词首字母大写。
    • AgeOfUser
  • 驼峰命名法(camelCase)。当标识符里有多个单词时,从第二个单词开始首字母大写。
    • ageOfUser

从现在开始,请养成对变量使用驼峰命名法,对常量使用帕斯卡命名法的习惯。

提前了解

我们目前接触到的变量都使用驼峰命名法。当以后学习其他变量时,可能会有不同的规则哦。

整数的赋值

变量

下面我们声明一些整数类型的变量,然后让它们代表一些数值。在Program.cs文件中清除之前的内容,输入:

1
2
3
int a;
a = 10;
Console.WriteLine(a);

Note

你之前可能注意到了 ⚠ CS0168:声明了变量“a”,但从未使用过 这样的警告。这是编译器想提醒你删除不需要的变量。现在我们在第2行使用了这个变量,所以警告消失了。

Warning

注意WriteLine的括号中是不带引号的a。为什么上一节里需要加引号,而这里又不用了呢?我们即将在本章第5节介绍。

我们用这种方式让a代表整数10,换句话说,把整数10赋予了变量a,即赋值(Assignment)。

赋值是有顺序的:它一定是把等号右边的量给予左边!写成10 = a;是不可以的哦!调试并运行程序,就能在控制台看到a代表的数字——10了。如果想偷个懒,可以在声明变量的同时进行赋值,像这样:

int a = 10;
Console.WriteLine(a);

很方便吧!另外,前面我们说过变量可以修改,来试一下吧!按下 F5 之前,请你先猜一下结果:

1
2
3
4
int a = 10;
Console.WriteLine(a);
a = 20;
Console.WriteLine(a);

输出的结果是:第1行是10,第2行是20。与你的猜测一样吗?编译器看完代码第1行后,为a分配了10,然后第2行把a现在的值10输出到控制台;到了第3行,又把a改为了20,在第4行把a最终的值20输出控制台的第2行上。

现在,试一下把第1行和第3行调换:

1
2
3
4
a = 20;
Console.WriteLine(a);
int a = 10;
Console.WriteLine(a);

出现了错误 ❌CS0841:本地变量“a”在声明之前无法使用。也就是说,你得先把变量a介绍给编译器认识,再让编译器使唤它。

隐藏成就

你注意到了吗?我们没有跟编译器声明过 intConsole 之类的标识符,但却可以直接使用它们?!

如果你能想到这一点,你真的很厉害!其实这一堆东西的声明写在一个叫System的地方,不信的话,把鼠标移动到它们上等待一会,就能看到System.XXX的介绍。C#在我们的文件开头偷偷引入了System,所以编译器早就已经认识它们了!(正规说法是“隐式引用System命名空间”)

呃……一个变量可以声明两次或者更多次吗?这真是一个奇怪的问题!看下面的例子:

int a;
int a;

好吧,引发了 ❌CS0128:已在此范围定义了名为“a”的局部变量或函数 的错误。

看起来,编译器保证了同类型变量标识符的唯一性。这样一来,如果我们写代码时不小心用了前面使用过的名字,就会收到提醒。“同类型”?嗯,那我们就测试一下不同类型变量能不能使用同一个名字吧。

测验时间到!

Program.cs 中输入以下内容:

int a;
float a;

其中 float 是下一节要讲的浮点数类型的一种。即使类型不同,编译器还是报错了!请你思考一下这是为什么?

查看答案

当后面要使用变量a时,编译器不知道应该调用哪一个a。想象一下你要找李华,对着人群大喊一声“李华!”然后一堆人回头看你时,该是多么绝望的场景啊!

当然,在不同的项目中分别使用同样的变量名是可以的,在同一个项目的不同部分使用也没问题。除此以外,还有一些特殊情况下也允许使用相同的标识符。我们将在后续部分提到这些情况。

说了这么多,你有没有产生过诸如给整数变量赋一个不是整数的值,又或者赋一个超级无敌大值的想法呢?如果你有这样想过,说明你很适合负责项目的测试工作!因为在实际情况下,你永远也不知道用户会输入多奇怪的东西(比如头压着键盘睡着就可能导致输入天文数字,又或者输入一句骂人的话等等)。

题外话

如果你很闲,你可以试一下你的电脑登录密码最多可以输入几个字符。

看看下列代码:

int a = 0.5f;

首先可以肯定的是,0.5f显然不像个整数。编译器提示了一种新的错误 ❌CS0266:无法将类型“float”隐式转换为“int”。存在一个显式转换(是否缺少强制转换?)。

好多新概念啊,我们还是留到下一节再说吧。至少我们已经明白了不能随便给变量赋其他类型的值(一个萝卜一个坑)。接下来看这个:

int a = 30000000000000000000;

哈哈哈!整整20位数!什么?数错位数了?好吧,你可以在数字中插入下划线来分隔数位,它的作用和西方的 300,000,000 表示方法中的逗号,或者中国的 3 0000 0000 中的空格作用一样。

int a = 30_000_000_000_000_000_000;

现在这列火车一样长的数字引发了 ❌CS1021:整数常量太大 的编译错误!是的,计算机中的整数不像数学中的整数,它是有范围限制的。事实上,C#提供了好几种整数类型供我们选择:

类型 范围 占用空间
sbyte [-128, 127] 8 bit
byte [0, 255] 8 bit
short [-32768, 32767] 16 bit
ushort [0, 65535] 16 bit
int [-2147483648, 2147483647] 32 bit
uint [0, 4294967295] 32 bit
long [-9223372036854775808, 9223372036854775807] 64 bit
ulong [0, 18446744073709551615] 64 bit

这些类型可以分为有符号无符号两类,无符号整数类型因为不需要表示负数,所以最大值比有符号整数类型扩大了一倍。除了 byte 类型无前缀时表示无符号,有前缀s(signed)时表示有符号以外,其他3类都是以加了前缀u(unsigned)表示无符号种类。它们的使用方法和你已经学会的 int 完全一样!

sbyte value1;
long value2 = 1_000_000_000;

好的。为了防止数据超出范围,我们一律用 long 或者 ulong 类型可不可以呢?

占用空间一栏展示了为了表示一个这种类型的数需要的储存空间。注意是一个这种类型的数,而不是一个这个范围内的数。分配的空间只与声明的类型有关,和数值的大小无关——同样是0, long 类型的数字0占用的空间是 sbyte 类型的数字0的8倍。

也就是说

容器的大小与容器里装了多少水无关,与容器的类型(杯子、桶,还是游泳池)有关。

单看一个变量占用的空间差异也许无关痛痒。但是,对于企业来说,当它们拥有的百万至上亿量级的数量乘以这些渺小的差异时,影响的效益是巨大的。因此,按需选择数据类型是一种良好的习惯。

常量

相对于眼花缭乱的变量赋值,对常量赋值就简单多了,因为下面4种情况都是错的。请在你的 Program.cs 文件中测试一下这几种情况分别会引发什么样的错误。

//(1)声明后不提供值
const int a;

//(2)先声明后赋值
const int b;
b = 5;

//(3)声明后修改值
const int c = 5;
c = 6;

//(4)用变量初始化常量
int d1 = 5;
const int d2 = d1;

我们之前已经知道了,常量是不能修改的(情况3)。它还需要在声明时就必须指派一个值(情况1和2),并且这个值不能是一个变量(情况4)。下面2种情况是正确的:

//(5)声明时初始化
const int a = 5;

//(6)给变量赋一个常量
const int b1 = 5;
int b2 = b1;

上面的案例中出现了一个新词语——初始化(Initialization)。为什么给常量指派一个值时不说“赋值”呢?

在声明变量的时候,运行时会根据这个变量的类型在内存上分配一个量身定制的房间(即“内存分配”。我们刚刚才讨论过“房间”的大小问题)。数值可以立刻入住,也可以稍后入住,新来的数值甚至可以把原来的住户赶走。可以订了房间不去住,客房经理(编译器)只会发发牢骚(引发 ⚠ CS0168变量未使用警告);但是不能去空的房间找人干活,这样经理会发脾气的(引发 ❌CS0165使用未赋值变量错误)。这个入住的过程就叫做赋值。

而用 const 定义的常量,比如 const int a = 5; ,编译器读到这句话以后就会立刻把后面所有提到a的地方统统替换成5。这个机制与变量的内存分配和赋值大相径庭,所以C#文档中使用初始化而不是赋值来称呼。

也正是这个机制决定了情况1~情况4都会遇到错误(请你自己推理一下错误是如何引发的,然后查看相关资料验证你的推断)。

字面量

直接写出来的数字,比如int a = 3;里面的数字3是变量还是常量?

都不是!你自己在代码里直接写入的值叫做“字面量”(literals)。

最后,请完成挑战,然后进入下一节吧!

挑战

假如要开发一款游戏,涉及以下数据:

  • 软件UID(8位数字);
  • 武器颜色(以RGBA四值格式储存,每个值的范围都是[0, 255]);
  • 生命值(范围[0, 500]),初始值为200;
  • 玩家分数(范围[0, 100000])。

请选择合适的整数类型声明这些数据。

参考答案
//软件UID
const uint GameId = 12345678;//也可以用int

//武器颜色
byte armColorRed, armColorGreen, armColorBlue, armColorAlpha;

//生命值
ushort hp = 200;//也可以使用short, hp是惯用缩写,可以用于命名

//玩家分数
int playerScore;//也可以使用uint