10? ?學習指針
1? 指針核心定義與本質
1.1 指針與指針變量
? ? ? ? 1、指針即地址,指針變量是存放地址的變量,其大小與操作系統位數相關:64 位系統中占 8 字節,32 位系統中占 4 字節。
? ? ? ? 2、指針的核心功能是通過地址間接訪問目標變量,實現靈活的內存操作。
1.2 指針類型的關鍵作用
? ? ? ? 1、承載地址信息與內存解析規則,基類型必須與指向數據類型嚴格一致。
? ? ? ? 2、決定內存訪問步長:char*
偏移 1 字節,int*
偏移 4 字節,double*
偏移 8 字節。
1.3 核心要求
????????指針變量本身及指向的內存空間必須確定,嚴禁使用野指針(未初始化)或懸垂指針(指向已釋放空間)。
2? 指針的基本操作規則
2.1 地址獲取與傳遞
????????1、通過&
(取地址運算符)可獲取變量在內存空間中的首地址,只有左值(可被賦值的變量)能進行&
操作,常量、表達式和寄存器變量(register
修飾)不能。
????????2、例如int Num = 0; &Num
可得到Num
在內存中 4 字節空間的首地址,其類型為int *
(由基類型int
升級而來)。
????????3、在函數傳參中,使用&
傳遞變量地址,能實現在被調函數中修改主調函數的變量值,突破值傳遞 “單向傳遞” 的限制(典型應用:函數返回多個值)。
2.2 指針訪問與解引用
????????1、通過*
運算符可獲得指針指向的空間或對應空間中的值,*
連接的內容必須為指針類型(否則編譯報錯)。
? ? ? ? 2、若直接使用*
對應的表達式,其值為指針指向空間中的值,類型為指針類型降級后的基類型,如int *p; *p
的類型為int
。
? ? ? ? 3、變量有兩種訪問形式:直接訪問(通過變量名,如Num = 5
)和間接訪問(通過指針,如*p = 5
)。
3? 字符串函數與指針應用(基于指針實現的庫函數)
????????字符串操作函數的底層實現依賴指針對字符數組的遍歷與修改,核心是通過char*
指針訪問內存中的字符序列(以'\0'
結尾)。
函數 | 功能 | 核心實現邏輯 | 關鍵點 |
---|---|---|---|
strlen | 計算字符串長度 | 指針遍歷至'\0' ,返回指針差值 | const char* 保證只讀,不包含'\0' |
strcpy | 復制字符串 | 逐個字符復制(含'\0' ) | 需保證目標空間足夠,禁止地址重疊 |
strcat | 拼接字符串 | 先移動指針至目標末尾,再復制源字符串 | 目標需足夠大,源和目標均需'\0' 結尾 |
strcmp | 比較字符串 | 按 ASCII 值逐個比較,遇不同字符返回差值 | 非長度比較,一旦不同立即返回 |
3.1?strlen
:計算字符串長度
? ? ? ? 1、功能:返回字符串中'\0'
前的字符個數(不包含'\0'
)。
? ? ? ? 2、底層實現:
size_t strlen(const char *str)
{const char *p = str; // 用指針遍歷字符串while (*p != '\0') { // 遍歷至結束符p++;}return p - str; // 指針差值即長度(字符個數)
}
? ? ? ? 3、關鍵點:const char*
保證不修改原字符串;通過指針自增遍歷,通過指針相減計算長度。
3.2?strcpy
:字符串復制
? ? ? ? 1、功能:將源字符串(src
)復制到目標空間(dest
),包括'\0'
,返回目標地址。
? ? ? ? 2、底層實現:
char *strcpy(char *dest, const char *src)
{char *p = dest; // 保存目標首地址(用于返回)while ((*p++ = *src++) != '\0'); // 逐個復制字符,包括'\0'return dest;
}
????????3、注意事項
????????????????需保證dest
空間足夠大,否則會導致緩沖區溢出。
????????????????源字符串必須以'\0'
結尾,否則會復制隨機數據。
????????????????禁止源地址與目標地址重疊(如strcpy(a, a+1)
會導致未定義行為)。
3.3 strcat
:字符串拼接
? ? ? ? 1、功能:將源字符串(src
)追加到目標字符串(dest
)的末尾(覆蓋dest
原有的'\0'
),返回目標地址。
? ? ? ? 2、底層實現:
char *strcat(char *dest, const char *src)
{char *p = dest;// 1. 移動指針到dest的末尾('\0'位置)while (*p != '\0') {p++;}// 2. 復制src到dest末尾,同strcpy邏輯while ((*p++ = *src++) != '\0');return dest;
}
? ? ? ? 3、注意事項:
????????dest
必須有足夠空間容納拼接后的字符串。
????????dest
和src
都必須以'\0'
結尾。
3.4?strcmp
:字符串比較
? ? ? ? 1、功能:按 ASCII 值比較兩個字符串,返回差值(0
表示相等,正數表示str1
大于str2
,負數表示str1
小于str2
)。
? ? ? ? 2、底層實現:
int strcmp(const char *str1, const char *str2)
{// 遍歷字符,直到遇到不同字符或'\0'while (*str1 != '\0' && *str2 != '\0' && *str1 == *str2) {str1++;str2++;}// 返回對應字符的ASCII差值return (unsigned char)*str1 - (unsigned char)*str2;
}
? ? ? ? 3、關鍵點:
????????????????比較的是字符的 ASCII 值,而非字符串長度。
????????????????一旦遇到不同字符立即返回差值,不繼續比較后續字符。
4? 指針偏移與內存訪問
? ? ? ? 1、指針偏移大小由基類型大小決定:char *
偏移 1 字節,int *
偏移 4 字節,double *
偏移 8 字節,結構體指針偏移整個結構體大小。
? ? ? ? 2、兩個同類型指針相減的結果為地址間相差的數據類型元素個數(非字節數),例如int *p1 = a; int *p2 = a+3; p2-p1
結果為 3(表示相差 3 個int
元素)。
? ? ? ? 3、指針不能與非指針類型運算,也不能跨類型相減(編譯報錯)。
5? 野指針與空指針
? ? ? ? 1、野指針成因:未經初始化的指針(如int *p;
)、指向已釋放空間的指針(如free(p);
后未置空)、越界訪問的指針(如數組越界)。
? ? ? ? 2、空指針:指向內存地址0x0
的指針,用NULL
(本質為(void*)0
)表示,該空間為系統保留的只讀區域,對空指針解引用(*p = 10
)會導致程序崩潰。
? ? ? ? 3、預防野指針:未使用的指針初始化為NULL
;釋放內存后立即置空(free(p); p = NULL;
);避免指針越界訪問。
6? 指針賦值與修改
? ? ? ? 1、對指針變量本身賦值(如p = q;
):改變指針的指向,使其指向新的地址。
? ? ? ? 2、對指針解引用賦值(如*p = *q;
):改變指針指向空間的值,指針指向不變。
? ? ? ? 3、示例:int a=1, b=2; int *p=&a, *q=&b; p=q;
后p
指向b
,*p
結果為 2;*p=*q;
后a
的值變為 2,p
仍指向a
。
7? 動態內存分配與管理
7.1 核心函數
函數 | 功能 | 特點 |
---|---|---|
malloc(size_t size) | 申請size 字節的連續內存 | 未初始化,內容為隨機值;返回void* ,需強轉;失敗返回NULL |
calloc(size_t n, size_t size) | 申請n 個size 字節的連續內存 | 自動初始化為 0;效率略低于malloc |
realloc(void *ptr, size_t size) | 調整已分配內存的大小 | 可能在原地址擴展或重新分配;失敗返回NULL ,原內存不變 |
free(void *ptr) | 釋放動態分配的內存 | 僅釋放ptr 指向的空間,不改變指針值;不能重復釋放或釋放非動態內存 |
7.2 內存管理規則
? ? ? ? 1、動態內存必須手動釋放,否則導致內存泄漏(程序運行中內存占用持續增長)。
? ? ? ? 2、釋放后指針需置空(p = NULL;
),避免成為野指針。
? ? ? ? 3、申請內存后必須檢查返回值是否為NULL
(防止內存不足導致崩潰):
int *p = (int*)malloc(10*sizeof(int));
if (p == NULL) { /* 內存申請失敗處理 */ }
? ? ? ? 4、避免 “內存碎片”:頻繁申請和釋放小塊內存會導致內存碎片,可通過內存池優化。
8? 指向函數的指針與指針函數
類型 | 定義 | 特點 |
---|---|---|
指針函數 | 返回指針的函數(類型* 函數名(參數) ) | 不可返回局部變量地址,可返回動態內存、全局變量地址 |
函數指針 | 指向函數的指針(返回類型 (*指針名)(參數列表) ) | 需匹配函數返回類型、參數類型和個數,用于回調函數(如qsort 比較器) |
8.1 指針函數
? ? ? ? 1、定義:返回值為指針的函數,格式為類型 *函數名(參數列表)
。
? ? ? ? 2、注意:不能返回局部變量的地址(局部變量在函數結束后釋放,返回后成為野指針),可返回動態分配內存、全局變量或靜態變量的地址。
? ? ? ? 3、示例:
int *createArray(int n)
{int *arr = (int*)malloc(n*sizeof(int));return arr; // 返回動態內存地址,需外部釋放
}
8.2 函數指針
? ? ? ? 1、定義:指向函數的指針,格式為返回類型 (*指針名)(參數類型列表)
,需嚴格匹配函數的返回類型、參數類型和個數。
? ? ? ? 2、函數名本質是函數入口地址,可直接賦值給函數指針(無需&
)。
? ? ? ? 3、應用:實現回調函數(如排序函數qsort
的比較器)、函數接口封裝,降低模塊耦合性。
? ? ? ? 4、示例:
int add(int a, int b)
{return a + b;
}
int (*funcPtr)(int, int) = add; // 函數指針指向add
int result = funcPtr(3, 4); // 調用函數,結果為7
9? 指針與數組的關系
9.1數組名的特殊性
? ? ? ? 1、數組名是指向首元素的指針常量(不可修改指向),如int a[5]; a
等價于&a[0]
,類型為int *
。
? ? ? ? 2、例外情況:
????sizeof(數組名)
:獲得數組總字節數(如int a[5]; sizeof(a) = 20
)。
????&數組名
:類型為指向整個數組的指針(如int (*)[5]
),偏移量為整個數組大小。
? ? ? ? ? ? ? ?數組名作為sizeof
參數或取地址時,不退化為首元素指針。
9.2 數組作為函數參數
? ? ? ? 1、三種傳遞形式等價:int fun(int a[5]);
、int fun(int a[]);
、int fun(int *a);
,函數內均按指針處理,丟失數組長度信息。
? ? ? ? 2、必須顯式傳遞數組長度:int fun(int *a, int len)
,避免越界訪問。
9.3 字符數組與字符串
? ? ? ? 1、字符串本質是'\0'
結尾的字符數組,傳參時可直接傳遞數組名(即char *
指針)。
? ? ? ? 2、遍歷字符串:while (*p != '\0') { printf("%c", *p++); }
。
? ? ? ? 3、字符串常量存儲在只讀區,不能通過指針修改(如char *p = "hello"; *p = 'H';
會崩潰)。
10? const 指針
10.1 三種形式及特性
定義 | 含義 | 指針值是否可改 | 指向空間是否可改 | 必須初始化 |
---|---|---|---|---|
const int *p; ?或?int const *p; | const 修飾*p | 是 | 否 | 否 |
int *const p; | const 修飾p | 否 | 是 | 是 |
const int *const p; ?或?int const *const p; | const 修飾p 和*p | 否 | 否 | 是 |
10.2 應用場景
? ? 1、const int *p
:保護指向的數據不被修改(如函數參數傳遞只讀數據)。
? ? 2、int *const p
:確保指針始終指向同一變量(如硬件寄存器地址)。
? ? 3、const int *const p
:既固定指針指向,又保護數據(如常量配置參數)。
11? 指針數組與數組指針
11.1 概念區分
類型 | 本質 | 定義形式 | 內存占用(64 位) | 示例 |
---|---|---|---|---|
指針數組 | 數組,元素為指針 | int *a[5]; | 5×8=40 字節 | char *strs[] = {"apple", "banana"}; |
數組指針 | 指針,指向數組 | int (*a)[5]; | 8 字節 | int (*p)[3] = &arr; (arr 為int[3] 數組) |
11.2數組指針與二維數組
? ? ? ? 1、二維數組名是指向第一行的數組指針,類型為int (*)[列數]
,如int a[2][3];
中a
的類型為int (*)[3]
。
? ? ? ? 2、訪問元素的方式:a[i][j]
、*(a[i] + j)
、*(*(a + i) + j)
、(*(a + i))[j]
。
? ? ? ? 3、遍歷二維數組:
int a[2][3] = {{1,2,3}, {4,5,6}};
int (*p)[3] = a; // p指向第一行
for (int i=0; i<2; i++)
{for (int j=0; j<3; j++) {printf("%d ", *(*(p+i) + j));}
}
12? 二級指針
12.1 定義與本質
? ? ? ? 1、二級指針是指向一級指針變量的指針,格式為類型 **p
,64 位系統中占 8 字節,用于存儲一級指針的地址。
? ? ? ? 2、示例:int a=10; int *p=&a; int **q=&p;
中q
是二級指針,*q
等價于p
,**q
等價于a
。
12.2 使用場景
? ? ? ? 1、函數內部修改外部指針的指向:
void allocMemory(int **p, int size)
{*p = (int*)malloc(size); // 修改外部指針p的指向
}
int *arr;
allocMemory(&arr, 10*sizeof(int)); // 傳遞一級指針的地址
? ? ? ? 2、指針數組傳參:指針數組的數組名是二級指針,如char *strs[] = {"a", "b"};
傳參時類型為char **
。
? ? ? ? 3、動態二維數組:int **arr = (int**)malloc(3*sizeof(int*));
用于創建行長度可變的二維數組。
13? 指針作為函數參數
13.1 傳遞方式對比
傳遞方式 | 特點 | 適用場景 |
---|---|---|
值傳遞 | 形參是實參的副本,修改形參不影響實參 | 函數僅使用參數值,不修改原變量 |
地址傳遞(一級指針) | 形參指向實參地址,可修改實參的值 | 函數需修改原變量的值(如交換兩個變量) |
二級指針傳遞 | 形參指向一級指針的地址,可修改一級指針的指向 | 函數需為外部指針分配內存或改變其指向 |
13.2典型應用:交換兩個變量
void swap(int *a, int *b)
{int temp = *a;*a = *b;*b = temp;
}
int x=1, y=2;
swap(&x, &y); // 調用后x=2, y=1
14? 學習總結
1、野指針操作:對未初始化、已釋放或越界的指針解引用,導致程序崩潰或數據損壞。
解決:初始化指針為NULL
,釋放后立即置空,避免越界。
2、類型不匹配:用char*
指針訪問int
變量(如char *p = (char*)&a;
)可能導致數據截斷或解析錯誤。
解決:嚴格保證指針類型與指向數據類型一致。
3、內存泄漏:動態分配的內存未釋放,長期運行導致系統內存耗盡。
解決:遵循 “誰申請誰釋放” 原則,使用智能指針(C++)或內存池管理。
4、重復釋放內存:對同一指針多次調用free
,導致內存管理混亂。
????????解決:釋放后立即置空,釋放前檢查是否為NULL
。
5、字符串函數使用錯誤:
? ? ? ? (1)使用strcpy
時目標空間不足,導致緩沖區溢出(如char dest[3]; strcpy(dest, "hello");
)。
解決:使用strncpy
限制復制長度,或確保目標空間足夠。
? ? ? ? (2)對非'\0'
結尾的字符序列使用strlen
,導致遍歷越界(如未初始化的字符數組)。
解決:確保字符串以'\0'
結尾,或手動指定長度。
6、const 指針違規操作:對const int *p
嘗試修改指向空間的值(*p = 5
),編譯報錯。
解決:明確const
修飾的對象,避免違規修改。
7、混淆指針數組與數組指針:錯誤定義(如int (*a)[5]
寫成int *a[5]
)導致內存訪問錯誤。
解決:記住優先級:[]
高于*
,數組指針需加括號。
8、返回局部變量地址:函數返回棧上變量的地址(如int *func() { int a=1; return &a; }
),返回后地址失效。
解決:返回全局變量、靜態變量或動態分配內存的地址。