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

解码C语言指针

一、指针的定义与本质

1. 指针是什么?

指针是一种 存储变量内存地址 的特殊变量。所有数据存储在内存中,每个内存单元都有唯一地址(编号),指针通过记录地址实现对数据的间接访问。

2. 指针的核心作用

  • 直接操作内存:动态内存分配、硬件编程等。
  • 提高效率:传递大对象时避免复制(如结构体)。
  • 灵活数据结构:实现链表、树、图等动态结构。

二、指针基本语法

1. 声明指针

数据类型 *指针变量名;// *表示这是个指针变量

示例

int *p;// p指向int类型变量
char *str;// str指向char类型变量

2. 指针初始化

  • 取地址操作符 &:获取变量的内存地址。

    int num = 10;
    int *p = #// p指向num的地址
    
  • 直接赋值(需确保地址合法):

    int *p = (int *)0x00007FFF1234;// 避免直接操作未知地址
    

三、指针的解引用

  • 解引用操作符 * ****:通过指针访问或修改目标地址的数据。

    int num = 10;
    int *p = #printf("num的值 = %d\n", *p);// 输出10(访问值)
    *p = 20;// 修改num为20
    

四、指针的算术运算

指针的加减操作以 数据类型大小 为单位进行偏移。

1. 指针与整数运算

int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;// p指向arr[0]p++;// p指向arr[1](地址+4字节,假设int为4字节)
printf("%d\n", *p);// 输出2p += 3;// p指向arr[4]
printf("%d\n", *p);// 输出5

2. 指针之间的减法

计算两个指针之间的元素距离(同类型指针):

int *p1 = &arr[0];
int *p2 = &arr[4];
printf("距离 = %ld\n", p2 - p1);// 输出4(间隔4个元素)

五、多级指针

  • 二级指针:指向指针的指针。
int num = 10;
int *p = #//数据类型 *指针变量名;
int **pp = &p;// pp指向p的地址
printf("%d\n", **pp);// 通过二级指针访问num的值 → 输出10

1. 遍历二维数组(数组指针)

#include <stdio.h>int main() {int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};int (*p)[3] = matrix;// 指向第一行的地址for (int i = 0; i < 2; i++) {for (int j = 0; j < 3; j++) {printf("%d ", p[i][j]);// 等价于 *(*(p + i) + j)}printf("\n");}return 0;
}

六、指针&数组&字符串

1. 数组名的本质

数组名是数组首元素的地址常量(不可修改)。

int arr[3] = {10, 20, 30};
int *p = arr;// p指向arr[0]
printf("%d\n", *(p + 1));// 右移一个int单位 → 输出20

2. 指针遍历数组

for (int *ptr = arr; ptr < arr + 3; ptr++) {printf("%d ", *ptr);// 输出10 20 30
}

3. 遍历字符串

char *p = "Hello";
while (*p != '\0') {printf("%c ", *p);// 输出 H e l l op++;
}

4. 字符串拼接(动态分配)

#include <stdlib.h>
#include <string.h>
char *concat(const char *s1, const char *s2) {char *result = malloc(strlen(s1) + strlen(s2) + 1);// +1 为 '\0'strcpy(result, s1);strcat(result, s2);return result;
}// 使用示例
char *str = concat("Hello, ", "world!");
printf("%s\n", str);// 输出 Hello, world!
free(str);// 必须释放内存

5. 字符串数组(指针数组)

char *fruits[] = {"Apple", "Banana", "Cherry"};// 指针数组
for (int i = 0; i < 3; i++) {printf("%s\n", fruits[i]);// 输出各水果名称
}
printf("%s\n",*(fruits + 1));//输出Banana,数组的第二个元素
printf("%c\n",*(*(fruits + 1) + 1));//输出a,Banana的第二个字符

七、指针与函数

1. 传递指针到函数

// 修改外部变量值
void increment(int *x) {(*x)++;
}int main() {int a = 5;increment(&a);// a变为6return 0;
}

2. 返回指针的函数(指针函数)

/* 返回数组最大元素的地址,只能返回静态变量的地址、动态开辟(malloc)的地址,全局变量
的地址等,不能返回局部变量的地址(否则程序会崩溃)
*/
int* find_max(int *arr, int size) {int *max = arr;for (int *p = arr; p < arr + size; p++) {if (*p > *max) max = p;}return max;
}
int* find_max(int *arr, int size) {static int max = *arr;for (int *p = arr; p < arr + size; p++) {if (*p > max) max = *p;}return &max; 
}

3. 函数指针

指向函数的指针,用于回调机制:

int add(int a, int b) { return a + b; }
int (*func_p)(int, int) = add;// 声明函数指针
printf("%d\n", func_p(3, 4));// 输出7

八、void * 指针

  • 通用指针,可指向任意类型数据。
  • 使用前需强制类型转换。
void *ptr;
int x = 10;
ptr = (void *)&x;// 合法
printf("%d\n", *(int *)ptr);// 需转换为int*

九、

核心规则:const 的位置决定保护的对象

const 在 * 的左边,保护的是指针所指向的数据*p 不可变)。

const 在 * 的右边,保护的是指针本身p 不可变)。


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

格式: const 类型 *ptr 或 类型 const *ptr

含义: 指针 ptr 可以指向不同的地址,但不能通过 ptr 来修改它所指向的数据。

示例:

int a = 10, b = 20;// 指向常量的指针
const int *ptr1 = &a;// ptr1 指向一个const int
int const *ptr2 = &a;// 等价写法
// *ptr1 = 15;  // 错误!不能通过ptr1修改数据
a = 15;// 合法,a本身不是constptr1 = &b;// 合法,ptr1可以指向其他地址
// *ptr1 = 25;  // 错误!仍然不能通过ptr1修改数据

2. 指针常量(Constant Pointer)

格式: 类型 * const ptr

含义: 指针 ptr 自身是常量,一旦初始化就不能再指向其他地址,但可以通过 ptr 修改它所指向的数据。

示例:

int x = 10, y = 20;// 指针常量
int * const ptr = &x;// ptr是常量指针
*ptr = 15;// 合法,可以修改指向的数据
// ptr = &y;    // 错误!ptr本身是常量,不能改变指向

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

格式: const 类型 * const ptr

含义: 既不能修改指针 ptr 的指向,也不能通过 ptr 修改所指向的数据。

示例:

const int z = 30;
int w = 40;// 指向常量的指针常量
const int * const ptr = &z;// *ptr = 35;    // 错误!不能修改数据
// ptr = &w;     // 错误!不能修改指针指向
int read = *ptr;// 合法,只能读取

总结表格(带变量名)

声明格式 指针(ptr)是否可修改 数据(*ptr)是否可修改 说明
int *ptr 普通指针
const int *ptr 指向常量的指针
int * const ptr 指针常量
const int * const ptr 指向常量的指针常量

十、野指针

1. 野指针

野指针是指指向无效内存地址的指针。这些指针通常指向已经被释放或未分配的内存区域。对野指针进行操作会导致未定义行为


2.主要成因

成因一:指针未初始化

int *ptr;// 未初始化,值是随机的垃圾值
*ptr = 10;// 危险!向随机内存地址写入数据
printf("%d", *ptr);// 可能导致程序崩溃

成因二:malloc后未检查或释放后继续使用

int *ptr = (int*)malloc(sizeof(int));
if (ptr != NULL) {*ptr = 100;
}
free(ptr);// 释放内存
// ptr现在成为野指针*ptr = 200;// 危险!访问已释放的内存
printf("%d", *ptr);// 未定义行为

成因三:指向局部变量(函数返回后)

int* createWildPointer() 
{int local = 42;return &local;// 返回局部变量的地址
}int main() {int *ptr = createWildPointer();// ptr是野指针printf("%d", *ptr);// 危险!local已销毁return 0;
}

成因四:指针越界访问

int arr[5] = {1, 2, 3, 4, 5};
int *ptr = arr;for (int i = 0; i < 10; i++) 
{// 越界访问printf("%d ", *(ptr + i));// 后5次是野指针访问
}

成因五:指针运算错误

int *ptr = (int*)malloc(5 * sizeof(int));
// ... 使用ptr
free(ptr);int *ptr2 = ptr + 2;// ptr2也是野指针

3. 野指针的危害

  1. 程序崩溃:段错误(Segmentation Fault)
  2. 数据损坏:意外修改其他有效数据
  3. 安全漏洞:可能被利用进行攻击
  4. 难以调试:错误表现随机,难以追踪

4. 避免野指针的方法

方法一:总是初始化指针

int *ptr = NULL;// 初始化为NULL
int *ptr2 = (int*)malloc(sizeof(int));// 或初始化为有效地址
if (ptr2 != NULL) {*ptr2 = 10;
}

方法二:释放后立即置NULL

int *ptr = (int*)malloc(sizeof(int));
if (ptr != NULL) {*ptr = 100;printf("%d\n", *ptr);
}free(ptr);// 释放内存
ptr = NULL;// 立即置NULL
// 安全检查
if (ptr != NULL) 
{printf("%d\n", *ptr);// 不会执行} 
else 
{printf("指针已释放\n");
}

方法三:避免返回局部变量地址

// 错误做法
int* badFunction() {int local = 5;return &local;
}// 正确做法1:使用静态变量
int* goodFunction1() {static int staticVar = 5;// 静态变量生命周期延长return &staticVar;
}// 正确做法2:使用动态内存
int* goodFunction2() {int *dynamicVar = (int*)malloc(sizeof(int));if (dynamicVar != NULL) {*dynamicVar = 5;}return dynamicVar;
}// 正确做法3:通过参数返回
void goodFunction3(int **result) {*result = (int*)malloc(sizeof(int));if (*result != NULL) {**result = 5;}
}

方法四:使用函数返回值检查

int *createArray(int size) {if (size <= 0) {return NULL;// 返回NULL而不是野指针}return (int*)malloc(size * sizeof(int));
}int main() {int *arr = createArray(5);if (arr == NULL) {printf("内存分配失败\n");return 1;}// 使用arr...free(arr);arr = NULL;return 0;
}

方法五:封装内存管理函数

// 安全的malloc封装
void* safeMalloc(size_t size) {void *ptr = malloc(size);if (ptr == NULL) {fprintf(stderr, "内存分配失败\n");exit(EXIT_FAILURE);}return ptr;
}// 安全的free封装
void safeFree(void **ptr) {if (ptr != NULL && *ptr != NULL) {free(*ptr);*ptr = NULL;// 自动置NULL}
}int main() {int *arr = (int*)safeMalloc(5 * sizeof(int));// 使用arr...safeFree((void**)&arr);// 自动置NULL// 现在arr为NULL,不会成为野指针return 0;
}

5. 检测野指针的工具

使用Valgrind(Linux)

gcc -g program.c -o program
valgrind --leak-check=full ./program

十一、动态内存分配

使用指针管理堆内存(需引入 <stdlib.h>):

int *arr = (int *)malloc(5 * sizeof(int));// 分配5个int的空间
if (arr != NULL) {arr[0] = 100;
// 使用完毕后释放内存free(arr);arr = NULL;// 避免野指针
}

十二、指针的注意事项&应用场合

问题 说明 规避方法
野指针 指向未知内存的指针 初始化时置空,释放后置空
空指针解引用 对 NULL 指针进行操作导致崩溃 使用前检查指针是否为 NULL
内存泄漏 未释放动态分配的内存 确保每个 malloc 对应一个 free
指针类型不匹配 操作不同类型指针导致数据错误 避免强制类型转换的误用
  1. 当函数需要传递一个数组时,指针代替数组
  2. 当函数需要返回一个地址(例如函数需要返回数组)的时候,需要使用指针
    实现。
  3. 当函数需要改变实参的时候,需要使用指针实现。-----形参改变实参

十三、代码示例

1. 指针操作基本流程

#include <stdio.h>
int main() {int a = 10;int *p = &a;printf("a的地址 = %p\n", (void *)p);// 输出地址(如0x7ffd...)printf("a的值 = %d\n", *p);// 输出10*p = 20;printf("修改后a的值 = %d\n", a);// 输出20return 0;
}

2. 动态数组操作

#include <stdio.h>
#include <stdlib.h>
int main() {int size = 5;int *arr = (int *)malloc(size * sizeof(int));if (arr == NULL) {printf("内存分配失败!\n");return 1;}for (int i = 0; i < size; i++) {arr[i] = i * 10;}for (int *p = arr; p < arr + size; p++) {printf("%d ", *p);// 输出0 10 20 30 40}free(arr);arr = NULL;return 0;
}
http://www.hskmm.com/?act=detail&tid=9435

相关文章:

  • windows下Qt调用fftw库
  • Gitee崛起:国产代码托管平台如何接棒CODING成为开发者新宠
  • flask下的MySQL增查配置
  • AT_agc056_c [AGC056C] 01 Balanced
  • 高效智能(到家服务)管理平台解决方案
  • jenkins如何与shell脚本结合使用
  • 【C++11】深度剖析智能指针 - 教程
  • struct * 初始化和造数据的简洁方式(二叉树,d, *left, *right)(面试)
  • Android抓包
  • 火山引擎多模态数据湖:基于 Daft 与 Lance,构筑 AI 时代数据湖新范式
  • doris窗口函数 LAG()(取上一条)和 LEAD()(取下一条)函数
  • debmirror工具
  • centos7卸载openjdk-java11
  • jenkins的安装和配置
  • 深入解析:【Day 52 】Linux-Jenkins
  • 本土开发者如何选择代码管理工具?Gitee与GitHub深度对比解析
  • MES系统核心组件
  • 易基因:多组学整合分析揭示DNA甲基化与基因组改变在肿瘤进化中的协同驱动机制|Nat Genet/IF29重磅
  • AI 视频模型大比拼(2025年7月版):价格、效果与分辨率
  • 为什么芯片行业需要私有化部署软件?
  • C++ std::string
  • MathType数学公式编辑器v7.9.1
  • git常见冲突场景及解决办法 - 指南
  • 有关字节的基础知识
  • strip去符号前后对比
  • 2025 ICPC网络赛第一场 L cover
  • 文件自动同步软件用哪个好,高效选择指南
  • 【初赛】指针 - Slayer
  • 国产化FPGA-2050-基于JFMK50T4(XC7A50T)的核心板
  • hbase学习2