C語言進階--動態內存管理

學習數據結構重要的三個部分:指針、結構體、動態內存管理(malloc、calloc、realloc、free)。

1.為什么存在動態內存分配?

1.空間開辟大小是固定的;

2.數組在聲明時,必須指定數組的長度,它所需要的內存在編譯時分配。

int main()
{int a = 10; //4個字節int arr[10]; //40個字節return 0;
}

2.動態內存函數的介紹

2.1malloc和free

C語言提供了一個動態內存開辟的函數:malloc()

這個函數向內存申請一塊連續可用的空間,并返回指向這塊空間的指針。

  • 如果開辟成功,則返回一個指向開辟好的空間的指針;
  • 如果開辟失敗,則返回一個NULL指針。因此malloc的返回值一定要做檢查;
  • 返回值的類型是void*,所以malloc函數并不知道開辟空間的類型,具體在使用的時候使用者自己來決定。
  • 如果參數size為0,malloc的行為是標準未定義的,取決于編譯器。

在這里插入圖片描述

#include <stdlib.h>
#include <string.h>
#include <errno.h>int main()
{int arr[10] = {0};//動態內存開辟int* p = (int*)malloc(40);if (p == NULL){printf("%s\n", strerror(errno));return 1;}//使用int i = 0;for (i = 0; i < 10; i++){*(p+i) = i;}//打印for (i = 0; i < 10; i++){printf("%d ", *(p+i)); //0 1 2 3 4 5 6 7 8 9 }return 0;
}

在這里插入圖片描述

#include <stdlib.h>
#include <string.h>
#include <errno.h>int main()
{int arr[10] = {0};//動態內存開辟int* p = (int*)malloc(INT_MAX);if (p == NULL){printf("%s\n", strerror(errno));return 1;}//使用int i = 0;for (i = 0; i < 10; i++){*(p+i) = i;}//打印for (i = 0; i < 10; i++){printf("%d ", *(p+i)); //0 1 2 3 4 5 6 7 8 9 }return 0;
}
free(動態內存的釋放和回收)
void free(void* ptr)
  • 如果參數ptr指向的空間不是動態開辟的,那么free函數的行為是未定義的;
  • 如果參數ptr是NULL指針,則函數什么事情也不做。

沒有free,并不是說內存空間就不回收了。當程序退出時,系統會自動回收內存空間。

#include <stdlib.h>
#include <string.h>
#include <errno.h>int main()
{int arr[10] = {0};//動態內存開辟int* p = (int*)malloc(40);if (p == NULL){printf("%s\n", strerror(errno));return 1;}//使用int i = 0;for (i = 0; i < 10; i++){*(p+i) = i;}//打印for (i = 0; i < 10; i++){printf("%d ", *(p+i)); //0 1 2 3 4 5 6 7 8 9 }free(p);p = NULL; //最好的做法,free釋放后,賦值為NULLreturn 0;
}

在這里插入圖片描述

//error,必須釋放動態開辟的空間
int main()
{int a = 10;int* p = &a;//···free(p); p = NULL;return 0;
}
int main()
{int* p = NULL;free(p); //啥事也不干return 0;
}

2.2calloc

在這里插入圖片描述

#include <stdlib.h>
#include <string.h>
#include <errno.h>//開辟10個整型的空間
int main()
{int* p = (int*)calloc(10, sizeof(int));if (p == NULL){printf("%s\n", strerror(errno));return 1;}//打印int i = 0;for (i = 0; i < 10; i++){printf("%d ", *(p+i)); //calloc會初始化為全0}//釋放free(p);p = NULL;return 0;
}

可以理解為:calloc=malloc+memset

2.3realloc

在這里插入圖片描述
在這里插入圖片描述

#include <stdlib.h>
#include <string.h>
#include <errno.h>int main()
{int* p = (int*)malloc(40);if (NULL == p){printf("%s\n", strerror(errno));return 1;}//使用1 2 3 4 5 6 7 8 9 10int i = 0;for (i = 0; i < 10; i++){*(p+i) = i+1;}//擴容int* ptr = (int*)realloc(p, 80);if (ptr != NULL)  //擴容成功{p = ptr;}//打印for (i = 0; i < 10; i++){printf("%s\n", *(p+i)); //1 2 3 4 5 6 7 8 9 10}//釋放free(p);p = NULL;return 0;
}

第一種情況:
在這里插入圖片描述
在這里插入圖片描述
第二種情況:
在這里插入圖片描述

int main()
{realloc(NULL, 40); //等價于malloc(40);return 0;
}

練習:動態版本的通訊錄

動態版本的通訊錄:
1.通訊錄默認能存放3個人的信息
2.如果空間不夠了,就增加空間,每次增加2個人的空間

//test.c
#define _CRT_SECURE_NO_WARNINGS #include "contact.h"void menu()
{printf("*************************************************\n");printf("************1.add     2.del**********************\n");printf("************3.search  4.modify*******************\n");printf("************5.show    6.sort*********************\n");printf("************     0.exit    **********************\n");printf("*************************************************\n");
}
int main()
{int input = 0;Contact con; //通訊錄InitContact(&con); //初始化通訊錄do{menu();printf("請選擇:>");scanf("%d", &input);switch (input){case 1:AddContact(&con);break;case 2:DelContact(&con);break;case 3:SearchContact(&con);break;case 4:ModifyContact(&con);break;case 5:ShowContact(&con);break;case 6:SortContact(&con);break;case 0:DestroyContact(&con); //動態版本,銷毀通訊錄printf("退出通訊錄\n");break;default:printf("選擇錯誤\n");break;}} while (input);return 0;
}
//contact.h
#pragma once#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>#define DEFAULT_SZ 3
#define INC_SZ 2#define MAX 100
#define MAX_NAME 20
#define MAX_SEX 10
#define MAX_TELE 12
#define MAX_ADDR 30//類型的聲明
typedef struct PeoInfo  //人的信息
{char name[MAX_NAME];int age;char sex[MAX_SEX];char tele[MAX_TELE];char addr[MAX_ADDR];
}PeoInfo;//靜態版本
//typedef struct Contact //通訊錄的信息
//{
//	PeoInfo data[MAX]; //存放人的信息
//	int count; //記錄當前通訊錄中實際人的個數
//}Contact;//動態版本
typedef struct Contact //通訊錄的信息
{PeoInfo* data; //存放人的信息int count; //記錄當前通訊錄中實際人的個數int capacity;//記錄當前通訊錄的容量
}Contact;int InitContact(Contact* pc); //初始化通訊錄void DestroyContact(Contact* pc); //動態版本,銷毀通訊錄void AddContact(Contact* pc); //添加聯系人的通訊錄void ShowContact(const Contact* pc); //打印通訊錄的信息void DelContact(Contact* pc); //刪除指定聯系人void SearchContact(Contact* pc); //查找指定聯系人void ModifyContact(Contact* pc); //修改指定聯系人void SortContact(Contact* pc); //排序通訊錄中的內容,可以按照名字來排序,按照年齡來排序···
//contact.c
#define _CRT_SECURE_NO_WARNINGS #include "contact.h"//靜態版本
//void InitContact(Contact* pc)
//{
//	assert(pc);
//	pc->count = 0;
//	memset(pc->data, 0, sizeof(pc->data));
//}//動態版本
int InitContact(Contact* pc)
{assert(pc);pc->count = 0;pc->data = (PeoInfo*)calloc(3, sizeof(PeoInfo));if (pc->data == NULL){printf("InitContact::%s\n", strerror(errno));return 1;}pc->capacity = DEFAULT_SZ;return 0;
}void DestroyContact(Contact* pc) //動態版本,銷毀通訊錄
{assert(pc);free(pc->data);pc->data = NULL;
}//靜態版本
//void AddContact(Contact* pc)
//{
//	assert(pc);
//	if (pc->count == MAX)
//	{
//		printf("通訊錄已滿,無法添加\n");
//		return;
//	}
//	printf("請輸入名字:>");
//	scanf("%s", pc->data[pc->count].name);
//	printf("請輸入年齡:>");
//	scanf("%d", &(pc->data[pc->count].age));
//	printf("請輸入性別:>");
//	scanf("%s", pc->data[pc->count].sex);
//	printf("請輸入電話:>");
//	scanf("%s", pc->data[pc->count].tele);
//	printf("請輸入地址:>");
//	scanf("%s", pc->data[pc->count].addr);
//
//	pc->count++;
//	printf("添加成功\n");
//}//動態版本
void CheckCapacity(Contact* pc)
{if (pc->count == pc->capacity){PeoInfo* ptr = (PeoInfo*)realloc(pc->data, (pc->capacity + INC_SZ)*sizeof(PeoInfo));if (ptr == NULL){printf("AddContact::%s\n", strerror(errno));return;}else{pc->data = ptr;pc->capacity += INC_SZ;printf("擴容成功\n");}}
}
void AddContact(Contact* pc)
{assert(pc);//擴容CheckCapacity(pc);printf("請輸入名字:>");scanf("%s", pc->data[pc->count].name);printf("請輸入年齡:>");scanf("%d", &(pc->data[pc->count].age));printf("請輸入性別:>");scanf("%s", pc->data[pc->count].sex);printf("請輸入電話:>");scanf("%s", pc->data[pc->count].tele);printf("請輸入地址:>");scanf("%s", pc->data[pc->count].addr);pc->count++;printf("添加成功\n");
}void ShowContact(const Contact* pc)
{assert(pc);int i = 0;printf("%-20s\t%-5s\t%-5s\t%-12s\t%-30s\n", "名字", "年齡", "性別", "電話", "地址");for (i = 0; i < pc->count; i++){printf("%-20s\t%-5d\t%-5s\t%-12s\t%-30s\n", pc->data[i].name,pc->data[i].age,pc->data[i].sex,pc->data[i].tele,pc->data[i].addr);}
}static int FindByName(Contact* pc, char name[])
{assert(pc);int i = 0;for (i = 0; i < pc->count; i++){if (0 == strcmp(pc->data[i].name, name)){return i;}}return -1;
}void DelContact(Contact* pc)
{char name[MAX_NAME] = { 0 };assert(pc);int i = 0;if (pc->count == 0){printf("通訊錄為空,沒有信息可以刪除\n");return;}printf("請輸入要刪除人的名字:>");scanf("%s", name);//刪除//1.查找int pos = FindByName(pc, name);if (pos == -1){printf("要刪除的人不存在\n");return;}//2.刪除for (i = pos; i < pc->count - 1; i++){pc->data[i] = pc->data[i + 1];}pc->count--;printf("刪除成功\n");}void SearchContact(Contact* pc)
{assert(pc);char name[MAX_NAME] = { 0 };printf("請輸入要查找人的名字:>");scanf("%s", name);//1.查找int pos = FindByName(pc, name);if (pos == -1){printf("要查找的人不存在\n");return;}//2.打印printf("%-20s\t%-5s\t%-5s\t%-12s\t%-30s\n", "名字", "年齡", "性別", "電話", "地址");printf("%-20s\t%-5d\t%-5s\t%-12s\t%-30s\n", pc->data[pos].name,pc->data[pos].age,pc->data[pos].sex,pc->data[pos].tele,pc->data[pos].addr);}void ModifyContact(Contact* pc)
{assert(pc);char name[MAX_NAME] = { 0 };printf("請輸入要修改人的名字:>");scanf("%s", name);//1.查找int pos = FindByName(pc, name);if (pos == -1){printf("要修改的人不存在\n");return;}printf("要修改的人的信息已經找到,接下來開始修改\n");//2.修改printf("請輸入名字:>");scanf("%s", pc->data[pos].name);printf("請輸入年齡:>");scanf("%d", &(pc->data[pos].age));printf("請輸入性別:>");scanf("%s", pc->data[pos].sex);printf("請輸入電話:>");scanf("%s", pc->data[pos].tele);printf("請輸入地址:>");scanf("%s", pc->data[pos].addr);printf("修改成功\n");
}int cmp_peo_by_name(const void* e1, const void* e2)
{return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name);
}
void SortContact(Contact* pc) //按照名字來排序
{assert(pc);qsort(pc->data, pc->count, sizeof(PeoInfo), cmp_peo_by_name);printf("排序成功\n");
}

3.常見的動態內存錯誤

3.1對NULL指針的解引用操作

#include <stdlib.h>
int main()
{int* p = (int*)malloc(40);*p = 20; //如果p的值是NULL,就會出現問題return 0;
}

修正:

#include <stdlib.h>int main()
{int* p = (int*)malloc(40);if (p == NULL){return 1;}*p = 20;free(p);p = NULL;return 0;
}

3.2對動態開辟空間的越界訪問

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>int main()
{int* p = (int*)malloc(40);if (p == NULL){printf("%s\n", strerror(errno));return 1;}//使用int i = 0;for (i = 0; i <= 10; i++)  {p[i] = i;}free(p);p = NULL;return 0;
}

3.3對非動態開辟內存使用free釋放

#include <stdlib.h>int main()
{int a = 10;int* p = &a;//···free(p);p = NULL;return 0;
}

在這里插入圖片描述

3.4使用free釋放一塊動態開辟內存的一部分

#include <stdlib.h>int main()
{int* p = (int*)malloc(40);if (p == NULL){return 1;}//使用int i = 0;for (i = 0; i < 5; i++){*p = i;p++;}//釋放free(p); //此時p的地址早已不是起始位置,必須指向開辟空間的起始位置p = NULL;return 0;
}

在這里插入圖片描述

3.5對同一塊動態內存多次釋放

#include <stdlib.h>int main()
{int* p = (int*)malloc(40);//···free(p);//···free(p);return 0;
}

在這里插入圖片描述
修正:

#include <stdlib.h>int main()
{int* p = (int*)malloc(40);//···free(p);p = NULL;//···free(p);return 0;
}

3.6動態開辟內存忘記釋放(內存泄漏)

#include <stdlib.h>void test()
{int* p = (int*)malloc(100);//···int flag = 0;scanf("%d", &flag); //5if (flag == 5)return;free(p);p = NULL;
}int main()
{test();//···return 0;
}

場景2:

#include <stdlib.h>int* test()
{int* p = (int*)malloc(100);  //開辟空間if (p == NULL){return p;}//···return p;
}int main()
{int* ret = test();//忘記釋放了return 0;
}

4.經典筆試題(出自《高質量的C-C++編程》)

在這里插入圖片描述
修改版本2:(有點別扭)

在這里插入圖片描述

#include <stdio.h>
int main()
{printf("hello world\n"); //hello worldchar* p = "hello world";printf(p); //hello worldprintf("%s\n", p); //hello worldreturn 0;
}//要理解底層原理,傳遞的是h字符的地址

返回棧空間地址的問題。

#include <stdio.h>int* test()
{//返回棧空間地址的問題int a = 10;return &a;
}int main()
{int* p = test();printf("%d\n", *p); //10return 0;
}
#include <stdio.h>int* test()
{int a = 10;return &a;
}int main()
{int* p = test();printf("hehe\n"); //heheprintf("%d\n", *p); //5return 0;
}

5.C/C++程序的內存開辟

自行學習。
書籍《深入理解計算機系統》

6.柔性數組(flexible array)

C99中,結構中的最后一個元素允許是未知大小的數組,這就叫做柔性數組成員。

typedef struct st_type
{int i;int a[0]; //柔性數組成員
}type_a;

有些編譯器會報錯無法編譯可以寫成:

typedef struct st_type
{int i;int a[]; //柔性數組成員
}type_a;

6.1柔性數組的特點

結構中的柔性數組成員前面必須至少一個其它成員;

sizeof返回的這種結構大小不包括柔性數組的內存;

包含柔性數組成員的結構用malloc()函數進行內存的動態分配,并且分配的內存應該大于結構的大小,以上適應柔性數組的預期大小。

#include <stdio.h>struct S
{int n;int arr[]; //柔性數組成員
};int main()
{int sz = sizeof(struct S);printf("%d\n", sz); //4      return 0;
}

6.2柔性數組的使用

#include <stdio.h>
#include <stdlib.h>struct S
{int n;int arr[]; //柔性數組成員
};int main()
{//柔性數組的使用struct S* ps = (struct S*)malloc(sizeof(struct S) + 40);if (ps == NULL){//···return 1;}ps->n = 100;int i = 0;for (i = 0; i < 10; i++){ps->arr[i] = i;}//打印for (i = 0; i < 10; i++){printf("%d ", ps->arr[i]);}//調整struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 80);if (ptr != NULL){ps = ptr;ptr = NULL;}//···//釋放free(ps);ps = NULL;return 0;
}

方案二:

#include <stdio.h>
#include <stdlib.h>
struct S
{int n;int* arr;
};int main()
{struct S* ps =(struct S*)malloc(sizeof(struct S));if (ps == NULL){return 1;}ps->n = 100;ps->arr = (int*)malloc(40);if (ps->arr == NULL){return 1;}//使用int i = 0;for (i = 0; i < 10; i++){ps->arr[i] = i;}//打印for (i = 0; i < 10; i++){printf("%d ", ps->arr[i]);}//調整int* ptr = (int*)realloc(ps->arr, 80);if (ptr == NULL){return 1;}//釋放free(ps->arr);free(ps);ps = NULL;return 0;
}

方案二:釋放時容易忘記導致內存泄漏;多次開辟空間,碎片化嚴重,導致內存利用率下降。

6.3柔性數組的優勢

方便內存釋放;

理論上有利于訪問速度(連續的內存有利于訪問速度,減少內存碎片)。

總結

今天就暫且更新至此吧,期待下周再會。如有錯誤還請不吝賜教。希望對您學習有所幫助,翻頁前留下你的支持,以防下次失蹤了嗷。

作者更新不易,免費關注別手軟。

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

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

相關文章

C# 密封類和密封方法

密封(sealed)是C#中用于限制繼承和多態行為的關鍵字&#xff0c;它可以應用于類和方法&#xff0c;提供了一種控制繼承層次的方式。 密封類 特點 使用 sealed 關鍵字修飾的類密封類不能被其他類繼承&#xff0c;但可以繼承其他類或接口主要用于防止派生所有結構(struct)都是…

thinkpad T-440p 2025.05.31

thinkpad T-440p 2025.05.31 老了退休了&#xff0c;說起來真的可惡現在筆記本的設計師&#xff0c;只有固態硬盤了

WPS自動換行

換行前 換行后 快捷鍵 第一步&#xff1a;啟用「自動換行」功能 選中目標單元格/區域&#xff1a;點擊需要設置的單元格&#xff08;或拖動選中多個單元格&#xff09;。開啟自動換行&#xff08;3種方式任選&#xff09;&#xff1a; 快捷按鈕&#xff1a;在頂部菜單欄點擊「…

cuda_fp8.h錯誤

現象&#xff1a; cuda_fp8.h錯誤 原因&#xff1a; CUDA Toolkit 小于11.8,會報fp8錯誤&#xff0c;因此是cuda工具版本太低。通過nvcc --version查看 CUDA Toolkit 是 NVIDIA 提供的一套 用于開發、優化和運行基于 CUDA 的 GPU 加速應用程序的工具集合。它的核心作用是讓開發…

【TTS】基于GRPO的流匹配文本到語音改進:F5R-TTS

論文地址&#xff1a;https://arxiv.org/abs/2504.02407v3 摘要 我們提出了F5R-TTS&#xff0c;這是一種新穎的文本到語音(TTS)系統&#xff0c;它將群體相對策略優化(GRPO)集成到基于流匹配的架構中。 通過將流匹配TTS的確定性輸出重新表述為概率高斯分布&#xff0c;我們的方…

頭歌java課程實驗(Java面向對象 - 包裝類)

第1關&#xff1a;基本數據類型和包裝類之間的轉換 任務描述 本關任務&#xff1a;實現基本數據類型與包裝類之間的互相轉換。 相關知識 為了完成本關任務&#xff0c;你需要掌握&#xff1a; 1.什么是包裝類&#xff1b; 2.怎么使用包裝類。 什么是包裝類 在JAVA中&#x…

實現一個免費可用的文生圖的MCP Server

概述 文生圖模型為使用 Cloudflare Worker AI 部署 Flux 模型&#xff0c;是參照視頻https://www.bilibili.com/video/BV1UbkcYcE24/?spm_id_from333.337.search-card.all.click&vd_source9ca2da6b1848bc903db417c336f9cb6b的復現Cursor MCP Server實現是參照文章https:/…

ES6 深克隆與淺克隆詳解:原理、實現與應用場景

ES6 深克隆與淺克隆詳解&#xff1a;原理、實現與應用場景 一、克隆的本質與必要性 在 JavaScript 中&#xff0c;數據分為兩大類型&#xff1a; 基本類型&#xff1a;Number、String、Boolean、null、undefined、Symbol、BigInt引用類型&#xff1a;Object、Array、Functio…

新聞數據加載(鴻蒙App開發實戰)

本案例基于ArkTS的聲明式開發范式&#xff0c;介紹了數據請求和onTouch事件的使用。包含以下功能&#xff1a; 數據請求。列表下拉刷新。列表上拉加載。 網絡數據請求需要權限&#xff1a;ohos.permission.INTERNET 一、案例效果截圖 操作說明&#xff1a; 點擊應用進入主頁…

辦公效率王Word批量轉PDF 50 +文檔一鍵轉換保留原格式零錯亂

各位辦公小能手們&#xff0c;我跟你們說啊&#xff01;在辦公的時候&#xff0c;咱經常會碰到要把一堆Word文檔轉成PDF格式的情況&#xff0c;比如說要統一文件格式、保護文檔內容或者方便分享啥的。這時候&#xff0c;就需要用到Word批量轉換成PDF的軟件啦。下面我就給你們好…

一張Billing項目的流程圖

流程圖 工作記錄 2016-11-11 序號 工作 相關人員 1 修改Payment Posted的導出。 Claim List的頁面加了導出。 Historical Job 加了Applied的顯示和詳細。 郝 識別引擎監控 Ps (iCDA LOG :剔除了160篇ASG_BLANK之后的結果): LOG_File 20161110.txt BLANK_CDA/ALL 45/10…

SpringAI系列4: Tool Calling 工具調用 【感覺這版本有bug】

前言&#xff1a;在最近發布的 Spring AI 1.0.0.M6 版本中&#xff0c;其中一個重大變化是 Function Calling 被廢棄&#xff0c;被 Tool Calling 取代。Tool Calling工具調用&#xff08;也稱為函數調用&#xff09;是AI應用中的常見模式&#xff0c;允許模型通過一組API或工具…

第六十三節:深度學習-模型推理與后處理

深度學習模型訓練完成后,如何高效地將其部署到實際應用中并進行準確預測?這正是模型推理與后處理的核心任務。OpenCV 的 dnn 模塊為此提供了強大支持,本文將深入探討 OpenCV 在深度學習模型推理與后處理中的關鍵技術與實踐。 第一部分:基礎概念與環境搭建 1.1 核心概念解析…

uniapp開發企業微信小程序時 wx.qy.login 在uniapp中使用的時候,需要導包嗎?

在 UniApp 中使用 “wx.qy.login” 不需要手動導包&#xff0c;但需要滿足以下條件&#xff1a; 一、環境要求與配置 1&#xfffd; 企業微信環境判斷 必須確保當前運行環境是企業微信客戶端&#xff0c;通過 “uni.getSystemInfoSync().environment” 判斷是否為 “wxwork”…

ONLYOFFICE文檔API:更強的安全功能

在數字化辦公時代&#xff0c;文檔的安全性與隱私保護已成為企業和個人用戶的核心關切。如何確保信息在存儲、傳輸及協作過程中的安全&#xff0c;是開發者與IT管理者亟需解決的問題。ONLYOFFICE作為一款功能強大的開源辦公套件&#xff0c;不僅提供了高效的文檔編輯與協作體驗…

Linux系統編程之共享內存

概述 在Linux系統中&#xff0c;共享內存也是一種高效的進程間通信機制&#xff0c;允許兩個或多個進程共享同一塊物理內存區域。通過這種方式&#xff0c;不同進程可以直接訪問和操作相同的數據&#xff0c;從而避免了數據的復制。由于數據直接在內存中共享&#xff0c;沒有額…

零知開源——STM32F407VET6驅動Flappy Bird游戲教程

簡介 本教程使用STM32F407VET6零知增強板驅動3.5寸TFT觸摸屏實現經典Flappy Bird游戲。通過觸摸屏控制小鳥跳躍&#xff0c;躲避障礙物柱體&#xff0c;挑戰最高分。項目涉及STM32底層驅動、圖形庫移植、觸摸控制和游戲邏輯設計。 目錄 簡介 一、硬件準備 二、軟件架構 三、…

Elasticsearch創建快照倉庫報錯處理

創建快照倉庫報錯&#xff1a; 根據報錯提示的信息&#xff0c;問題可能出在 Elasticsearch 的配置中。當你嘗試創建一個文件系統&#xff08;fs&#xff09;類型的快照倉庫時&#xff0c;雖然已經指定了 location 參數&#xff0c;但 Elasticsearch 仍然報錯&#xff0c;這通…

服務器如何配置防火墻管理端口訪問?

配置服務器防火墻來管理端口訪問&#xff0c;是保障云服務器安全的核心步驟。下面我將根據你使用的不同操作系統&#xff08;Linux: Ubuntu/Debian/CentOS&#xff1b;Windows Server&#xff09;介紹常用防火墻配置方法。 ? 一、Linux 防火墻配置&#xff08;UFW / firewalld…

Redis最佳實踐——安全與穩定性保障之連接池管理詳解

Redis 在電商應用的連接池管理全面詳解 一、連接池核心原理與架構 1. 連接池工作模型 #mermaid-svg-G7I3ukCljlJZAXaA {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-G7I3ukCljlJZAXaA .error-icon{fill:#552222;}…