# 7 指针基础 ## 7.1 完整概念讲解 ### 内存地址概念 内存是一个线性的字节数组,每个字节都有唯一的地址。程序运行时,变量存储在内存中,编译器为每个变量分配内存空间。指针就是存储内存地址的变量,通过指针可以直接访问和操作内存。 ### 指针变量定义与初始化 ```c int *p; // 声明一个指向int类型变量的指针 int a = 10; p = &a; // 初始化:将a的地址赋值给p int *q = &a; // 声明并初始化 ``` ### 解引用(*)和取地址(&) - `&` 取地址运算符:获取变量的内存地址 - `*` 解引用运算符:通过地址访问对应的内存值 ### 指针大小 - 32位系统:指针大小为4字节(可寻址2^32 = 4GB内存) - 64位系统:指针大小为8字节(可寻址2^64内存) - 指针大小与指向的数据类型无关,只与系统位数有关 ### NULL指针 - NULL是宏定义,通常为 `(void*)0` - 表示指针不指向任何有效内存 - 使用前应检查指针是否为NULL ### void指针 - `void*` 是通用指针类型,可以指向任意类型的数据 - 不能直接解引用,必须先转换为具体类型指针 - 不能进行指针算术运算(C标准规定) ### 指针赋值规则 - 指针类型必须匹配(或使用void*中转) - 可以将一个指针赋值给另一个指针 - 可以将NULL或0赋值给指针 - 不同类型的指针赋值需要显式类型转换 ## 7.2 核心API/语法 ### 指针相关运算符 | 运算符 | 名称 | 作用 | 示例 | | ------ | ------ | ---------------- | ----------- | | `&` | 取地址 | 获取变量地址 | `&a` | | `*` | 解引用 | 通过地址访问值 | `*p` | | `->` | 箭头 | 通过指针访问成员 | `p->member` | ### 指针声明语法 ```c int *p; // 指向int的指针 int **pp; // 指向int指针的指针(二级指针) int *arr[10]; // 指针数组:包含10个int指针的数组 int (*p)[10]; // 数组指针:指向包含10个int的数组的指针 ``` ### 内存操作函数 ```c #include void *malloc(size_t size); // 分配内存 void *calloc(size_t n, size_t size); // 分配并清零 void *realloc(void *ptr, size_t new_size); // 重新分配 void free(void *ptr); // 释放内存 ``` ## 7.3 代码示例(完整可编译,附gcc命令) ### 示例1:基本指针操作 ```c #include int main() { int a = 42; int *p = &a; printf("变量a的值: %d\n", a); printf("变量a的地址: %p\n", (void*)&a); printf("指针p存储的地址: %p\n", (void*)p); printf("通过指针解引用访问的值: %d\n", *p); // 通过指针修改变量值 *p = 100; printf("修改后a的值: %d\n", a); // 指针大小 printf("指针大小: %zu字节\n", sizeof(p)); printf("int大小: %zu字节\n", sizeof(int)); return 0; } ``` 编译命令:`gcc -o pointer_basics pointer_basics.c` ### 示例2:NULL指针和void指针 ```c #include #include int main() { // NULL指针 int *p = NULL; if (p != NULL) { printf("p指向的值: %d\n", *p); } else { printf("p是空指针,不能解引用\n"); } // void指针 int a = 10; double b = 3.14; void *vp; vp = &a; printf("通过void*访问int: %d\n", *(int*)vp); vp = &b; printf("通过void*访问double: %f\n", *(double*)vp); // 动态内存分配中的void* int *arr = (int*)malloc(5 * sizeof(int)); if (arr == NULL) { fprintf(stderr, "内存分配失败\n"); return 1; } for (int i = 0; i < 5; i++) { arr[i] = i * 10; } for (int i = 0; i < 5; i++) { printf("arr[%d] = %d\n", i, arr[i]); } free(arr); return 0; } ``` 编译命令:`gcc -o pointer_types pointer_types.c` ### 示例3:指针赋值和类型匹配 ```c #include int main() { int a = 100; int *p = &a; // 类型匹配赋值 int *q = p; // 正确:相同类型指针 printf("q = %d\n", *q); // void指针中转 void *vp = p; // 正确:任何类型指针都可以赋给void* int *r = (int*)vp; // 正确:void*可以赋给任何类型指针 printf("r = %d\n", *r); // 错误的类型赋值(需要强制转换) // int *s = (double*)&a; // 警告:类型不匹配 // 指针与0 int *t = 0; // 正确:0可以赋给指针,表示NULL // int *u = 100; // 错误:非零整数不能赋给指针 printf("t = %p\n", (void*)t); return 0; } ``` 编译命令:`gcc -o pointer_assignment pointer_assignment.c` ## 7.4 注意事项与易错点 ### 野指针 - 未初始化的指针,指向随机内存地址 - 危害:可能导致程序崩溃或数据损坏 - 预防:指针声明后立即初始化 ### 指针类型不匹配 - 不同类型指针直接赋值可能导致未定义行为 - 强制类型转换可以绕过编译器检查,但可能引发运行时错误 ### NULL指针解引用 - 解引用NULL指针会导致段错误(Segmentation Fault) - 使用指针前应检查是否为NULL ### 指针与数组混淆 - 数组名在大多数情况下退化为指针,但有例外(sizeof和&操作符) - 指针和数组不能完全互换 ### 内存管理 - malloc/free必须配对使用 - 重复释放(double free)会导致未定义行为 - 释放后继续使用指针(悬空指针)是危险的 ### 指针算术 - 指针加减运算以指向类型的大小为单位 - 不同数组间的指针算术是未定义行为 ## 7.5 面试要点(3-5个Q&A) **Q1: 指针和数组有什么区别?** A: 数组是连续内存块,指针是存储地址的变量。数组名在大多数情况下退化为首元素指针,但sizeof和&操作符对数组有特殊行为。数组大小固定,指针可以指向动态分配的内存。 **Q2: 什么是野指针?如何避免?** A: 野指针是未初始化或指向已释放内存的指针。避免方法:1) 指针声明时初始化为NULL或有效地址;2) 释放内存后将指针设为NULL;3) 使用前检查指针有效性。 **Q3: NULL指针和void指针有什么区别?** A: NULL指针是特殊的空指针,表示不指向任何内存;void*是通用指针,可以指向任意类型数据,但不能直接解引用。NULL指针主要用于错误检查,void*主要用于泛型编程。 **Q4: 为什么指针大小与数据类型无关?** A: 指针存储的是内存地址,地址的长度由系统位数决定(32位系统4字节,64位系统8字节)。不同数据类型的指针只是指向的内存大小不同,但存储地址的指针本身大小相同。 **Q5: 指针赋值时需要注意什么?** A: 1) 类型匹配:相同类型指针可以直接赋值;2) void*可以与其他类型指针相互赋值;3) 不同类型指针赋值需要强制转换;4) 只有0或NULL可以赋给指针表示空指针;5) 不能将非地址整数值赋给指针。 ## 7.6 完整示例程序 ```c #include #include int main() { // 1. 基本指针操作 int a = 10; int *p = &a; printf("a = %d, *p = %d\n", a, *p); // 2. 通过指针修改值 *p = 20; printf("修改后: a = %d\n", a); // 3. 指针大小 printf("指针大小: %zu字节\n", sizeof(p)); // 4. NULL指针 int *np = NULL; if (np == NULL) { printf("np是空指针\n"); } // 5. void指针 void *vp = &a; printf("通过void*访问: %d\n", *(int*)vp); // 6. 动态内存分配 int *arr = (int*)malloc(3 * sizeof(int)); if (arr != NULL) { arr[0] = 100; arr[1] = 200; arr[2] = 300; for (int i = 0; i < 3; i++) { printf("arr[%d] = %d\n", i, arr[i]); } free(arr); } return 0; } ``` 编译命令:`gcc -o pointer_demo pointer_demo.c`