目录
[0.1 指针与指针变量](#0.1 指针与指针变量)
[0.2 指针变量定义](#0.2 指针变量定义)
[0.3 *号和&号的含义](#0.3 *号和&号的含义)
[0.4 &和*的关系](#0.4 &和*的关系)
[1.1 查询和修改数据](#1.1 查询和修改数据)
[1.2 函数参数传递(修改外部变量)](#1.2 函数参数传递(修改外部变量))
[1.3 函数返回多个值](#1.3 函数返回多个值)
[1.4 函数返回状态和结果分离](#1.4 函数返回状态和结果分离)
[1.5 延长变量生命周期(static)](#1.5 延长变量生命周期(static))
[2.1 指针加减整数(偏移)](#2.1 指针加减整数(偏移))
[2.2 指针减法(计算距离)](#2.2 指针减法(计算距离))
[2.3 指针运算规则](#2.3 指针运算规则)
[3.1 数组名与指针的关系](#3.1 数组名与指针的关系)
[3.2 数组名退化的例外情况](#3.2 数组名退化的例外情况)
[3.3 指针遍历数组](#3.3 指针遍历数组)
[3.4 数组指针 vs 指针数组](#3.4 数组指针 vs 指针数组)
[4.1 函数指针](#4.1 函数指针)
[4.2 函数指针数组(回调函数)](#4.2 函数指针数组(回调函数))
[4.3 函数作为参数(回调)](#4.3 函数作为参数(回调))
[5.1 void指针(万能指针)](#5.1 void指针(万能指针))
[5.2 const与指针](#5.2 const与指针)
[5.3 二级指针(多级指针)](#5.3 二级指针(多级指针))
[5.4 三级指针示例](#5.4 三级指针示例)
[6.1 野指针](#6.1 野指针)
[6.2 悬空指针](#6.2 悬空指针)
[6.3 返回局部变量地址](#6.3 返回局部变量地址)
[6.4 指针类型总结](#6.4 指针类型总结)
[7.1 malloc/free](#7.1 malloc/free)
[7.2 calloc(初始化为0)](#7.2 calloc(初始化为0))
[7.3 realloc(重新分配)](#7.3 realloc(重新分配))
[8.1 字符串复制函数](#8.1 字符串复制函数)
[8.2 动态数组实现](#8.2 动态数组实现)
[8.3 链表节点(指针应用)](#8.3 链表节点(指针应用))
指针:存储内存地址的变量,是C语言最强大也最危险的特性
本质:指针变量里存储的是另一个变量的内存地址
核心价值:直接操作内存、提高效率、实现复杂数据结构
0、指针基础概念
0.1 指针与指针变量
cpp
指针 → 内存地址本身
指针变量 → 存储内存地址的变量(通常简称"指针")
关系:指针变量 存储 指针(地址) 指向 目标变量
0.2 指针变量定义
cpp
// 语法格式
数据类型 *指针变量名;
// 示例
int *p; // 指向int的指针
float *pf; // 指向float的指针
char *pc; // 指向char的指针
// 定义并初始化
int a = 10;
int *p = &a; // p存储a的地址
⚠️ 重要:指针的数据类型必须与指向变量的类型保持一致
0.3 *号和&号的含义
| 符号 | 名称 | 使用场景 | 含义 | 示例 |
|---|---|---|---|---|
& |
取地址运算符 | 普通变量前 | 获取变量的内存地址 | &a |
* |
指针声明符 | 定义时 | 声明这是一个指针变量 | int *p; |
* |
解引用运算符 | 使用时 | 通过地址访问目标数据 | *p |
cpp
#include <stdio.h>
int main() {
int a = 10;
int *p = &a; // 定义时的*是指针声明符
printf("a的值: %d\n", a); // 10
printf("a的地址: %p\n", &a); // 0x...
printf("p的值: %p\n", p); // 0x... (与&a相同)
printf("p的地址: %p\n", &p); // 0x... (p变量自己的地址)
printf("*p的值: %d\n", *p); // 10 (解引用,获取a的值)
*p = 20; // 使用时的*是解引用运算符,修改a的值
printf("修改后a的值: %d\n", a); // 20
return 0;
}
0.4 &和*的关系
cpp
& 和 * 互为逆运算:
&*p = p (先解引用再取地址,回到指针本身)
*&a = a (先取地址再解引用,回到原值)
形象理解:
& → 问"你家住哪儿?" (得到门牌号/地址)
* → 按门牌号"找人" (得到房子里的人/数据)
1、指针的作用
1.1 查询和修改数据
cpp
#include <stdio.h>
int main() {
int a = 10;
int *p = &a;
// 查询数据
int value = *p; // value = 10
// 修改数据
*p = 20; // a 变为 20
printf("a = %d\n", a); // 20
return 0;
}
1.2 函数参数传递(修改外部变量)
cpp
#include <stdio.h>
// 值传递:无法修改原变量
void swapByValue(int a, int b) {
int temp = a;
a = b;
b = temp;
}
// 地址传递:可以修改原变量
void swapByPointer(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = 10, y = 20;
swapByValue(x, y);
printf("值传递后: x=%d, y=%d\n", x, y); // 10, 20 (未变)
swapByPointer(&x, &y);
printf("地址传递后: x=%d, y=%d\n", x, y); // 20, 10 (已变)
return 0;
}
1.3 函数返回多个值
cpp
#include <stdio.h>
// 通过指针参数返回多个结果
void getMaxAndMin(int arr[], int len, int *max, int *min) {
*max = arr[0];
*min = arr[0];
for (int i = 1; i < len; i++) {
if (arr[i] > *max) {
*max = arr[i];
}
if (arr[i] < *min) {
*min = arr[i];
}
}
}
int main() {
int arr[] = {3, 7, 1, 9, 5, 2, 8, 4, 6, 10};
int len = sizeof(arr) / sizeof(arr[0]);
int max, min;
getMaxAndMin(arr, len, &max, &min);
printf("最大值: %d\n", max); // 10
printf("最小值: %d\n", min); // 1
return 0;
}
1.4 函数返回状态和结果分离
cpp
#include <stdio.h>
// 返回值表示状态,指针参数返回结果
// 返回0表示成功,返回1表示失败
int getRemainder(int num1, int num2, int *result) {
if (num2 == 0) {
return 1; // 错误:除数为0
}
*result = num1 % num2;
return 0; // 成功
}
int main() {
int a = 10, b = 3, res;
int status = getRemainder(a, b, &res);
if (status == 0) {
printf("%d %% %d = %d\n", a, b, res);
} else {
printf("错误:除数不能为0\n");
}
// 测试除数为0的情况
status = getRemainder(10, 0, &res);
if (status == 1) {
printf("捕获到除零错误!\n");
}
return 0;
}
1.5 延长变量生命周期(static)
cpp
#include <stdio.h>
// 普通局部变量:函数结束即销毁
int* getPointerNormal() {
int a = 10;
return &a; // 危险!返回局部变量地址
}
// static变量:程序结束才销毁
int* getPointerStatic() {
static int a = 10;
return &a; // 安全
}
int main() {
// int *p = getPointerNormal(); // 野指针!
int *p = getPointerStatic();
printf("*p = %d\n", *p); // 10
return 0;
}
2、指针的运算
2.1 指针加减整数(偏移)
cpp
#include <stdio.h>
int main() {
int arr[] = {10, 20, 30, 40, 50};
int *p = arr; // p指向arr[0]
printf("p = %p, *p = %d\n", p, *p); // arr[0]
printf("p+1 = %p, *(p+1) = %d\n", p+1, *(p+1)); // arr[1]
printf("p+2 = %p, *(p+2) = %d\n", p+2, *(p+2)); // arr[2]
// 指针步长 = sizeof(基类型)
// int* 步长 = 4字节
// char* 步长 = 1字节
// double* 步长 = 8字节
return 0;
}
2.2 指针减法(计算距离)
cpp
#include <stdio.h>
int main() {
int arr[] = {10, 20, 30, 40, 50};
int *p1 = &arr[1]; // 指向arr[1]
int *p2 = &arr[4]; // 指向arr[4]
// 指针相减 = 元素个数(不是字节数)
int distance = p2 - p1; // 3
printf("距离: %d 个元素\n", distance);
printf("字节距离: %ld 字节\n", (char*)p2 - (char*)p1); // 12字节
return 0;
}
2.3 指针运算规则
| 操作 | 是否合法 | 说明 |
|---|---|---|
| 指针 + 整数 | ✅ | 向后移动N个元素 |
| 指针 - 整数 | ✅ | 向前移动N个元素 |
| 指针 - 指针 | ✅ | 计算元素个数差 |
| 指针 + 指针 | ❌ | 无意义 |
| 指针 × 整数 | ❌ | 无意义 |
| 指针 ÷ 整数 | ❌ | 无意义 |
3、指针与数组
3.1 数组名与指针的关系
cpp
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr; // 数组名退化为首元素地址
// 三种等价写法
printf("%d\n", arr[0]); // 1
printf("%d\n", *arr); // 1
printf("%d\n", *p); // 1
printf("%d\n", p[0]); // 1
// 地址相同但类型不同
printf("arr = %p\n", (void*)arr);
printf("&arr[0] = %p\n", (void*)&arr[0]);
printf("&arr = %p\n", (void*)&arr);
return 0;
}
3.2 数组名退化的例外情况
cpp
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
// 例外1:sizeof不会退化
printf("sizeof(arr) = %zu\n", sizeof(arr)); // 20 (5×4)
printf("sizeof(*arr) = %zu\n", sizeof(*arr)); // 4
// 例外2:&arr不会退化
printf("arr = %p\n", (void*)arr); // 0x...
printf("arr+1 = %p\n", (void*)(arr+1)); // +4字节
printf("&arr = %p\n", (void*)&arr); // 0x... (值相同)
printf("&arr+1 = %p\n", (void*)(&arr+1)); // +20字节 (整个数组)
return 0;
}
3.3 指针遍历数组
cpp
#include <stdio.h>
int main() {
int arr[] = {10, 20, 30, 40, 50};
int len = sizeof(arr) / sizeof(arr[0]);
// 方式1:下标法
printf("下标法: ");
for (int i = 0; i < len; i++) {
printf("%d ", arr[i]);
}
printf("\n");
// 方式2:指针法(不改变p)
printf("指针法1: ");
for (int i = 0; i < len; i++) {
printf("%d ", *(arr + i));
}
printf("\n");
// 方式3:指针移动
printf("指针法2: ");
int *p = arr;
for (int i = 0; i < len; i++) {
printf("%d ", *p++);
}
printf("\n");
return 0;
}
3.4 数组指针 vs 指针数组
cpp
// 数组指针:指向数组的指针
int (*p)[5]; // p是一个指针,指向含5个int的数组
int arr[5];
p = &arr; // 正确
// 指针数组:存放指针的数组
int *arr2[5]; // arr2是一个数组,含5个int指针
int a = 1, b = 2, c = 3, d = 4, e = 5;
arr2[0] = &a;
arr2[1] = &b;
// ...
| 类型 | 声明 | 含义 | 步长 |
|---|---|---|---|
| 数组指针 | int (*p)[5] |
指向数组的指针 | 20字节 |
| 指针数组 | int *p[5] |
存放指针的数组 | 8字节 |
4、指针与函数
4.1 函数指针
cpp
#include <stdio.h>
// 普通函数
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
int main() {
// 定义函数指针
int (*funcPtr)(int, int);
// 指向函数
funcPtr = add;
printf("5 + 3 = %d\n", funcPtr(5, 3)); // 8
funcPtr = subtract;
printf("5 - 3 = %d\n", funcPtr(5, 3)); // 2
return 0;
}
4.2 函数指针数组(回调函数)
cpp
#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) {
if (b != 0) return a / b;
return 0;
}
int main() {
// 函数指针数组
int (*operations[4])(int, int) = {add, subtract, multiply, divide};
const char *names[] = {"加法", "减法", "乘法", "除法"};
int num1, num2, choice;
printf("请输入两个数字: ");
scanf("%d %d", &num1, &num2);
printf("选择运算: 0-加法 1-减法 2-乘法 3-除法\n");
scanf("%d", &choice);
if (choice >= 0 && choice < 4) {
int result = operations[choice](num1, num2);
printf("%s 结果: %d\n", names[choice], result);
} else {
printf("无效选择\n");
}
return 0;
}
4.3 函数作为参数(回调)
cpp
#include <stdio.h>
// 通用排序函数(使用回调)
void bubbleSort(int arr[], int len, int (*compare)(int, int)) {
for (int i = 0; i < len - 1; i++) {
for (int j = 0; j < len - 1 - i; j++) {
if (compare(arr[j], arr[j + 1]) > 0) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
// 升序比较
int compareAsc(int a, int b) {
return a - b;
}
// 降序比较
int compareDesc(int a, int b) {
return b - a;
}
int main() {
int arr[] = {5, 2, 8, 1, 9};
int len = sizeof(arr) / sizeof(arr[0]);
bubbleSort(arr, len, compareAsc);
printf("升序: ");
for (int i = 0; i < len; i++) printf("%d ", arr[i]);
printf("\n");
bubbleSort(arr, len, compareDesc);
printf("降序: ");
for (int i = 0; i < len; i++) printf("%d ", arr[i]);
printf("\n");
return 0;
}
5、特殊指针类型
5.1 void指针(万能指针)
cpp
#include <stdio.h>
#include <string.h>
// 通用交换函数(按字节交换)
void swap(void *p1, void *p2, size_t size) {
char *temp = (char*)malloc(size);
if (temp == NULL) return;
memcpy(temp, p1, size);
memcpy(p1, p2, size);
memcpy(p2, temp, size);
free(temp);
}
int main() {
// 交换int
int a = 10, b = 20;
swap(&a, &b, sizeof(int));
printf("a=%d, b=%d\n", a, b); // 20, 10
// 交换double
double x = 1.5, y = 2.5;
swap(&x, &y, sizeof(double));
printf("x=%.1f, y=%.1f\n", x, y); // 2.5, 1.5
return 0;
}
⚠️ void指针特点:
- 可以接收任意类型指针
- 不能直接解引用(需类型转换)
- 不能进行指针运算(需类型转换)
5.2 const与指针
cpp
#include <stdio.h>
int main() {
int a = 10, b = 20;
// 1. 指针常量:指针本身不能变
int * const p1 = &a;
*p1 = 30; // 可以修改指向的值
// p1 = &b; // 不能修改指针本身
// 2. 常量指针:指向的值不能变
const int *p2 = &a;
// *p2 = 30; // 不能修改指向的值
p2 = &b; // 可以修改指针本身
// 3. 常量指针常量:都不能变
const int * const p3 = &a;
// *p3 = 30; // 不能变
// p3 = &b; // 不能变
return 0;
}
| 类型 | 声明 | 指针可变 | 值可变 |
|---|---|---|---|
| 普通指针 | int *p |
✅ | ✅ |
| 指针常量 | int * const p |
❌ | ✅ |
| 常量指针 | const int *p |
✅ | ❌ |
| 常量指针常量 | const int * const p |
❌ | ❌ |
5.3 二级指针(多级指针)
cpp
#include <stdio.h>
int main() {
int a = 10;
int *p = &a; // 一级指针
int **pp = &p; // 二级指针
printf("a = %d\n", a); // 10
printf("*p = %d\n", *p); // 10
printf("**pp = %d\n", **pp); // 10
printf("&a = %p\n", (void*)&a);
printf("p = %p\n", (void*)p);
printf("*pp = %p\n", (void*)*pp);
// 通过二级指针修改一级指针
int b = 20;
*pp = &b; // 修改p指向b
printf("修改后 *p = %d\n", *p); // 20
return 0;
}
5.4 三级指针示例
cpp
#include <stdio.h>
int main() {
int a = 100;
int *p = &a;
int **pp = &p;
int ***ppp = &pp;
printf("a = %d\n", a); // 100
printf("*p = %d\n", *p); // 100
printf("**pp = %d\n", **pp); // 100
printf("***ppp = %d\n", ***ppp); // 100
return 0;
}
6、危险指针
6.1 野指针
cpp
#include <stdio.h>
int main() {
// 野指针:未初始化的指针
int *p; // p的值是随机的
// *p = 10; // 危险!可能崩溃
// 正确做法:初始化为NULL
int *p2 = NULL;
if (p2 != NULL) {
*p2 = 10;
}
return 0;
}
6.2 悬空指针
cpp
#include <stdio.h>
#include <stdlib.h>
int main() {
int *p = (int*)malloc(sizeof(int));
*p = 10;
free(p); // 释放内存
// p = NULL; // 忘记置NULL
// *p = 20; // 悬空指针!危险!
// 正确做法
int *p2 = (int*)malloc(sizeof(int));
*p2 = 10;
free(p2);
p2 = NULL; // 避免悬空
return 0;
}
6.3 返回局部变量地址
cpp
#include <stdio.h>
// 错误:返回局部变量地址
int* getBadPointer() {
int a = 10;
return &a; // 函数结束后a被销毁
}
// 正确:使用static或动态分配
int* getGoodPointer1() {
static int a = 10;
return &a; // static变量生命周期到程序结束
}
int* getGoodPointer2() {
int *a = (int*)malloc(sizeof(int));
*a = 10;
return a; // 堆内存,调用者负责free
}
int main() {
// int *p = getBadPointer(); // 危险!
int *p1 = getGoodPointer1();
printf("*p1 = %d\n", *p1);
int *p2 = getGoodPointer2();
printf("*p2 = %d\n", *p2);
free(p2);
return 0;
}
6.4 指针类型总结
| 指针类型 | 状态 | 危险程度 | 解决方案 |
|---|---|---|---|
| 野指针 | 未初始化 | ⚠️⚠️⚠️ | 初始化为NULL |
| 悬空指针 | 已释放 | ⚠️⚠️⚠️ | free后置NULL |
| 越界指针 | 超出范围 | ⚠️⚠️⚠️ | 检查边界 |
| 类型不匹配 | 错误转换 | ⚠️⚠️ | 正确类型转换 |
7、指针与动态内存
7.1 malloc/free
cpp
#include <stdio.h>
#include <stdlib.h>
int main() {
// 分配单个int
int *p = (int*)malloc(sizeof(int));
if (p == NULL) {
printf("内存分配失败\n");
return 1;
}
*p = 10;
printf("*p = %d\n", *p);
free(p);
p = NULL;
// 分配数组
int n = 5;
int *arr = (int*)malloc(n * sizeof(int));
if (arr == NULL) {
printf("内存分配失败\n");
return 1;
}
for (int i = 0; i < n; i++) {
arr[i] = i * 10;
}
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
free(arr);
arr = NULL;
return 0;
}
7.2 calloc(初始化为0)
cpp
#include <stdio.h>
#include <stdlib.h>
int main() {
// calloc自动初始化为0
int *arr = (int*)calloc(5, sizeof(int));
for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]); // 0 0 0 0 0
}
printf("\n");
free(arr);
return 0;
}
7.3 realloc(重新分配)
cpp
#include <stdio.h>
#include <stdlib.h>
int main() {
int *arr = (int*)malloc(3 * sizeof(int));
arr[0] = 1; arr[1] = 2; arr[2] = 3;
// 扩容到5个元素
int *temp = (int*)realloc(arr, 5 * sizeof(int));
if (temp == NULL) {
free(arr);
return 1;
}
arr = temp;
arr[3] = 4; arr[4] = 5;
for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]); // 1 2 3 4 5
}
printf("\n");
free(arr);
return 0;
}
8、综合练习
8.1 字符串复制函数
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 手动实现strcpy
char* myStrcpy(char *dest, const char *src) {
char *originalDest = dest;
while ((*dest++ = *src++) != '\0');
return originalDest;
}
// 动态分配字符串
char* createString(const char *src) {
size_t len = strlen(src) + 1;
char *str = (char*)malloc(len);
if (str != NULL) {
myStrcpy(str, src);
}
return str;
}
int main() {
char dest[100];
myStrcpy(dest, "Hello, World!");
printf("%s\n", dest);
char *str = createString("Dynamic String");
if (str != NULL) {
printf("%s\n", str);
free(str);
}
return 0;
}
8.2 动态数组实现
cpp
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int *data;
int size;
int capacity;
} DynamicArray;
// 初始化
void initArray(DynamicArray *arr, int capacity) {
arr->data = (int*)malloc(capacity * sizeof(int));
arr->size = 0;
arr->capacity = capacity;
}
// 添加元素
void pushBack(DynamicArray *arr, int value) {
if (arr->size >= arr->capacity) {
arr->capacity *= 2;
arr->data = (int*)realloc(arr->data, arr->capacity * sizeof(int));
}
arr->data[arr->size++] = value;
}
// 获取元素
int get(DynamicArray *arr, int index) {
if (index >= 0 && index < arr->size) {
return arr->data[index];
}
return -1;
}
// 释放
void freeArray(DynamicArray *arr) {
free(arr->data);
arr->data = NULL;
arr->size = 0;
arr->capacity = 0;
}
int main() {
DynamicArray arr;
initArray(&arr, 5);
for (int i = 0; i < 10; i++) {
pushBack(&arr, i * 10);
}
printf("数组内容: ");
for (int i = 0; i < arr.size; i++) {
printf("%d ", get(&arr, i));
}
printf("\n");
freeArray(&arr);
return 0;
}
8.3 链表节点(指针应用)
cpp
#include <stdio.h>
#include <stdlib.h>
// 链表节点
typedef struct Node {
int data;
struct Node *next;
} Node;
// 创建节点
Node* createNode(int data) {
Node *node = (Node*)malloc(sizeof(Node));
node->data = data;
node->next = NULL;
return node;
}
// 插入头部
void insertHead(Node **head, int data) {
Node *newNode = createNode(data);
newNode->next = *head;
*head = newNode;
}
// 遍历
void printList(Node *head) {
Node *current = head;
while (current != NULL) {
printf("%d -> ", current->data);
current = current->next;
}
printf("NULL\n");
}
// 释放
void freeList(Node *head) {
Node *temp;
while (head != NULL) {
temp = head;
head = head->next;
free(temp);
}
}
int main() {
Node *head = NULL;
insertHead(&head, 3);
insertHead(&head, 2);
insertHead(&head, 1);
printList(head); // 1 -> 2 -> 3 -> NULL
freeList(head);
return 0;
}
9、常见错误与避坑指南
| 错误类型 | 错误示例 | 正确写法 |
|---|---|---|
| 野指针 | int *p; *p=10; |
int *p = NULL; |
| 悬空指针 | free(p); *p=10; |
free(p); p=NULL; |
| 返回局部地址 | return &local; |
使用static或malloc |
| 类型不匹配 | int *p; double *d; p=d; |
使用类型转换 |
| 数组越界 | arr[len] |
arr[len-1] |
| 忘记free | malloc后不释放 |
配对使用free |
| 重复free | free(p); free(p); |
free后置NULL |
| const误用 | const int *p; *p=10; |
去掉const或改值 |
10、指针使用原则
cpp
✅ 应该做的:
1. 定义指针时初始化为NULL
2. 使用前检查是否为NULL
3. free后立即置NULL
4. 指针类型与指向类型一致
5. 数组传参时同时传递长度
6. 返回堆内存时文档说明需要free
❌ 不应该做的:
1. 使用未初始化的指针
2. 访问已释放的内存
3. 返回局部变量地址
4. 指针运算超出数组范围
5. 混用不同类型的指针
6. 忘记释放动态内存