一、指針的概念
1.1變量和地址
- 變量:直觀來說,int a、char ch、float num這些都是聲明變量,而a、ch、num就是變量
- 地址:在計算機中,內存被分爲一小塊一小塊的,而每一塊都有一個編號,叫做地址。
- 一般變量都存儲在內存當中。而每塊內存都有一個獨一無二的地址,這個地址就是指針
- 如果把內存比作一個賓館,在聲明一個變量時(int a),就相當於在賓館前臺辦了入住手續。前臺會給你一個門卡和門牌號,簡單理解門牌號就是地址。
二、變量的指針與指針變量
指針爲變量的地址,而專門用來存儲另一個變量的地址的變量就是指針變量。
2.1、指針變量的定義及使用
(1)、指針變量的定義
定義指針變量的符合爲*,如下定義了三個指針變量。它們的變量名爲pi、pj、pf,而不是*pi、*pj、*pf。*號在此只用來聲明。
//聲明瞭兩個整型變量和一個浮點型變量
int i, j;
float f;
//聲明三個指針變量
int *pi, *pj;
float *pf;
(2)、指針變量的使用
- 取地址符&:單目運算符“&”的功能是取操作對象的地址。常量、表達式和寄存器變量不能取地址,因爲它們不是存放在內存某個存儲單元中,而是放在寄存器中,寄存器無地址。
- 指針運算符(間接尋址運算符)*:單目運算符“*”的功能是按照操作對象的地址值,訪問對應存儲單元。與“&”互爲逆運算。
//聲明一個變量i,初始化值爲10
int i = 10;
//利用取地址符&獲取i的地址
printf("%d", &i);
//定義一個指針變量pi,指向i的地址
int *pi = &i;
//利用指針運算符*獲取pi指向的內存,即爲i的值
printf("%d", *pi);
注:在C語言中,所有變量的聲明都必須放在最前面,但是有些編譯器你沒放前面也可以通過,這裏注意一下
(3)、&和*運算符的結合方向
“&”和“*”兩個運算符優先級相同,但按從右至左方向結合。可理解爲從右開始運算
//聲明一個變量i
int i = 10;
//聲明一個指針變量pi,指向i
int *pi = &i;
//輸出i的地址
printf("%d", &*pi);
上面的代碼定義了一個指向i的指針變量pi,而輸出i的地址使用了“&*pi”。首先,pi是一個指針變量,pi的內容爲i的地址。因爲運算符是右結合,則先是運算*pi。即爲pi地址中的內容,就是10。然後再取地址,&*pi即爲i的地址。
2.2、指針變量的初始化
void main(){
int a = 10;
//利用取地址符&,獲取變量a的地址,給指針變量pa賦值
int *pa = &a;
}
2.3、指針運算
(1)賦值運算
void main(){
int *px, *py, *pz, x;
//1、指向某個地址
px = &x;
//2、賦予空指針
py = NULL;
//3、賦予指定地址
pz = 4000;
}
(2)指針與整數的加減運算
- 指針變量自增或自減,即指針向前或者向後移動一個存儲單元
- 指針比那裏加上一個整型數,即指針向前或者向後移動指定的存儲單元
(3)關係運算
- px < py,判斷px指向的地址是否小於py指向的地址
- px == py,判斷px和py是否指向同一個地址
- px == 0和px != 0表示px是否爲空指針
接下來來一個小練習:
//聲明函數
void invert(int *a, int start, int end);
/**
* 採用遞歸法對a數組的元素進行逆序
*/
void main(){
}
/**
* 實現函數
* a爲數組首地址
* i位起始逆序元素
* j爲逆序結尾元素
*/
void invert(int *a, int start, int end){
//臨時變量,用於交換
int temp;
//當起始逆序元素小於逆序結尾元素時,說明還沒有逆序到中間元素
if(start < end){
//將起始元素和結尾元素交換
temp = a[start];
a[start] = a[end];
a[end] = temp;
//交換後再次調用invert,將其餘元素逆序,此時start和end要同時向中間移動
invert(a, start + 1, end - 1);
}
}
三、指針與數組
3.1、指向數組的指針
- 數組名即爲該數組的首地址,a爲一個數組,a = &a[0]。
- 可以通過指針對數組元素進行訪問,*a = a[0]、*(a + 1) = a[1]。
- 數組名不能進行指針的操作,像指針p++是合法的,但是數組a++是非法的。
3.2、字符指針和字符數組
在C語言中,系統本身沒有提供字符串數據類型,但可以使用兩種方式存儲一個字符串:字符數組方式和字符指針方式。
(1)字符數組方式
也就是我們比較常用的方式
void main(){
//定義一個字符數組
char sentence[] = "Do not go gengle into that good night!";
printf("%s", sentence);
}
其中sentence就是字符數組的首地址。
(2)字符指針方式實現字符串
void main(){
char *sentence = "Do not go gentle into that good night!";
printf("%s", sentence);
}
來個小練習:
/**
* 用數組將字符串sentence複製到字符串copy
*/
void mian(){
char *sentence = "Do not go gentle into that good night!", copy[50];
int i;
//當沒有遇到結束符時,一直循環
for(i = 0; sentence[i] != '\0'; i++){
//將數據複製到copy中
copy[i] = sentence[i];
}
printf("複製後的copy是:%s", copy);
}
3.3、多級指針及指針數組
(1)多級指針
簡單來說就是指針的指針,指針變量作爲一個變量,也有自己的存儲空間。而這個存儲空間也有一個地址:
void main(){
//定義一個普通變量
int a = 10;
//定義一個指針變量,指向a
int *p = &a;
//定義另一個指針變量,指向指針變量p,此時pp就是二級指針
int **pp = &p;
//輸出兩個指針
printf("一級指針pa爲:%d\n", p);
printf("二級指針ppa爲:%d", pp);
//指針的指針和普通指針操作一樣,可以用*pp獲取pp指向地址中的內容,即p存儲的內容
printf("p存儲的內容爲:%d", *pp);
}
注:因爲一級指針和二級指針性質不一樣,所以一級指針和二級指針之間不能賦值,如p = pp在編譯時會報錯(這是書中寫的,但是在我實際測試當中,可以賦值,可能是編譯器的問題)。
(2)指針數組
即一個元素爲指針的數組,定義如下:
int *a[10];
用一個練習熟悉指針數組,解釋全在註釋當中:
void main(){
//定義並初始化一個int數組
int a[5] = {1, 3, 5, 6, 8}, i;
//定義一個指針數組,與a數組中元素對應
int *p[5];
for(i = 0; i < 5; i++){
p[i] = &a[i];
}
//定義一個二級指針,存放指針數組的首地址。指針數組和普通數組一樣,數組名爲數組首地址
int **pp = p;
//利用指針數組首地址輸出數據
for(i = 0; i < 5; i++){
//數組a中第零個元素地址爲p,而p的的地址爲pp,所以**pp = a[0]
//數組a中第一個元素地址爲p + 1,而p + 1的地址爲pp + 1,所有**(pp + 1) = a[1]
//以此類推,**(pp + n) = a[n]
printf("%d\t", **(pp + i));
}
}
3.4、指針與多維數組
(1)多維數組的地址
假設有個二維數組a[4][2],那麼可以分兩個維度來理解這個數組。
- 先去掉[2],只看“a[4]”一個維度。此時a只是個普通的一維數組,而後面的[2]也只是決定了數組a元素的性質
- 在數組a中,有四個元素,我們取一個來分析第二個維度。其第一個元素爲a[0],我們將a[0]看做一個整體,不作爲數組元素,只作爲一個名稱X。那麼第二個維度就可以看做X[2],即一個有兩個元素的數組。
- 由上面可知,X數組的首地址爲數組名,即X。X實際上是a[0],類推的話X1、X2等就是a[1]、a[2]。可以間接理解爲數組的第一個維度裝的全是地址,每個元素X的地址。
(2)多維數組的指針
舉個例子方便理解
void mian(){
//創建一個普通二維數組
int num[5][5] = {
{1, 3, 4, 5, 6},
{4, 5, 7, 8, 8},
{6, 8, 9, 0, 1},
{3, 4, 2, 1, 2},
{4, 5, 6, 3, 2}
};
//聲明一個指針數組
int *p_num[5];
int i, j;
//初始化指針數組,每個元素分別指向num[0][0]、num[1][0]、、、
for(i = 0; i < 5; i++){
p_num[i] = &num[i][0];
}
//利用指針數組p_num輸出num數組中的元素
for(i = 0; i < 5; i++){
printf("這是第%d輪數組\n", i+1);
for(j = 0; j < 5; j++){
//將p_num[i]作爲一個數組首地址,數組存儲的內容爲*(p_num+j)
printf("%d\t", *(p_num[i] + j));
}
printf("\n");
}
}