C 指针

🔍 C 语言学习:指针(Pointer)


🎯 学习目标

  1. 理解指针的基本概念与作用,掌握其在内存访问中的核心地位。
  2. 掌握指针的定义、赋值、取地址和间接访问操作符 * 的使用。
  3. 能够使用指针完成数组遍历、函数参数传递、动态内存管理等常见任务。

🔑 核心重点

  1. 指针是变量的地址,通过指针可以访问或修改变量的值。
  2. 指针类型决定了访问的数据大小和解释方式,如 int* 指向的是一个 int 类型数据。
  3. 指针运算要符合类型对齐规则,不能随意对空指针或无效地址进行操作。

📚 详细讲解

image

一、什么是指针?

指针是一种特殊的变量,它存储的是另一个变量的内存地址。你可以通过这个地址来访问或修改那个变量的内容。

✅ 基本语法:

数据类型 *指针变量名;

示例:基本指针操作

#include <stdio.h>

int main() {
    int a = 42;
    int *p = &a;  // p 是指向整数 a 的指针

    printf("a 的值:%d\n", a);        // 输出:42
    printf("a 的地址:%p\n", &a);     // 输出类似:0x7ffee...
    printf("p 的值(a 的地址):%p\n", p);   // 同上
    printf("*p 的值(a 的内容):%d\n", *p); // 输出:42

    return 0;
}

📌 解释:

  • &:取地址运算符
  • *:间接访问运算符(也叫“解引用”)

⚠️ 注意事项:

  • 使用未初始化的指针会导致未定义行为(Undefined Behavior)。
  • 不要对 NULL 或非法地址进行解引用操作。

二、指针与数组的关系

在 C 语言中,数组名本质上是一个常量指针,指向数组第一个元素的地址。

示例:用指针遍历数组

#include <stdio.h>

int main() {
    int arr[] = {10, 20, 30, 40, 50};
    int *p = arr;  // 等价于 &arr[0]

    for (int i = 0; i < 5; i++) {
        printf("arr[%d] = %d\n", i, *(p + i));  // 通过指针访问数组元素
    }

    return 0;
}

📌 小技巧:

  • arr[i] == *(arr + i)
  • 指针比数组下标更快(因为不需要计算偏移)

三、指针与函数参数传递

C 语言中函数参数默认是值传递,使用指针可以实现模拟引用传递,让函数修改外部变量。

示例:交换两个整数

#include <stdio.h>

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

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

    printf("Before: x = %d, y = %d\n", x, y);
    swap(&x, &y);
    printf("After:  x = %d, y = %d\n", x, y);

    return 0;
}

📌 输出:

Before: x = 10, y = 20
After:  x = 20, y = 10

⚠️ 注意事项:

  • 函数内部不要返回局部变量的地址(栈空间已释放)。
  • 多级指针可用于传递二维数组或修改指针本身。

四、指针与字符串

字符串本质是一个字符数组,可以用字符指针指向字符串字面量。

示例:使用字符指针处理字符串

#include <stdio.h>

int main() {
    char *str = "Hello, World!";  // str 指向字符串常量
    while (*str != '\0') {
        putchar(*str++);
    }
    putchar('\n');

    return 0;
}

📌 注意:

  • 字符串常量不能被修改(尝试写入会崩溃),如:str[0] = 'h'
  • 如果需要修改字符串,应使用字符数组:char str[] = "Hello";

五、指针与动态内存分配(C23 支持良好)

使用 malloc / calloc / reallocfree 可以在堆上动态申请内存,并通过指针访问。

示例:动态创建一个整型数组

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

int main() {
    int n = 5;
    int *arr = malloc(n * sizeof(int));
    if (arr == NULL) {
        printf("Memory allocation failed!\n");
        return 1;
    }

    for (int i = 0; i < n; i++) {
        arr[i] = i * 10;
    }

    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }

    free(arr);  // 释放内存
    return 0;
}

📌 输出:

0 10 20 30 40

⚠️ 注意事项:

  • 动态内存必须手动释放(否则造成内存泄漏)
  • 分配失败时一定要检查是否为 NULL

六、多级指针(pointer to pointer)

多级指针用于指向另一个指针,常见于函数中需要修改指针本身的场景。

示例:二级指针修改一级指针指向

#include <stdio.h>

void change_ptr(int **p) {
    static int value = 99;
    *p = &value;
}

int main() {
    int a = 10;
    int *ptr = &a;

    printf("Before: %d\n", *ptr);
    change_ptr(&ptr);
    printf("After:  %d\n", *ptr);

    return 0;
}

📌 输出:

Before: 10
After:  99

📌 应用场景:

  • 修改指针本身(例如动态分配内存后传回)
  • 二维数组传参
  • 字符串数组(如命令行参数 char *argv[]

🧩 拓展练习(动手实践题)

  1. 练习一:指针交换

    • 编写函数 swap_float(float *a, float *b),交换两个浮点数的值。
  2. 练习二:字符串反转

    • 使用指针编写函数 reverse_string(char *str),将输入字符串原地反转。
  3. 练习三:查找最大值

    • 给定一个整型数组,使用指针找出其中的最大值及其索引。
  4. 挑战练习:动态二维数组

    • 使用 malloc 创建一个 M x N 的二维数组,并用指针访问每个元素。
  5. 项目实战:简易链表

    • 定义结构体节点 struct Node { int data; struct Node *next; }
    • 使用指针实现链表的插入与打印功能。

📝 小结

特点 说明
指针是 C 语言的灵魂 直接操作内存,提高效率与灵活性
指针可指向任何变量、数组、函数、结构体 实现复杂数据结构的关键
指针支持动态内存管理 配合 malloc/free 实现灵活内存控制
指针操作需谨慎 避免野指针、空指针、越界访问等问题

📚 推荐阅读 & 工具建议


如果你希望我为你提供:

  • 指针使用陷阱与避坑指南 PDF
  • 更多实战项目(如内存池、链表、树结构、文件读写等)
  • 指针 vs 数组、const 指针、函数指针详解
  • 指针命名规范与代码风格建议
  • 视频教学资源推荐(中文讲解)

欢迎随时告诉我 😊

二级标题:NULL 指针详解

在 C 语言中,NULL 是一个非常重要的概念,它表示一个“空指针”或“无效地址”。虽然 NULL 看起来只是一个简单的宏定义,但它在程序的安全性、健壮性和可读性方面起着关键作用。

什么是 NULL?

在标准头文件(如 <stdio.h><stddef.h>)中,NULL 通常被定义为:

#define NULL ((void *)0)

或者简化为:

#define NULL 0

也就是说,NULL 实际上是一个值为 0 的指针常量。它用来表示某个指针变量当前没有指向任何有效的内存地址


为什么使用 NULL?

  1. 初始化指针变量
    在声明指针时,如果没有立即赋值,建议将其初始化为 NULL,避免成为“野指针”。

    int *p = NULL;
    
  2. 判断指针是否有效
    使用前通过判断指针是否为 NULL,可以防止对无效地址进行解引用,从而避免段错误。

    if (p != NULL) {
        printf("%d\n", *p);
    }
    
  3. 作为函数返回值
    常用于表示函数执行失败或无结果。例如动态内存分配失败时,malloc 返回 NULL

    int *arr = malloc(100 * sizeof(int));
    if (arr == NULL) {
        printf("内存分配失败!\n");
        return -1;
    }
    
  4. 作为链表、树等结构的终止标志
    在数据结构中,NULL 常用来表示链表尾部、树的叶子节点等。

    struct Node {
        int data;
        struct Node *next;
    };
    
    // 遍历链表
    struct Node *current = head;
    while (current != NULL) {
        printf("%d ", current->data);
        current = current->next;
    }
    

NULL 指针与野指针的区别

类型 含义 是否安全
NULL 指针 明确不指向任何地址 ✅ 安全(可判断)
野指针 未初始化,内容随机(可能指向非法地址) ❌ 不安全(不可控)

⚠️ 对野指针进行解引用会导致未定义行为;而 NULL 指针可以通过判断避免访问。


注意事项

  • 不要对 NULL 指针解引用
    会引发段错误(Segmentation Fault)或崩溃。

    int *p = NULL;
    printf("%d\n", *p); // ❌ 错误!不能解引用 NULL 指针
    
  • 释放后的指针应置为 NULL
    防止“悬挂指针”问题(即指针仍保留旧地址,但内存已被释放)。

    int *p = malloc(sizeof(int));
    free(p);
    p = NULL; // 避免后续误用
    
  • 不要混淆 NULL'\0'
    NULL 是指针常量,表示无效地址;\0 是字符串结束符,表示字符数组的结尾。

  • 不要直接使用 0 替代 NULL
    虽然它们本质上是等价的,但使用 NULL 更具语义清晰性,提升代码可读性。


总结

NULL 是 C 语言中非常基础但也非常关键的概念。它不仅帮助我们写出更安全、更可靠的代码,还能提高程序的可维护性。掌握 NULL 的使用方式和背后的原理,是每个 C 语言开发者必须具备的基本功之一。

posted @ 2025-06-04 21:46  红尘过客2022  阅读(45)  评论(0)    收藏  举报