原创:C/C++语言-指针(point)

因为我觉得学习C语言最重要的知识点之一就是指针,可是无论对于新手还是有一定经验的人来说,指针的理解还是不够系统的,于是结合我个人见解写出了这么一份代码形式的笔记,读者可以自行探究加深理解和记忆。

此篇文章经过几年很多次的修改,个人觉得足够完善了,如有疑问欢迎联系作者本人一起探讨学习~,尊重作者的劳动,转载请记得注明来源:http://www.cnblogs.com/weifeng727/p/5584151.html

 

  1 /*----------------------------------------
  2                 指针练习(精华)
  3 
  4     1)首先,要理解变量或数组的首地址,指的就是存放数据的RAM或ROM中地址号最小的那个字节地址。
  5 
  6     2)指针前面的类型说明符,具有2重意义(既决定了对一级指针进行解引用时,将会操作的字节数,以及对一级指针进行算术运算时,会跳转的地址个数)。    
  7       ①决定了指针加一时,指针会跳转多少个地址,
  8       例如:
  9               如果指针是
 10               char类型的,(指针+n) 指针会跳转n*1个地址。
 11               int 类型的,(指针+n) 指针会跳转n*2个地址。
 12               long类型的,(指针+n) 指针会跳转n*4个地址。
 13               
 14       ②并且还决定了通过指针操作地址值时,实际上会返回多少个字节的值,且地址号大的字节先返回。 
 15       例如:    
 16               假设要操作指针的一次地址返回值,那么如果指针是
 17               char类型的,返回1个字节。
 18               int 类型的,返回2个字节。
 19               long类型的, 返回4个字节。
 20               
 21       数组前面的类型说明符,同样具有2重意义,且跟上面的很相似。
 22       例如:
 23               #include"stdio.h"
 24               int c[]={0x1234,0x5678};
 25               void main()
 26               {
 27                 printf("%p %d\n",c,*c);            //数组是int类型意味着返回2个字节
 28                 printf("%p %d\n",(c+1),*(c+1));  //实际上(c+1)与c是夹着一个地址,因为数组类型符号是int,如果数组类型是long,则夹着3地址
 29               }
 30       
 31       也就是要注意类型所占的字节数,还有就是什么时候该看数组类型符号或者指针类型符号。
 32     3)&叫取首地址符号,*叫解引用符号。
 33 
 34     4)数组名是指一个首地址,所以,point=a(point是一个指针,a是一个数组名), a的前面不需要加&符号。
 35       变量名指的是一个值,a[1]指的也是一个值,这些值包含着一个或多个字节,在想要让指针指向这些值的字节的地址时,
 36       需要在变量名以及a的前面加上&符号,即意思是要让指针赋值符号(=)右边的东西是地址。
 37 
 38     5)数组或变量的数据是一个一个字节的存放的,而且字节的地址是呈现连续的,赋值的时候,从左到右看
 39       越往右,字节的地址号越大。因此,对于多字节数据类型的数组而言,看起来有种“首尾相连”的效果,
 40       因为一个元素的最低位字节其地址的加一地址对应的字节,就是下一个元素的最高位字节。
 41       
 42       简单点来说就是低地址存放高字节,这种现象称为大端排列(常用单片机)。注意:有些时候则是低地址存放低字节,这种现象称为小端排列(ARM)。
 43       
 44     6)指针可分为:函数指针,数组指针(多维指针),变量指针,结构体指针。  又可分为:多级指针,多维指针。  地址可分为:多级地址,多维地址。
 45 
 46     7)只有字符数组的最后一个元素会紧接一个隐藏元素,该元素值为0,映射的字符为“\0”。
 47 
 48     8)数据指针型函数,也叫指针函数(即返回值是一个地址)。   
 49     
 50     9)char (*p)[2];   //声明一个1维指针(也叫1维数组指针)
 51       分析方括号([])对多维指针的操作时,要遵循一个原则:先明确指针的维数,再分析有多少组方括号,方括号里面的数字是多少,由此得到地址是如何跳转的;
 52                                                         然后根据方括号的组数得知地址最终会发生多少次的解引用,如果解引用的次数少于地址的维数,
 53                                                         那么最终得到的还是一个地址,也如果解引用的次数等于地址的维数+1,那么得到是一个数据值。
 54                                                         每次对多维地址进行一次解引用后,地址的维数将会变小。
 55       一维数组名就是一个一级0维地址,二维数组名就是一个一级1维地址,多维数组名就是一个一级多维地址。每一个数组名都是一个地址。这些地址是固定的。
 56       一级多维指针的特点是:解引用的写法很特殊;运算时地址的跳转很特殊。
 57       探究代码如下:
 58       int Array[2][3][2]={{{1,2},{3,4},{5,6}},{{7,8},{9,10},{11,12}}};  //Array是一个一级2维地址
 59 
 60       printf("%d\n", Array); 
 61       printf("%d\n", Array[1]);   //与上一行代码相比,发生了 3*2*4=24个地址 的跳转
 62       printf("%d\n", Array[1][1]);    //与上一行代码相比,发生了 2*4=8个地址 的跳转
 63 
 64       printf("%d\n",*(Array[1][1]));//对0维地址进行1次解引用,得到一个数据值,为9
 65 
 66       printf("%d\n",*(*(Array[1])));    //对1维地址进行2次解引用,得到一个数据值,为7
 67       printf("%d\n",*(Array[1]));        //对1维地址进行1次解引用,得到的是一个0维地址,且与Array[1]值一样,但Array[1]是一个1维地址
 68 
 69     10) #include<stdio.h>  
 70         #include<stdlib.h>  
 71         int main()  
 72         {  
 73 
 74             //下面是存在着非法读写的演示,虽然非法读写是可以实现,但是这只能存在于同一个进程里面,而且这种情况没有什么有利的实际意义
 75 
 76             int *p;    //int类型指针,操作4个字节
 77             p=(int *)malloc(2);  //向堆申请了2个字节,但不进行初始化,calloc方式的申请会进行初始化
 78             if(p)  
 79                 printf("Memory Allocated at: %p\n",p);  
 80             else  
 81                 printf("Not Enough Memory!\n");
 82             printf("%x\n",*p);     //由于只申请了2个字节,所以非法读取了2个字节
 83             *p=0x12345678;        //由于只申请了2个字节,所以非法写入了2个字节
 84             printf("%x\n",*p);    //由于只申请了2个字节,所以非法读取了2个字节     
 85             free(p);   //释放堆中申请过的2个字节,并且有可能把内存中的值也清0,这要取决于运行的内核
 86             //下面是非法读写了4个字节
 87             printf("%x\n",*p);    
 88             *p=0x87654321;
 89             printf("%x\n",*p);    
 90             return 0;  
 91         } 
 92 
 93     11)经探究发现,不同类型的指针指向的地址跟指针类型不一致时,有可能会报错,也有可能只是警告而已
 94 
 95     12)unsigned char (*q)();  //声明一个函数指针
 96        指针形式调用函数时不给定传递参数值的话,默认是传递-1   ,  指针调用函数的格式为:"(*指针名)(形参列表)" 或 "指针名(形参列表)"
 97 
 98     13)一级和多级指针的使用:
 99     int val=0x1122;
100     char *p3=&val;
101     char **p2=&p3;
102 
103     printf("%x\n", p3); 
104     printf("%x\n", p3+1); //跳转1个地址(因为p3是个一级指针而且类型修饰符为char)
105 
106     printf("%x\n", *(p3)); //操作1个字节(因为p3是个一级指针而且类型修饰符为char)
107     printf("%x\n", *(p3+1));//操作1个字节(因为p3是个一级指针而且类型修饰符为char)
108 
109     printf("%x\n", (p2)); 
110     printf("%x\n", (p2+1)); //跳转4个地址(因为内存中字节所使用的地址长度为32位且指针p2是一个二级指针)
111 
112     printf("%x\n", *(p2)); //操作4个字节(因为内存中字节所使用的地址长度为32位且指针p2是一个二级指针)
113 
114     14)对多级多维指针的探究:
115     //假设已经掌握对多级0维指针,和一级多维指针的使用
116 
117     unsigned char (**p)[3];  //这是一个二级2维指针
118     int Array3[10]={0x804a0e0,0x55667788,2,3,4,5,6,7,8,9};  //之所以第一个元素设为0x804a0e0,是因为如果该值取的不当,下面对(*(*p))进行解引用的时候,有可能在程序执行时导致内核塌陷,看起来好像是程序语法错误,也就是说要保证解引用的指针是在正确范围内的
119     p=Array3;  //Array3是一个一级1维地址,先让Array3转变为二级2维地址,再让二级2维指针p所指向
120 
121     printf("%x\n", p);  //原本p是一个二级2维指针,在这里p表示为一个二级2维地址
122     printf("%x\n", p+1);  //发生4个地址的跳转(因为地址长度为4个字节),因为(p+1)是一个二级2维地址,也就是此行语句输出值比上一行语句大4
123 
124     printf("%x\n", *p);   //解引用得到4个字节的值,因为p是一个二级2维地址。*p 得到的是一个一级2维地址
125     printf("%x\n", (*p+1));  //跳转3个字节,因为 *p 属于一级2维地址,也就是此行语句输出值比上一行语句大3。因为一级指针地址的跳转,是取决于维数,*p是一个一级2维地址,那么跳转数为:sizeof(unsigned char)*[3]=3
126     
127     printf("%x\n", *(*p));  //对一级2维地址(*p)进行解引用,得到一级1维地址*(*p)
128 
129     printf("%d\n", *(*(*p))); //对一级1维地址*(*p)进行解引用,得到1个字节的值,值为0xe0也就是224,因为指针 p 的类型修饰符为char,而sizeof(unsigned char)=1
130 
131     printf("%d\n", *(*(*p))+1); //该行打印值比上一行大1,为225
132 
133     //总结:对多级多维指针进行解引用的时,每次解引用都会遵循先降级再降维,当级数没降到1,那么维数不起作用。
134     //      当已经是一级指针了后,维数起作用,当做一级多维指针或一级1维指针处理  ,一级1维地址再解引用就得到值了
135     
136     15)指针常量和常量指针
137     int a =3;  
138     int b = 1;  
139     int c = 2;  
140     int const *p1 = &b;//const 在前,定义为常量指针  
141     int *const p2 = &c;//*在前,定义为指针常量
142     //常量指针p1:指向的地址可以变,但内容不可以重新赋值,内容的改变只能通过修改地址指向后变换。   
143     //p1 = &a是正确的,但 *p1 = a是错误的。
144     //指针常量p2:指向的地址不可以重新赋值,但内容可以改变,必须初始化,地址跟随一生。
145     //p2= &a是错误的,而*p2 = a 是正确的。
146 
147     16)假设a是数组名,p是指针名,那么p=&a等效于p=a
148     int abc[4]={1,2,3,4};
149     int (*p)[4]; 
150     p=abc;  //等效于p=&abc;
151     for(i=0;i<4;++i)
152     {
153         printf("%d\n",p[0][i]);
154     }
155 
156     17)在sizeof()函数参数列表里,解引用符*的作用稍微发生了变化。
157     //下面证明了,解引用符号“ * ”在不同的场合应用,其作用发生了改变,如在sizeof()函数的参数中时,对数组名进行解引用,将会影响打印出来的数组“维积”
158     printf("%d\n", sizeof((**pp)));    //打印出数组“维积”,为4
159     printf("%d\n", sizeof(*p));  //打印数组的“维积”,为4*sizeof(int)=16
160     printf("%d\n", sizeof(p));  //打印出指针“维积”,为3*4*4=48
161     printf("%d\n", sizeof(*(**p[3][4])));  //打印出指针“维积”,为5*4=20
162 
163     18)关于指针和数组的一种特殊数据结构
164     int (**p[3][4])[2][5];  //定义一个二级3维指针2维数组
165     int pp[5][6];
166     int test;
167     //p=&test;   //因为p是数组名,是一个常量,所以p=&test;这句有错误
168 
169     18)int array[10];
170        double *a=&array[5];
171        double *b=array;
172        printf("a-b=%d\n",a-b);  //打印结果为:2。   4*5/8=2    此行代码是这样理解的: a-b得到一个double类型的地址值,然后转换成int类型,那么有20/8=2
173     
174 ------------------------------------------*/
175 #include"stdio.h"
176 #include"stdlib.h"
177 
178 
179 void main()
180 {
181 /*
182     int i;
183     int a[10]={[4]=4,5,6,7,[1]=1,2,3,110};    //数组的初始化技巧
184     for (i=0;i<10 ;++i )
185     {
186         printf("%d\n",a[i]);
187     }
188 */
189 /*
190     int abc[4]={1,2,3,4};
191     int i=0;
192     int (*p)[4];
193     p=&abc;  //p=abc;等效于p=&abc;
194     printf("%x,%x\n",&abc,abc);
195     for(i=0;i<4;++i)
196     {
197         printf("%d\n",p[0][i]);
198     }
199 */
200 /*
201     int Array[2][3][2]={{{1,2},{3,4},{5,6}},{{7,8},{9,10},{11,12}}};  
202     printf("%d\n",*(*(*(Array+1)))); 
203     printf("%d\n",*(*(Array+1))); 
204     printf("%d\n",*(Array+1)); 
205     printf("%d\n",(Array+1)); 
206     //printf("%d\n",*(*(Array+1)));
207 */
208 /*
209     int *p; 
210     int c[]={0x1234,0x5678};
211     int a=4;
212     int *b=&a;
213     p=(int *)malloc(4);
214     //printf("%p %d\n",b,*b);
215     //printf("%p %d\n",c,*c);
216     //printf("%p %d\n",(c+1),*(c+1));
217     //printf("%x",*p);
218     *p=0x12345678;
219     printf("%p\n",p);
220     printf("%x\n",*p);
221     free(p);
222     printf("%p\n",p);
223     printf("%x\n",*p);
224     b++;
225     printf("%p %d\n",b,*b);
226     b++;
227     printf("%p %d\n",b,*b);
228     b++;
229     printf("%p %d\n",b,*b);
230     b++;
231 */          
232 /*
233     char d[6] = {0x01,0x02,0x03,0x04,0x05,0x06};
234     char (*p)[2];   
235     p = d;  
236     printf("%p\n", (p));  
237     printf("%x\n", *(p)); 
238     printf("%p\n", *(*(p))); 
239     printf("%p\n", (p+1));
240     printf("%x\n", *(p+1)); 
241     printf("%p\n", *(*(p+1)));
242 */
243 /*
244     //-------------一级和多级指针的使用----------//
245     int val=0x1122;
246     char *p3=&val;
247     char **p2=&p3;
248 
249     printf("%x\n", p3); 
250     printf("%x\n", p3+1); //跳转1个地址(因为p3是个一级指针而且类型修饰符为char)
251 
252     printf("%x\n", *(p3)); //操作1个字节(因为p3是个一级指针而且类型修饰符为char)
253     printf("%x\n", *(p3+1));//操作1个字节(因为p3是个一级指针而且类型修饰符为char)
254 
255     printf("%x\n", (p2)); 
256     printf("%x\n", (p2+1)); //跳转4个地址(因为内存中字节所使用的地址长度为32位且指针p2是一个二级指针)
257 
258     printf("%x\n", *(p2)); //操作4个字节(因为内存中字节所使用的地址长度为32位且指针p2是一个二级指针)
259     //------------------------------------------//
260 */
261 /*
262     //-------------对多维指针的方括号运算以及多次解引用----------//
263     printf("%d\n", Array); 
264     printf("%d\n", Array[1]);   
265     printf("%d\n", Array[1][1]);    
266 
267     printf("%d\n",*(Array[1][1]));
268 
269     printf("%d\n",*(*(Array[1])));    
270     printf("%d\n",*(Array[1]));        
271     //-----------------------------------------------------------//
272 */
273 /*
274     //----------------------对多级多维指针的探究-----------------//
275     //假设已经掌握对多级0维指针,和一级多维指针的使用
276 
277     unsigned char (**p)[3];  //这是一个二级2维指针
278     int Array3[10]={0x804a0e0,0x55667788,2,3,4,5,6,7,8,9};  //之所以第一个元素设为0x804a0e0,是因为如果该值取的不当,下面对(*(*p))进行解引用的时候,有可能在程序执行时导致内核塌陷,看起来好像是程序语法错误,也就是说要保证解引用的指针是在正确范围内的
279     p=Array3;  //Array3是一个一级1维地址,先让Array3转变为二级2维地址,再让二级2维指针p所指向
280 
281     printf("%x\n", p);  //原本p是一个二级2维指针,在这里p表示为一个二级2维地址
282     printf("%x\n", p+1);  //发生4个地址的跳转(因为地址长度为4个字节),因为(p+1)是一个二级2维地址,也就是此行语句输出值比上一行语句大4
283 
284     printf("%x\n", *p);   //解引用得到4个字节的值,因为p是一个二级2维地址。*p 得到的是一个一级2维地址
285     printf("%x\n", (*p+1));  //跳转3个字节,因为 *p 属于一级2维地址,也就是此行语句输出值比上一行语句大3。因为一级指针地址的跳转,是取决于维数,*p是一个一级2维地址,那么跳转数为:sizeof(unsigned char)*[3]=3
286     
287     printf("%x\n", *(*p));  //对一级2维地址(*p)进行解引用,得到一级1维地址*(*p)
288 
289     printf("%d\n", *(*(*p))); //对一级1维地址*(*p)进行解引用,得到1个字节的值,值为0xe0也就是224,因为指针 p 的类型修饰符为char,而sizeof(unsigned char)=1
290 
291     printf("%d\n", *(*(*p))+1); //该行打印值比上一行大1,为225
292 
293     //总结:对多级多维指针进行解引用的时,每次解引用都会遵循先降级再降维,当级数没降到1,那么维数不起作用。
294     //      当已经是一级指针了后,维数起作用,当做一级多维指针或一级1维指针处理  ,一级1维地址再解引用就得到值了
295 */
296 /*
297     int (**p[3][4])[2][5];  //定义一个二级2维指针2维数组
298     int pp[5][6];
299     int test;
300     //p=&test;   //因为p是数组名,是一个常量,所以p=&test;这句有错误
301 
302     //下面证明了,解引用符号“ * ”在不同的场合应用,其作用发生了改变,如在sizeof()函数的参数中时,对数组名进行解引用,将会影打印出来的响数组“维积”
303     printf("%d\n", sizeof((**pp)));    //打印出数组“维积”,为4
304     printf("%d\n", sizeof(*p));  //打印数组的“维积”,为4*sizeof(int)=16
305     printf("%d\n", sizeof(p));  //打印出指针“维积”,为3*4*4=48
306     printf("%d\n", sizeof(*(**p[3][4])));  //打印出指针“维积”,为5*4=20
307 */
308 /*
309     int a =3;  
310     int b = 1;  
311     int c = 2;  
312     int const *p1 = &b;//const 在前,定义为常量指针  
313     int *const p2 = &c;//*在前,定义为指针常量
314     //常量指针p1:指向的地址可以变,但内容不可以重新赋值,内容的改变只能通过修改地址指向后变换。   
315     //p1 = &a是正确的,但 *p1 = a是错误的。
316     //指针常量p2:指向的地址不可以重新赋值,但内容可以改变,必须初始化,地址跟随一生。
317     //p2= &a是错误的,而*p2 = a 是正确的。
318 */
319 
320     int array[10];
321     double *a=&array[5];
322     double *b=array;
323     printf("a-b=%d\n",a-b);  //打印结果为:2。   4*5/8=2    此行代码是这样理解的: a-b得到一个double类型的地址值,然后转换成int类型,那么有20/8=2
324 
325 }

 

posted @ 2016-06-26 14:40  weifeng727  阅读(2875)  评论(1编辑  收藏  举报