C語言:第14天筆記
內容提要
- 指針
- 變量指針與指針變量
- 指針變量做函數參數
- 指針變量指向數組元素
- 數組指針與指針數組
- 數組指針
- 變量指針與指針變量
回顧
變量指針與指針變量
變量指針:變量的地址值(首地址),本質是指針、地址
指針變量:存儲指針的變量,本質是變量
指針操作的兩個運算符
&
:取地址運算符,作用是獲取指定對象的地址
*
:指針操作符,如果這個符號前面有數據類型,就被稱作聲明指針;如果沒有,就被稱作解引用
關于指針中指向的問題
int a = 10;
int *p = &a // 指針變量p指向對象a
指向:指針變量存儲了誰的地址,這個指針變量就指向了誰!
使用間接操作,如何交換a和b的值
- 交換指向:指向發生改變,指向對象的數據不會改變
- 交換數據:指向不發生改變,指向對象的數據會改變
關于指針變量
指針變量本質上還是變量,只不過指針變量只能存儲其他內存單元的地址,我們借助于指針變量,可以實現內存空間的共享。
關于共享
-
共享他人的空間
int a = 10; int *p = &a; int *q = p; // p和q共享a的空間
-
共享自己的空間
int a = 10; int *p = &a; // a和p共享a的空間
指針
變量指針與指針變量
指針變量做函數參數
指針變量做函數參數往往傳遞的是變量的首地址,借助于指針變量間接訪問是可以修改實參變量數據的。
指針有一個作用就是,通過形參修改實參,我們將這樣的參數稱之為輸出型參數。
案例
需求:有a,b兩個變量,要求交換后輸出,使用函數處理,用指針變量做函數的參數
-
方式1:交換指向(指針指向改變,指向對象的數據不變)
代碼:
#include <stdio.h>
/**
-
方式1:交換指向
*/
void swap(int *p_a, int *p_b)
{
int *p_t;// 交換
p_t = p_a;
p_a = p_b;
p_b = p_t;printf(“交換后:%d,%d\n”,*p_a, *p_b); // 交換后:4,3
}
int main(int argc,char *argv[])
{
int a = 3, b = 4;
printf(“交換前:%d,%d\n”, a, b);// 交換前:3,4swap(&a, &b); // 傳參的過程可以理解:int *p_a = &a, int *p_b = &b;return 0;
}
-
-
方式2:交換數據(指針指向不變,指向對象的數據改變)
代碼:
#include <stdio.h>
/**
-
方式2:交換數據
*/
void swap(int *p_a, int *p_b)
{
int temp;// 交換
temp = *p_a;
*p_a = *p_b; // 將p_b指向對象的值賦給p_a指向的對象
*p_b = temp; // p_b:訪問指針變量的空間,*p_b:訪問指針指向對象的空間printf(“交換后:%d,%d\n”,*p_a, *p_b); // 交換后:4,3
}
int main(int argc,char *argv[])
{
int a = 3, b = 4;
printf(“交換前:%d,%d\n”, a, b);// 交換前:3,4swap(&a, &b); // 傳參的過程可以理解:int *p_a = &a, int *p_b = &b;return 0;
}
-
指針變量指向數組元素【重難點】
數組元素的指針
- 數組的指針就是數組中第一個元素的地址,也就是數組的首地址。
- 數組元素的指針是指數組的首地址。因此,同樣可以用指針變量來指向數組或者數組元素。
- 在C語言中,由于數組名代表數組的首地址,因此數組名實際上也是指針。訪問數組名就是訪問數組首地址。
范例:
#include <stdio.h>int main(int argc,char *argv[])
{// 創建一個數組int arr[] = {11,22,33};int *p1 = &arr[0]; // 指針變量指向數組arr第一個元素,指針的范圍就是數組元素int *p2 = arr; // 等價于上面寫法,數組名默認就是一個指向首元素地址的指針,推薦printf("%p,%p,%p\n", p1, p2, arr); // 0x7ffe33135e2c,0x7ffe33135e2c,0x7ffe33135e2creturn 0;
}
注意:雖然我們定義了一個指針變量接收了數組地址,但不能理解為指針變量指向了數組,而應該理解為指向來了數組的元素(默認為第1個元素)。
指針的運算
指針運算:前提是指針變量必須要指向數組的某個元素。(指針運算只能在同一數組內進行,并且只能是元素之間的偏移)
序號 | 指針運算 | 偏移量 | 說明 |
---|---|---|---|
1 | 自增:p++、++p、p+=1 | sizeof(type) | 指向下一個元素的首地址 需邊界檢測,防止越界 |
2 | 自減:p--、--p、p-=1 | sizeof(type) | 指向上一個元素的首地址 需邊界檢測,防止越界 |
3 | 加n個數:p+n | n * sizeof(type) | 指向后面n個元素的首地址 需邊界檢測,防止越界 |
4 | 減n個數:p-n | n * sizeof(type) | 指向前面n個元素的首地址 需邊界檢測,防止越界 |
5 | 指針相減:p1 - p2 | ` | (p1 - p2) |
6 | 指針比較:p1 < p2 | 邏輯值:真(1),假(0) | 前面的指針小于后面的指針 |
注意:
-
上面表格中的type,是指針指向數組的元素的類型
-
sizeof不支持運算,舉例:
#include <stdio.h>int main(int argc,char *argv[]) {int a = 10;printf("sizeof(a)=%lu,sizeof(int)=%lu,sizeof(++a)=%lu\n", sizeof(a), sizeof(int), sizeof(++a)); // sizeof(a)=4,sizeof(int)=4,sizeof(a++)=4return 0; }
說明:
① 如果指針變量p已指向數組中的一個元素,則p+1指向同一數組中的 下一個元素,p-1指向同一數組中的上一個元素。即p+1或p-1也表示地址。但要注意的是,雖然指針變量p中存放的是地址,但p+1并不表示該地址加1,而表示在原地址的基礎上加了該數據類型所占的字節數d(d = sizeof(數據類型)) 。
② 如果p原來指向a[0],執行++p后p的值改變了,在p的原值基礎上加d,這樣p就指向數組的下一個元素a[1]。d是數組元素占的字節數。
③ 如果p的初值為&a[0]則p+i 和a+i 就是數組元素a[i]的地址,或者說,它們指向a數組的第 i 個元素 。
④
*(p+i)
或*(a+i)是p+i或a+i所指向的數組元素,即a[i]。⑤ 如果指針變量p1和p2都指向同一數組,如執行p2-p1,結果是兩個地址之差除以數組元素的長度d。
案例
#include <stdio.h>
#include <math.h>int main(int argc,char *argv[])
{// 創建一個用來實現指針運算的數組int arr[] = {11,22,33,44,55};int *p1 = arr + 4; // 55 等價于 arr[4]int *p2 = arr + 1; // 22 等價于 arr[1]size_t size = fabs(p2 - p1); // 3 = fabs(22對應的地址 - 55對應的地址) / int的字節數printf("*p1=%d,*p2=%d,size=%lu,&arr[1]+2=%d\n", *p1, *p2, size, *(&arr[1]+2));return 0;
}
運行結果:
案例
-
需求:通過下標法和指針法遍歷數組
-
代碼:
#include <stdio.h>/*** 下標法遍歷數組*/ void arr1(int arr[], int len) // 數組作為函數參數,傳遞的是數組的首地址(數組被降級為指針) {for (register int i = 0; i < len; i++) printf("%-4d", arr[i]);printf("\n"); }/*** 指針法遍歷數組*/ void arr2(int arr[], int len) {// 創建一個指針變量,接收數組,此時實際上接收到的是數組中第一個元素的地址int *p = arr;for (register int i = 0; i < len; i++) printf("%-4d",/* *(arr+i) 等價于*/ *(p+i));printf("\n"); }/*** 指針法遍歷數組*/ void arr3(int arr[], int len) {int *p = arr;for (register int i = 0; i < len; i++){printf("%-4d", *p);p++;}printf("\n"); }/*** 指針法遍歷數組*/ void arr4(int arr[], int len) {int *p = arr;for(; p < arr + len; p++) // 判斷的時候不能寫作 p + len,因為p在變化,而arr沒有變化{printf("%-4d", *p);}printf("\n"); }int main(int argc,char *argv[]) {int arr[] = {11,22,33,44,55};int len = sizeof(arr) / sizeof(arr[0]);arr1(arr, len);arr2(arr, len);arr3(arr, len);arr4(arr, len);return 0; }
案例
-
需求:推導以下代碼的運行結果
-
代碼:
#include <stdio.h>int arr2() {// 創建一個普通數組int arr[] = {11,22,33,44,55,66,77,88};int *p = arr;printf("%d\n", *p); // 11p++; // 指針偏移 1 * sizeof(int) 指針移動到22這個位置printf("%d\n", *p); // 22int x = *p++; // 第1步:解引用p的值賦值給x, x = 22; 第2步:p++,指針移動到33這個位置printf("%d,%d\n", x, *p);// 22,33int y = *(++p);// 第1步:++p,指針偏移到44這個位置;第2步:對44這個地址解引用,得到44printf("%d,%d\n", y, *p);// 44,44(*p)++; // 第1步:對p解引用得到44;第2步:對44這個值+1,得到45printf("%d\n",*p); // 45 }
※小貼士:
*p++:先解引用p,然后p這個指針自增(指針自增)
int arr[] = {11,22,33}, *p = arr;int x = *p++; // x=11,*p=22 ① *p解引用,其實就是將指向的對象a的值賦值給x ② 指針p++,也就是指針偏移一位
(*p)++:先解引用p,然后使用解引用出來的數據自增(數值自增)
int arr[] = {11,22,33}, *p = arr;int x = (*p)++; // x=11,*p=12 ① *p解引用,其實就是將指向的對象a的值賦值給x ② 解引用出來的對象數據自增
通過指針引用數組元素
引用一個數組元素,可以用:
① 下標法:如arr[i]
② 指針法:如*(arr + 1)
或者*(p+i)
。其中arr是數組名,p是指向數組元素的指針變量,其初始值:p = arr;
案例
需求:
-
下標法:(通過改變下標輸出所有元素)
#include <stdio.h>int main() {int arr[10], i;// 給數組元素賦值for (i = 0; i < 10; i++) scanf("%d", &arr[i]);// 遍歷數組元素for (i = 0; i < 10; i++) printf("%-4d", arr[i]);printf("\n");return 0; }
-
指針法(地址):(通過數組名計算出數組元素的地址,找出數組元素值)
#include <stdio.h>int main() {int arr[10], i;// 給數組元素賦值for (i = 0; i < 10; i++) scanf("%d", &arr[i]);// 遍歷數組元素for (i = 0; i < 10; i++) printf("%-4d", *(arr + i)); printf("\n");return 0; }
-
指針法(指針變量):(用指針變量指向數組元素)
#include <stdio.h>int main() {int arr[10], i, *p;// 給數組元素賦值for (i = 0; i < 10; i++) scanf("%d", &arr[i]);// 遍歷數組元素for (p = arr; p < (arr + 10); p++) printf("%-4d", *p);printf("\n");return 0; }
注意:數組一旦創建,就無法改變其值。
以上3種寫法比較:
- 第①種寫法和第②種寫法執行效率相同。系統是將arr[i]轉換為*(arr+i)處理的,即先計算出地址,因此比較費時。
- 第③種方法比第①②種方法快。用指針變量直接指向數組元素,不必每次都重新計算地址。(p++)能大大提高執行效率。
- 用第①種寫法比較直觀,而用地址法或者指針變量的方法難以很快判斷出當前處理的元素。
使用指針變量指向數組元素時(上面第③種寫法),注意以下前兩點:
①
*(p--) 相當于arr[i--],先*p,再p--;
*(p++) 相當于arr[i++],先*p,再p++;
②
*(--p) 相當于arr[--i],先--p,再*
*(++p) 相當于arr[++i],先++p,再*;
③
*p++ 先*p,再p++
④
(*p)++ 先*p,再*p++
具體關系參照下面表格:
操作類型 指針表達式 數組下標等價 執行順序 指針移動方向 是否改變指針地址 前置自減+取值 *(--p)
arr[--i]
1. 指針前移
2. 取新地址的值向前(←) √ 前置自加+取值 *(++p)
arr[++i]
1. 指針后移
2. 取新地址的值向后(→) √ 后置自減+取值 *(p--)
arr[i--]
1. 取原地址的值
2. 指針前移向前(←) √ 后置自加+取值 *(p++)
arr[i++]
1. 取原地址的值
2. 指針后移向后(→) √ 后置自減(簡寫) *p--
arr[i--]
1. 取原地址的值
2. 指針前移向前(←) √ 后置自加(簡寫) *p++
arr[i++]
1. 取原地址的值
2. 指針后移向后(→) √ 取值后值自減 (*p)--
arr[i]--
1. 取原地址的值
2. 值-1不移動 × 取值后值自加 (*p)++
arr[i]++
1. 取原地址的值
2. 值+1不移動 ×
數組名作函數參數
① 形參和實參都是數組名
// arr 數組 形參
void fun(int arr[], int len){..}void main()
{int arr[] = {11,22,33};int len = sizeof(arr) / sizeof(arr[0]);// arr 數組 實參fun(arr, len);
}
② 實參用數組名,形參用指針變量
// arr 指針 形參
void fun(int *arr, int len){..}void main()
{int arr[] = {11,22,33};int len = sizeof(arr) / sizeof(arr[0]);// arr 數組 實參fun(arr, len);
}
③ 實參和形參都用指針變量
// arr 指針 形參
void fun(int *arr, int len){..}void main()
{int arr[] = {11,22,33};int len = sizeof(arr) / sizeof(arr[0]);// arr 指針 實參int *p = arr;fun(p, len);
}
④ 實參用指針,形參用數組名
// arr 數組 形參
void fun(int arr[], int len){..}void main()
{int arr[] = {11,22,33};int len = sizeof(arr) / sizeof(arr[0]);// arr 指針 實參int *p = arr;fun(p, len);
}
案例:
需求:將數組a中的n個整數按相反順序存放(數組反轉)
分析:
代碼:
#include <stdio.h>/*** 數組的反轉:下標法*/
void inv1(int arr[], int len)
{// 反轉思路:第0個和最后一個交換,第1個和倒數第二個交換...// 定義循環變量和臨時變量register int i = 0, temp;// 遍歷數組for (; i < len/2; i++){temp = arr[i];arr[i] = arr[len-1-i];arr[len-1-i] = temp;}
}/*** 數組的反轉:指針法*/
void inv2(int *p, int len)
{// 反轉思路:第0個和最后一個交換,第1個和倒數第二個交換...// 定義循環變量和臨時變量int *i = p, *j = p + len - 1, temp;// 遍歷數組for (; i < j; i++, j--){temp = *i;*i = *j;*j = temp;}
}/*** 遍歷數組*/
void list(const int *arr, int len) // const int *arr = arr;
{const int *p = arr; // 添加const之后,指針指向對象的值不變,指針指向可以改變for (; p < arr + len; p++) printf("%-4d", *p); printf("\n");
}int main(int argc,char *argv[])
{int arr[] = {11,12,13,14,15};int len = sizeof(arr) / sizeof(arr[0]);list(arr, len);inv1(arr, len);list(arr, len);inv2(arr, len);list(arr, len);return 0;
}
數組指針與指針數組
數組指針
定義
**概念:**數組指針是指向數組的指針(指針變量),本質上還是指針。
指針變量指向數組元素和數組指針的區別?
特點:
① 先有數組,再有指針
② 它指向的是一個完整的數組
一維數組指針
語法:
數據類型 (*指針變量名)[容量];
案例:
#include <stdio.h>int main(int argc,char *argv[])
{// 一維數組指針int arr[] = {100,200,300};int len = sizeof(arr) / sizeof(arr[0]);// 定義一個數組指針(一維數組指針)int (*p)[len] = &arr; // arr默認指向數組元素,&arr指向整個數組,需要注意的的是,它們表示的范圍不同,地址相同// p++:此時不能p++,否則會越界printf("&arr=%p,arr=%p,&arr[0]=%p\n", &arr, arr, &arr[0]); // arr 等價于 &arr[0]// 如何訪問數組指針printf("%d\n", (*p)[2]); // 300// 遍歷數組指針for (int i = 0; i < len; i++) printf("%-6d", (*p)[i]); printf("\n");return 0;
}
我們之前所學的是指向數組元素的指針,本質上是指針變量;現在我們學的是指向數組的指針,叫作數組指針。
二維數組指針
語法:
數據類型 (*指針變量名)[行容量][列容量];
案例:
-
寫法1:二維數組指針指向二維數組【不推薦】
#include <stdio.h>int main(int argc,char *argv[]) {// 創建一個二維數組int arr[][3] = {10,20,30,100,200,300,1000,2000,3000};// 定義一個二維數組指針指向二維數組int (*p)[][3] = &arr;// 遍歷數組for (int i = 0; i < 3; i++){for (int j = 0; j < 3; j++){printf("%-6d", (*p)[i][j]);}}printf("\n");return 0; }
-
寫法2:一維數組指針指向二維數組【推薦】
#include <stdio.h>int main(int argc,char *argv[]) {// 創建一個二維數組int arr[][3] = {10,20,30,100,200,300,1000,2000,3000};// 定義一個一維數組指針指向二維數組,相當于指針指向的是二維數組的行 [行容量]int (*p)[3] = arr; // 等價于 &arr[0] (*p):指向數組的行 int arr[] = {100, 200, 300}; int *p = arr; 解引用p 得到第一個元素// 遍歷數組for (int i = 0; i < 3; i++){for (int j = 0; j < 3; j++){printf("%-6d", p[i][j]);// printf("%-6d", *(*(p+i)+j));// printf("%-6d", (*(p+i))[j]);// printf("%-6d", *(p[i]+j));}}printf("\n");return 0; }