基础
1,指针是一个其数值为地址的变量,正如char类型的变量以字符作为其数值,而int类型的数值是整数,指针是以地址作为数值的。而地址由多少位bit表示跟操作系统有关。对于32位的操作系统而言,使用32bit表示一个地址,因此某一个指针变量需要存储32位bit,即需要4字节的内存。
2,任何数值都是存储在内存中的,所以任何变量都有一个内存地址。
3,指针的数值就是它所指向的对象的地址,它也需要用内存来存储,因此指针变量也有一个内存地址。
4,当需要改变调用函数中某个变量的值时,任何被调用的无返回值的C函数都需要使用该变量的地址做为参数。
5,&可以取得变量的存储地址,例如a是一个变量名,那么&a就是读取该变量的地址。一个变量的地址可以理解为是该变量在内存中的位置。而a就是存储在a这块变量中的值,如a=3,那么a代表的内存中存储的就是3。我们通常使用变量时,就是使用该变量所代表的内存中存储的值。
6,地址是该变量所代表的内存在内存区域中的编号,值是该变量所代表的内存区域中存储的数值。如a=3,那么a的值是3,地址却是&a(该值不固定)。
7,在指针前运用运算符*就可以得到该指针所指向的对象的数值。如指针a指向了变量b(即a中存储的是b的地址),那么*a就是b,为*a赋值就是为变量b赋值,使用*a就是使用b。pbarn = & barn,那么*pbarn表示存储在地址&barn中的数值,即barn。
8,系统中按字节进行编址,因此对指针加1,就相当于增加一个存储单元,而不仅仅是跳到下一个字节或者下一位。例如对于数组而言,地址会增加到下一个元素的地址,而不是下一个字节。这也是为什么在声明数组时,必须声明其指向对象的类型。例如dates为int类型数组,dates值为56014,那么dates+1值为56016(假设int占两个字节)。总结:指针加1,等价于对指针的值加上它所指向的对象的字节大小。
9,使用指针做为参数时,可以修改调用函数的值,但有时候这并不是我们想要的。为避免这种错误,可以在定义原型和形式参数的时候使用const关键字修饰。const关键字表明:函数在使用实参时,应当把实参当作固定不变的。例如实参为一个数组,那么const修饰后,该数组中的每一个元素都是常量,不可修改。
操作
声明:声明一个指针时,系统只分配了用来存储指针本身的内存空间,并不分配用来存储数据的内存空间。如:
double *pb;
*pb=2.4;//不合法,因为并没有分配用来存储数据的空间
赋值:可以把一个地址赋值给一个指针,通常使用数组名或者地址运算符&来进行地址赋值。
求值:运算符*可取出指针指向的地址中存储的数值。
取指针地址:指针变量同其他变量一样具有地址和数值,使用取地址符&可以得到存储指针本身的地址。其数值为别的变量的地址。
加:指针变量的加,并不是将变量的值加上相应的值,而是加上一个存储单元。公式为:
p+n=p的值(内存地址编号)+n*sizeof(p的指向类型)
如下
char a ,*p = &a;
printf("%p %p\n",p,p+1);
输出的变量值分别为0x0022febb和0x0022febc,两者相差1个字节。如果将char换成int,输出的结果为0x0022feb8和0x0022febc,两者相差4个字节。这是因为char在内存中占一个字节,所以+1时地址相差1个字节;int在内存中占4个字节,所以+1时地址相差4个字节。
减:与加类似,它的计算结果也不是指针变量中值相差后值,而是相减后的值除以指针类型的字节数的商。如
int a ,*p = &a,*q = p+4;
printf("%p %p %d\n",p,q,q-p);
求差值:可以求出两个指针的差值。通常对分别指向同一个数组内两个元素的指针求差值,以求出元素之间的距离。差值的单元是存储单元的大小。例如q-p输出的值是4,但q,p中数据实际相差16,只是因为int占了4个字节,所以16/4=4即q-p的值为4。
比较:两个指针的比较是将两个指针变量所存的地址进行比较,并不是将两个地址所对应的内容进行比较。
指针与字符串
除了通过字符数组定义字符串外,还可以通过字符指针定义一个字符串,如:char *a="hellow world",执行这句话时系统分配了两段内存。一段是连续的,用来存储字符串常量,一段是一个字符指针(即a),它的值就是字符串常量的首地址。
此时&a指的是字符指针所在的内存的地址。a指的是字符串常量的首地址,即&a[0]的值。
指针与数组
数组名代表着数组的首个元素的地址,因此可以直接将数组名赋值给某个指针。如下:
int a[]={2,3,4},*p;
p = a;//a本身就代表一个地址,因此可以直接赋值给p
由于数组在内存的地址中是连续,按指针加法计算公式可知,p+n中存储的地址值正是a[n]的地址。所以*(p+n)就是a[n]。
对于数组而言,a[n]完全等价于*(a+n),等价于*(p+n),等价于p[n]。
在不越界的情况下,一维数组在如下公式
*(p+n)=a[n](它的前提条件是p=a)
数组指针与指针数组
数组指针:a pointer to an array,即指向数组的指针,它的本质仍旧是指针,如int (*p)[4]。定义数组指针时,需要在[]中指定指向的数组的长度。
按java的书写数组的方式,可能更方便理解数组指针。对于一个数组a,java可以写成int[4] a,可以把a的类型理解为int[4]。所以对于a来说,指向它的指针的指向类型为int[4]。因此,该数组指针可以写成int[4] *p,变换成c语言的格式就是int (*p)[4]——*表明P是一个指针,p是指针名,它的指向类型为int[4]——之所以把*p括起来,是因为[]的优先级高于*,为了保证p是一个指针而不是数组,所以将*p括起来。
指针指向某一个变量,是将该变量的地址赋值给这个指针。因此,指针指向数组时,应该将数组的地址赋值给指针,而对数组名取地址得到的就是数组的地址。所以对数组指针赋值,右值为对数组名取地址如:
int a[] ={1,2,3,4,5,6,7},x=0,y=0;
int (*p)[7] = &a;//指向数组的指针,所以指针中存储的值应该是整个数组的地址,即&a
指针数组:array of pointers,用于存储指针的数组,它的本质仍旧是数组,也就是说数组的元素是指针。如int *p[3],[]运算优先级高于*,所以p是一个数组,而int *又表明存储类型为int *,所以p是一个指针数组——元素个数为3,元素类型为int * 的数组,按Java习惯来说,可以换成int*[3] p——int *是存储类型,[3]表明是数组,并且存储的元素个数是3,p是数组名。对于p来说,它里面定义了p[0]p[1]p[2]三个指针。
使用指针数组,可以定义一个字符串数组。如
char * s1="first";//指向first的首地址。s2,s3同
char * s2="second";
char * s3="third";
char * s[]={s1,s2,s3};//将三个字符指针分别赋值给s[0]s[1]s[2]
int x;
for(x=0;x<3;x++){
printf("%s\n",*(s+x));//输出各个字符串
}
在定义s时,将s1,s2,s3中存储的值分别赋值给了s[0]s[1]s[2],因此s[0]s[1]s[2]分别指向各自数组的首地址,故而使用*(s+x)进行格式化输出时,输出的是各个字符串。
其实在定义s时完全可以简写成char * s[] = {"first"……},效果完全一样。
再者,我们知道s+n指向的是s[n]的地址,而s[n]中存储的是另一个地址,所以s+n是二级指针——指向指针的指针。那么上述程序可以改写成如下形式
char * s[]={"first","second","third"};//s[0]s[1]s[2]的值分别是三个字符串的首地址
int x;
for(x=0;x<3;x++){
int ** p=s+x;
printf("%s\n",*p);//输出各个字符串
}
二维数组指针
二维数组的本质仍旧是一维数组,只不过元素是一维数组。
一维数组int a[] ={2,3,4};,可以定义一个int *p指针,并且可以直接p =a;。分析可以发现,p的指向类型为int,与a中元素类型相同。
因此,当a中元素为数组时,p的指向类型也应该为数组。所以,对于二维数组而言,它对应的指针应该为数组指针。
如int b[][2]={{1,2},{2,3},{4,5}};b的元素类型为int[2](此处按java习惯,表示元素个数为2,类型为int的数组),那么与b对应的数组指针的指向类型为int[2],所以这个数级指针可以写成int (*q)[2]。
简单点说:二维数组对应数组指针,并且数组指针[]中的值为二维数组中每一个元素的长度(这个长度是固定的)。如
int a[][2] ={{1,2},{3,4},{5,6}};
int (*p)[2] = a;
printf("%p %p %p\n",p+1,&a[1],&a[1][0]);//值相等
printf("%d %d\n",*(*(p+1)+1),a[1][1]);//两者值相等,都是4
按一维数组公式*(p+n)=a[n]可知,*(p+1)=a[1],而a[1]又是一个数组的首地址,所以*(p+1)+1对应的便是a[1]这个数组中的第2个元素,即a[1][1]的地址,故而*(*(p+1)+1)与a[1][1]的值相等
对于二维数组和数组指针有如下公式
*(*(p+n)+m)=a[n][m]
该公式放到一维数组中也成立。因为一维数组可以看成只有一个元素的二维数组,所以n恒等于0,公式变成
*(*p+m)=a[m](它的前提条件是:p=&a)
总结
一维数组对应的指针类型为:数组元素类型 * 指针名。如int a[]={3,4,5},那么它对应的指针为int * p。一维数组对应一级指针。
二维数组对应的指针类型为:元素的元素类型 (*指针名)[每一个元素中元素个数]。如int a[][2]={{4,5},{2,3},{2,1}};它对应的指针为int (*p)[2]。二维数组对应数组指针。
特殊指针
void * p:首先p肯定是一个指针,而且p在内存中也占4个字节,只不过它的指向类型是void。当无法确定P的指向类型为何种类型时,用于声明指针。可以将它强转为任何指向类型的指针。如系统的malloc()函数返回的就是该类型的的指针。
指向指针的指针:int ** p,p是变量名,紧挨着P的*代表着p是一个指针,余下的int *代表着该指针的指向类型为int *,即该指针的指向类型为一个指针。