指针
一、指针基础概念
1.1 什么是指针?
指针是一种特殊变量,用于存储内存地址。
1.2 为什么需要专门的指针类型?
虽然地址本质上是整数,但使用特定类型可以:
- 明确指向的数据类型
- 支持指针算术运算
- 帮助编译器进行类型检查
1.3 基本指针类型
char* // char 类型变量的地址
short* // short 类型变量的地址
int* // int 类型变量的地址
float* // float 类型变量的地址
double* // double 类型变量的地址
unsigned char* // unsigned char 类型变量的地址
XXX* // 任意 XXX 类型变量的地址
二、指针的基本操作
2.1 指针的定义与使用
#include <stdio.h>
int main() {
int *p; // 定义指针变量 p
int i = 3; // 定义整型变量 i
p = &i; // p 保存 i 的地址(p 指向 i)
int j;
j = *p; // 通过 *p 访问 i 的值
printf("i = %d, j = %d, *p = %d\n", i, j, *p);
return 0;
}
2.2 理解指针语法
-
int *p;- p 是指针变量名 -
int*- 数据类型,表示 p 存放 int 变量的地址 -
&i- 取地址运算符,获取 i 的内存地址 -
*p- 解引用运算符,访问 p 指向的内存
关键理解:
*p和i访问的是同一块内存,可以互相替换
2.3 指针声明风格
以下三种写法完全等价:
int* p; // 风格1:强调 p 是 int* 类型
int * p; // 风格2:空格分隔
int *p; // 风格3:强调 *p 是 int 类型
三、指针的重要规则
3.1 类型安全规则
int a = 10;
int* pa = &a;
// ❌ 错误:不同类型指针不能互相赋值
double* pd = pa; // 错误!int* → double*
char c = 'A';
float* pf = &c; // 错误!char* → float*
原因:不同类型的数据在内存中的表示方式和大小不同。
3.2 星号操作:通过地址访问内存
int a = 0x123;
int *p = &a; // p 指向 a 的内存
*p += 2; // 修改 p 指向的内存:a = 0x125
int b = *p; // 读取 p 指向的值:b = 0x125
int c = *p + 2; // 使用指针值运算:c = 0x127
四、指针作为函数参数
4.1 基本用法
#include <stdio.h>
void modify_value(int *p) {
*p = 10; // 修改指针指向的值
}
int main() {
int a = 0;
modify_value(&a);
printf("a = %d\n", a); // 输出:a = 10
return 0;
}
4.2 实际应用:交换两个变量
#include <stdio.h>
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = 10, y = 20;
printf("交换前: x=%d, y=%d\n", x, y);
swap(&x, &y);
printf("交换后: x=%d, y=%d\n", x, y);
return 0;
}
4.3 指针参数的优点
- ✅ 读取上一层函数的变量值
- ✅ 修改上一层函数的变量值
- ✅ 实现函数返回多个值
- ✅ 高效传递大型数据结构
五、指针与数组
5.1 数组名即指针
#include <stdio.h>
// 两种完全等价的函数声明
int sum_array(int *arr, int length) {
// int sum_array(int arr[], int length) { // 等价写法
int sum = 0;
for(int i = 0; i < length; i++) {
sum += arr[i]; // arr[i] 等价于 *(arr + i)
}
return sum;
}
int main() {
int numbers[] = {1, 2, 3, 4, 5};
// 不同的调用方式
int total1 = sum_array(numbers, 5); // 整个数组
int total2 = sum_array(numbers, 3); // 前3个元素
int total3 = sum_array(numbers + 1, 3); // 从第2个元素开始
printf("结果: %d, %d, %d\n", total1, total2, total3);
return 0;
}
重要原则:传递数组时,必须同时传递长度信息!
六、const 指针
6.1 const 指针的基本概念
// 1. 普通指针:可读可写
int a = 10;
int* p = &a;
*p = 20; // ✅ 可以修改
int b = *p; // ✅ 可以读取
// 2. const 指针:只读
int a = 10;
const int* p = &a;
// *p = 20; // ❌ 错误:不能修改
int b = *p; // ✅ 可以读取
6.2 const 指针的实际应用
#include <stdio.h>
// 使用 const 保护输入数据不被修改
int calculate_sum(const int* arr, int length) {
int sum = 0;
for(int i = 0; i < length; i++) {
sum += arr[i]; // ✅ 可以读取
// arr[i] = 0; // ❌ 错误:不能修改
}
return sum;
}
void process_data(const int* input, int* output, int length) {
for(int i = 0; i < length; i++) {
output[i] = input[i] * 2; // ✅ 读取input,写入output
}
}
int main() {
int data[] = {1, 2, 3, 4, 5};
int result[5];
int sum = calculate_sum(data, 5);
process_data(data, result, 5);
printf("总和: %d\n", sum);
return 0;
}
6.3 const 使用场景总结
- 函数参数:表明该参数是输入参数,函数内不会修改
- 保护数据:防止意外修改重要数据
- 提高代码可读性:明确标识数据的只读属性
注意:const 只限制通过指针修改内存,不影响指针本身的算术运算。
七、指针的安全使用
7.1 空指针(Null Pointer)
int *p = NULL; // 或 int *p = 0;
// ❌ 危险:解引用空指针
// printf("%d\n", *p);
// ✅ 安全做法:使用前检查
if (p != NULL) {
printf("%d\n", *p);
}
7.2 空指针的实际应用
#include <stdio.h>
void find_values(const int* arr, int length,
int* pmin, int* pmax) {
if (length == 0) return;
int min = arr[0], max = arr[0];
for (int i = 1; i < length; i++) {
if (arr[i] < min) min = arr[i];
if (arr[i] > max) max = arr[i];
}
// 安全赋值:只在指针非空时写入
if (pmin) *pmin = min;
if (pmax) *pmax = max;
}
int main() {
int data[] = {5, 2, 8, 1, 9};
int min_val, max_val;
// 获取最小值和最大值
find_values(data, 5, &min_val, &max_val);
printf("最小值: %d, 最大值: %d\n", min_val, max_val);
// 只获取最小值
find_values(data, 5, &min_val, NULL);
printf("最小值: %d\n", min_val);
return 0;
}
7.3 野指针(Wild Pointer)
int *p; // ❌ 危险:未初始化的指针(野指针)
// printf("%d", *p); // 指向随机内存,可能导致崩溃
// ✅ 好习惯:始终初始化指针
int *safe_ptr = NULL;
7.4 数组越界
int arr[4] = {1, 2, 3, 4};
int *p = arr;
// p += 4; // ❌ 越界:指向数组后的位置
// *p = 12; // 修改未知内存,极其危险!
// ✅ 安全做法:检查边界
if (p >= arr && p < arr + 4) {
*p = 12;
}
7.5 悬挂指针(Dangling Pointer)
#include <stdio.h>
int main() {
int *p = NULL;
{ // 代码块开始
int a = 10; // a 的生命期开始
p = &a; // p 指向 a
} // 代码块结束,a 的生命期结束
// *p = 11; // ❌ 危险:p 指向已失效的内存
return 0;
}
八、安全使用指针的黄金法则
8.1 初始化规则
int *p = NULL; // ✅ 初始化为空
int *q = &valid_var; // ✅ 初始化为有效地址
8.2 使用前检查
void safe_function(int *ptr) {
if (ptr == NULL) {
printf("错误:空指针\n");
return;
}
// 安全使用指针
*ptr = 100;
}
8.3 生命周期管理
// ✅ 安全:指向全局变量
int global_var;
int *safe_ptr = &global_var;
// ✅ 安全:指向动态分配内存
int *dynamic_ptr = malloc(sizeof(int));
// ❌ 危险:指向即将失效的局部变量
int* dangerous_function() {
int local = 10;
return &local; // 错误!
}
九、总结
9.1 指针的核心价值
- 多返回值:通过指针参数返回多个值
- 高效传参:传递地址比传递大对象更高效
- 动态内存:实现动态数据结构和内存管理
- 硬件访问:直接操作内存映射的硬件寄存器
9.2 安全指针使用检查清单
- ✅ 指针是否已初始化?
- ✅ 指针是否指向有效内存?
- ✅ 是否检查了空指针?
- ✅ 数组访问是否越界?
- ✅ 指向的内存生命周期是否有效?
- ✅ 是否需要使用 const 保护数据?
掌握这些原则,你就能安全有效地使用C语言指针这个强大工具!