C语言指针-3
C语言指针是存储内存地址的变量,通过&取地址与*解引用实现对内存的直接操控,其核心价值在于赋予程序员精细的内存控制能力:广泛应用于数组/字符串遍历、函数参数传递(单指针修改数据内容,双指针修改指针指向如动态内存分配与链表头更新)、函数指针实现回调与策略模式、多级指针构建树/图等复杂数据结构、void指针编写泛型函数、以及malloc/realloc/free实现动态内存管理。然而,指针也是C语言中最易出错的部分——必须严格防范空指针解引用、野指针、悬空指针、内存泄漏与越界访问,遵循“初始化→检查→使用→释放→置NULL”原则。掌握指针,是理解C语言灵魂、编写高效安全底层代码、深入操作系统与嵌入式开发的基石,亦是区分初级与资深C程序员的关键分水岭。
一、指针的本质与基础概念
1.1 什么是指针?
指针本质上是一个存储内存地址的变量。通过指针,我们可以直接访问和操作内存中的数据。
形象比喻:可以把内存看作一栋宿舍楼,每个房间(内存单元)都有一个门牌号(地址)。指针就是这个门牌号,通过它我们可以找到对应的房间并访问其中的内容。
int var = 10; // 普通整型变量
int *ptr = &var; // ptr是指向var的指针,存储var的地址
1.2 指针变量的定义与初始化
// 定义指针变量的一般形式
类型名 *指针变量名;
// 示例
int *p_i; // 定义int型指针变量
float *p_f; // 定义float型指针变量
double *p_d; // 定义double型指针变量
char *p_c; // 定义char型指针变量
// 初始化指针
int a = 100;
int *p_a = &a; // 取整型变量a的地址赋给指针变量p_a
// 空指针
int *p_null = NULL; // 赋NULL值的指针被称为空指针
1.3 指针运算符
#include <stdio.h>
int main() {
int a = 10;
int *p;
p = &a; // &取地址运算符,获取a的地址
printf("a的值: %d\n", a); // 输出: 10
printf("a的地址: %p\n", &a); // 输出a的地址
printf("p的值(地址): %p\n", p); // 输出p中存储的地址
printf("*p的值: %d\n", *p); // *解引用运算符,获取p指向的值
*p = 20; // 通过指针修改a的值
printf("修改后a的值: %d\n", a); // 输出: 20
return 0;
}
二、指针的核心应用场景
2.1 指针与数组
数组名本质上是指向数组首元素的指针。
#include <stdio.h>
// 使用指针遍历数组,计算数组元素的和
int sum(int *arr, int size) {
int total = 0;
for (int i = 0; i < size; i++) {
total += *(arr + i); // 通过指针访问数组元素
}
return total;
}
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int result = sum(arr, 5); // 数组名作为指针传递
printf("数组元素的和为: %d\n", result); // 输出: 15
return 0;
}
指针与数组的等价关系:
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr; // 等价于 p = &arr[0]
// 以下访问方式等价
arr[2] == *(arr + 2) == p[2] == *(p + 2)
2.2 指针与字符串
在C语言中,字符串是以字符数组的形式存储的,字符串常量实际上是一个指向字符数组首元素的指针。
#include <stdio.h>
// 使用指针遍历字符串,计算字符串的长度
int string_length(char *str) {
int len = 0;
while (*str != '\0') { // 遍历直到遇到字符串结束符
len++;
str++; // 指针移动到下一个字符
}
return len;
}
int main() {
char *str1 = "Hello, World!"; // 字符串常量,指向只读内存
char str2[] = "Hello"; // 字符数组,内容可修改
printf("字符串1: %s\n", str1);
printf("字符串2: %s\n", str2);
int length = string_length(str2);
printf("字符串的长度为: %d\n", length); // 输出: 5
// 修改字符串内容
str2[0] = 'h';
printf("修改后的字符串: %s\n", str2); // 输出: hello
return 0;
}
2.3 指针与函数参数传递
2.3.1 单指针:修改指针指向的数据内容
#include <stdio.h>
// 交换两个整数的值
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
// 修改数组元素
void fillArray(int *arr, int size) {
for (int i = 0; i < size; i++) {
arr[i] = i * 10; // 通过指针修改数组内容
}
}
int main() {
int x = 5, y = 10;
printf("交换前: x = %d, y = %d\n", x, y);
swap(&x, &y); // 传递变量的地址
printf("交换后: x = %d, y = %d\n", x, y); // 输出: x = 10, y = 5
int arr[5];
fillArray(arr, 5);
printf("数组内容: ");
for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]); // 输出: 0 10 20 30 40
}
printf("\n");
return 0;
}
2.3.2 双指针:修改指针本身的指向
#include <stdio.h>
#include <stdlib.h>
// 动态分配内存
void allocate(int **ptr, int size) {
*ptr = (int *)malloc(size * sizeof(int)); // 修改外部指针的地址
if (*ptr != NULL) {
for (int i = 0; i < size; i++) {
(*ptr)[i] = i + 1;
}
}
}
// 交换两个指针
void swapPointers(int **a, int **b) {
int *temp = *a;
*a = *b;
*b = temp;
}
int main() {
int *p1 = NULL;
int *p2 = NULL;
// 动态分配内存
allocate(&p1, 3);
if (p1 != NULL) {
printf("分配的数组: ");
for (int i = 0; i < 3; i++) {
printf("%d ", p1[i]); // 输出: 1 2 3
}
printf("\n");
free(p1); // 释放内存
}
// 交换指针
int a = 10, b = 20;
p1 = &a;
p2 = &b;
printf("交换前: *p1 = %d, *p2 = %d\n", *p1, *p2);
swapPointers(&p1, &p2);
printf("交换后: *p1 = %d, *p2 = %d\n", *p1, *p2); // 输出: *p1 = 20, *p2 = 10
return 0;
}
2.4 函数指针
函数指针是指向函数的指针变量,可以用来调用函数或作为参数传递。
#include <stdio.h>
// 定义函数
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
int multiply(int a, int b) {
return a * b;
}
// 使用函数指针实现计算器
int calculator(int (*func)(int, int), int a, int b) {
return func(a, b); // 通过函数指针调用函数
}
// 查找数组中的最大值
int findMax(int arr[], int size, int (*compare)(int, int)) {
int max = arr[0];
for (int i = 1; i < size; i++) {
if (compare(arr[i], max) > 0) {
max = arr[i];
}
}
return max;
}
// 比较函数
int compareInt(int a, int b) {
return a - b;
}
int main() {
// 基本函数指针用法
int (*func_ptr)(int, int); // 定义函数指针
func_ptr = add; // 将函数指针指向add函数
int result = func_ptr(3, 5); // 通过函数指针调用函数
printf("3 + 5 = %d\n", result); // 输出: 8
// 使用函数指针实现计算器
int num1 = 10, num2 = 5;
int sum = calculator(add, num1, num2);
int diff = calculator(subtract, num1, num2);
int prod = calculator(multiply, num1, num2);
printf("%d + %d = %d\n", num1, num2, sum); // 输出: 10 + 5 = 15
printf("%d - %d = %d\n", num1, num2, diff); // 输出: 10 - 5 = 5
printf("%d * %d = %d\n", num1, num2, prod); // 输出: 10 * 5 = 50
// 使用函数指针进行自定义比较
int arr[] = {3, 7, 2, 9, 1, 5};
int max = findMax(arr, 6, compareInt);
printf("数组中的最大值: %d\n", max); // 输出: 9
return 0;
}
2.5 多级指针(二级指针及以上)
#include <stdio.h>
int main() {
int n = 123;
int *pn = &n; // 一级指针
int **pnn = &pn; // 二级指针
int ***pnnn = &pnn; // 三级指针
printf("n = %d\n", n);
printf("*pn = %d\n", *pn);
printf("**pnn = %d\n", **pnn);
printf("***pnnn = %d\n", ***pnnn);
// 修改原始值
***pnnn = 456;
printf("修改后n = %d\n", n); // 输出: 456
return 0;
}
多级指针的实际应用:
#include <stdio.h>
#include <stdlib.h>
// 动态创建二维数组
int **create2DArray(int rows, int cols) {
int **arr = (int **)malloc(rows * sizeof(int *));
for (int i = 0; i < rows; i++) {
arr[i] = (int *)malloc(cols * sizeof(int));
for (int j = 0; j < cols; j++) {
arr[i][j] = i * cols + j + 1;
}
}
return arr;
}
// 释放二维数组
void free2DArray(int **arr, int rows) {
for (int i = 0; i < rows; i++) {
free(arr[i]);
}
free(arr);
}
int main() {
int rows = 3, cols = 4;
int **matrix = create2DArray(rows, cols);
printf("二维数组内容:\n");
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%3d ", matrix[i][j]);
}
printf("\n");
}
/* 输出:
1 2 3 4
5 6 7 8
9 10 11 12
*/
free2DArray(matrix, rows);
return 0;
}
2.6 动态内存分配
#include <stdio.h>
#include <stdlib.h>
int main() {
// 分配单个整数
int *p = (int *)malloc(sizeof(int));
if (p != NULL) {
*p = 42;
printf("分配的整数: %d\n", *p); // 输出: 42
free(p); // 释放内存
}
// 分配整数数组
int size = 5;
int *arr = (int *)malloc(size * sizeof(int));
if (arr != NULL) {
for (int i = 0; i < size; i++) {
arr[i] = i * 10;
}
printf("动态数组: ");
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]); // 输出: 0 10 20 30 40
}
printf("\n");
free(arr); // 释放内存
}
// 重新分配内存
int *arr2 = (int *)malloc(3 * sizeof(int));
if (arr2 != NULL) {
for (int i = 0; i < 3; i++) {
arr2[i] = i + 1;
}
// 扩展数组大小
arr2 = (int *)realloc(arr2, 6 * sizeof(int));
if (arr2 != NULL) {
for (int i = 3; i < 6; i++) {
arr2[i] = i + 1;
}
printf("重新分配后的数组: ");
for (int i = 0; i < 6; i++) {
printf("%d ", arr2[i]); // 输出: 1 2 3 4 5 6
}
printf("\n");
free(arr2);
}
}
return 0;
}
2.7 void指针(泛型指针)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 通用交换函数
int swap2(void *x, void *y, size_t size) {
void *tmp = malloc(size);
if (tmp == NULL) {
return -1;
}
memcpy(tmp, x, size);
memcpy(x, y, size);
memcpy(y, tmp, size);
free(tmp);
return 0;
}
// 通用打印函数
void printArray(void *array, size_t size, size_t elemSize,
void (*printElem)(void *)) {
char *ptr = (char *)array;
for (size_t i = 0; i < size; i++) {
printElem(ptr + i * elemSize);
printf(" ");
}
printf("\n");
}
// 打印整数
void printInt(void *elem) {
printf("%d", *(int *)elem);
}
// 打印浮点数
void printDouble(void *elem) {
printf("%.2f", *(double *)elem);
}
int main() {
// 交换整数
int a = 3, b = 4;
printf("交换前: a = %d, b = %d\n", a, b);
swap2(&a, &b, sizeof(int));
printf("交换后: a = %d, b = %d\n", a, b); // 输出: a = 4, b = 3
// 交换浮点数
double c = 3.14, d = 2.71;
printf("交换前: c = %.2f, d = %.2f\n", c, d);
swap2(&c, &d, sizeof(double));
printf("交换后: c = %.2f, d = %.2f\n", c, d); // 输出: c = 2.71, d = 3.14
// 使用通用打印函数
int intArr[] = {1, 2, 3, 4, 5};
double doubleArr[] = {1.1, 2.2, 3.3, 4.4, 5.5};
printf("整数数组: ");
printArray(intArr, 5, sizeof(int), printInt); // 输出: 1 2 3 4 5
printf("浮点数数组: ");
printArray(doubleArr, 5, sizeof(double), printDouble); // 输出: 1.10 2.20 3.30 4.40 5.50
return 0;
}
三、指针使用注意事项与常见陷阱
3.1 空指针检查
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr = NULL;
// 安全访问:先检查指针是否为空
if (ptr != NULL) {
*ptr = 10; // 安全访问
} else {
printf("指针为空,无法访问!\n");
}
// 分配内存后检查
ptr = (int *)malloc(sizeof(int));
if (ptr != NULL) {
*ptr = 20;
printf("分配的值: %d\n", *ptr); // 输出: 20
free(ptr);
} else {
printf("内存分配失败!\n");
}
return 0;
}
3.2 野指针问题
#include <stdio.h>
#include <stdlib.h>
void badFunction() {
int *ptr;
// ptr未初始化,是野指针
// *ptr = 10; // 危险!可能导致程序崩溃
int value = 100;
ptr = &value;
printf("*ptr = %d\n", *ptr); // 输出: 100
// 函数结束后,value被销毁,ptr成为悬空指针
} // value在此处被销毁
int main() {
badFunction();
// 正确做法:动态分配内存
int *safePtr = (int *)malloc(sizeof(int));
if (safePtr != NULL) {
*safePtr = 200;
printf("*safePtr = %d\n", *safePtr); // 输出: 200
free(safePtr); // 释放内存
safePtr = NULL; // 释放后将指针设为NULL,避免悬空指针
}
return 0;
}
3.3 内存泄漏
#include <stdio.h>
#include <stdlib.h>
void memoryLeakExample() {
int *ptr = (int *)malloc(sizeof(int));
if (ptr != NULL) {
*ptr = 42;
// 忘记释放内存,导致内存泄漏
// 应该添加: free(ptr);
}
}
void correctMemoryManagement() {
int *ptr = (int *)malloc(10 * sizeof(int));
if (ptr != NULL) {
for (int i = 0; i < 10; i++) {
ptr[i] = i;
}
// 使用完后释放内存
free(ptr);
ptr = NULL; // 避免悬空指针
}
}
int main() {
// 演示内存泄漏(仅用于说明,实际代码中应避免)
// memoryLeakExample();
// 正确的内存管理
correctMemoryManagement();
printf("内存管理正确,无泄漏!\n");
return 0;
}
3.4 指针运算边界检查
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int *ptr = arr;
// 安全的指针运算
for (int i = 0; i < 5; i++) {
printf("%d ", *(ptr + i)); // 输出: 1 2 3 4 5
}
printf("\n");
// 危险的指针运算:越界访问
// printf("%d\n", *(ptr + 10)); // 可能导致程序崩溃或未定义行为
// 正确做法:始终确保指针在合法范围内
int index = 3;
if (index >= 0 && index < 5) {
printf("arr[%d] = %d\n", index, *(ptr + index)); // 输出: arr[3] = 4
} else {
printf("索引越界!\n");
}
return 0;
}
四、指针传递方式总结
C语言中指针传递方式可以总结如下:
| 场景 | 传递方式 | 说明 |
|---|---|---|
| 只读访问或修改数据内容 | 单指针 Type* | 函数仅需读取数据或修改指针指向的内存值,但不改变指针本身地址 |
| 修改指针指向地址或分配内存 | 双重指针 Type** | 需要修改指针本身的指向地址,如动态内存分配、链表头指针更新等 |
#include <stdio.h>
#include <stdlib.h>
// 单指针:只修改指针指向的数据
void modifyValue(int *ptr) {
*ptr = 100; // 修改指针指向的值,但不改变指针本身
}
// 双指针:修改指针本身的指向
void allocateMemory(int **ptr) {
*ptr = (int *)malloc(sizeof(int)); // 修改外部指针的地址
if (*ptr != NULL) {
**ptr = 200; // 修改新分配内存的值
}
}
// 链表节点
typedef struct Node {
int data;
struct Node *next;
} Node;
// 使用双指针插入节点(修改头指针)
void insertNode(Node **head, int value) {
Node *newNode = (Node *)malloc(sizeof(Node));
newNode->data = value;
newNode->next = *head; // 新节点指向原头节点
*head = newNode; // 更新头指针
}
int main() {
// 单指针示例
int a = 10;
printf("修改前: a = %d\n", a);
modifyValue(&a);
printf("修改后: a = %d\n", a); // 输出: 100
// 双指针示例
int *p = NULL;
allocateMemory(&p);
if (p != NULL) {
printf("分配的值: %d\n", *p); // 输出: 200
free(p);
}
// 链表操作示例
Node *head = NULL;
insertNode(&head, 3);
insertNode(&head, 2);
insertNode(&head, 1);
printf("链表内容: ");
Node *current = head;
while (current != NULL) {
printf("%d ", current->data); // 输出: 1 2 3
current = current->next;
}
printf("\n");
// 释放链表内存
while (head != NULL) {
Node *temp = head;
head = head->next;
free(temp);
}
return 0;
}
五、总结
C语言指针是C语言中最强大也最复杂的概念之一。掌握指针的使用可以:
- 提高程序效率:直接操作内存,减少数据复制
- 实现复杂数据结构:如链表、树、图等
- 动态内存管理:根据需要分配和释放内存
- 实现回调机制:通过函数指针实现灵活的程序结构
- 编写通用代码:使用void指针编写适用于多种数据类型的函数
然而,指针使用不当也会导致严重问题:
- 空指针解引用导致程序崩溃
- 野指针和悬空指针导致未定义行为
- 内存泄漏导致资源浪费
- 缓冲区溢出导致安全漏洞
因此,在使用指针时,务必遵循以下原则:
- 始终初始化指针
- 使用前检查指针是否为空
- 动态分配的内存使用后及时释放
- 释放后将指针设为NULL
- 确保指针运算在合法范围内
- 理解单指针和双指针的使用场景
通过系统学习和实践,你将能够熟练掌握C语言指针,编写出高效、安全的C程序。
- 感谢你赐予我前进的力量
赞赏者名单
因为你们的支持让我意识到写文章的价值🙏
本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 软件从业者Hort
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果

