C語言超詳細指針知識(一)

通過前面一段時間C語言的學習,我們了解了數組,函數,操作符等的相關知識,今天我們將要開始進行指針的學習,這是C語言中較難掌握的一個部分,一定要認真學習!!!


1.內存與地址

在正式學習指針前,我們首先要了解兩個概念,內存與地址。該如何理解它們呢?

舉個例子,一棟宿舍樓有不同房間,每間房門前都有屬于他自己的門牌號,讓我們可以正確的尋找到對應的宿舍。一棟宿舍樓對應一塊內存,每間宿舍對應一小塊內存,地址就是門牌號,方便我們快速找到。相對應的我們可以把內存劃分為一個個的內存單元,每個內存單元的大小是一個字節。字節是是計算機中最基本的存儲單位。以下是一些常見的計算機內存單位:

bit? ? --比特位

Byte? --字節? ? ? ? ? ? 1Byte = 8bit

KB? ? ? ? ? ? ? ? ? ? ? ? ? 1KB = 1024 Byte

MB? ? ? ? ? ? ? ? ? ? ? ? ??1MB = 1024 KB

GB? ? ? ? ? ? ? ? ? ? ? ? ? ?1GB = 1024 MB

TB? ? ? ? ? ? ? ? ? ? ? ? ? ?1TB = 1024 GB

其中,每個內存單元,相當于一個學生宿舍,一個字節空間能放8個比特位,就好比一個八人寢。每個內存單元也都有一個編號(相當于門牌號),有了這個編號(就是地址),CPU就能快速找到一個內存空間。C語言中給地址起了新的名字:指針。所以:

內存單元的編號==地址==指針


2.指針變量和地址

2.1取地址操作符(&)

理解了內存和地址的關系,我們再回到C語言,在C語言中創建變量其實就是向內存申請空間,比如:

上述代碼創建了整型變量a,向內存申請了四個字節的空間,用于存放整數10,每個字節都有自己的地址,整數10會優先存放在四個字節中地址較小的字節。

那么我們要如何得到a的地址呢?這就需要用到一個取地址操作符&,還是上面這個代碼,我們用&a來得到了a的地址,存放到p中,打印出來:

?雖然整型變量占?4個字節,我們只要知道了第?個字節地址,順藤摸瓜訪問到4個字節的數據也是可行的。


2.2指針變量和解引用操作符

上面代碼我們打印了&a的地址004FF818,這是一個十六進制的數值,這個數值是可以存儲起來的,如整數存放在整型變量中,指針當然也要存放在指針變量中,為了存放&a的地址我們用了p來充當指針變量,它的類型是int*,int*中的*是在說明p是指針變量,與整形變量做區分,而為什么是int呢?因為p地址對應的內存空間存放的是整型變量。

那如果是char類型的變量,要存放它的地址,指針變量該是什么呢?當然是char*,*做區分,char表明存放的是字符類型,如:

char ch = 'a';
char* pc = &ch;

我們知道變量利用取地址操作符得到了地址,那么知道地址有沒有對應的操作符得到變量呢?答案是肯定的,這時候就要拿出解引用操作符*了,沒錯,他跟上文的*一模一樣,但意義截然不同,我們看代碼:

?我們先創建了一個整型變量num,然后把該變量地址命名為pz,將pz解引用,賦值32,num里的值也產生了相應變化,說明我們通過*pz找到了對應23的內存空間,改變了它。

有同學肯定在想,這?如果目的就是把num改成32的話,寫成 num ?= 32; 不就完了,為啥?要使?指針呢?其實這?是把num的修改交給了pz來操作,這樣對num的修改,就多了?種的途徑,寫代碼就會更加靈活,后期慢慢就能理解了。

2.3指針變量的大小

指針變量的大小取決于所處環境(x86環境和x64環境)

int main()
{//在x86環境下,指針變量的大小是四個字節printf("%zd\n", sizeof(char*));//4printf("%zd\n", sizeof(short*));//4printf("%zd\n", sizeof(int*));//4printf("%zd\n", sizeof(long*));//4//在x64環境下,指針變量的大小是四個字節printf("%zd\n", sizeof(char*));//8printf("%zd\n", sizeof(short*));//8printf("%zd\n", sizeof(int*));//8printf("%zd\n", sizeof(long*));//8return 0;
}
? 32位平臺下地址是32個bit位,指針變量大小是4個字節
? 64位平臺下地址是64個bit位,指針變量大小是8個字節
?從代碼里,我們可以明白指針變量的大小和類型是無關的,只要在相同的環境下,大小都是相同的。

指針變量的大小既然和類型無關,為什么會有各種各樣的指針類型呢?我們繼續接下來的學習。


我們來看一個代碼:

void test1()
{int n = 0x11223344;int* pi = &n;*pi = 0;printf("%0x\n", n);//0
}void test2()
{int n = 0x11223344;char* pc = (char*)&n;*pc = 0;printf("%0x\n", n);//11223300
}int main()
{test1();test2();return 0;
}

?觀察結果,我們會發現test1會將n的四個字節對應的內存空間全部改為0,但是test2只將n的第一個字節對應的內存空間改為0。

指針的類型決定了,對指針解引用的時候有多大的權限(?次能操作幾個字節)。
比如: char* 的指針解引用就只能訪問?個字節,而?int* 的指針的解引用就能訪問四個字節。

接下來我們再來看一個代碼:

我們可以看出, char* 類型的指針變量+1跳過1個字節, int* 類型的指針變量+1跳過了4個字節。
這就是指針變量的類型差異帶來的變化。
指針的類型決定了指針向前或者向后走一步有多?(距離)。

?2.4 void*指針

在指針類型中有?種特殊的類型是 void* 類型的,可以理解為無具體類型的指針(或者叫泛型指
針),這種類型的指針可以用來接受任意類型地址。但是也有局限性, void* 類型的指針不能直接進?指針的+-整數和解引用的運算。?
看如下代碼:

?我們看到,void*類型的指針并不能進行相應的計算,那么void*指針到底有什么用呢?它一般使用在函數參數的部分,用來接收不同類型數據的地址,可以達到范式編程的效果。我們在后面的學習中會慢慢接觸到。


3.const修飾

變量是可以修改的,如果把變量的地址交給?個指針變量,通過指針變量的也可以修改這個變量。
但是如果我們希望?個變量加上?些限制,不能被修改,怎么做呢?這就是const的作用。看如下代碼:
上述代碼中n是不能被修改的,其實n本質是變量,只不過被const修飾后,在語法上加了限制,只要我們在代碼中對n進行修改,就不符合語法規則,就報錯。
但是不要忘記我們是學習過指針知識,可以通過地址解引用來改變數值,這種方法可不可以繞過const修飾呢?看下面結果:

可以看到,錯誤變成了警告,代碼得以正常運行 ,打破了const的限制。但我們的目的是讓變量無法被修改,有沒有其他的限制辦法呢?當然有,我們讓const修飾指針就行了,看下面代碼:

代碼馬上又報錯了,指針指向的內存空間將無法被修改,但是我們馬上又有了另一個發現?:

?將const放置在指針變量類型之后,代碼又可以正常運行了,這是為什么呢?

int main()
{int m = 10;m = 12;const n = 12;const int* p1 = &n;*p1 = 10;//errorp1 = &m;//rightreturn 0;
}int main()
{int m = 10;m = 12;const n = 12;int* const p1 = &n;p1 = &m;//error*p1 = 13;//rightreturn 0;
}

?在上述代碼中,我們發現:

? const如果放在*的左邊,修飾的是指針指向的內容,保證指針指向的內容不能通過指針來改變。但是指針變量本身的內容可變。
? const如果放在*的右邊,修飾的是指針變量本身,保證了指針變量的內容不能修改,但是指針指向的內容,可以通過指針改變。

?4.指針運算

指針的基本運算有三種:

? 指針+-整數
? 指針-指針
? 指針的關系運算

4.1指針+-整數?

因為數組在內存中是連續存放的,只要知道第?個元素的地址,順藤摸瓜就能找到后?的所有元素。

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?數組元素和下標?

int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p1 = arr;int sz = sizeof(arr) / sizeof(arr[0]);for (int i = 0; i < sz; i++){printf("%d ", *(p1 + i));}return 0;
}

?4.2指針減指針

int my_strlen(char* ch)
{char* p = ch;while (*p != '\0'){p++;}return p - ch;
}int main()
{printf("%d\n", my_strlen("abcdef"));return 0;
}

4.3指針的運算關系

int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int sz = sizeof(arr) / sizeof(arr[0]);int* p = arr;while (p < arr + sz){printf("%d ", *p);p++;}return 0;
}

5.野指針

概念: 野指針就是指針指向的位置是不可知的(隨機的、不正確的、沒有明確限制的)

5.1野指針成因?

5.1.1指針未初始化

5.1.2指針越界訪問?

?5.1.3指針指向的空間釋放

int* test()
{int num = 100;return &num;
}int main()
{int* p = test();printf("%d\n", *p);return 0;
}

5.2如何規避野指針

5.2.1指針初始化

如果明確知道指針指向哪?就直接賦值地址,如果不知道指針應該指向哪?,可以給指針賦值 NULL 。NULL 是C語?中定義的?個標識符常量,值是0,0也是地址,這個地址是?法使?的,讀寫該地址會報錯。
int main()
{int num1 = 12;int* p1 = &num1;int* p2 = NULL;return 0;
}

5.2.2小心指針越界訪問

?個程序向內存申請了哪些空間,通過指針也就只能訪問哪些空間,不能超出范圍訪問,超出了就是越界訪問。

5.2.3指針變量不再使用時,及時置NULL,指針使用之前檢查有效性?

當指針變量指向?塊區域的時候,我們可以通過指針訪問該區域,后期不再使?這個指針訪問空間的時候,我們可以把該指針置為NULL。因為約定俗成的?個規則就是:只要是NULL指針就不去訪問,同時使?指針之前可以判斷指針是否為NULL。

5.2.4避免返回局部變量的地址

如造成野指針的第3個例?,不要返回局部變量的地址。

6.assert斷言

assert.h 頭?件定義了宏 assert() ,?于在運?時確保程序符合指定條件,如果不符合,就
報錯終?運?。這個宏常常被稱為“斷?”。
基本語法:
#include <assert.h>
assert(expression);

這里的?expression?是一個布爾表達式,assert?會對其進行求值。如果?expression?的值為真(非零),程序會繼續正常執行;如果?expression?的值為假(零),assert?會觸發一個斷言失敗,程序會調用?abort()?函數終止執行,并輸出錯誤信息。?

它不僅能自動標識?件和出問題的?號,還有?種?需更改代碼就能開啟或關閉 assert() 的機制。如果已經確認程序沒有問題,不需要再做斷?,就在 #include <assert.h> 語句的前?,定義?個宏 NDEBUG

#define NDEBUG
#include<assert.h>

然后,重新編譯程序,編譯器就會禁用文件中所有的 assert() 語句。如果程序又出現問題,可以移 除這條 #define NDBUG 指令(或者把它注釋掉),再次編譯,這樣就重新啟用了 assert() 語句。

assert() 的缺點是,因為引入了額外的檢查,增加了程序的運行時間。

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

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

相關文章

程序化廣告行業(70/89):ABTester系統助力落地頁優化實踐

程序化廣告行業&#xff08;70/89&#xff09;&#xff1a;ABTester系統助力落地頁優化實踐 在程序化廣告領域摸爬滾打多年&#xff0c;深知持續學習和知識共享的重要性。寫這篇博客&#xff0c;就是希望能和大家一起深入探索程序化廣告行業&#xff0c;共同學習、共同進步。今…

項目管理(高軟56)

系列文章目錄 項目管理 文章目錄 系列文章目錄前言一、進度管理二、配置管理三、質量四、風險管理五、真題總結 前言 本節主要講項目管理知識&#xff0c;這些知識聽的有點意思啊。對于技術人想創業&#xff0c;單干的都很有必要聽聽。 一、進度管理 二、配置管理 三、質量 四…

常見的后綴名

.exe .exe&#xff08;“executable”&#xff08;可執行的&#xff09;&#xff09;是 Windows 操作系統中最常見的可執行文件擴展名。此類文件包含了計算機能夠直接運行的機器碼指令。當用戶雙擊 .exe 文件時&#xff0c;操作系統會讀取其中的指令并執行相應的程序或任務。…

XILINX DDR3專題---(1)IP核時鐘框架介紹

1.什么是Reference Clock&#xff0c;這個時鐘一定是200MHz嗎&#xff1f; 2.為什么APP_DATA是128bit&#xff0c;怎么算出來的&#xff1f; 3.APP &#xff1a;MEM的比值一定是1:4嗎&#xff1f; 4.NO BUFFER是什么意思&#xff1f; 5.什么情況下Reference Clock的時鐘源可…

Doris 安裝部署、實際應用及優化實踐:對比 ClickHouse 的深度解析

在實時分析、報表系統以及高并發 OLAP 查詢等場景中&#xff0c;列式存儲數據庫因其卓越的查詢性能逐漸成為主流。Doris 和 ClickHouse 是近年來最受歡迎的兩款開源 OLAP 引擎&#xff0c;本文將系統介紹 Doris 的安裝部署、應用場景及優化實踐&#xff0c;并與 ClickHouse 做一…

OracleLinuxR5U5系統重啟后啟動數據庫oracle23ai

1、切換到oracle用戶 [rootOracleLinux-R9-U5 ~]# su oracle2、查看oracle是否配置了ORACLE_SID [oracleOracleLinux-R9-U5 root]$ cd ~ [oracleOracleLinux-R9-U5 ~]$ cat .bash_profile3、輸出內容如下&#xff1a; [oracleOracleLinux-R9-U5 ~]$ cat .bash_profile # .ba…

【正點原子】STM32MP257 同構多核架構下的 ADC 電壓采集與處理應用開發實戰

在嵌入式系統中&#xff0c;ADC模擬電壓的讀取是常見的需求。如何高效、并發、且可控地完成數據采集與處理&#xff1f;本篇文章通過雙線程分別綁定在 Linux 系統的不同 CPU 核心上&#xff0c;采集 /sys/bus/iio 接口的 ADC 原始值與縮放系數 scale&#xff0c;并在另一個核上…

電商用戶購物行為分析:基于K-Means聚類與分類驗證的完整流程

隨著電商行業的快速發展,用戶行為分析成為企業優化營銷策略、提升用戶體驗的重要手段。通過分析用戶的購物行為數據,企業可以挖掘出用戶群體的消費特征和行為模式,從而制定更加精準的營銷策略。本文將詳細介紹一個基于Python實現的電商用戶購物行為分析系統,涵蓋數據預處理…

AMGCL庫的Backends及使用示例

AMGCL庫的Backends及使用示例 AMGCL是一個用于解決大型稀疏線性方程組的C庫&#xff0c;它提供了多種后端(backends)實現&#xff0c;允許用戶根據不同的硬件和性能需求選擇合適的計算后端。 AMGCL支持的主要Backends 內置Backends: builtin - 默認的純C實現block - 支持塊狀…

Express中間件(Middleware)詳解:從零開始掌握(3)

實用中間件模式25例 1. 基礎增強模式 請求屬性擴展 function extendRequest() {return (req, res, next) > {req.getClientLanguage () > {return req.headers[accept-language]?.split(,)[0] || en;};next();}; } 響應時間頭 function responseTime() {return (r…

05--MQTT物聯網協議

一、MQTT的概念 MQTT 協議快速入門 2025&#xff1a;基礎知識和實用教程 | EMQ 1.MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;是一種輕量級、基于發布-訂閱模式的消息傳輸協議&#xff0c;適用于資源受限的設備和低帶寬、高延遲或不穩定的網絡環境。它…

數據結構與算法——鏈表OJ題詳解(2)

文章目錄 一、前言二、OJ續享2.1相交鏈表2.2環形鏈表12.2環形鏈表2 三、總結 一、前言 哦了兄弟們&#xff0c;咱們上次在詳解鏈表OJ題的時候&#xff0c;有一部分OJ題呢up并沒有整理完&#xff0c;這一個星期呢&#xff0c;up也是在不斷的學習并且沉淀著&#xff0c;也是終于…

SQL Server AlwaysOn (SQL 查詢數據詳解及監控用途)

修正后的完整查詢 SELECT ar.replica_server_name AS [副本名稱],ar.availability_mode_desc AS [同步模式],DB_NAME(dbr.database_id) AS [數據庫名稱],dbr.database_state_desc AS [數據庫狀態],dbr.synchronization_state_desc AS [同步狀態],dbr.synchronization_health_d…

力扣熱題100刷題day63|49.字母異位詞分組

目錄 一、哈希表相關理論 二、思路 核心思路 三、相關題目 四、總結 一、哈希表相關理論 代碼隨想錄刷題day15|&#xff08;哈希表篇&#xff09;242.有效的字母異位詞、383.贖金信-CSDN博客 二、思路 首先&#xff0c;創建一個map集合&#xff0c;遍歷字符串數組&…

愛普生可編程晶振SG8201CJ和SG8200CJ在胃鏡機器人發揮重要作用

在醫療機器人技術高速發展的今天&#xff0c;胃鏡機器人作為胃腸道疾病診斷與治療的創新設備&#xff0c;正逐漸改變傳統診療模式。其復雜精密的系統需要精準的時間同步與穩定的信號輸出&#xff0c;胃鏡機器人是一種先進的醫療設備&#xff0c;用于無創性地檢查胃部疾病。與傳…

Ubuntu22環境下,Docker部署阿里FunASR的gpu版本

番外: 隨著deepseek的爆火,人工智能相關的開發變得異常火爆,相關的大模型開發很常見的agent智能體需要ASR語音識別的功能,阿里開源的FunASR幾乎是把一個商業的項目放給我們使用了。那么我們項目中的生產環境怎么部署gpu版本的語音識別服務呢?經過跟deepseek的一上午的極限…

圖解Java設計模式

1、設計模式面試題 2、設計模式的重要性 3、7大設計原則介紹 3.1、單一職責原則

transformers的 pipeline是什么:將模型加載、數據預處理、推理等步驟進行了封裝

transformers的 pipeline是什么:將模型加載、數據預處理、推理等步驟進行了封裝 pipe = pipeline("text-generation", model=model, tokenizer=tokenizer, max_new_tokens=50 )pipeline :這是 transformers 庫中一個非常實用的工具函數。它可以基于預訓練模型快速構…

jmeter插件安裝

1、下載 下載地址&#xff1a; Documentation :: JMeter-Plugins.org 然后復制到D:\apache-jmeter-5.6.3\lib\ext 復制后 2、重啟jmeter 在菜單【選項】找到“Plugins Manager” 在 Plugins Manager 界面上&#xff0c;點擊“Available Plugins”標簽頁&#xff0c;可以瀏覽所…

VSCode CMake調試CPP程序

文章目錄 1 安裝C與CMake插件2 配置CMakeLists.txt3 使用CMake編譯調試3.1 編譯3.2 調試 4 自定義構建調試參考 1 安裝C與CMake插件 C插件 CMake插件 2 配置CMakeLists.txt 編寫測試程序 #include<iostream>int main(int argc, char const *argv[]) {int a 1, b 2;i…