引言:變量的內存地址:先前我們了解到,C程序中變量的值都是存儲在計算機內存中特定的存儲單元中。而內存中的每個單元都有唯一的地址。1.1理解指針是什么?🔻
1.指針是內存中一個最小的單元編號,也就是地址。
2.口語中的指針,指的是指針變量,是用來存放變量的地址。
我們可以進一步將指針理解為“內存”
創新互聯從2013年成立,先為道外等服務建站,道外等地企業,進行企業商務咨詢服務。為道外企業網站制作PC+手機+微官網三網同步一站式服務解決您的所有建站問題。圖片描述
1.2指針變量😁如何獲得變量的地址呢?通過&(取地址操作符)取出變量的其實地址,然后放在一個變量中,這個變量就是指針變量。 通過指針變量間接存取它指向的變量的訪問方式稱為間接尋址。
#includeint main()
{
int a = 5;//在內存中開辟一塊空間
int* pa = &a;//取出變量a的地址,存放到pa這個變量中。pa就是指針變量。
printf("%p\n",&a);//需要用到取地址操作符
printf("%p\n", pa);
? ? //打印地址格式用%p,表示輸出變量的地址值
? ? //地址值時用一個十六進制(以16為基數)的無符號整數表示。
return 0;
}
圖片描述
//如何定義兩個相同基類型的指針變量呢?
int*pa,*pb;
//注意不可以用int*pa,pb。這表示定義了可以指向整型數據的指針變量pa和整型變量pb。
總結:?
1. 指針變量--->用來存放地址的變量。(存放在指針中的值都被當成地址來處理)
2.那么最小的一個單元有多大呢?---- 一個字節
3.深刻剖析內存單元如何進行編址?
經過仔細的計算和權衡我們發現一個字節給一個對應的地址是比較合適的。
對于32位的機器,假設有32根地址線,那么假設每根地址線在尋址的時候產生高電平(高電壓)和低電
平(低電壓)就是(1或者0);
那么32根地址線產生的地址就會是:
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
...
11111111 11111111 11111111 11111111
這里就有2的32次方個地址。
每個地址標識一個字節,那我們就可以給 (2^32Byte == 2^32/1024KB ==
2^32/1024/1024MB==2^32/1024/1024/1024GB == 4GB) 4G的空閑進行編址。
同樣的方法,那64位機器,如果給64根地址線,那能編址多大空間,自己計算。
也就是說:
1.在32位的機器上,地址是32個0或者1組成二進制序列,那地址就得用4個字節的空間來存儲,所以
一個指針變量的大小就應該是4個字節。
2.那如果在64位機器上,如果有64個地址線,那一個指針變量的大小是8個字節,才能存放一個地
址
總結:?
1.指針是用來存放地址的,地址是唯一標識一塊地址空間的。
2.在32位平臺上,指針的大小占4個字節;在64位平臺上,指針的大小占8個字節。
接下來我們觀察一下指針變量在內存中的存儲情況
int main()
{
int num = 0;//地址編號為:0x00effcf0,存放的值為00000000
int* pa = #//地址編號為:0x00EFFCE4,存放的值為0x00effcf0
char* pc = (char*)#//地址編號為:0x00EFFCD8,存放的值為0x00effcf0
return 0;
}
注意:
本例第5行:指針變量只能指向同一基類型的變量,否則會引起warning。所以本例使用了強制類型轉換。
通過上述代碼和圖片解釋,我們發現:🌜2.指針和指針類型🌛
指向某變量的指針變量,雖然指針變量中存放的是變量的地址值,二者在數值上相等,但在概念上變量的指針并不等同于變量的地址。變量的地址是一個常量,不能對其進行賦值。而變量的指針是一個變量,其值是可以改變的。
我們都知道,變量有不同的類型,如整型、浮點型等等。那么指針也有對應的指針類型。
int num=10;
int* p=#//pa是一個指針變量,它指向一個整型變量
變量p是一個指針變量,用來存放num的地址。因為num的類型是整型,那么指針存放的數據類型是整型,對應p是一個整型指針,同時需要在int和p之間加一個*確保它是一個指針變量。2.1指針類型:
指針類型定義的方式:類型關鍵字+*+指針變量名
char* p=NULL;(存放char類型變量的地址)
short* p=NULL;(存放short類型變量的地址)
long* p=NULL;(存放long類型變量的地址)
int* p=NULL;(存放int類型變量的地址)
float* p=NULL;(存放float類型變量的地址)
double* p=NULL;(存放double類型變量的地址)
介紹一下 指針運算符, 也稱 間接尋址運算符 或 解引用運算符:(*)間接尋址運算符*用來訪問指針變量指向變量的值。 運算時,要求指針已經被正確初始化或者已經指向內存中某個確定的存儲單元,防止野指針的出現。2.2指針+-整數
int main()
{
int num = 10;
int* pn = #
char* pc = (char*)#
printf("&num=%p\n", &num);
printf("pn=%p\n", pn);
printf("pn+1=%p\n",pn+1);
printf("pc=%p\n",pc);
printf("pc+1=%p\n",pc+1);
return 0;
}
內存中的地址是由十六進制顯示的:pn是整型指針,pn+1與pn相差4個字節;而pc是字符型指針,pc+1與pc相差1個字節
總結:指針的類型決定了指針向前或者向后走一步是多大距離
2.3指針的解引用int main()
{
int num = 10;
int* pn = #
char* pc = (char*)#
printf("%d\n", *pn);
printf("%d\n", *pc);
return 0;
}
這樣看對指針的解引用不深刻,我們換一種方法:
int main()
{
int n = 0x11223344;
char* pc = (char*)&n;
int* pn = &n;
*pc = 0; //重點在調試的過程中觀察內存的變化。
*pn = 0; //重點在調試的過程中觀察內存的變化。
return 0;
}
//接下來我們通過調試觀察在內存中的變化
通過對pc進行解引用操作,按ctrl+F10進行逐語句調試過程,按F10到25行,再按一次調試25行代碼跳到26行,可以觀察25行程序運行的情況:觀察內存變化發現內存由0x11223344變成了0x11223300。*pc對pc進行解引用操作并賦值為0,因為pc是Char類型指針,訪問一個字節的內存空間,所以將內存地址的低位44改成了00。
當我們再按F10的時候,語句調試第26行,跳到27行,觀察第26行程序的運行:因為pn是整型指針,對pn解引用操作會對內存訪問四個字節的內存空間,將*pn賦值為0,也就是將0x11223344變成了0x00000000。
總結:💗
指針的類型決定了指針解引用操作對內存能訪問多大的權限(能操作幾個字節)
比如: char* 的指針解引用就只能訪問一個字節,而 int* 的指針的解引用就能訪問四個字節。
同樣,我們可以通過解引用修改變量的值,看下面程序運行結果: 📄
int main()
{
int num = 0;
int* pa = #//定義指針變量的同時對其初始化
*pa = 20;//修改指針變量pa所指向的變量的值
? ? //引用指針所指向的變量的值,也稱為指針的解引用。
printf("%d\n", num);
return 0;
}
那么:既然*pa和變量num的值一樣,我們原本可以很容易的將變量a的值打印出來,為什么還要舍近求遠地使用指針變量來得到a的值呢?通過”標題4展現指針的強大功能“😍
👺3.野指針👺概念:
?野指針就是指指向的內存是不可知的。(隨機的、不正確的、沒有明確限制的)
3.1野指針的成因😺 3.1.1指針的未初始化int main()
{
? ? int*p;//此時指針變量p指向的內存是不可知的
? ? *p=20;//對指針進行解引用修改值可能會對內存發生不可逆的影響
? ? return 0;
}
3.1.2指針的越界訪問int main()
{
int arr[10] = { 0 };
int* p=&arr;//數組的第一個元素的首地址
int i = 0;
for (i = 0; i< 11; i++)
{
*(p++) = i;//先對p賦值,后p++(詳看++的運算規則)
? ? ? ? //指針指向的范圍超出了數組的范圍,此時p就是野指針
}
for (i = 0; i< 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
3.1.3指針指向的空間釋放與動態內存相關內容,后續發布文章講解3.2如何規避野指針
1.指針初始化
2.防止指針越界的情況發生
3.指針指向空間釋放即使置NULL
4.避免返回局部變量的指針
5.指針使用之前檢查有效性
int main()
{
int* p = NULL;
int num = 10;
p = #
if (p != NULL)
{
*p = 10;
}
printf("%d\n", num);
? ? //10
}
恪守準則:🌜4.指針運算🌛 4.1指針+-整數😇
1.永遠清楚每個指針指向了哪里,指針必須只想一塊有意義的內存;
2.永遠清楚每個指針指向的對象的內容是什么;
3.永遠不要使用未初始化的指針變量。
#define N_VALUES 5
float values[N_VALUES];//浮點型數組
float* vp;//定義一個浮點型指針
//指針+-整數;指針的關系運算
int main()
{
for (vp = &values[0]; vp< &values[N_VALUES];)
{
*vp++ = 0;//數組從下標為0的元素開始賦值為0;
}
return 0;
}
4.2指針-指針😇//函數功能:模擬實現strlen
//函數形參:字符型指針
//函數返回值:整型
int my_strlen(char* ch)
{
char* p = ch;
//數組名是數組首地址
while (*p!='\0')
{
p++;
}
return p - ch;
//指針-指針:實現指針之間的距離(相差的字節數)
}
int main()
{
char ch[20] = { 0 };
fgets(ch, sizeof(ch), stdin);
printf("%d\n", my_strlen(ch));
return 0;
}
4.3指針的關系運算😇#define N_VALUES 5
float values[N_VALUES];//浮點型數組
float* vp;//定義一個浮點型指針
//指針+-整數;指針的關系運算
int main()
{
for(vp = &values[N_VALUES]; vp >&values[0];)//vp指向下標為5的內存空間
? ? {
? ? ? ? *--vp = 0;//從下標為4的位置開始
? ? }
return 0;
}
當我們對代碼進行簡化時:
#define N_VALUES 5
float values[N_VALUES];//浮點型數組
float* vp;//定義一個浮點型指針
//指針+-整數;指針的關系運算
int main()
{? ?
? ? for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
? ? {
? ? ? ? *vp = 0;
? ? ? ? //當vp=&values[0]時,*vp=0,此時會進行vp--,導致指針指向第一個元素之前的內存空間,應避免這樣? ? ? ? ? //的問題
? ? }
return 0;
}
實際在絕大部分的編譯器上是可以順利完成任務的,然而我們還是應該避免這樣寫,因為標準并不保證
它可行
?標準規定?
允許指向數組元素的指針與 指向數組最后一個元素后面的那個內存位置的指針比較,但是 不允許與4.4按值調用和模擬按引用調用 ?引例1?修改實參的值
指向第一個元素之前的那個內存位置的指針進行比較。
void Change(int num)
{
num = 20;
}
int main()
{
int num = 10;
printf("%d\n", num);
Change(num);
printf("%d\n", num);
return 0;
}
程序在函數Change中改變了num的值,但是再次輸出實參的值卻發生沒有任何變化,說明函數的形參值的改變并未影響到實參值的改變。這是因為形參是實參的一份臨時拷貝,通過按值調用不能在被調函數中改變其調用語句中的實參值。這時我們就要用到指針這個秘密武器了。指針變量的一個重要作用就是用作函數參數,由于傳給被調函數的這個值不是變量的值,而是變量的地址,通過向被調函數傳遞變量的地址可以在被調函數中改變主調函數中變量的值,模擬C++中的按引用調用。
void Change(int* num)
{
*num = 20;
}
int main()
{
int num = 10;
printf("%d\n", num);
Change(&num);
printf("%d\n", num);
return 0;
}
實際上是通過實參的地址對實參進行修改
?引例2?實現兩個整數的交換void Swap(int*a,int*b)//形參是兩個整型指針
{
int tmp = 0;//交換兩個數的值
tmp = *a;
*a = *b;
*b = tmp;
}
int main()
{
int a = 10;
int b = 20;
Swap(&a, &b);//傳址
printf("%d\n%d\n", a, b);
return 0;
}
如果修改為Swap(a,b),void Swap(int a,int b)結果是什么呢?原理和引例1相同,我就不過多解釋啦😁
🌜5.指針和數組🌛先看一個例子:
int main()
{
int arr[10] = { 0 };
printf("%p\n", arr);
printf("%p\n", &arr[0]);
return 0;
}
結論:數組名表示的是數組首元素的地址
//也就是說我們可以這樣寫:
int arr[10]={0};
int*pa=&arr;//pa存放的是數組首元素的地址
例如:
int main()
{
? ? int arr[] = {1,2,3,4,5,6,7,8,9,0};
? ? int *p = arr; //指針存放數組首元素的地址
? ? int sz = sizeof(arr)/sizeof(arr[0]);
? ? for(i=0; ip+%d = %p\n", i, &arr[i], i, p+i);
? ? }
? ? return 0;
}
進一步可以表示為以下這種方式:
int main()
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
int* p = arr; //指針存放數組首元素的地址
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i< sz; i++)
{
printf("%d ", *(p + i));
}
return 0;
}
🌜6.二級指針🌛指針變量也是變量,那么指針變量的地址存放在哪里呢?-----二級指針
int main()
{
int a = 10;
int* pa = &a;
printf("對pa進行解引用=%d\n", *pa);
printf("變量a的地址=%p\n", &a);
printf("變量a的地址存儲在pa中=%p\n", pa);
int** ppa = &pa;
printf("指針變量pa的地址=%p\n", &pa);
printf("對ppa解引用得到pa的值=%p\n", *ppa);
printf("對ppa解引用得到pa再解引用得到a=%d\n", **ppa);
printf("變量pa的地址存儲在ppa中=%p\n",ppa);
**ppa = 30;
//**ppa相當于*pa,*pa相當于a
printf("a=%d\n", a);
return 0;
}
程序運行結果如上
🌜7.指針數組🌛指針數組是指針還是數組呢?答案是數組,是存放指針變量的數組。
我們知道存在整型數組、浮點型數組、字符數組,那指針數組是什么樣子的呢?
int* arr[5]={0};
如圖片所示
簡單介紹即可,進階版跟后續筆記😭
你是否還在尋找穩定的海外服務器提供商?創新互聯www.cdcxhl.cn海外機房具備T級流量清洗系統配攻擊溯源,準確流量調度確保服務器高可用性,企業級服務器適合批量采購,新人活動首月15元起,快前往官網查看詳情吧
當前文章:修仙之指針初階-創新互聯
網頁URL:http://vcdvsql.cn/article28/cdghcp.html
成都網站建設公司_創新互聯,為您提供網站策劃、手機網站建設、網站收錄、網站建設、網站內鏈、外貿建站
聲明:本網站發布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創新互聯