文章目錄
- 一、內存和地址
- 1. 內存
- 2. 如何理解編址
- 二、指針變量和地址
- 2.1 取地址操作符(&)
- 2.2 指針變量和解引用操作符(*)
- 2.2.1 指針變量
- 2.2.2 如何拆解指針變量
- 2.2.3 解引用操作符
- 2.3 指針變量的大小
- 三、指針變量類型的意義
- 3.1 指針的解引用
- 3.2 指針 +- 整數
- 3.3 void*指針
- 四、const 修飾指針
- 4.1 const修飾變量
- 4.2 const修飾指針變量
一、內存和地址
1. 內存
內存是計算機中用于存儲數據和程序的硬件設備,也被稱為隨機存取存儲器(RAM)。內存具有較快的讀寫速度,用于臨時存儲當前正在執行的程序和數據。
在計算機系統中,內存扮演著至關重要的角色,它直接影響著計算機的性能和運行效率。以下是一些關于內存的基本信息:
- 作用:內存用于存儲當前正在運行的程序和數據,包括操作系統、應用程序和用戶數據等。當計算機啟動時,操作系統會將需要的程序和數據加載到內存中,CPU 可以直接從內存中讀取和寫入數據,而不需要像從硬盤那樣慢速地進行輸入和輸出操作。
- 易失性:內存是一種易失性存儲設備,這意味著當計算機斷電或重新啟動時,內存中的數據將丟失。因此,重要的數據通常需要保存在持久性存儲設備(如硬盤、固態硬盤)中。
- 訪問速度:內存的訪問速度通常比硬盤或固態硬盤快得多,這使得計算機能夠更快地讀取和寫入數據,從而提高系統的性能和響應速度。
- 單位:內存容量通常以字節為單位進行衡量,常見的單位有千兆字節(Gigabyte,GB)和千兆字節(Terabyte,TB)等。
為了更方便理解,我們舉一個生活中的小案例:
假設有?棟宿舍樓,把你放在樓里,樓上有100個房間,但是房間沒有編號,你的?個朋友來找你玩,
如果想找到你,就得挨個房子去找,這樣效率很低,但是我們如果根據樓層和樓層的房間的情況,給
每個房間編上號,如:
?樓:101,102,103...
?樓:201,202,203....
...
有了房間號,如果你的朋友得到房間號,就可以快速的找房間,找到你。
這里的房間號就相當于地址。
計算機上CPU( 中央處理器) 在處理數據的時候 ,需要的數據是在內存中讀取的 ,處理后的 數據也會放回內存中 ,那我們買電腦的時候 , 電腦上內存是8GB/16GB/32GB等 ,那這些內存空間如何 高效的管理呢?
其實也是把內存劃分為一個個的內存單元 ,每個內存單元的大小取1個字節。
補充:計算機中常見的單位
一個比特位可以存儲一個二進制位的0或1。
bit (比特位)
1 Byte (字節) = 8 bit
1 KB = 1024 Byte
1 MB = 1024 KB
1 GB = 1024 MB
1 TB = 1024 GB
1 PB = 1024 TB
...
其中,每個內存單元,相當于一個學生宿舍,一個字節空間里面能放8個比特位,就好比同學們住的八人間,每個人是一個比特位。
每個內存單元也都有一個編號(這個編號就相當于宿舍房間的門牌號), 有了這個內存單元的編號,CPU就可以快速找到一個內存空間。
生活中我們把門牌號也叫地址,在計算機中我們把內存單元的編號也稱為地址。C語言中給地址起了新的名字叫:指針。
也可以說:內存單元的編號 == 地址 == 指針
2. 如何理解編址
在計算機科學和計算機網絡中,“編址”通常指的是為了標識和定位計算機或網絡設備而給它們分配一個唯一的地址。這個地址可以用來在網絡中準確定位設備,以便進行數據傳輸、通信或者其他操作。
首先,必須理解,計算機內是有很多的硬件單元,而硬件單元是要互相協同工作的。所謂的協同,至少相互之間要能夠進行數據傳遞。
但是硬件與硬件之間是互相獨立的,那么如何通信呢?答案很簡單,用"線"連起來。而CPU和內存之間也是有大量的數據交互的,所以,兩者必須也用線連起來。
我們可以簡單理解,32位機器有32根地址總線,每根線只有兩態,表示0,1【電脈沖有無】,那么?根線,就能表示2種含義,2根線就能表示4種含義,依次類推。32根地址線,就能表示2^32種含義,每?種含義都代表?個地址。地址信息被下達給內存,在內存上,就可以找到該地址對應的數據,將數據在通過數據總線傳?CPU內寄存器。
二、指針變量和地址
2.1 取地址操作符(&)
在C語言中創建變量其實就是向內存申請空間,比如:
int main()
{int a = 10;return 0;
}
在上述代碼中,我們創建了一個整型變量a,向內存中申請了4個字節,用來存放整數10,其中每個字節都有一個地址,上面4個字節的地址分別是:
0x0000004B0D2FF984
0x0000004B0D2FF985
0x0000004B0D2FF986
0x0000004B0D2FF987
這里我們用&取地址操作符
int main()
{int a = 10;&a; // 取a的地址printf("%p\n", &a);return 0;
}
按我們所學的知識可以知道,打印的結果是:0x0000004B0D2FF984
雖然整型變量占4個字節,但我們只要知道了第一個字節的地址,就可以順藤摸瓜訪問第4個字節的數據。
2.2 指針變量和解引用操作符(*)
2.2.1 指針變量
我們通過取地址操作符(&)拿到的地址是一個字符,比如:0x0000004B0D2FF984,這個數值我們可以用指針變量把這個數值存儲在指針變量中。
int main()
{int a = 10;int* pa = &a; // 取出a的地址并存在指針變量pa中return 0;
}
指針變量也是一種變量,這種變量就是用來存放地址的,存放在指針變量中的值都會理解為地址。
2.2.2 如何拆解指針變量
我們看到pa的類型是int*,我們該如何理解指針的類型呢?
int a = 10;
int* pa = &a; // 取出a的地址并存在指針變量pa中
這里pa左邊寫的是int*, * 是在說明pa是指針變量,而前面的int是在說明pa指向的是整型(int)類型的對象。
2.2.3 解引用操作符
在C語言中我們只要拿到了地址(指針), 就可以通過地址找到地址指向的對象,這里必須學習一個操作符叫解引用操作符(*)。
#include <stdio.h>
int main()
{int a = 10;int* pa = &a; // 取出a的地址并存在指針變量pa中*pa = 0;printf("%d\n", a);return 0;
}
*pa 的意思就是通過pa中存放的地址,找到指向的空間, pa其實就是a變量了;所以pa = 0 ,這個操作符是把a改成了0。
2.3 指針變量的大小
前面的內容我們了解到,32位機器假設有32根地址總線,每根地址線出來的電信號轉換成數字信號后是1或者0 ,那我們把32根地址線產生的2進制序列當做一個地址,那么一個地址就是32個bit位,需要4個字節才能存儲。
如果指針變量是用來存放地址的 ,那么指針變的大小就得是4個字節的空間才可以。
同理64位機器,假設有64根地址線,一個地址就是64個二進制位組成的二進制序列,存儲起來就需要 8個字節的空間,指針變量的大小就是8個字節。
#include <stdio.h>
int main()
{// 指針變量的大小取決于地址的大小// 32位平臺下地址是32個bit位(即4個字節)// 64位平臺下地址是64個bit位(即8個字節)printf("%zd\n", sizeof(int*));printf("%zd\n", sizeof(char*));printf("%zd\n", sizeof(short*));printf("%zd\n", sizeof(double*));return 0;
}
x86環境輸出結果:
X64環境輸出結果:
結論:
- 32位平臺下地址是32個bit位,指針變量大小是4個字節
- 64位平臺下地址是64個bit位,指針變量大小是8個字節
- 注意指針變量的大小和類型是無關的,只要指針類型的變量,在相同的平臺下,大小都是相同的。
三、指針變量類型的意義
3.1 指針的解引用
調試我們可以看到,代碼1會將n的4個字節全部改為0,但是代碼2只是將n的第一個字節改為0。
結論:指針的類型決定了,對指針解引用的時候有多大的權限(一次能操作幾個字節)。
比如: char* 的指針解引用就只能訪問一個字節,而 int* 的指針的解引用就能訪問四個字節。
3.2 指針 ± 整數
通過下面的代碼觀察地址的變化
#include <stdio.h>
int main()
{int n = 10;char* pa= (char*)&n;int* pi = &n;printf("%p\n", &n);printf("%p\n", pa);printf("%p\n", pa + 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類型的指針不能直接進
行指針的±整數和解引用的運算。
【舉例】
int main()
{int a = 10;int* pa = &a;char* pc = &a;return 0;
}
在上面的代碼中,將一個int類型的變量的地址賦值給一個char類型的指針變量。編譯器給出了一個警告(如下圖), 是因為類型不兼容。而使用void類型就不會有這樣的問題。
使用void*類型的指針接收地址:
int main()
{int a = 10;void* pa = &a;void* pc = &a;*pa = 10;*pc = 0;return 0;
}
這里我們可以看到, void* 類型的指針可以接收不同類型的地址,但是無法直接進行指針運算。
一般 void* 類型的指針是使用在函數參數的部分,用來接收不同類型數據的地址,這樣的設計可以實現泛型編程的效果。使得一個函數來處理多種類型的數據。
四、const 修飾指針
4.1 const修飾變量
變量是可以修改的,如果把變量的地址交給一個指針變量,通過指針變量的也可以修改這個變量。
但是如果我們希望一個變量加上一些限制,不能被修改,怎么做呢?這就是const的作用。
int main()
{int n = 0;n = 20; //n可以被修改const int m = 10;m = 20; //m不可以被修改return 0;
}
上述代碼中m是不能被修改的,其實m本質還是變量,只不過被const修飾后,在語法上加了限制,只要我們在代碼中對m就行修改,就不符合語法規則,就報錯,致使沒法直接修改m。
但是如果我們繞過m,使用m的地址,去修改m就能做到了,雖然這樣做是在打破語法規則。
#include <stdio.h>
int main()
{const m = 0;int* p = &m;*p = 20;printf("%d\n", m);return 0;
}
我們可以看到這里一個確實修改了,但是我們還是要思考一下,為什么n要被const修飾呢?就是為了不能被修改,如果p拿到n的地址就能修改n,這樣就打破了const的限制,這是不合理的,所以應該讓p拿到n的地址也不能修改n,那接下來怎么做呢?
4.2 const修飾指針變量
一般來講const修飾指針變量 ,可以放在 * 的左邊,也可以放在 * 的右邊,意義是不一樣的。
int * p;//沒有const修飾?
int const * p;//const 放在*的左邊做修飾
int * const p;//const 放在*的右邊做修飾
測試const放在*的左邊情況
通過上面幾張圖可以看到,const在*號左邊和在右邊是有著不同作用的。
- const如果放在 * 的左邊,修飾的是指針指向的內容,保證指針指向的內容不能通過指針來改變。但是指針變量本身的內容可變。
- const如果放在*的右邊,修飾的是指針變量本身,保證了指針變量的內容不能修改,但是指針指向的內容,可以通過指針改變。
- 如果*號的左右兩邊都放const,既修飾的是指針指向的內容,保證指針指向的內容不能通過指針來改變。又修飾的是指針變量本身,保證了指針變量的內容不能修改。