Skip to content

Latest commit

 

History

History
597 lines (341 loc) · 29.2 KB

chapter-2.md

File metadata and controls

597 lines (341 loc) · 29.2 KB

2. 常量、变量和表达式

2.1 继续 Hello World

注释

  • 注释是给程序员看的,编译器不管 1.4

    注释只是写给程序员看的,编译器会忽略从/**/的所有字符,所以写注释没有语法规则,爱怎么写就怎么写,并且不管写多少都不会被编译进可执行文件中。

  • 注释的两种形式

    注释可以跨行,也可以穿插在程序之中,看下面的例子。

    #include <stdio.h>
    
    /*
     * comment1
     * main: generate some simple output
     */
    
    int main(void)
    {
        printf(/* comment2 */"Hello, world.\n"); /* comment3 */
        return 0;
    }
    • /* */

      第一个注释跨了四行,头尾两行是注释的界定符(Delimiter)/**/,中间两行开头的*号(Asterisk)并没有特殊含义,只是为了看起来整齐,这不是语法规则而是大家都遵守的C代码风格(Coding Style)之一,代码风格将在第9章详细介绍。

    • 嵌套

      注释不能嵌套(Nest)使用,就是说一个注释的文字中不能再出现/**/了,例如/*text1/*text2*/text3*/是错误的,编译器只把/*text1/*text2*/看成注释,后面的text3*/无法解析,因而会报错。

    • //

      有的C代码中有类似//comment的注释,两个/斜线(Slash)表示从这里直到该行末尾的所有字符都属于注释,这种注释不能跨行,也不能穿插在一行代码中间。

      这是从C++借鉴的语法,在C99中被标准化。

C 语言标准

C语言的发展历史大致上分为三个阶段:Old Style C、C89和C99。

  • Old style C

    Ken Thompson和Dennis Ritchie最初发明C语言时有很多语法和现在最常用的写法并不一样,但为了向后兼容性(Backward Compatibility),这些语法仍然在C89和C99中保留下来了,本书不详细讲Old Style C,但在必要的地方会加以说明。

  • C89

    C89是最早的C语言规范,于1989年提出,1990年首先由ANSI(美国国家标准委员会,American National Standards Institute)推出,后来被采纳为ISO国际标准(ISO/IEC 9899:1990),因而有时也称为C90,最经典的C语言教材参考文献[3]就是基于这个版本的,C89是目前最广泛采用的C语言标准,大多数编译器都完全支持C89。

  • C99

    C99标准(ISO/IEC 9899:1999)是在1999年推出的,加入了许多新特性,但目前仍没有得到广泛支持,在C99推出之后相当长的一段时间里,连gcc也没有完全实现C99的所有特性。C99标准详见参考文献[8]。

    本书讲C的语法以C99为准,但示例代码通常只使用C89语法,很少使用C99的新特性。

  • C 标准参考材料

    C标准的目的是为了精确定义C语言,而不是为了教别人怎么编程,C标准在表达上追求准确和无歧义,却十分不容易看懂,参考文献[4]和参考文献[5]是对C89及其修订版本的阐释(可惜作者没有随C99更新这两本书),比C标准更容易看懂,另外,参考文献[6]也有助于加深对C标准的理解。

字符串字面值

像"Hello, world.\n"这种由双引号(Double Quote)引起来的一串字符称为字符串字面值(String Literal),或者简称字符串。

注意,程序的运行结果并没有双引号,printf打印出来的只是里面的一串字符Hello, world.,因此双引号是字符串字面值的界定符,夹在双引号中间的一串字符才是它的内容。

转义序列

注意,打印出来的结果也没有\n这两个字符,这是为什么呢?

在第1.2节中提到过,C语言规定了一些转义序列(Escape Sequence),这里的\n并不表示它的字面意思,也就是说并不表示\和n这两个字符本身,而是合起来表示一个换行符(Line Feed)。

  • C 标准规定的转义字符

    C标准规定的转义字符有以下几种,如表2.1所示。

    转义字符

  • 在字符串字面值中表示 \、" 必须使用转义序列

    如果在字符串字面值中要表示单引号和问号,既可以使用转义序列\'\?,也可以直接用字符'和?,而要表示\或"则必须使用转义序列,因为\字符表示转义而不表示它的字面含义,"表示字符串的界定符而不表示它的字面含义。

    可见转义序列有两个作用:一是把普通字符转义成特殊字符,例如把字母n转义成换行符;二是把特殊字符转义成普通字符,例如\和"是特殊字符,转义后取它的字面值。

  • 控制字符

    C语言规定了几个控制字符,不能用键盘直接输入,因此采用\加字母的转义序列表示。

    \a是响铃字符,在字符终端下显示这个字符的效果是PC喇叭发出嘀的一声,在图形界面终端下的输出效果取决于终端的配置。

    在终端下显示\b和按下退格键的效果相同。

    \f是分页符,主要用于控制打印机在打印源代码时提前分页,这样可以避免一个函数跨两页打印。

    \n和\r分别表示Line Feed和Carriage Return,这两个词来自老式的英文打字机,Line Feed是跳到下一行(进纸,喂纸,有个喂的动作所以是feed),Carriage Return是回到本行开头(Carriage是卷着纸的轴,随着打字慢慢左移,打完一行就一下子移回最右边,如果你看过欧美的老电影应该能想起来这是什么)。

    用老式打字机打完一行之后需要这么两个动作,\r\n,所以现在Windows平台的文本文件用\r\n做换行符,许多应用层网络协议(如HTTP)也用\r\n做换行符,而Linux和各种UNIX平台的文本文件只用\n做换行符。

    在终端下显示\t和按下Tab键的效果相同,用于在终端下定位表格的下一列,\v用于在终端下定位表格的下一行。\v比较少用,\t比较常用,以后将“水平制表符”简称为“制表符”或Tab。

  • \? 2.2

    读者可能会奇怪,为什么需要规定一个转义序列\?呢?因为C语言规定了一些三连符(Trigraph),在某些特殊的终端上缺少某些字符,需要用Trigraph输入,例如用??=表示#字符。

    Trigraph极不常用,极不常用的C语法在本书中通常不会介绍,介绍这个只是为了让读者理解转义序列的作用,即特殊字符转普通字符、普通字符转特殊字符,?号也是一种特殊字符,要表示其字面意思也需要用转义序列,但如果?号单独出现,不会被误认为是三连符,也可以不用转义序列。

空格、Tab、换行、代码风格

  • 空格和 tab 无关紧要,但 int 和 main 之间要有空格

    程序中别处的空格和Tab多一个少一个往往是无关紧要的,不会对编译的结果产生任何影响,例如不缩进不会影响程序的结果,main后面多几个空格也没影响,但是int和main之间至少要有一个空格分隔开:

    int main    (void)
    {
    printf("Hello, world.\n");
    return 0;
    }
  • 漂亮的代码必须有整齐的缩进(空格和 tab)1.4

    我们看到这两句相比main那一行都缩进(Indent)了一些,在代码中可以用若干个空格(Blank)和Tab字符来缩进,缩进不是必需的,但这样使我们更容易看出这两行是属于main的定义之中的,要写出漂亮的程序必须有整齐的缩进,第9.1节将介绍推荐的缩进写法。

  • 换行也无关紧要,但 include 要单独占一行

    不仅空格和Tab是无关紧要的,换行也是如此,我甚至可以把整个程序写成一行,但是include必须单独占一行:

    #include<stdio.h>
    int main(void){printf("Hello, world.\n");return 0;}

    这样也行,但肯定不是好的代码风格,去掉缩进已经很影响可读性了,写成现在这个样子可读性更差。如果编译器说第2行有错误,也很难判断是第2行的哪个语句出的错。

  • 好的代码风格

    所以,好的代码风格要求缩进整齐,每个语句一行,适当留空行。

2.2 常量

常量(Constant)是程序中最基本的元素,有字符(Character)常量、整数(Integer)常量、浮点数(Floating Point)常量和枚举常量。枚举常量将在第7.3节介绍。

下面看一个例子:

printf("character: %c\ninteger:%d\nfloating point: %f\n", '}', 34, 3.14);

字符常量

字符常量要用单引号括起来,例如上面的'}',注意单引号只能括一个字符而不能像双引号那样括一串字符,字符常量也可以是一个转义序列,例如'\n',这时虽然单引号括了两个字符,但实际上只表示一个字符。

  • 在字符常量中表示 \、' 必须使用转义序列

    和字符串字面值中使用转义序列有一点区别,如果在字符常量中要表示双引号"和问号?,既可以使用转义序列\"\?,也可以直接用字符"和?,而要表示'和\则必须使用转义序列。

整数常量和浮点数常量

在计算机中整数和小数的内部表示方式不同(将在第13章详细介绍),因而在C语言中是两种不同的类型(Type),通常小数在计算机中的表示方式称为浮点数,详见第13.4节。

上例的34和3.14分别是整数常量和浮点数常量。

初步接触 printf

  • 格式化字符串、转换说明、占位符

    printf中的第一个字符串称为格式化字符串(Format String),它规定了后面几个常量以何种格式插入到这个字符串中,在格式化字符串中%号(Percent Sign)后面加上字母c、d、f分别表示字符型、整型和浮点型的转换说明(Conversion Specification),转换说明只在格式化字符串中占个位置,并不出现在最终的打印结果中,这种用法通常叫做占位符(Placeholder)。

  • 转换说明和转义序列的区别

    这也是一种字面意思与真实意思不同的情况,但是转换说明和转义序列又有区别。

    转义序列是在编译时处理的。

    而转换说明是在运行时调用printf函数处理的:

    • 源文件中的字符串字面值是"character: %c\ninteger: %d\nfloating point:%f\n",\n占两个字符;

    • 编译之后保存在可执行文件中的字符串是character:%c换行integer: %d换行floating point: %f换行,\n已经被替换成一个换行符,而%c这两个字符不变;

    • 在运行时这个字符串被传给printf,printf再把其中的%c、%d、%f解释成转换说明。

2.3 变量

变量代表名字、空间、值

变量(Variable)是编程语言最重要的概念之一,在程序中变量是一个名字,而这个名字代表的是计算机存储器中的一块空间,可以在里面保存一个值(Value),保存的值是可以随时变的,比如这次存个字符'a',变量的值就是'a',下次存个字符'b',变量的值就变成'b',正因为变量的值可以随时变所以才叫变量。

变量类型

常量有不同的类型,变量也有不同的类型,变量的类型决定了它所占的存储空间的大小。

  • 不同类型变量大小不同,表示方式也不同 2.4

    不同类型的变量所占的存储空间大小是不同的,数据表示方式也不同,变量的最小存储单位是字节(Byte),在C语言中char型变量占一个字节,其他类型的变量占多少字节在不同平台上有不同的规定,将在第14章详细讨论。

声明和定义

  • 声明

    在C语言中用声明(Declaration)来规定变量的名字和类型。

    例如下面有四条声明,规定了四个变量fred、bob、jimmy和tom的类型分别是字符型、整型、单精度浮点型、双精度浮点型:

    char fred;
    int bob;
    float jimmy;
    double tom;
  • 三种声明

    C语言中的声明有变量声明、函数声明和类型声明三种。本节只讲变量声明,下一章会讲到函数声明,从第7章开始我们会看到类型声明。

  • 声明分为:是定义的声明、不是定义的声明

    从另一个角度来看,声明分为“是定义(Definition)的声明”和“不是定义的声明”,那么什么样的声明同时也是定义呢?简单地说,分配存储空间的声明同时也是定义,不分配存储空间的声明不是定义。

    • 存在变量定义和变量声明

      如果一个变量声明要求编译器为它分配存储空间,那么这个声明同时也是变量的定义。

      本章和接下来几章的示例代码中的变量声明都是要分配存储空间的,因而都是定义;等学到第19.2节我们会看到有些变量声明不分配存储空间,因而不是定义。

    • 存在函数定义(函数声明加函数体)和函数声明

      如果一个函数声明带有函数体,要求编译器为它生成指令(当然也需要分配存储空间来保存这些指令),那么这个声明同时也是函数的定义。

      在下一章我们会看到带函数体的声明和不带函数体的声明,不带函数体的声明不是函数定义。

    • 存在类型声明,不存在类型定义

      类型声明总是不分配存储空间的,所以严格来说只有类型声明而没有类型定义,但通常我们习惯说“定义了某种类型”,所以在本书中“类型定义”和“类型声明”表示相同的含义,不加区分。

  • 声明和语句的区别

    • 语句以 ; 结束 1.4

      注意语句的末尾以;号(Semicolon)结束,下一条语句return 0;也是如此。

    • 声明可以出现在函数外面

      声明也是以;号结尾,这一点和语句类似,但是在语法上声明和语句是有区别的,语句只能出现在函数体中,而声明既可以出现在函数体中也可以出现在所有函数之外。

  • 一起声明

    两个相同类型的变量可以一起声明。

    int hour, minute;

浮点数类型

浮点型有三种:float是单精度浮点型;double是双精度浮点型;long double是精度更高的浮点型。

它们之间的区别和转换规则将在第14章详细介绍,在随后的几章中我们只使用double类型。

  • 浮点数常量一般都是 double 类型

    上一节介绍的常量3.14是double类型的常量。

  • printf 的 %f 也是 double 类型的转换说明

    printf的%f也是double型的转换说明(注意%f不是float型的转换说明)。

标识符起名限制

  • 变量的起名限制

    给变量起名有一定的限制,C语言规定必须以字母或下划线_(Underscore)开头,后面可以跟若干个字母、数字、下划线,但不能有其他字符。

    例如这些是合法的变量名:Abc__abc___123

    但这些是不合法的变量名:3abcab$

  • 变量的起名限制就是标识符的起名限制

    其实这条规则不仅适用于变量名,也适用于所有可以由程序员起名的语法元素,例如以后要讲的函数名、宏定义、结构体成员名等,在C语言中这些统称为标识符(Identifier)。

  • 关键字

    另外要注意,表示类型的char、int、float、double等虽然符合上述规则,但也不能用作标识符。

    在C语言中有些单词有特殊意义,不允许用作标识符,这些单词称为关键字(Keyword)或保留字(Reserved Word)。通常用于编程的文本编辑器都会高亮显示(Highlight)这些关键字,所以只要小心一点通常不会误用作标识符。

    C99规定的关键字有:

    auto break case char const continue default do 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
    
  • 避免使用下划线开头的标识符

    还有一点要注意,一般来说应避免使用以下划线开头的标识符,以下划线开头的标识符只要不和C语言关键字冲突都是合法的,但是往往被编译器用作一些功能扩展(比如第18.4节讲到gcc的__attribute__语法),C标准库也定义了很多以下划线开头的标识符留作内部使用,所以除非你对编译器的特性和C标准库的实现特别清楚,一般应避免使用这种标识符,以免造成命名冲突。

2.4 赋值

赋值语句

定义了变量之后,我们要把值存到它们所表示的存储空间里,可以用赋值(Assignment)语句实现:

char firstletter;
int hour, minute;
firstletter = 'a';
hour = 11;
minute = 59;
  • 变量要先声明后使用

    注意变量一定要先声明后使用,编译器必须先看到变量声明,才知道firstletter、hour和minute是变量名,各自代表一块存储空间。另外,变量声明中的类型表明这个变量代表多大的一块存储空间,这样编译器才知道如何读写这块存储空间。

  • 赋值语句的等号不表示数学的相等

    还要注意,这里的等号不表示数学里的相等关系,和1+1=2的等号是不同的,这里的等号表示赋值。

    在数学上不会有i=i+1这种等式成立,而在C语言中这条语句表示把变量i的存储空间中的值取出来,再加上1,得到的结果再存回i的存储空间中。

    再比如,在数学上a=7和7=a是一样的,而在C语言中后者是不合法的。

变量初始化

变量的定义和赋值也可以一步完成,这称为变量的初始化(Initialization),例如要达到上面代码的效果也可以这样写:

char firstletter = 'a';
int hour = 11, minute = 59;
  • Initializers

    其中等号右边的值叫做Initializer,例如上面的'a'、11和59。

  • 初始化和赋值语句的区别

    注意,初始化是一种特殊的声明,而不是一种赋值语句。

    就目前来看,先定义一个变量再给它赋值和定义这个变量的同时给它初始化所达到的效果是一样的,C语言的很多语法规则既适用于赋值也适用于初始化,但在以后的学习中你也会了解到它们之间的不同,请在学习过程中注意总结赋值和初始化的相同和不同之处。

赋的值和变量类型不符

如果所赋的值和变量的类型不符会导致编译器报警告报错(这是一种语义错误),例如:

#include <stdio.h>

int main(void)
{
    int minute;
    minute = "59";
}
$ gcc main.c
main.c: In function ‘main’:
main.c:6:12: warning: assignment makes integer from pointer without a cast [-Wint-conversion]
     minute = "59";
            ^
#include <stdio.h>

int main(void)
{
    int minute = "59";
}
$ gcc main.c
main.c: In function ‘main’:
main.c:5:18: warning: initialization makes integer from pointer without a cast [-Wint-conversion]
     int minute = "59";
                  ^~~~

2.5 表达式

表达式组成:运算符和操作数

常量和变量都可以参与加减乘除运算,例如1+1、hour-1、hour * 60 + minute、minute/60等。

这里的+-*/称为运算符(Operator),而参与运算的常量和变量称为操作数(Operand),上面四个由运算符和操作数所组成的算式称为表达式(Expression)。

运算符的优先级

和数学上规定的一样,hour * 60 + minute这个表达式应该先算乘再算加,也就是说运算符是有优先级(Precedence)的,*和/是同一优先级,+和-是同一优先级,*和/的优先级高于+和-。

对于同一优先级的运算从左到右计算,如果不希望按默认的优先级计算则要加()括号(Parenthesis)。例如(3+4)*5/6应先算3+4,再算*5,再算/6。

表达式语句

前面讲过打印语句和赋值语句,现在我们定义:在任意表达式后面加个;号也是一种语句,称为表达式语句。例如:

hour * 60 + minute;

这是个合法的语句,但这个语句在程序中起不到任何作用,把hour的值和minute的值取出来相加和相乘,得到的计算结果却没有保存,白算了一通。

再比如:

int total_minute;
total_minute = hour * 60 + minute;

这个语句就很有意义,把计算结果保存在另一个变量total_minute里。

  • 等号也是一种运算符,赋值语句也是一种表达式语句

    事实上等号也是一种运算符,称为赋值运算符,赋值语句就是一种表达式语句,等号的优先级比+和*都低,所以先算出等号右边的结果然后才做赋值操作,整个表达式total_minute = hour * 60 + minute加个;号构成一个语句。

表达式的两个属性:值和类型

任何表达式都有类型两个基本属性。

hour * 60 + minute的值是由三个int型的操作数计算出来的,所以这个表达式的类型也是int型。

  • 赋值表达式的值和类型

    同理,表达式total_minute= hour * 60 + minute的类型也是int,它的值是多少呢?

    C语言规定等号运算符的计算结果就是等号左边被赋予的那个值,所以这个表达式的值和hour * 60 + minute的值相同,也和total_minute被赋值之后的值相同。

运算符的结合性

  • 赋值运算符从右到左计算

    等号运算符还有一个和+-*/不同的特性,如果一个表达式中出现多个等号,不是从左到右计算而是从右到左计算,例如:

    int total_minute, total;
    total = total_minute = hour * 60 + minute;

    计算顺序是先算hour * 60 + minute得到一个结果,然后算右边的等号,就是把hour* 60 + minute的结果赋给变量total_minute,这个结果同时也是整个表达式total_minute = hour * 60 + minute的值,再算左边的等号,即把这个值再赋给变量total。

  • 运算符结合性

    如果一个操作数的左右两侧各有一个相同优先级的运算符,这个操作数与左边的运算符结合还是与右边的运算符结合取决于运算符的结合性(Associativity),相同优先级的运算符应该具有相同的结合性,+-和*/是左结合的,而等号是右结合的。

    在上面的表达式中,操作数total_minute的左右两边都有等号,应该和右边的等号结合,相当于total = (total_minute = hour * 60 + minute),而不是(total = total_minute) = hour * 60 + minute。

目前学过的语法规则

现在我们总结一下到目前为止学过的语法规则:

表达式 → 标识符

表达式 → 常量

表达式 → 字符串字面值

表达式 → (表达式)

表达式 → 表达式 + 表达式

表达式 → 表达式 - 表达式

表达式 → 表达式 * 表达式

表达式 → 表达式 / 表达式

表达式 → 表达式 = 表达式

语句 → 表达式;

语句 → printf(表达式, 表达式, 表达式, ...);

变量声明 → 类型 标识符 = Initializer, 标识符 = Initializer, ...;

(= Initializer的部分可以不写)

注意,本书所列的语法规则都是简化过的,是不准确的,目的是为了便于初学者理解,比如上面所列的语法规则并没有描述运算符的优先级和结合性。完整的C语法规则请查看参考文献[8]的Annex A。

  • 组合

    表达式可以是单个的常量或变量,也可以是根据以上规则组合而成的更复杂的表达式。

    理解组合(Composition)规则是理解语法规则的关键所在,正因为可以根据语法规则任意组合,我们才可以用简单的常量、变量、表达式、语句和声明搭建出任意复杂的程序,以后我们学习新的语法规则时会进一步体会到这一点。

左值和右值

根据语法规则组合出来的表达式在语义上并不总是正确的,例如:

minute + 1 = hour;

等号左边的表达式要求表示一个存储位置而不是一个,这是等号运算符和+-*/运算符的又一个显著的不同。

  • 有的表达式可以表示存储位置也可以表示值,有的表达式只能表示值

    有的表达式既可以表示一个存储位置也可以表示一个值,而有的表达式只能表示值,不能表示存储位置,例如minute+1这个表达式就不能表示存储位置,放在等号左边是语义错误

  • 有的表达式可以做左值也可以做右值,有的表达式只能做右值

    表达式所表示的存储位置称为左值(lvalue),允许放在等号左边,而之前我们所说的表达式的值也称为右值(rvalue),只能放在等号右边。

    上面的话换一种说法就是:有的表达式既可以做左值也可以做右值,而有的表达式只能做右值。

    目前我们学过的表达式中只有变量可以做左值,可以做左值的表达式还有几种,以后会讲到。

  • 另一个例子

    我们看一个有意思的例子,如果定义三个变量int a, b, c;,表达式a=b=c是合法的,先求b=c的值,再把这个值赋给a,而表达式(a=b)=c是不合法的,先求(a=b)的值没问题,但(a=b)这个表达式不能再做左值了,因此放在=c的等号左边是错的。

整数除法特殊之处

  • 整数除法无论是正是负,总是把小数部分截掉

    关于整数除法运算有一点特殊之处:

    hour = 11;
    minute = 59;
    printf("%d and %d hours\n", hour, minute / 60);

    执行结果是11 and 0 hours,也就是说59/60得0,这是因为两个int型操作数相除的表达式仍为int型,只能保存计算结果的整数部分,即使小数部分是0.98也要舍去。

    向下取整的运算称为Floor,用数学符号⌊⌋表示;向上取整的运算称为Ceiling,用数学符号⌈⌉表示。

    在C语言中整数除法取的既不是Floor也不是Ceiling,无论操作数是正是负总是把小数部分截掉,在数轴上向零的方向取整(Truncate towards Zero),或者说当操作数为正的时候相当于Floor,当操作数为负的时候相当于Ceiling。

  • 修正前面的例子:隐式类型转换

    回到先前的例子,要得到更精确的结果可以这样:

    printf("%d hours and %d percent of an hour\n", hour, minute * 100 / 60);
    printf("%d and %f hours\n", hour, minute / 60.0);

    在第二个printf中,表达式是minute / 60.0,60.0是double型的,/运算符要求左右两边的操作数类型一致,而现在并不一致。

    C语言规定了一套隐式类型转换规则,在这里编译器自动把左边的minute也转成double型来计算,整个表达式的值也是double型的,在格式化字符串中应该用%f转换说明与之对应。

    本来编程语言作为一种形式语言要求有简单而严格的规则,自动类型转换规则不仅很复杂,而且使C语言的形式看起来也不那么严格了,C语言这么设计是为了书写程序简便而做的折中,有些事情编译器可以自动做好,程序员就不必每次都写一堆繁琐的转换代码。

    然而C语言的类型转换规则非常难掌握,本书的前几章会尽量避免类型转换,到第14.3节再集中解决这个问题。

2.6 字符类型与字符编码

  • 字符常量或字符型变量可以当作整数参与运算

    字符常量或字符型变量也可以当做整数参与运算,例如:

    printf("%c\n", 'a'+1);

    执行结果是b。

  • 字符编码:ASCII 码

    我们知道,符号在计算机内部也用数字表示,每个字符在计算机内部用一个整数表示,称为字符编码(Character Encoding),目前最常用的是ASCII码(American Standard Code for Information Interchange),详见表A.1。表中每一栏的最后一列是字符,前三列分别是用十进制(Dec)、十六进制(Hx)和八进制(Oct)表示的字符编码,各种进制之间的换算将在第13.2节介绍。从十进制那一列可以看出ASCII码的取值范围是0~127。

    表中的很多字符是不可见字符(Non-printable Character)和空白字符(Whitespace),不能像字母a这样把字符本身填在表中,而是用一个名字来描述该字符,例如CR(Carriage Return)、LF(NL Line Feed,Newline)、DEL等。作为练习,请读者查一查表2.1中的字符在ASCII码表中的什么位置。

    • 空白字符

      空白字符在不同的上下文中有不同的含义,在C语言中空白字符定义为空格、水平Tab(\t)、垂直Tab(\v)、换行(\r和\n)和分页符(\f),本书在使用“空白字符”这个词时会明确说明在当前上下文中空白字符指的是哪些字符。

  • 对上面例子的解释

    回到刚才的例子,在ASCII码中字符a是97,字符b是98。计算'a'+1这个表达式,应该按ASCII码把'a'当做整数值97,然后加1,得到98,然后printf把98这个整数值当做ASCII码来解释,打印出相应的字符b。

  • 整数类型除了 int 还有 char 和别的类型

    之前我们说“整型”是指int型,而现在我们知道char型本质上就是整数,只不过取值范围比int型小,所以以后我们把char型和int型统称为整数类型(Integer Type)或简称整型,后面我们还要学习几种类型也属于整型,将在第14.1节详细介绍。

  • 字符可以用 ASCII 码转义序列表示

    字符也可以用ASCII码转义序列表示,这种转义序列由\加上1~3个八进制数字组成,或者由\x加上1~2个十六进制数字组成,可以用在字符常量字符串字面值中。例如'\0'表示NUL字符(Null Character),'\11'或'\x9'表示Tab字符,"\11"或"\x9"表示由Tab字符组成的字符串。

    注意'0'的ASCII码是48,而'\0'的ASCII码是0,两者是不同的。