大家好啊,我是小象?(?òωó?)?
我的博客:Xiao Xiangζ?????
很高興見到大家,希望能夠和大家一起交流學習,共同進步。
這一節我們來學習指針的相關知識,學習內存和地址,指針變量和地址,包括取地址操作符,指針變量和解引用操作符,指針變量類型的意義,指針變量的大小,指針的解引用,指針±整數,void指針
文章目錄
- 一、內存和地址
- 1.1 內存
- 1.2 如何理解編址
- 二、指針變量和地址
- 2.1 取地址操作符(&)
- 2.2 指針變量和解引用操作符(*)
- 2.2.1 指針變量
- 2.2.2 如何拆解指針類型
- 2.2.3 解引用操作符
- 2.3 指針變量的大小
- 三、指針變量類型的意義
- 3.1 指針的解引用
- 3.2 指針+-整數
- 3.3 void指針
- 四、結尾
一、內存和地址
1.1 內存
在 C 語言中,內存是程序運行的基礎,用于存儲程序中的數據和代碼。
我們來舉一個生活中的例子:假設有?棟宿舍樓,把你放在樓里,樓上有100個房間,但是房間沒有編號,你的一個朋友來找你玩,如果想找到你,就得挨個房子去找,這樣效率很低,但是我們如果根據樓層和樓層的房間的情況,給每個房間編上號,如:
?樓:101,102,103...
?樓:201,202,203...
...
有了房間號,如果你的朋友得到房間號,就可以快速的找房間,找到你。
同理,我們在買電腦的時候也會發現電腦的內存有8GB、16GB、32GB等,那這些內存空間如何高效的管理呢?其實一樣也是把內存劃分為一個個的內存單元,每個內存單元的大小取1個字節。
PS:計算機中的常見單位:一個比特位可以存儲一個2進制的位1或者0
bit - ?特位
Byte - 字節
KB
MB
GB
TB
PB
1Byte = 8bit
1KB = 1024Byte
1MB = 1024KB
1GB = 1024MB
1TB = 1024GB
1PB = 1024TB
其中,每個內存單元,相當于一個學生宿舍,一個字節空間里面能放8個比特位,就好比同學們住的八人間,每個人是一個比特位。每個內存單元也都有一個編號(這個編號就相當于宿舍房間的門牌號),有了這個內存單元的編號,CPU就可以快速找到一個內存空間。生活中我們把門牌號也叫地址,在計算機中我們
把內存單元的編號也稱為地址。C語言中給地址起了新的名字叫做:指針。
所以我們可以理解為:
內存單元的編號=地址=指針
1.2 如何理解編址
編址是計算機系統中一個關鍵的概念,它涉及為計算機中的各種存儲單元或設備分配唯一標識符,以便能夠對它們進行準確的訪問和管理。
CPU訪問內存中的某個字節空間,必須知道這個字節空間在內存的什么位置,?因為內存中字節很多,所以需要給內存進而編址(就如同宿舍很多,需要給宿舍編號一樣)。計算機中的編址,并不是把每個字節的地址記錄下來,而是通過硬件設計完成的。鋼琴、吉他上面沒有寫上“剁、來、咪、發、唆、拉、西”這樣的信息,但演奏者照樣能夠準確找到每一個琴弦的每?個位置,這是為何?因為制造商已經在樂器硬件層面上設計好了,并且所有的演奏者都知道。本質是一種約定出來的共識!
首先,必須理解,計算機內是有很多的硬件單元,而硬件單元是要互相協工作的。所謂的協同,至少相互之間要能夠進行數據傳遞。但是硬件與硬件之間是互相獨立的,那么如何通信呢?答案很簡單,用"線"連起來。而CPU和內存之間也是有大量的數據交互的,所以,兩者必須也用線連起來。不過,我們今天關心一組線,叫做地址總線。硬件編址也是如此我們可以簡單理解,32位機器有32根地址總線,每根線只有兩態,表示0,1【電脈沖有無】,那么一根線,就能表示2種含義,2根線就能表示4種含義,依次類推。32根地址線,就能表示2^32種含義,每?種含義都代表?個地址。地址信息被下達給內存,在內存上,就可以找到該地址對應的數據,將數據在通過數據總線傳入CPU內寄存器。
舉個例子,如果我們想要訪問某個地址,首先控制總線會發出指令,如何根據地址總線找到內存中對應的位置,接著在通過數據總線傳遞回來。
二、指針變量和地址
2.1 取地址操作符(&)
在 C 語言中,取地址操作符是“&”。它用于獲取一個變量的內存地址。例如,如果有一個變量 int a; ,那么 &a 就表示變量 a 的地址。
PS:變量創建的本質其實是:在內存中申請空間,向內存申請4個字節的空間,存放10
比如,上述的代碼就是創建了整型變量a,內存中申請4個字節,用于存放整數10,其中每個字節都有地址,上圖中4個字節的地址分別是:
0x004FF9D4
0x004FF9D5
0x004FF9D6
0x004FF9D7
那我們如何能得到a的地址呢?
這里就得學習一個操作符(&)——取地址操作符
#include<stdio.h>
int main ()
{int a = 10;printf("%p\n", &a);return 0;
}
注意:&a取出的是a所占4個字節中地址較小的字節的地址。
實際上,地址也是二進制的,只是為了在vs容易表示寫成了16進制
雖然整型變量占用4個字節,我們只要知道了第一個字節地址,順藤摸瓜訪問到4個字節的數據也是可行的。
2.2 指針變量和解引用操作符(*)
2.2.1 指針變量
在C語言中,指針變量是一種特殊的變量,它存儲的是內存地址,而不是普通的數據值。
我們通過取地址操作符(&)拿到的地址是一個數值,比如:0x004FF9D4,這個數值有時候也是需要存儲起來,方便后期再使用的,那我們把這樣的地址值存放在哪里呢?答案是:指針變量中。
#include <stdio.h>
int main()
{int a = 10;int* pa = &a; //取出a的地址并存儲到指針變量pa中return 0
}
指針變量也是一種變量,這種變量就是用來存放地址的,存放在指針變量中的值都會理解為地址。
2.2.2 如何拆解指針類型
我們看到pa的類型是 int * ,我們該如何理解指針的類型呢?
int a = 10;
int * pa = &a;
這里pa左邊寫的是 int* , * 是在說明pa是指針變量,而前面的 int 是在說明pa指向的是整型(int)類型的對象。
那如果有一個char類型的變量ch,ch的地址,要放在什么類型的指針變量中呢?
很顯然,自然是放在char類型的指針變量中。
2.2.3 解引用操作符
在現實生活中,我們使用地址要找到一個房間,在房間里可以拿去或者存放物品。
C語言中其實也是?樣的,我們只要拿到了地址(指針),就可以通過地址(指針)找到地址(指針)
指向的對象,這里必須學習一個操作符叫解引用操作符(*)。
解引用操作符用于訪問指針所指向的內存位置的值。也就是說,當你有一個指針變量,它存儲了某個變量的內存地址,通過解引用操作符,你可以獲取或修改該內存地址中存儲的值。
舉個例子:
#include<stdio.h>
int main()
{int a = 100;int* pa = &a;*pa = 0;return 0;
}
第五行中定義了一個指向整型的指針變量 pa。int* 表示指針的類型,即該指針指向的是一個整型變量。& 是取地址運算符,&a 表示獲取變量 a 的內存地址。因此,這行代碼將變量 a 的地址賦值給指針 pa,使得 pa 指向了變量 a。
而第六行就有了解引用操作符的使用,*pa 表示訪問 pa 所指向的變量,也就是變量 a。這行代碼將 0 賦值給 *pa,實際上就是將 0 賦值給變量 a,從而修改了變量 a 的值。
有同學肯定在想,這里如果目的就是把a改成0的話,寫成 a = 0;不就完了,為啥非要使用指針呢?
其實這里是把a的修改交給了pa來操作,這樣對a的修改,就多了一種的途徑,寫代碼就會更加靈活,
后期慢慢就能理解了。
2.3 指針變量的大小
前面的內容我們了解到,32位機器假設有32根地址總線,每根地址線出來的電信號轉換成數字信號后
是1或者0,那我們把32根地址線產生的2進制序列當做一個地址,那么一個地址就是32個bit位,需要4
個字節才能存儲。
如果指針變量是用來存放地址的,那么指針變的大小就得是4個字節的空間才可以。
同理64位機器,假設有64根地址線,一個地址就是64個二進制位組成的二進制序列,存儲起來就需要
8個字節的空間,指針變量的大小就是8個字節。
#include <stdio.h>
//指針變量的大小取決于地址的大小
//32位平臺下地址是32個bit位(即4個字節)
//64位平臺下地址是64個bit位(即8個字節)
int main()
{printf("%zd\n", sizeof(char*));printf("%zd\n", sizeof(short*));printf("%zd\n", sizeof(int*));printf("%zd\n", sizeof(double*));return 0;
}
結論:
? 32位平臺下地址是32個bit位,指針變量大小是4個字節
? 64位平臺下地址是64個bit位,指針變量大小是8個字節
? 注意指針變量的大小和類型是無關的,只要指針類型的變量,在相同的平臺下,大小都是相同的
三、指針變量類型的意義
指針變量的大小和類型無關,只要是指針變量,在同?個平臺下,大小都是?樣的,為什么還要有各
種各樣的指針類型呢?
其實指針類型是有特殊意義的,我們接下來繼續學習。
3.1 指針的解引用
C 語言指針解引用是通過指針訪問其指向內存地址中存儲值的操作,使用星號 * 作為解引用操作符。它主要用于訪問和修改數據,以及對動態分配的內存進行讀寫。 代碼應用場景包括基本指針解引用、指針與數組結合的解引用和多級指針的解引用。使用時要注意進行空指針檢查,避免對 NULL 指針解引用導致程序崩潰,同時防止使用未正確初始化或指向已釋放內存的野指針,以免引發未定義行為。
對比下面兩段代碼:
//代碼1
#include <stdio.h>
int main()
{int n = 0x11223344;int* pi = &n;*pi = 0;return 0;
}
//代碼2
#include <stdio.h>
int main()
{int n = 0x11223344;char* pc = (char*)&n;*pc = 0;return 0;
}
調試我們可以看到,代碼1會將n的4個字節全部改為0,但是代碼2只是將n的第一個字節改為0。
結論:指針的類型決定了,對指針解引?的時候有多大的權限(一次能操作幾個字節)。
比如: char* 的指針解引用就只能訪問一個字節,而int* 的指針的解引用就能訪問四個字節。
3.2 指針±整數
先看一段代碼,調試觀察地址的變化。
#include <stdio.h>
int main()
{int n = 10;char* pc = (char*)&n;int* pi = &n;printf("%p\n", &n);printf("%p\n", pc);printf("%p\n", pc + 1);printf("%p\n", pi);printf("%p\n", pi + 1);return 0;
}
我們可以看出, char* 類型的指針變量+1跳過1個字節, int* 類型的指針變量+1跳過了4個字節。
這就是指針變量的類型差異帶來的變化。指針+1,其實跳過1個指針指向的元素。指針可以+1,那也可
以-1。
結論:指針的類型決定了指針向前或者向后走一步有多大(距離)。
3.3 void指針
void 指針也被稱為通用指針,其類型聲明為 void *。它可以指向任意類型的數據,也就是說,void 指針可以存儲任何類型變量的地址,但它本身并不明確所指向數據的具體類型。
這種類型的指針可以用來接受任意類型地址。但是也有局限性, void*類型的指針不能直接進行指針的±整數和解引用的運算。
舉個例子:
#include<stdio.h>int main(){int a = 10;int* pa = &a;char* pc = &a;return 0;}
在上面的代碼中,將一個int類型的變量的地址賦值給一個char類型的指針變量。編譯器給出了一個警
告(如下圖),是因為類型不兼容。而使用void類型就不會有這樣的問題。
使用void*類型的指針接收地址:
#include <stdio.h>
int main()
{int a = 10;void* pa = &a;void* pc = &a;*pa = 10;*pc = 0;return 0;
}
這里我們可以看到, void* 類型的指針可以接收不同類型的地址,但是無法直接進行指針運算。
那么 void* 類型的指針到底有什么用呢?
一般 void * 類型的指針是使用在函數參數的部分,用來接收不同類型數據的地址,這樣的設計可以實現泛型編程的效果,后面我們會繼續講解viod指針。
四、結尾
這一課的內容就到這里了,下節課繼續學習操作符的其他一些知識
如果內容有什么問題的話歡迎指正,有什么問題也可以問我!