嵌入式之C/C++(三)指针

指针是 C/C++ 的灵魂,更是嵌入式开发的核心考点。本文系统梳理数组指针、指针数组、函数指针、指针函数等易混淆概念,结合代码示例和面试真题,拆解指针的底层逻辑,帮你彻底掌握指针的使用技巧和避坑方法。

1 数组指针与指针数组

  • 数组指针 :本质是指针,指向一个数组(重点:指针);
  • 指针数组 :本质是数组,数组元素都是指针(重点:数组)。

1.1 数组指针(指向数组的指针)

cs 复制代码
// 指向包含N个T类型元素的数组的指针
T (*p)[N];
  • (*p)表明p是指针;
  • [N]表明该指针指向一个长度为N的数组;
  • T表明数组元素的类型。
cs 复制代码
#include <stdio.h>

int main() {
    // 一维数组,可视为3个长度为4的子数组:{1,2,3,4}、{5,6,7,8}、{9,10,11,12}
    int b[12] = {1,2,3,4,5,6,7,8,9,10,11,12};
    int (*p)[4]; // 数组指针:指向包含4个int的数组
    
    p = b; // p指向第一个子数组{1,2,3,4}
    // ++p:指针向后移动1个数组长度(4个int=16字节),指向{5,6,7,8}
    // *++p:取该数组的首地址;**++p:取数组第一个元素5
    printf("%d\n", **(++p)); 
    return 0;
}

输出结果5

核心特点:

  • 指针步长 = 指向数组的总字节数(如上例中p++移动4×4=16字节);
  • 常用于遍历二维数组(视为 "数组的数组")。

1.2 指针数组(元素为指针的数组)

cs 复制代码
// 数组包含N个元素,每个元素是指向T类型的指针
T *p[N];
  • p[N] 表明p是长度为N的数组;
  • T* 表明数组元素是指向T类型的指针。
cs 复制代码
#include <stdio.h>

int main() {
    int *p[4]; // 指针数组:4个元素,每个元素是int*
    int a[4] = {1,2,3,4};
    
    // 数组元素指向a的对应位置
    p[0] = &a[0];
    p[1] = &a[1];
    p[2] = &a[2];
    p[3] = &a[3];
    
    // 遍历指针数组,解引用取值
    for(int i=0; i<4; i++) {
        printf("%d", *p[i]);
    }
    printf("\n");
    return 0;
}

输出结果1234

核心特点:

  • 数组每个元素独立指向不同内存地址;
  • 常用于存储多个字符串(如char *strs[] = {"a", "b", "c"})。

1.3 快速记忆技巧

  • 看括号/优先级:(*p)[N]先算*p(指针),*p[N]先算p[N](数组);
  • 口诀:数组指针是指针,指针数组是数组

2 函数指针与指针函数

  • 函数指针 :本质是指针,指向一个函数(重点:指针);
  • 指针函数 :本质是函数,返回值是指针(重点:函数)。

2.1 函数指针(指向函数的指针)

cs 复制代码
// 指向"返回值为T、参数为T1/T2/...的函数"的指针
T (*p)(T1, T2, ...);
  • (*p) 表明p是指针;
  • (T1, T2, ...) 表明指向的函数参数类型;
  • T 表明指向的函数返回值类型。
cs 复制代码
#include <stdio.h>

// 普通函数:返回两个int的最大值
int Max(int x, int y) {
    return x > y ? x : y;
}

int main() {
    int(*p)(int, int); // 函数指针:指向返回int、参数为两个int的函数
    p = Max; // 函数名就是函数地址,赋值给指针
    
    int a = 10, b = 20, c;
    c = (*p)(a, b); // 通过函数指针调用Max
    // 简化写法:c = p(a, b); (函数指针可省略*)
    printf("max = %d\n", c);
    return 0;
}

输出结果max = 20

核心特点

  • 函数指针无++/--运算(函数地址固定);
  • 常用于回调函数(如中断处理、排序函数qsort)。

1.2 指针函数(返回指针的函数)

cs 复制代码
// 返回值为指向T类型指针的函数,参数为T1/T2/...
T *p(T1, T2, ...);
  • p(...) 表明p是函数;
  • T* 表明函数返回值是指向T类型的指针。
cs 复制代码
#include <stdio.h>

// 指针函数:返回指向float数组的指针
float *find(float(*pointer)[4], int n) {
    float *pt;
    pt = *(pointer + n); // 指向第n行数组的首地址
    return pt;
}

int main() {
    // 3个学生的4门成绩(二维数组)
    static float score[][4] = {{60,70,80,90},{56,89,34,45},{34,23,56,45}};
    float *p;
    int m;
    
    printf("输入要查询的学生序号(1-3):");
    scanf("%d", &m);
    p = find(score, m-1); // 调用指针函数,返回第m个学生的成绩首地址
    
    printf("第%d个学生的成绩:\n", m);
    for(int i=0; i<4; i++) {
        printf("%5.2f\t", *(p+i));
    }
    return 0;
}

输出示例

复制代码
输入要查询的学生序号(1-3):2
第2个学生的成绩:
56.00    89.00    34.00    45.00

3 数组名与指针的区别与联系

| 维度 | 数组名 | 指针 |
| 本质 | 数组首元素地址(常量) | 存储地址的变量 |
| 内存占用 | 无额外占用(仅表示地址) | 占用 4/8 字节(32/64 位系统) |
| 可修改性 | 不可修改(a++非法) | 可修改(p++合法) |
| 访问方式 | 直接访问(a[i]) | 间接访问(*p) |
| sizeof | 数组总字节数(sizeof(a)) | 指针本身字节数(sizeof(p)) |

&操作 &a指向整个数组(步长 = 数组长度) &p指向指针变量本身

关键示例

cs 复制代码
#include <stdio.h>

int main() {
    int a[5] = {1,2,3,4,5};
    int *p = a;
    
    printf("sizeof(a) = %zu\n", sizeof(a)); // 输出20(5×4)
    printf("sizeof(p) = %zu\n", sizeof(p)); // 输出8(64位系统)
    
    // a++;// 非法:数组名是常量
    p++; // 合法:指针变量可修改
    printf("*p = %d\n", *p); // 输出2
    return 0;
}

4 指针类型详解(常量指针/指向常量的指针)

4.1 常量指针(指针常量)

cs 复制代码
int *const p;
  • 解读:const修饰p(指针本身),指针指向不可改 ,指向的值可改

  • 示例:

    cs 复制代码
    int a=10, b=20;
    int *const p = &a;
    *p = 30; // 合法:修改指向的值
    // p = &b; // 非法:修改指针指向

4.2 指向常量的指针(常量的指针)

cs 复制代码
const int *p;
  • 解读:const修饰*p(指针指向的值),指针指向可改 ,指向的值不可改

  • 示例:

    cs 复制代码
    int a=10, b=20;
    const int *p = &a;
    // *p = 30; // 非法:修改指向的常量值
    p = &b; // 合法:修改指针指向

4.3 指向常量的常量指针

cs 复制代码
const int *const p;
  • 解读:指针指向不可改 ,指向的值也不可改

  • 示例:

    cs 复制代码
    int a=10, b=20;
    const int *const p = &a;
    // *p = 30; // 非法
    // p = &b; // 非法

4.4 快速记忆技巧

  • const位置:
    • const*前:值不可改(指向常量);
    • const*后:指针不可改(指针常量);
    • 两边都有:值和指针都不可改。

5 指针与引用的异同

5.1 相同点

  • 本质都是地址概念:指针存储地址,引用是变量的别名;
  • 都可间接访问变量,避免值拷贝(提升效率)。

5.2 核心区别

| 特性 | 指针 | 引用 |
| 本质 | 独立变量(存储地址) | 变量别名(无独立空间) |
| 初始化 | 可空、可后初始化 | 必须初始化、不可空 |
| 可修改性 | 可指向不同变量(p=&b) | 一旦绑定不可修改 |
| 解引用 | 需要**p) | 无需解引用(直接用) |
| ++运算 | 地址偏移(p++) | 值自增(q++) |

sizeof 指针本身大小(4/8 字节) 指向变量的大小
cs 复制代码
#include <stdio.h>

int main() {
    int x = 5;
    int *p = &x;   // 指针
    int &q = x;    // 引用
    
    printf("*p = %d, sizeof(p) = %zu\n", *p, sizeof(p)); // 5 8(64位)
    printf("q = %d, sizeof(q) = %zu\n", q, sizeof(q));   // 5 4(int大小)
    
    p++; // 指针地址偏移
    q++; // 引用值自增(x变为6)
    printf("x = %d\n", x); // 输出6
    return 0;
}

5.3 相互转换

  • 指针转引用:解引用指针(*p),作为引用参数;
  • 引用转指针:取引用地址(&q),得到指针。
cs 复制代码
void fun(int &x) { x++; }

int main() {
    int a = 5;
    int *p = &a;
    fun(*p); // 指针转引用
    int &q = a;
    int *p2 = &q; // 引用转指针
    printf("a = %d\n", a); // 输出6
    return 0;
}

6 野指针:成因与避免

6.1 野指针定义

指向不可用内存的指针(地址无效 / 已释放 / 越界),访问野指针会导致程序崩溃或内存错误。

6.2 野指针成因

  1. 指针未初始化(默认随机地址);
  2. 指针指向的内存被free/delete后未置NULL
  3. 指针操作超出变量作用域(如返回栈变量地址)。

6.3 避免方法

  1. 强制初始化

    cs 复制代码
    char *p = NULL; // 空指针
    char *p2 = (char*)malloc(10); // 指向合法内存
    if (p2 == NULL) { // 检查malloc是否成功
        perror("malloc failed");
        return 1;
    }
  2. 释放后置 NULL

    cs 复制代码
    free(p2);
    p2 = NULL; // 避免野指针
  3. 清空堆内存 (避免垃圾值):

    cs 复制代码
    // 清空p2指向的10字节内存为0
    memset(p2, 0, 10);
    // 或bzero
    bzero(p2, 10);

7 C++ 智能指针(解决内存泄漏)

7.1 核心问题

普通指针易导致内存泄漏(忘记释放)、二次释放、野指针,智能指针通过RAII 机制(资源获取即初始化)自动管理内存。

7.2 常用智能指针

  1. unique_ptr:独占所有权,禁止拷贝,避免二次释放;
  2. shared_ptr:共享所有权,引用计数,计数为 0 时释放内存;
  3. weak_ptr:弱引用,解决shared_ptr循环引用导致的内存泄漏。

7.3 循环引用问题与解决

cpp 复制代码
#include <memory>
using namespace std;

class B; // 前向声明
class A {
public:
    shared_ptr<B> b_ptr;
    ~A() { printf("A析构\n"); }
};
class B {
public:
    // 改为weak_ptr,避免循环引用
    weak_ptr<A> a_ptr; 
    ~B() { printf("B析构\n"); }
};

int main() {
    shared_ptr<A> a = make_shared<A>();
    shared_ptr<B> b = make_shared<B>();
    a->b_ptr = b;
    b->a_ptr = a;
    // 离开作用域时,引用计数为0,正常析构
    return 0;
}

输出

复制代码
B析构
A析构
相关推荐
热爱编程的小刘1 小时前
Lesson03---类与对象(中篇)
c++
lxl13072 小时前
学习C++(4)构造函数+析构函数+拷贝构造函数
开发语言·c++·学习
季明洵2 小时前
两数之和、四数相加II、三数之和、四数之和
java·数据结构·算法·leetcode·蓝桥杯·哈希算法
机器学习之心2 小时前
基于AHP(层次分析法)-模糊综合评价法的工程实践能力评价系统MATLAB代码
算法·matlab·层次分析法·模糊综合评价法
阿kun要赚马内2 小时前
Qt写群聊项目(二):客户端
开发语言·c++·qt
轩情吖2 小时前
数据结构-并查集
开发语言·数据结构·c++·后端··并查集
好学且牛逼的马2 小时前
【Hot100|18-LeetCode 54. 螺旋矩阵】
算法·leetcode·矩阵
YYYing.2 小时前
【Linux/C++进阶篇 (一)】man手册、gdb调试、静态库与动态库
linux·运维·c++
孞㐑¥2 小时前
算法—模拟
c++·经验分享·笔记·算法