指针是一种特殊的变量,它存储的值被解释为内存中的地址。搞清楚一个指针,需要搞清楚指针的四个方面:指针的类型、指针指向的类型、指针的值或者指针指向的内存区域、内存区域被指针本身占用。我们分开来分解。声明几个指针,例如:Example1:int*ptr;字符*指针;int**指针;整数(*指针)[3];int*(*ptr)[4];从上看,只需要去掉指针声明语句中的指针名称,剩下的就是指针的类型。这是指针本身的类型。我们看一下例1中各个指针的类型:int*ptr;//指针的类型是int*char*ptr;//指针的类型是char*int**ptr;//指针的类型thepointerisint**int(*ptr)[3];//指针类型为int(*)[3]int*(*ptr)[4];//指针类型为int*(*)[4]?有没有一种简单的方法可以找出指针的类型?指针指向的类型当你通过指针访问指针指向的内存区域时,指针指向的类型决定了编译器会把那块内存区域的内容当作什么。从语法上看,只需要去掉指针声明语句中的指针名称和名称左边的指针声明符*,剩下的就是指针指向的类型。例如:int*ptr;//指针指向的类型是intchar*ptr;//指针指向的类型是charint**ptr;//指针指向的类型是int*int(*ptr)[3];//指针指向的类型是int()[3]int*(*ptr)[4];//指针指向的类型是int*()[4]在指针的算术运算中,指针指向的类型与它有很大关系。指针的类型(即指针本身的类型)和指针指向的类型是两个概念。当你对C越来越熟悉的时候,你会发现,“类型”这个概念和指针混在一起,分为“指针的类型”和“指针指向的类型”两个概念,这是掌握指针的关键一。看了很多书,发现有些写得不好的书把指针这两个概念混在一起,所以书上看起来前后不一致,越看越糊涂。指针的值指针的值是指针本身存储的值,这个值会被编译器当作一个地址,而不是一般的值。在32位程序中,所有类型的指针的值都是32位整数,因为32位程序中所有的内存地址都是32位长。指针指向的内存区是从指针的值所代表的内存地址开始,长度为sizeof(指针指向的类型)的一块内存区。以后,当我们说一个指针的值为XX时,就相当于说这个指针指向了一块地址以XX为首的内存区域;当我们说一个指针指向某个内存区域时,就相当于说这个指针的值就是这个内存区域的首地址。指针指向的内存区域和指针指向的类型是两个完全不同的概念。例1中,指针指向的类型已经存在,但是由于指针还没有初始化,所以它指向的内存区域不存在,或者没有意义。以后遇到指针就应该问:这个指针是什么类型的?指针指向什么类型?这个指针指向哪里?指针本身占用的内存区域指针本身占用多少内存?你只需要使用函数sizeof(指针类型)来测量它。在32位平台上,指针本身占用4个字节的长度。指针本身占用内存的概念在确定指针表达式是否为左值时很有用。指针的算术运算指针可以加或减一个整数。指针的这种操作的意义和通常的值加减运算的意义是不一样的。例如:例2:chara[20];int*ptr=a;.....ptr++;上面的例子中,指针ptr的类型是int*,它指向的类型是int,初始化后指向一个整型变量a。接下来第三句,指针ptr加1,编译器是这样处理的:它给指针ptr的值加上sizeof(int),在32位程序中,加4.由于地址是以字节为单位的,所以ptr指向的地址从原变量a的地址向高地址方向增加4个字节。由于char类型的长度是一个字节,所以原来的ptr指向数组a中从第0单元开始的四个字节,现在指向数组a中从第4单元开始的四个字节。我们可以用一个指针和一个循环来遍历一个数组,看例子:例3:intarray[20];int*ptr=array;...//这里省略给整型数组赋值的代码。...for(i=0;i<20;i++){(*ptr)++;ptr++;}此示例将整数数组中每个单元格的值加1。由于每次循环指针ptr递增1,因此每次循环都可以访问数组的下一个单元。再看例子:例子4:chara[20];int*ptr=a;......ptr+=5;value的值加上5倍sizeof(int),在32位程序中,加上5倍4=20。由于地址的单位是字节,所以当前ptr指向的地址比加5后的ptr指向的地址向高地址方向移动了20个字节。本例中,加5前的ptr指向从数组a的单元0开始的四个字节。加上5后,ptr已经指向了数组a的合法范围之外。这种情况虽然在应用上有问题,但在语法上是可以的。这也体现了指针的灵活性。如果在上面的例子中,ptr减了5,那么过程类似,只是ptr的值减了5倍sizeof(int),新ptr指向的地址会比原来指向的地址低由原来的ptr地址方向平移了20个字节。综上所述,一个指针ptrold加上一个整数n后,结果是一个新的指针ptrnew,ptrnew的类型和ptrold的类型相同,ptrnew指向的类型也和指向的类型相同通过控制。ptrnew的值将比ptrold的值增加n倍sizeof(ptrold指向的类型)字节。也就是说ptrnew指向的内存区会比ptrold指向的内存区往更高的地址方向移动n倍sizeof(ptrold指向的类型)字节。从指针ptrold中减去一个整数n后,结果是一个新的指针ptrnew。ptrnew的类型与ptrold的类型相同,ptrnew指向的类型也与ptrold指向的类型相同。ptrnew的值会比ptrold的值减少n倍sizeof(ptrold指向的类型)字节,也就是说ptrnew指向的内存区域会向比内存低的地址方向移动ptrold指向的区域n乘以sizeof(ptrold指向的类型)字节。运算符&和*这里&是地址运算符,*在书中被称为“间接运算符”。&a的运算结果是一个指针,指针的类型就是a加*的类型,指针指向的类型就是a的类型,指针指向的地址就是a的地址。*p的计算结果是多种多样的。简而言之,*p的结果就是p指向的东西。这个东西有这些特点:它的类型就是p指向的类型,它占用的地址就是p指向的地址。例5:inta=12;intb;int*p;int**ptr;p=&a;//&a结果为指针,类型为int*,指向的类型为int,指向的地址为地址的一个。*p=24;//*p的结果,这里它的类型是int,它占用的地址就是p指向的地址,显然,*p就是变量a。ptr=&p;//&p结果是一个指针,指针的类型就是p的类型加上*,这里是int**。指针指向的类型就是p的类型,这里是int*。这个指针指向的地址就是指针p本身的地址。*ptr=&b;//*ptr是一个指针,&b的结果也是一个指针,而且两个指针的类型和指向的类型一样,所以给?amp;b赋值是没有问题的到*ptrup。**ptr=34;//*ptr的结果就是ptr指向的,这里是一个指针,对这个指针再做一次*操作,结果是一个int类型的变量。指针表达式如果一个表达式的最终结果是一个指针,那么这个表达式就称为指针表达式。以下是指针表达式的一些示例:示例6:inta,b;intarray[10];int*pa;pa=&a;//&a是一个指针表达式。int**ptr=&pa;//&pa也是一个指针表达式。*ptr=&b;//*ptr和&b都是指针表达式。pa=array;pa++;//这也是一个指针表达式。例7:char*arr[20];char**parr=arr;//如果把arr看成一个指针,arr也是一个指针表达式char*str;str=*parr;//*parr是一个指针表达式str=*(parr+1);//*(parr+1)是一个指针表达式str=*(parr+2);//*(parr+2)是一个指针表达式,因为指针表达式的结果是一个指针,所以指针表达式也有指针的四个要素:指针的类型、指针指向的类型、指针指向的内存区域、指针本身占用的内存。好吧,如果指针表达式的结果指针已经显式地具有指针本身占用的内存,则指针表达式是左值,否则它不是左值。在示例7中,&a不是左值,因为它还没有占用显式内存。*ptr是一个左值,因为指针*ptr已经占据了内存,实际上*ptr就是指针pa,既然pa在内存中已经有自己的位置,那么*ptr当然也有自己的位置。数组和指针的关系如果对声明数组的语句不理解,请参考我前段时间发的文章<<如何理解c和c++的复杂类型声明>>。数组的数组名其实可以看作是一个指针。看下面的例子:例8:intarray[10]={0,1,2,3,4,5,6,7,8,9},value;...value=array[0];//也可以写成:value=*array;value=array[3];//也可以写成:value=*(array+3);value=array[4];//也可以写成:value=*(array+4);在上面的例子中,一般来说,数组名array代表数组本身,类型是int[10],但是如果把数组看成一个指针,它指向数组的第0个单元,类型是int*,它指向的类型是数组元素的类型即int。所以*array等于0也就不足为奇了。同理,array+3是指向数组第三个元素的指针,所以*(array+3)等于3。其他以此类推。例9:char*str[3]={"Hello,thisisasample!",Hi,goodmorning.","Helloworld"};chars[80];strcpy(s,str[0]);//也可以这样写asstrcpy(s,*str);strcpy(s,str[1]);//也可以写成strcpy(s,*(str+1));strcpy(s,str[2]);//也可以写成strcpy(s,*(str+2));在上面的例子中,str是一个包含三个元素的数组,数组的每个元素都是一个指针,这些指针分别指向一个字符串。把指针数组名str当作一个指针,它指向数组的第0单元,它的类型是char*,它指向的类型是char。*str也是一个指针,它的类型是char*,并且它指向的类型是char,地址是字符串“Hello,thisisasample!”的第一个字符的地址,即'H'的地址。str+1也是一个指针,它指向数组的第一个单元,它的类型是char*,指向的类型是char。*(str+1)也是一个指针,它的类型是char*,它指向的类型是char,它指向“Hi,goodmorning.”的第一个字符'H',等等。下面总结一下数组的数组名的问题。声明一个数组TYPEarray[n],数组名array有两层意思:第一,代表整个数组,其类型为TYPE[n];第二,它是一个指针,指针的类型是TYPE*??,指针指向的类型是TYPE,也就是数组单元的类型,指针指向的内存区域是第0个单元数组,和指针本身占用一个单独的内存区域。内存区域不同。这个指针的值是不能修改的,也就是像array++这样的表达式是错误的。数组名数组在不同的表达式中可以起到不同的作用。在表达式sizeof(array)中,数组名array代表数组本身,所以sizeof函数衡量的是整个数组的大小。在表达式*array中,array充当指针,因此该表达式的结果是数组的单元格0的值。sizeof(*array)测量数组单元的大小。在表达式array+n(wheren=0,1,2,....)中,array作为一个指针,所以array+n的结果是一个指针,它的类型是TYPE*??,它指向的类型to是TYPE,它指向数组的第n个单元。因此,sizeof(array+n)衡量的是指针类型的大小。示例10:intarray[10];int(*ptr)[10];ptr=&array;在上面的例子中,ptr是一个指针,它的类型是int(*)[10],它指向的类型是int[10],我们用整个数组的首地址来初始化它。在语句ptr=&array中,array表示数组本身。本节提到函数sizeof(),那么请问,sizeof(指针名)衡量的是指针本身类型的大小,还是指针指向的类型的大小?答案是前者。例如:int(*ptr)[10];在32位程序中,有:sizeof(int(*)[10])==4sizeof(int[10])==40sizeof(ptr)==4actually,sizeof(object)衡量的是类型的大小对象本身,而不是任何其他类型的大小。指针和结构类型的关系可以声明一个指向结构类型对象的指针。例11:structMyStruct{inta;intb;intc;}MyStructss={20,30,40};//声明结构体对象ss,并将ss的三个成员分别初始化为20、30、40。MyStruct*ptr=&ss;//声明一个指向结构对象ss的指针。它的类型是MyStruct*,它指向的类型是MyStruct。int*pstr=(int*)&ss;//声明一个指向结构对象ss的指针。但是它的类型不同于它指向的类型和ptr。请问如何通过指针ptr访问ss的三个成员变量?答:ptr->a;ptr->b;ptr->c;以及如何通过指针pstr访问ss的三个成员变量?答案:*pstr;//访问ss的成员a。*(pstr+1);//访问ss的成员b。*(pstr+2)//访问ss的成员c。呵呵,虽然我在我的MSVC++6.0上调试过上面的代码,但是你要知道,这样使用pstr来访问结构体成员是不正常的。为了解释为什么不正常,让我们看看如何通过指针访问数组各单元:例12:intarray[3]={35,56,37};int*pa=数组;通过指针pa访问数组array的三个单元的方法是:*pa;//访问过的0号单元*(pa+1);//访问过的1号单元*(pa+2);//访问单元2从格式上看,与通过指针访问结构成员的非正式方法相同。所有的C/C++编译器在排列数组的单元时,总是将每个数组单元存储在一个连续的存储区中,单元之间没有空隙。但是,在存储结构对象的每个成员时,在一定的编译环境下,可能需要字对齐或双字对齐或其他对齐方式,需要在相邻的两个成员之间添加若干个“填充字节”,这可能会导致每个成员之间有几个字节的间隙。因此,在例12中,即使pstr访问了结构对象ss的第一个成员变量a,也不能保证`(pstr+1)一定能够访问到结构成员b。因为成员a和成员b之间可能存在一些填充字节,也许*(pstr+1)`只是访问了这些填充字节。这也体现了指针的灵活性。如果你的目的是看各个结构体成员之间是否有填充字节,嘿嘿,这是个好方法。通过指针访问结构成员的正确方法应该是例12中使用指针ptr的方法。指针与函数的关系可以将指针声明为指向函数的指针。intfun1(char*,int);int(*pfun1)(char*,int);pfun1=fun1;.......inta=(*pfun1)("abcdefg",7);//通过function指针调用函数。指针可以用作函数的形参。在函数调用语句中,指针表达式可以用作实际参数。
