C语言指针-2
C语言指针是存储内存地址的变量,作为C语言的“灵魂”,它贯穿程序设计的方方面面:通过&取地址、*解引用实现内存直接操控;支撑数组遍历(arr+i)、字符串处理(以\0结尾的字符指针)、函数参数传递(实现引用调用与交换)、动态内存管理(malloc/free配合二级指针)、函数指针(实现回调、策略模式、事件系统)及复杂数据结构(链表、树)构建;同时需严格遵循安全规范——初始化为NULL、使用前校验、释放后置空、避免越界与悬空指针。指针赋予C语言极致的效率与灵活性,但也要求开发者具备严谨的内存意识:善用则如虎添翼,误用则隐患丛生。掌握指针,即是掌握C语言高效、底层编程的核心能力。
指针是C语言的灵魂,掌握指针是成为C语言高手的必经之路。本文将系统、全面地讲解C语言指针的方方面面,包含核心概念、详细应用场景及完整代码示例。
一、指针基础概念
1.1 什么是指针?
- 指针本质:存储内存地址的变量("门牌号"概念)
- 指针变量:专门用于存放地址的变量容器
- 关键符号:
- &:取地址运算符
- *:声明指针 / 解引用运算符
1.2 指针声明与初始化
#include <stdio.h>
int main() {
int var = 42;
int *ptr = &var; // 声明并初始化指针
printf("变量var的值: %d\n", var);
printf("变量var的地址: %p\n", (void*)&var);
printf("指针ptr存储的地址: %p\n", (void*)ptr);
printf("通过指针访问的值: %d\n", *ptr); // 解引用
*ptr = 100; // 通过指针修改原变量
printf("修改后var的值: %d\n", var);
return 0;
}
输出:
变量var的值: 42
变量var的地址: 0x7ffd4f4a5a4c
指针ptr存储的地址: 0x7ffd4f4a5a4c
通过指针访问的值: 42
修改后var的值: 100
1.3 指针大小与平台关系
#include <stdio.h>
int main() {
printf("int指针大小: %zu 字节\n", sizeof(int*));
printf("char指针大小: %zu 字节\n", sizeof(char*));
printf("double指针大小: %zu 字节\n", sizeof(double*));
// 32位系统均为4字节,64位系统均为8字节
return 0;
}
二、指针核心运算规则
2.1 指针±整数:类型决定步长
#include <stdio.h>
int main() {
int a = 100;
double b = 3.14;
char c = 'A';
int *pa = &a;
double *pb = &b;
char *pc = &c;
printf("int指针+1: %p -> %p (差%ld字节)\n", (void*)pa, (void*)(pa+1), (long)((pa+1)-pa)*sizeof(int));
printf("double指针+1: %p -> %p (差%ld字节)\n", (void*)pb, (void*)(pb+1), (long)((pb+1)-pb)*sizeof(double));
printf("char指针+1: %p -> %p (差%ld字节)\n", (void*)pc, (void*)(pc+1), (long)((pc+1)-pc)*sizeof(char));
return 0;
}
关键结论:指针 + n 实际移动 n × sizeof(指针类型) 字节
2.2 指针相减:计算元素间隔
#include <stdio.h>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int *p1 = &arr[1];
int *p2 = &arr[4];
printf("p2 - p1 = %ld (间隔3个int元素)\n", p2 - p1);
printf("实际字节差: %ld\n", (char*)p2 - (char*)p1); // 12字节
return 0;
}
三、指针与数组
3.1 一维数组遍历(两种等价写法)
#include <stdio.h>
//int size = sizeof(arr) / sizeof(int);
//在函数内部使用 sizeof(arr) 无法获取原数组的真实长度
//当数组作为参数传递给函数时(sum_array(arrSum)),C语言会将数组退化为指针。在函数 sum_array 内部:
//arr 的实际类型是 int*(指针),而非数组
//sizeof(arr) 返回的是指针本身的大小(32位系统=4字节,64位系统=8字节)
//sizeof(int) 通常是4字节
//因此 size = sizeof(arr)/sizeof(int) 计算结果为:
//32位系统:4/4 = 1
//64位系统:8/4 = 2
//循环只会执行1~2次,导致只累加了前1~2个元素(结果为1或3),而非完整的15。
int sum_array1(int *arr, int size) {
int total = 0;
// 写法1:指针算术
for (int i = 0; i < size; i++) {
total += *(arr + i);
}
// 写法2:指针自增(更高效)
// int *end = arr + size;
// while (arr < end) total += *arr++;
return total;
}
int sum_array2(int *arr, int size) {
//int size = sizeof(arr) / sizeof(arr[0]);
int *end = arr + size;
int sum = 0;
while (arr < end) {
sum += *arr++;
}
return sum;
}
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int arrSum_size = sizeof(arrSum) / sizeof(arrSum[0]);
printf("数组和: %d\n", sum_array(arr, 5)); // 输出15
printf("数组和: %d\n", sum_array1(arr, arrSum_size)); // 输出15
printf("数组和: %d\n", sum_array2(arr, arrSum_size)); // 输出15
return 0;
}
3.2 二维数组与数组指针
#include <stdio.h>
int main() {
int arr[3][5] = {
{1, 2, 3, 4, 5},
{6, 7, 8, 9, 10},
{11, 12, 13, 14, 15}
};
// 指向"包含5个int的数组"的指针
int (*p)[5] = arr;
printf("第二行第二列元素: %d\n", *(*(p + 1) + 1)); // 输出7
printf("等价写法: %d\n", p[1][1]); // 同样输出7
// 遍历整个二维数组
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 5; j++) {
printf("%3d", *(*(p + i) + j));
}
printf("\n");
}
return 0;
}
四、指针与字符串
4.1 字符串长度计算
#include <stdio.h>
int string_length(char *str) {
int len = 0;
while (*str != '\0') { // 遍历直到空字符
len++;
str++; // 指针自增
}
return len;
}
int main() {
char str[] = "Hello, Pointer!";
printf("字符串: \"%s\"\n", str);
printf("长度: %d (不含\\0)\n", string_length(str));
printf("标准库验证: %zu\n", strlen(str));
return 0;
}
4.2 字符串反转(原地操作)
#include <stdio.h>
void reverse_string(char *str) {
char *start = str;
char *end = str;
while (*end) end++; // 移动到末尾
end--; // 回退到有效字符
while (start < end) {
char temp = *start;
*start = *end;
*end = temp;
start++;
end--;
}
}
int main() {
char text[] = "Pointer Magic";
printf("原字符串: %s\n", text);
reverse_string(text);
printf("反转后: %s\n", text); // "cigaM retnioP"
return 0;
}
五、指针与函数
5.1 按引用传递(交换函数)
#include <stdio.h>
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
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
return 0;
}
5.2 函数指针基础
#include <stdio.h>
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int main() {
// 声明函数指针
int (*func_ptr)(int, int);
func_ptr = add;
printf("3 + 5 = %d\n", func_ptr(3, 5)); // 8
func_ptr = subtract;
printf("10 - 4 = %d\n", func_ptr(10, 4)); // 6
return 0;
}
5.3 高级应用:函数指针实现计算器
#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 divide(int a, int b) { return b != 0 ? a / b : 0; }
// 计算器核心:接收函数指针作为参数
int calculator(int (*op)(int, int), int x, int y) {
return op(x, y);
}
int main() {
int a = 20, b = 5;
printf("%d + %d = %d\n", a, b, calculator(add, a, b));
printf("%d - %d = %d\n", a, b, calculator(subtract, a, b));
printf("%d * %d = %d\n", a, b, calculator(multiply, a, b));
printf("%d / %d = %d\n", a, b, calculator(divide, a, b));
return 0;
}
5.4 回调函数实战:事件处理系统
#include <stdio.h>
// 回调函数类型定义
typedef void (*EventCallback)(int);
void handle_event(int event_id, EventCallback callback) {
printf("【事件触发】ID: %d\n", event_id);
if (callback != NULL) {
callback(event_id); // 执行回调
}
}
void on_login(int id) {
printf(" → 用户登录事件处理 (ID:%d)\n", id);
}
void on_logout(int id) {
printf(" → 用户登出事件处理 (ID:%d)\n", id);
}
int main() {
handle_event(101, on_login);
handle_event(102, on_logout);
handle_event(103, NULL); // 无回调
return 0;
}
六、动态内存管理
6.1 安全的内存分配与释放
#include <stdio.h>
#include <stdlib.h>
int main() {
int n = 5;
// 动态分配整型数组
int *arr = (int *)malloc(n * sizeof(int));
if (arr == NULL) {
fprintf(stderr, "内存分配失败!\n");
return 1;
}
// 初始化数组
for (int i = 0; i < n; i++) {
arr[i] = (i + 1) * 10;
}
// 使用数组
printf("动态数组元素: ");
for (int i = 0; i < n; i++) {
printf("%d ", *(arr+i));
}
printf("\n");
// 释放内存(关键!)
free(arr);
arr = NULL; // 避免野指针
return 0;
}
6.2 二级指针:修改指针本身
#include <stdio.h>
#include <stdlib.h>
// 通过二级指针动态分配内存(改变外部指针)
void create_array(int **ptr, int size) {
*ptr = (int *)malloc(size * sizeof(int));
for (int i = 0; i < size; i++) {
(*ptr)[i] = i * 2;
}
}
int main() {
int *data = NULL;
create_array(&data, 4);
printf("动态创建的数组: ");
for (int i = 0; i < 4; i++) {
printf("%d ", *(data + i)); // 0 2 4 6
}
free(data);
return 0;
}
七、指针数组 vs 数组指针
7.1 指针数组:存储多个字符串
#include <stdio.h>
int main() {
// 指针数组:每个元素是指向字符串的指针
char *days[] = {
"Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday", "Sunday"
};
printf("一周七天:\n");
for (int i = 0; i < 7; i++) {
printf("%d,%s\n", i + 1, *(days + i));
}
return 0;
}
7.2 数组指针:指向整个数组
#include <stdio.h>
int main() {
int arr[3][4] = {
{1,2,3,4},
{5,6,7,8},
{9,10,11,12}
};
// 指向"包含4个int的数组"的指针
int (*p)[4] = arr;
printf("通过数组指针访问:\n");
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
printf("%3d", *(*(p + i) + j));
}
printf("\n");
}
return 0;
}
八、指针使用安全规范(避坑指南)
8.1 野指针防护三原则
#include <stdio.h>
#include <stdlib.h>
int main() {
// 原则1:初始化为NULL
int *safe_ptr = NULL;
// 原则2:使用前检查
if (safe_ptr != NULL) {
*safe_ptr = 10; // 安全
}
// 原则3:释放后置NULL
int *temp = (int *)malloc(sizeof(int));
if (temp) {
*temp = 42;
free(temp);
temp = NULL; // 防止二次释放
}
// 避免:未初始化指针
// int *danger;
// *danger = 100; // 严重错误!
return 0;
}
8.2 常见陷阱示例
#include <stdio.h>
#include <string.h>
void dangerous() {
// 陷阱1:返回局部变量地址
// char *str = "Hello"; // 字符串常量在只读区,相对安全
// 但以下危险:
// char local[] = "Danger";
// return local; // 局部数组,函数结束后内存失效
// 陷阱2:内存泄漏
// int *leak = (int *)malloc(100);
// 未free -> 内存泄漏
// 陷阱3:越界访问
int arr[3] = {1,2,3};
// arr[5] = 10; // 未定义行为
}
九、综合实战:学生成绩管理系统(指针核心应用)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char name[50];
int score;
} Student;
// 按分数排序(函数指针实现灵活排序)
void sort_students(Student *students, int n,
int (*compare)(Student*, Student*)) {
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (compare(&students[j], &students[j+1]) > 0) {
Student temp = students[j];
students[j] = students[j+1];
students[j+1] = temp;
}
}
}
}
int compare_by_score(Student *a, Student *b) {
return a->score - b->score; // 升序
}
int compare_by_name(Student *a, Student *b) {
return strcmp(a->name, b->name);
}
int main() {
Student *class = (Student *)malloc(3 * sizeof(Student));
// 初始化数据(指针操作)
strcpy((class+0)->name, "赵六"); (class+0)->score = 85;
strcpy((class+1)->name, "张三"); (class+1)->score = 92;
strcpy((class+2)->name, "李明"); (class+2)->score = 78;
printf("=== 按分数排序 ===\n");
sort_students(class, 3, compare_by_score);
for (int i = 0; i < 3; i++) {
printf("%s: %d分\n", (class+i)->name, (class+i)->score);
}
printf("\n=== 按姓名排序 ===\n");
sort_students(class, 3, compare_by_name);
for (int i = 0; i < 3; i++) {
printf("%s: %d分\n", class[i].name, class[i].score);
}
free(class);
return 0;
}
十、核心总结与最佳实践
| 类别 | 关键要点 | 安全建议 |
|---|---|---|
| 基础 | 指针=地址,*解引用,&取地址 | 始终初始化指针 |
| 运算 | ptr+n移动n×sizeof(type)字节 | 避免越界访问 |
| 数组 | 数组名≈首元素指针(除sizeof等场景) | 用arr+i替代&arr[i]提升效率 |
| 字符串 | 以\0结尾,字符指针操作字符串 | 检查空指针,避免缓冲区溢出 |
| 函数 | 函数指针实现回调、策略模式 | 用typedef简化复杂声明 |
| 内存 | malloc/calloc/realloc + free | 分配后检查NULL,释放后置NULL |
| 多级 | 二级指针修改指针本身 | 理解*层级关系 |
| 安全 | 野指针、内存泄漏、悬空指针 | 遵循"分配-使用-释放-置NULL"流程 |
重要提醒:
- C++中优先使用智能指针(unique_ptr/shared_ptr)替代原始指针
- 现代C开发中,对复杂场景考虑使用迭代器、容器等更安全的抽象
- 指针是强大工具,但"能力越大,责任越大"——严谨的编码习惯是安全基石
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int sum_array1(int *arr, int size) {
//int size = sizeof(arr) / sizeof(int);
int sum = 0;
for (int i = 0; i < size; i++) {
sum += *(arr + i);
}
return sum;
}
int sum_array2(int *arr, int size) {
//int size = sizeof(arr) / sizeof(arr[0]);
int *end = arr + size;
int sum = 0;
while (arr < end) {
sum += *arr++;
}
return sum;
}
int string_length(char *str) {
int length = 0;
//遍历直到空字符
while (*str != '\0') {
length++;
//指针自增
str++;
}
return length;
}
void reverse_string(char *str) {
char *start = str;
char *end = str;
while (*end) {
end++;
}
end--;
while (start < end) {
char temp = *start;
*start = *end;
*end = temp;
start++;
end--;
}
}
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
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 divide(int a, int b) {
return a / b;
}
//计算器核心:接收函数指针作为参数
int calculator(int (*op)(int, int), int x, int y) {
return op(x, y);
}
//回调函数类型定义
typedef void (*EventCallback)(int);
void handle_event(int event_id, EventCallback callback) {
printf("事件触发id:%d\n", event_id);
if (callback != NULL) {
//执行回调
callback(event_id);
}
}
void on_login(int id) {
printf("->用户登录事件处理:ID:%d\n", id);
}
void on_logout(int id) {
printf("->用户登出事件处理:ID:%d\n", id);
}
void malloc_demo() {
int n = 10;
//动态分配整形数组
int *arr = (int *) malloc(n * sizeof(int));
if (arr == NULL) {
fprintf(stderr, "内存分配失败!\n");
exit(1);
}
//初始化数组
for (int i = 0; i < n; i++) {
arr[i] = (i + 1) * 10;
}
//使用数组
printf("动态数组元素:");
for (int i = 0; i < n; i++) {
printf("%d ", *(arr + i));
}
printf("\n");
//释放内存!!关键
free(arr);
arr = NULL;
printf("方法执行完成\n");
}
//通过二级指针动态分配内存(改变外部指针)
void create_array(int **ptr, int size) {
*ptr = (int *) malloc(size * sizeof(int));
for (int i = 0; i < size; i++) {
(*ptr)[i] = (i + 1) * 10;
}
}
//指针核心应用
typedef struct {
char name[50];
int score;
} Student;
//按分数排序(函数指针实现灵活排序)
void sort_student(Student *student, int n, int (*compare)(Student *, Student *)) {
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - 1 - i; j++) {
if (compare(&student[j], &student[j + 1]) > 0) {
Student temp = student[j];
student[j] = student[j + 1];
student[j + 1] = temp;
}
}
}
}
int compare_by_score(Student *a, Student *b) {
return a->score - b->score; //升序
}
int compare_by_name(Student *a, Student *b) {
return strcmp(a->name, b->name);
}
int main() {
int var = 42;
int *ptr = &var;
printf("变量var的值:%d\n", var);
printf("变量var的地址:%p\n", (void *) &var);
printf("指针ptr存储的地址:%p\n", (void *) ptr);
printf("通过指针访问的值:%d\n", *ptr);
*ptr = 100;
printf("修改后var的值:%d\n", var);
printf("int指针大小:%zu\n", sizeof(int *));
printf("char指针大小:%zu\n", sizeof(char *));
printf("double指针大小:%zu\n", sizeof(double *));
int a = 100;
double b = 3.14;
char c = 'A';
int *pa = &a;
double *pb = &b;
char *pc = &c;
printf("int指针+1:%p -> %p (差%llu个字节)\n", pa, pa + 1, ((pa + 1) - pa) * sizeof(int));
printf("double指针+1:%p -> %p (差%llu个字节)\n", pb, pb + 1, ((pb + 1) - pb) * sizeof(double));
printf("char指针+1:%p -> %p (差%llu个字节)\n", pc, pc + 1, ((pc + 1) - pc) * sizeof(char));
int arr[5] = {10, 20, 30, 40, 50};
int *p_arr1 = &arr[1];
int *p_arr2 = &arr[4];
printf("p_arr2 - p_arr1:%lld\n", p_arr2 - p_arr1);
printf("实际相差字节:%lld\n", (char *) p_arr2 - (char *) p_arr1);
//
printf("--------------------------------------\n");
int arrSum[] = {1, 2, 3, 4, 5};
int arrSum_size = sizeof(arrSum) / sizeof(arrSum[0]);
printf("数组和1:%d\n", sum_array1(arrSum, arrSum_size));
printf("数组和2:%d\n", sum_array2(arrSum, arrSum_size));
int arr_1[3][5] = {
{1, 2, 3, 4, 5},
{6, 7, 8, 9, 10},
{11, 12, 13, 14, 15}
};
int (*p)[5] = arr_1;
printf("第二行第二列元素:%d\n", *(*(p + 1) + 1));
printf("等价写法:%d\n", arr_1[1][1]);
//遍历整个数组
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 5; j++) {
printf("%3d", *(*(p + i) + j));
}
printf("\n");
}
//字符串长度计算
printf("--------------------------------------\n");
char str_1[] = "Hello,C!";
printf("字符串:%s\n", str_1);
printf("长度不包含'\\0':%d\n", string_length(str_1));
printf("标准库验证:%llu\n", strlen(str_1));
//字符串反转(原地操作)
char str_2[] = "Hello,C!";
printf("原字符串:%s\n", str_2);
reverse_string(str_2);
printf("反转后:%s\n", str_2);
//按引用传递
int x_1 = 5, y_1 = 10;
printf("交换前x_1=%d,y_1=%d\n", x_1, y_1);
swap(&x_1, &y_1);
printf("交换后x_1=%d,y_1=%d\n", x_1, y_1);
//函数指针基础
//声明函数指针
int (*func_ptr)(int, int);
func_ptr = add;
printf("3+5=%d\n", func_ptr(3, 5));
func_ptr = subtract;
printf("3-5=%d\n", func_ptr(3, 5));
printf("--------------------------------------\n");
//函数指针实现计算器
int a_1 = 20, b_1 = 5;
printf("%d+%d=%d\n", a_1, b_1, calculator(add, a_1, b_1));
printf("%d-%d=%d\n", a_1, b_1, calculator(subtract, a_1, b_1));
printf("%d*%d=%d\n", a_1, b_1, calculator(multiply, a_1, b_1));
printf("%d/%d=%d\n", a_1, b_1, calculator(divide, a_1, b_1));
printf("--------------------------------------\n");
//事件处理系统
handle_event(1001, on_login);
handle_event(1002, on_logout);
handle_event(1003, nullptr); //无回调
//安全的内存分配与释放
malloc_demo();
//二级指针:修改指针本身
int *dd_date = NULL;
create_array(&dd_date, 10);
//动态创建的数组
printf("动态创建的数组:");
for (int i = 0; i < 10; i++) {
printf("%d ", *(dd_date + i));
}
printf("\n");
free(dd_date);
//指针数组:存储多个字符串
//指针数组:每个元素是指向字符串的指针
char *days[] = {
"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"
};
printf("一周七天:\n");
for (int i = 0; i < 7; i++) {
printf("%d,%s\n", i + 1, *(days + i));
}
printf("--------------------------------------\n");
Student *class = (Student *) malloc(3 * sizeof(Student));
//初始化数据(指针操作)
strcpy((class + 0)->name, "张三");
(class + 0)->score = 85;
strcpy((class + 1)->name, "李明");
(class + 1)->score = 92;
strcpy((class + 2)->name, "赵六");
(class + 2)->score = 78;
//按分数排序
printf("按分数排序:\n");
printf("排序前:\n");
for (int i = 0; i < 3; i++) {
printf("%s,%d\n", (class + i)->name, (class + i)->score);
}
sort_student(class, 3, compare_by_score);
printf("排序后:\n");
for (int i = 0; i < 3; i++) {
printf("%s,%d\n", (class + i)->name, (class + i)->score);
}
printf("按照名字排序:");
sort_student(class, 3, compare_by_name);
for (int i = 0; i < 3; i++) {
printf("%s,%d\n", (class + i)->name, (class + i)->score);
}
free(class);
return 0;
}
掌握指针,就掌握了C语言的精髓。通过本文系统学习+大量实践,你将能自信地应对各类指针场景,编写高效、安全的C程序!
- 感谢你赐予我前进的力量
赞赏者名单
因为你们的支持让我意识到写文章的价值🙏
本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 软件从业者Hort
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果

