本章概述思維導圖:
C語言指針
指針是C語言中最強大但也最容易混淆的特性之一。它提供了直接操作內存地址的能力,使得C語言具有高效性和靈活性。下面我將詳細介紹C語言指針的各個方面。
指針定義
指針的本質:指針是一個變量,其值為另一個變量的內存地址;可以理解為:(普通變量:存儲數據值的變量)、(指針變量:存儲內存地址的變量);
指針變量定義格式:
指針查詢數據和修改數據格式:
指針注意事項:
1.數據類型 :要跟指向變量的類型保持一致;
2.定義中的' * ':這是標記,不是解引用的含義;
3.變量名和指針名都是自己起的名字;
4.指針指向變量名時,指向的是內存地址,并不是數據,所以要用取地址符&;
5.定義中的‘ * ’是標記,查詢數據和存儲數據中的‘ * ’是解引用,二者并不一樣;
代碼示例:
#include <stdio.h>
int main()
{int num1=100;int *p=&num1;printf("num1=%d\n",num1);printf("指針指向的內存地址上的值為:%d\n",*p);return 0;
}//代碼運行結果:
num1=100
指針指向的內存地址上的值為:100
代碼講解:首先定義一個整型變量num1
并賦值為100,然后聲明一個整型指針p
并用&
運算符將num1
的地址賦給它(此時p
存儲的是num1
的內存地址),接著通過printf
直接輸出num1
的值,再通過解引用指針*p
(即訪問p
指向的內存地址中的值)輸出相同結果,最終證明指針p
確實指向了num1
的內存空間。
指針使用細節:
1.指針變量的名字不能重復定義;
2.指針變量的數據類型要跟指向變量的類型保持一致;
3.指針變量占用的大小,跟數據類型無關,跟操作系統有關;(32位操作系統:占4個字節,64位操作系統占8個字節);
4.給指針變量賦值時,不能把一個數值賦值給指針變量;
指針的作用
指針的作用1:操作其它函數中的變量
指針的一個重要作用是在不同函數之間直接操作變量,避免全局變量的使用,同時提高代碼的靈活性和效率。
代碼示例(利用指針在不同函數交換變量):
#include <stdio.h>
void huan(int *p1,int *p2);//函數封裝:交換變量
int main()
{int num1=100;int num2=1000;printf("初始值為:num1=%d\tnum2=%d\n",num1,num2);huan(&num1,&num2);printf("交換后值為:num1=%d\tnum2=%d\n",num1,num2);return 0;
}
void huan(int *p1,int *p2)//函數封裝:交換變量
{int temp=0;temp=*p1;*p1=*p2;*p2=temp;
}//代碼運行結果:
初始值為:num1=100 num2=1000
交換后值為:num1=1000 num2=100
代碼講解:在?main
?函數中定義兩個變量?num1
?和?num2
,調用?huan
?函數時傳遞它們的地址(&num1
?和?&num2
),huan
?函數通過指針參數(int *p1
?和?int *p2
)接收地址,并通過解引用(*p1
?和?*p2
)直接修改原始變量的值,最終完成交換。
關鍵點在于傳遞地址而非值,使得函數能操作外部變量,避免了值傳遞的副本限制,體現了指針在間接訪問和修改數據時的核心作用。
指針的作用2:函數返回多個值
在C語言中,函數通常只能通過返回值傳遞一個結果,但通過指針參數,可以繞過這一限制,實現返回多個值的效果。
代碼示例(在數組中找出最大值和最小值):
#include <stdio.h>
void out_max_min(int arr[],int len,int *p1,int *p2);//函數封裝:找最大值和最小值
int main()
{int arr[]={88,54,69,75,25};int len=sizeof(arr)/sizeof(int);for(int i=0;i<len;i++){printf("%d\t",arr[i]);}putchar('\n');int max=0,min=0;out_max_min(arr,len,&max,&min);printf("數據中最大值為%d\t最小值為:%d\n",max,min);}
void out_max_min(int arr[],int len,int *p1,int *p2)//函數封裝:找最大值和最小值
{*p1=arr[0];*p2=arr[0];for(int i=0;i<len;i++){if(*p1<arr[i])//找最大值{*p1=arr[i];}if(*p2>arr[i])//找最小值{*p2=arr[i];}}
}//代碼運行結果:
88 54 69 75 25
數據中最大值為88 最小值為:25
代碼講解:在?main()
?函數中定義數組并計算長度,打印數組后調用?out_max_min()
?函數,傳入數組、長度以及?max
?和?min
?的地址;該函數先將指針指向的值初始化為數組首元素(避免初始化為?0
?導致全負數數組出錯),然后遍歷數組,通過指針更新最大值和最小值,最終返回結果到?main()
?函數并打印。
指針的作用3:函數的結果和計算狀態分開
在C語言中,指針的核心價值之一是實現“計算邏輯”與“結果存儲”的分離,使函數能夠專注于核心任務(如數據處理),而將結果的去向交由調用者控制。
代碼示例(定義函數,將兩數相除,獲取他們的余數。返回值為這段算數是否有意義):
#include <stdio.h>
int yu(int *p1,int *p2,int *p3);
int main()
{int num1,num2;int num3=0;printf("輸入兩個數智能獲取余數\n");scanf("%d%d",&num1,&num2);int flag=yu(&num1,&num2,&num3);if(flag){printf("式子無意義");}else{printf("余數為:%d",num3);}return 0;
}
int yu(int *p1,int *p2,int *p3)//函數封裝:將兩數相處,獲取余數。返回值為0這個式子有意義,返回值為1,這個式子無意義
{if(*p2 == 0){return 1;}*p3=*p1%*p2;return 0;
}//代碼運行結果:
輸入兩個數智能獲取余數
85
7
余數為:1
代碼講解:主函數中用戶輸入兩個整數后,將它們的地址連同存儲余數的變量地址一起傳給yu()
函數,函數內通過指針參數直接讀取輸入值并檢查除數是否為零,若為零則返回錯誤標志1
,否則計算余數并通過指針將結果寫入主函數的變量中,最后主函數根據返回值判斷輸出余數或錯誤提示,這種設計既利用指針高效傳遞了計算結果,又通過返回值清晰區分了正常和異常情況。
指針作用4:方便操作數組和函數傳參
指針操作數組的核心優勢在于它能直接通過內存地址高效訪問和修改元素,避免了數組下標計算的間接開銷,同時支持動態內存分配(如用malloc
創建可變大小的數組),且在函數間傳遞數組時只需傳遞首地址指針而非整個數組,極大節省了內存和時間開銷,尤其適合處理大規模數據或多維數組等復雜結構,但需注意指針的邊界控制以防止越界訪問。
代碼示例(在函數中使用指針對數組進行遍歷):
#include <stdio.h>
void arr_bianli(int *p,int *p1);//函數封裝:利用指針對數組遍歷
int main()
{int arr1[]={1,2,3,4,5,6,7,8,9};int len=sizeof(arr1)/sizeof(int);arr_bianli(arr1,&len);return 0;
}
void arr_bianli(int *p,int *p1)//函數封裝:利用指針對數組遍歷
{for(int i=0;i<*p1;i++){printf("arr[%d]=%d\t",i,*(p+i));}putchar('\n');
}//代碼運行結果:
arr[0]=1 arr[1]=2 arr[2]=3 arr[3]=4 arr[4]=5 arr[5]=6 arr[6]=7 arr[7]=8 arr[8]=9
代碼講解:主函數中定義數組?arr1
?并計算其長度?len
,調用?arr_bianli()
?時傳遞數組首地址(arr1
)和長度指針(&len
),函數內通過指針解引用獲取長度(*p1
)控制循環次數,并利用指針算術(*(p+i)
)訪問每個元素并打印其下標和值,這種方式避免了數組拷貝,直接操作內存地址,既高效又清晰地展示了指針與數組的緊密關聯。
指針的計算
指針的計算是C語言中操作內存地址的核心機制,它通過基于數據類型大小的地址偏移實現高效的數據訪問和內存操作。
指針的定義:
指針中數據類型的作用:獲取字節數據的個數;
步長:指針移動一次的字節個數
char:1個字節;????????short:2個字節? ? ? ? int:4個字節? ? ? ? longlong:8個字節;
指針加法:指針往后移動了n步,總字節=n*sizeof(數據類型)=N個字節;
指針減法:指針往前移動了n步,總字節=n*sizeof(數據類型)=N個字節;
代碼示例:
#include <stdio.h>
int main()
{//int類型指針步長int arr1[]={1,22,33,44};int *p1=arr1;printf("int類型指針移動2次多少字節:%d字節\narr1[2]=%d\n",2*sizeof(int),*(p1+2));//char類型指針步長char arr2[]="abcd";char *p2=arr2;printf("char類型指針移動2次多少字節:%d字節\narr2[2]=%c\n",2*sizeof(char),*(p2+2));return 0;
}//代碼運行結果:
int類型指針移動2次多少字節:8字節
arr1[2]=33
char類型指針移動2次多少字節:2字節
arr2[2]=c
代碼講解:指針的加減運算會根據其指向的數據類型自動計算地址偏移量:當對指針進行?+n
?或?-n
?操作時,實際移動的字節數等于?n × sizeof(數據類型)
(例如?int*
?每次移動4字節,char*
?每次移動1字節),因此?*(p + i)
?能直接訪問數組第i個元素(如?int* p
?指向數組時,p+2
?會跳過8字節訪問?arr[2]
),這種機制使得指針運算與數組索引天然對應,但需注意避免越界訪問。
指針步長和指針變量占用大小的區別
指針步長和指針變量占用大小是兩個完全不同的概念,前者決定指針移動時的地址偏移量,后者反映指針變量本身在內存中占用的空間。
指針步長:由指針指向的數據類型決定,計算公式為:步長 =?sizeof(數據類型);
指針變量占用大小:
指針變量本身在內存中占用的字節數。由操作系統和編譯器決定(與指針類型無關),通常為:32位系統:4字節、64位系統:8字節;
C語言野指針
野指針(Wild Pointer)是指未初始化或已釋放的指針變量,其指向的內存地址不可預測或無效。訪問野指針會導致未定義行為(如程序崩潰、數據損壞)。
野指針的常見成因
1.未初始化的指針
代碼示例:
#include <stdio.h>
int main()
{int *p;*p=10;printf("%d\n",*p);return 0;
}//代碼運行結果:
段錯誤 (核心已轉儲)
代碼講解:未初始化,p的值是隨機地址(野指針),危險!可能訪問非法內存;
2. 指針越界訪問
代碼示例:
#include <stdio.h>
int main()
{int arr[3]={1,2,3};int *p=arr;p+=5;printf("%d\n",*p);return 0;
}//代碼運行結果:
-912009440
C語言野指針總結
野指針的本質:指向不可控或無效內存的指針。
防范手段:
1. 初始化指針為?NULL
。
2. 釋放內存后立即置空。
3. 確保指針操作在合法范圍內。
C語言懸空指針
懸空指針是指指向已被釋放或回收的內存地址的指針。此時指針仍保存著舊的內存地址,但該地址已無效,訪問它會導致未定義行為(如程序崩潰或數據損壞)。
懸空指針的常見成因
1.內存被釋放后未置空
代碼示例:
#include <stdio.h>
#include <stdlib.h>
int main()
{int *p=malloc(sizeof(int));*p=10;free(p);*p=20;printf("%d\n",*p);return 0;
}//代碼運行結果:
段錯誤
代碼講解:free已經釋放內存了,此時p仍指向原地址,成為懸空指針,危險!訪問已釋放的內存
2.局部變量指針超出作用域
代碼示例:
#include <stdio.h>
int *num(void);//函數封裝:返回數據
int main()
{int *p=*num();printf("%d",*p);return 0;
}
int *num(void)//函數封裝:返回數據
{int num1=10;return &num1;
}//代碼運行結果:
段錯誤 (核心已轉儲)
代碼講解:函數num返回局部變量num1的地址,可是num1在函數結束后被銷毀釋放了。因此成為懸空指針,在主函數中*p是屬于未定義行為;
C語言懸空指針總結
懸空指針的本質:指針與內存生命周期不同步。
防范手段:
1.釋放內存后立即置空(p = NULL
)。
2.不返回局部變量的地址。
3.使用動態內存分配(malloc
)替代局部變量。
void類型的指針
void*
(無類型指針)是一種通用指針類型,可以指向任意數據類型(如?int
、float
、結構體等),但不能直接解引用(即不能通過?*
?訪問值),必須先轉換為具體類型的指針。
代碼示例:
#include <stdio.h>
int main()
{int num1=10;void *p=&num1;int *p1=(int*)p;printf("%d\n",*p1);return 0;
}//代碼運行結果:
10
代碼講解:先定義一個整型變量?num1
?并賦值為 10,再用?void* p
?存儲其地址(此時?p
?僅保存地址信息,不關聯具體類型),接著通過顯式類型轉換?(int*)p
?將?void*
?還原為?int*
?指針?p1
,最后通過?p1
?解引用并打印?num1
?的值(10),體現了?void*
?作為通用指針需轉換后才能操作數據的特性,同時強調了類型轉換必須與實際數據類型匹配,否則會導致未定義行為。
制作不易!喜歡的小伙伴給個小贊贊!喜歡我的小伙伴點個關注!有不懂的地方和需要的資源隨時問我喲!