1. 指针的基本分类体系
按指向的数据类型分类:
// 1. 基本类型指针
int *int_ptr;
char *char_ptr;
float *float_ptr;
double *double_ptr;
// 2. 结构体指针
struct student *stu_ptr;
// 3. 联合体指针
union data *union_ptr;
// 4. 枚举指针
enum color *enum_ptr;
// 5. 函数指针
int (*func_ptr)(int, int);
// 6. 空类型指针
void *void_ptr;
2. 按指针的层级分类
单级指针
int a = 10;
int *p = &a; // p是指向int的指针
多级指针
int a = 10;
int *p = &a; // 一级指针
int **pp = &p; // 二级指针(指向指针的指针)
int ***ppp = &pp; // 三级指针
3. 按指针的用途和特性分类
3.1 数组指针 vs 指针数组
数组指针 - 指向整个数组的指针:
int arr[5] = {1, 2, 3, 4, 5};
int (*ptr_to_array)[5] = &arr; // 指向包含5个int的数组
// 访问方式
printf("%d\n", (*ptr_to_array)[2]); // 输出: 3
指针数组 - 元素都是指针的数组:
int a = 1, b = 2, c = 3;
int *ptr_array[3] = {&a, &b, &c}; // 包含3个int指针的数组
// 访问方式
printf("%d\n", *ptr_array[1]); // 输出: 2
3.2 函数指针
// 函数原型
int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }
// 函数指针声明和使用
int (*operation)(int, int); // 声明函数指针
operation = add; // 指向add函数
printf("%d\n", operation(3, 4)); // 输出: 7
operation = multiply; // 指向multiply函数
printf("%d\n", operation(3, 4)); // 输出: 12
3.3 常量指针 vs 指针常量
常量指针 - 指向常量的指针(指针可改,值不可改):
const int *p1; // 指向const int的指针
int const *p2; // 同上,等价写法
int a = 10, b = 20;
const int *p = &a;
// *p = 30; // 错误:不能通过p修改a的值
p = &b; // 正确:可以改变指针的指向
指针常量 - 指针本身是常量(指针不可改,值可改):
int *const p; // 指向int的const指针
int a = 10, b = 20;
int *const p = &a;
*p = 30; // 正确:可以修改a的值
// p = &b; // 错误:不能改变指针的指向
指向常量的指针常量 - 都不能改:
const int *const p; // 指向const int的const指针
int a = 10;
const int *const p = &a;
// *p = 20; // 错误:不能修改值
// p = &b; // 错误:不能改变指向
4. 理解指针的语法规律
4.1 "右左法则" - 解析复杂指针声明的秘诀
规则:从变量名开始: 有括号先看括号; 没括号就 先向右看,再向左看。
示例1:int *p[5]
p → 变量名p
p[5] → 向右:p是包含5个元素的数组
*p[5] → 向左:数组的元素是指针
int *p[5] → 向左:指针指向int类型
结果:p是包含5个int指针的数组(指针数组)
示例2:int (*p)[5]
(*p) → 变量名p,被括号包围,先看括号内:p是指针
(*p)[5] → 向右:指针指向包含5个元素的数组
int (*p)[5] → 向左:数组的元素是int类型
结果:p是指向包含5个int的数组的指针(数组指针)
示例3:int *(*p[5])(int)
p → 变量名p
p[5] → p是包含5个元素的数组
*p[5] → 数组的元素是指针
(*p[5])(int) → 向右:指针指向函数,函数接受int参数
*(*p[5])(int) → 向左:函数返回指针
int *(*p[5])(int) → 向左:指针指向int类型
结果:p是包含5个函数指针的数组,这些函数接受int参数并返回int指针
4.2 星号(*)的位置规律
星号的位置不影响含义,只是风格差异:
int* p; // 风格1:紧贴类型(强调p是int指针类型)
int * p; // 风格2:在中间
int *p; // 风格3:紧贴变量(强调*p是int类型)
5. 特殊指针类型详解
5.1 void指针(通用指针)
void *generic_ptr;
int a = 10;
char c = 'A';
generic_ptr = &a; // 可以指向任何类型
generic_ptr = &c;
// 使用时必须进行类型转换
int *int_ptr = (int*)generic_ptr;
char *char_ptr = (char*)generic_ptr;
5.2 空指针和野指针
int *null_ptr = NULL; // 空指针,明确表示不指向任何地方
int *wild_ptr; // 野指针,未初始化,指向随机地址(危险!)
// 正确做法:总是初始化指针
int *safe_ptr = NULL;
5.3 近指针、远指针、巨指针(历史概念)
// 在16位DOS时代的内存模型
char near *near_ptr; // 近指针(同一段内)
char far *far_ptr; // 远指针(不同段)
char huge *huge_ptr; // 巨指针(可跨段)
// 现代32/64位系统中已不再需要
6. 指针运算的规律
6.1 算术运算
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;
p + 1; // 指向arr[1],实际地址增加 sizeof(int)
p + 2; // 指向arr[2],实际地址增加 2 * sizeof(int)
p++; // p现在指向arr[1]
p--; // p现在指向arr[0]
6.2 关系运算
int *p1 = &arr[0];
int *p2 = &arr[2];
if (p1 < p2) // true,比较内存地址
if (p1 == p2) // false
7. 实际应用模式
7.1 动态内存分配
int *dynamic_array = (int*)malloc(10 * sizeof(int));
if (dynamic_array != NULL) {
for (int i = 0; i < 10; i++) {
dynamic_array[i] = i * 10;
}
free(dynamic_array); // 必须释放
}
7.2 字符串处理
char str[] = "Hello";
char *p = str;
while (*p != '\0') {
printf("%c", *p);
p++;
}
7.3 结构体访问
typedef struct {
int id;
char name[20];
float score;
} Student;
Student s = {1, "Alice", 95.5};
Student *ptr = &s;
// 两种访问方式
(*ptr).id = 2; // 传统方式
ptr->id = 3; // 箭头语法(推荐)
8. 记忆口诀和技巧
8.1 指针声明口诀
"有括号先处理括号, 先右后左"
8.2 常量指针记忆法
const int *p; // "const在左,值不变"(指向常量的指针)
int *const p; // "const在右,针不变"(指针常量)
const int *const p; // "两边const,都不变"
8.3 数组vs指针记忆
int *p[5]; // "p[5]先出现,是指针数组"
int (*p)[5]; // "(*p)先出现,是数组指针"
9. 综合练习
让我们解析一个复杂声明:
char *(*(*func_array[5])(int))[10];
使用右左法则:
func_array → 变量名
func_array[5] → 5个元素的数组
*func_array[5] → 数组元素是指针
(*func_array[5])(int) → 指针指向函数,函数接受int参数
*(*func_array[5])(int) → 函数返回指针
(*(*func_array[5])(int))[10] → 指针指向10个元素的数组
char *(*(*func_array[5])(int))[10] → 数组元素是char指针
结果:func_array是包含5个函数指针的数组,这些函数接受int参数, 返回指向包含10个char指针的数组的指针
11万+

被折叠的 条评论
为什么被折叠?



