购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.2
变量和类型

当然,我们能做的远不止打印文本。我们还可以在执行算法或任务时存储和操作数据。在C语言(以及大多数语言)中,数据存储在变量中,而变量是解决问题的有力工具。变量有类型,类型决定了可以存储哪些类型的数据。这两个概念在我提到的语句中都占有重要地位:声明和初始化。

变量是数值的占位符。变量可以存放简单的数值,如数字(班上有多少学生?我购物车中物品的总价是多少?),也可以存放更复杂的内容(这个学生叫什么名字?每个学生的成绩是多少?甚至是实际的复杂数值,如-1的平方根)。变量可以存储从用户处接收到的数据,还能让你在编写程序时无须重写程序本身就解决一般问题。

2.2.1 获取用户输入

我们很快就会探讨定义和初始化变量的细节,但让我们先运行一下为用户获取一些输入来创建动态输出的想法,而无须每次都重新编译程序。我们将回到“Hello,World”程序,并稍微升级一下。我们可以让用户告诉我们他们的名字,然后亲自向他们问好!

到目前为止,你已经看到了一个输出语句,即我们用于问候的printf()函数调用。还有一个对应的输入函数scanf()。你可以使用print/scan对来提示用户,然后等待他们输入答案。我们将答案存储在一个变量中。如果你用其他语言编写过程序,那么你对接下来的程序应该不会陌生。如果你是编程和C语言的新手,那么程序列表可能会有点密集或奇怪。没关系!输入这些程序,并在纠正错别字后让它们运行,是一种有用的学习方法。

很多编程都是经过深思熟虑的“剽窃”。这有点开玩笑,但也只是有点。编程的起点和人类学习口语的起点差不多:重复你看到(或听到)的东西,但不一定了解它的一切。如果你重复的次数足够多,你就会发现语言中固有的模式,并了解在哪些地方可以做出有用的改变。做出足够多有用的改变,你就会发现如何从头开始创造新的、有意义的东西。这就是我们的目标。

这个 ch02/hello2.c 程序只是你在探索编程道路上可以复制的另一段代码:

希望这个程序的结构看起来似曾相识。我们包含了标准I/O库,有一个main()函数,该函数有一个主体,在一对大括号内包含多个语句。不过,这个主体包含了几个新项目。让我们逐行查看。

下面是我们声明变量的第一个示例。变量的名称是“name”。它的类型是char,C语言用char来表示单个(ASCII)字符 。它也是一个数组,这意味着它可以依次存储多个char值。在我们的例子中,可以存储20个这样的值。有关数组的更多信息,请参见第4章。现在只需注意,只要长度小于20个字符,该变量就可以保存一个人的姓名。

这是一个相当标准的printf()调用——与我们在1.2.2节中使用的第一个程序非常相似。唯一有意义的区别是双引号内的最后一个字符。如果查看 hello.c verbose.c ,你会发现最后两个字符是反斜杠和字母“n”。这两个字符的组合(\n)代表一个“换行”字符。如果在最后加上\n,就等于打印了一行,随后对printf()的任何调用都将在下一行进行。相反,如果省略\n,终端中的光标就会停留在当前行上。如果你想打印一个表格,但每次只打印一个表格单元格,这就很方便了。或者,在我们的例子中,如果你想提示用户输入一些信息,然后允许他们在问题的同一行输入他们的回答。

这就是我在本节开头提到的新函数。函数scanf()可以“扫描”字符,并将其转换为C数据类型,如数字,或者本例中的字符数组。转换完成后,scanf()会将每个“东西”存储到一个变量中。因此,在这一行中,我们正在扫描一堆字符,并将它们存储到name变量中。我们将在2.3节中看看括号内的东西的奇怪语法。

最后,我们要打印问候语。同样,这看起来应该很熟悉,但现在我们有了更多奇怪的语法。如果%s与调用scanf()时的奇怪内容一样,那么恭喜你!你刚刚发现了一个非常有用的模式。这对字符正是C语言在打印或扫描字符数组时使用的字符。在C语言中,字符数组是一种非常常见的类型,因此它有一个更简单的名字:字符串。因此,在这对字符中使用了“s”。

那么name是怎么回事?扫描函数scanf()调用你输入的任何名称(不包括你按下的Return键 )并将其存储在内存中。我们的name变量包含这些字符的内存位置。当我们调用printf()时,我们的第一个参数("Well hello,%s!\n"部分)包含几个字面字符,如“Well”一词中的字符和一个字符串占位符(%s部分)。变量非常适合填充占位符。无论你输入的是什么名称,现在都会显示出来。

请注意,我们在问候语中加入了特殊换行符。这意味着我们将打印问候语,然后按“Return”键,这样终端中显示的任何其他内容都将进入下一行。

让我们运行程序,看看运行情况如何。你可以使用VS Code底部的“终端”选项卡,或者你平台上的“终端”或“命令”应用程序。你需要先用gcc进行编译,然后使用-o选项运行a.out或任何你选择的名称。你应该会得到与图2-1类似的结果。

图2-1:我们定制的Hello World输出

请注意,当你输入一个名称时,它会与要求你输入的提示出现在同一行。这正是我们省略换行符时想要的结果。但请尝试再次运行并输入不同的名称。你得到预期的结果了吗?再试第三次。这种响应用户输入的动态行为使得变量在计算机编程中变得非常宝贵。同一个程序可以根据不同的输入产生不同的输出,而无须重新编译。这种能力反过来又使计算机程序成为我们日常生活中不可或缺的。

2.2.2 字符串和字符

让我们再仔细看看char类型和它的近亲——字符数组char[],也就是我们常说的字符串。在C语言中声明变量,要同时给它一个名称和类型。最简单的声明如下:

在这里,我们创建一个名为response的变量,其类型为char。char类型可存储一个字符。例如,我们可以存储一个“y”或“n”。第5章将详细介绍内存地址和引用的细节,但现在只需记住,变量声明将在内存中留出足够的空间来存储一个你指定类型的变量。如果我们有一系列问题要问,那么就可以创建一系列变量:

每个变量都可以容纳一个字符。但同样,在使用变量时,不必事先预测或决定该字符的内容。内容可以变化。

C编译器决定你的源字符使用哪种编码。较早的编译器使用较早的ASCII 格式,而较新的编译器通常使用UTF-8。这两种编码都包括小写和大写字母、数字以及你在键盘上看到的大多数符号。要表示一个特定的字符而不是char类型的变量,可以使用单引号进行分隔。例如,'a'、'A'、'8'和'@'都是有效的。

特殊字符

字符也可以是特殊字符。C支持制表符和换行符。我们已经看到了换行符(\n),但表2-1中还列出了其他一些特殊字符。这些特殊字符使用“转义序列”进行编码,反斜杠被称为“转义字符”。

表2-1:C语言中的转义序列

注:这并不是一份详尽的清单,但涵盖了我们将在本书中使用的角色。

这些已命名的快捷键只涵盖了最常用的字符。如果必须使用其他特殊字符,例如,调制解调器发出的传输结束(EOT,ASCII值为4)信号,可以用反斜线给出该字符的八进制ASCII值。这样,我们的EOT字符就是'\4',有时也会出现三位数:'\004'。(由于ASCII是7位编码,所以三位八进制数字涵盖了最高的ASCII字符。如果你好奇的话,这就是删除(DEL,ASCII 127)或八进制转义序列'\177'。有些人更喜欢始终看到三位数字的一致性。)

你可能不需要很多这样的快捷方式,但由于Windows路径名使用反斜线字符,因此记住某些字符需要这个特殊的前缀是很重要的。当然,换行符也会继续出现在我们的许多打印语句中。你可能已经注意到,在使用八进制转义序列时,前缀反斜线包含在单引号内。因此制表符是'\t',反斜线是'\'。

字符串

字符串是一系列字符,但却是非常正式的字符串。许多编程语言都支持这种称为数组的序列。第4章将更详细地介绍数组,但C语法中char类型的数组(char[])非常常见,所以我想单独提一下。

我们一直在使用字符串,但并没有明确说明。在我们的第一个hello程序中,我们调用了带有字符串参数的printf()。在C语言中,字符串是由0个或多个字符组成的集合,最后带有一个特殊的“空”字符,即0(ASCII值为0)。你通常会在代码中将字符包含在双引号之间,例如我们的"Hello,world!\n"参数。令人高兴的是,当你使用这些双引号时,你不必自己添加\0。它隐含在字符串字面量的定义中。

声明字符串变量与声明char变量一样简单:

每个变量都可以存储简单的内容(如姓名)或更复杂的内容(如“高级代码和美味馅饼开发人员”)。字符串也可以为空:“”。这可能看起来很傻,但想想你在表单中输入姓名之类的东西。如果你碰巧是一个只有一个名字的流行歌星,那么上面的姓氏变量就可以被赋予有效值""(即只有结尾的'\0')。

2.2.3 数字

令人惊讶的是,C语言也有可以存储数值的类型。或者更准确地说,C语言也有可以存储比char类型变量通常所能容纳的数值更大的数字的类型。(尽管本章的示例使用char来存储实际字符,但它仍然是一种数值类型,适合存储与字符编码无关的小数字。)C将这些数值类型分为两个子类别:整数和浮点数(即小数)。

整数类型

整数类型存储简单的数字。主要类型称为int,但也有许多变体。不同类型的主要区别在于给定类型的变量中可以存储的最大数字的大小。表2-2总结了这些类型及其存储容量。

表2-2:整数类型及其典型大小

表2-2:整数类型及其典型大小(续)

注:字符定义为一个字节,其他大小则取决于系统。

上述大多数类型都是有符号类型 ,这意味着它们可以存储小于0的值。所有这五种类型都有一个明确的无符号变体(如无符号int或无符号char),其位/字节大小相同,但不能存储负值。如表2-3所示,它们的范围从0开始,大约是有符号范围的两倍。

表2-3:无符号整数类型及其典型大小

下面是整数类型声明示例。注意x和y变量的声明。我们经常可以看到网格或图形上的坐标是用“x和y”来表示的。C语言允许使用逗号分隔声明多个相同类型的变量名。这种格式没有什么特别之处,但如果你有一些相关的简短变量名,这可能是一个不错的选择。

如果要存储的数值较小,例如,“最多一打”或“前100名”,请记住你可以使用char类型。它的长度只有1字节,编译器不会在意你是将数值打印为实际字符还是简单的数字。

浮点类型

如果要存储小数或金融数字,可以使用float或double类型。这两种类型都是浮点类型,其中的小数点不是固定的(例如可以浮动),可以存储999.9或3.14这样的值。但由于我们所讨论的计算机是以离散块为单位进行思考的,因此浮点类型存储的是以1和0编码的近似值,就像int一样。float类型是一种32位编码,可以存储从很小的分数到很大的指数等各种数值。但是,float在-32k~32k之间的小范围内以及小数点后的6个有效位上最为精确。

double类型的精度是float的“双倍” [1] 。这意味着小数点后大约15位数字将被精确表示。我们会在一些地方看到这种近似值可能导致的问题,但对于收据总数或温度传感器读数等一般用途,这些类型已经足够了。

与其他类型一样,要将类型放在名称之前:

由于普通十进制数也可以存储整数值,如6(存储为6.0),因此使用float作为默认数值类型可能很有吸引力。但是,在Arduino这样的小型CPU上处理小数点编码的数字可能会很昂贵。即使在大型芯片上,处理小数点也比处理简单整数昂贵。出于性能和准确性的考虑,除非有明确的理由,否则大多数C程序员都会坚持使用int。

2.2.4 变量名称

无论变量是什么类型,它都有一个名称。在大多数情况下,你可以自由地使用任何你想要的名称,但有一些规则你必须遵守。

在C语言中,变量名可以以任何字母或下划线字符(“_”)开头。在开头字符之后,变量名可以包含更多的字母、下划线或数字。变量名区分大小写(total和Total不是同一个变量),长度(通常)限制为31个字符 ,不过按照惯例,长度会更短。

C语言还将一些关键字保留给C语言本身使用。由于表2-4中的关键字对C语言已经有一定意义,因此不能用作变量名。有些实现可能会保留其他词(如asm、typeof和inline),但大多数备用关键字都以一个或两个下划线开头,这样可以避免与你自己的变量名发生冲突。

表2-4:C关键字

如果在声明变量时与关键字发生冲突,则会出现与使用无效变量名(如以数字开头的变量名)类似的错误:

“expected identifier”这一短语是导致错误的主要原因。编译器期待的是一个变量名,却发现了一个关键字:

2.2.5 变量赋值

hello2.c 示例中,我们依赖于对name变量的隐式赋值。作为scanf()函数的参数,用户输入的任何内容都会存储在该变量中。但我们可以(也经常)对变量进行直接赋值。可以使用等号(“=”)来表示赋值,例如:

现在,你已成功地将数值7存储到了变量total中。

你也可以随时覆盖该值:

虽然连续赋值有点浪费,但这个C代码段并没有什么问题。不过,变量total只能保留一个整数值,所以最近的赋值是最终生效的,本例中为42。

你经常会看到变量同时被定义和分配初始值(用程序员的话说就是初始化):

现在,总数和答案都有了可以使用的值,但两者仍可根据需要进行更改。这正是变量的作用。

字面量

在这些示例中,我们将简单的值插入变量,这些值被称为字面量。字面量只是一个无须解释的值。数字、单引号内的字符或双引号内的字符串都属于字面量:

希望前两个变量定义看起来很熟悉。但请注意,当我们初始化名为label的字符串时,并没有给数组设置长度。C编译器会根据我们在初始化中使用的文字推断长度。在本例中,label的长度为12个字符,其中11个字符代表“Description”(描述)中的字母,另外一个字符代表结束符'\0'。如果你知道以后在代码中会用到字符串变量,你可以给它更多的空间,但不应该指定太小的空间。

如果你尝试分配一个字符串字面量,但该字符串字面量对于char[]变量来说太长,编译器可能会发出警告:

这是一个相当特殊的错误,希望你能发现它很容易修复。顺便说一句,你的程序仍然可以运行。请注意,编译器给你的是一个警告,而不是我们在前面一些编译器问题的例子中看到的错误。警告通常意味着编译器觉得你犯了一个错误,但你可以得到提醒避免潜在错误,因此通常最好还是处理警告(并不强制)。 4q6WhlzJS3wwUdvt+whc45VhMT//tBQ/c6ij61X8+lvNdQmVmE2a2YGbxT8pzW7u

点击中间区域
呼出菜单
上一章
目录
下一章
×