C語言中級_動態內存分配、指針和常量、各種指針類型、指針和數組、函數指針

0、前言:

  • 動態內存分配是一個重要概念,要和靜態數組對比著學習;
  • 指針和數組搭配在一起,讓指針理解的難度上了一個臺階,尤其是二維數組搭配指針,要獲取數組的值,什么時候“取地址”,什么時候“解引用”都需要深刻理解一些概念才能正確使用指針和數組。
  • 寫這些東西,就是把自己學習的筆記記錄下來,供自己日后翻找,若是與此同時能給別人提供些幫助,那就更好了。

1、動態內存分配:

  • ★前提知識:內存分為棧和堆,
    • “棧”是由編譯器自動分配和釋放,無需程序員手動操作。當函數執行結束時,其內部的局部變量會被自動彈出棧并釋放內存。主要存儲局部變量函數參數、返回地址等。靜態分配,編譯時就能確定所需內存大小。注意如果一個數組定義在自定義函數當中,那它就位于棧當中,函數周期結束,該數組自動釋放。
    • “堆”需要程序員手動分配(如 C 語言的malloc)和釋放(如free),否則可能導致內存泄漏。主要存儲動態分配的對象、數組、大型數據結構等。動態分配,運行時才能確定所需內存大。
  • 在堆中開辟內存空間的三種方式:malloc、calloc、realloc;開辟堆空間成功后,都會返回一個 void* 類型指針,所以需要根據空間存儲內容,強轉這個指針。
  • 數組是靜態內存分配,數組大小必須是常量。是和動態內存分配相對應的一種連續空間開辟的方式。C語言中二維數組空間地址連續,可以用指針遍歷。
    • malloc:依賴于頭文件 stdlib.h,函數聲明:void* malloc(size_t size); // 從堆上分配一塊大小為size的“連續”空間, 并且返回它的首地址。
    • calloc:malloc不會像數組初始化一樣把開辟出的內存空間初始化,這時就要使用calloc,會初始化空間;
    • realloc:在不改變原來空間內容的情況下,對空間進行縮放。如果擴大了空間,就新增空間,但新增的空間不會初始化,然后返回這塊空間首地址,如果縮小了空間,就會截斷多余空間,其余空間內容保持不變(縮小可能導致數據丟失),然后返回這塊空間首地址。使用realloc之后,會自動釋放之前的空間,切記不要重復釋放之前的空間,否則程序會報錯。
  • sizeof運算符是無法計算malloc、calloc、realloc動態生成的空間大小的,但是通過sizeof可以獲取數組的總的字節數。
#include<stdio.h>
#include<stdlib.h>// 在堆中用malloc開辟空間,存放五個double型數據,打印
void mal_loc() {// 開辟空間,把首地址給指針pdouble* p = (double*)malloc(sizeof(double) * 5);int i;// 給空間元素賦值for (i = 0; i < 5; i++) {if (p != NULL) {*(p + i) = (double)(5 + i) / 2;}}// 使用這塊空間for (i = 0; i < 5; i++) {if (p != NULL) {printf("%f\n", *(p + i));  // 也可以用p[i]遍歷}}// 釋放這塊空間free(p);// 指針指向空p = NULL;
}// 在堆中用calloc開辟空間,存放五個double型數據,驗證calloc是否會初始化空間
void cal_loc() {// 開辟空間,把首地址給指針pdouble* p = (double*)calloc(5, sizeof(double));int i;// 使用這塊空間for (i = 0; i < 5; i++) {if (p != NULL) {printf("%f\n", *(p + i)); // 也可以用p[i]遍歷}}// 釋放這塊空間free(p);// 指針指向空p = NULL;
}// 嘗試使用realloc對calloc開辟的5個int空間,縮小至4個int空間,驗證數據丟失
void real_loc() {// 用calloc開辟空間;int* p = (int*)calloc(5, sizeof(int));if (p == NULL) {return;}int i;// 給最后一位設為1;for (i = 0; i < 5; i++) {if (i == 4) {*(p + i) = 1;printf("%d, ", *(p + i));}else {printf("%d, ", *(p + i));}}printf("\n--------------\n");// 用realloc縮小空間;int* newp = (int*)realloc(p, 4*sizeof(int));// 驗證縮小空間是否成功if (newp == NULL) {free(p); p = NULL;// 避免出現懸空指針p = NULL;// 避免出現懸空指針return;}else {p = NULL;// 避免出現懸空指針// 創建成功,測試空間當中的內容for (i = 0; i < 4; i++) {printf("%d, ", *(newp+i)); // 驗證結果}// 最后釋放空間和避免出現懸空指針free(newp); newp = NULL;}
}int main()
{/*mal_loc();printf("-------------\n");cal_loc();printf("-------------\n");*/real_loc();return 0;
}

2、指針和常量:

  • 常量指針:指針指向的值不可以通過指針改變;也就是說*p = num 這條語句失效。
  • 指針常量:指針的指向不能變,也就是說 p = &num 這條語句失效。
// 常量指針
int a = 99;
int const* p1 = &a;
//*p1 = 100;  // 會報錯// 指針常量
int b = 99;
int* const p2 = &b;
*p2 = 100;
//p2 = &a;  // 會報錯

3、各種指針類型:一些指針在江湖上的諢名

  • 萬能指針:void* p,這種萬能指針,可以強轉為其他任何類型的指針:
int *p2 = (int*)vp;  // 顯式轉換:void* → int*
  • 懸空指針:指針曾經指向有效的內存,但該內存已被釋放或失效。
int *p = (int*)malloc(sizeof(int));
*p = 42;
free(p);  // 內存被釋放
// 避免懸空指針:p = NULL;
printf("%d", *p);  // 懸空指針!p 指向的內存已無效
  • 空指針:指向為NULL的指針,int *p = NULL; // 空指針
  • 野指針:未初始化的指針,指向地址是隨機的,int *p; // 野指針!未初始化

3、指針和數組:

  • ★首先搞明白什么是“指針數組”什么是“數組指針”這個很重要
    • 指針數組:本質是數組,數組的每個元素是一個指針。
    • 數組指針:指向一整個數組的指針。
    • 具體的代碼實例:
// ---------指針數組:
int a = 10, b = 20, c = 30;
int *arr[3];  // 指針數組:3個元素的數組,每個元素是 int* 類型
arr[0] = &a;
arr[1] = &b;
arr[2] = &c;printf("%d", *arr[1]);  // 輸出 20(通過指針訪問 b 的值)
const char *names[] = {"Alice", "Bob", "Charlie"};  // 3個字符串的地址
printf("%s", names[0]);  // 輸出 "Alice"
printf("%c\n", (names[0])[2]); // i
/*
names 是一個數組([] 表示數組)。
數組的每個元素是 const char*(指向常量字符的指針)。
因此,names 是一個 指針數組(數組的元素是指針),且這些指針指向 const char(常量指針)
*/// ---------數組指針
int arr[5] = {1, 2, 3, 4, 5};
int (*p)[5];  // 數組指針:指向一個包含 5 個 int 的數組
p = &arr;     // p 指向整個數組 arrprintf("%d", (*p)[2]);  // 輸出 3(解引用 p 得到數組 arr,再訪問下標 2)
  • 通過二維數組的例子深度理解下數組和指針之間的關系:
// 獲取二維數組中第一個一維數組首地址
int arr[3][3] = {{1,2,3},{4,5,6},{7,8,9}
};
printf("*arr = %p\tarr[0]=%p\t&arr[0][0]=%p\tarr+0=%p\n", *arr, arr[0], &arr[0][0], arr + 0);
// 獲取二維數組中第二個一維數組首地址
printf("tarr[1]=%p\t&arr[1][0]=%p\tarr+1=%p\n", arr[1], &arr[1][0], arr + 1);
// 獲取二維數組中第三個一維數組首地址
printf("tarr[2]=%p\t&arr[2][0]=%p\tarr+2=%p\n", arr[2], &arr[2][0], arr + 2);
/*
理解 arr + 0/1/2作為地址的方式很簡單,對于arr數組而言,里面的一維數組就是它的元素,arr
就是這個以一維數組作為元素的數組的首地址,因此,arr每加一個單位,就是移動一個元素的位置。
*/
  • 檢驗一下,在下面的代碼中,請說出常量指針是誰?指針數組又是誰?
const char *names[] = {"Alice", "Bob", "Charlie"}; 
// 在這個代碼中,指針數組是names,其中存放的都是常量指針

4、函數指針:

  • 類比數組指針記憶,數組指針是指向整個數組的指針,函數指針就是指向整個函數的指針。
  • 函數指針的定義
#include<stdio.h>int add(int a, int b) {return a + b;
}
int sub(int a, int b) {return a - b;
}
int main() {// 定義函數指針int (*Add)(int, int);Add = add;int (*Sub)(int, int);Sub = sub;// 借助指針調用printf("%d\n", Add(1, 2)); // 3printf("%d\n", Sub(3, 1)); // 2typedef int(*Func)(int, int);  // 使用了這個重命名之后,就相當于用AddFunc代替了 int 函數名 ( int 參數1名, int 參數2名)   這種類型的指針名Func p1 = add;Func p2 = sub;printf("%d\n", p1(2, 1)); // 3printf("%d", p2(2, 1)); // 1return 0;
}
  • 在給函數指針類型用typedef 起別名的時候,發現對typedef起別名時,簡單的類型還好寫,這種復雜類型寫起來就比較吃力了。因此總結如下:

1、給基本數據類型創建別名:typedef int Integer; // 為int起別名Integer
2、為指針類型創建別名:typedef int* IntPtr; // 為int*起別名IntPtr
3、為數組類型創建別名:typedef int IntArray5[5]; // 為數組類型創建別名(表示"包含5個int元素的數組")。例如:IntArray5 arr = {1, 2, 3, 4, 5}; // 等價于 int arr[5] = {1,2,3,4,5};
4、 ★為函數指針創建別名(最常用場景之一):typedef int (*CalcFunc)(int, int); // 定義一個函數指針類型(接收兩個int,返回int)
CalcFunc func1 = add; // 假設add是已經定義好的函數,func1是CalcFunc類型的函數指針;
上述函數指針其實就相當于:int (*func1)(int, int) = add;
- 總結:在給數組或者函數指針起別名的時候,方法就和定義數組或者定義函數時寫法一樣,這兩種相對其他數據類型起別名都比較特殊一點。

  • 函數指針的用途之一:回調函數
    • 回調函數就是往函數當中通過函數指針作為形參傳遞函數
    • 我在學習回調函數的時候產生過這樣的疑問,為什么明明可以在一個函數當中就調用另一個函數,還非得用回調函數?經過學習我想通了,函數調用函數固然可以,但每次都是調用固定函數,而采用回調函數,就可以動態選擇傳入函數當中的函數。
#include<stdio.h>
// 調用函數的函數:作用是判斷數組當中有幾個1
int oneNum(int(*arr), int len, int (*Fun)(int)) {int i, count = 0;for (i = 0; i < len; i++) {if (Fun(arr[i])) {count += 1;}}return count;
}// 被調用的函數:作為條件判斷當前值是否為1
int oneNo(int a)
{if (a == 1) {return 1;}else {return 0;}
}
// c語言標準庫中快速排序qsort的回調函數
int cmp(void const * a, const void* b) {//return *(int const*)a - *(int const*)b; // 升序排列return *(int const*)b - *(int const*)a; // 降序排列
}int main() {int a[5] = { 1,5,4,2,3 };printf("%d\n", oneNum(a, 5, oneNo)); // 3qsort(a, 5,sizeof(int) , cmp);int i;for (i = 0; i < 5; i++) {printf("%d  ", a[i]);}return 0;
}

總結:

  • ★指針往細節學習,就會發現每個指針的大小都是固定的,一般電腦如果是64位的,指針大小就是64位(8個字節),如果電腦是32位的,指針大小就是32位(4個字節),這是因為指針存放的地址。指針前面的類型表示的是這個指針指向的空間當中存放的是什么類型,聲明指針類型,就是讓程序明白這一點,順著指針地址過去取值的時候取多大的空間,也就清清楚楚的告訴程序了。
  • 數組指針&指針數組,其本質就是哪個詞在后面它的本質就是什么。
  • 函數指針是個挺好用的東西,有了函數指針,我們就可以使用回調函數,向函數當中傳遞函數了。

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

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

相關文章

單變量單步時序預測:CNN-GRU卷積神經網絡結合門控循環單元

目錄預測效果1. **CNN-GRU的基本原理**2. **應用場景**3. **模型結構與實現**4. **優勢與挑戰**5. **相關研究與實現**6. **未來發展方向**結論代碼設計預測效果 CNN-GRU卷積神經網絡結合門控循環單元是一種結合了卷積神經網絡&#xff08;CNN&#xff09;和門控循環單元&#…

MonoFusion 與 Genie 3

卡內基梅隆大學的研究者發明了一種叫 MonoFusion 的新技術&#xff0c;它能用很少的普通相機&#xff08;比如4個&#xff09;&#xff0c;就能拍出像電影特效一樣細膩流暢的動態3D場景&#xff08;4D重建&#xff09;&#xff0c;比如彈鋼琴、修自行車這種復雜動作&#xff0c…

kubernets命令行創建Token并附加權限給dashboard控制臺登錄

1、創建登錄token kubectl create token default -n graph-node-test dgjeojrgopejgeropjgpsdjgerjglsdjfsjogjeojgeorjgortlfhj4yu493460uwperg3wef;lsj2y3r934tnrhifrlfe9t4h5tlhobdrmlgw485tw4yp653ut9ogogjerolj4w9erjgotj3fgjletyj49yr20o359truyo5u6908430jt5grjsdtgj49…

什么是SpringBoot

題目詳細答案Spring Boot 是由 Pivotal 團隊提供的一個基于 Spring 框架的項目&#xff0c;它旨在簡化 Spring 應用的開發和部署。Spring Boot 通過提供一系列的約定和開箱即用的功能&#xff0c;使得開發者可以更快地構建獨立的、生產級的 Spring 應用程序&#xff0c;而無需進…

從零開始設計一個分布式KV存儲:基于Raft的協程化實現

從零開始設計一個分布式KV存儲&#xff1a;基于Raft的協程化實現 本文將以一個最小可運行的分布式KV系統為例&#xff0c;帶你拆解如何用C、Raft算法和協程模型構建高可用的Key-Value存儲。 一、為什么需要分布式KV&#xff1f; 單機KV&#xff08;如Redis&#xff09;存在單點…

虛擬機或docker的ubuntu無界面安裝完成后鏡像源設置

ubuntu系統源 在裝好虛擬機或者docker鏡像后&#xff0c;直接使用apt update && apt upgrade是無法完更新的。 此時系統中也沒有vim工具&#xff0c;我們可以在清華源的網站中找到幫助文檔。mirrors.tuna.tsinghua.edu.cn/help/ubuntu/為了避免沖突&#xff0c;我們使用…

串口通信02 溫度傳感DS18B20 01 day49

九&#xff1a;串口通信 通信&#xff1a;無線和有線 ? 單工 半雙工 全雙工 并行&#xff1a;多個數據線 串行&#xff1a;一根數據線 同步&#xff1a;通信雙方使用同一個時鐘&#xff0c;SPI信息幀&#xff0c;有CLK引腳 異步&#xff1a;通信雙方使用不同時鐘&#xff0c;雙…

【FreeRTOS 】任務通知

FreeRTOS 任務通知任務通知簡介一 、發送通知1.1 xTaskNotify()1.2 xTaskNotifyFromISR()1.3 xTaskNotifyGive()1.4 xTaskNotifyAndQuery()1.5 xTaskNotifyAndQueryFromISR()二、接收通知2.1 ulTaskNotifyTake()2.2 xTaskNotifyWait()三、清除通知狀態和值3.1 xTaskNotifyState…

Android視圖狀態以及重繪

一、視圖狀態&#xff08;View States&#xff09;1. 五種核心狀態狀態作用修改方法特點enabled視圖是否響應交互setEnabled(boolean)禁用狀態下不響應onTouch事件focused視圖是否獲得焦點requestFocus()需同時滿足focusable和focusableInTouchModewindow_focused視圖所在窗口是…

vue3接收SSE流數據進行實時渲染日志

后端使用的是 Spring Boot WebFlux&#xff08;響應式編程框架&#xff09;&#xff0c;并且返回的是 Server-Sent Events (SSE) 流式數據&#xff0c;那么在 Vue3 中&#xff0c;需要使用 EventSource API 或 fetch 流式讀取 來正確獲取響應內容。方案 1&#xff1a;使用 Eve…

每日五個pyecharts可視化圖表-bars(6)

探索pyecharts庫中條形圖的高級用法與定制技巧 在數據可視化中&#xff0c;條形圖是最常用的圖表類型之一&#xff0c;它能夠清晰地展示不同類別之間的數量對比。今天&#xff0c;我們將繼續學習如何使用pyecharts創建5種不同風格的條形圖。pyecahts源碼 圖表1&#xff1a;帶…

【C語言】文件操作全解析

文章目錄一、為什么需要文件操作&#xff1f;二、認識文件&#xff1a;不止是磁盤上的存儲2.1 程序文件2.2 數據文件2.3 文件名的構成三、文本文件與二進制文件&#xff1a;數據的兩種形態3.1 存儲方式差異3.2 實例對比&#xff1a;整數10000的存儲3.3 二進制文件操作示例四、文…

C結構體的幾種定義形式 + typedef結合使用的好處

struct 語句定義了一個包含多個成員的新的數據類型&#xff0c;struct 語句的格式如下&#xff1a; struct tag { member-list member-list member-list ... } variable-…

SPICE電容矩陣

SPICE電容矩陣: 如果有許多條傳輸線,就可以用下標來標記每一條線。例如,如果有5條線,就用1~5分別標記,依慣例把返回路徑導體標記為導線0。圖10.6給出了5條導線和一個公共返回平面的橫截面圖。首先研究電容器元件,下一節再討論電感器元件。 在這個線的集合中,每對導線之間…

【Java】棧和隊列

文章目錄1.棧1.1 棧的定義1.2 棧的使用1.3 棧的模擬實現2.隊列2.1 隊列的定義2.2 隊列的使用2.3 隊列的模擬實現3.循環隊列3.1 循環隊列的概念3.2 循環隊列判斷空和滿4.雙端隊列Deque1.棧 1.1 棧的定義 棧是一種特殊的線性表&#xff0c;其只允許在固定的一段進行數據的插入或…

【性能測試】---測試工具篇(jmeter)

目錄 1、安裝并啟動jemeter 2、重點組件 2.1、線程組&#xff1a; 2.2、HTTP取樣器?編輯 2.3、查看結果樹 2.4、HTTP請求默認值 2.5、HTTP信息頭管理器 2.6、JSON提取器 2.7、JSON斷言 2.8、同步定時器 2.9、CSV數據文件設置 2.10、HTTP Cookie管理器 3、測試報告…

機器學習(12):拉索回歸Lasso

- 拉索回歸可以將一些權重壓縮到零&#xff0c;從而實現特征選擇。這意味著模型最終可能只包含一部分特征。 - 適用于特征數量遠大于樣本數量的情況&#xff0c;或者當特征間存在相關性時&#xff0c;可以從中選擇最相關的特征。 - 拉索回歸產生的模型可能更簡單&#xff0c;因…

Redis持久化存儲

Redis持久化存儲詳解 一、核心持久化機制 Redis提供兩種主要持久化方式&#xff1a;RDB&#xff08;快照&#xff09; 和 AOF&#xff08;追加文件&#xff09;&#xff0c;以及兩者的混合模式。 RDB&#xff08;Redis Database&#xff09;快照持久化 工作原理 RDB通過創建數據…

python學智能算法(三十四)|SVM-KKT條件回顧

【1】引言 前序學習進程中&#xff0c;對軟邊界拉格朗日方程進行了初步構建。 其中約定了兩個拉格朗日乘子要非負&#xff0c;其本質是要滿足KKT條件。 今天就乘此次機會&#xff0c;在回顧一下KKT條件。 【2】定義 當問題無約束的時候&#xff0c;只要讓函數梯度為零&#…

【網絡基礎】計算機網絡發展背景及傳輸數據過程介紹

本文旨在幫助初學者建立起計算機網絡的基礎認知&#xff0c;從網絡的發展背景到網絡協議的分層模型&#xff0c;再到IP與MAC地址的基本概念&#xff0c;全面覆蓋第一階段學習重點。 &#x1f4cc; 本節重點 了解計算機網絡的發展背景&#xff0c;掌握局域網&#xff08;LAN&am…