计算机系统概论第十二章.docx

上传人:b****5 文档编号:12305605 上传时间:2023-04-18 格式:DOCX 页数:27 大小:38.84KB
下载 相关 举报
计算机系统概论第十二章.docx_第1页
第1页 / 共27页
计算机系统概论第十二章.docx_第2页
第2页 / 共27页
计算机系统概论第十二章.docx_第3页
第3页 / 共27页
计算机系统概论第十二章.docx_第4页
第4页 / 共27页
计算机系统概论第十二章.docx_第5页
第5页 / 共27页
点击查看更多>>
下载资源
资源描述

计算机系统概论第十二章.docx

《计算机系统概论第十二章.docx》由会员分享,可在线阅读,更多相关《计算机系统概论第十二章.docx(27页珍藏版)》请在冰豆网上搜索。

计算机系统概论第十二章.docx

计算机系统概论第十二章

第十二章变量和运算符

12.1引言

在这一章中,我们包含了高级程序设计语言的两个基本概念,变量和运算符。

变量保存了程序执行所需的数值,而运算符是对这些数值进行操作的语言机制。

变量和运算符一起使得程序员能够更容易的表达程序所要实施的工作。

下面的一行C代码是包含了变量和运算符的语句。

在这个语句中,加法运算符“+”被用来在变量score的原值上加3。

这个新值再通过使用赋值运算符“=”被赋值给变量score。

如果score在这个语句执行之前等于7,执行之后它将等于10。

score=score+3;

在这一章的第一部分,我们将进一步认识C程序设计语言的变量。

C中的变量是很简明的:

三个最基本的类型就是整数,字符以及浮点数。

在变量之后,我们会介绍C的丰富的运算符集合,并提供大量的例子来帮助说明它们的运算。

我们的研究方法的一个唯一的特点就是我们可以把这些高级概念和低级材料相联系,在这一章的第三部分,我们就会通过讨论当编译器试图生成机器代码来处理变量和运算符时的观点来实现这一点。

我们将会通过解决一些问题和一些包含了C的变量和运算符的概念来结束这一章。

12.2变量

数值是程序执行操作的数据项。

数值的例子包括一个循环的重复计数器,用户输入的数值,或者一系列数字相加的部分和。

程序员花费很多的精力来保存这些数值。

因为数值是如此重要的程序设计的概念,高级语言努力让程序员管理它们的过程更容易。

高级语言允许程序员可以用符号引用数值,也就是说,使用一个名字而不是一个存储单元。

不管什么时候我们想对数值进行运算,语言会自动生成数据传送操作的正确顺序。

因此程序员可以集中精力在写程序上,而不需要担心在存储器中何处存储数值,或者在存储器和寄存器之间传送。

在高级语言中,这些用符号命名的数值被称为变量。

为了正确地记录程序中的变量,高级语言翻译器(例如C编译器)需要知道每个变量的几个特征。

很明显,需要知道变量的符号名。

需要知道变量包含的信息的类型。

需要知道在程序中何处可以访问该变量。

在大多数语言中,包括C语言,这个信息通过变量的声明提供。

让我们看一个例子,下面声明了一个包含了整数值的被称为echo的变量。

intecho;

基于该声明,编译器会为echo预留一个整数所需的存储空间(有时,编译器可以优化程序,这样echo被存储在一个寄存器中,因此不需要存储单元,但那是后面的课程的主题)。

无论echo何时被后面的C代码所引用,编译器会生成适当的机器代码来访问它。

12.2.1三种基本数据类型:

整数,字符,双精度

至此,你应该对下列概念非常熟悉了:

一个特定的位组合的含义取决于加在该组合上的数据类型。

例如,二进制组合01100110可能表示小写字母f或者表示十进制数102,这取决于我们把该组合当作一个ASCII码数据类型还是一个二进制补码的整数数据类型来对待。

变量的声明告诉编译器关于该变量的类型。

编译器根据变量的类型信息为该变量分配适当的存储空间。

类型还指明在机器层上如何对该变量上执行运算。

例如,在LC-3上可以用一个ADD指令对两个整数类型变量执行加法运算。

如果两个变量是浮点类型的,LC-3编译器将生成一个指令序列来执行加法,因为没有一条单独的LC-3指令能实现浮点数的加法。

C支持三种基本数据类型:

整数,字符和浮点数。

这些类型的变量可以通过使用类型标识符int、char和double(双精度浮点数的缩写)被创造。

int

int类型标识符声明了一个有符号的整数变量。

一个int数值的内部表示和范围取决于计算机的ISA和被使用的编译器的规定。

例如,在LC-3上,一个int是一个能表示从-32768到32767之间的16位的二进制补码整数。

在一台基于x86的计算机上,一个int可能是一个能表示从-2147483648到+2147483647之间的数值的32位的二进制补码数。

在大多数情况下,一个int是一个底层ISA的字长的二进制补码整数。

下面的一行代码声明了一个被称为numberOfSeconds的整数变量。

当编译器看到这个声明时,就为这个变量留出足够的存储空间(在LC-3的情况下是一个存储单元)。

intnumberOfSeconds;

这一点应该不奇怪,整数的变量是很频繁地在程序中被使用的。

它们经常很方便的表示我们想在程序中处理的现实世界的数据。

如果我们想要表示时间,假如用秒表示的话,一个整数变量将是完美的。

在一个跟踪鲸鱼移动的应用中,我们能够使用一个整数来表示能够看到的加利福尼亚海岸的一群灰鲸的大小。

整数还对程序的控制有用。

一个整数能够在计数器控制的循环中作为重复计数器使用。

char

char类型标识符声明了表示一个字符数据的变量。

接下来有两个例子。

第一个声明创造了一个名为lock的变量。

第二个声明了key。

第二个声明有点不同,它还包含了一个初始化。

在C中,任何一个变量都能够在声明中被直接设置一个初始值。

在这个例子中,变量key将会有一个大写字母Q的ASCII码的初始值。

还要注意,大写字母Q被单引号所包围。

在C中,被解释为ASCII码字面常量的字符是被单引号所包围的。

lock呢?

它有什么初始值?

我们稍后会处理这个问题。

charlock;

charkey='Q';

尽管8位对于保存一个ASCII码字符是足够的,为了使本书中的例子不那么混乱,所有的字符型变量将会占用16位。

那就是说,char类型,和int一样,每个都会占用一个存储单元。

double

double类型标识符允许我们可以声明在2.7.2节中查看的浮点数类型的变量。

浮点数能让我们方便的处理有小数部分的很大或很小的数。

回忆我们在之前的2.7.2节讨论过的,在最低层,浮点数是有三个部分的位组合:

符号,分数和指数。

以下是关于double类型变量的三个例子:

doublecostPerLiter;

doubleelectronsPerSecond;

doubleaverageTemp;

就像int和char一样,我们也可以在声明一个浮点数时对它初始化。

在我们可以完全描述出如何初始化一个浮点数变量之前,我们必须首先讨论如何在C中表示一个浮点数字面常量。

浮点数字面常量被表示为包含一个十进制小数点或指数,或两者都有,正如下面的例子所示。

指数是用字符e或E来表示,可以是正数或负数。

它表示了被小数部分(e或E之前的那部分)去乘的10的幂次。

注意指数必须是一个整数。

更多的有关浮点数字面常量的信息,见附录D.2.4。

doubletwoPointOne=2.1; /*这是2.1*/

doubletwoHundredTen=2.1E2;/*这是210.0*/

doubletowHundred=2E2; /*这是200.0*/

doubletwoTenths=2E-1;/这是0.2*/

doubleminusTwoTenths=-2E-1;/*这是–0.2*/

另外一种C中的浮点数类型标识符叫做float。

它声明了一个单精度的浮点数变量,而double创造的是双精度。

回忆我们曾经在第二章中关于浮点数的讨论,其精度取决于分配给小数部分的位数。

在C里,取决于编译器和指令集结构,一个double较float而言,可以给小数部分分配更多的位数,但是不可以比它更少。

double的长度取决于指令集结构和编译器。

通常,一个double是64位长而float是依照IEEE754浮点数标准的32位。

12.2.2选择标识符

对于在程序内选择变量的名字(通常被称为标识符),大多数高级语言有固定的规则。

C允许你去创造由字母表中的字母、数字和下划线组成的标识符。

然而,只有字母和下划线可以作为标识符的开头。

一个标识符可以是任意长度的,但是只有开头的31个字符被C编译器用来区分不同的变量——只有开头的31个字符对编译器有意义。

大写和小写的使用也有意义:

C把Capital和capital作为不同的变量对待。

这里有一些关于标准的C命名惯例的提示:

以下划线开头的变量(例如,_index_)习惯上只被用于特殊的库代码中。

变量几乎从来不用全部的大写字母声明。

按照惯例,全部大写字母只被用于预处理器指令#define创造的符号值。

注意11.5.3节中符号常量的例子。

程序希望直观地区分由多个单词组成的变量。

在这本书中,我们使用大写字母(例如,wordsPerSecond)。

其他程序员更倾向于使用下划线(例如,words_per_second)。

为写出好的代码而给变量取一个有意义的名称是重要的。

被选择的变量名应能反映它们所表示的值的特性,使得程序员能够更容易地回忆起该值是用来干什么的。

例如,一个用来计算键盘前的人每秒打字数目的数值可能被命名为wordsPerSecond。

在C语言中,有一些有特殊的意义的关键词,因此在用作标识符时会受到限制。

在附录D.2.6中,可以查到一个关于C关键词的表。

我们已经遇到的一个关键词是int,所以我们不能把int用作为变量名。

将int用作变量名不但会对人阅读代码造成混乱,而且也会使编译器在翻译它时产生混乱。

编译器不能够确定一个特定的int究竟指的是变量还是类型标识符。

12.2.3 作用域:

局部对全局

正如我们所提到的,一个变量的声明有助于编译器管理这个变量的存储。

在C语言中,一个变量的声明传达了三条信息给编译器:

变量的标识符,它的类型,以及它的作用域。

其中的前两项,即标识符和类型,C编译器可以从变量声明中显式的获得。

而第三项,作用域,编译器可以从代码中声明所在的位置推断出来。

变量的作用域是程序中变量是“活跃”的和可以访问的一段区域。

好消息是,在C语言中,只有两种基本的变量作用域类型。

要么这个变量对于整个程序是全局的,要么对于某个特定的代码块是局部的或私有的。

局部变量

在C语言中,所有变量在它们使用之前必须被声明。

实际上,一些变量必须在它们所在程序块的开头就声明,这些变量被称为局部变量。

在C语言中,一个程序块就是以大括号“{”开头和“}”结束的一个程序的任何一个子段。

所有的局部变量必须立刻跟在大括号“{”后面被声明。

下面的代码就是一个简单的从键盘输入一个数字并在显示器上显示出来的C程序。

整数变量echo在包含main函数代码的程序块中被声明,它只对于main函数可见。

如果程序中包含有除main函数之外的任意其他函数,这个变量是其他函数无法访问的。

典型地说,正如代码中的echo,大部分局部变量都要在它们被使用的程序块的开头被声明。

在同一函数的不同块中,使用相同的名字来声明两个不同的变量是可能的,有时也是很有用的。

例如,在同一个程序的几个不同的循环中,使用名字count来表示计数变量会很方便。

只要共享同一名字的不同变量在不同的程序块里被声明,C语言就允许这样做。

在下一节中,我们要讨论的例子,即图12.1,提供了一个这样的例子。

全局变量

对比只能在被声明的程序块中访问的局部变量,全局变量可以在整个程序中被访问。

它们在程序执行期间,一直保持它们的存储和数值。

下面的代码既包含了一个全局变量,又包含了main函数的局部变量:

#include

intglobalVar=2;/*这是全局变量*/

intmain()

{

intlocalVar=3;/*这是main函数的局部变量*/

printf("Global%dLocal%d\n",globalVar,localVar);

}

在某些编程情况下,全局变量极其有用,但是初学编程的人经常被建议采用一种更多地使用局部变量而非全局变量的编程风格。

因为全局变量是公用的而且可以在代码中的任何地方被修改,大量地使用全局变量会使你的代码更易出现缺陷并更难以重新使用和修改。

这本书中几乎所有的C语言代码的例子,我们都只用局部变量。

让我们看一个稍微有点复杂的例子。

图12.1中的C程序与之前的程序很类似,唯一的不同是我们在main中加入了一个子块。

在这个子块中,我们声明了一个新变量localVar。

它与在main的开头声明的局部变量有着相同的变量名。

执行这个程序,你会注意到当子块执行时,之前的lovalVar变量值是不可见的,也就是说,相同名字的变量的新的声明取代了之前的变量。

一旦子块执行结束,之前的localVar变量值再次变为可见。

这是一个被称为嵌套的作用域的例子。

变量的初始化

既然我们已经讨论了全局变量和局部变量,让我们回答我们早些时候提出的问题:

如果一个变量未被初始化,其初始值将会是什么?

在C语言中,缺省情况下,局部变量以一个未知的值开始。

也就是说,一个局部变量被分配的存储单元是未被清零的,因此它包含着前一个存储在那里的值。

更一般的说,在C语言中,局部变量是未被初始化的(特别是所有的自动存储类型的变量)。

相反地,全局变量(以及所有其他的静态存储类型变量)则是当程序开始执行时,被初始化为0。

12.2.4更多的例子

让我们多查看几个C语言中变量声明的例子。

下面的例子说明了这一章中讨论的三种基本类型的声明。

有些声明没有初始化,有些则有。

注意浮点数和字符字面常量在C语言中是如何被表达的。

doublewidth;

doublepType=9.44;

doublemass=6.34E2;

doubleverySmallAmount=9.1094E-31;

doubleveryLargeAmount=7.334553E102;

intaverage=12;

intwindChillIndex=-21;

intunknownValue;

charcar='A';

charnumber='4';

在C语言中,让字面常量为十六进制的数值也是可能的。

一个前缀是0x的字面常量会被作为十六进制数对待。

在下面的例子中,所有三个整型变量都使用十六进制的字面常量初始化。

intprogramCounter=0x3000;

intsevenBits=0xA1234;

intvalueD=0xD;

问题:

如果我们在声明变量之后,执行printf("%d\n",valueD);会发生什么?

你会期望在与valueD相关的存储单元中找到什么位组合?

12.3运算符

在介绍完C语言中的变量基础后,我们现在准备研究运算符。

C语言,像其它高级语言一样,支持一套丰富的允许程序员操作变量的运算符集合。

一些运算符执行算术运算,一些执行逻辑运算,其它的执行数值之间的比较。

这些运算符允许程序员以一种比用汇编语言指令序列表达更自然,更方便,更简洁地方式表达一个运算。

给出一些C代码后,编译器的工作就是接受这些代码,并将它们转化成能被底层硬件执行的机器代码。

在C程序被编译到LC-3上的情况下,编译器必须把程序可能包含的任何运算翻译成LC-3指令集中的指令——假如LC-3只有很少的运算指令,这显然不是一件容易的工作。

为了帮助说明这一点,我们查看一个将两个整数相乘的简单的C语句生成的代码。

在以下的代码片断中,x、y和z是整数变量,x和y是被乘的数,结果赋值给z。

z=x*y;

既然没有一条单独的LC-3指令来将两个数值相乘,我们的LC-3编译器必须生成实现两个整数(可能是负数)相乘的代码序列。

一种可能的实现方法是把x的值重复自加y次。

这段代码类似于第十章里的计算器的例子。

图12.2列出了由LC-3编译器生成的LC-3代码。

假设寄存器5(R5)包含的是变量x被分配的存储器地址。

位于那个单元之前的空间分配给了变量y(即R5-1),而在它之前的空间被预留给变量z。

尽管这种在存储器中分配变量的方法可能一开始看上去有点奇怪,我们会在后面的12.5.2节解释它。

ANDR0,R0,#0;R0<=0

LDRR1,R5,#0;取出x的值

LDRR2,R5,#-1;取出y的值

BRZDONE;如果y是0,我们结束

BRPLOOP;如果y是正数,开始乘法

;y是负数

NOTR1,R1;

ADDR1,R1,#1;R1<=-x

NOTR2,R2

ADDR2,R2,#1;R2<=-y(-y是一个正数)

LOOPADDR0,R0,R1;乘法循环

ADDR2,R2,#-1;结果在R2中

BRPLOOP

DONESTRR0,R5,#-2;Z=x*y;

图12.2C语言乘法的LC-3代码

12.3.1表达式和语句

在我们继续讨论运算符之前,我们先来研究C语言的语法,以帮助阐明一些在C程序中用到的语法符号。

我们可以使用运算符,比如前面的例子中的乘法运算符,把变量和字面常量结合起来形成一个C表达式。

在该例子中,x*y是一个表达式。

表达式可以被组合起来形成一条语句。

例如,z=x*y;是一个语句。

C中的语句完全就像英语中的句子。

正如一个句子拥有一个完整的思想或动作一样,一条C语句表达了一个被计算机执行的完整的工作单元。

所有的C语句都以一个分号结束,(或者是像我们将要在下一段看到的大括号“”)。

用分号表示一个语句的结束,与英语中用标点符号表示一个句子的结束的方法是一样的。

C语言中一个有趣的(或者奇怪的)特点是可以创造一个不表示任何计算的语句,但是它在语法上被认为是语句。

这条空语句就是一个简单的分号,它不完成任何操作。

一条或者多条简单的语句组合起来形成一个复合语句,即通过把这些简单的语句包围在大括号“{}”内形成一个块。

按照语法,复合语句等价于简单语句。

我们将在下一章看到许多复合语句的真正使用。

下面的例子显示了一些简单的,复合的和空的语句。

12.3.2赋值运算符

我们已经看过C语言的赋值运算符的例子。

它的符号是等号,=。

该运算符通过首先计算出右手边的值,再将右手边的数值赋值给左手边的对象起作用。

例如,在C语句

a=b+c;

中,变量a的值会被设置为表达式b+c的值。

注意,尽管表示相等的算术符号与C语言中的赋值符号相同,但它们有不同的意义。

在数学中,使用等号,是要说明右手边和左手边的表达式是相等的。

在C语言中,使用“=”运算符,是让编译器生成使左手边改变为等于右手边的数值的代码。

换句话说,左手边的数值被赋以右手边的数值。

让我们看看当LC-3的C编译器为一条包含赋值运算符的语句生成代码时,发生了什么。

下面的C语句表示整型变量x被增加4。

x=x+4;

这条语句的LC-3代码是非常易懂的。

这里,R5包含了变量x的地址。

在C中,所有表达式都可以计算出一个特定类型的数值。

在前面的例子中,表达式x+4计算出的是整数数值,因为我们将整数4加到另一个整数上(变量x)。

这个整数结果就被赋给一个整数变量。

但是如果我们构造了一个混合类型的表达式,例如x+4.3,会发生什么呢?

C语言中的一般规则是像这样的混合表达式会将整数转换为符点数类型。

如果一个表达式包含了整数与字符类型,那么它将被提升为整数。

一般的,在C语言中,较短的类型会被转换成较长的类型。

要是我们想把一种类型的表达式赋值给另一种类型的变量,例如,x=x+4.3,会怎么样?

在C语言中,一个变量的类型是保持不变的(意味着不能被改变),所以这个表达式会被转换为变量的类型。

在这个例子中,符点型表达式x+4.3被转换成整数。

在C语言中,符点数值通过省略小数部分被舍为整数值。

例如,当从浮点数转化为整数时,4.3会被舍为4,5.9会被舍为5。

12.3.3算术运算符

算术运算符是很容易就能理解的。

许多运算与对应的符号都是我们所习惯的,从小学学习算术的时候就开始使用了。

例如,“+”代表加法,“-”代表减法,“*”代表乘法(为了避免与字母x的混淆,这与我们平时所习惯的乘号不同),还有“/”代表除法。

就像我们用手做算术一样,也有一个表达式计算的顺序。

乘法与除法先运算,之后是加法和减法。

运算符被计算的顺序被称为优先级,我们将在下一节做更详细的讨论。

下面是使用算术运算符形成的几条C语句。

C有一个对我们来说不如+、-、*和/一样熟悉的算术运算符。

这就是模运算符“%”,(也被称为整数求余运算符)。

为了说明这个运算,考虑当我们将两个数相除,会发生什么?

当用C语言执行整数除法时,小数部分会被忽略,整数部分会被作为结果。

表达式“11/4”计算出2。

模运算符“%”,可以被用作计算余数。

例如,“11%4”计算出3。

换句话说,(11/4)*4+(11%4)等于11。

在下面的例子中,所有的变量都是整数。

表12.1列举了所有的算术运算符和它们的符号。

乘法,除法和模运算都比加法和减法拥有更高的优先级。

12.3.4计算顺序

在处理下面的C运算符集合之前,我们先来回答一个重要的问题:

作为下面的语句的结果,x中将保存什么值?

x=2+3*4;

优先级

正如我们手工做算术一样,对于计算哪一个表达式是有顺序的。

这个顺序被称为优先级。

例如,当进行算术运算时,乘法和除法比加法和减法有更高的优先级。

对于算术运算符,C的优先级规则与我们在小学算术里教的相同。

在前面的语句里,因为乘法运算符比加法有更高的优先级,x被赋值为14。

那就是说,表达式的计算与2+(3*4)相同。

结合性

但是优先级相同的运算符呢?

下面的语句计算出的结果是什么?

x=2+3-4+5;

取决于我们首先计算哪个运算符,表达式2+3-4+5的值可能等于6或-4。

既然所有运算符的优先级都是相同的(也就是说,C中加法和减法有相同的优先级),我们需要一个明确的关于在C中应该如何计算那样的表达式的规则。

对于相同优先级的运算符,它们的结合性决定了被计算的顺序。

对于加法和减法的情况,二者都是自左向右结合。

因此2+3-4+5的计算与((2+3)-4)+5一样。

C的所有运算符的优先级和结合性规则的完整集合提供在本章末尾的表12.5和表D.4中。

我们建议你不要记这张表(除非你想向你的朋友提供这些细节)。

相反的,重要的是知道优先级规则的存在,以及大概的理解隐藏在它们后面的逻辑。

无论你何时需要了解特定运算符之间的关系,你都可以引用这张表。

然而,有一个安全措施,即圆括号。

圆括号

圆括号不考虑计算规则而直接地指明哪一个运算先于其他运算执行。

与算术中一样,计算总是从最里面的一组圆括号开始。

如果我们想让一个子表达式首先被执行,我们就用圆括号将该子表达式包围起来。

所以在下面的例子中,假定a、b、c和d都等于4。

语句

x=a*b+c*d/4;

等价于

x=(a*b)+((c*d)/4);

在这两个语句中,x的结果都为20。

这里程序总会在运用优先级规则之前,首先计算最里面的子表达式的值,再向外计算。

如果a、b、c和d都为4,下面的表达式得出什么结果?

x=a*(b*c)*d/4;

圆括号可以增强代码的可读性。

大多数读你的代码的人都不可能记得C的优先级规则。

正是这个原因,在长或复杂的表达式中,通常使用圆括号在风格上更可取,即使没有它们代码仍可正常工作。

12.3.5按位运算符

我们现在回到C运算符的讨论。

C有一个对一个值进行位运算的被称作按位运算符的运算符集合。

那就是说,它们对一个值的每一位执行诸如与,或,非,异或的逻辑运算。

例如,C的按位运算符“&”执行一个类似于LC-3中AND指令的运算。

那就是说,运算符“&”对两个输入的操作数一位一位地执行“与”运算。

C运算符“|”执行按位“或”运算。

运算符“~”执行按位“非”并且只有一个操作数(即它是一元运算符)。

运算符“^”执行按位“异或”。

下面是对16位的数值使用这些运算符的表达式的例子。

C的按位运算符集合中有两个移位运算符:

<<,执行左移,和>>,执行右移。

它们两个都是二元运算符,即它们需两个操作数。

第一个操作数是被移位的数值,第二个操作数是移动的位数。

在左移中,空出的位用零填充,在

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > PPT模板 > 其它模板

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1