C語言:指針(1-2)

5. 指針運算

指針的基本運算有三種,分別是:

指針+-整數

指針-指針

指針的關系運算

5.1 指針運算

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

那么,運用這一點,我們就可以寫出下面的代碼:

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

我們利用指針 arr (數組名即為數組首元素的地址)?+ i?,訪問數組中下標為 i 的元素,并打印出來。而指針與整數運算后跳過的字節數的大小是與數據的類型有關的。例如,上面代碼中, arr 數組是整型數組,所以在運算時,會在 arr 的位置,跳過4 * i 個字節,訪問到數組中下標為 i 的元素。

5.2 指針 -?指針

上面,我們知道指針可以和整數進行加減運算,那指針是否可以與指針進行加減運算呢?

#include <stdio.h>int main()
{int arr[] = {1,2,3,4,5,6,7,8,9};int a = (arr + 9) - (arr + 3);//正常運行int b = (arr + 9) + (arr + 3);//編譯器報錯:Invalid operands to binary expression ('int *' and 'int *')printf("%d",a);return 0;
}

將指針加法的那一行代碼刪去后,我們得到了如下輸出:

6
進程已結束,退出代碼為 0

輸出結果為6,這代表了(arr + 9)與(arr + 3)兩個指針之間一共有6個元素。因此,指針的減法運算所得到的結果就是兩個地址之間的元素個數。

利用這一點,我們可以自己寫出類似于函數 strlen()的效果的代碼:

int my_strlen(char* s)
{char *p = s;while(*p != '\0'){p++;}return p - s;
}
#include <stdio.h>
int main()
{char s1[] = "asdf";int a = my_strlen(s1);printf("%d",a);return 0;
}//輸出結果
4

我們發現,輸出結果為4,正等于字符數組中的字符數。

5.3?指針的關系運算

我們知道,指針就是地址,而地址有高低之分,那指針是否可以比較大小呢?

#include <stdio.h>int main()
{int arr[] = {1,2,3,4,5,6,7,8,9};int sz = sizeof(arr)/ sizeof(arr[0]);int *p = &arr[0];while(p < arr + sz){printf("%d ",*p);p++;}return 0;
}//輸出結果
1 2 3 4 5 6 7 8 9 

我們可以發現,循環正常進行,說明表達式是合法有效的,指針可以用來進行比較大小

6.?野指針

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

6.1 產生野指針的原因

6.1.1.?指針未初始化

#include <stdio.h>int main()
{int* p ;*p = 0;//編譯器警告:Variable 'p' is uninitialized when used herereturn 0;
}

指針未初始化時,默認為隨機值。直接使用可能導致系統報錯。

6.1.2?指針越界訪問

這種錯誤可以類比數組訪問越界:

#include <stdio.h>int main()
{int i;int arr[10] = {0};int* p = &arr[0];for(i = 0;i < 11;i++){*p = i;p++;}return 0;
}

在這個代碼中,當指針指向的范圍超出數組范圍時,p就會成為野指針,執行預期外的操作。

6.1.3?指針指向的空間釋放

當指針所指向的空間已經被釋放時,就會導致野指針的產生:

int*  test()
{int n = 1;return &n;
}
#include <stdio.h>int main()
{int* p = test();printf("%p",p);return 0;
}

由于變量n是在函數test中創建,因此函數執行完畢后,變量n的內存也會被回收,空間被釋放。此時,程序就會打印出一個無效地址或者程序崩潰。

6.2?如何規避野指針

6.2.1?指針初始化

在創建指針變量時,如果明確知道指針指向哪里就直接賦值地址;如果不知道指針應該指向哪里,可以給指針賦值NULL,再后面使用時再進行賦值。

NULL是C語言中定義的一個標識符常量,值是0,地址也是0,這個地址是無法使用的,讀寫該地址時程序會報錯。

#include <stdio.h>int main()
{int n = 0;int* p1 = &n;int* p2 = NULL;return 0;
}

6.2.2 防止指針越界

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

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

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

#include <stdio.h>int main()
{int i;int arr[10] = {0};int* p = &arr[0];for(i = 0;i < 11;i++){*p = i;p++;}//此時,指針已經訪問越界p = NULL;//將p賦值為NULL,防止p成為野指針...if(p != NULL)//使用前,檢驗p是否為空指針{...}return 0;
}

6.2.4?避免返回局部變量的地址

如上面的示例,避免返回局部變量的地址,防止使用野指針。

7. assert 斷言

assert.h 頭文件中定義了宏assert(),用于在運行時確保程序符合指定條件,如果不符合,就報錯終止運行。這個宏常常被稱為“斷言”。

例如:

assert(p != NULL);

上面代碼在程序運行到這?行語句時,驗證變量p是否為空指針。如果表示,程序正常運行;否則,程序終止運行,并且會給出錯誤信息。

assert() 宏接受?個表達式作為參數。如果該表達式為真(返回值非零),assert()宏則不會產生任何作用,程序繼續運行。如果該表達式為假(返回值為零),?assert() 就會報錯,在標準錯誤流stderr 中寫入一條錯誤信息,顯示沒有通過的表達式,以及包含這個表達式的文件名和行號。

assert()的使用對程序員非常友好,使用assert()的好處在于:它不僅能自動標識文件和出問題的行號,還有一種無需更改代碼就能開啟或關閉assert()的機制。如果已經確認程序沒有問題,不需要再做斷言,就在 #include <assert.h> 語句前面定義一個 NDEBUG

 #define NDEBUG#include <assert.h>

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

而使用assert()的缺點在于:引入了額外的檢查,增加了程序的運行時間。

一般我們可以在Debug中使用,在Release版本中選擇禁用assert()就行。這樣在debug版本寫有利于程序員排查問題,在Release版本不影響用戶的使用體驗。

8. 指針的使用和傳址調用

8.1?strlen的模擬實現

庫函數strlen的功能是求字符串長度,統計的是字符串中 '\0' 前的字符數。

函數原型如下:

size_t strlen ( const char * str );

參數str接收一個字符串的起始地址,然后開始統計字符串中 '\0' 之前的字符個數,最終返回長度。

因此,我們模擬就需要從起始地址開始向后逐個檢查字符,如果不為 '\0' ,計數器就+1,知道遇到 '\0' 為止。

例如:

#include <stdio.h>
#include <assert.h>int my_strlen(const char* s)
{assert(s);int count = 0;while(*s != '\0'){count++;s++;}return count;
}//輸出結果
5

8.2?傳值調用和傳址調用

學習了指針的知識,現在我們來看看專門用指針來解決的問題。

例如:寫一個函數,交換兩個整型變量的值

思考之后,我們可能會寫出這樣的代碼:

#include <stdio.h>void Swap(int x,int y)
{int temp;temp = x;x = y;y = temp;
}int main()
{int a = 1,b = 2;printf("交換前:a = %d,b = %d",a,b);Swap(a,b);printf("交換后:a = %d,b = %d",a,b);return 0;
}

但是當我們檢查打印結果時:

交換前:a = 1,b = 2
交換后:a = 1,b = 2

我們發現a,b的值并沒有和我們預期中一樣實現交換,這是為什么呢?

這個時候,我們就要回顧一下前面的知識:形參是實參的一份臨時拷貝,也就是說,形參與實參的地址是不同的。在函數內部實現的值的交換只是交換了形參的地址中的值,而實參的地址的值并沒有變化。在函數結束后,內存被釋放。所以,x和y的值的交換不會影響a和b的值。

像Swap函數這樣,在調用函數時傳遞變量本身的調用方法被稱為傳值調用

結論:實參傳遞給形參的時候,形參會單獨創建?份臨時空間來接收實參,對形參的修改不影響實 參。

因此,這種寫法是錯誤的,那我們應該怎么實現題目要求呢?

我們現在要解決的事情就是,在函數Swap內部實現main函數中變量a和b的值的交換。既然直接傳遞變量時,形參與實參的地址是不同的,那我們直接傳遞地址是否能解決這個問題呢?

于是,我們可以得到下面的代碼:

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

此時,我們再檢查打印結果:

交換前:a = 1,b = 2
交換后:a = 2,b = 1

可以發現,代碼成功實現了值的交換。

而像這樣在調用函數時傳遞變量的地址的調用方式被稱為傳址調用。

傳址調用,可以讓函數和主調函數之間建立真正的聯系,在函數內部可以修改主調函數中的變量;所以未來函數中只是需要主調函數中的變量值來實現計算,就可以采用傳值調用。如果函數內部要修改主調函數中的變量的值,就需要傳址調用。

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

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

相關文章

【多模態】DPO學習筆記

DPO學習筆記1 原理1.0 名詞1.1 preference model1.2 RLHF1.3 從RLHF到DPOA.解的最優形式B. DPO下參數估計C. DPO下梯度更新D. DPO訓練的穩定性2 源代碼2.1 數據集構成2.2 計算log prob2.3 DPO loss1 原理 1.0 名詞 preference model&#xff1a;對人類偏好進行建模&#xff0…

2025最新、UI媲美豆包、DeepSeek等AI大廠的AIGC系統 - IMYAI源碼部署教程

IMYAI 系統部署與使用手冊 一、系統演示 &#x1f539; 快速體驗 前端演示地址&#xff1a;https://super.imyaigc.com后臺演示地址&#xff1a;https://super.imyaigc.com/settings &#x1f539; 技術架構 前端&#xff1a;Vite Vue3 NaiveUI TailwindCSS Plyr后端&…

【關于Java的反射】

在 Java 編程中&#xff0c;反射&#xff08;Reflection&#xff09; 是一個非常強大的工具&#xff0c;它允許你在運行時動態地獲取類的信息、創建對象、調用方法和訪問字段。雖然反射功能強大&#xff0c;但它也有一些局限性和性能開銷&#xff0c;因此需要謹慎使用。一、什么…

Gitee推出“移動軟件工廠“解決方案 解決嵌入式與涉密場景研發困局

Gitee推出"移動軟件工廠"解決方案 破解嵌入式與涉密場景研發困局 隨著數字化轉型浪潮的推進&#xff0c;軟件開發正面臨著前所未有的復雜環境挑戰。特別是在嵌入式系統、FPGA開發以及涉密信息系統等特殊場景下&#xff0c;研發團隊往往需要在高安全要求與有限網絡環境…

低功耗16*8位四線串行8*4按鍵陣矩LED驅動專用電路

概述&#xff1a;PC0340是占空比可調的LED顯示控制驅動電路。由16根段輸出、8根位輸出、數字接口、數據鎖存器、顯示存儲器、鍵掃描電路及相關控制電路組成了一個高可靠性的單片機外圍LED驅動電路。串行數據通過4線串行接口輸入到PC0340&#xff0c;采用LQFP44L的封裝形式。本產…

通過自定義注解加aop切面實現權限控制

前言&#xff1a;自定義注解&#xff0c;通過aop切面前置通知&#xff0c;對請求接口進行權限控制1&#xff0c;創建枚舉類package org.springblade.sample.annotationCommon;import lombok.AllArgsConstructor; import lombok.Getter;import java.util.Arrays; import java.ut…

IDS知識點

在網絡安全工程師、系統運維工程師等崗位的面試中&#xff0c;??IDS&#xff08;Intrusion Detection System&#xff0c;入侵檢測系統&#xff09;?? 是高頻考點&#xff0c;尤其是對網絡安全防護、安全監控類崗位。以下是IDS的核心考點和必須掌握的知識點&#xff0c;按優…

Adobe Analytics 數據分析平臺|全渠道客戶行為分析與體驗優化

Adobe Analytics 是業界領先的數據分析平臺&#xff0c;幫助企業實時追蹤客戶行為&#xff0c;整合多渠道數據&#xff0c;通過強大的分析與可視化工具深入分析客戶旅程&#xff0c;優化數字體驗。結合 Adobe Experience Cloud&#xff0c;Adobe Analytics 成為推動數字化增長和…

【輪播圖】H5端輪播圖、橫向滑動、劃屏效果實現方案——Vue3+CSS position/CSS scroller

文章目錄定位實現滑屏效果前置知識CSS: touch-action屬性CSS: transform屬性觸摸事件forEach回調占位符準備階段實現移動效果實現跟手效果觸摸結束優化完整代碼滾動實現滑屏效果前置知識CSS: scroll-snap-type屬性準備階段實現滑動效果實現吸附效果滾動條隱藏存在問題完整代碼s…

忘記了WordPress管理員密碼的找回方法

WordPress管理員密碼找回方法 如果您忘記了WordPress管理員密碼&#xff0c;可以通過以下幾種方法找回或重置&#xff1a; 方法1&#xff1a;通過電子郵件重置(最簡單) 訪問您的WordPress登錄頁面(通常是wodepress.com/wp-admin或wodepress.com/wp-login.php) 點擊”忘記密…

RAFT:讓語言模型更聰明地用文檔答題

RAFT&#xff1a;讓語言模型更聰明地用文檔答題 作者注&#xff1a; 本文旨在面向零基礎讀者介紹 UC Berkeley 提出的 RAFT&#xff08;Retrieval-Augmented Fine-Tuning&#xff09;方法。它是一種訓練語言模型的新方式&#xff0c;讓模型更好地利用“外部知識”——比如文檔、…

【緊急預警】NVIDIA Triton推理服務器漏洞鏈可導致RCE!

2025 年 8 月 4 日消息&#xff0c;NVIDIA 旗下的 Triton 推理服務器&#xff08;一款支持 Windows 和 Linux 系統、用于大規模運行 AI 模型的開源平臺&#xff09;被曝出一系列安全漏洞。這些漏洞一旦被利用&#xff0c;攻擊者有可能完全接管存在漏洞的服務器。 Wiz 安全公司…

基于深度學習的醫學圖像分析:使用PixelCNN實現醫學圖像生成

前言 醫學圖像分析是計算機視覺領域中的一個重要應用&#xff0c;特別是在醫學圖像生成任務中&#xff0c;深度學習技術已經取得了顯著的進展。醫學圖像生成是指通過深度學習模型生成醫學圖像&#xff0c;這對于醫學研究、疾病模擬和圖像增強等任務具有重要意義。近年來&#x…

React ahooks——副作用類hooks之useDebounceFn

useDebounceFn 是 ahooks 提供的用于函數防抖的 Hook&#xff0c;它可以確保一個函數在連續觸發時只執行最后一次。一、基本用法import { useDebounceFn } from ahooks; import { Button } from antd;const Demo () > {const { run } useDebounceFn(() > {console.log(…

【機器學習深度學習】 知識蒸餾

目錄 前言 一、什么是知識蒸餾&#xff1f; 二、知識蒸餾的核心意義 2.1 降低算力與成本 2.2 加速推理與邊緣部署 2.3 推動行業應用落地 2.4 技術自主可控 三、知識蒸餾的本質&#xff1a;大模型的知識傳承 四、知識蒸餾的“四重紅利” 五、DeepSeek的知識蒸餾實踐 …

Python高級編程與實踐:Python高級數據結構與編程技巧

高級數據結構&#xff1a;掌握Python中的高效編程技巧 學習目標 通過本課程&#xff0c;學員將深入了解Python中的高級數據結構&#xff0c;包括列表推導式、字典推導式、集合推導式和生成器表達式。學員將學習如何利用這些結構來編寫更簡潔、更高效的代碼&#xff0c;并了解它…

【C++】Stack and Queue and Functor

本文是小編鞏固自身而作&#xff0c;如有錯誤&#xff0c;歡迎指出&#xff01;本次我們介紹STL中的stack和queue和其相關的一些容器和仿函數一.stack and queue1.適配器stack和queue其實不是真正意義上的容器&#xff0c;而是容器適配器&#xff0c;而容器適配器又是什么呢&am…

Python爬蟲實戰:研究OpenCV技術構建圖像數據處理系統

1. 引言 1.1 研究背景 在當今數字化時代,圖像作為一種重要的信息載體,廣泛存在于各類網站、社交媒體和在線平臺中。這些圖像數據涵蓋了從自然風光、人物肖像到商品展示、新聞事件等豐富內容,為數據分析和模式識別提供了寶貴的資源。隨著計算機視覺技術的快速發展,對大規模…

電感矩陣-信號完整性分析

電感矩陣:正如電容矩陣用于存儲許多信號路徑和返回路徑的所有電容量&#xff0c;我們也需要一個矩陣存儲許多導線的回路自感和回路互感值。需要牢記的是&#xff0c;這里的電感元件是回路電感。當信號沿傳輸線傳播時&#xff0c;電流回路沿信號路徑傳輸&#xff0c;然后立即從返…

JUC相關知識點總結

Java JUC&#xff08;java.util.concurrent&#xff09;是Java并發編程的核心工具包&#xff0c;提供了豐富的并發工具類和框架。以下是JUC的主要知識點&#xff0c;按難易程度分類&#xff0c;供你參考&#xff1a; 1. 基礎概念與工具類 1.1 并發與并行&#xff08;易&#x…