3.4.4 指针简介
不管什么时候运行一个程序,都是首先把它装入(一般从磁盘装入)计算机内存。因此,程序中的所有元素都驻留在内存的某处。内存一般被布置成一系列连续的内存位置;我们通常把这些位置看做是8位字节,但实际上每一个空间的大小取决于具体机器的结构,一般称为机器的字长(word size)。每一个空间可按它的地址与其他空间区分。为了便于讨论,我们认为所有机器都使用有连续地址的字节从零开始,一直到该计算机的内存的上限。
因为程序运行时驻留内存中,所以程序中的每一个元素都有地址。假设我们从一个简单的程序开始:
程序运行的时候,程序中的每一个元素在内存中都占有一个位置。甚至函数也占用内存。我们将会看到,定义什么样的元素和定义元素的方式通常决定元素在内存中放置的地方。
C和C++中有一个运算符会告诉我们元素的地址。这就是‘&’运算符。只要在标识符前加上‘&’,就会得出标识符的地址。可以修改程序YourPets1.cpp,用以打印所有元素的地址。修改如下:
(long)是一种类型转换(cast)。意思是“不要把它看做是原来的类型,而是看做是long类型”。这个类型转换不是必须的,但是如果没有的话,地址是以十六进制的形式打印,所以转换为long类型会增加一些可读性。
这个程序的结果会随计算机、操作系统和各种其他的因素的不同而变化,但我们总会看到一些有趣的现象。在我的计算机上运行一次的结果如下:
现在可以看到在函数main()的内部和外部定义的变量存放在不同的区域;当对语言有更多的了解时,就会明白为什么如此。同样,f()出现在它自己的区域,在内存中代码和数据一般是分开存放的。
另一个值得注意的有趣的事情是,相继定义的变量在内存中是连续存放的。它们根据各自的数据类型所要求的字节数分隔开。这个例子中只使用了整型数据类型,变量cat距离变量dog 4个字节,变量bird距离变量cat 4个字节,等等。所以在这台机器上,一个int占4个字节。
这个有趣的实验显示了怎样分配内存,那么利用地址能干什么呢?能做的最重要的事就是,把地址存放在别的变量中以便以后使用。C和C++有一个专门的存放地址的变量类型。这个变量叫做指针(pointer)。
定义指针的运算符和用于乘法的运算符‘*’是一样的。正如我们将看到的那样,编译器会根据它所在的上下文知道它表示的不是乘法。
定义一个指针时,必须规定它指向的变量类型。可以先给出一个类型名,然后不是立即给出变量的标识符,而是在类型和标识符之间插入一个星号,这就是说“等一等,它是一个指针”。一个指向int的指针如下所示:
把‘*’和类型联系起来似乎是很明白且易读的,但是事实上可能容易产生错觉。有人可能更倾向于说“整型指针”好像它是一个单独的类型。可是,对于int或其他的基本数据类型,可以写成
而对于指针,可能想写成
C的语法(并由C++语法继承)不允许像这样合乎情理的表达。在上面的定义中,只有ipa是一个指针,而ipb和ipc是一般的int(可以认为“*和标识符结合得更紧密”)。因此,最好是每一行定义一个指针;这样就能得到一个清晰的语法而不会混淆:
C++编程的一般原则是在定义时进行初始化,事实上这种形式工作得很好。例如,上面的变量并没有初始化为任何一个特定的值,它们所具有的是一些无意义的值。如果写成下面的形式会更好:
现在已经初始化了a和ipa, ipa存放a的地址。
一旦有一个初始化了的指针,我们能做的最基本的事就是利用指针来修改它指向的值。要通过指针访问变量,可以使用以前定义指针使用的同样的运算符来间接引用这个指针,如像:
现在a的值是100而不是47。
这些是指针基础:可以保存地址,可以使用地址去修改原先的变量。但还是留下问题:为什么要通过另一个变量作为代理来修改一个变量?
通过对指针的介绍,我们可以把答案分为两大类:
1)为了能在函数内改变“外部对象”。这可能是指针最基本的用途,并且在下一小节对它进行验证。
2)为了获得许多灵活的编程技巧,而这些将在本书的其余部分见到。