C/C++ 指针完全指南

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++指针!如有疑问,欢迎继续探讨。 🚀

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张工在路上

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值