#第二章 变量和类型
##2.1 更多的输出
如前文所述,在 main 中可以放任意多的语句。例如,多个输出语句:
#include <stdio.h>
#include <stdlib.h>
/* main: generate some simple output */
int main(void)
{
printf("Hello World.\n"); /* output one line */
printf("How are you?\n"); /* output another line */
return (EXIT_SUCCESS);
}
如你所见,位于行尾的注释和单独一行的注释都是合法的。
双引号中出现的短语叫做 字符串,因为它们是由一个字母序列组成的。事实上,字符串可以是任何字母、数字、标点符号和其它特殊字符组成的集合。
经常需要在一行中显示多个输出语句的输出。可以通过在 printf 中去掉 \n 来实现。
int main(void)
{
printf("Goodbye, ");
printf("cruel world!\n");
return (EXIT_SUCCESS);
}
这时,Goodbye, cruel world! 就会输出在单独的一行。注意,“Goodbye,“ 和第二个引号之间有一个空格。输出语句中的空格影响了程序的执行结果。
而双引号外的空格通常不会影响程序的执行结果。例如,我可以这样写:
int main(void)
{
printf("Goodbye, ");
printf("cruel world!\n");
return(EXIT_SUCCESS);
}
这个程序的编译和执行同原来的一样。每行末尾的间隔(换行)也不会影响程序的执行,所以可以这样写:
int main(void){printf("Goodbye, ");printf("cruel world!\n");return(EXIT_SUCCESS);}
你也许已经注意到这个程序非常难读了,但它还是可以执行。换行和空格有助于组织程序的结构,使程序更易读,并且更容易定位语法错误。
##2.2 值
计算机程序的操作对象是储存在内存中的值。值——例如一个字母或数字——是一个程序的基本组成部分。目前为止我们操作的唯一的值就是输出的字符串,例如 “Hello, world.”。你(和编译器)能够识别这些字符串是因为它们被放在了双引号里。
整数是一个完整的数字,例如 1 或 17 。你可以像输出字符串那样输出整数值:
printf("%i\n",17);
字符是一个放在单引号里的字母、数字或标点,例如 'a' 或 '5'。用类似的方法输出字符的值。
printf("%c\n",'}');
这个例子在一行里输出了一个左引号。
很容易混淆不同的类型,例如 "5" 、'5' 和 5 ,但是如果你把注意力放在它们的标点上,就会很容易的区分,第一个是字符串,第二个是一个字符,第三个是一个整数。这些差别是很重要的,所以要很快的区分。
##2.3 变量
一种程序语言最强大的一个特性就是通过变量操作值的能力。目前为止,我们用到的值都是直接放在语句中。现在,我们要用一个变量为存储值的位置命名。
与值的类型一样(整数,字符,等等),变量也有不同的类型。当你创建一个新的变量时,就要为它指定类型。例如,C 语言中的字符类型叫做 char 。下面的语句将创建一个 char 类型的名叫 fred 的变量。
char fred;
这样的语句叫做 声明 (declaration)。
变量的类型决定了它可以存放什么类型的值。 char 型变量可以存放字符,同样 int 型变量可以存放整数。
与其它编程语言相反,C 语言没有专门存放字符串的变量类型。我们将在后面的章节看到 C 语言如何存放字符串变量。
创建一个整型变量的语法是:
int bob;
这里的 bob 是用于识别变量的自定义的名字。通常情况下,变量的名字应该能够表示它的用途。例如,如果你看到这些变量的声明:
char first_letter;
char last_letter;
int hour,minute;
你应该很容易猜到它们存放的值的意义。这个例子还展示了同一类型声明多个变量的语法:hour 和 minute 都是整数(int 型)。
注意:过去的 C89 标准只允许在代码块的开始处声明变量。因此,即使变量在很久之后才会用到,也应该将变量的声明放在其它所有语句之前。
##2.4 赋值
我们已经新建了一些变量,现在想要用它们存放一些值。这要用到 赋值语句。
first_letter = 'a'; /* give first_letter the value 'a' */
hour = 11; /* assign the value 11 to hour */
minute = 59; /* set minute to 59 */
这个例子展示了三个赋值,注释显示了这三个赋值语句的作用。声明和赋值可能会被混淆,但是它们的意思是不同的:
- 声明一个变量就是创建一个有名字的存储位置。
- 为一个变量赋值就是给变量一个明确的值。
在纸面上通常用一个框表现变量,变量名在框外,变量的值在框内。这种图叫做状态图(state diagram),因为它展示了每个变量的内部状态(可以理解为变量的“内部状态”)。下面的图展示了三条赋值语句的结果:
有时我会用不同的形状表示不同类型的变量。这些形状会帮你记住 C 语言的一条规则:赋值的类型必须和变量的类型一致。例如,不能在 int 型变量中存放字符串。下面的语句会产生一个编译器警告。
int hour;
hour = "Hello."; /* WRONG !! */
这条规则有时会产生混乱,因为有很多方法可以转换变量的类型,并且 C 语言有时会自动转换。但是现在你要记住这条通用的规则:变量和值的类型要一致,稍后我们再讨论特殊情况。
另一种引起混乱的原因是一些看起来像整数的字符串。例如,字符串 “123”,它是由字符 1,2,3组成的,并不是数字 123 。这样的赋值语句是非法的:
minute = "59"; /* WRONG!! */
##2.5 输出变量
与前面直接输出值类似,你可以用同样的命令输出变量的值。
int hour, minute;
char colon;
hour = 11;
minute = 59;
colon = ':';
printf("The current time is ");
printf("%i", hour);
printf("%c", colon);
printf("%i",minute);
printf("\n");
这个程序新建了两个名叫 hour 和 minute 整型变量,还有一个字符变量 colon 。为每个变量赋了合适的值,然后用一套输出语句生成了:
The current time is 11:59
当我们讨论“输出一个变量”时,我们的意思是输出变量的值。变量的名字只对程序员有意义。编译后的程序不再包含程序中的变量名
正如我们所看到的,可以在一个输出语句中包含多个值,这样可以将前面的程序改造的更简洁:
int hour, minute;
char colon;
hour = 11;
minute = 59;
colon = ':';
printf("The current time is %i%c%i\n", hour, colon, minute);
这个程序用一行输出了一个字符串,两个整数和一个字符。非常醒目!
##2.6 关键字
我在前面曾经说过,可以为变量随意命名,但是这不完全正确。有几个词是 C 语言的保留字,因为编译器要用它们来解析程序的结构,如果你把它们用作变量名,会产生混乱。这些词叫做关键字(keywords),包括 int,char,void等等。
C 语言保留的关键字:
auto break case char const
continue default de double else
enum extern float for goto
if inline int long register
restrict return short signed sizeof
static struct switch typedef union
unsigned void volatile while _Bool
_Complex _Imaginary
这份完整的关键字列表,于 1998 年 9 月 1 日被国际标准组织(ISO)定义的官方 C 语言标准所包含。
与其熟记这份列表,我更建议你使用很多开发环境提供的特性:代码高亮。当你打字时,程序的不同部分会以不同的颜色出现。例如,关键字可能是蓝色,字符串是红色,其他代码是黑色。如果键入的变量名变成了蓝色,就要小心了!也许会在编译时出现一些奇怪的现象。
##2.7 运算符
运算符 是用来表示简单运算(例如加法和乘法)的特殊符号。C 语言里的大多数运算符都是你熟悉的,因为它们都是普通的数学符号。例如,将两个整数相加的运算符是 +。
下面都是合法的 C 语言表达式,它们的意思显而易见:
1+1 hour-1 hour*60 + minute minute/60
表达式 可以包含变量名和值。每次开始计算前,变量的名称都会替换为它们的值。
加法,减法和乘法都没什么特别之处,但是除法可能会让你感到意外。例如下面的程序:
int hour, minute;
hour = 11;
minute = 59;
printf("Number of minutes since midnight: ");
printf("%i\n", hour*60 + minute);
printf("Fraction of the hour that has passed: ");
printf("%i\n", minute/60);
它产生的输出如下:
Number of minute since midnight: 719
Fraction of the hour that has passed: 0
第一行与我们的预期一样,但是第二行有点奇怪。变量 minute 的值是 59,59 除以 60 应该是 0.98333,而不是0。产生这个矛盾的原因是 C 语言用的是 整数除法 。
当两个 操作数(opernds) 都是整数时(操作数就是运算符处理的东西),结果也必然是个整数,并且总是把小数部分舍去,即使小数部分很大。
这种情况下可以选择计算百分比,而不是小数:
printf("Percentage of the hour that has passed: ");
printf("%i\n",minute*100/60);
结果是:
Percentage of the hour that has passed: 98
虽然小数部分还是被舍去了,但是至少现在的答案是近似正确的。为了得到更精确的答案,我们可以使用另一种变量类型,叫做浮点数,它可以存储小数,我们下一章会讲到它。
##2.8 操作顺序
当多个运算符出现在一个表达式时,计算的顺序依据 优先级(precedence) 的规则。完整的优先级解释很复杂,但是可以先带你入门:
- 先乘除后加减。所以 2*3-1 等于 5 ,而不是 4 ;2/3-1 等于 -1 ,而不是(别忘了 2/3 等于 0)。
- 如果多个运算符的优先级相同,按从左到右的顺序计算。所以,表达式 minute*100/60 中先计算乘法,等于 5900/60 ,最后等于 98 。如果从左到右的计算,结果就是 59*1 等于 59 ,这是错误的。
- 当你想屏蔽优先级规则(或者无法确定运算符的优先级)时,可以使用括号。括号里的表达式是优先运算的,所以,2* (3-1) 等于 4 。也可以用括号增加表达式的可读性,例如 (minute*100)/60 ,尽管括号没有改变运算的结果。
##2.9 操作字符
有意思的是,整数之间使用的数学运算符也可以用于字符。例如:
char letter;
letter = 'a' + 1;
printf("%c\n", letter);
输出的是 b 。虽然乘法对于字符也是合法的,但是尽量不要使用。
我在前面说过,整型变量只能赋整数,字符变量只能赋字符,但这不完全正确。某些情况下,C 语言会自动转换类型。例如,下面的语句是合法的:
int number;
number = 'a';
printf("%i\n", number);
结果是 97,是字母 ‘a' 在 C 语言中的整数值。但是,通常做好是字符按字符处理,整数按整数处理,除非有好的理由需要转换类型。
自动类型转换是设计编程语言过程中的一个普遍的问题,这是一个形式主义之间的 冲突,形式语言应该对一些例外有简单的规则,又要提供便利,使编程语言更容易使用。
往往便利会获胜,这对于专业程序员很好,他们可以从严谨但笨拙的形式上节省不少精力,但是对于初级程序员就糟糕了,他们往往在复杂的规则和大量的例外之中感到迷惑。在这本书中,我会通过强调规则并省略大部分例外来简化这些事。
##2.10 组合
目前我们已经孤立的了解一些编程语言的元素 —— 变量,表达式和语句 ,还没有讨论怎样把它们结合起来。
编程语言的一个最有用的特性就是搭积木的能力。例如,我们知道怎样做整数乘法,怎样输出变量;那就可以同时做这两件事:
printf("%i\n, 17*3");
事实上,不应该说 “同时” ,因为乘法发生在输出之前,但重点是,任何表达式,包括数字、字符和变量,都可以在输出表达式中使用。我们已经看到了一个例子:
printf("%i\n", hour * 60 + minute);
也可以将一个表达式放在赋值语句的右边:
int percentage;
percentage = (minute * 100) / 60;
这个功能目前看来可能不重要,但是我们会在其它例子里看到,这样的组合可以清晰简洁的表达复杂的运算。
表达式的使用范围有限制:最显著的是,赋值语句的左边必须是一个变量名,不能是表达式。这是因为左边用于指示结果的存储位置。表达式无法表示存储位置,只能表示值。所以这条语句是非法的:minute + 1 = hour ;
##2.11 词汇
varialble(变量): 一个已命名的存储值的位置。所有变量都有一个类型,决定了它能存储什么样的值。
value(值): 一个字母,或者数字,或者其它存储在变量里的东西。
type(类型): 值的意义。目前我们看到的类型有整型( C 语言中的 int )和字符型( C 语言中的 char )。
keyword(关键字): 编译器用于解析程序的保留字。例如我们开到的 int ,void 和 char 。
statement(语句): 一行代码,表示一个命令或动作。目前为止,我们看到的语句有声明,赋值和输出语句。
declaration(声明): 一种语句,新建一个变量并声明它的类型。
assignment(赋值): 一种语句,为变量分配一个值。
expression(表达式): 变量、运算符和值的结合体,其结果是一个唯一的值。表达式也有类型,由它们的运算符和操作数决定。
operator(运算符): 一个特定的符号,表示一种简单的运算,例如加法或乘法。
operand(操作数): 运算符操作的一个值。
precedence(优先级): 用于预计操作顺序。
composition(组合): 将简单的表达式和语句结合为复合语句和复合表达式,以便简洁的表达复制的运算。
##2.12 练习
Exercise 2.1
-
a. 新建一个名为 MyDate.c 的程序,复制或输入一些类似于“Hello, World”的程序,并确定可以编译和运行。
-
b. 参照 2.5 节的例子,在程序中新建名为 day,month 和 year 的变量,它们的类型应该是什么?
-
c. 用一个打印语句打印所有变量。这一步可以检验我们目前学到的所有东西。
-
d. 修改程序,用标准美国格式 mm/dd/yyyy 打印日期。
-
e. 再次修改程序,输出如下:
American format: 3/18/2009 European format: 18.3.2009
这个联系的重点是用输出函数 printf 显示不同类型的值,并通过逐次添加一些语句来练习普通的程序开发。
Exercise 2.2
- a. 新建一个名为 MyTime.c 的程序。从现在开始,我不会再帮你写初始的程序,你要自己做。
- b. 参照 2.7 节的例子新建名为 hour ,minute 和 second 的变量,并用当前的时间为它们赋值。使用 24 小时制时钟,所以,2pm 时 hour 的值应该是 14 。
- c. 写一段程序,计算并打印从午夜到现在经过的秒数。
- d. 写一段程序,计算并打印这一天还剩余的秒数。
- e. 写一段程序,计算并打印这一天已经过去的百分比。
- f. 修改 hour ,minute 和 second 的值为当前时间(假设已经过去了一段时间),然后检查确认当前程序可以在不同的值下工作。
这个练习的重点是使用一些算数操作,同时开始思考像时间这样有多个值的复合体。你也可能在用 int 型计算百分比时遇到一些问题,这涉及到下一章会讲到的浮点数。
注意:你可能想在计算过程中用额外的变量来暂时存放一些值。像这样在计算中使用但不打印的变量,有时叫做中间变量或零时变量。