首页 > 代码库 > 关于指针
关于指针
指针是什么?
指针是C语言中的一类数据类型的统称。这种类型的数据专门用来存储和表示内存单元的编号,以实现通过地址得以完成的各种运算。
9.1.3 指针是一类数据的泛称
当某个数据 的数据类型是指针时,通常也简称这个数据是一个指针。很显然,在这里”指针“具有”名词“的含义。而指针表示 ”数据类型“含义时,显然具有“形容词”的意味。这种“一词多用”的现象,对于熟悉C语言特点的人来说并不值得大惊小怪,C语言本身也是这样的。比如,"[]"即可以作为类型说明符也可以作为运算符。
9.1.5 指针的分类
尽管有无穷多种指针类型,但从指针所关联的数据类型方面看,指针可以分为3类:指向数据对象的指针(Object Pointer)、指向函数的指针(Function Pointer)、指向虚无的指针("void *" 类型)。前两者都内存中的实体(数据和一段函数的执行代码)有关,而"void *"类型的指针则仅仅是一个值,是纯粹的地址。“指针就是地址”这样的说法对于"void *" 这种类型的指针是成立的。但对于与一段具体内存实体相关联的指针类型来说,这种说法是极其片面的,甚至片面到了几乎完全忽略了指针的本质而只剩下了指针的皮毛的地步。正确的说法是,指针的值(右值)是地址,这与"指针就是地址"是完全不同的概念。学习指针最重要的内容通常是关心指针的值以外的东西,而指针的值 ——下面将会看到,那几乎倒是无关紧要的。
从所具有的运算方面看,这3类指针各自拥有不同的运算种类的集合。有有运算各类多些,有些少些。
指向数据对象的指针
什么是“数据对象”
所谓“数据对象”(Object),含义如下
(1)是内存中一段定长的、以Byte为基本单位的连续区域。
(2)这段内存区域中的内容表示具有某种类型的一个数据。
数据对象的类型不一定是简单的数据类型(int、long、double)等,也可以是派生类型,比如数组,甚至指针等。
而所谓的“指向”(Pointer to)的含义是指针与这块具有类型含义的整体的关联。例如,对于
int i;
"i" 可以表示它所占据的内存块,当说到某个指针指向 "i"时,期确切的含义是指向 "i" 所占据内存的整体。显然这里提到的 "i" 是左值意义上的 "i"。
函数类型不属于数据对象
9.2.2 一元"&"运算
尽管前面各章从来没有提到指针,但实际上在前面编程的过程中已经和指针打过无数次交道了。这可能令人感到吃惊,但却是事实。
比如,在调用scanf()函数输入变量值的时候,在实参中经常可以看到的“&”,实际上就是在求一个指向某个数据对象的指针。
对于下面的变量定义
double d;
表达式 "&d"就是一个指针类型的数据,类型是“double *”, 这种类型的指针被称为是指向“double”类型数据的指针。
前面讲过,作为二元运算符,“&”是按位与运算。当“&”作为一个一元运算符时,要求它的运算对象是一个左值表达式(一块内存),得到的是指向这块内存(类型)的指针。而一个变量的名字的含义之一就是这个变量所占据的内存。大多数人在多数情况下关心的只是变量名的另一个含义——值,这可能是学不好指针以有C语言的一个主要原因。在些,简要地复习一下C语言的一些最基本的内容。假如有如下定义:
double d = 3.0;
那么,应该如何理解表达式 "d = d+ 5.0" 呢?
这是一个赋值表达式,表示的确切含义是"取出变量 ‘d‘ 的值与常量 ‘5.0‘ 相加,然后把结果放到变量‘d‘ 所在的内存中去"。请特别注意在赋值号"="的左边和右边,"d"这个标识符的含义是不同的:在赋值号"="右边的“d”表示的是"d"的值,计算机的动作是取出这个值(本质上是在运算器中建立"d"的副本),并不关心“d”存放在内存中的什么地方;而在赋值号"="左边的"d"表示的是"d"所在的内存空间,是把一个值放入这块内存中去,后一个动作与"d"中的值没有什么关系(只是把原来的值擦除),“d”中原来有什么值都不妨碍把一个新的值放入其中,也对新的值没有任何影响。
由此可见,同一个变量名确定有两种含义。针对两种不同的含义,计算机能进行的操作也不同。换句话说,对于某些运算,变量名的含义是其右值;而对于另一些运算,变量名的含义是其左值。编译器根据上下文来分辨变量名究竟是哪含义。对于用C语言编程的人来说,不分辨清楚这两种含义就不可能透彻地理解C语言。
再举个例子,在“sizeof d”这个表达式中,"d"的含义也是"d"占据内存而不是"d"的值——无论"d"的值是多少,表达式"sizeof d"的值都为8。
在表达式“&d”中,“d”的含义也是“d”所在的内存而不是“d”的值,“d”的值是多少都对“&”的运算结果没有任何影响。
有一种说法称一元“&”运算是求地址运算,这种说法既是片面的,也是不严格的,同时对于学习指针有很大的负面作用。理由如下。
在C语言中根本没有“地址”这种数据类型,只有“指针”数据类型,而指针的值才是一个地址。用地址即指针的值的概念偷换指针的概念,显然是以偏概全。更为严重的是,这种说法使得许多人根本就不知道“&d”是个指针,也掩盖了“&d”指向一块内存的事实,因为“&d”的值仅仅是“d”所占据的那块内存单元中第一个byte的编号。
那么“&d”的值是多少呢?实际上多数情况下,尤其是对于初学者来说,根本没必要关心这个值是多少,也不可能事先知道这个值。因为为变量“d”安排存储空间是编译器的工作,编译器是根据程序运行时内存中的实际情况“随机”为变量“d”安排内存的。源程序的作者是永远不可能为变量“指定”一块特定的存储空间,同样也不可能改变“d”在内存中的存储位置。
这样,“&d”就是一个既不可能通过代码被赋值也不可能通过代码被改变的值,因而是个常量,叫做常量指针,类型是"double *"。这样的常量不可以被赋值也不可以进行类似"++"、"--"之类的运算,因为改变“&d”的值就相当于改变了变量“d”的存储空间的位置,然而这是根本不可能的。
当然,在程序运行之后,具体来说是“d”的存储空间确定之后(也就是定义了变量“d”之后,因为这时“d”才开始存在),“&d”的值是确实可以知道的(其实知道了也没什么用)。如果想查看一下,可能通过调用printf()函数用"%p"格式输出(指针类型数据的输出格式是“%p)。如下面所示
程序代码9-1
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
double d;
printf("%p\n", &d);
return 0;
}
这段代码的程序运行结果并不能事先确定,这和程序运行的具体环境有关。
关于指针