一、什麼是指針?
-
用來存放變量地址的變量,就稱爲"指針變量"。
二、指針的定義
一般形式: 類名標識符 *指針變量名;
int *p; float *q;
- "*"是一個說明符,用來說明這個變量是個指針變量,是不能省略的,但它不屬於變量名的一部分
- 前面的類型標識符表示指針變量所指向的變量的類型,而且只能指向這種類型的變量
三、指針的初始化
1.指針的初始化有:先定義後初始化、在定義的同時初始化
2.初始化的注意
指針變量是用來存放變量地址的,不要給它隨意賦值一個常數。下面的寫法是錯誤的
int *p; p = 200; // 這是錯誤的
四、指針運算符
1.給指針指向的變量賦值以及內存分佈
1 char a = 10; 2 printf("修改前,a的值:%d\n", a); 3 4 // 指針變量p指向變量a 5 char *p = &a; 6 7 // 通過指針變量p間接修改變量a的值 8 *p = 9; 9 10 printf("修改後,a的值:%d", a);
當程序剛執行完第5行代碼時,內存中大概的分佈情況是這樣的
,a值是10,p值就是變量a的地址ffc3。
2.取出指針所指向變量的值
指針運算符除了可以賦值之外,還可以用於取值
1 char a = 10; 2 3 char *p; 4 p = &a; 5 6 char value = *p; 7 printf("取出a的值:%d", value);
第6行中的*p的意思是:根據p值(即變量a的地址)訪問對應的存儲空間,並取出存儲的內容(即取出變量a的值),賦值給value
3.使用注意
在指針變量沒有指向確定地址之前,不要對它所指的內容賦值。下面的寫法是錯誤的
int *p; *p = 10; //這是錯誤的
4. 在同一種編譯器環境下,一個指針變量所佔用的內存空間是固定的。比如,在16位編譯器環境下,任何一個指針變量都只佔用2個字節,並不會隨所指向變量的類型而改變。
五、用指針指向一維數組的元素
1 // 定義一個int類型的數組 2 int a[2]; 3 4 // 定義一個int類型的指針 5 int *p; 6 7 // 讓指針指向數組的第0個元素 8 p = &a[0]; 9 10 // 修改所指向元素的值 11 *p = 10; 12 13 // 打印第一個元素的值 14 printf("a[0] = %d", a[0]);
輸出結果:,說明已經通過指針間接修改了數組元素的值,跟指向一個普通int類型變量是一樣的。
由於數組名代表着數組的首地址,即a == &a[0],因此第8行代碼等價於:
// 讓指針指向數組的第0個元素
p = a;
內存分析圖如下,一個指針變量佔用2個字節,一個int類型的數組元素佔用2個字節
六、用指針遍歷數組元素代碼
1.最普通的遍歷方式是用數組下標來遍歷元素
1 // 定義一個int類型的數組 2 int a[4] = {1, 2, 3, 4}; 3 4 int i; 5 for (i = 0; i < 4; i++) { 6 printf("a[%d] = %d \n", i, a[i]); 7 }
2.接下來我們用指針來遍歷數組元素
1 // 定義一個int類型的數組 2 int a[4] = {1, 2, 3, 4}; 3 4 // 定義一個int類型的指針,並指向數組的第0個元素 5 int *p = a; 6 7 int i; 8 for (i = 0; i < 4; i++) { 9 // 利用指針運算符*取出數組元素的值 10 int value = *(p+i); 11 12 printf("a[%d] = %d \n", i, value); 13 }
七、指針與數組的總結
p是指針,a是一個數組
1. 如果p指向了一個數組元素,則p+1表示指向數組該元素的下一個元素。比如,假設p = &a[0],則p+1表示a[1]的地址
2. 對於不同類型的數組元素,p值的改變是不同的。如果數組元素爲int類型,p+1代表着p的值加上2(16位編譯器環境下)
4. 如果p的初值是&a[0],那麼
-
p+i和a+i都可以表示元素a[i]的地址,它們都指向數組的第i個元素。a代表數組首地址,a+i也是地址,它的計算方法與p+i相同
-
*(p+i)和*(a+i)都表示數組元素a[i]
-
雖然p+i和a+i都指向數組的第i個元素,但二者使用時還是有區別的。因爲作爲指針變量的p可以改變自身值,如p++,使p的值自增。而數組名a是一個代表數組首地址的常量,它的值是不能改變的,即a++是不合法的
4. 引用一個數組元素可以有 兩種 方法:
-
下標法: 如a[i]
-
指針法: 如*(p+i) 或 *(a+i)
八、數組、指針與函數參數
1.用數組名作爲函數實參時,是把實參數組的首地址傳遞給形參數組,兩個數組共同佔用同一段內存空間,這樣形參數組中的元素值發生變化就會使實參數組的元素值也同時變化
1 void change(int b[]) { 2 b[0] = 10; 3 } 4 5 int main() 6 { 7 // 定義一個int類型的數組 8 int a[4] = {1, 2, 3, 4}; 9 10 // 將數組名a傳入change函數中 11 change(a); 12 13 // 查看a[0] 14 printf("a[0]=%d", a[0]); 15 16 return 0; 17 }
change函數的形參是數組類型的,在第11行調用change函數時,將數組名a,也就是數組的地址傳給了數組b。因此數組a和b佔用着同一塊內存空間。
輸出結果:
2.這種地址的傳遞也可以用指針來實現。函數的實參和形參都可以分別使用數組或指針。這樣就有4種情況:
也就是說,如果一個函數的形參類型是一個數組,調用函數時,你可以傳入數組名或者指針變量;
1 void change(int b[]) { 2 b[0] = 10; 3 } 4 5 int main() 6 { 7 // 定義一個int類型的數組 8 int a[4] = {1, 2, 3, 4}; 9 10 int *p = a; 11 12 // 將數組名a傳入change函數中 13 change(p); 14 15 // 查看a[0] 16 printf("a[0]=%d", a[0]); 17 18 return 0; 19 }
注意第1行的形參類型是個數組int b[],第10行定義了指針變量p,第13行將p當做實參傳入函數
如果一個函數的形參類型是一個指針變量,調用函數時,你可以傳入數組名或者指針變量。
1 void change(int *b) { 2 b[0] = 10; 3 // 或者*b = 10; 4 } 5 6 int main() 7 { 8 // 定義一個int類型的數組 9 int a[4] = {1, 2, 3, 4}; 10 11 // 將數組名a傳入change函數中 12 change(a); 13 14 // 查看a[0] 15 printf("a[0]=%d", a[0]); 16 17 return 0; 18 }
注意第1行的形參類型是個指針變量int *b,第12行將數組名a當做實參傳入函數。
由第2行可以看出,在很多情況下,指針和數組是可以相互切換使用的。但是,並不能說指針就等於數組。
九、用指針遍歷字符串的所有字符代碼以及內存分佈
1 // 定義一個指針p
2 char *p;
3
4 // 定義一個數組s存放字符串
5 char s[] = "mj";
6
7 // 指針p指向字符串的首字符'm'
8 p = s; // 或者 p = &s[0];
9
10 for (; *p != '\0'; p++) {
11 printf("%c \n", *p);
12 }
執行完第8行後,內存分佈如右圖:
十、用指針直接指向字符串代碼
1 #include <string.h> 2 3 int main() 4 { 5 // 定義一個字符串,用指針s指向這個字符串 6 char *s = "mj"; 7 8 // 使用strlen函數測量字符串長度 9 int len = strlen(s); 10 11 printf("字符串長度:%D", len); 12 return 0; 13 }
十一、指針處理字符串的注意
現在想將字符串"lmj"的首字符'l'改爲'L',解決方案是多種的
1.第一種方案
1 // 定義一個字符串變量"lmj"
2 char a[] = "lmj";
3
4 // 將字符串的首字符改爲'L'
5 *a = 'L';
6
7 printf("%s", a);
2.第二種方案
1 char *p2 = "lmj"; 2 *p2 = 'L'; 3 4 printf("%s", p2);
看起來似乎是可行的,但這是 錯誤代碼 ,錯在第2行。首先看第1行,指針變量p2指向的是一塊字符串常量,正因爲是常量,所以它內部的字符是不允許修改的。
有人可能搞蒙了,這裏的第1行代碼char *p2 = "lmj";跟第一種方案中的第2行代碼char a[] = "lmj";不是一樣的麼?這是不一樣的。
- char a[] = "lmj";定義的是一個字符串變量!
- char *p2 = "lmj";定義的是一個字符串常量!
十二、返回指針的函數
返回指針的函數的一般形式爲: 類型名 * 函數名(參數列表)
十三、指向函數的指針
1. 函數作爲一段程序,在內存中也要佔據部分存儲空間,它也有一個起始地址,即函數的入口地址。函數有自己的地址,那就好辦了,我們的指針變量就是用來存儲地址的。因此,可以利用一個指針指向一個函數。其中,函數名就代表着函數的地址。
2.指向函數的指針的 一般形式: 函數的返回值類型 (*指針變量名)(形式參數1, 形式參數2, ...);
注意:形式參數的變量名可以省略,甚至整個形式參數列表都可以省略