【C語言基礎】:深入理解指針(一)

文章目錄

    • 一、內存和地址
      • 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個房間,但是房間沒有編號,你的?個朋友來找你玩,
如果想找到你,就得挨個房子去找,這樣效率很低,但是我們如果根據樓層和樓層的房間的情況,給
每個房間編上號,如:

?樓:101102103...
?樓:201202203....
...

有了房間號,如果你的朋友得到房間號,就可以快速的找房間,找到你。
這里的房間號就相當于地址

計算機上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,既修飾的是指針指向的內容,保證指針指向的內容不能通過指針來改變。又修飾的是指針變量本身,保證了指針變量的內容不能修改。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/710687.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/710687.shtml
英文地址,請注明出處:http://en.pswp.cn/news/710687.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

HCIA-HarmonyOS設備開發認證V2.0-習題

目錄 習題一習題二&#xff08;待續...&#xff09;堅持就有收獲 習題一 # HarmonyOS簡介 1. 以下哪幾項屬于OpenHarmony的技術特性&#xff1f;&#xff08;&#xff09;A. 統一OS&#xff0c;彈性部署B. 一次開發&#xff0c;多端部署C. 硬件互助&#xff0c;資源共享2. Ope…

從零開始的Java知識(下)

從零開始的Java知識 雙列數據集合&#xff08;Day1&#xff09;Map 雙列數據集合&#xff08;Day1&#xff09; Map 注意點&#xff1a; Map一次加入一個key-value一個key對應一個valuekey與key之間是不重復的key-value被稱為鍵值對&#xff0c;鍵值對對象或者是entry對象 …

離散數學

(理解大于識記, 這么多公式我是記不住) 命題邏輯 P P P Q Q Q P \neg P P 否定/非 P ∧ Q P \wedge Q P∧Q 合取/與 P ∨ Q P \vee Q P∨Q 析取/或 P → Q P \to Q P→Q 蘊含 P ? Q P \leftrightarrow Q P?Q 等價0010011011011010001001101111 P → Q P\to Q P→Q 的自然語…

openssl 加密文件(支持大文件,對稱、非對稱)

一、非對稱加密&#xff08;小文件&#xff09; 生成 2048 位密鑰 openssl genrsa -out rsa2048.key 2048從 rsa2048.key 密鑰文件中提取出公鑰 pub2048.key openssl rsa -in rsa2048.key -pubout -out pub2048.key使用 pub2048.key 公鑰加密一個文件 (data.zip 為原始文件&…

C# WPF編程-創建項目

1.創建新項目 選擇“WPF應用程序”》“下一步” 2. 設置項目 設置項目名稱&#xff0c;保存位置等參數>下一步 3.選擇框架 4.項目創建成功 5.運行項目

兩張二值化圖像融合

python實現&#xff0c;已知兩張二值化圖像&#xff0c;對比兩張圖&#xff0c;將像素點一致的坐標保留原來顏色&#xff0c;不一致的坐標像素值變為128 讀取原圖 import cv2 import matplotlib.pyplot as plt import numpy as npcup_file_pathname"/home/wzc/zlt_self/…

vue-顯示數據

? v-text和v-html專門用來展示數據, 其作用和插值表達式類似。v-text和v-html可以避免插值閃爍問題. ? 當網速比較慢時, 使用{{}}來展示數據, 有可能會產生插值閃爍問題。 ? 插值閃爍: 在數據未加載完成時&#xff0c;頁面會顯示出原始的{{}}, 過一會才會展示正常數據.語法…

Opencv實戰(5)平滑處理與常見函數

平滑處理 Opencv實戰&#xff1a; Opencv(1)讀取與圖像操作 Opencv(2)繪圖與圖像操作 Opencv(3)詳解霍夫變換 Opencv(4)詳解輪廓 文章目錄 平滑處理1.均值濾波2.方框濾波3.高斯濾波4.中值濾波5.雙邊濾波 常見函數(1).createTrackbar()(2).SetMouseCallback() 圖像的平滑處理是…

細數Android開發者的艱辛歷程,android零基礎

首先我們來看一下組件化項目和傳統項目的區別: 在傳統的項目里 我們通常情況下會有一個commonLib的Libary模塊和一個app的application模塊&#xff0c;業務中的邏輯都寫在app中各個功能模塊放到不同的包下。這樣做有以下幾個主要的缺點&#xff1a; 1.無論分包做的再好&…

【Linux】USB Functionfs編程:libusb接口詳解

1、USB host_app代碼注釋 #include <libusb.h> libusb頭文件,編譯時鏈接庫:libusb-1.0#define VENDOR 0x1d6b 廠商:Linux Foundation #define PRODUCT 0x0105 設備:FunctionFS Gadge

Zynq—AD9238數據采集DDR3緩存千兆以太網發送實驗(一)

ACM9238 高速雙通道ADC模塊自助服務手冊AD9238 一、實驗目的 本次實驗通過電腦上的網絡調試助手&#xff0c;將命令幀進行發送&#xff0c;然后通過ACZ7015開發板上的以太網芯片接收&#xff0c;隨后將接收到的數據轉換成命令&#xff0c;從而實現對ACM9238模塊采樣頻率、數據…

JavaScript中的new Proxy()和Object.defineProperty使用詳細,Vue2和vue3中雙向數據綁定的原理

簡介&#xff1a; Object.defineProperty() 是 JavaScript 中一個強大且常用的方法&#xff0c;用于定義對象屬性&#xff0c;允許我們精確地控制屬性的行為&#xff0c;包括讀取、寫入和刪除等操作&#xff0c;是vue2中雙向數據綁定的原理&#xff1b; new Proxy() 是ES6中一…

Tomcat布署及優化

1.Tomcat簡介 Tomcat 是 Java 語言開發的&#xff0c;Tomcat 服務器是一個免費的開放源代碼的 Web 應用服務器&#xff0c;Tomcat 屬于輕量級應用服務器&#xff0c;在中小型系統和并發訪問用戶不是很多的場合下被普遍使用&#xff0c;是開發和調試 JSP 程序的首選。一般來說&…

在實訓云平臺上配置云主機

文章目錄 零、學習目標一、實訓云升級二、實訓云登錄&#xff08;一&#xff09;登錄實訓云&#xff08;二&#xff09;切換界面語言&#xff08;三&#xff09;規劃云主機實例 三、創建網絡三、創建路由器2024-2-29更新到此四、添加接口五、創建端口六、添加安全組規則七、創建…

反相輸入放大器與生俱來的坑

我們都知道反相放大器能將輸入的信號反相放大&#xff0c;這是很基本的知識&#xff0c;學過電路的一般都知道。反相放大器的公式為Vout -Vin*Rf/Rin&#xff08;運算放大器應用匯總&#xff09;。根據已知的公式&#xff0c;能很輕松的完成設計&#xff0c;但反相放大器與生俱…

每日一類:QString類深入講解

QString類是Qt框架中的一個核心組件&#xff0c;設計用于方便、高效地處理Unicode字符串。與標準C中的字符串處理方式相比&#xff0c;QString提供了更為豐富的API&#xff0c;支持國際化&#xff0c;并且內部使用UTF-16編碼&#xff0c;能夠處理世界上幾乎所有的語言文字。 設…

【PHP進階】Rabbitmq的實際使用

RabbitMQ是一個流行的消息隊列中間件&#xff0c;它提供了可靠的消息傳遞機制。在使用RabbitMQ時&#xff0c;有幾個重要的概念需要了解&#xff1a; 消息隊列&#xff08;Message Queue&#xff09;&#xff1a;RabbitMQ中的核心概念之一。它是消息的緩沖區&#xff0c;用于存…

容器安全工具使用指南:保障容器環境安全的利器

隨著容器技術的廣泛應用,容器安全成為關注的焦點。本文將深入介紹幾個流行的容器安全工具,我們將深入了解容器安全領域的Top 10工具,包括Trivy、veinmind-tools、Clair、Docker Bench for Security、Sysdig Falco、neuVector等,詳細講解它們的功能、原理、安裝和使用方法,…

【精簡版】Ubuntu/Linux Anaconda 命令行終端安裝

網上重復內容很多&#xff0c;大都啰里啰嗦&#xff0c;特作此筆記。 【精簡版】Ubuntu/Linux Anaconda 命令行安裝 1 下載安裝包1.1 尋找適配版本安裝包1.2 下載 2 運行安裝程序3 設置安裝路徑4 添加環境變量并運行4.1 環境變量4.2 運行 5 驗證安裝成功感謝及參考博文 1 下載…

js ES6判斷字符串是否以某個字符串開頭或者結尾startsWith、endsWith

1.前言 startsWith&#xff1a;startsWith方法用于檢查字符串是否以指定的字符串開頭。 endsWith&#xff1a;endsWith方法用于檢查字符串是否以指定的字符串結尾。 2.用法示例 const str Hello, world!;console.log(str.startsWith(Hello)); // true console.log(str.starts…