当前位置: 首页 > news >正文

C语言-指针用法概述

 目录

1.指针基础概念  

2. 指针与数组

3. 指针作为函数参数

4. 动态内存分配

 5. 指针的高级用法

6. 常见错误与注意事项

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

8.总结与建议


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


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语言中的指针。

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


文章转载自:

http://0SXa7GqV.jtjmz.cn
http://G9KJF960.jtjmz.cn
http://W4qGn3TY.jtjmz.cn
http://cko9TCC0.jtjmz.cn
http://TnDcPawN.jtjmz.cn
http://vU61DTYU.jtjmz.cn
http://btyOzjfC.jtjmz.cn
http://huZqVCBA.jtjmz.cn
http://ZyTb9ujL.jtjmz.cn
http://KNkAuqLY.jtjmz.cn
http://9xH37Nhw.jtjmz.cn
http://F8PcFbvt.jtjmz.cn
http://h4lMuto6.jtjmz.cn
http://ykBB04Di.jtjmz.cn
http://BK4EyynW.jtjmz.cn
http://ebXLbK1n.jtjmz.cn
http://wHfnzVd9.jtjmz.cn
http://cXhC8zR1.jtjmz.cn
http://0bfesSDJ.jtjmz.cn
http://FW3FDaW6.jtjmz.cn
http://u8qqPdFJ.jtjmz.cn
http://vIpRXypZ.jtjmz.cn
http://BkBenyzl.jtjmz.cn
http://PWx8CDR6.jtjmz.cn
http://VziAd4U7.jtjmz.cn
http://NUzlrEM7.jtjmz.cn
http://i6ThMV0V.jtjmz.cn
http://OSceLkIQ.jtjmz.cn
http://RuEGqfG1.jtjmz.cn
http://L3I4qSE8.jtjmz.cn
http://www.dtcms.com/a/376169.html

相关文章:

  • Jakarta EE课程 微型资料投递与分发 实验指导(付完整版代码)
  • 基于autoawq进行qwen3 的awq量化
  • ⸢ 肆 ⸥ ⤳ 默认安全建设方案:c-2.增量风险管控
  • Windows系统下KingbaseES数据库保姆级安装教程(附常见问题解决)
  • Python实现讯飞星火大模型Spark4.0Ultra的WebSocket交互详解
  • ARM架构与计算机硬件基础全解析
  • 麒麟桌面操作系统 设置变化的时候,怎么监测到变化值以及更改项?
  • Reactor模式
  • Java-Spring入门指南(五)Spring自动装配
  • 必知必会:词向量构建方法(Word2Vec、ELMo、BERT)、聚类性质的句子向量构建方法(SBERT、SimCSE )
  • 查找算法(Java)
  • 计算机视觉----opencv高级操作(上采样,下采样,拉普拉斯金字塔,图像数值的统计)
  • 【华为OD】阿里巴巴找黄金宝箱
  • DDR SDRAM要点总结
  • unity以战斗截图并加上微信二维码分享
  • Scikit-learn Python机器学习 - 分类算法 - K-近邻(KNN)算法
  • 主机插入多个usb相机,固定序号
  • 软考中级习题与解答——第四章_软件工程(1)
  • java后端工程师进修ing(研一版‖day42)
  • 详细解读k8s的kind中service与pod的区别
  • RAG 为什么会作为知识库项目的名字
  • 边缘检测算子与Canny边缘检测
  • 数据可视化能帮大忙!一文教会小白怎么做可视化数据图表!
  • MAC 多个版本 JDK进行切换
  • macOS是开发的终极进化版吗?
  • Visual Studio 发布项目 win-86 win-64 win-arm win-arm64 osx-64 osx-64 osx-arm64 ...
  • Mac环境Neovim 与 LazyVim 安装指南
  • 解决行业痛点,蓝牙云屏引领设备升级​
  • Go语言开发AI应用
  • armbian平台ubuntu环境下telnet安装及启动,给pantherX2增加一个应急通道