我们前面讲到的赋值语句是最简单的赋值语句。出现在赋值符号左边的只是一个简单的变量名。实际上,能够出现在赋值符号左边的内容远远不止如此。我们下面就来讲解更加复杂的赋值语句——复合结构的赋值。
复合结构的赋值
什么叫做复合结构呢?比如,我们大家一般都用过手机,也应该都知道,手机里面有个联系人名录,里面记录了联系人的姓名、手机、住宅电话、公司电话、备注等信息。在这个例子里面,每个联系人条目就是一个包含了姓名、手机、住宅电话、公司电话、备注等信息的复合结构。
这个概念不难理解,但是,如果认真详细解释起来,还需要费一大堆口舌。本书不打算在这种简单易懂的概念上浪费口水。读者如果有什么不明白的,可以参考具体语言中的复合结构的概念和定义。比如,在C语言中,复合结构的对应数据类型叫做“structure”(结构)。在C++、Java、Python、Ruby等更加高级的面向对象的语言当中,复合结构的对应数据类型叫做“Class”(类)。
面向对象是一个非常重要的概念,是命令式编程语言的主流编程模型,后面会加以详细讲述。
现在,假设我们有一个叫做contact(联系方式)的复合结构数据,其中包含name、mobile、home_phone、office_phone、memo等属性。我们就可以这样一条条设置contact这个数据的每一个属性。
contact.name = “Tom”
contact.mobile = “1338978776”
contact.home_phone = “8978776”
contact.office_phone = contact.home_phone
contact.memo = “Tom is a SOHO. He works at home.”
上述语句中的“.”表示访问复合结构的内部数据。比如,contact.name就表示contact这个复合结构数据中的name属性。这也是高级命令式语言的一种语法惯例。
从上面的例子中可以看到,复合结构变量的属性的用法和简单变量完全相同。复合结构变量的属性既可以出现在赋值符号的左边,也可以出现在赋值符号的右边。比如,contact.home_phone先是出现在“=”的左边,接着又出现在“=”的右边。
那么,我们如何在内存结构中理解复合结构呢?首先,我们还是要给内存单元贴上标签。我们想象一下,在一个布满了小格子的大柜子里面,选出一个小格子,然后在上面贴上“contact”这个标签。然后,我们从贴上“contact”的那个小格子开始,根据每个属性的数据宽度(即占用最小内存单元的个数),依次贴上“name”、“mobile”、“home_phone”、“office_home”、“memo”等几个标签。
在这个例子中,“contact”就相当于内存中的一个基本地址,而那些属性则相当于以基本地址为基础的几个偏移地址。
当我们访问contact的属性的时候,实际上就相当于访问“contact”基本地址再加上属性偏移地址的那个单元格的内容。比如,contact.name实际上就是contact基本地址加上name属性偏移地址之后的那个单元格的内容。对于复合结构的属性的访问,实际上就是一次内存中的间接寻址。
当我们定义了一个包含了多个属性的复合结构的时候,实际上就相当于我们自己定义了一套内存结构映射方案。这还不是最简单的情况,复合结构里面还有可能包含复合结构。事实上,复合结构的嵌套层次是没有限制的,可以嵌套到任意深度。因此,我们有可能写出这样的访问深层次属性的代码:contact.address.city.zipcode。
我们需要把复合结构的概念理解道内存结构映射的层次吗?需要。即使你现在不需要,以后早晚也会需要。随着你对编程语言掌握的深入,你早晚需要理解到这个层次。与其到时候费二遍功,还不如现在就一次搞定。
下面我们看看数组类型的复合结构在内存中的映射。
数组类型内存映射
复合结构并非唯一的内存结构映射定义。在命令式语言中,还有一个极为常见的类型——数组类型,同样对内存结构进行了映射。
数组类型对内存结构的映射是一种十分整齐的映射。我们可以想象一列整整齐齐的单元格,每个单元格的数据宽度完全相等。因此,我们可以通过简单的等距位移来访问其中某一个单元格的内容。事实上,数组正是通过数字下标来访问其中某一个位置的数据的。
比如,假设我们有一个数组变量array。我们可以想象一列长长的宽度相同的单元格,第一个单元格上贴着一个标签“array”。
我们想访问array数组中第30个数据。我们就可以这么写,array[30],就可以定位到array数组的第30个数据单元。
“[]”这样的方括号,表示访问一个数组中的某一个位置。这也是高级命令式语言的一种语法惯例。
同样,array[30]可以出现在赋值表达式的左边,也可以出现在赋值表达式的右边。比如:
array[30] = 1
array[31] = array[30]
需要注意的是,在很多命令式语言中,数组下标是从0开始的。因此,如果我们想访问第30个数据单元,很多情况下,我们必须写成array[29]。
综上所述,出现在赋值符号左边的变量,主要就是三种——简单变量、复合结构变量、数组变量。而且,这三种变量都有一个特点,他们都可以唯一定位到内存中的某一个具体位置。这体现了赋值语句的最根本的含义——将一个表达式执行的结果存入到某一个指定的内存单元中去。我们对赋值语句的理解,必须达到这个层次,才能够正确理解随之而来的一系列相关概念。
另外,在一些更高级别的命令式编程语言(如Python、Ruby等),实现了部分函数式编程语言(Functional Programming Language)的部分特性,如模式匹配(Pattern Match)这样的特性。在这些语言中,有可能出现一次对多个变量同时赋值的赋值语句。比如:
(a, b) = (1, 2)
a, b = 1, 2
某些情况下,这种语法用起来相当方便。比如,某个复合结构中有多个属性,我们想就把其中一些属性一次性复制到多个变量中的时候,就可以这么写。
想深入了解这种语法的读者,可以去研究一下函数式编程语言中的模式匹配特性。
当然,在我个人看来,即使在函数式编程语言中,模式匹配也不是什么核心的概念,只不过是一种简化书写的语法糖。掌握不掌握,都对编程核心概念的理解没有什么本质影响。
以上讲述的赋值语句都是“显式”赋值语句,即存在明确的赋值符号(=)的赋值语句。除了显式赋值语句之外,还有一种特殊的隐式赋值语句——参数传递。这个在后面小节再做介绍。