C/C++ 指针完全指南
3.2、获取对象地址
指针用于存放某个对象的地址,要想获取该对象的地址,需要使用取地址运算符 &:
int a = 10;
int* p = &a; // &a 获取变量a的地址,赋值给指针p
关键要点:
| 表达式 | 含义 | 类型 |
|---|---|---|
a | 变量的值 | int |
&a | 变量的地址 | int* |
p | 指针存储的地址 | int* |
&p | 指针变量本身的地址 | int** |
int a = 10;
int* p = &a;
printf("a的值: %d\n", a); // 10
printf("a的地址: %p\n", &a); // 0x28FF40
printf("p的值: %p\n", p); // 0x28FF40 (与&a相同)
printf("p的地址: %p\n", &p); // 0x28FF30 (指针变量自己的地址)
⚠️ 注意:不能对常量、表达式取地址
int* p1 = &10; // ❌ 错误!常量没有固定地址 int* p2 = &(a + 1); // ❌ 错误!表达式结果是临时值
3.3、解引用指针
获取指针所指向地址中存储的值,称为解引用(dereference),使用 * 运算符:
int a = 10;
int* p = &a;
printf("%d\n", *p); // 10,访问p指向的内存中的值
*p = 20; // 修改p指向的内存,a变为20
printf("%d\n", a); // 20
内存可视化:
内存地址: 0x28FF30 0x28FF40
+----------+ +----------+
变量: | p | | a |
值: | 0x28FF40 | | 10 |
+----------+ +----------+
↓
*p = 10
双重解引用:
int a = 10;
int* p1 = &a;
int** p2 = &p1;
printf("%d\n", *p1); // 10
printf("%d\n", **p2); // 10,双重解引用
4、指针运算
指针可以进行算术运算,但与普通整数运算不同,指针运算考虑了数据类型的大小。
4.1、指针加减整数
int arr[5] = {10, 20, 30, 40, 50};
int* p = arr; // p指向arr[0]
p++; // p移动 sizeof(int) 字节,指向arr[1]
p = p + 2; // p再移动 2×sizeof(int) 字节,指向arr[3]
printf("%d\n", *p); // 40
不同数据类型的指针步长:
| 指针类型 | 32位系统步长 | 64位系统步长 |
|---|---|---|
char* | 1 字节 | 1 字节 |
short* | 2 字节 | 2 字节 |
int* | 4 字节 | 4 字节 |
double* | 8 字节 | 8 字节 |
void* | 1 字节 | 1 字节 |
char c[5] = "abcde";
double d[5] = {1.1, 2.2, 3.3, 4.4, 5.5};
char* pc = c;
double* pd = d;
pc++; // 地址 +1
pd++; // 地址 +8
4.2、指针相减
两个指针相减,得到的是它们之间相差的元素个数,不是字节数:
int arr[10] = {0};
int* p1 = &arr[2];
int* p2 = &arr[7];
ptrdiff_t diff = p2 - p1; // 5,不是 20 字节!
printf("%td\n", diff); // 5
⚠️ 注意:只有指向同一数组的指针才能相减,否则行为未定义!
4.3、指针比较
int arr[5] = {1, 2, 3, 4, 5};
int* p1 = &arr[1];
int* p2 = &arr[3];
if (p1 < p2) {
printf("p1在p2前面\n");
}
if (p1 == p2) {
printf("p1和p2指向同一位置\n");
}
5、指针与数组
指针和数组是C/C++中最紧密相关的概念,理解它们的关系至关重要。
5.1、数组名即指针
int arr[5] = {1, 2, 3, 4, 5};
printf("%p\n", arr); // 数组首地址
printf("%p\n", &arr[0]); // 同上
printf("%d\n", *arr); // 1,解引用数组名
5.2、数组访问的两种写法
int arr[5] = {10, 20, 30, 40, 50};
// 方式1:数组下标
printf("%d\n", arr[2]); // 30
// 方式2:指针运算
printf("%d\n", *(arr + 2)); // 30,完全等价!
// 甚至可以这样写(但不推荐)
printf("%d\n", 2[arr]); // 30,arr[2] 等价于 *(arr+2) 等价于 *(2+arr)
5.3、指针遍历数组
int arr[5] = {1, 2, 3, 4, 5};
// 方式1:下标遍历
for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]);
}
// 方式2:指针遍历
for (int* p = arr; p < arr + 5; p++) {
printf("%d ", *p);
}
// 方式3:C++11范围for(底层仍是指针)
for (int x : arr) {
printf("%d ", x);
}
5.4、指针与数组的关键区别
| 特性 | 数组 | 指针 |
|---|---|---|
sizeof | 返回整个数组大小 | 返回指针大小(4/8字节) |
| 可重新赋值 | ❌ 不能 | ✅ 可以 |
| 内存位置 | 栈/静态区 | 栈(存储地址) |
| 可自增 | ❌ 不能 | ✅ 可以 |
int arr[5] = {1, 2, 3, 4, 5};
int* p = arr;
sizeof(arr); // 20 (5×4)
sizeof(p); // 8 (64位指针)
p++; // ✓ 合法
arr++; // ✗ 非法!数组名不是左值
6、指针与函数
6.1、指针作为函数参数(传址调用)
// 传值调用 - 无法修改原变量
void swap_by_value(int a, int b) {
int temp = a;
a = b;
b = temp;
}
// 传址调用 - 可以修改原变量
void swap_by_pointer(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = 10, y = 20;
swap_by_value(x, y); // x,y不变
swap_by_pointer(&x, &y); // x=20, y=10
return 0;
}
6.2、返回指针的函数
int* find_max(int* arr, int size) {
int* max_ptr = arr;
for (int i = 1; i < size; i++) {
if (arr[i] > *max_ptr) {
max_ptr = &arr[i];
}
}
return max_ptr;
}
// ⚠️ 危险!不要返回局部变量的地址
int* dangerous() {
int local = 100;
return &local; // ❌ 函数结束后local被销毁,指针悬空!
}
// ✅ 正确做法
int* safe() {
static int value = 100; // 静态变量,生命周期贯穿整个程序
return &value;
}
6.3、函数指针
函数指针存储函数的入口地址,可用于回调函数、函数表等:
// 定义函数
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
// 定义函数指针
int (*func_ptr)(int, int);
// 指向函数
func_ptr = add;
printf("%d\n", func_ptr(3, 4)); // 7
// 函数指针数组(跳转表)
int (*operations[3])(int, int) = {add, sub, mul};
printf("%d\n", operations[0](5, 3)); // 8
printf("%d\n", operations[1](5, 3)); // 2
printf("%d\n", operations[2](5, 3)); // 15
6.4、回调函数示例
// 回调函数类型
typedef void (*Callback)(int);
// 接收回调的函数
void process(int data, Callback cb) {
int result = data * 2;
cb(result); // 调用回调
}
// 回调函数实现
void print_result(int value) {
printf("结果: %d\n", value);
}
int main() {
process(10, print_result); // 输出: 结果: 20
return 0;
}
7、特殊指针类型
7.1、void 指针(万能指针)
void* 可以指向任何类型的数据,但不能直接解引用:
int a = 10;
double b = 3.14;
char c = 'A';
void* p;
p = &a; // ✓
p = &b; // ✓
p = &c; // ✓
// ❌ 错误!不知道指向什么类型,无法解引用
// printf("%d\n", *p);
// ✅ 正确:先类型转换
printf("%d\n", *(int*)p);
7.2、const 与指针
const int* p1; // 指向常量的指针(指针可变,值不可变)
int* const p2; // 常量指针(指针不可变,值可变)
const int* const p3; // 指向常量的常量指针(都不可变)
int a = 10, b = 20;
// p1: 指针可变,值不可变
p1 = &a; // ✓
*p1 = 30; // ❌ 不能修改指向的值
p1 = &b; // ✓ 可以改变指向
// p2: 指针不可变,值可变
p2 = &a; // ✓ (初始化时)
*p2 = 30; // ✓ 可以修改值
p2 = &b; // ❌ 不能改变指向
// p3: 都不可变
const int* const p3 = &a;
*p3 = 30; // ❌
p3 = &b; // ❌
记忆口诀:
const在*左边,值不能改;const在*右边,指针不能改
7.3、野指针与空指针
// 野指针 - 未初始化的指针,指向随机地址
int* p1; // ❌ 危险!
*p1 = 10; // 可能导致程序崩溃
// 空指针 - 明确指向NULL/nullptr
int* p2 = nullptr; // ✅ C++11推荐
int* p3 = NULL; // ✅ C风格
int* p4 = 0; // ✅ 也可以
if (p2 == nullptr) {
printf("p2是空指针\n");
}
// 悬空指针 - 指向已释放内存的指针
int* p5 = new int(10);
delete p5;
p5 = nullptr; // ✅ 好习惯,避免悬空
8、动态内存管理
8.1、C风格(malloc/free)
#include <stdlib.h>
int* arr = (int*)malloc(10 * sizeof(int)); // 分配内存
if (arr == NULL) {
// 处理分配失败
}
arr[0] = 100; // 使用内存
free(arr); // 释放内存
arr = NULL; // 避免悬空指针
8.2、C++风格(new/delete)
// 单个对象
int* p = new int(10); // 分配并初始化
delete p; // 释放
p = nullptr;
// 数组
int* arr = new int[10]; // 分配数组
arr[0] = 100;
delete[] arr; // 释放数组(注意[])
arr = nullptr;
8.3、常见内存错误
| 错误类型 | 描述 | 示例 |
|---|---|---|
| 内存泄漏 | 分配后未释放 | new 后忘记 delete |
| 重复释放 | 同一内存释放多次 | delete p; delete p; |
| 越界访问 | 访问分配范围外 | arr[100](只分配了10个) |
| 悬空指针 | 使用已释放的指针 | delete p; *p = 10; |
| 野指针 | 使用未初始化的指针 | int* p; *p = 10; |
9、指针与字符串
9.1、字符串的两种表示
// 方式1:字符数组
char str1[] = "Hello"; // 可修改,占6字节(含\0)
str1[0] = 'h'; // ✓ 合法
// 方式2:字符指针
char* str2 = "Hello"; // 指向字符串常量,通常不可修改
str2[0] = 'h'; // ❌ 未定义行为,可能崩溃
9.2、字符串指针数组
const char* fruits[] = {
"Apple",
"Banana",
"Orange"
};
for (int i = 0; i < 3; i++) {
printf("%s\n", fruits[i]);
}
10、多级指针
int a = 10;
int* p1 = &a;
int** p2 = &p1;
int*** p3 = &p2;
printf("%d\n", a); // 10
printf("%d\n", *p1); // 10
printf("%d\n", **p2); // 10
printf("%d\n", ***p3); // 10
多级指针的应用场景:
// 二维数组的动态分配
int** create_matrix(int rows, int cols) {
int** matrix = new int*[rows];
for (int i = 0; i < rows; i++) {
matrix[i] = new int[cols];
}
return matrix;
}
// 修改指针本身(需要指针的指针)
void allocate(int** ptr, int size) {
*ptr = new int[size];
}
int* p = nullptr;
allocate(&p, 100); // p现在指向分配的内存
11、指针最佳实践
✅ 推荐做法
// 1. 初始化指针
int* p = nullptr;
// 2. 检查空指针
if (p != nullptr) {
use(p);
}
// 3. 释放后置空
delete p;
p = nullptr;
// 4. 使用const保护数据
void print(const int* arr, int size);
// 5. 优先使用智能指针(C++11)
#include <memory>
std::unique_ptr<int> p1 = std::make_unique<int>(10);
std::shared_ptr<int> p2 = std::make_shared<int>(20);
❌ 避免做法
// 1. 野指针
int* p; // 未初始化
// 2. 返回局部变量地址
int* func() {
int local;
return &local; // 危险!
}
// 3. 内存泄漏
void leak() {
int* p = new int[100];
// 忘记delete
}
// 4. 数组越界
int* arr = new int[10];
arr[100] = 1; // 越界!
12、指针常见面试题
Q1: 以下代码输出什么?
int arr[5] = {1, 2, 3, 4, 5};
int* p = arr;
printf("%d\n", *(p++)); // 1
printf("%d\n", *(++p)); // 3
Q2: 指针和引用的区别?
| 特性 | 指针 | 引用 |
|---|---|---|
| 可为空 | ✅ | ❌ |
| 可重新赋值 | ✅ | ❌ |
| 需要解引用 | ✅ | ❌ |
| 占用内存 | 是(4/8字节) | 否(别名) |
Q3: 以下代码有什么问题?
char* get_string() {
char str[] = "Hello";
return str; // ❌ 返回局部数组地址
}
13、总结
指针知识体系图
指针
│
┌────────────────┼────────────────┐
│ │ │
指针运算 指针与数组 指针与函数
│ │ │
┌────┴────┐ ┌────┴────┐ ┌────┴────┐
│ │ │ │ │ │
加减 比较 数组名 遍历 参数 返回值
│ │ │
└────────────────┼────────────────┘
│
┌───────────┴───────────┐
│ │
特殊指针 内存管理
│ │
┌────┴────┐ ┌────┴────┐
│ │ │ │
void* const malloc new/delete
│ │
└───────────┬───────────┘
│
最佳实践
核心要点回顾
| 概念 | 关键点 |
|---|---|
| 指针本质 | 内存地址的抽象 |
| 取地址 | & 运算符 |
| 解引用 | * 运算符 |
| 指针运算 | 考虑数据类型大小 |
| 数组关系 | 数组名退化为指针 |
| 函数参数 | 传址可修改原值 |
| 内存管理 | 分配后必须释放 |
| 安全实践 | 初始化、检查、置空 |
🎯 最后忠告
指针是C/C++最强大的武器,也是最危险的陷阱。
- 理解指针,才能真正理解C/C++
- 滥用指针,程序将充满bug和漏洞
- 现代C++推荐使用智能指针和容器,减少裸指针使用
掌握指针,但不依赖指针,这才是C++程序员的成熟之道。
希望这篇完整的指针指南能帮助你彻底理解C/C++指针!如有疑问,欢迎继续探讨。 🚀

9189

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



