【C語言】指針深度剖析(一)

文章目錄

  • 一、內存和地址
    • 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* 指針
  • 四、const修飾指針
    • 4.1 const修飾變量
    • 4.2 const修飾指針變量
  • 五、指針運算
    • 5.1 指針±整數
    • 5.2 指針-指針
    • 5.3 指針的關系運算
  • 六、野指針
    • 6.1 野指針成因
    • 6.2 如何規避野指針
  • 七、assert斷言
  • 八、指針的使用和傳址調用
    • 8.1 strlen的模擬實現
    • 8.2 傳值調用和傳址調用

一、內存和地址

1.1 內存的基本概念

在計算機中,CPU處理數據時需要從內存中讀取數據,處理后的數據也會放回內存。為了高效管理內存,內存被劃分為一個個大小為1字節的內存單元,每個內存單元都有唯一的編號,這個編號就是我們所說的地址,在C語言中也被稱為指針。

舉個例子:一棟宿舍樓有100個房間,給每個房間編上號(如101、102等),根據房間號就能快速找到房間。內存就像這棟宿舍樓,每個內存單元就像一個房間,地址則是房間號,有了地址,CPU就能快速找到對應的內存單元。

計算機中常見的存儲單位及換算關系如下:

  • 1byte(字節)= 8bit(比特位)
  • 1KB = 1024byte
  • 1MB = 1024KB
  • 1GB = 1024MB
  • 1TB = 1024GB
  • 1PB = 1024TB

1.2 編址的原理

CPU訪問內存中的某個字節空間,必須知道該字節空間的地址。計算機的編址是通過硬件設計實現的,就像鋼琴、吉他等樂器,制造商在硬件層面設計好,演奏者就能準確找到相應位置。

CPU和內存之間通過大量的線連接,其中一組重要的線是地址總線。32位機器有32根地址總線,每根線有0和1兩種狀態(表示電脈沖有無),32根地址線能表示2^32種不同的地址。地址信息通過地址總線傳給內存,內存根據地址找到對應數據,再通過數據總線傳入CPU寄存器。

在這里插入圖片描述

二、指針變量和地址

2.1 取地址操作符(&)

在C語言中,創建變量的本質是向內存申請空間

#include <stdio.h>
int main()
{int a = 10;return 0;
}

變量a占用4個字節的內存空間,每個字節都有自己的地址。我們可以使用取地址操作符&來獲取變量的地址,如&a得到的是a所占4個字節中地址較小的那個字節的地址。
在這里插入圖片描述

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

2.2.1 指針變量

通過取地址操作符&得到的地址是一個數值,我們可以將其存儲在指針變量中。指針變量就是專門用來存放地址的變量

#include <stdio.h>
int main()
{int a = 10;int* pa = &a; // 取出a的地址并存儲到指針變量pa中return 0;
}

2.2.2 指針類型的解讀

指針變量的類型由*和前面的類型組成,如int*表示該指針變量指向的是整型(int)類型的對象。對于char類型的變量ch,其地址應存放在char*類型的指針變量中。

2.2.3 解引用操作符

有了指針變量存儲地址后,我們可以使用解引用操作符*通過地址找到對應的變量并進行操作。

#include <stdio.h>
int main()
{int a = 100;int* pa = &a;*pa = 0; // 通過pa中存放的地址找到a,并將a的值改為0return 0;
}

這里*pa就相當于變量a,通過*pa可以對a進行修改,這為操作變量提供了另一種途徑。

2.3 指針變量的大小

指針變量的大小取決于地址的大小:

  • 在32位平臺下,地址是32個比特位,指針變量大小為4個字節。
  • 在64位平臺下,地址是64個比特位,指針變量大小為8個字節。

需要注意的是,指針變量的大小和其類型無關,在相同平臺下,所有指針類型的變量大小都是相同的。例如:

#include <stdio.h>
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位環境下輸出結果均為4,在64位環境下輸出結果均為8。

三、指針變量類型的意義

雖然指針變量的大小和類型無關,但指針類型有著重要的意義。

3.1 影響解引用的權限

指針的類型決定了對指針解引用時的操作權限,即一次能操作的字節數。例如:

  • char*類型的指針解引用只能訪問1個字節。
  • int*類型的指針解引用能訪問4個字節。

看下面兩段代碼:

// 代碼1
int main() {int a = 0x11223344;int* p = &a;*p = 0;return 0;
}

逐語句調試,代碼運行到15行時。int*可以訪問四個字節,將四個字節都改為0
在這里插入圖片描述

// 代碼2
int main() {int a = 0x11223344;char* p = &a;*p = 0;return 0;
}

逐語句調試,代碼運行到22行時。char*只訪問訪問一個字節,將第一個字節改為0
在這里插入圖片描述

3.2 影響指針 ± 整數的步長

指針的類型決定了指針向前或者向后走一步的距離。例如:

int* + 1 跳過四個字節(int大小),char* + 1 跳過一個字節(char大小)
在這里插入圖片描述

3.3 void* 指針

void*類型的指針可以接受任意類型的地址,可以理解為無具體類型指針(或者叫泛型指針)但它不能直接進行指針的±整數和解引用運算。通常用于函數參數部分,實現泛型編程的效果,以處理多種類型的數據。

四、const修飾指針

4.1 const修飾變量

const修飾的變量不能直接被修改。

#include <stdio.h>
int main()
{const int n = 0;n = 20; // 報錯,n不能被直接修改return 0;
}

此時變量具有常屬性,稱為常變量,但本質依舊是變量而不是常量。

在C++中被const修飾則為常量。

但如果通過指針獲取其地址,還是可以修改該變量的值,這顯然打破了const的限制,所以需要用const修飾指針變量。

在這里插入圖片描述

4.2 const修飾指針變量

  • const放在*的右邊:修飾的是指針變量本身,指針變量不可以再指向其他變量。但可以通過指針修改指向的內容。
int main()
{int a = 10;int b = 20;int * const p = &a;p = &b;//err*p = 100;//可以通過編譯return 0;
}
  • const放在*的左邊:限制指向的內容,不可以通過指針來修改,但可以修改指針指向的變量。
int main()
{int a = 10;int b = 20;int const* p = &a;p = &b;//可以通過編譯*p = 100;//errreturn 0;
}

在這里插入圖片描述

五、指針運算

指針的基本運算有三種:

5.1 指針±整數

原理同本文3.2部分。

由于數組在內存中是連續存放的,知道第一個元素的地址后,通過指針±整數可以訪問數組中的其他元素。

int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};int* p = &arr[0];int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);for (i = 0; i < sz; i++){printf("%d ", *(p + i)); // 通過指針+整數訪問數組元素}return 0;
}

在這里插入圖片描述
注意:

  • *(p+1)不要寫成*p+1,前者表示指針變量+1,后者表示p指向的內容+1
  • sizeof()中,輸入數組名arr,計算整個數組的大小。

5.2 指針-指針

通過上述,我們可以明確:

指針1 + 整數 = 指針2

以此推理出

整數 = 指針2 - 指針1

類比“日期 - 日期”,得到之間的天數。兩個指針相減的結果是它們之間的元素個數,常用于計算字符串長度等場景

  • strlen()求字符串長度,統計字符串\0之前字符個數
  • 數組名arr是數組首元素的地址。arr等價于&arr[0]

模擬實現strlen()函數:

  • 方法1 計數器
int my_strlen(char* s)
{int cnt = 0; //計數器while(*s != '\0'){cnt++;str++}return cnt; // 計算兩個指針之間的元素個數,即字符串長度
}
int main()
{printf("%d\n", my_strlen("abc")); //輸出3return 0;
}
  • 方法2 指針 - 指針
int my_strlen(char* s)
{char* p = s;while (*p != '\0')p++;return p - s; // 計算兩個指針之間的元素個數,即字符串長度
}
int main()
{printf("%d\n", my_strlen("abc")); //輸出3return 0;
}

注意:

  • 指針 - 指針 的前提時兩個指針指向同一塊空間!

例如:

int main()
{int arr[10] = {0};char ch[10] = {'0'};printf("%d\n",&ch[0] - &arr[0]);//errreturn 0;
}
  • "日期 + 日期"沒有意義,同樣的,“指針 + 指針”也沒有任何意義。

5.3 指針的關系運算

指針與指針比較大小,其實就是地址與地質比較大小。

數組隨下標變大,地址由低變高。

int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};int* p = &arr[0];int sz = sizeof(arr) / sizeof(arr[0]);while (p < arr + sz) // 指針的大小比較,當p指向的地址小于arr+sz(相當于數組最后一個元素的地址)進入循環{printf("%d ", *p);p++;}//輸出 1 2 3 4 5 6 7 8 9 10return 0;
}

六、野指針

野指針就是指針指向內容是不可知的(不正確、隨機、沒有明確限制)

6.1 野指針成因

  • 指針未初始化:局部變量指針未初始化時,其值是隨機的。
int main()
{ int *p;//此時p是局部變量,指針未初始化,默認為隨機值 *p = 20;return 0;
}
  • 指針越界訪問:指針指向的范圍超出數組等申請的內存空間。
int main()
{int arr[10] = {0};int *p = &arr[0];int i = 0;for(i=0; i<=11; i++){//當指針指向的范圍超出數組arr的范圍時,p就是野指針*(p++) = i;}return 0;
}
  • 指針指向的空間釋放:返回局部變量的地址,該局部變量的空間在函數調用結束后會被釋放。
int* test()
{int n = 100;return &n;
}int main()
{int*p = test();printf("%d\n", *p);//此時test()調用完成,棧幀被銷毀, 內存被釋放return 0;
}

6.2 如何規避野指針

  • 指針初始化:明確指向時直接賦值地址,否則賦值NULLNULL是值為0的標識符常量,該地址無法使用,讀寫地址也會報錯)。
int main()
{int* p = NULL;*p = 20//err
}
  • 小心指針越界:不訪問超出申請內存范圍的空間。
  • 及時置NULL并檢查:指針變量不再使用時置為NULL,使用前判斷是否為NULL
  • 避免返回局部變量的地址。

七、assert斷言

assert.h頭文件中的assert()宏用于在運行時確保程序符合指定條件,如果不符合就報錯終止運行。其表達式為真時程序繼續運行,為假時報錯并顯示相關信息。

assert(p != NULL;//確保 p為有效指針
  • assert()斷言相對if語句的優點:
    • 出現錯誤會直接報錯,指明在什么文件,哪一行
    • 無需修改代碼就可以禁用assert().可以通過在#include <assert.h>前定義NDEBUG宏來禁用assert()語句.
      在這里插入圖片描述
  • 缺點:
    • 引入了額外的檢查,增加了程序運行時間

通常在Debug版本中使用,Release版本中禁用,以不影響程序效率。VS2022中release版會直接禁用assert

八、指針的使用和傳址調用

8.1 strlen的模擬實現

strlen函數用于求字符串長度,統計的是字符串中\0之前的字符個數。模擬實現如下:

#include <stdio.h>
#include <assert.h>
size_t my_strlen(const char* str)//限制內容,不能被修改
{int count = 0;assert(str); // 確保 str不為NULLwhile (*str){count++;str++;}return count;
}
int main()
{int len = my_strlen("abcdef");printf("%zd\n", len);return 0;
}

求出的長度不可能是負數,因此返回值類型使用size_t(無符號整型)更合適,打印應使用zd%作為占位符。

8.2 傳值調用和傳址調用

  • 傳值調用:實參傳遞給形參時,形參創建臨時空間接收實參。形參和實參是獨立的兩個空間。形參只是實參的一份臨時拷貝,對形參的修改不影響實參。

  • 傳址調用:將變量的地址傳遞給函數,函數內部通過地址間接操作主調函數中的變量,可實現對變量的修改。

例如交換兩個整型變量的值,使用傳址調用:

#include <stdio.h>
void Swap(int* px, int* py)
{int tmp = 0;tmp = *px;*px = *py;*py = tmp;
}
int main()
{int a = 0;int b = 0;scanf("%d %d", &a, &b);printf("交換前:a=%d b=%d\n", a, b);Swap(&a, &b);printf("交換后:a=%d b=%d\n", a, b);return 0;
}

運行結果:
在這里插入圖片描述

指針是C語言的精華,掌握好指針能讓我們在編程中更加得心應手。希望本文能幫助大家更好地理解指針的相關知識,后續還會有更深入的探討。

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

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

相關文章

半導體企業選用的跨網文件交換系統到底應該具備什么功能?

在半導體行業的數字化轉型過程中&#xff0c;跨網文件交換已成為連接研發、生產、供應鏈的關鍵紐帶。半導體企業的跨網文件交換不僅涉及設計圖紙、工藝參數等核心知識產權&#xff0c;還需要滿足跨國協同、合規審計等復雜需求。那么&#xff0c;一款適合半導體行業的跨網文件交…

影刀RPA_初級課程_玩轉影刀自動化_網頁操作自動化

聲明&#xff1a;相關內容來自影刀學院&#xff0c;本文章為自用筆記&#xff0c;切勿商用&#xff01;&#xff08;若有侵權&#xff0c;請聯絡刪除&#xff09; 1. 基本概念與操作 1.1 正確處理下拉框元素&#xff08;先判斷頁面元素&#xff0c;后進行流程編制&#xff09;…

Spark初探:揭秘速度優勢與生態融合實踐

更多推薦閱讀 Spark與Flink深度對比&#xff1a;大數據流批一體框架的技術選型指南-CSDN博客 LightProxy使用操作手冊-CSDN博客 Sentry一看就會教程_sentry教程-CSDN博客 微前端架構解析&#xff1a;核心概念與主流方案特性對比_微前端方案對比-CSDN博客 目錄 Spark為何比Hadoo…

詳談OSI七層模型和TCP/IP四層模型以及tcp與udp為什么是4層,http與https為什么是7層

一、網絡模型&#xff1a;OSI七層 vs TCP/IP四層OSI七層模型 (理論參考模型):目的&#xff1a;提供一個標準化的理論框架&#xff0c;用于理解網絡通信過程和各層的功能劃分&#xff0c;促進不同廠商設備的互操作性。它是一個理想化的模型。分層 (從下到上):物理層&#xff1a;…

ClickHouse 高性能實時分析數據庫-索引與數據跳過(查詢的“瞬移”能力)

告別等待&#xff0c;秒級響應&#xff01;這不只是教程&#xff0c;這是你駕馭PB級數據的超能力&#xff01;我的ClickHouse視頻課&#xff0c;凝練十年實戰精華&#xff0c;從入門到精通&#xff0c;從單機到集群。點開它&#xff0c;讓數據處理速度快到飛起&#xff0c;讓你…

Jetpack - Room(Room 引入、Room 優化)

一、Room 引入 1、基本介紹 Room 在 SQLite 上提供了一個抽象層&#xff0c;以便在充分利用 SQLite 的強大功能的同時&#xff0c;能夠流暢地訪問數據庫&#xff0c;官方強烈建議使用 Room 而不是 SQLite 2、演示 &#xff08;1&#xff09;Setting 模塊級 build.gradle depend…

【江科大CAN】2.1 STM32 CAN外設(上)

2.1 STM32 CAN外設&#xff08;上&#xff09;2.1.1 STM32 CAN外設簡介2.1.2 外圍電路設計2.1.3 STM32 CAN內部結構2.1.4 發送流程詳解2.1.5 接收流程詳解2.1.6 關鍵配置位總結STM32 CAN外設講解 大家好&#xff0c;歡迎繼續觀看CAN總線入門教程。本節開始&#xff0c;我們正式…

人工智能技術革命:AI工具與大模型如何重塑開發者工作模式與行業格局

引言&#xff1a;AI技術爆發的時代背景過去五年間&#xff0c;人工智能領域經歷了前所未有的爆發式增長。從2020年GPT-3的橫空出世到2023年多模態大模型的全面突破&#xff0c;AI技術已經從實驗室走向了產業應用的前沿。開發者作為技術生態的核心推動者&#xff0c;其工作模式正…

傅里葉變換

傅里葉變換:運用頻域的出發點就是能夠將波形從時域變換到頻域&#xff0c;用傅里葉變換可以做到這一點。有如下3種傅里葉變換類型&#xff1a;1.傅里葉積分(FI); 2.離散傅里葉變換(DFT); 3.快速傅里葉變換(FFT)。傅里葉積分是一種將時域的理想數學表達變換成頻域描述的數學技術…

【IQA技術專題】紋理相似度圖像評價指標DISTS

紋理一致性圖像評價指標: Image Quality Assessment: Unifying Structure and Texture Similarity&#xff08;2020 PAMI&#xff09;專題介紹一、研究背景二、方法總覽2.1 初始變換2.2 紋理表示和結構表示2.3 DISTS指標2.4 優化DISTS指標三、實驗結果四、總結本文將對統一圖像…

windows下Docker安裝路徑、存儲路徑修改

一、命令行指定安裝路徑? ??下載安裝包??&#xff1a;從Docker官網獲取安裝程序&#xff08;如Docker Desktop Installer.exe&#xff09;。??運行PowerShell??&#xff1a; & "H:\Docker Desktop Installer.exe" install --installation-dir"F:…

thingsboard 自定義動作JS編程

在 ThingsBoard 中實現 自定義動作&#xff08;Custom Action&#xff09;的 JavaScript 編程&#xff0c;主要通過“Custom action (with HTML template&#xff09;”方式完成&#xff0c;適用于創建彈窗、編輯實體、控制設備等交互行為。 實現步驟&#xff08;以添加設備或資…

Spring Boot 簡單接口角色授權檢查實現

一、背景與目標在Spring Boot應用開發中&#xff0c;接口級別的權限控制是系統安全的重要組成部分。本文將介紹一種簡單直接的接口角色授權檢查實現方案&#xff0c;適合快速開發和安全合規檢查場景。二、技術方案概述本方案采用自定義注解攔截器的方式實現&#xff0c;具有以下…

PytorchLightning最佳實踐日志篇

在 PyTorch Lightning&#xff08;PL&#xff09;中&#xff0c;日志系統是 “煉丹” 過程中復現實驗、對比效果、排查問題的核心工具。結合實際工程經驗&#xff0c;總結以下最佳實踐和技巧&#xff0c;幫助提升實驗效率&#xff1a; 一、日志工具的選擇與配置 PL 通過統一的s…

基于JavaWeb的兼職發布平臺的設計與實現

開發語言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服務器&#xff1a;tomcat7數據庫&#xff1a;mysql 5.7數據庫工具&#xff1a;Navicat12開發軟件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;Maven3.6系統展示系統首頁用戶登錄招聘信…

Linux學習--C語言(指針3)

1.指針函數和函數指針1.1 指針函數指針函數是函數&#xff0c;函數的返回值是指針不能返回局部變量的地址指針函數返回的地址可以作為下一個函數調用的參數1.2 函數指針函數指針是指針&#xff0c;指針指向一個函數#include <stdio.h>int Add(int x, int y) {return x y…

【JAVA EE初階】多線程(上)

目錄 1.預備知識 1.1 馮諾依曼體系結構&#xff1a; 1.2 現代CPU主要關心指標&#xff08;和日常開發密切相關的&#xff09; 1.3 計算機中&#xff0c;一個漢字占幾個字節&#xff1f; 1.4 Windows和Linux的區別 1.5 PCB的一些關鍵要點 2.線程和進程 2.1 創建線程的寫法…

用互聯網思維擴展電商后臺的 CRUD 功能

一、自定義實現MyBatis-Plus逆向工程 多數據源的問題解決了&#xff0c;接下來開始進行實際開發時&#xff0c;你會發現&#xff0c;最麻煩的一件事情就是要創建與數據庫表對應的POJO了。這些沒什么難度&#xff0c;但是繁瑣的內容會占據大量的開發時間。比如一個PmsProducr對…

無代碼測試平臺ATECLOUD全場景測試方案

ATECLOUD 智能云測試平臺是有納米軟件開發的一款以無代碼架構與彈性擴展體系為核心的自動化測試平臺&#xff0c;通過數據模型驅動的創新設計&#xff0c;為研發、產線等多場景提供高效可控的測試解決方案。?無代碼架構 ATECLOUD 打破傳統技術壁壘&#xff0c;構建完全可視化的…

當 AI 重構審計流程,CISA 認證為何成為破局關鍵

在南京審計大學最新發布的《面向審計行業 DeepSeek 大模型操作指南》中&#xff0c;一組數據引發行業深思&#xff1a;通過自動化數據處理、智能風險識別和定制化報告生成&#xff0c;AI 大模型能幫助審計人員降低 40% 以上的人工成本&#xff0c;同時將風險識別準確率提升至 9…