原创: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 }