当前位置: 首页 > news >正文

嵌入式(C语言篇)Day11

嵌入式Day11

一、动态内存分配核心函数

(一)函数列表

函数名功能头文件返回值
malloc分配连续的size字节堆内存stdlib.h成功返回首地址(void*),失败返回NULL
calloc分配num个元素×size字节/元素的堆内存,自动初始化为0stdlib.h成功返回首地址(void*),失败返回NULL
realloc调整已分配内存块的大小stdlib.h成功返回新首地址(void*),失败返回NULL
free释放堆内存块stdlib.h

(二)通用注意事项

  1. 返回值处理
    • 必须用指针接收返回值,推荐显式强转(如(int*)malloc(...))。
    • 严格平台(如C++)必须显式强转,普通C环境可隐式转换。
  2. 错误处理
    • 分配函数(malloc/calloc/realloc)调用后必须判断返回值是否为NULL,失败时需进行错误处理(如打印日志、退出程序)。
  3. 内存释放
    • 不再使用的内存块必须用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对比
特性malloccalloc
初始化无,需手动处理自动初始化为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≠NULLnew_size≠0
  1. new_size=原大小:不操作,直接返回ptr
  2. new_size<原大小:从高地址截断内存块,截断部分自动释放。
  3. 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 内存溢出

问题定义后果
内存泄漏分配的内存未释放,长期积累导致可用内存减少短期无明显影响,长期可能引发内存溢出
内存溢出申请内存超过系统可用空间,或访问越界程序崩溃、数据损坏

(二)避免内存泄漏的关键措施

  1. 正确调用free:确保传入原始指针,释放前检查指针是否被移动。
  2. 释放后置空指针free(p); p = NULL;,避免“二次释放”和野指针。
  3. 谨慎修改堆指针:若需移动指针(如遍历数组),使用临时指针(如int* temp = p;)。
  4. 单一职责原则:同一内存块的分配与释放由同一函数管理,避免多函数交叉操作。

(三)性能与安全权衡

  • 优先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源文件:实现具体功能,包含头文件以暴露接口。
  • 设计目标:低耦合、高内聚,模块间通过明确接口交互,隐藏内部实现细节。

(二)头文件内容规范

  1. 必含内容
    • 函数声明(如Vector* vector_create();)。
    • 结构体类型定义(如Vector结构体)。
    • 类型别名(如typedef int ElementType;)。
    • 宏定义(如#define DEFAULT_CAPACITY 10)。
  2. 禁止内容
    • 函数具体实现代码(应放在.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_createvector_destroy必须成对调用,避免内存泄漏。
  • 扩容时的临时指针:使用realloc时先用临时指针接收返回值,确保扩容失败时不丢失原始指针。

(二)性能优化

  • 扩容策略选择:小容量时2倍扩容(快速增长),大容量时1.5倍扩容(减少内存浪费)。
  • 元素移位方向:插入操作从后往前移位,避免覆盖未处理的元素(如vector_insert中倒序遍历)。

(三)接口设计建议

  • 隐藏内部函数:扩容函数grow_capacitystatic修饰,不暴露给头文件,符合模块化封装原则。
  • 参数校验:关键函数(如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修改所指内容。

十一、单链表基本概念与结构

(一)核心术语

  1. 结点(Node)

    • 组成链表的基本单元,包含数据域(data)和指针域(next,指向下一结点)。
    typedef struct Node {int data;          // 数据域struct Node *next; // 指针域
    } Node;
    
  2. 头结点(Head Node)

    • 可选的虚拟结点,位于链表头部,不存储实际数据,用于简化头部操作(如插入、删除)。
  3. 头指针(Head Pointer)

    • 必有的指针,指向链表第一个结点(若有头结点则指向头结点,否则指向首数据结点)。
  4. 尾结点(Tail Node)

    • 链表最后一个结点,其指针域为NULL
  5. 尾指针(Tail Pointer)

    • 可选指针,指向尾结点,用于优化尾部操作性能(如尾插法)。

(二)链表分类

  • 带头结点链表:头指针指向头结点,头结点next指向首数据结点。
  • 不带头结点链表:头指针直接指向首数据结点,空链表时头指针为NULL

(三)构建方式

  1. 头插法
    • 新结点插入到链表头部,成为新的首结点。
    • 特点:插入速度快(无需遍历),但结点顺序与插入顺序相反。
  2. 尾插法
    • 新结点插入到链表尾部,需遍历至尾结点或借助尾指针。
    • 特点:结点顺序与插入顺序一致,适合顺序构建链表。

十二、二级指针在链表操作中的应用

(一)头插法中的二级指针

场景:修改头指针的指向
  • 不使用二级指针的实现(返回新头指针):

    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;       // 头指针指向新结点(修改实参)
    }
    
  • 优势:无需返回值,直接通过二级指针修改头指针,代码更简洁安全。

(二)尾插法中的二级指针

场景:空链表时需修改头指针
  • 实现逻辑

    1. 若链表为空(头指针为NULL),新结点即为头结点,需通过二级指针更新头指针。
    2. 若链表非空,遍历至尾结点,修改其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; // 尾结点指向新结点
    }
    

十三、单链表核心操作:遍历与查找

(一)遍历链表

  • 目标:依次访问每个结点的数据域。

  • 实现步骤

    1. 创建临时指针curr,初始化为头指针。
    2. 循环条件:curr != NULL(处理当前结点后,curr指向下一结点)。
  • 代码示例

    void print_list(Node* head) {Node* curr = head;while (curr != NULL) {printf("%d -> ", curr->data);curr = curr->next;}printf("NULL\n");
    }
    

(二)查找尾结点

  • 目标:获取链表最后一个结点。

  • 实现逻辑

    1. 临时指针tail从头部开始遍历。
    2. 循环条件:tail->next != NULL(当tail->nextNULL时,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); 

十七、函数指针匹配规则

(一)必须匹配的要素

  1. 返回值类型:需与函数指针声明完全一致(包括是否为指针、const修饰等)。
  2. 参数列表:参数个数、顺序、类型必须完全一致(参数名可忽略)。

(二)无需匹配的要素

  • 函数名:无关,只要地址正确即可指向。
  • 函数形参名:仅需类型匹配,名称任意。
  • 函数实现:与指针指向无关,可指向任意符合规则的函数。

十八、函数指针的核心作用:回调函数

(一)回调函数概念

  • 定义:通过函数指针传递的函数,作为参数传入其他函数(如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语法备注01
  • BGP选路
  • STM32 控制 OLED 全攻略(二):实现字符和汉字的显示
  • 【Vite】静态资源的动态访问
  • 4.6 sys模块
  • 数据库(一):分布式数据库
  • 【通用智能体】Lynx :一款基于终端的纯文本网页浏览器
  • 【计网】作业5
  • 【C++模板与泛型编程】实例化
  • 龙虎榜——20250519
  • C++ 函数对象、仿函数与 Lambda 表达式详解
  • Python中的整型(int)和浮点数(float)
  • vue3 vite 路由
  • 打卡第二十二天
  • C++:判断闰年
  • turf的pointsWithinPolygon排查
  • C++(2)关键字+数据类型 +数据类型输入
  • Linux云计算训练营笔记day11【Linux CentOS7(cat、less、head、tail、lscpu、lsblk、hostname、vim、which、mount、alias)】
  • 技术决策缺乏团队参与,如何增强执行力?
  • YoloV9改进策略:卷积篇|风车卷积|即插即用
  • 国家发改委:大部分稳就业稳经济政策将在6月底前落地
  • 印尼总统20年来首次访泰:建立战略伙伴关系,加强打击网络诈骗等合作
  • 牛市早报|年内首次存款利率下调启动,5月LPR今公布
  • 夜驾遇东北虎隔窗对视?延吉林业局:村里有牛被咬死,保险公司会理赔
  • 国家统计局:4月份各线城市商品住宅销售价格环比持平或略降
  • 玛丽亚·凯莉虹口连唱两夜,舞台绽放唤醒三代人青春记忆