嵌入式(C语言篇)Day11
嵌入式Day11
一、动态内存分配核心函数
(一)函数列表
函数名 | 功能 | 头文件 | 返回值 |
---|---|---|---|
malloc | 分配连续的size 字节堆内存 | stdlib.h | 成功返回首地址(void* ),失败返回NULL |
calloc | 分配num 个元素×size 字节/元素的堆内存,自动初始化为0 | stdlib.h | 成功返回首地址(void* ),失败返回NULL |
realloc | 调整已分配内存块的大小 | stdlib.h | 成功返回新首地址(void* ),失败返回NULL |
free | 释放堆内存块 | stdlib.h | 无 |
(二)通用注意事项
- 返回值处理
- 必须用指针接收返回值,推荐显式强转(如
(int*)malloc(...)
)。 - 严格平台(如C++)必须显式强转,普通C环境可隐式转换。
- 必须用指针接收返回值,推荐显式强转(如
- 错误处理
- 分配函数(
malloc/calloc/realloc
)调用后必须判断返回值是否为NULL
,失败时需进行错误处理(如打印日志、退出程序)。
- 分配函数(
- 内存释放
- 不再使用的内存块必须用
free
释放,否则导致内存泄漏。 free
参数必须是分配函数返回的原始指针,不可是移动后的指针。- 释放后建议将指针置为
NULL
,避免野指针问题。
- 不再使用的内存块必须用
二、核心函数详解
(一)malloc
:基础内存分配
void* malloc(size_t size); // 分配size字节的连续堆内存
特点
- 参数:仅一个参数
size
,表示目标内存块的字节大小。 - 返回值:成功返回首地址,失败返回
NULL
。 - 初始化:分配的内存块未初始化,包含随机值,使用前需手动初始化。
示例
int* p = (int*)malloc(5 * sizeof(int)); // 分配5个int的数组
if (p == NULL) {perror("malloc failed");exit(1);
}
// 使用后释放
free(p);
p = NULL; // 置空避免野指针
(二)free
:内存释放
void free(void* ptr); // 释放ptr指向的堆内存块
注意事项
- 参数要求:必须传入分配函数返回的原始指针,否则程序可能崩溃(如传入移动后的指针)。
- 释放后处理:释放后指针变为野指针,需手动置
NULL
。 - 重复释放:同一块内存不可释放两次,否则导致程序崩溃。
扩展思考
- free是否清理数据:不清理,仅告知操作系统内存可重用,原有数据变为垃圾数据。
- MSVC平台特殊值:
- 未初始化的
malloc
内存填充0xCD
(标记“已分配未初始化”)。 - 未初始化的局部变量填充
0xCC
。 free
后的内存填充0xDD
(标记“已释放”)。
- 未初始化的
(三)calloc
:清零内存分配
void* calloc(size_t num, size_t size); // 分配num个元素×size字节/元素的内存,自动初始化为0
特点
- 参数:
num
为元素数量,size
为单个元素大小,总分配大小为num×size
字节。 - 初始化:分配的内存块自动填充为0,无需手动初始化。
- 适用场景:需要初始化为0的数组或结构体,如二叉树节点(指针自动置
NULL
)。
与malloc
对比
特性 | malloc | calloc |
---|---|---|
初始化 | 无,需手动处理 | 自动初始化为0 |
参数数量 | 1个(总字节数) | 2个(元素数×单元素大小) |
性能 | 较高(无初始化) | 较低(需清零内存) |
安全性 | 可能因未初始化导致未定义行为 | 更安全,避免随机值问题 |
(四)realloc
:内存重分配
void* realloc(void* ptr, size_t new_size); // 调整ptr指向的内存块大小为new_size字节
特殊行为
ptr=NULL
:等价于malloc(new_size)
。new_size=0
:等价于free(ptr)
,但ptr
不可为NULL
(否则无意义)。
正常行为(ptr≠NULL
且new_size≠0
)
new_size=原大小
:不操作,直接返回ptr
。new_size<原大小
:从高地址截断内存块,截断部分自动释放。new_size>原大小
:- 优先原地扩容:若原内存块后方有足够空间,直接扩展(未初始化新增区域)。
- 异地扩容:若空间不足,分配新内存块,复制原数据,释放旧块(程序员无需手动处理旧块)。
调用规范
int* p = (int*)malloc(5 * sizeof(int));
int new_size = 10;
int* temp = (int*)realloc(p, new_size); // 用临时指针接收新地址
if (temp == NULL) {free(p); // 若扩容失败,释放原内存exit(1);
}
p = temp; // 更新原始指针
// 扩容后新增区域(5~9号元素)未初始化,需手动处理
三、内存问题与优化
(一)内存泄漏 vs 内存溢出
问题 | 定义 | 后果 |
---|---|---|
内存泄漏 | 分配的内存未释放,长期积累导致可用内存减少 | 短期无明显影响,长期可能引发内存溢出 |
内存溢出 | 申请内存超过系统可用空间,或访问越界 | 程序崩溃、数据损坏 |
(二)避免内存泄漏的关键措施
- 正确调用
free
:确保传入原始指针,释放前检查指针是否被移动。 - 释放后置空指针:
free(p); p = NULL;
,避免“二次释放”和野指针。 - 谨慎修改堆指针:若需移动指针(如遍历数组),使用临时指针(如
int* temp = p;
)。 - 单一职责原则:同一内存块的分配与释放由同一函数管理,避免多函数交叉操作。
(三)性能与安全权衡
- 优先
malloc
场景:需手动赋值非0值,或对性能敏感的场景。 - 优先
calloc
场景:需初始化为0值,或对安全性要求高(如避免随机值导致的bug)。 realloc
使用建议:扩容后需手动初始化新增区域,避免使用未定义值。
四、代码示例:动态数组操作
#include <stdio.h>
#include <stdlib.h>int main() {// 分配5个int的数组(malloc)int arr_len = 5;int* p = (int*)malloc(arr_len * sizeof(int));if (p == NULL) {perror("malloc failed");exit(1);}// 手动初始化for (int i = 0; i < arr_len; i++) {p[i] = i + 1;}// 扩容至10个元素(realloc)int new_len = 10;int* temp = (int*)realloc(p, new_len * sizeof(int));if (temp == NULL) {free(p);exit(1);}p = temp;// 初始化新增区域for (int i = arr_len; i < new_len; i++) {p[i] = 0;}// 释放内存free(p);p = NULL;return 0;
}
五、动态数组Vector核心概念
(一)基本定义
-
动态数组特性:具有初始容量,可在容量不足时自动扩容,存储元素数量(
size
)随数据增减变化,实际内存容量(capacity
)按需扩展。 -
内存模型:通过结构体管理,包含以下成员:
typedef struct {ElementType *data; // 指向堆内存的数组指针int size; // 当前已存储元素数量int capacity; // 动态数组总容量 } Vector;
(二)核心操作场景
- 初始化:创建空动态数组,分配初始容量(如默认
DEFAULT_CAPACITY
)。 - 扩容:当
size == capacity
时,自动扩展容量以容纳新元素。 - 元素操作:支持末尾添加、头部插入、指定位置插入等操作,涉及元素移位和内存重分配。
六、模块化编程与头文件设计
(一)模块化编程原则
- 分模块实现:
.h
头文件:声明对外接口(函数原型、结构体定义、宏定义等)。.c
源文件:实现具体功能,包含头文件以暴露接口。
- 设计目标:低耦合、高内聚,模块间通过明确接口交互,隐藏内部实现细节。
(二)头文件内容规范
- 必含内容:
- 函数声明(如
Vector* vector_create();
)。 - 结构体类型定义(如
Vector
结构体)。 - 类型别名(如
typedef int ElementType;
)。 - 宏定义(如
#define DEFAULT_CAPACITY 10
)。
- 函数声明(如
- 禁止内容:
- 函数具体实现代码(应放在
.c
文件中)。 - 非公开的内部函数(用
static
修饰,仅限当前文件使用)。
- 函数具体实现代码(应放在
(三)头文件保护语法
-
作用:避免头文件被重复包含,防止编译错误。
-
标准写法:
#ifndef VECTOR_H // 宏名通常与头文件同名(全大写,下划线分隔) #define VECTOR_H // 头文件内容 #endif
-
替代方案:
#pragma once
(非C标准,但现代编译器普遍支持,写法更简洁)。
七、动态数组Vector实现细节
(一)关键函数实现
1. vector_create
:初始化动态数组
Vector* vector_create() {// 1. 分配Vector结构体内存(自动初始化为0)Vector* v = (Vector*)calloc(1, sizeof(Vector));if (v == NULL) {perror("calloc Vector failed");return NULL;}// 2. 分配初始数组内存(默认容量,自动清零)v->data = (ElementType*)calloc(DEFAULT_CAPACITY, sizeof(ElementType));if (v->data == NULL) {free(v); // 分配失败时释放结构体perror("calloc array failed");return NULL;}v->capacity = DEFAULT_CAPACITY; // 初始化容量return v;
}
2. vector_destroy
:销毁动态数组
void vector_destroy(Vector* v) {free(v->data); // 释放数组内存free(v); // 释放结构体内存
}
3. vector_print
:遍历打印数组
void vector_print(const Vector* v) {printf("[");for (size_t i = 0; i < v->size; i++) {printf("%d, ", v->data[i]);}printf("\b\b]\n"); // 去除末尾多余逗号
}
(二)扩容机制
1. 扩容策略
-
阈值判断:若当前容量小于扩展阈值(
EXPAND_THRESHOLD
),按2倍扩容;否则按1.5倍扩容(避免大数组过度扩容)。 -
代码实现:
static void grow_capacity(Vector* v) {int old_capacity = v->capacity;int new_capacity = (old_capacity < EXPAND_THRESHOLD) ?(old_capacity << 1) : // 左移1位等价于×2(old_capacity + (old_capacity >> 1)); // 右移1位等价于÷2,即1.5倍// 调用realloc调整内存大小ElementType* temp = (ElementType*)realloc(v->data, new_capacity * sizeof(ElementType));if (temp == NULL) {perror("realloc failed");exit(1); // 扩容失败时终止程序}v->data = temp; // 更新数组指针v->capacity = new_capacity; // 更新容量 }
2. 触发场景
- 当调用
vector_push_back
/vector_insert
等函数时,若size == capacity
,自动触发扩容。
(三)元素添加操作
1. vector_push_back
:末尾添加元素
void vector_push_back(Vector* v, ElementType element) {if (v->size == v->capacity) { // 容量不足时扩容grow_capacity(v);}v->data[v->size++] = element; // 直接追加到末尾
}
2. vector_insert
:指定位置插入元素
void vector_insert(Vector* v, int idx, ElementType val) {// 参数校验:确保索引合法(0 ≤ idx ≤ size)if (idx < 0 || idx > v->size) {fprintf(stderr, "Illegal index: %d, size=%d\n", idx, v->size);exit(1);}if (v->size == v->capacity) { // 容量不足时扩容grow_capacity(v);}// 元素后移:从末尾开始,将[idx, size-1]的元素右移一位for (int i = v->size - 1; i >= idx; i--) {v->data[i + 1] = v->data[i];}v->data[idx] = val; // 插入新元素v->size++; // 更新元素数量
}
八、注意事项与优化点
(一)内存管理要点
- 初始化与销毁配对:
vector_create
与vector_destroy
必须成对调用,避免内存泄漏。 - 扩容时的临时指针:使用
realloc
时先用临时指针接收返回值,确保扩容失败时不丢失原始指针。
(二)性能优化
- 扩容策略选择:小容量时2倍扩容(快速增长),大容量时1.5倍扩容(减少内存浪费)。
- 元素移位方向:插入操作从后往前移位,避免覆盖未处理的元素(如
vector_insert
中倒序遍历)。
(三)接口设计建议
- 隐藏内部函数:扩容函数
grow_capacity
用static
修饰,不暴露给头文件,符合模块化封装原则。 - 参数校验:关键函数(如
vector_insert
)需校验输入合法性,增强程序鲁棒性。
九、代码示例:动态数组基本操作
#include "vector.h" // 假设头文件已包含相关声明int main() {Vector* v = vector_create(); // 创建动态数组if (v == NULL) return 1;// 末尾添加元素vector_push_back(v, 10);vector_push_back(v, 20);vector_push_back(v, 30);// 插入元素到索引1的位置vector_insert(v, 1, 15); // 数组变为[10, 15, 20, 30]vector_print(v); // 输出: [10, 15, 20, 30]vector_destroy(v); // 销毁数组,释放内存return 0;
}
十、二级指针基础概念
(一)定义与语法
-
二级指针:指向一级指针的指针,用于间接操作原始数据或修改一级指针的指向。
int a = 10; // 原始数据 int *p = &a; // 一级指针,指向a的地址 int **pp = &p; // 二级指针,指向p的地址
-
解引用操作:
*pp
:获取一级指针p
的值(即a
的地址)。**pp
:获取a
的值(即10
)。
(二)核心作用
-
修改一级指针的指向:当函数需要改变实参指针的指向时,需传递二级指针。
void modify_ptr(int **pp) {int b = 20;*pp = &b; // 通过二级指针修改一级指针的指向 }
-
对比一级指针:
- 一级指针可修改所指内容,但无法修改自身指向(实参指针的指向不变)。
- 二级指针可通过解引用
*pp
修改一级指针的指向,或通过**pp
修改所指内容。
十一、单链表基本概念与结构
(一)核心术语
-
结点(Node):
- 组成链表的基本单元,包含数据域(
data
)和指针域(next
,指向下一结点)。
typedef struct Node {int data; // 数据域struct Node *next; // 指针域 } Node;
- 组成链表的基本单元,包含数据域(
-
头结点(Head Node):
- 可选的虚拟结点,位于链表头部,不存储实际数据,用于简化头部操作(如插入、删除)。
-
头指针(Head Pointer):
- 必有的指针,指向链表第一个结点(若有头结点则指向头结点,否则指向首数据结点)。
-
尾结点(Tail Node):
- 链表最后一个结点,其指针域为
NULL
。
- 链表最后一个结点,其指针域为
-
尾指针(Tail Pointer):
- 可选指针,指向尾结点,用于优化尾部操作性能(如尾插法)。
(二)链表分类
- 带头结点链表:头指针指向头结点,头结点
next
指向首数据结点。 - 不带头结点链表:头指针直接指向首数据结点,空链表时头指针为
NULL
。
(三)构建方式
- 头插法:
- 新结点插入到链表头部,成为新的首结点。
- 特点:插入速度快(无需遍历),但结点顺序与插入顺序相反。
- 尾插法:
- 新结点插入到链表尾部,需遍历至尾结点或借助尾指针。
- 特点:结点顺序与插入顺序一致,适合顺序构建链表。
十二、二级指针在链表操作中的应用
(一)头插法中的二级指针
场景:修改头指针的指向
-
不使用二级指针的实现(返回新头指针):
Node* insert_head(Node* head, int data) {Node* new_node = malloc(sizeof(Node));new_node->data = data;new_node->next = head;return new_node; // 返回新头指针,需手动更新实参 }
-
使用二级指针的实现(直接修改实参头指针):
void insert_head2(Node** phead, int data) { // phead指向实参头指针Node* new_node = malloc(sizeof(Node));new_node->data = data;new_node->next = *phead; // 新结点指向原头结点*phead = new_node; // 头指针指向新结点(修改实参) }
-
优势:无需返回值,直接通过二级指针修改头指针,代码更简洁安全。
(二)尾插法中的二级指针
场景:空链表时需修改头指针
-
实现逻辑:
- 若链表为空(头指针为
NULL
),新结点即为头结点,需通过二级指针更新头指针。 - 若链表非空,遍历至尾结点,修改其
next
指针。
void insert_tail(Node** phead, int data) {Node* new_node = malloc(sizeof(Node));new_node->data = data;new_node->next = NULL;if (*phead == NULL) { // 空链表时更新头指针*phead = new_node;return;}Node* tail = *phead;while (tail->next != NULL) tail = tail->next; // 遍历至尾结点tail->next = new_node; // 尾结点指向新结点 }
- 若链表为空(头指针为
十三、单链表核心操作:遍历与查找
(一)遍历链表
-
目标:依次访问每个结点的数据域。
-
实现步骤:
- 创建临时指针
curr
,初始化为头指针。 - 循环条件:
curr != NULL
(处理当前结点后,curr
指向下一结点)。
- 创建临时指针
-
代码示例:
void print_list(Node* head) {Node* curr = head;while (curr != NULL) {printf("%d -> ", curr->data);curr = curr->next;}printf("NULL\n"); }
(二)查找尾结点
-
目标:获取链表最后一个结点。
-
实现逻辑:
- 临时指针
tail
从头部开始遍历。 - 循环条件:
tail->next != NULL
(当tail->next
为NULL
时,tail
即为尾结点)。
- 临时指针
-
代码示例:
void find_tail(Node* head) {if (head == NULL) return; // 空链表处理Node* tail = head;while (tail->next != NULL) tail = tail->next;printf("Tail data: %d\n", tail->data); }
十四、关键对比与注意事项
(一)头插法 vs 尾插法
特性 | 头插法 | 尾插法 |
---|---|---|
插入位置 | 头部 | 尾部 |
是否需要遍历 | 否 | 是(无尾指针时) |
头指针修改 | 必改 | 空链表时需改 |
结点顺序 | 逆序 | 顺序 |
适用场景 | 快速插入、逆序构建 | 顺序构建、需要保持插入顺序 |
(二)二级指针使用场景
- 必须使用二级指针:
- 函数需要修改头指针的指向(如头插法、空链表尾插法)。
- 避免滥用:
- 仅操作结点内容(不修改指针指向)时,使用一级指针即可。
(三)内存管理要点
- 结点分配与释放:
- 插入新结点时需用
malloc
分配内存,并在删除时用free
释放,避免内存泄漏。
- 插入新结点时需用
- 头指针置空:
- 销毁链表时,需将头指针置为
NULL
,避免野指针。
- 销毁链表时,需将头指针置为
十五、代码示例:基于二级指针的单链表操作
#include <stdio.h>
#include <stdlib.h>// 结点定义
typedef struct Node {int data;struct Node *next;
} Node;// 头插法(二级指针版)
void insert_head(Node** phead, int data) {Node* new_node = (Node*)malloc(sizeof(Node));new_node->data = data;new_node->next = *phead;*phead = new_node;
}// 尾插法(二级指针版)
void insert_tail(Node** phead, int data) {Node* new_node = (Node*)malloc(sizeof(Node));new_node->data = data;new_node->next = NULL;if (*phead == NULL) {*phead = new_node;return;}Node* tail = *phead;while (tail->next != NULL) tail = tail->next;tail->next = new_node;
}// 遍历打印
void print_list(Node* head) {Node* curr = head;while (curr != NULL) {printf("%d -> ", curr->data);curr = curr->next;}printf("NULL\n");
}int main() {Node* head = NULL; // 空链表头指针// 头插法添加结点insert_head(&head, 3); // 链表:3 -> NULLinsert_head(&head, 2); // 链表:2 -> 3 -> NULLinsert_head(&head, 1); // 链表:1 -> 2 -> 3 -> NULL// 尾插法添加结点insert_tail(&head, 4); // 链表:1 -> 2 -> 3 -> 4 -> NULLprint_list(head); // 输出:1 -> 2 -> 3 -> 4 -> NULL// 释放内存(示例省略,实际需遍历释放所有结点)return 0;
}
十六、函数指针基础概念
(一)本质与定义
- 函数指针:指向函数的指针变量,存储函数在代码段中的入口地址(首字节地址)。
- 函数地址:函数编译后生成的机器指令存储区域的起始地址,函数名即代表该地址。
(二)声明与初始化
1. 变量声明格式
返回值类型 (*指针变量名)(参数列表);
// 示例:指向无参无返回值函数的指针
void (*ptr)();
// 示例:指向带两个int参数、返回int的函数的指针
int (*calc_ptr)(int, int);
2. 类型别名定义(推荐写法)
typedef 返回值类型 (*别名)(参数列表);
// 示例:定义计算器函数指针类型
typedef int (*CalculatePtr)(int, int);
3. 初始化方式
CalculatePtr add = add_func; // 直接用函数名赋值
// 或
CalculatePtr add = &add_func; // 取地址符可选(函数名隐式转换为地址)
(三)调用方式
int add(int a, int b) { return a + b; }
CalculatePtr ptr = add; // 初始化// 方式1:直接通过函数指针调用(最常用)
int result = ptr(3, 5); // 等价于 add(3,5)// 方式2:解引用后调用(不推荐)
int result = (*ptr)(3, 5); // 方式3:通过函数名解引用调用(极少用)
int result = (*add)(3, 5);
十七、函数指针匹配规则
(一)必须匹配的要素
- 返回值类型:需与函数指针声明完全一致(包括是否为指针、const修饰等)。
- 参数列表:参数个数、顺序、类型必须完全一致(参数名可忽略)。
(二)无需匹配的要素
- 函数名:无关,只要地址正确即可指向。
- 函数形参名:仅需类型匹配,名称任意。
- 函数实现:与指针指向无关,可指向任意符合规则的函数。
十八、函数指针的核心作用:回调函数
(一)回调函数概念
- 定义:通过函数指针传递的函数,作为参数传入其他函数(如
qsort
、自定义过滤函数等),在适当的时候被调用以完成特定逻辑。 - 作用:解耦算法与规则,提高代码灵活性和可扩展性。
(二)经典场景:排序函数qsort
1. qsort
函数原型
void qsort(void* base, size_t num, size_t size, int (*compar)(const void*, const void*));
- 参数:
base
:待排序数组首地址。num
:元素个数。size
:单个元素大小(字节)。compar
:比较函数指针,返回值决定元素顺序:<0
:第一个参数应排在第二个之前。=0
:两者相等。>0
:第一个参数应排在第二个之后。
2. 自定义比较函数示例(整数升序)
int int_cmp(const void* a, const void* b) {return *(int*)a - *(int*)b; // 升序排列
}// 使用qsort排序整数数组
int arr[] = {3, 1, 4, 2};
qsort(arr, sizeof(arr)/sizeof(arr[0]), sizeof(int), int_cmp);
(三)自定义回调函数示例:计算器
1. 定义函数指针类型
typedef int (*OpFunc)(int, int); // 操作函数指针类型
2. 实现具体运算函数
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }
int divide(int a, int b) { return b != 0 ? a / b : 0; } // 简化处理除零情况
3. 通用计算函数(接收回调函数)
int calculate(int a, int b, OpFunc op) {return op(a, b); // 通过函数指针调用回调函数
}// 使用示例
int result = calculate(10, 3, add); // 调用加法,结果13
result = calculate(10, 3, subtract); // 调用减法,结果7
十九、函数指针的注意事项
(一)空指针检查
- 调用函数指针前需确保其非
NULL
,避免程序崩溃。
OpFunc op = NULL;
if (op != NULL) { // 关键检查op(10, 5);
}
(二)类型安全
- 确保函数指针与目标函数的参数、返回值严格匹配,避免未定义行为(如传入不匹配的函数导致栈溢出)。
(三)与数据指针的区别
- 函数指针:指向代码段,存储函数入口地址,调用时执行函数逻辑。
- 数据指针(如
int*
):指向数据段或堆/栈空间,存储数据地址,解引用时操作数据。
(四)回调函数的内存管理
- 回调函数若为全局函数或静态函数,无需担心生命周期;若为局部函数(如嵌套函数),需确保其在调用期间有效(C语言不支持局部函数作为回调,会编译报错)。
二十、函数指针与闭包(扩展)
- 闭包概念:在支持嵌套函数的语言(如Python、JavaScript)中,闭包允许函数捕获外部作用域的变量。
- C语言的局限性:C语言不直接支持闭包,但可通过函数指针结合结构体(存储上下文数据)模拟类似功能。
模拟闭包示例:带状态的计数器
typedef struct {int count; // 状态变量int (*increment)(struct Counter*); // 函数指针
} Counter;int increment(Counter* c) {return ++c->count;
}Counter* create_counter() {Counter* c = malloc(sizeof(Counter));c->count = 0;c->increment = increment; // 函数指针指向增量函数return c;
}// 使用
Counter* cnt = create_counter();
printf("%d\n", cnt->increment(cnt)); // 输出1
printf("%d\n", cnt->increment(cnt)); // 输出2
二十一、总结:函数指针的价值
- 灵活性:通过传递不同的回调函数,同一函数可实现多种逻辑(如排序规则、过滤条件)。
- 可扩展性:无需修改现有代码,通过新增回调函数即可扩展功能,符合开闭原则。
- 底层开发必备:在嵌入式、系统编程中常用于实现驱动接口、事件回调等场景。
关键记忆点:
- 函数指针声明需匹配返回值和参数列表。
- 回调函数是函数指针的核心应用,用于解耦算法与规则。
- 调用前检查指针非空,确保类型安全。
相关文章:
嵌入式(C语言篇)Day11
嵌入式Day11 一、动态内存分配核心函数 (一)函数列表 函数名功能头文件返回值malloc分配连续的size字节堆内存stdlib.h成功返回首地址(void*),失败返回NULLcalloc分配num个元素size字节/元素的堆内存,自…...
Java集合框架解析:从基础到底层源码
Java集合框架解析:从基础到底层源码 一、集合体系 1.1 两大核心接口深度解析 Collection 单列集合 List 系列 ArrayList:动态数组实现,初始容量10,扩容策略为 原容量的1.5倍// JDK17 扩容源码片段 int newCapacity oldCapacity…...
C语言实现android/linux按键模拟
C语言实现 input事件模拟 #include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <linux/input.h>int main() {int fd open("/dev/input/event0", O_RDWR);if (fd < 0) {perror("Failed to open device");ret…...
VisionPro:轴承错位标识
找出轴承缺陷并标记:效果 1.打开导入图片 2.添加CogToolblock:方便后续写代码 3.对零件进行模板匹配 4.对图片进行预处理(重点) 5.添加找圆工具和展开工具 根据下图将线链接 6.添加模板匹配工具并运行 7.训练模板 好的全找出来 8.代码编写 红色框为添加的代码(下面有完整代码) …...
专题五:floodfill算法(扫雷游戏精讲)
以leetcode529题 题目解析: M表示:未挖出的地雷 E表示:未挖出的空白方块 什么叫未挖出,就是你没玩的时候,棋盘是什么样的,就是你站在上帝视角看待棋盘 B:表示挖出来是空白的,如…...
MySQL企业版免费开启,强先体验
近期Oracle突然宣布,MySQL企业版面向开发者免费开放下载,这一消息瞬间引爆DBA圈。作为数据库领域的“顶配车型”,企业版长期因高昂授权费让中小团队望而却步,如今免费开放无异于“劳斯莱斯开进菜市场”。 本文将深度拆解企业版的…...
React Contxt详解
React Contxt详解 React 的 Context API 是用于跨组件层级传递数据的解决方案,尤其适合解决「prop drilling」(多层组件手动传递 props)的问题。以下是关于 Context 的详细解析: 文章目录 React Contxt详解一、Context 核心概念二…...
前端工程的相关管理 git、branch、build
环境配置 标准环境打包 测试版:npm run build-test 预生产:npm run build-preview 正式版:npm run build 建议本地建里一个 .env.development.local 方便和后端联调时修改配置相关信息。 和 src 同级有一下区分环境的文件: .env.d…...
鸿蒙 Location Kit(位置服务)
移动终端设备已经深入人们日常生活的方方面面,如查看所在城市的天气、新闻轶事、出行打车、旅行导航、运动记录。这些习以为常的活动,都离不开定位用户终端设备的位置。 Location Kit 使用多种定位技术提供服务,可以准确地确定设备在室外/室…...
人工智能、机器学习与深度学习:概念解析与内在联系
人工智能、机器学习与深度学习:概念解析与内在联系 一、人工智能(Artificial Intelligence, AI) (一)人工智能的定义 人工智能的定义随着技术发展不断演变。从广义上讲,人工智能是指通过计算机技术实现的…...
【Linux学习】Ubuntu对用户进行管理
目录 写在前面 【Linux学习】Ubuntu对用户进行管理一、为什么需要用户管理?二、用户管理基础操作(一)用户管理1. 用户管理命令表格2. 关键操作场景说明场景一:创建用户并配置权限场景二:修改用户属性场景三:…...
JVM 垃圾回收器
以下是对主流 JVM 垃圾回收器的详细解析,涵盖 一、Serial GC(单线程串行回收器) 二、Parallel GC(吞吐量优先回收器) 三、CMS(Concurrent Mark Sweep,低延迟回收器) 四、G1&…...
JUC入门(三)
7、Callable 1、可以有返回值 2、可以抛出异常 3、方法不同 run()/call() 代码测试 老版本的应用 package com.yw.callable;public class Old {public static void main(String[] args) {new Thread(new MyThread()).start();} }class MyThread implements Runnable{Overri…...
RV1126多线程获取SMARTP的GOP模式数据和普通GOP模式数据
通过代码的方式同时获取SMARTP模式的VENC码流数据和普通GOP模式的VENC码流数据,并进行对比画质。 一.RV1126 VI采集摄像头数据并同时编码SMARTP模式和普通GOP模式的编码码流流程 RV1126利用多线程同时获取普通GOP的VENC码流数据和SMARTP的码流数据一般如上图&#…...
MySQL事务、视图、索引、备份和恢复
1. 事务 如果不使用事务,那么如果出现了张三账户汇款成功-500元,但是李四那边的服务器出现了故障没有接收到500元,那么就会出现张三账户里有500元但是李四那边的账户还是1元的情况,转账的500元被吞了。 创建事务 2. 视图 创建视图…...
yolov8训练模型优化模型【误检】
针对 YOLOv8 模型在电动车人脸检测中出现误检行人人脸的问题,结合最新研究与实践经验,以下提供多维度优化方案及具体实施步骤: 一、数据集优化 数据清洗与标注增强 剔除干扰样本:确保训练集中所有标注仅包含骑行状态的人脸&#x…...
初识MySQL · 索引
目录 前言: 重温磁盘 认识索引 为什么这么做,怎么做 重谈page 聚簇索引VS非聚簇索引 回表查询 索引分类 前言: 前文我们主要是介绍了MySQL的一些基本操作,增删查改一类的操作都介绍了,并且因为大多数情况下&am…...
Kubernetes in action-配置和应用升级
Kubernetes的配置和应用升级 1、配置1.1 configMap1.2 secret1.3 Downward API1.4 Kubernetes API 2、服务升级2.1 升级方式2.1.1 先删除所有的旧版pod,使用新版本pod替换2.1.2 先创建新版pod,再删除旧版本pod2.1.3 滚动优化 2.2 使用deployment声明式升…...
十三、面向对象底层逻辑-Dubbo序列化Serialization接口
一、引言:分布式通信的数据桥梁 在分布式服务调用中,参数的跨网络传输需要将对象转化为二进制流,这一过程直接影响系统的性能、兼容性与安全性。Dubbo通过Serialization接口构建了可扩展的序列化体系,支持多种序列化协议的无缝切…...
5.19 打卡
DAY 30 模块和库的导入 知识点回顾: 导入官方库的三种手段导入自定义库/模块的方式导入库/模块的核心逻辑:找到根目录(python解释器的目录和终端的目录不一致) 作业:自己新建几个不同路径文件尝试下如何导入...
MathType公式如何按照(1)(2)…编号
在word中使用mathtype插入公式,发现插入的公式编号默认为(1.1),(1.2)…,但论文中常用的公式编号是(1)(2)…,分享一下如何实现(1.1)→(1)。 ①word菜单找到“MathType”,点击“插入编号”-“格式化”。 ②取消勾选“章编号”和“节…...
解决即使安装了pageoffice网页还是无法跳转、点击按钮没有反应等问题
出现的现象: 安装了pageoffice网页还是无法跳转、点击按钮没有反应 网页提示的错误: 点击按钮之前右键检查网页发现的错误: 点击无效按钮之后右键检查出现的错误: 原因: 鉴于安全性的考虑,新版浏览器不支…...
正点原子STM32新建工程
MDK 源自德国的 KEIL 公司,是 RealView MDK 的简称。 MDK5 由两个部分组成: MDK Core 和 Software Packs。 MDK Core 又分成四个部分: uVision IDE with Editor(编辑器), ARMC/C Compiler(编译…...
计算机网络 第三章:运输层(一)
运输层位于应用层和网络层之间,是分层的网络体系结构的重要部分。该层为运行在不同主机上的应用进程提供直接的通信服务。通常特别关注因特网协议,即 TCP 和 UDP 运输层协议。 讨论运输层和网络层的关析,为研究运输层第一个关键功能ÿ…...
机器学习(14)——模型调参
文章目录 一、动态调参方法论1. 调参策略选择2. 千万数据优化原则 二、模型调参策略对比1. LightGBM调参路线2. XGBoost调参路线3. 随机森林调参策略 三、代码实现示例通用数据准备(适用于所有模型)1. LightGBM调参示例2. XGBoost调参示例3. 随机森林调参…...
基于PetaLinux的Zynq PS应用自启动全攻略
一、嵌入式Linux启动管理的艺术 在工业机器人、智能摄像头、边缘计算网关等场景中,开机自启动管理是系统可靠性的第一道关卡。本文将深入讲解Zynq PS端在PetaLinux环境下实现: Systemd服务深度定制启动时序精准控制启动速度优化技巧动态服务管理创新方案二、环境搭建与工程配…...
贪心算法:多处最优服务次序、删数问题
多处最优服务次序问题 问题描述:设有n个顾客同时等待一项服务。顾客i需要的服务时间为ti(1≤i≤n),共有s处可以提供此项服务。应如何安排n个顾客的服务次序,才能使平均等待时间达到最小?平均等待时间是n个顾客等待服务时间的总和除以n。 算法设计:对于给定的n个顾…...
使用 Flask 框架实现FTP,允许用户通过 Web 界面浏览和下载文件夹中的所有文件
Flask 文件和文件夹下载服务实现 以下是一个基于 Flask 框架的简单 Web 服务,用于开放指定文件夹(./shared_files),允许用户通过浏览器浏览和下载文件夹中的所有文件和子文件夹。ZIP 和 TAR 文件将直接下载,而文件夹将…...
【Go】从0开始学习Go
文章目录 从0开始学习Go0 与C对比1 代码框架1.1 helloworld式代码示例1.2 主体代码元素(核心三部分)1.3 其他 2 与C/C区别3 有用的小工具4 注意事项 从0开始学习Go 0 与C对比 特性CGo编译型语言需要编译为机器码直接编译为二进制可执行文件静态类型类型…...
软件设计师SQL考点分析——求三连
一、考点分值占比与趋势分析 综合知识分值统计表(75分制) 年份考题数量分值分值占比考察重点2018334%关系代数、权限控制2019222.67%SQL注入、授权语句2020445.33%投影操作、权限回收2021334%视图操作、权限传递2022222.67%数据库安全、WITH GRANT OPT…...
使用tcs34725传感器和51单片机识别颜色
使用TCS34725颜色传感器和51单片机来识别颜色是一个非常有趣的项目。TCS34725是一种常用的RGB颜色传感器,能够测量红、绿、蓝光的强度,从而实现颜色识别。 1. 硬件连接 TCS34725传感器通过IC接口与51单片机连接。以下是连接方式: SDA&…...
数据库-oracle-包-视图传参
并发下可能不准确 -- 修改包规范 CREATE OR REPLACE PACKAGE sczz.p_view_param IS function set_n(n varchar2) return varchar2; function get_n return varchar2; function set_ny(ny varchar2) return varchar2; function get_ny return varchar2; …...
深入探讨Java中的上下文传递与ThreadLocal的局限性及Scoped Values的兴起
在Java开发中,特别是在依赖框架的应用程序中,上下文数据的管理是一个常见但具有挑战性的问题。上下文数据可能包括元数据、配置信息或其他需要在代码不同部分之间共享的信息。传统的做法是通过方法参数显式传递这些上下文,但这种方法会导致代码复杂、难以维护,尤其是在大型…...
Spring boot 学习笔记2
Maven 项目管理工具:Maven 通过 pom.xml(Project Object Model)文件描述项目配置,包括依赖、构建流程、插件等,实现项目标准化管理 依赖管理:自动下载并管理项目所需的第三方库(如 Spring、MyB…...
“保证医疗器械信息来源合法 真实、安全的保障措施、情况说明及相关证明”模板
保证医疗器械信息来源合法真实、安全的保障措施、情况说明及相关证明 一、医疗器械信息来源合法、真实、安全的管理措施 目前我公司网站所展示的医疗器械是企业代理品种,是取得合法注册资格的产品,拥有合法证明文件的产品。本网站仅展示本公司行政许可…...
Feature Toggle 不再乱:如何设计一个干净、安全、可控的特性开关系统?
网罗开发 (小红书、快手、视频号同名) 大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等…...
不锈钢保温容器行业2025数据分析报告
不锈钢保温容器市场概况 2024年全球不锈钢保温容器市场规模约为453.3亿元,预计到2031年将增长至608.3亿元,年均复合增长率(CAGR)为4.3%。这一增长主要得益于全球范围内对保温容器需求的持续增加,尤其是在户外活动、餐…...
leetcode239 滑动窗口最大值deque方式
这段文字描述的是使用单调队列(Monotonic Queue) 解决滑动窗口最大值问题的优化算法。我来简单解释一下: 核心思路 问题分析:在滑动窗口中,若存在两个下标 i < j 且 nums[i] ≤ nums[j],则 nums[i] 永远…...
腾讯云怎么在游戏云中助力
腾讯云游戏云:依托深厚游戏基因,打造高质量全方位生态平台 在竞争激烈的云计算市场中,腾讯云凭借其得天独厚的游戏生态资源和深耕多年的技术沉淀,正成为游戏行业不可忽视的重要力量。腾讯不仅是全球领先的游戏开发和发行商&#…...
深入理解pip:Python包管理的核心工具与实战指南
# 深入理解pip:Python包管理的核心工具与实战指南 在Python开发中,第三方库是提升效率的关键。而pip作为Python官方的包管理工具,承担着安装、卸载、升级和管理库的重要职责。本文将全面解析pip的核心命令,结合实例演示用法&#…...
【python】windows修改 pip 默认安装路径
在 Windows 系统 下,希望修改 pip 默认安装路径,结合你前面贴的图片和信息,一个 推荐做法(不修改 site.py)的完整教程。 目标:让 pip 安装包默认装到你指定的路径(如 D:\MyPythonLibsÿ…...
Python函数——万字详解
—— 小 峰 编 程 导 语: 从今天开始,我们将进入第二模块的学习——函数。第一模块主要是学习python基础知识,从第二模块开始就可以通过程序去解决工作中实际的问题。从今天开始,我们将进入第二模块的学习,此模块…...
es在已有历史数据的文档新增加字段操作
新增字段设置默认值 场景 在已经有大量数据的索引文档上,增加新字段 技术实现 一.更新索引映射 通过PUT请求显式定义新字段类型,确保后续写入的文档能被正确解析 PUT /文档名/_mapping {"properties": {"字段名1": {"type…...
LeetCode 35 搜索插入位置题解
LeetCode 35 搜索插入位置题解 题目描述 题目链接 给定一个排序数组和一个目标值,在数组中找到目标值并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置(需保证数组仍然有序)。要求时间复杂度为 O(log n)。…...
RabbitMQ通信模式(Simplest)Python示例
RabbitMQ通信模式-Python示例 0.RabbitMQ官网通信模式1.Simplest(简单)模式1.1 发送端1.2 接收端 0.RabbitMQ官网通信模式 1.Simplest(简单)模式 1.1 发送端 # -*- coding: utf-8 -*- """ Author: xxx date: 2025/5/19 11:30 Description: Simaple简单模…...
游戏开发实战(一):Python复刻「崩坏星穹铁道」嗷呜嗷呜事务所---源码级解析该小游戏背后的算法与设计模式【纯原创】
文章目录 奇美拉项目游戏规则奇美拉(Chimeras)档案领队成员 结果展示: 奇美拉项目 由于项目工程较大,并且我打算把我的思考过程和实现过程中踩过的坑都分享一下,因此会分3-4篇博文详细讲解本项目。本文首先介绍下游戏规则并给出奇美拉档案。…...
力扣热题100之删除链表的倒数第N个节点
题目 给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。 代码 方法一 将链表中的值放入列表中,然后删除倒数第n个值,再将剩下的数依次转化为链表 # Definition for singly-linked list. # class ListNode: # …...
OCframework编译Swift
建一个OC的framework: 需要对外暴露的OC文件,需要放到OC的.h文件中 framework中,OC类,调用framework中的Swift类: #import "WowAudioFocus/WowAudioFocus-Swift.h" //02 #import "{工程名}/{工程…...
【AI News | 20250519】每日AI进展
AI Repos 1、deepdrone DeepDrone是一款基于smolagents框架的无人机聊天代理,集成DroneKit实现无人机分析与操作。用户可通过自然语言聊天与无人机助手交互,实现飞行路径和传感器数据可视化、基于飞行时长的维护建议、任务规划以及真实的无人机控制&…...
分布式ID生成系统
代码地址: github mid 简介 分布式 ID 生成系统是一个高性能、可靠的 ID 生成服务,支持两种模式:Snowflake(基于时间戳的内存生成)和 Segment(基于 MySQL 的号段分配)。系统采用双 Buffer 策略优化性能,集成 Prometheus 监控和 Zap 结构化日志,确保高可用性和可观测性…...