一、指针相关
指针我从三个方面去理解指针,如下所示:
(1)总所周知,指针中存放的是一个地址,这个地址存放的数据就是指针指向的数据,同时,指针本身也需要存放在一个地址,当然我们一般不关心这个地址,但是需要知道这个地址就是指针本身的地址,这个和指针存放的地址不同,要注意区分;(2)指针指向的数据是有确定类型的,指针本身也是有确定类型的,那么指针指向的数据类型和指针本身的数据类型是不同的,要注意区分;
(3)指针会指向一定长度的存储空间,这个存储空间就是指针所指向的数据占据的存储空间,所以该存储空间的大小就是指向的数据类型所占空间的大小,同时指针本身也需要占一定的存储空间,那么指针所占存储空间的大小和指针所指向的存储空间是不同的,要注意区分。
所以通过上面的阅读,我们知道,要准确的理解指针、用好指针,至少要区分指针的3方面,即:
(1)指针本身的地址和指针存放的地址
(2)指针本身的数据类型和指针所指向的数据类型
(3)指针本身所占据的存储空间和指针所指向的存储空间
一个指针变量涉及两个东西,即指针本身和指针指向的数据,这这两个东西的上面那3个方面是不同的,所以我们在用指针变量之前应该好好想想要用的指针变量这三个方面到底什么样的,做到心中有数,才不会出错。
在我们使用指针的时候,要时刻想到上面的三个方面,才不会出错,例如:
int *p;
以前的时候,我总是搞不清楚,这个p没有初始化,为什么不能用,现在对照一下上面三个方面就好理解了:
(1)指针本身的地址和指针存放的地址
指针本身的地址即指针变量p的地址是什么,p的地址是由系统自动分配,所以p处于内存中的栈区;我们一般不关心指针本身的地址,但是我们关心指针存放的地址,这时候你就会发现,p没有存放任何地址,上面的语句仅仅是声明了一个指针变量p;我们定义指针不就是使用指针存放的地址和指针指向的数据吗,但是现在p中没有存放地址,也就没有指向任何数据,假设你一定要说p也是会指向一个地址的,我只能说,这是系统任意分配给p的值,它有可能是不合法的;所以,我们在使用指针之前,一定要给指针分配一个地址,即让p指向一个数据,假设暂时没有需要指向的数据,声明这个指针仅仅是作为以后备用,那也需要让p指向NULL,即防止野指针出现。
通过这一点,我们知道使用指针的目的,就是为了我们方便对数据的间接处理,将要处理的数据的地址存放在指针中,然后间接的对数据进行处理;
所以在使用指针之前,一定要把指针存放一个地址,即使这个地址没有存放任何数据也是可以的;哪怕指向NULL,这就是所谓的指针的初始化。
int *p;
int a;
p = &a;
在上面,其实没有给a分配任何值,但是这样做是合法的,以后就可以引用p
(2)指针本身的数据类型和指针所指向的数据类型
指针本身的数据类型就是将定义的指针变量名去掉后就是,指针所指向的数据类型就是再去掉指针本身的数据类型最后面一个*后,就是指针所指向的数据类型。
现在举例说明:
int *p;
int **q;
char *ch;
char **cha;
struct lnode{
int data;
int *next;
};
struct lnode *Lnode;
struct lnod **LNode;
对p来说,指针本身的数据类型就是int *,指针所指向的数据类型就是int;
对q来说,指针本身的数据类型就是int **,指针所指向的数据类型就是int *;
对ch来说,指针本身的数据类型是char*,指针所指向的数据类型就是char;
对Lnode来说,指针本身的数据类型是lnode *,指针所指向的数据类型是lnode;
对LNode来说,指针本身的数据类型就是lnode **,指针所指向的数据类型就是lnode*;
看出来了吧,对于二级指针来说,二级指针存放的数据也是一个指针,因为存放的数据类型和一级指针本身的数据类型相同,例如p和q ;
(3)指针本身所占据的存储空间和指针所指向存储空间
指针本身所占的存储空间是固定的,无论指针本身的数据类型是什么类型,指针本身所占的存储空间都是4个字节;而指针所指向的数据所占的存储空间就不同了,要取决于所指向的数据类型,存储空间的大小就是所指向的数据类型的大小,当然数组是整个该数据类型数组的大小;
现在以上面(2)中的几个例子为例:
指针本身所占的存储空间都是4个字节,下面但看指针指向的数据所占的存储空间大小
对于p来说,p所指向的存储空间大小就是int型数据的大小,即4个字节;
对于二级指针q来说,q所指向的存储空间大小就是int *型数据的大小,因为int *是个指针型数据,所以为4个字节;
对于ch来说,ch所指向的存储空间大小就是char型数据的大小,即1个字节;
对出二级指针cha来说,cha所指向的存储空间就是char *型数据,因为char*是个指针型数据,所以也为4个字节;
对于Lnode来说,Lnode所指向的数据类型是lnode,结构体lnode所占空间大小为:8字节,所以Lnode所指向空间大小也为8字节;
对于LNode来说,LNode所指向的数据类型是lnode*,所以LNode所指向的空间大小为4个字节。
综上所述,一个能用的指针变量必定有两个要注意的东西,即有以上3个方面的不同,用的时候多想想,到底能不能分清这几个方面的不同。
二、数组名和数组名取地址
void array_point(void)
{
int a[10] = {12,14};
int *p = a;
printf("the address a represents is %p\n",a);
printf("the address of a[0] is %p\n",&a[0]);
printf("a+1 = %p\n",a + 1);
printf("&a + 1 = %p \n",&a + 1);
printf("&a[0] + 1 = %p \n",&a[0] + 1);
printf("the address of a is %p\n",&a);
printf("the address p represents is %p\n",p);
printf("the address of p is %p\n",&p);
}
打印出来的结果为:
the address a represents is 001AFAB8
the address of a[0] is 001AFAB8
a+1 = 001AFABC
&a + 1 = 001AFAE0
&a[0] + 1 = 001AFABC
the address of a is 001AFAB8
the address p represents is 001AFAB8
the address of p is 001AFAAC
解释:
(1)数据类型分析:
a:数据类型是int[10]:表示a是指向由10个int型变量组成的一维数组
a[0]:数据类型int:表示a[0]是整型变量
&a:数据类型是int[10]*:表示&a是指向由10个int型变量组成的一维数组的指针
&a[0]:数据类型是int*:表示&a[0]是指向int型变量的指针
p:数据类型是int*,指向单个整型变量的指针
&p:数据类型是int**,指向“指向单个整型变量的指针”的指针,即指向指针的指针
(2)a是数组名,表示数组首元素的地址,&a[0]也表示数组首元素的地址,这两个是相同的,都是表示数组首元素的地址,可以这样来看:
&a[0] = &(*(a + 0)) = &(*a) = a:在此处,取地址符&和解引用符*是一对逆运算,所以可以相消,实际上可以这么说,当a在表示数组首元素地址的时候都是可以使用&a[0];
(3)&a实际上才真正是数组的首地址,而数组名a不过是数组首元素的地址,因为数组首元素的地址就是数组首地址,所以&a和a才在数值上相同,注意,仅仅是数值上的相同,数据类型是不同的;
现在来看a+1和&a+1为什么不同?
因为a和&a的数据类型不同,即是不同类型的指针,在对指针进行加减运算时,步长实际上是指向的数据类型的长度;上边说过,数组名a在表示数组首元素地址时,可以换成&a[0],所以即比较&a[0]和&a所指向的数据类型的不同,因为&a[0]是指向int型变量的指针,而&a是指向一维数组的指针,&a[0]进行加减运算时的步长就是sizeof(int)= 4,而&a进行加减运算时的步长就是sizeof(int[10])= 40,所以会出现不同;
(4)大家可以看出,p的数据类型是in*,而a的数据类型是int[10],为什么p=a是合法的呢,这里就是把数组名当成数组的首地址,即&a[0],你看,要是这样写的话,
p = &a[0],是不是很清晰了;
三、一些注意
char *p = “hello world”;这个是存放在数据段,当程序很小时,你可以把指针p作为返回值返回,可以得到正确的指针值,但是当程序很大时,由于系统的回收机制,可能会将以前的数据段覆盖,就有可能出现错误,所以这种指针变量返回值很不安全;
char p[] = "hello world";这个是存放在堆栈段,当离开本程序时,该变量就会被销毁,因此使用局部变量作为指针变量返回值有可能是错误的,