目录

C语言-指针用法概述

C语言-指针用法概述

目录


本文主要作为指针用法的复习,会对指针的大致用法进行举例和概述。


1.指针基础概念

  • 什么是指针​:指针是一种变量,它存储的是另一个变量的内存地址,而不是数据本身。这允许你通过地址间接访问和操作数据。
  • 声明指针​:声明指针时,需要在数据类型后跟一个星号 *。例如 int *p;声明了一个指向整型的指针 p
  • 取地址与解引用​:
    • 使用取地址运算符 &可以获取变量的地址。例如,int a = 10; int *p = &a;使得指针 p指向变量 a的地址。
    • 使用解引用运算符 *可以访问指针所指向地址中存储的值。例如,printf("%d", *p);会输出 a的值 10

2. 指针与数组

        C语言中数组名通常可被当作指向其首元素的指针使用。

int arr[3] = {1, 2, 3};
int *p = arr; // p 指向 arr[0]
printf("%d", *(p + 1)); // 输出 arr[1] 的值 2

        指针可以通过算术运算(如 +-++--)来遍历数组,单位是所指向类型的大小(例如 int指针每次移动通常为4字节)。


3. 指针作为函数参数

        通过向函数传递指针(即变量的地址),可以在函数内部修改调用函数中的变量值,实现“引用传递”。

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

int main() {
    int x = 1, y = 2;
    swap(&x, &y); // 交换后 x=2, y=1
}

        当需要向函数传递数组时,通常传递数组名(即首元素地址)和数组长度。使用 const限定符可以保护指针所指向的数据在函数内不被修改。


4. 动态内存分配

        C语言允许在程序运行时动态地申请和释放内存,这类内存分配在上进行。

  • ​**malloc​:分配指定字节数的内存,​不初始化**内存内容,返回 void*
int *p = (int*)malloc(5 * sizeof(int)); // 分配容纳5个整数的空间
if (p == NULL) { /* 处理分配失败 */ }
  • calloc​:分配指定数量、特定类型的内存空间,并初始化为0​。
int *p = (int*)calloc(5, sizeof(int)); // 分配并初始化5个整数为0
  • **realloc**​:调整之前通过 malloc, callocrealloc分配的内存块的大小。

  • free​:​必须用于释放之前动态分配的内存,否则会导致内存泄漏。释放后最好将指针置为 NULL,以避免“悬空指针”。

    free(p);
    p = NULL;

5. 指针的高级用法

  • 多级指针​:指向指针的指针,例如 int **pp是指向 int*的指针,常用于动态二维数组或需要修改指针本身值的场景。
  • 函数指针​:指向函数的指针,允许动态调用函数,常用于回调机制。
int add(int a, int b) { return a + b; }
int (*funcPtr)(int, int) = add; // 声明并初始化函数指针
printf("%d", funcPtr(2, 3)); // 通过指针调用函数,输出5

•​**const与指针**​:

  • const int *pint const *p:指向常量数据的指针,​不能通过 p修改所指数据,但可以改变 p的指向。
  • int *const p:指针本身是常量,​**p的指向不能变**,但可以通过 p修改所指数据。
  • const int *const p:指针本身和所指数据都不可变。

6. 常见错误与注意事项

  • 未初始化的指针(野指针)​​:声明指针后未赋予有效地址前就使用,可能导致程序崩溃。​务必初始化,例如设为 NULL
  • 悬空指针​:指针指向的内存已被释放,但指针仍在被使用。​**释放内存后应将指针置为 NULL**​。
  • 内存泄漏​:分配的内存未被释放且失去对其的引用,导致内存浪费。确保 ​**malloc/callocfree成对出现**​。
  • 越界访问​:访问了分配内存范围之外的空间,行为不可预知。​确保访问在合法范围内​。

7. 指针数组 vs. 数组指针

对于我们新手来说特别难搞清楚他们的区别,理解它们的区别很重要:

类型声明示例含义
指针数组int *arr[10]一个数组,其每个元素都是指向 int的指针
数组指针int (*arr)[10]一个指针,它指向一个包含10个整数的数组

理解指针数组(Array of Pointers)和数组指针(Pointer to Array)的区别确实是C语言学习中的一个重点和难点。下面我将通过一个表格帮你快速梳理它们的核心差异,然后进行详细解释。

特性指针数组 (Array of Pointers)数组指针 (Pointer to Array)
本质数组,其每个元素都是指针指针,它指向整个数组
声明语法数据类型 *数组名[数组长度]; (例如:int *ptr_arr[5];数据类型 (*指针名)[数组长度]; (例如:int (*arr_ptr)[5];
内存布局连续存储多个指针(每个指针占4或8字节)存储一个指针变量(指向数组首地址),本身通常占4或8字节
步长指针运算以单个指针的大小为单位(如+1移动4或8字节)指针运算以整个数组的大小为单位(如+1移动sizeof(类型[长度])字节)
典型用途管理多个字符串、存储动态分配的不同长度内存块的地址、命令行参数argv操作多维数组(尤其是二维数组)、向函数传递固定长度的数组

深入理解两者

1. 指针数组(Array of Pointers)

指针数组的本质是一个数组,但这个数组里的每个元素都不是普通的数据,而是一个指针变量,这些指针可以指向相同或不同类型的地址。

  • 声明与初始化

    语法格式为:数据类型 *数组名[数组长度];

    #include <stdio.h>
    
    int main() {
        int a = 10, b = 20, c = 30;
        // 声明并初始化一个指针数组,元素是int指针
        int *ptr_arr[3] = {&a, &b, &c}; 
    
        // 遍历指针数组,通过解引用访问所指的值
        for (int i = 0; i < 3; i++) {
            printf("ptr_arr[%d] = %d\n", i, *ptr_arr[i]);
        }
        return 0;
    }

    输出:

    ptr_arr[0] = 10
    ptr_arr[1] = 20
    ptr_arr[2] = 30
  • 常见应用场景

    • 管理多个字符串​:这是指针数组非常常见的用途,可以高效地处理一堆长度不一的字符串。

      char *str_arr[] = {"Hello", "World", "C", "Programming"}; // 初始化字符串指针数组
      for (int i = 0; i < 4; i++) {
          printf("%s ", str_arr[i]);
      }
      // 输出: Hello World C Programming
    • 动态内存管理​:当需要动态分配多个独立的内存块时,可以用指针数组来存储这些内存块的地址。

    • 命令行参数​:C语言中的main函数参数char *argv[]就是一个典型的指针数组,用于存储命令行输入的字符串参数。

2. 数组指针(Pointer to Array)

数组指针的本质是一个指针,但它不是指向单个变量,而是指向整个数组

  • 声明与初始化

    语法格式为:数据类型 (*指针名)[数组长度];注意括号是必须的,它保证了*先与指针名结合。

    #include <stdio.h>
    
    int main() {
        int arr[5] = {1, 2, 3, 4, 5};
        // 声明一个数组指针,指向包含5个int的数组
        int (*arr_ptr)[5] = &arr; // 取数组的地址赋给数组指针
    
        // 通过数组指针访问数组元素
        for (int i = 0; i < 5; i++) {
            printf("%d ", (*arr_ptr)[i]); // 先解引用arr_ptr得到数组,再通过下标访问
        }
        return 0;
    }

    输出:

    1 2 3 4 5
  • 常见应用场景

    • 处理二维数组​:数组指针在遍历和理解二维数组时特别有用,它可以表示二维数组中的一行。

      int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
      // 数组指针,指向一个包含3个int的数组(即一行)
      int (*row_ptr)[3] = matrix; // matrix是首行地址,可直接赋值
      
      for (int i = 0; i < 2; i++) {
          for (int j = 0; j < 3; j++) {
              printf("%d ", row_ptr[i][j]); // 像二维数组一样访问
              // 等价于 *(*(row_ptr + i) + j)
          }
          printf("\n");
      }

      输出:

      1 2 3 
      4 5 6
    • 向函数传递二维数组​:当函数需要接收一个二维数组时,使用数组指针可以明确列数。

如何快速区分声明?

记住一点:​​“看括号和优先级”​

  • int *p[5];[]的优先级高于*,所以p先与[5]结合,说明p是一个数组,里面存放的是int*类型。这是指针数组
  • int (*p)[5];:括号()改变了优先级,*先与p结合,说明p是一个指针,它指向一个int [5]类型的数组。这是数组指针

注意事项

  1. 匹配类型和长度​:对于数组指针,其指向的数组类型和长度必须严格匹配。例如,int (*p)[5]只能指向包含5个整数的数组。
  2. 初始化​:使用指针数组时,务必确保其中的每个指针都指向有效的内存地址,避免野指针。
  3. 内存管理​:如果指针数组的元素指向动态分配的内存,记得在使用完毕后释放这些内存,防止内存泄漏。

小总结

简单来说:

  • 指针数组​:是一个仓库(数组)​,里面放着很多把钥匙(指针)​,每把钥匙可以打开不同的房间。
  • 数组指针​:是一把特殊的钥匙(指针)​,这把钥匙对应着一整排连续的仓库(整个数组)​

希望这些解释和例子能帮助你清晰地区分指针数组和数组指针。


8.总结与建议

        指针是C语言的精髓,提供了直接操作内存的强大能力和灵活性,常用于动态内存管理、数组操作、函数参数传递以及构建复杂数据结构(如链表、树)。同时,指针的使用也伴随着风险,需要谨慎处理内存管理和避免常见陷阱。

最佳实践​:

  • 始终初始化指针,若暂时不知指向何处,可初始化为 NULL
  • 检查动态内存分配​(如 malloc, calloc)的返回值是否为 NULL,以防分配失败。
  • 确保分配的内存及时释放,并**在释放后将指针置为 NULL**​。
  • 使用 const限定符保护不应被修改的数据,增强代码健壮性。
  • 利用工具​(如 Valgrind)检查内存泄漏和非法访问。

希望以上梳理能帮助你更好地理解C语言中的指针。

请大家点点关注和点赞,后面我一定会分享更多实用的文章的