一、指针:用来保存地址的“变量”叫做指针,可以理解成指针是地址的一个别名。

例:定义一个整形指针

spacer.gif

wKiom1cfdxHw4FQsAAA37X6Ri0A666.png

“指针的内容”,“指针所指向的内容”,“指针变量的地址”:

wKiom1cfdybjUbapAACjM92HHMQ499.png

spacer.gif

指针的内容:指针变量p里面存放的是a的地址,也就是0x0018ff44.

指针所指向的内容:指针变量p里面存放的地址(0x18ff44)这块空间所对应的值,也就是10,我们通过*p(解引用)可以访问到这个值。即:*p作为右值时,*p==10,当*p作为左值时代表的就是a这块空间。

指针的地址:指针p本身有一个地址,由编译器分配的我们不知道,注意:不要讲指针变量p本身的地址和p所保存的地址(0x0018ff44)相混淆。


"未初始化的指针"和"NULL指针":

例:定义两个指针变量p1和p2:

int *p1;              //未初始化的指针

int *p2=NULL;         //空指针

*p1=10;

*p2=10;

这样赋值显然是错误的,为什么呢???

试想一下,p1,p2是一个指针变量,所以p1,p2里面应该存放的应该是一个地址。而对于p1我们没有往里面放地址,这时p1是随机指向的。如果此时解引用*p1,访问到的是块随机的地址,再修改这个随机地址里面的值,假设这个随机空间里面的值非常重要,那你就再也找不回来了,所以通常定义一个指针就将他初始化为NULL。

对于p2:虽然我们将它初始化为NULL,但它指向的是一块空地址啊!!!向一块空地址里面放东西,根本是不可能的。


指针常量:

例:*(int *)200=10;

相信很多人对这个表达是都会产生疑惑,其实很好理解的,200是一个常数,我们将它强制转化为 int *(也就是将常数200转化成一个整形地址),再对这块地址进行*引用,就访问到一块空间,可以对这块空间进行赋值。

常量指针:

例:int *p=&100;

让指针指向一个常量,一般用不到。


二级指针:什么是指针???存放地址的变量就叫做指针,所以二级指针就是存放一级指针地址的指针变量。

wKiom1cfd2rCXgJrAAA5oj3837c189.png

spacer.gif

注意:跟一级指针一样,二级指针变量p2里面存放的是一级指针变量p1的地址,一级指针变量p1里面存放的是a的地址。要想访问一块地址里面的内容可以使用间接访问符“*”,所以:

*p2==&p1, *p2就是访问p2这块空间里面存放的地址所对应的内容。

**p2==10,因为*p2得到的结果是p1的地址,在对p1的地址进行解引用*p1就访问到了10.



例:分析下面几种情况下能不能作为可修改的左值(可修改的左值必须代表一块空间)

int a=10;

int *cp=&a;


*cp=20 //可以作为左值,当cp指向a时,*cp放到等号左边代表a这块空间,当*cp放到等号右边代表a这块空间的值。


&a=20  //错误,&a不可以作为左值,因为他不能表示一块特定的空间,&a得到的结果是a的地址,但并不代表a这块空间,要想使用这块空间,必须进行*引用,*&a=20正确。&a可以作为右值,代表是a的地址这个数值。


*cp+1     //不可以作为左值,因为*优先级高于+,所以*cp先结合,再加1相当于10+1,不代表一块空间。


*(cp+1)   //可以作为左值,cp+1先结合,指向a的下一块空间,再对这块空间进行*引用,放在等号左边就代表这块空间。


++cp  //不可以作为左值,因为++cp只是将cp里面的内容加一,并没有进行*引用

cp++  //不可以作为左值


*cp++   //可以作为左值,先对cp里面的地址进行*引用。再让cp=cp+1(也就是让cp指向a的下一块空间,因为++优先级高于*)


++*cp   //不可以作为左值,*cp代表cp所指向的内容,再让其加一,相当于10+1

注意:C中++cp和--cp都不能做左值,C++中++cp可以作为左值。


const 修饰一级指针,修饰二级指针:(const修饰的变量,还是一个变量,只不过只是可读的

int const a=10;

int b=30;

1、a=20;   //错误,const修饰a,所以不能改变a的值


2、int const *p;    //const修饰的是*p,所以不能改变*p

p=&a;       //正确  

*p=20;      //错误 不能通过*p改变a的值


3、const int *p;    //const修饰的是*p

p=&a;       //正确      

*p=20;      //错误


4、int *const p=&a;          //const修饰的是p

p=&b;          //错误     不能改变p

*p=20;         //正确     


5、int const * const p;     //const修饰*p,也修饰p,所以*p,p都不能改变

p=&b;        //错误     

*p=20;       //错误

注意const修饰变量的原则是离谁近修饰谁。const int *p与int const *p完全一样。


二、指针和数组的关系 ,指针数组,数组指针,指针的运算

指针和数组的关系:

很多人都分不清指针和数组之间的关系,严格的来讲指针和数组之间没关系,指针是指针,数组是数组。只不过他们两个都可以通过“*”引用的方式和下标的方式来访问元素而已。

例:

int a[5]={1,2,3,4,5};

int *p=a;

a[5]占20个字节的大小,而p只占4个字节的大小,其次p本身有自己的地址,只不过他里面存放的是数组首元素的地址。

要访问3则有两种方式:a[2]或者*(a+2).

其中*(a+2)就是*的形式访问的,因为a表示首元素的地址,加2表示向后偏移2个整形大小,找到3的地址,在通过*得到3.

在编译器中a[2]会被先解析成*(a+2)访问的。

例2:

wKioL1cfeOvAekVuAAC6r3lQoAM257.png所以必须保持定义和声明的一致性,指针就是指针,数组就是数组。


指针数组,数组指针:

spacer.gifwKiom1cfeFXidtNwAABQmND7fSo334.png

注意:[]的优先级高于*,指针数组是一个数组,只不过里面的元素全部都是指针。数组指针是一个指针,指向数组的指针,偏移的单位是整个数组。

例:

int a[6]={1,2,3,4,5,6};

int (*p2)[6];

p2=a;

这是错误的,因为指针p2的类型是int [6],所以应该是p2=&a;

int (*p2)[3];

这样的话p2的类型是int [3],所以p2=(int(*) [3])&a;  要强制转换成数组指针的类型

注意:数组指针“所指向”的类型就是去掉指针变量的名字之后所剩下的内容。

数组指针的类型就是去掉指针后剩下的内容。

例:int (*p3)[5];

p3的类型是 int [5];

p3所指向的类型是 int (*)[5];


指针的运算:

1、指针相减得到的结果是两指针之间元素的个数,而不是字节数。

2、指针的运算

例:

struct test

{

int name;

char *pcname;

short data;

                 char c[2];

}*p;

假设p的值是0x100000,结构体的大小是12;

则:

p+0x1=0x10000c                            //p指向结构体 则p+1表示的是p+sizeof(test)*1

(unsigned int)p+0x1=0x100001     //p已经被转化为一个无符号的数,加一就相当于两个数相加

(int *)p+0x1=0x100004            //p被转化为int *,所以p+1就表示p+sizeof(int *)*1


例2:假设当前机器小端存储

   int a[4] = { 1, 2, 3, 4 };

   int *p = (int *)(a + 1);

   int *p1 = (int *)(&a + 1);

   int *p2 = (int *)(( int)&a + 1);

   printf( "*p=%d,*p1=%d,*p2=%d\n" , *p, p1[-1],*p2);

输出结果:*p=2,*p1=4,*p2=2000000 

解析:p = (int *)(a + 1); a代表首元素地址,加1指向第二个元素,所以是2.

      p1 = (int *)(&a + 1); &a表示数组的地址,&a+1就相当于&a+sizeof(a),p1指向的就是a[3]后面的地址,p1[-1]被解析成*(p1-1),所以是4.

      p2 = (int *)(( int)&a + 1); (int)&a是把数组的地址取出来再转化为一个int类型的数再加1,这时的加1就相当于两个数相加(效果相当于让&a向后偏移一个字节),相加的结果再转化成(int *)地址,使p2指向这个地址。当前数组的存储:01 00 00 00 02 00 00 00 ........... 因为&a指向的是01这个字节的位置,(int)&a+1的结果是指向了01的下一个字节,这时在强制类型转换成int *,则p2这时指向的空间的内容就是 00 00 00 02,因为是小端存储,所以大端就是 02 00 00 00,输出后就是2000000.



三、求内存和长度的差异:

整形数组:

int a[] = { 1, 2, 3, 4 };

printf( "%p\n",a);          //a代表首元素地址

printf( "%p\n",a+1);  //a+1表示第二个元素的地址

printf( "%p\n",&a);      //&a代表整个数组的首地址

printf( "%p\n",&a+1);  //&a代表数组的地址,&a+1则跳过整个数组

printf( "%d\n", sizeof (a));         //a在sizeof()中代表整个数组,    16  

printf( "%d\n", sizeof (a + 0));   //代表&a[0],32位下所有的地址大小都为4                4  

printf( "%d\n", sizeof (*a));     //表示a[0],int占4个字节                   4

printf( "%d\n", sizeof (a + 1));    //表示&a[1],32位下所有的地址大小都为4             4  

printf( "%d\n", sizeof (a[1]));      //表示a[1],int占4个字节                 4

printf( "%d\n", sizeof (&a));        //代表整个数组的地址,32位下所有的地址大小都为4    4

printf( "%d\n", sizeof (&a + 1));     //因为&a代表整个数组地址,&a+1跳过整个数组,但还是一个地址       

printf( "%d\n", sizeof (&a[0]));       //表示a[0]的地址        4

printf( "%d\n", sizeof (&a[0] + 1)); //表示a[1]的地址        4

printf( "%d\n", sizeof (*&a));         //&a代表整个数组,再*引用访问这块地址,就相当于求取真个数组的大小                   16 


字符数组:

char name[] = "abcdef" ;                          //这时字符串不代表首元素地址,而是字符数组的一种初始化方式,并且字符串总是默认以’\0‘结尾

printf( "%d\n", sizeof (name[0]));        //表示a,32位下char大小为1         1

printf( "%d\n", sizeof (&name));          //表示整个数组的地址,32位下地址就是4          4

printf( "%d\n", sizeof (*name));           //表示 a                                  1

printf( "%d\n", sizeof (&name+1));      //表示指向整个数组之后的一块空间,但还是一个地址  4

printf( "%d\n", sizeof (name+1));         //表示b的地址            4

printf( "%d\n", sizeof (name));              //代表整个数组,还要加上‘\0'      7      

printf( "%d\n", strlen(name));                //求取字符串长度,不包括’\0‘      6

printf( "%d\n", strlen(&name));             //代表整个数组的地址,&name==name,strlen遇到’\0‘停下                          6

printf( "%d\n", strlen(&name + 1));       //是一个随机值,表示指向整个数组之后的一个地址,从这个地址开始向后寻找'\0',因为’\0‘位置不确定所以是随机值

printf( "%d\n", strlen(name + 1));         //表示b的地址


字符指针:

char *name = "abcdef" ;                      //字符串放在等号右边代表首元素地址

printf( "%d\n", sizeof (name[0]));        //以下标形式访问,代表 a         1

printf( "%d\n", sizeof (&name));         //表示字符指针name的地址        4

printf( "%d\n", sizeof (*name));          //通过*访问,表示a                    1

printf( "%d\n", sizeof (&name+1));      //表示指针name之后的一块地址      4

printf( "%d\n", sizeof (name+1));          //表示b的地址                                 4

printf( "%d\n", sizeof (name));              //代表a的地址,                          4

printf( "%d\n", strlen(name));                //代表字符串首元素地址            6

printf( "%d\n", strlen(&name));             //表示指针name本身地址,因为从name开始向后’\0‘的位置不确定,所以是个随机值

printf( "%d\n", strlen(&name + 1));         //表示指针name本身地址之后的地址,因为’\0‘的位置不确定,所以是个随机值

printf( "%d\n", strlen(name + 1));             //代表b的地址                   5