http://blog.sina.com.cn/s/blog_491fee8d010009sa.html
针 (Pointers)详解
指针 (Pointers)
我们已经明白变量其实是可以由标识来存取的内存单元。但这些变量实际上是存储在内存中具体的位置上的。对我们的程序来说,计算机内存只是一串连续的单字节单元(1byte cell),即最小数据单位,每一个单元有一个唯一地址。 计算机内存就好像城市中的街道。在一条街上,所有的房子被顺序编号,每所房子有唯一编号。因此如果我们说芝麻街27号,我们很容易找到它,因为只有一所房子会是这个编号,而且我们知道它会在26号和28号之间。 同房屋按街道地址编号一样,操作系统(operating system)也按照唯一顺序编号来组织内存。因此,当我们说内存中的位置1776,我们知道内存中只有一个位置是这个地址,而且它在地址1775和1777之间。
地质操作符Address (dereference) operator (&)
|
// my first pointer #include <iostream.h> int main ( ) { |
value1==10 / value2==20 |
注意变量value1 和 value2 是怎样间接的被改变数值的。首先我们使用 ampersand sign (&) 将value1的地址赋给mypointer 。然后我们将10 赋给 mypointer所指向的数值,它其实指向value1的地址,因此,我们间接的修改了value1的数值。
为了让你了解在同一个程序中一个指针可以被用作不同的数值,我们在这个程序中用value2 和同一个指针重复了上面的过程。
下面是一个更复杂一些的例子:
// more pointers #include <iostream.h> int main () { |
value1==10 / value2==20 |
上面每一行都有注释说明代码的意思:ampersand (&) 为”address of”,asterisk (*) 为 “value pointed by”。注意有些包含p1 和p2 的表达式不带星号。加不加星号的含义十分不同:星号(*)后面跟指针名称表示指针所指向的地方,而指针名称不加星号(*)表示指针本身的数值,即它所指向的地方的地址。
另一个需要注意的地方是这一行:
int *p1, *p2;
声明了上例用到的两个指针,每个带一个星号(*),因为是这一行定义的所有指针都是整型int (而不是 int*)。原因是引用操作符(*) 的优先级顺序与类型声明的相同,因此,由于它们都是向右结合的操作,星号被优先计算。我们在
section 1.3: Operators
中已经讨论过这些。注意在声明每一个指针的时候前面加上星号asterisk (*)。
指针和数组Pointers and arrays
数组的概念与指针的概念联系非常解密。其实数组的标识相当于它的第一个元素的地址,就像一个指针相当于它所指向的第一个元素的地址,因此其实它们是同一个东西。例如,假设我们有以下声明:
int * p;
下面的赋值为合法的:
p = numbers;
这里指针p 和numbers 是等价的,它们有相同的属性,唯一的不同是我们可以给指针p赋其它的数值,而numbers 总是指向被定义的20个整数组中的第一个。所以,p只是一个普通的指针变量,而与之不同,numbers 是一个指针常量(constant pointer),数组名的确是一个指针常量。因此虽然前面的赋值表达式是合法的,但下面的不是:
numbers = p;
因为numbers 是一个数组(指针常量),常量标识不可以被赋其它数值。
由于变量的特性,以下例子中所有包含指针的表达式都是合法的:
// more pointers #include <iostream.h> int main () { |
10, 20, 30, 40, 50, |
在数组一章中我们使用了括号[]来指明我们要引用的数组元素的索引(index)。中括号[]也叫位移(offset)操作符,它相当于在指针中的地址上加上括号中的数字。例如,下面两个表达式互相等价:
// a [offset of 5] = 0
*(a+5) = 0;
// pointed by (a+5) = 0
不管a 是一个指针还是一个数组名, 这两个表达式都是合法的。
指针初始化Pointer initialization
当声明一个指针的时候我们可能需要同时指定它们指向哪个变量,
int *tommy = &number;
这相当于:
int *tommy;
tommy = &number;
当给一个指针赋值的时候,我们总是赋给它一个地址值,而不是它所指向数据的值。你必须考虑到在声明一个指针的时候,星号 (*) 只是用来指明它是指针,而从不表示引用操作符reference operator (*)。记住,它们是两种不同操作,虽然它们写成同样的符号。因此,我们要注意不要将以上的代码与下面的代码混淆:
int *tommy;
*tommy = &number;
这些代码也没有什么实际意义。
在定义数组指针的时候,编译器允许我们在声明变量指针的同时对数组进行初始化,初始化的内容需要是常量,例如:
char * terry = "hello";
在这个例子中,内存中预留了存储”hello” 的空间,并且terry被赋予了向这个内存块的第一个字符(对应’h’)的指针。假设”hello”存储在地址1702,下图显示了上面的定义在内存中状态:
这里需要强调,terry 存储的是数值1702 ,而不是’h’ 或 “hello”,虽然1702 指向这些字符。
指针terry 指向一个字符串,可以被当作数组一样使用(数组只是一个常量指针)。例如,如果我们的心情变了,而想把terry指向的内容中的字符’o’ 变为符号’!’ ,我们可以用以下两种方式的任何一种来实现:
*(terry+4) = ‘!’;
记住写 terry[4] 与*(terry+4)是一样的,虽然第一种表达方式更常用一些。以上两个表达式都会实现以下改变:
指针的数学运算Arithmetic of pointers
对指针进行数学运算与其他整型数据类型进行数学运算稍有不同。首先,对指针只有加法和减法运算,其它运算在指针世界里没有意义。但是指针的加法和减法的具体运算根据它所指向的数据的类型的大小的不同而有所不同。
我们知道不同的数据类型在内存中占用的存储空间是不一样的。例如,对于整型数据,字符char 占用1 的字节(1 byte),短整型short 占用2 个字节,长整型long 占用4个字节。
假设我们有3个指针:
short *myshort;
long *mylong;
而且我们知道他们分别指向内存地址1000, 2000 和3000。
因此如果我们有以下代码:
myshort++;
mylong++;
就像你可能想到的,mychar的值将会变为1001。而myshort 的值将会变为2002,mylong的值将会变为3004。原因是当我们给指针加1时,我们实际是让该指针指向下一个与它被定义的数据类型的相同的元素。因此,它所指向的数据类型的长度字节数将会被加到指针的数值上。以上过程可以由下图表示:
这一点对指针的加法和减法运算都适用。如果我们写以下代码,它们与上面例子的作用一抹一样: mychar = mychar + 1;
mylong = mylong + 1;
这里需要提醒你的是,递增 (++) 和递减 (–) 操作符比引用操作符reference operator (*)有更高的优先级,因此,以下的表达式有可能引起歧义:
*p++ = *q++;
第一个表达式等同于*(p++) ,它所作的是增加p (它所指向的地址,而不是它存储的数值)。
在第二个表达式中,因为两个递增操作(++) 都是在整个表达式被计算之后进行而不是在之前,所以*q 的值首先被赋予*p ,然后q 和p 都增加1。它相当于:
p++;
q++;
像通常一样,我们建议使用括号()以避免意想不到的结果。
指针的指针Pointers to pointers
C++ 允许使用指向指针的指针。要做到这一点,我们只需要在每一层引用之前加星号(*)即可:
char * b;
char ** c;
a = ‘z’;
b = &a;
c = &b;
假设随机选择内存地址为7230, 8092 和10502,以上例子可以用下图表示:
(方框内为变量的内容;方框下面为内存地址)
这个例子中新的元素是变量c,关于它我们可以从3个方面来讨论,每一个方面对应了不同的数值:
c 是一个(char **)类型的变量,它的值是8092
*c 是一个(char*)类型的变量,它的值是7230
**c 是一个(char)类型的变量,它的值是’z’
空指针void pointers
指针void 是一种特殊类型的指针。void 指针可以指向任意类型的数据,可以是整数,浮点数甚至字符串。唯一个限制是被指向的数值不可以被直接引用(不可以对它们使用引用星号*),因为它的长度是不定的,因此,必须使用类型转换操作或赋值操作来把void 指针指向一个具体的数据类型。
它的应用之一是被用来给函数传递通用参数:
// integer increaser #include <iostream.h> void increase (void* data, int type) { int main () { |
6, 10, 13 |
sizeof 是C++的一个操作符,用来返回其参数的长度字节数常量。例如,sizeof(char) 返回 1,因为 char 类型是1字节长数据类型。
函数指针Pointers to functions
C++ 允许对指向函数的指针进行操作。它最大的作用是把一个函数作为参数传递给另外一个函数。声明一个函数指针像声明一个函数原型一样,除了函数的名字需要被括在括号内并在前面加星号asterisk (*)。例如:
// pointer to functions #include <iostream.h> int addition (int a, int b) { int subtraction (int a, int b) { int (*minus)(int,int) = subtraction; int main () { |
8 |
在这个例子里, minus 是一个全局指针,指向一个有两个整型参数的函数,它被赋值指向函数subtraction,所有这些由一行代码实现:
int (* minus)(int,int) = subtraction;