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

C 语言各种指针详解

指针是 C 语言的核心特性之一,它通过直接操作内存地址赋予程序高效的内存访问能力,但也因其灵活性成为初学者的“拦路虎”。本文将系统解析六种核心指针类型,结合代码示例与原理分析,帮助开发者更好地掌握指针的正确用法。

在这里插入图片描述

一、普通指针:内存访问的基础工具

普通指针指向具体的数据类型,核心功能是通过地址间接访问和修改数据,突破函数传值调用的限制。

1. 定义与初始化

语法:数据类型 *指针变量名 = 地址;

指针变量存储的是目标变量的内存起始地址,需要通过取地址符&获取。

2. 核心操作示例

#include <stdio.h>// 1. 交换两个变量的值(突破传值限制)
void swap(int *a, int *b) {int temp = *a;  // 解引用获取a指向的内存值*a = *b;        // 修改a指向的内存数据*b = temp;      // 修改b指向的内存数据
}// 2. 返回多个计算结果
void calc(int a, int b, int *sum, int *diff) {*sum = a + b;   // 结果写入sum指向的内存*diff = a - b;  // 结果写入diff指向的内存
}int main() {int x = 10, y = 20;swap(&x, &y);   // 传入变量地址printf("交换后:x=%d, y=%d\n", x, y);  // 输出:交换后:x=20, y=10int s, d;calc(30, 12, &s, &d);printf("和:%d, 差:%d\n", s, d);  // 输出:和:42, 差:18return 0;
}

3. 关键特性

  • 解引用运算符*:通过指针访问目标内存数据。
  • 步长特性:指针加减运算的步长等于指向的数据类型的大小(例如int*的步长为4字节)。
  • 必须初始化:未初始化的野指针会访问未知内存,导致程序崩溃。

二、函数指针:指向代码的指针

函数指针存储函数的入口地址,能够实现函数的动态调用与回调机制,是模块化编程的核心工具。

1. 定义与初始化

语法:返回值类型 (*指针名)(参数类型列表) = 函数名;

括号不可省略,否则会被解析为“返回指针的函数”。

2. 核心操作示例

#include <stdio.h>
#include <stdlib.h>// 基础函数
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }// 回调函数场景:排序比较器
int ascending(int a, int b) { return a - b; }  // 升序
int descending(int a, int b) { return b - a; } // 降序// 通用排序函数(通过函数指针实现动态排序规则)
void sort(int arr[], int len, int (*cmp)(int, int)) {for (int i = 0; i < len - 1; i++) {for (int j = 0; j < len - i - 1; j++) {if (cmp(arr[j], arr[j + 1]) > 0) {int temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}}
}int main() {// 1. 基础函数调用int (*funcPtr)(int, int) = add;printf("2+3=%d\n", funcPtr(2, 3));  // 输出:2+3=5// 2. 回调函数应用int arr[] = {5, 2, 8, 1};int len = sizeof(arr) / sizeof(arr[0]);sort(arr, len, ascending);printf("升序:");for (int i = 0; i < len; i++) printf("%d ", arr[i]);  // 输出:升序:1 2 5 8sort(arr, len, descending);printf("\n降序:");for (int i = 0; i < len; i++) printf("%d ", arr[i]);  // 输出:降序:8 5 2 1return 0;
}

3. 典型应用

  • 回调函数:如排序算法中的比较器、事件响应机制。
  • 函数表:实现状态机、插件系统等动态功能。
  • 跨模块调用:减少代码耦合度。

三、二级指针:指向指针的指针

二级指针存储指针变量的地址,常用于修改一级指针的指向或传递动态多维数组。

1. 定义与初始化

语法:数据类型 **二级指针名 = &一级指针名;

可以理解为“指针的指针”,需要两次解引用才能访问目标数据。

2. 核心操作示例

#include <stdio.h>
#include <stdlib.h>// 1. 修改一级指针的指向
void resetPointer(int **p) {int *newPtr = (int *)malloc(sizeof(int));*newPtr = 100;*p = newPtr;  // 修改一级指针的指向
}// 2. 动态创建二维数组
int **create2DArray(int rows, int cols) {int **arr = (int **)malloc(rows * sizeof(int *));for (int i = 0; i < rows; i++) {arr[i] = (int *)calloc(cols, sizeof(int));  // 初始化为0}return arr;
}int main() {// 示例1:修改指针指向int *ptr = NULL;resetPointer(&ptr);printf("动态值:%d\n", *ptr);  // 输出:动态值:100free(ptr);ptr = NULL;// 示例2:动态二维数组int rows = 2, cols = 3;int **matrix = create2DArray(rows, cols);matrix[0][1] = 5;matrix[1][2] = 8;printf("二维数组:\n");for (int i = 0; i < rows; i++) {for (int j = 0; j < cols; j++) {printf("%d ", matrix[i][j]);}printf("\n");}// 释放内存for (int i = 0; i < rows; i++) free(matrix[i]);free(matrix);matrix = NULL;return 0;
}

3. 注意事项

  • 解引用层级:**p访问目标数据,*p访问一级指针地址。
  • 内存管理:动态多维数组需逐层释放,避免内存泄漏。
  • 常见场景:函数传递指针地址、实现链表等复杂数据结构。

四、数组指针:指向数组的指针

数组指针指向整个数组而非单个元素,常用于二维数组的高效访问与传递。

1. 定义与初始化

语法:数据类型 (*指针名)[数组长度] = &数组名;

与“指针数组”(数据类型 *指针名[数组长度])需严格区分。

2. 核心操作示例

#include <stdio.h>// 数组指针作为函数参数(传递二维数组)
void printMatrix(int (*matrix)[3], int rows) {for (int i = 0; i < rows; i++) {for (int j = 0; j < 3; j++) {// 两种访问方式等效printf("%d ", matrix[i][j]);// printf("%d ", *( *(matrix+i)+j));}printf("\n");}
}int main() {// 初始化数组指针int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};int (*pArr)[3] = arr;  // 数组名自动转换为数组指针// 指针运算特性(步长为数组总大小:3*4=12字节)printf("首行地址:%p\n", (void *)pArr);printf("第二行地址:%p\n", (void *)(pArr + 1));  // 地址差值为12// 函数传递与访问printf("二维数组内容:\n");printMatrix(pArr, 2);return 0;
}

3. 关键区别:数组指针 vs 指针数组

类型定义示例指向对象步长特性
数组指针int (*p)[3]整个 int [3] 数组数组总大小(12B)
指针数组int *p[3]3 个 int * 指针单个指针大小(通常是8B)

五、字符串指针:指向字符序列的指针

字符串指针指向字符串常量或字符数组的首地址,是 C 语言处理字符串的核心方式。

1. 定义与初始化

语法 1(字符串常量):const char *str = "hello";(不可修改内容)

语法 2(字符数组):char str[] = "hello";(可修改内容)

2. 核心操作示例

#include <stdio.h>
#include <string.h>int main() {// 1. 字符串指针(指向常量,不可修改)const char *str1 = "C language";printf("字符串1:%s\n", str1);printf("首字符:%c\n", *str1);  // 输出:首字符:C// *str1 = 'c';  // 错误:字符串常量不可修改// 2. 字符数组(可修改)char str2[] = "pointer";str2[0] = 'P';printf("修改后字符串2:%s\n", str2);  // 输出:修改后字符串2:Pointer// 3. 字符串长度计算printf("str1长度:%zu\n", strlen(str1));  // 输出:str1长度:10(不含'\0')printf("str2占用内存:%zu\n", sizeof(str2));  // 输出:str2占用内存:8(含'\0')// 4. 指针遍历字符串const char *p = str1;printf("遍历字符串1:");while (*p != '\0') {  // 遇'\0'结束printf("%c", *p);p++;}printf("\n");return 0;
}

3. 关键特性

  • 结束标志:字符串以'\0'结尾,缺失会导致遍历越界。
  • 存储差异:字符串常量存放在只读区,字符数组存放在栈区。
  • 效率优势:字符串指针操作比数组下标访问更高效。

六、void * 指针:无类型通用指针

void * 指针又称空类型指针,可以指向任意类型的数据,是实现通用接口的关键工具。

1. 核心规则

  1. 可接收任意类型指针的赋值,无需强制转换。
  2. 赋值给其他类型指针时,必须强制转换。
  3. ANSI C 中不可直接解引用或进行算术运算(GNU 编译器视为 char*)。

2. 核心操作示例

#include <stdio.h>
#include <string.h>// 通用内存拷贝函数(模拟memcpy)
void my_memcpy(void *dest, const void *src, size_t size) {// 转换为char*进行字节级拷贝char *d = (char *)dest;const char *s = (const char *)src;for (size_t i = 0; i < size; i++) {d[i] = s[i];}
}int main() {// 1. 多类型指针赋值int num = 100;char ch = 'A';void *p = &num;  // 指向intprintf("int值:%d\n", *(int *)p);  // 需强制转换,输出:int值:100p = &ch;  // 指向char,无需转换printf("char值:%c\n", *(char *)p);  // 输出:char值:A// 2. 通用函数应用int arr1[] = {1, 2, 3};int arr2[3];my_memcpy(arr2, arr1, sizeof(arr1));  // 拷贝int数组printf("拷贝结果:%d %d %d\n", arr2[0], arr2[1], arr2[2]);  // 输出:1 2 3// 3. 算术运算限制(ANSI C报错,GNU视为char*)// p++;  // ANSI C错误:void*无法确定步长return 0;
}

3. 典型应用

  • 内存操作函数:如 mallocmemcpymemset
  • 通用数据结构:如链表、栈可存储任意类型数据。
  • 跨类型接口:减少函数重载,实现多态效果。

七、指针避坑指南

  1. 野指针防治
  • 定义时初始化为NULLint *p = NULL;
  • 动态内存释放后立即置空:free(p); p = NULL;
  • 使用前检查有效性:if (p != NULL) { ... }
  1. 越界访问
  • 数组遍历严格控制边界,避免p >= arr+len
  • 字符串操作确保'\0'存在
  1. 类型匹配
  • 避免不同类型指针直接赋值(void * 除外)
  • 函数指针参数类型必须与函数原型一致
  1. 内存泄漏
  • 动态分配的内存(malloc/calloc)必须free
  • 多维动态数组需逐层释放

结语

指针是 C 语言的核心竞争力,从普通指针的内存访问到函数指针的回调机制,从二级指针的多维数组到 void * 的通用接口,每种指针都承载着特定的功能价值。掌握指针的关键在于理解“地址”本质与“类型”约束,结合实践规避野指针、越界等常见错误,才能真正发挥 C 语言的高效与灵活特性。

http://www.dtcms.com/a/427538.html

相关文章:

  • 【个人随想】我们是否缺乏从头再来的勇气
  • 自监督学习在医疗AI中的技术实现路径分析(上)
  • 麻涌手机网站设计建设的网站服务器
  • 五维论-解释万物法则
  • 国际海运业务全流程操作解析:易境通海运系统如何赋能各环节?
  • 【代码随想录day 29】 力扣 406.根据身高重建队列
  • 上海网站建设报价单网站设计平台及开发工具
  • 2006 年真题配套词汇单词笔记(考研真相)
  • Ubuntu 24.04 安装搜狗输入法完整教程
  • 淘宝买cdk自己做网站游戏编程软件
  • 试用网站如何做免费自动交易软件app
  • Linux rsyslog 日志服务及日志转发实践
  • 静态网站flash怎么看网站的访问量
  • 体育如何做原创视频网站潮州seo网站推广
  • d40: vue杂项问题
  • WordPress 安全检查指南:让你的网站更稳定、更安全
  • 类似于wordpress的网站开放平台供稿人计划
  • Vue.js props mutating:反模式如何被视为一种良好实践。
  • 基于STM32与influxDB的电力监控系统-12
  • 学习html的网站网站备案一次就可以了吧
  • Linux兄弟线程唤醒和调度的实现
  • Process Hacker下载和安装教程(附安装包)
  • 坑梓网站建设方案公众号制作网页
  • 企业身份认证系统选型:Azure AD 与 Keycloak 功能详解
  • 云手机ARM架构都具有哪些挑战
  • 域名与网站amh wordpress伪静态设置
  • docker 安装 xxl-job 详解
  • 数字博物馆网站建设备案的时候需要网站吗
  • 数学-绝对值(二)
  • css三角形源码