什么是指针?

在C语言中,指针(pointer) 是一种特殊的变量,它存储的是另一个变量的内存地址。换句话说,指针”指向”内存中的某个位置。

指针的本质是内存地址。通过指针,我们可以直接访问或修改内存中的数据。

基本概念

  • 内存地址:计算机内存中每个字节都有一个唯一的地址
  • 指针变量:存储内存地址的变量
  • 间接访问:通过指针访问它所指向的值

指针的声明和初始化

指针的声明

数据类型 *指针变量名;

例如:

int *p;  // p 是一个指向 int 类型的指针
char *c; // c 是一个指向 char 类型的指针

指针的初始化: 声明指针变量时, 可以赋初值. 注意指针的类型必须与其指向的对象的类型一致.

int x;
int * px = &x; // 初始化

没有初始化或赋值的指针是无效的指针,引用无效指针会带来难以预料的后果。

指针运算符

C语言提供了两个特殊的指针运算符:

  • &(地址运算符):返回变量的内存地址
  • *(解引用运算符):返回指针所指向地址的值

&(取地址运算符)

& 用于获取变量的内存地址。

示例:获取变量的地址

#include <stdio.h>

int main() {
    int num = 10;
    printf("num 的值: %d\n", num);      // 输出 10
    printf("num 的地址: %p\n", &num);   // 输出 num 的内存地址(如 0x7ffd...)
    return 0;
}
  • &num 返回 num 的内存地址。

*(解引用运算符)

* 用于访问指针指向的内存地址中存储的值。

示例 :指针的基本使用

#include <stdio.h>

int main() {
    int num = 10;
    int *p = &num;  // p 存储 num 的地址

    printf("num 的值: %d\n", num);   // 10
    printf("p 存储的地址: %p\n", p); // 0x7ffd...(num 的地址)
    printf("*p 的值: %d\n", *p);     // 10(解引用 p,获取 num 的值)

    *p = 20;  // 通过指针修改 num 的值
    printf("修改后 num 的值: %d\n", num);  // 20

    return 0;
}
  • *p 表示访问 p 指向的变量的值(即 num)。
  • 修改 *p 相当于修改 num

指针与 const 关键字的结合

指针与 const 关键字的结合可以形成三种不同的指针类型,它们有不同的特性和用途:

  1. 指向常量的指针(Pointer to Constant)
  2. 常量指针(Constant Pointer)
  3. 指向常量的常量指针(Constant Pointer to Constant)

指向常量的指针(Pointer to Constant)

定义

const 数据类型 *指针名;

数据类型 const *指针名;  // 两种写法等价

特点

  • 指针可以修改(可以指向不同的地址)。
  • 不能通过该指针修改它所指向的数据(数据是 const 的)。
  • 适用于保护数据不被修改,常用于函数参数传递。

示例

#include <stdio.h>

int main() {
    int num = 10;
    const int *ptr = &num;  // ptr 是指向常量的指针

    printf("*ptr = %d\n", *ptr);  // 输出 10

    // *ptr = 20;  // 错误!不能通过 ptr 修改 num
    num = 20;      // 合法:直接修改 num

    printf("*ptr = %d\n", *ptr);  // 输出 20

    int another_num = 30;
    ptr = &another_num;  // 合法:指针可以指向新地址
    printf("*ptr = %d\n", *ptr);  // 输出 30

    return 0;
}

关键点:

  • ptr 可以重新指向不同的变量(ptr = &another_num)。
  • 但不能通过 ptr 修改数据(*ptr = 20 会报错)。

常量指针(Constant Pointer)

常量指针, 简称常指针: 指针本身的值不能修改.

定义

数据类型 *const 指针名;

特点

  • 指针不能修改(始终指向同一个地址)。
  • 可以通过该指针修改它所指向的数据(数据不是 const 的)。
  • 适用于固定内存地址访问,如硬件寄存器操作。

示例

#include <stdio.h>

int main() {
    int num = 10;
    int *const ptr = &num;  // ptr 是常量指针

    printf("*ptr = %d\n", *ptr);  // 输出 10

    *ptr = 20;  // 合法:可以通过 ptr 修改 num
    printf("*ptr = %d\n", *ptr);  // 输出 20

    // ptr = &another_num;  // 错误!ptr 不能指向新地址

    return 0;
}

关键点:

  • ptr 不能重新赋值(ptr = &another_num 会报错)。
  • 但可以通过 ptr 修改数据(*ptr = 20 合法)。

指向常量的常量指针(Constant Pointer to Constant)

定义

const 数据类型 *const 指针名;

特点

  • 指针不能修改(始终指向同一个地址)。
  • 也不能通过该指针修改它所指向的数据(数据是 const 的)。
  • 适用于完全只读访问,如字符串常量或硬件只读寄存器。

示例

#include <stdio.h>

int main() {
    int num = 10;
    const int *const ptr = &num;  // ptr 是指向常量的常量指针

    printf("*ptr = %d\n", *ptr);  // 输出 10

    // *ptr = 20;  // 错误!不能通过 ptr 修改 num
    num = 20;      // 合法:直接修改 num

    printf("*ptr = %d\n", *ptr);  // 输出 20

    // ptr = &another_num;  // 错误!ptr 不能指向新地址

    return 0;
}

关键点:

  • ptr 既不能重新赋值(ptr = &another_num 报错)。
  • 也不能通过 ptr 修改数据(*ptr = 20 报错)。

实际应用场景

指向常量的指针(保护函数参数)

void print_array(const int *arr, int size) {
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
        // arr[i] = 0;  // 错误!不能修改数据
    }
}

作用: 防止函数内部意外修改外部数据。

常量指针(硬件寄存器访问)

volatile uint32_t *const GPIO_PORT = (uint32_t *)0x40000000;
*GPIO_PORT = 0xFF;  // 合法:修改寄存器值
// GPIO_PORT = 0x50000000;  // 错误!指针不能修改

作用: 确保指针始终指向固定的硬件地址。

指向常量的常量指针(字符串常量)

const char *const MESSAGE = "Hello, World!";
printf("%s\n", MESSAGE);
// MESSAGE[0] = 'h';  // 错误!不能修改数据
// MESSAGE = "New";   // 错误!指针不能修改

作用: 确保字符串完全不可修改。

总结

  • const int *ptr(指向常量的指针):数据不可变,指针可变。
  • int *const ptr(常量指针):指针不可变,数据可变。
  • const int *const ptr(指向常量的常量指针):指针和数据都不可变。

核心规则:

  • const* 左边 → 数据不可变
  • const* 右边 → 指针不可变

指针算术运算

指针算术运算(Pointer Arithmetic)是C语言中一项强大但需要谨慎使用的特性。它允许我们对指针进行加减运算,从而在内存中移动指针位置。

基本概念

指针算术运算主要包括以下几种操作:

  • 指针与整数的加减(+, -, +=, -=, ++, --
  • 指针与指针的减法(得到的是两个指针之间的距离)
  • 指针的比较(>, <, >=, <=, ==, !=

重要特性

  • 指针运算的单位是所指向类型的大小,而不是字节
  • void*指针不能直接进行算术运算(因为编译器不知道类型大小)

指针与整数的运算

基本运算规则

int arr[5] = {10, 20, 30, 40, 50};
int *ptr = arr;  // 等价于 &arr[0]

ptr = ptr + 1;   // 现在指向arr[1]
ptr += 2;        // 现在指向arr[3]
ptr--;           // 现在指向arr[2]

内存变化(假设int占4字节):

  • ptr + 1 实际上是 ptr + 1*sizeof(int) = 原地址 + 4字节
  • ptr + n 实际上是 ptr + n*sizeof(int)

示例代码

#include <stdio.h>

int main() {
    int arr[] = {10, 20, 30, 40, 50};
    int *ptr = arr;

    printf("初始指针指向: %p, 值: %d\n", ptr, *ptr);

    ptr++;
    printf("ptr++ 后指向: %p, 值: %d\n", ptr, *ptr);

    ptr += 2;
    printf("ptr+=2 后指向: %p, 值: %d\n", ptr, *ptr);

    ptr--;
    printf("ptr-- 后指向: %p, 值: %d\n", ptr, *ptr);

    return 0;
}

指针与指针的运算

指针减法

两个相同类型的指针相减,得到的是它们之间相隔的元素个数:

int arr[5] = {0};
int *p1 = &arr[0];
int *p2 = &arr[3];

ptrdiff_t diff = p2 - p1;  // 结果是3,不是字节差

注意

  • 结果是ptrdiff_t类型(定义在stddef.h中的有符号整数类型)
  • 只有指向同一数组的指针相减才有意义

示例代码

#include <stdio.h>
#include <stddef.h>

int main() {
    int arr[] = {10, 20, 30, 40, 50};
    int *start = &arr[0];
    int *end = &arr[4];

    ptrdiff_t distance = end - start;
    printf("两个指针之间的距离: %td 个元素\n", distance);
    printf("实际字节差: %td 字节\n", 
          (char*)end - (char*)start);

    return 0;
}

指针比较运算

可以比较两个指针的大小关系(通常用于同一数组或内存块中的指针):

int arr[5] = {0};
int *p1 = &arr[0];
int *p2 = &arr[3];

if (p1 < p2) {
    printf("p1在p2之前\n");
}

注意

  • 比较不同数组的指针结果是未定义的
  • 比较NULL指针是允许的

不同类型指针的运算差异

指针运算的步长取决于指向类型的大小:

数据类型 典型大小 ptr+1的地址增量
char 1字节 +1
int 4字节 +4
double 8字节 +8
结构体 不定 +sizeof(结构体)

示例代码

#include <stdio.h>

int main() {
    char char_arr[5] = {'a','b','c','d','e'};
    int int_arr[5] = {1,2,3,4,5};

    char *cptr = char_arr;
    int *iptr = int_arr;

    printf("char指针运算:\n");
    printf("cptr: %p, cptr+1: %p (差 %ld 字节)\n", 
          (void*)cptr, (void*)(cptr+1), (long)(cptr+1)-(long)cptr);

    printf("\nint指针运算:\n");
    printf("iptr: %p, iptr+1: %p (差 %ld 字节)\n", 
          (void*)iptr, (void*)(iptr+1), (long)(iptr+1)-(long)iptr);

    return 0;
}

指针运算的常见应用

(1) 数组遍历

int arr[5] = {1,2,3,4,5};
for(int *p = arr; p < arr+5; p++) {
    printf("%d ", *p);
}

(2) 字符串处理

char str[] = "Hello";
char *p = str;
while(*p != '\0') {
    putchar(*p);
    p++;
}

(3) 动态内存管理

int *arr = malloc(5 * sizeof(int));
if(arr) {
    for(int i=0; i<5; i++) {
        *(arr + i) = i * 10;  // 等价于arr[i]
    }
    free(arr);
}

(4) 多维数组访问

int matrix[3][3] = {{1,2,3},{4,5,6},{7,8,9}};
int *ptr = &matrix[0][0];
for(int i=0; i<9; i++) {
    printf("%d ", *(ptr + i));
}

特殊指针的运算

void指针的运算

void*指针不能直接进行算术运算,必须先转换为具体类型:

void *vp = malloc(100);
// vp++;  // 错误!
char *cp = (char*)vp;
cp++;    // 正确

函数指针的运算

函数指针的算术运算通常是未定义行为,应避免使用。

注意事项和常见错误

  1. 指针越界访问

    int arr[5] = {0};
    int *p = arr + 5;  // 指向数组末尾之后
    *p = 10;           // 未定义行为!
    
  2. 不同类型的指针运算

    int i = 0;
    char *cp = (char*)&i;
    int *ip = &i;
    if(cp == ip) { ... }  // 合法比较
    // ptrdiff_t diff = cp - ip;  // 非法!
    
  3. 无效指针运算

    int *p1, *p2;
    // int sum = p1 + p2;  // 非法操作!
    
  4. 指针运算与数组索引的等价性

    int arr[5];
    arr[2] = 10;     // 等价于
    *(arr + 2) = 10; // 这两种写法完全等价
    

高级应用:自定义内存管理

指针运算在实现自定义内存分配器时非常有用:

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

#define POOL_SIZE 1024

typedef struct {
    unsigned char data[POOL_SIZE];
    unsigned char *next_free;
} MemoryPool;

void init_pool(MemoryPool *pool) {
    pool->next_free = pool->data;
}

void* pool_alloc(MemoryPool *pool, size_t size) {
    if(pool->next_free + size > pool->data + POOL_SIZE) {
        return NULL; // 内存不足
    }
    void *ptr = pool->next_free;
    pool->next_free += size;
    return ptr;
}

int main() {
    MemoryPool pool;
    init_pool(&pool);

    int *num = pool_alloc(&pool, sizeof(int));
    *num = 42;

    double *dbl = pool_alloc(&pool, sizeof(double));
    *dbl = 3.14;

    printf("Allocated values: %d, %.2f\n", *num, *dbl);

    return 0;
}

总结

指针算术运算的核心要点:

  1. 指针加减整数时,步长由指向类型的大小决定
  2. 只有指向同一数组/内存块的指针相减才有意义
  3. 指针比较通常用于检查指针的相对位置
  4. void*指针需要转换后才能进行算术运算
  5. 使用指针运算时要特别注意边界检查,避免越界访问

正确使用指针算术可以写出高效灵活的代码,但不当使用也会导致难以调试的内存错误。建议:

  • 在数组遍历时优先使用索引表示法(arr[i]
  • 必须使用指针运算时,添加清晰的注释
  • 进行边界检查,避免越界访问
  • 使用const修饰符保护不应被修改的数据

指针和数组

指针与一维数组

数组名本质上是一个指向数组第一个元素的常量指针。在 C 语言中,由于数组元素在内存中是连续存放的,因此使用指针可以非常方便地处理数组元素。

引用数组元素的四种方式:

  • 数组名 + 下标;
  • 数组名 + 指针运算;
  • 指针 + 指针运算;
  • 指针 + 下标(数组运算).
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;  // 等价于 int *p = &arr[0]

// 以下两种方式等价
printf("arr[2] = %d\n", arr[2]);
printf("*(p+2) = %d\n", *(p+2));
#include <stdio.h>

int main() {
    int arr[] = {1, 2, 3, 4, 5};

    printf("数组名+下标:\n");
    for (int i = 0; i < 5; i++)
        printf("%2d", arr[i]);
    printf("\n\n");

    printf("数组名+指针运算:\n");
    for (int i = 0; i < 5; i++)
        printf("%2d", *(arr + i));
    printf("\n\n");

    printf("指针+指针运算:\n");
    for (int *pa = arr; pa < arr + 5; pa++)
        printf("%2d", *pa);
    printf("\n\n");

    printf("指针+下标:\n");
    int *pa = arr;
    for (int i = 0; i < 5; i++)
        printf("%2d", pa[i]);
    printf("\n\n");

    return 0;
}

数组名代表数组的首地址, 当数组名出现在表达式中时, 等效于一个常指针。

int arr[] = {0, 2, 4, 8};
int *pa = arr;  // OK, 数组名代表数组的首地址,即 pa=&arr[0]
*pa = 3;        // OK,等价于 arr[0]=3
*(pa + 2) = 5;  // OK,等价于 arr[2]=5
*(arr + 2) = 5; // OK,等价于 arr[2]=5
*(pa++) = 3;    // OK,等价于 arr[0]=3; pa = pa+1;
// *(arr++) = 3;   // ERROR! a代表数组首地址,等效于常指针!

小结: 一维数组 arr[n] 与指针 pa=arr

  • 一维数组名 arr 是地址常量, 数组名 arr&arr[0] 等价;
  • arr+iarr[i] 的地址, arr[i]*(arr+i) 等价;
  • 若指针 pa 存储的是数组 arr 的首地址, 则 *(pa+i)pa[i] 等价;
  • 数组元素的下标访问方式也是按地址进行的;
  • 指针的值可以随时改变, 即可以指向不同的元素, 通过指针访问数组的元素更加灵活;
  • 数组名等效于常量指针, 值不能改变;
  • pa++++pa 合法, 但 arr++ 不合法;

arr[i] <=> pa[i] <=> *(pa+i) <=> *(arr+i)

指针与二维数组

在 C 语言中, 二维数组是按行存储的, 可以理解为由一维数组组成的数组, 其元素也是连续存放的.

image-20250408110127461

二维数组名的含义

  • A:整个二维数组,类型是 int[2][3]
  • A[i]:第 i 行的一维数组,类型是 int[3]
  • A[i][j]:第 i 行第 j 列的元素
  • 对于二维数组 A, 虽然 AA[0] 都是数组首地址, 但二者指向的对象不同:
    • A[0] 是一维数组名, 它指向的是行数组 A[0] 的首元素, 即 *A[0]A[0][0] 等价;
    • A 是二维数组名, 它指向的是它的首元素, 而它的元素都是一维数组(即行数组), 因此 *AA[0] 等价. 另外, 它的指针移动单位是“行”, 所以 A+i 对应的是第 i 个行数组, 即 A[i]

指针访问二维数组

int arr[3][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12}
};

行指针(数组指针)

int (*p)[4] = arr;  // 指向包含4个int的数组的指针
  • p 指向第 0 行
  • p+1 指向第 1 行(地址增加 4×sizeof(int)
  • *(p+i) 获取第 i 行首地址
  • *(*(p+i)+j) 访问 arr[i][j]

列指针(普通指针)

int *p = &arr[0][0];  // 或 int *p = *arr;
  • p 指向第一个元素
  • p+1 指向下一个元素
  • 访问 arr[i][j]*(p + i*4 + j)

指针数组

int *p[3];  // 包含3个int指针的数组
for(int i=0; i<3; i++)
    p[i] = arr[i];  // 每行首地址
  • 访问 arr[i][j]*(p[i] + j)p[i][j]

常见操作示例

遍历二维数组

// 方法1:使用下标
for(int i=0; i<3; i++)
    for(int j=0; j<4; j++)
        printf("%d ", arr[i][j]);

// 方法2:使用行指针
int (*p)[4] = arr;
for(int i=0; i<3; i++)
    for(int j=0; j<4; j++)
        printf("%d ", *(*(p+i)+j));

// 方法3:使用列指针
int *q = *arr;
for(int i=0; i<3*4; i++)
    printf("%d ", *(q+i));

动态分配二维数组

// 方法1:连续分配
int **arr = (int **)malloc(rows * sizeof(int *));
arr[0] = (int *)malloc(rows * cols * sizeof(int));
for(int i=1; i<rows; i++)
    arr[i] = arr[0] + i*cols;

// 方法2:非连续分配
int **arr = (int **)malloc(rows * sizeof(int *));
for(int i=0; i<rows; i++)
    arr[i] = (int *)malloc(cols * sizeof(int));

指针与二维数组的关系

表达式 类型 含义
arr int[3][4] 整个二维数组
arr+i int(*)[4] 指向第i行的指针
*(arr+i) int* 第i行首元素地址
*(arr+i)+j int* 第i行第j列元素地址
*(*(arr+i)+j) int 第i行第j列元素值
arr[i][j] int 第i行第j列元素值

注意事项

  1. 类型匹配:行指针 int(*)[N] 与普通指针 int* 类型不同

  2. 指针运算arr+1 移动一行的大小,*arr+1 移动一个元素的大小

  3. 作为函数参数

    • void func(int arr[][4], int rows)
    • void func(int (*arr)[4], int rows)
  4. 动态分配内存后要记得释放:

    // 对于连续分配
    free(arr[0]);
    free(arr);
    
    // 对于非连续分配
    for(int i=0; i<rows; i++)
        free(arr[i]);
    free(arr);
    

指针数组和数组指针

指针数组:是一个数组,其元素都是指针。

int *ptr[3];  // 包含3个int指针的数组

数组指针:是一个指针,指向一个数组。

int (*ptr)[3];  // 指向包含3个int的数组的指针

多级指针

指针可以指向另一个指针,形成多级指针。

int var = 10;
int *p = &var;
int **pp = &p;  // 指向指针的指针

printf("var = %d\n", var);    // 10
printf("*p = %d\n", *p);      // 10
printf("**pp = %d\n", **pp);  // 10

指针与函数

指针函数

指针函数是指返回值为指针类型的函数,其本质是一个函数,只是它返回的是一个地址(指针)。

语法

返回类型 *函数名(参数列表);

示例

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

// 指针函数:返回int指针
int* createIntArray(int size) {
    int *arr = (int*)malloc(size * sizeof(int));
    if(arr == NULL) {
        return NULL;
    }
    for(int i = 0; i < size; i++) {
        arr[i] = i + 1;
    }
    return arr;  // 返回动态分配的数组指针
}

int main() {
    int *myArray = createIntArray(5);
    if(myArray != NULL) {
        for(int i = 0; i < 5; i++) {
            printf("%d ", myArray[i]);
        }
        free(myArray);  // 记得释放内存
    }
    return 0;
}

函数指针

函数指针是指指向函数的指针变量,它存储的是函数的入口地址,可以通过该指针调用函数。

语法

返回类型 (*指针变量名)(参数类型列表);

示例

#include <stdio.h>

// 普通函数
int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}

int main() {
    // 声明函数指针
    int (*operation)(int, int);

    // 指向add函数
    operation = add;
    printf("10 + 5 = %d\n", operation(10, 5));

    // 指向subtract函数
    operation = subtract;
    printf("10 - 5 = %d\n", operation(10, 5));

    return 0;
}

函数指针数组

#include <stdio.h>

double add(double a, double b) { return a + b; }
double sub(double a, double b) { return a - b; }
double mul(double a, double b) { return a * b; }
double div(double a, double b) { return a / b; }

int main() {
    // 函数指针数组
    double (*ops[4])(double, double) = {add, sub, mul, div};
    char *op_names[] = {"加法", "减法", "乘法", "除法"};

    double x = 10.0, y = 5.0;
    for(int i = 0; i < 4; i++) {
        printf("%s: %.2f %c %.2f = %.2f\n", 
              op_names[i], x, 
              i == 0 ? '+' : i == 1 ? '-' : i == 2 ? '*' : '/',
              y, ops[i](x, y));
    }

    return 0;
}

区别

特性 指针函数 函数指针
本质 是一个函数 是一个指针变量
声明 int* func() int (*func)()
返回值 返回指针 指向函数的地址
调用方式 直接调用:func() 通过指针调用:func_ptr()
主要用途 返回动态分配的内存或对象指针 实现回调、多态、插件架构等
内存管理 通常需要调用者管理返回的内存 不需要特殊的内存管理

void指针

void指针是一种通用指针,可以指向任何数据类型。在使用前需要强制类型转换。

void * 指针名
  • void 类型的指针可以存储任何类型的对象的地址;
  • 不允许直接使用 void 指针访问其目标对象;
  • 必须通过显式类型转换, 才可以访问 void 类型指针的目标对象.
int a = 10;
float b = 3.14;
void *p;

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

p = &b;
printf("b = %f\n", *(float *)p);

可以存储任何类型的地址。void * 是一种“无类型指针”,可以指向 intcharfloat 等任意类型的数据:

int num = 10;
float f = 3.14;
char c = 'A';

void *p1 = &num;  // 存储 int 的地址
void *p2 = &f;    // 存储 float 的地址
void *p3 = &c;    // 存储 char 的地址

不能直接解引用。void * 不知道它指向的数据类型,所以不能直接用 * 访问数据:

void *p = &num;
printf("%d", *p);  // 错误!不能直接解引用 void 指针

必须强制类型转换后才能访问。要访问 void * 指向的数据,必须先转换为具体类型的指针:

void *p = &num;
int *int_ptr = (int *)p;  // 转换为 int *
printf("%d", *int_ptr);   // 正确:输出 10

动态内存分配

指针常用于动态内存分配:

  • malloc(): 分配内存
  • calloc(): 分配并初始化内存
  • realloc(): 重新分配内存
  • free(): 释放内存
int *arr = (int *)malloc(5 * sizeof(int));  // 分配5个int的空间
if (arr != NULL) {
    for (int i = 0; i < 5; i++) {
        arr[i] = i + 1;
    }
    free(arr);  // 释放内存
}

常见指针错误

  1. 未初始化的指针:使用前未初始化
  2. 野指针:指针指向已释放的内存
  3. 内存泄漏:分配的内存未释放
  4. 指针越界:访问超出分配范围的内存
  5. 空指针解引用:解引用NULL指针

指针的应用场景

  1. 动态内存管理
  2. 实现复杂数据结构(链表、树、图等)
  3. 函数参数传递(实现引用传递)
  4. 函数回调
  5. 数组和字符串操作
  6. 系统编程和硬件访问

其他

指针的常见应用

指针作为函数参数(按引用传递)

C语言默认是值传递,但可以通过指针实现引用传递,从而在函数内部修改外部变量。

示例:交换两个变量的值

#include <stdio.h>

// 错误示例:值传递(无法交换)
void swap_wrong(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
}

// 正确示例:指针传递(可以交换)
void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    int x = 10, y = 20;

    swap_wrong(x, y);
    printf("x=%d, y=%d\n", x, y);  // x=10, y=20(未交换)

    swap(&x, &y);  // 传递 x 和 y 的地址
    printf("x=%d, y=%d\n", x, y);  // x=20, y=10(成功交换)

    return 0;
}
  • swap(&x, &y) 传递的是 xy 的地址,函数内部通过 *a*b 直接修改它们的值。

指针与数组

数组名本质上是一个指向数组首元素的指针

示例:指针遍历数组

#include <stdio.h>

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int *p = arr;  // p 指向 arr[0]

    for (int i = 0; i < 5; i++) {
        printf("arr[%d] = %d\n", i, *(p + i));  // 等价于 arr[i]
    }

    return 0;
}
  • *(p + i) 等价于 arr[i],因为指针运算会自动计算偏移量。

动态内存分配(mallocfree

指针可以指向动态分配的内存,用于灵活管理数据。

示例:动态创建数组

#include <stdio.h>
#include <stdlib.h>  // 提供 malloc 和 free

int main() {
    int n = 5;
    int *arr = (int *)malloc(n * sizeof(int));  // 动态分配 5 个 int 的内存

    if (arr == NULL) {
        printf("内存分配失败!\n");
        return 1;
    }

    for (int i = 0; i < n; i++) {
        arr[i] = i + 1;  // 等价于 *(arr + i) = i + 1
    }

    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);  // 输出 1 2 3 4 5
    }

    free(arr);  // 释放内存
    return 0;
}
  • malloc 动态分配内存,返回 void*,需要强制类型转换。
  • free 释放内存,防止内存泄漏。

常见错误

未初始化的指针(野指针)

int *p;  // 未初始化
*p = 10; // 错误!p 指向未知内存,可能导致程序崩溃

修正方法:

int num = 10;
int *p = &num;  // 正确:p 指向 num
*p = 20;        // 合法

空指针解引用

int *p = NULL;  // p 是空指针
printf("%d", *p);  // 错误!解引用空指针会导致崩溃

修正方法:

if (p != NULL) {
    printf("%d", *p);
}

总结

概念 运算符 作用 示例
取地址 & 获取变量的内存地址 int *p = #
解引用 * 访问指针指向的值 int val = *p;
指针声明 * 定义指针变量 int *p;
动态内存 malloc / free 分配/释放堆内存 int *arr = malloc(5 * sizeof(int));
  • 指针的核心是内存地址& 取地址,* 解引用。
  • 指针可用于函数传参、数组访问、动态内存管理
  • 避免野指针和空指针解引用,否则会导致程序崩溃。

YOLO