因為我覺得學習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 }