C/C++ 指针解析

C/C++ 指针解析笔记

一、指针的核心本质

  1. 定义
    • 指针是一种特殊的变量,它存储的是另一个变量的内存地址
    • 通过这个地址,我们可以间接访问和修改该地址上存储的数据。
    • 可以把内存看作一系列带有编号的格子,内存地址就是格子的编号,指针变量存储的就是这个编号。
  2. 为什么需要指针?
    • 直接内存操作:允许对内存进行更底层的控制。
    • 动态内存分配:如 malloc, new 返回的就是分配内存的起始地址(指针)。
    • 高效传递大型数据结构:通过传递指针(地址)而非整个数据结构的副本给函数,可以提高效率。
    • 实现复杂数据结构:如链表、树、图等,其节点间的连接通常通过指针实现。
    • 函数间修改外部变量:通过传递变量的地址,函数内部可以修改函数外部的变量。

二、指针的声明与基本操作

  1. 声明指针变量

    data_type *pointer_name;
    
    • data_type:指针所指向的变量的数据类型。这个类型至关重要,它决定了:
      • 解引用行为:从指针指向的地址开始读取多少字节,以及如何解释这些字节。
      • 指针算术的步长:指针加减运算时,地址移动的单位长度。
    • *:表明 pointer_name 是一个指针变量。
    • pointer_name:指针变量的名称。
    • 示例int *p_int; char *p_char; double *p_double;
  2. 取地址运算符 (&)

    • 获取一个变量在内存中的实际地址。

    • 示例:

      C++

      int var = 10;
      int *ptr = &var; // ptr 现在存储了 var 的内存地址
      
  3. 解引用运算符 (*) (间接寻址运算符)

    • 访问指针所指向地址中存储的值。

    • 示例:

      int value = *ptr; // value 将被赋值为 10 (var 的值)
      *ptr = 20;        // var 的值现在被修改为 20
      
    • 重要:解引用一个未初始化或指向无效内存区域的指针是危险的,会导致未定义行为(通常是程序崩溃)。

  4. 空指针 (nullptr C++11及以后, NULL C/旧C++)

    • 表示一个不指向任何有效内存地址的指针。
    • 良好的编程习惯是初始化指针为 nullptr,并在释放内存后也将其设为 nullptr,以防止野指针。
    • 示例int *ptr = nullptr;
    • 解引用空指针同样会导致程序崩溃。

三、指针类型的重要性

  1. 决定解引用的字节数和解释方式
    • int *p; *p 会访问 sizeof(int) 个字节并将其解释为整数。
    • char *p; *p 会访问 sizeof(char) 个字节并将其解释为字符。
    • 如果指针类型与实际存储的数据类型不匹配,解引用可能导致错误的数据或行为。
  2. 决定指针算术的“步长”
    • int_ptr++:地址值增加 sizeof(int)
    • char_ptr++:地址值增加 sizeof(char)

四、指针算术 (Pointer Arithmetic)

指针算术是基于指针所指向的数据类型的大小进行的。

  1. 递增 (++) / 递减 (--)
    • ptr++:使指针指向内存中下一个同类型元素。
    • ptr--:使指针指向内存中上一个同类型元素。
  2. 加法 (+) / 减法 (-) 整数
    • ptr + n:结果是一个新指针,指向从 ptr 当前位置之后第 n 个同类型元素。地址增加 n * sizeof(pointed_type)
    • ptr - n:结果是一个新指针,指向从 ptr 当前位置之前第 n 个同类型元素。地址减少 n * sizeof(pointed_type)
  3. 指针相减 (ptr1 - ptr2)
    • 前提:ptr1ptr2 必须指向同一个数组中的元素(或者是数组末尾的下一个位置)。
    • 结果:它们之间相差的元素个数,类型通常是 ptrdiff_t
  4. 注意事项
    • 指针算术主要用于操作数组。
    • 避免对非数组元素的指针进行越界算术运算。
    • 不能对指针进行乘法、除法、模运算。
    • 不同类型的指针(void* 除外,且其不能直接运算)不能直接进行算术运算。

五、void* (泛型指针 / 无类型指针)

  1. 定义:可以存储任何类型对象的地址,但本身不包含类型信息。

  2. 特点:

    • 通用性void *vptr; int i; char c; vptr = &i; vptr = &c;

    • 不能直接解引用:编译器不知道如何解释所指数据。必须先显式类型转换 (cast)为具体的指针类型。

      int i = 10;
      void *vptr = &i;
      int val = *( (int*)vptr ); // 转换为 int* 后解引用
      
    • 不能直接进行指针算术 (标准C/C++):因为不知道对象大小。若需运算,先转换。 (GNU C 扩展可能允许,但不推荐,因不可移植)。

  3. 常见用途:

    • 泛型函数接口:如 malloc 返回 void* (分配的内存块可以用于任何类型),free 接收 void*memcpy, memset 的参数。
    • 在不同数据类型的指针间传递地址。

六、多级指针 (Pointers to Pointers)

指针本身也是变量,也存储在内存中,因此也有地址。多级指针就是指向另一个指针地址的指针。

  1. 二级指针 (data_type **ptr_to_ptr;):存储一个一级指针的地址。

    • 示例int var = 10; int *p1 = &var; int **p2 = &p1;
    • p2:存储 p1 的地址。
    • *p2:解引用一次,得到 p1 的值 (即 var 的地址)。
    • **p2:解引用两次,得到 var 的值 (即 10)。
  2. 常见用途

    • 在函数中修改指针本身:如果函数需要改变一个外部指针变量指向的地址(例如,在函数内部分配内存并让外部指针指向它),需要传递该外部指针的地址 (即二级指针) 给函数。

      void allocate(int **arr_ptr, int size) {
          *arr_ptr = new int[size]; // 修改了调用者传入的指针
      }
      // ...
      int *my_array = nullptr;
      allocate(&my_array, 10); // 传递 my_array 的地址
      // my_array 现在指向新分配的内存
      
    • 动态分配指针数组:例如,管理一个字符串数组 (每个字符串本身是 char*)。

      char **names = new char*[count]; // names 是一个指向 char* 的指针
      names[0] = new char[length];
      strcpy(names[0], "Alice");
      
    • C语言中的 main 函数参数 char *argv[]char **argv 也是二级指针,argv 指向一个指针数组,其中每个指针指向一个命令行参数字符串。

七、指针与数组

  1. 数组名作为指针:在大多数表达式中,数组名会隐式转换为其第一个元素的地址 (指针)。

    int arr[5] = {1, 2, 3, 4, 5};
    int *ptr = arr; // 等价于 int *ptr = &arr[0];
    std::cout << *ptr; // 输出 1
    std::cout << *(arr + 1); // 输出 2 (arr[1])
    
  2. 区别:

    • sizeof 运算符sizeof(arr) 返回整个数组占用的字节数,而 sizeof(ptr) 返回指针变量自身占用的字节数 (通常4或8字节)。
    • & 运算符&arr 得到的是整个数组的地址,虽然其数值与 arr (首元素地址) 相同,但类型不同 (int (*)[5] vs int*)。(&arr + 1) 会跳过整个数组。
    • 数组名是常量指针,不能被赋值修改:arr = another_array; (错误);ptr = another_array; (正确)。

八、函数指针

  1. 定义:指向函数的指针,存储函数的入口地址。

  2. 声明:

    return_type (*pointer_name)(parameter_types);
    
    • 示例int (*add_func_ptr)(int, int); (一个指向返回int并接收两个int参数的函数的指针)
  3. 赋值与调用:

    int add(int a, int b) { return a + b; }
    add_func_ptr = add; // 或 add_func_ptr = &add;
    int result = (*add_func_ptr)(10, 20); // 或 result = add_func_ptr(10, 20);
    
  4. 用途:

    • 回调函数:将函数作为参数传递给另一个函数。
    • 跳转表/状态机:根据不同条件调用不同函数。
    • 实现某些设计模式 (如策略模式)。

九、指针的危险与注意事项

  1. 野指针 (Wild Pointers):未初始化的指针,或指向已释放/无效内存区域的指针。解引用野指针是灾难性的。

    • 避免:声明时初始化 (nullptr);释放内存后将指针设为 nullptr
  2. 悬垂指针 (Dangling Pointers):指向已经被释放的内存的指针。

    • 避免:确保指针在指向的内存释放后不再被使用。
  3. 内存泄漏 (Memory Leaks):动态分配的内存不再需要但没有被释放,导致程序持续占用不必要的内存。

    • 避免malloc/new 配对使用 free/delete (或 delete[] for arrays)。使用智能指针 (C++)。
  4. 访问越界:通过指针算术访问数组边界之外的内存。

  5. 类型不匹配:通过一个类型的指针去访问另一种类型的数据,可能导致数据损坏或错误解释。


提供的链接 (深入理解C/C++中的指针 - GitHub)

posted @ 2025-05-21 16:44  phen  阅读(67)  评论(0)    收藏  举报