嵌入式C语言高级编程之二级指针

嵌入式C语言高级编程之二级指针

本专题主要讲解嵌入式C语言编程中二级指针,掌握二级指针在嵌入式开发中的核心应用场景。


目录

  1. 二级指针基础概念
  2. 二级指针定义与基本操作
  3. 二级指针核心应用场景
  4. 实战应用案例
  5. 与一级指针的区别
  6. 常见错误与陷阱
  7. GDB调试二级指针
  8. 实战练习

一、二级指针基础概念

1.1 什么是二级指针?

二级指针 = 指向指针的指针

简单来说:

  • 一级指针存储变量的地址
  • 二级指针存储一级指针的地址
内存示意图:
┌─────────────┐
│   a = 100   │ ← 普通变量a,地址0x1000
│  地址0x1000  │
└─────────────┘
      ↑
      │ 存储的地址
┌─────────────┐
│   p = 0x1000│ ← 一级指针p,地址0x2000
│  地址0x2000  │
└─────────────┘
      ↑
      │ 存储的地址
┌─────────────┐
│   pp=0x2000 │ ← 二级指针pp,地址0x3000
│  地址0x3000  │
└─────────────┘

1.2 声明语法

int a = 100;        // 普通变量
int *p = &a;        // 一级指针,指向a
int **pp = &p;      // 二级指针,指向p

关键点:

  • int **pp: 两个*表示这是二级指针
  • *pp 解引用一次得到一级指针p
  • **pp 解引用两次得到变量a

1.3 为什么需要二级指针?

嵌入式开发中的核心场景:

  1. 修改指针本身 - 函数需要改变指针的指向
  2. 动态内存分配 - 在函数内部分配内存并返回指针
  3. 二维数组操作 - 高效处理矩阵数据
  4. 命令行参数 - 处理argv参数
  5. 回调函数注册 - 注册函数指针到回调表
  6. 链表/树结构 - 实现动态数据结构

二、二级指针定义与基本操作

2.1 基本定义与初始化

#include <stdio.h>

int main(void)
{
    int a = 100;
    int *p = &a;        // 一级指针
    int **pp = &p;      // 二级指针

    printf("a的值: %d\n", a);
    printf("a的地址: %p\n", &a);
    printf("p存储的地址: %p\n", p);
    printf("p的地址: %p\n", &p);
    printf("pp存储的地址: %p\n", pp);
    printf("pp的地址: %p\n", &pp);

    // 解引用
    printf("\n通过二级指针访问:\n");
    printf("*pp = %p (这是p的地址)\n", *pp);
    printf("**pp = %d (这是a的值)\n", **pp);

    return 0;
}

2.2 解引用层次

int a = 100;
int *p = &a;
int **pp = &p;

// 各层访问方式
a           // 直接访问变量a
*p          // 通过一级指针访问a
**pp        // 通过二级指针访问a

p           // 访问一级指针p(存储a的地址)
*pp         // 通过二级指针访问p

& p         // 取p的地址
pp          // 二级指针pp(存储p的地址)

2.3 通过二级指针修改变量

int a = 100;
int *p = &a;
int **pp = &p;

// 方法1: 两次解引用
**pp = 200;  // 等价于 a = 200

// 方法2: 先得到一级指针,再解引用
*(*pp) = 300;  // 等价于 *p = 300

// 方法3: 通过一级指针修改
(*pp) = &a;   // 让p指向a
*(*pp) = 400; // 修改a的值

三、二级指针核心应用场景

3.1 场景一: 函数内修改指针的指向

问题: 需要在函数中修改指针的指向,而不仅仅是修改指针指向的值

// ❌ 错误: 无法修改指针本身的指向
void alloc_memory_wrong(int *p)
{
    p = (int*)malloc(sizeof(int));  // 只修改了副本!
    *p = 100;
}

// ✅ 正确: 使用二级指针修改指针指向
void alloc_memory(int **pp)
{
    *pp = (int*)malloc(sizeof(int));  // 修改原指针
    **pp = 100;                       // 修改内存中的值
}

完整示例:

#include <stdio.h>
#include <stdlib.h>

// 使用二级指针分配内存
void create_buffer(int **buffer, int size)
{
    *buffer = (int*)malloc(size * sizeof(int));   
    for(int i = 0; i < size; i++)
    {
        (*buffer)[i] = i * 10;
    }
}

int main(void)
{
    int *buf = NULL;
    create_buffer(&buf, 5);

    if(buf != NULL)
    {
        printf("动态分配的数组:\n");
        for(int i = 0; i < 5; i++)
        {
            printf("buf[%d] = %d\n", i, buf[i]);
        }
        free(buf);
    }

    return 0;
}

3.2 场景二: 二维数组动态创建

#include <stdio.h>
#include <stdlib.h>

// 动态创建二维数组
int** create_2d_array(int rows, int cols)
{
    int **array = (int**)malloc(rows * sizeof(int*));

    for(int i = 0; i < rows; i++)
    {
        array[i] = (int*)malloc(cols * sizeof(int));
    }

    return array;
}

// 初始化二维数组
void init_2d_array(int **array, int rows, int cols)
{
    for(int i = 0; i < rows; i++)
    {
        for(int j = 0; j < cols; j++)
        {
            array[i][j] = i * cols + j;
        }
    }
}

// 释放二维数组
void free_2d_array(int **array, int rows)
{
    for(int i = 0; i < rows; i++)
    {
        free(array[i]);
    }
    free(array);
}

int main(void)
{
    int rows = 3, cols = 4;
    int **matrix = create_2d_array(rows, cols);

    init_2d_array(matrix, rows, cols);

    printf("二维数组内容:\n");
    for(int i = 0; i < rows; i++)
    {
        for(int j = 0; j < cols; j++)
        {
            printf("%3d ", matrix[i][j]);
        }
        printf("\n");
    }

    free_2d_array(matrix, rows);
    return 0;
}

3.3 场景三: 字符串数组处理

#include <stdio.h>

// 打印字符串数组
void print_strings(char **strings, int count)
{
    for(int i = 0; i < count; i++)
    {
        printf("strings[%d] = %s\n", i, strings[i]);
    }
}

// 查找字符串
char* find_string(char **strings, int count, const char *target)
{
    for(int i = 0; i < count; i++)
    {
        if(strcmp(strings[i], target) == 0)
        {
            return strings[i];
        }
    }
    return NULL;
}

int main(void)
{
    char *fruits[] = {"apple", "banana", "orange", "grape"};
    int count = sizeof(fruits) / sizeof(fruits[0]);

    print_strings(fruits, count);

    char *found = find_string(fruits, count, "banana");
    if(found != NULL)
    {
        printf("\n找到字符串: %s\n", found);
    }

    return 0;
}

四、实战应用案例

4.1 嵌入式案例: 消息队列管理

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_QUEUE_SIZE 10

// 消息结构
typedef struct {
    int id;
    char data[32];
} Message;

// 消息队列
typedef struct {
    Message *buffer[MAX_QUEUE_SIZE];  // 消息指针数组
    int head;
    int tail;
    int count;
} MessageQueue;

// 初始化队列
void queue_init(MessageQueue *q)
{
    q->head = 0;
    q->tail = 0;
    q->count = 0;
    memset(q->buffer, 0, sizeof(q->buffer));
}

// 入队(使用二级指针接收消息)
int queue_enqueue(MessageQueue *q, Message **msg_ptr)
{
    if(q->count >= MAX_QUEUE_SIZE)
    {
        printf("队列已满!\n");
        return -1;
    }

    // 将消息指针存入队列
    q->buffer[q->tail] = *msg_ptr;
    q->tail = (q->tail + 1) % MAX_QUEUE_SIZE;
    q->count++;

    return 0;
}

// 出队(通过二级指针返回消息)
int queue_dequeue(MessageQueue *q, Message **msg_ptr)
{
    if(q->count <= 0)
    {
        printf("队列为空!\n");
        return -1;
    }

    *msg_ptr = q->buffer[q->head];
    q->head = (q->head + 1) % MAX_QUEUE_SIZE;
    q->count--;

    return 0;
}

int main(void)
{
    MessageQueue queue;
    queue_init(&queue);

    // 创建消息
    Message msg1 = {1, "First message"};
    Message msg2 = {2, "Second message"};
    Message msg3 = {3, "Third message"};

    Message *msg_ptr = &msg1;
    queue_enqueue(&queue, &msg_ptr);

    msg_ptr = &msg2;
    queue_enqueue(&queue, &msg_ptr);

    msg_ptr = &msg3;
    queue_enqueue(&queue, &msg_ptr);

    // 出队消息
    printf("出队消息:\n");
    Message *out_msg;
    while(queue_dequeue(&queue, &out_msg) == 0)
    {
        printf("ID: %d, Data: %s\n", out_msg->id, out_msg->data);
    }

    return 0;
}

4.2 嵌入式案例: 设备回调注册

#include <stdio.h>

// 回调函数类型定义
typedef void (*DeviceCallback)(int event, void *data);

#define MAX_CALLBACKS 5

// 回调管理器
typedef struct {
    DeviceCallback callbacks[MAX_CALLBACKS];  // 回调函数指针数组
    void *user_data[MAX_CALLBACKS];          // 用户数据指针数组
    int count;
} CallbackManager;

// 注册回调(使用二级指针接收回调函数)
int register_callback(CallbackManager *mgr, DeviceCallback *cb, void *data)
{
    if(mgr->count >= MAX_CALLBACKS)
        return -1;

    mgr->callbacks[mgr->count] = *cb;
    mgr->user_data[mgr->count] = data;
    mgr->count++;

    return 0;
}

// 触发回调
void trigger_callbacks(CallbackManager *mgr, int event)
{
    for(int i = 0; i < mgr->count; i++)
    {
        if(mgr->callbacks[i] != NULL)
        {
            mgr->callbacks[i](event, mgr->user_data[i]);
        }
    }
}

// 示例回调函数
void on_button_press(int event, void *data)
{
    printf("按钮按下! 事件ID: %d, 数据: %d\n", event, *(int*)data);
}

void on_sensor_ready(int event, void *data)
{
    printf("传感器就绪! 事件ID: %d\n", event);
}

int main(void)
{
    CallbackManager mgr = {0};

    int button_data = 100;
    DeviceCallback cb = on_button_press;
    register_callback(&mgr, &cb, &button_data);

    cb = on_sensor_ready;
    register_callback(&mgr, &cb, NULL);

    printf("触发事件:\n");
    trigger_callbacks(&mgr, 1);
    trigger_callbacks(&mgr, 2);

    return 0;
}

4.3 嵌入式案例: 链表节点插入

#include <stdio.h>
#include <stdlib.h>

// 链表节点
typedef struct Node {
    int data;
    struct Node *next;
} Node;

// 在头部插入节点(使用二级指针修改头指针)
void insert_at_head(Node **head_ref, int data)
{
    Node *new_node = (Node*)malloc(sizeof(Node));
    new_node->data = data;
    new_node->next = *head_ref;  // 新节点指向原头节点
    *head_ref = new_node;        // 更新头指针
}

// 打印链表
void print_list(Node *head)
{
    Node *current = head;
    while(current != NULL)
    {
        printf("%d -> ", current->data);
        current = current->next;
    }
    printf("NULL\n");
}

// 释放链表
void free_list(Node **head_ref)
{
    Node *current = *head_ref;
    Node *next;

    while(current != NULL)
    {
        next = current->next;
        free(current);
        current = next;
    }

    *head_ref = NULL;
}

int main(void)
{
    Node *head = NULL;

    // 插入节点
    insert_at_head(&head, 10);
    insert_at_head(&head, 20);
    insert_at_head(&head, 30);

    printf("链表内容: ");
    print_list(head);

    // 释放链表
    free_list(&head);
    printf("释放后: ");
    print_list(head);

    return 0;
}

4.4 嵌入式案例: 配置参数动态更新

#include <stdio.h>
#include <string.h>

// 配置参数结构
typedef struct {
    int baudrate;
    int timeout;
    char mode[16];
} Config;

// 配置表
typedef struct {
    Config *configs[10];  // 配置指针数组
    int count;
} ConfigTable;

// 添加配置(使用二级指针)
void add_config(ConfigTable *table, Config **config_ptr)
{
    if(table->count < 10)
    {
        table->configs[table->count] = *config_ptr;
        table->count++;
    }
}

// 更新配置(通过索引和二级指针)
void update_config(ConfigTable *table, int index, Config **new_config)
{
    if(index >= 0 && index < table->count)
    {
        table->configs[index] = *new_config;
    }
}

// 打印配置
void print_configs(ConfigTable *table)
{
    for(int i = 0; i < table->count; i++)
    {
        printf("Config[%d]: baudrate=%d, timeout=%d, mode=%s\n",
               i, table->configs[i]->baudrate,
               table->configs[i]->timeout,
               table->configs[i]->mode);
    }
}

int main(void)
{
    ConfigTable table = {0};

    Config cfg1 = {115200, 1000, "UART"};
    Config cfg2 = {9600, 500, "I2C"};
    Config cfg3 = {400000, 100, "SPI"};

    Config *cfg_ptr = &cfg1;
    add_config(&table, &cfg_ptr);

    cfg_ptr = &cfg2;
    add_config(&table, &cfg_ptr);

    cfg_ptr = &cfg3;
    add_config(&table, &cfg_ptr);

    printf("初始配置:\n");
    print_configs(&table);

    // 更新第一个配置
    Config new_cfg1 = {57600, 2000, "UART_UPDATED"};
    cfg_ptr = &new_cfg1;
    update_config(&table, 0, &cfg_ptr);

    printf("\n更新后:\n");
    print_configs(&table);

    return 0;
}

五、与一级指针的区别

5.1 对比表

特性一级指针 int *p二级指针 int **pp
存储内容变量的地址指针的地址
解引用次数1次 *p2次 **pp
直接访问变量值 *p一级指针 *pp
主要用途修改变量值修改指针指向
内存层级2层(指针→变量)3层(二级指针→一级指针→变量)

5.2 使用场景对比

// 一级指针: 修改变量值
void modify_value(int *p)
{
    *p = 100;  // 修改p指向的变量
}

// 二级指针: 修改指针指向
void modify_pointer(int **pp)
{
    static int new_var = 200;
    *pp = &new_var;  // 修改pp指向的指针
}

5.3 选择标准

使用一级指针当:

  • 只需要修改变量的值
  • 传递数组参数
  • 避免大结构体拷贝

使用二级指针当:

  • 需要修改指针本身的指向
  • 在函数内分配内存并返回指针
  • 处理二维数组
  • 实现链表、树等动态数据结构
  • 需要返回多个指针参数

六、常见错误与陷阱

6.1 未初始化的二级指针

// ❌ 错误
int **pp;
**pp = 100;  // 野指针!

// ✅ 正确
int a = 100;
int *p = &a;
int **pp = &p;

6.2 混淆解引用层次

int a = 100;
int *p = &a;
int **pp = &p;

// ❌ 错误: 类型不匹配
*pp = 100;  // pp是int**, *pp是int*,不能赋值100

// ✅ 正确
*pp = &a;   // *pp是int*,可以赋值int*
**pp = 100; // **pp是int,可以赋值100

6.3 内存分配错误

// ❌ 错误: 只分配了指针数组,没有分配每行的空间
int **matrix = (int**)malloc(3 * sizeof(int*));
matrix[0][0] = 10;  // 未分配内存!

// ✅ 正确
int **matrix = (int**)malloc(3 * sizeof(int*));
for(int i = 0; i < 3; i++)
{
    matrix[i] = (int*)malloc(4 * sizeof(int));
}
matrix[0][0] = 10;  // 正确

6.4 释放内存顺序错误

int **matrix = create_2d_array(3, 4);

// ❌ 错误顺序
free(matrix);  // 先释放外层,内层内存泄漏!

// ✅ 正确顺序
for(int i = 0; i < 3; i++)
{
    free(matrix[i]);  // 先释放内层
}
free(matrix);  // 再释放外层

6.5 传递参数错误

void func(int **pp)
{
    *pp = (int*)malloc(sizeof(int));
}

// ❌ 错误: 传一级指针
int *p = NULL;
func(p);  // p的值会被拷贝,原p不会改变

// ✅ 正确: 传指针的地址
int *p = NULL;
func(&p);  // 传p的地址

七、GDB调试二级指针

7.1 测试代码

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int a = 100;
    int *p = &a;
    int **pp = &p;

    printf("a = %d\n", a);
    printf("*p = %d\n", *p);
    printf("**pp = %d\n", **pp);

    return 0;
}

7.2 GDB调试命令

# 编译调试版本
gcc -g pointer2_debug.c -o pointer2_debug

# 启动GDB
gdb ./pointer2_debug

# 设置断点并运行
(gdb) break main
(gdb) run

# 查看变量地址
(gdb) print &a
(gdb) print &p
(gdb) print &pp

# 查看指针指向
(gdb) print p
(gdb) print pp

# 查看各层值
(gdb) print a
(gdb) print *p
(gdb) print **pp

# 查看内存
(gdb) x /d &a
(gdb) x /d p
(gdb) x /d *pp

# 查看指针链
(gdb) print *pp
(gdb) print **pp

7.3 内存查看示例(不同硬件有差异)

(gdb) print &a
$1 = (int *) 0x7fffffffe04c

(gdb) print &p
$2 = (int **) 0x7fffffffe040

(gdb) print &pp
$3 = (int ***) 0x7fffffffe038

(gdb) print pp
$4 = (int **) 0x7fffffffe040

(gdb) print *pp
$5 = (int *) 0x7fffffffe04c

(gdb) print **pp
$6 = 100

(gdb) x /d &a
0x7fffffffe04c:    100

(gdb) x /x p
0x7fffffffe040:    0x7fffffffe04c

(gdb) x /x *pp
0x7fffffffe04c:    0x00000064

八、实战练习

练习1: 二级指针基础

任务: 编写程序,实现以下功能

  1. 定义一个变量和一级指针
  2. 定义二级指针指向一级指针
  3. 通过二级指针修改变量值
  4. 打印各层地址和值

练习2: 动态二维数组

任务: 实现动态创建、初始化、打印、释放二维数组的完整函数

int** create_matrix(int rows, int cols);
void init_matrix(int **matrix, int rows, int cols);
void print_matrix(int **matrix, int rows, int cols);
void free_matrix(int **matrix, int rows);

练习3: 字符串排序

任务: 使用二级指针对字符串数组进行排序

void sort_strings(char **strings, int count);

练习4: 链表操作

任务: 实现链表的插入、删除、查找操作(使用二级指针修改头指针)

练习5: 消息队列

任务: 实现一个环形消息队列,支持入队和出队操作


附录: 快速参考

A.1 二级指针声明速查

声明含义
int **pp指向int指针的指针
int ***ppp指向int二级指针的指针
int **arr[5]包含5个int二级指针的数组
int *(*arr)[5]指向包含5个int指针数组的指针

A.2 常用模式

// 模式1: 函数内修改指针
void func(Type **ptr)
{
    *ptr = malloc(sizeof(Type));
}

// 模式2: 二维数组访问
int **matrix;
matrix[i][j]  // 等价于 *(*(matrix + i) + j)

// 模式3: 字符串数组
char *strings[] = {"a", "b", "c"};
char **str_ptr = strings;  // 数组名退化为二级指针

A.3 内存分配模板

// 二维数组
int **array = (int**)malloc(rows * sizeof(int*));
for(int i = 0; i < rows; i++)
{
    array[i] = (int*)malloc(cols * sizeof(int));
}

// 释放
for(int i = 0; i < rows; i++)
{
    free(array[i]);
}
free(array);

总结

二级指针核心要点:

  1. 本质: 指向指针的指针,存储一级指针的地址
  2. 解引用: *pp得到一级指针,**pp得到最终变量
  3. 核心应用:
    • 修改指针本身的指向
    • 动态内存分配
    • 二维数组操作
    • 链表/树等动态结构
  4. 安全第一: 正确初始化、合理分配/释放内存
  5. 调试技巧: GDB分层查看,追踪指针链

学习路径:

  1. ✅ 理解指针链: 二级指针→一级指针→变量
  2. ✅ 掌握解引用层次
  3. ✅ 练习动态内存分配
  4. ✅ 实现链表/树结构
  5. ✅ 在实际项目中应用

记住: 二级指针不是黑魔法,只是多了一层间接访问,理解内存模型就能轻松掌握!

C核心高级技术 C语言广泛应用于系统编程、嵌入式开发、高性能计算等领域,其核心高级技术包括指针操作、内存管理和多线程编程。多级指针用于动态创建多维数组,函数指针实现灵活回调机制;自定义内存分配器可优化性能关键场景;多线程编程需合理使用互斥锁等同步机制。这些技术通过代码示例和内存布局图示(如Mermaid图表)得到清晰展示,体现了C语言在底层控制和高性能计算中的独特优势。 阅读详情

相关推荐

万字详解C语言高级特性

在 C 语言的内存管理领域,自定义内存池是一项极具价值的技术,它能够显著提升内存分配的效率,有效减少内存碎片的产生。内存池,就像是一个精心管理的内存仓库,在程序运行之前,预先分配出一大块连续的内存空间。当程序需要分配内存时,不是直接向操作系统申请,而是从这个内存池中获取。当程序释放内存时,也不是立即归还给操作系统,而是将其标记为可用,留待下次分配使用。这样一来,避免了频繁地向操作系统申请和释放内存所带来的开销,大大提高了内存分配的速度。1024// 内存池结构体// 初始化内存池。

2301_78981884的博客 1333

嵌入式经验总结——c语言(指针)

C语言是一门通用的计算机编程语言,应用广泛。它把高级语言的基本结构和语句与低级语言的实用性结合起来。很多学习软件设计开发的小伙伴都应该比较了解c语言这一课程,都或多或少被指针,数组折磨,今天就分享一下我的经验,大家一起相互学习一起进步。...

free_malloc_1的博客 670

嵌入式C语言中二级指针主要用途及其在函数参数传递应用

本文探讨了嵌入式开发中二级指针的重要性及其应用场景。二级指针(Pointer to Pointer)主要用于修改指针变量的值、传递指针数组、处理动态二维数组以及操作链表和树等数据结构。文章还分析了传值调用与传址调用的区别,指出传址调用通过传递指针地址,可直接修改原始变量,而传值调用仅操作副本。通过示例代码,展示了如何利用二级指针在函数中修改指针的指向和值。最终,文章总结了二级指针在嵌入式开发中的关键作用,强调其对于高效内存管理和复杂数据操作的重要性。

嵌入式技术开发 511

嵌入式 C 语言大神的进阶之路

掌握的程度:掌握一个模块的封装与调用、函数接口的声明与定义、C语言的多文件编程,能做一些简单的C语言项目,但需要参考一些案例,在大学学过《C语言程序设计》相关的书籍,掌握的不错的获得计算机二级等考证,应付个期末考试,顺便拿个毕业证啥的。和他们在一起工作的心情是愉快的,仿佛在打一场王者高端局,执行到位,配合娴熟,时不时来一波小高潮,让人酣畅淋漓。他们不愁工作,仿佛自带光圈,到哪里,哪里一片光明,他们工作的气场很强大,已经超越了嵌入式、C语言本身,

嵌入式技术开发 821

嵌入式C 语言中的三块技术难点

​ C 语言在嵌入式学习中是必备的知识,甚至大部分操作系统都要围绕 C 语言进行,而其中有,几乎是公认级别的“今天就来带你将这三块硬骨头细细拆解开来,一定让你看明白了。

嵌入式技术开发 1468

嵌入式系统中的高级C语言编程技巧

指针是C语言中一种基础且强大的数据类型,用于存储变量的内存地址。一个指针变量可以指向任何类型的数据,比如整数、字符或函数。它提供了一种间接访问内存的方式,允许程序在运行时动态地访问和操作数据。在C语言中,定义指针的语法如下:数据类型 *指针变量名;其中,用于声明指针变量,数据类型表示指针指向的变量类型。以下是一个指针定义的示例:int *p;这行代码声明了一个名为p的指针变量,它可以指向一个int类型的变量。

weixin_42497762的博客 1115

STM32智能头盔DIY全攻略:从心率监测到跌倒报警的完整实现

本文提供了基于STM32的智能头盔完整DIY实现方案,涵盖从心率监测到跌倒报警的核心功能。详细解析了系统设计、硬件搭建、软件算法及系统联调,重点介绍了STM32主控、MAX30102心率传感器和MPU6050姿态传感器的应用,并分享了跌倒检测算法与功耗优化等实战经验,为嵌入式开发与物联网创新项目提供全面指导。

sony5的博客 498

AD9361 官方例程

文章目录一、官方资料1.1 介绍1.2 下载 和 使用1.3 相关资料二、步骤2.1 在Vivado中构建HDL2.2 在SDK中构建No- OS总结 一、官方资料 1.1 介绍 HDL参考设计介绍:AD9361 HDL Reference Designs 无操作系统介绍:AD9361 No-OS Software 1.2 下载 和 使用 HDL下载:Download HDL 在Vivado中构建HDL:Building HDL No-OS下载:Download No-OS 1.3 相关资料 AD

weixin_44630490的博客 3万+

中文文本纠错实战:pycorrector与kenlm的安装与高效使用指南

本文提供了一份详细的中文文本纠错实战指南,重点介绍了pycorrector与kenlm的安装与高效使用方法。内容涵盖从编译环境准备、依赖安装到核心参数调优、批量处理及领域自适应等全流程,旨在帮助开发者快速部署并优化纠错系统,提升中文文本处理的质量与效率。

weixin_29055699的博客 411

OpenCV快速入门:绘制图形、图像金字塔和感兴趣区域

OpenCV是一个强大的计算机视觉库,广泛应用于图像处理、计算机视觉和机器学习等领域。本文将介绍OpenCV中的一些基础功能,包括如何绘制图形、创建图像金字塔以及如何处理感兴趣区域(ROI)。

吾家有猫名探花的博客 1962

简单说说Python中的pip如何换源

【安装包】:pip install 【升级包】:pip install --upgrade 【卸载包】:pip uninstall 【查看已安装的包】:pip list【搜索包】:pip search (注意:pip search功能在pip 10.0.0之后被弃用,建议使用其他方式搜索包,如访问PyPI官网或使用其他搜索引擎)

一捌柒的博客 2667

如何设计提示词来减少 AI 的幻觉问题?

如何设计提示词来减少 AI 的幻觉问题?

我哎GIS博客 2468

【OpenCV】第二十四章: 图像特征匹配:暴力特征匹配算法、FLANN特征匹配算法

参数match就是匹配器的匹配结果;参数crossCheck表示交叉检查,意思就是用第一组中的每个描述子和第二组的所有描述子进行匹配完后,再用第二组中的每个描述子和第一组的所有描述子进行匹配,这两步做完后,两步同时找到的相同的匹配对儿就是有效的匹配。方法一:用匹配器的match方法,进行特征匹配:match = bf.match(des1, des2),就是对两幅图的描述子进行计算,返回匹配的结果。匹配的时候是计算两个描述子之间的相似度,然后返回相似度最高的一对儿匹配点,也就是最终的匹配结果。

因为热爱所以坚持 5061

《手把手教你YOLOv9实战》,改进专栏目录

为了提供友好的操作效果,本专栏将会手把手搭建可视化界面,我将用PyQt5 搭建一个可视化界面,可视化界面能够实现基本的图像加载与检测功能,如支持视频、摄像头,还支持更换不同的 YOLOv9 模型,并适应不同领域(如口罩检测系统、人脸检测系统、工业缺陷检测系统等),界面灵活、功能强大。通过YOLOv9加以改进设计,形成新的算法框架,一起水科研和论文,专栏会一直持续更新中,本专栏适合目标检测、分割、分类。

在职AI算法工程师,擅长计算机视觉,YOLO目标检测、分割等,擅长web、pyqt界面可视化,好内容持续更新中,来这里跟大家一起学习,共同进步 2153

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

上一篇: 嵌入式 C 语言高级编程之一级指针
下一篇: 嵌入式C语言高级编程之DRY原则
CPUOS2010
博客等级 码龄5年 88粉丝 33原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CPUOS2010

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值