C++ 系统学习日记・第 09 天|指针全解:定义 + 内存 + 空 / 野指针 + const 修饰 + 数组 + 函数

4 阅读8分钟

📖 学习信息

学习课程:黑马 C++ 零基础入门教程

本日学习:C++ 指针核心全套知识包含:指针的定义与基础使用、指针占用内存空间、空指针、野指针、const 修饰指针的三种形式、指针与数组结合、指针与函数(地址传递)学习目标:彻底理解指针的本质是地址,掌握指针的所有基础用法,区分值传递与地址传递,规避指针常见错误


一、前言

指针是 C++ 最核心也最容易出错的语法,同时也是 C++ 区别于其他高级语言的重要特性。指针的本质是存储内存地址的变量,通过指针可以直接操作计算机内存,这也是 C++ 执行效率高的根本原因。后续学习的数组、字符串、面向对象、动态内存分配、数据结构与算法,全部建立在指针的基础上,必须彻底吃透。


二、指针的定义与基础使用

1. 什么是指针

普通变量存储的是数据本身,而指针变量存储的是数据在内存中的地址。通过指针,我们可以间接访问和修改该地址指向的内存中的数据。

2. 基础语法

// 定义指针:数据类型 *指针变量名;
数据类型 *指针名;

// 给指针赋值:指针名 = &变量名;
// & 是取地址符,获取变量的内存地址
指针名 = &普通变量;

// 解引用:*指针名;
// * 是解引用符,访问指针指向的内存中的数据
*指针名 = 新值;

3. 完整代码示例

#include <iostream>
using namespace std;

int main()
{
    // 1. 定义普通变量
    int a = 10;
    cout << "a的值:" << a << endl;
    cout << "a的内存地址:" << &a << endl;

    // 2. 定义指针变量,指向a的地址
    int *p = &a;
    cout << "指针p存储的地址:" << p << endl;
    cout << "指针p指向的值:" << *p << endl;

    // 3. 通过指针修改a的值
    *p = 100;
    cout << "修改后a的值:" << a << endl;
    cout << "修改后*p的值:" << *p << endl;

    return 0;
}

4. 核心概念区分

  • &a:取变量 a 的内存地址
  • p:指针变量本身,存储的是地址值
  • *p:解引用,访问指针 p 指向的内存中的数据

三、指针占用的内存空间

指针本身也是一个变量,会占用内存空间,其大小只与操作系统的位数有关,与指向的数据类型无关。

1. 内存大小规则

  • 32 位操作系统:所有指针都占用 4 字节
  • 64 位操作系统:所有指针都占用 8 字节

2. 代码验证

#include <iostream>
using namespace std;

int main()
{
    int *p1;
    double *p2;
    char *p3;

    // 输出不同类型指针的大小
    cout << "int指针大小:" << sizeof(p1) << "字节" << endl;
    cout << "double指针大小:" << sizeof(p2) << "字节" << endl;
    cout << "char指针大小:" << sizeof(p3) << "字节" << endl;

    return 0;
}

运行结果(64 位系统)

int指针大小:8字节
double指针大小:8字节
char指针大小:8字节

四、空指针

1. 定义

空指针是指向内存地址 0的指针,用于初始化指针变量,避免指针变成野指针。

2. 语法

// 两种写法等价,推荐第二种(C++11及以上)
int *p = NULL;
int *p = nullptr;

3. 核心注意事项

空指针不能进行解引用操作,因为地址 0 是系统占用的内存区域,不允许用户访问,访问会导致程序崩溃。

int *p = NULL;
*p = 100;  // 错误:空指针解引用,程序崩溃

4. 作用

  • 初始化指针,避免指针指向随机内存
  • 作为函数返回值,表示操作失败

五、野指针

1. 定义

野指针是指向非法内存空间的指针,这些内存空间可能未被分配,或者已经被释放。

2. 常见产生原因

  1. 指针未初始化

    int *p;  // 未初始化,指向随机内存地址
    *p = 100;  // 错误:野指针访问
    
  2. 指针释放后未置空

    int *p = new int(10);
    delete p;  // 释放内存,但p仍然指向原来的地址
    *p = 100;  // 错误:野指针访问
    
  3. 指针越界访问

    int arr[5] = {1,2,3,4,5};
    int *p = arr;
    p += 10;  // 超出数组范围,变成野指针
    

3. 危害与避免方法

  • 危害:程序崩溃、数据异常、难以调试的 bug

  • 避免方法

    1. 所有指针必须初始化(初始化为 NULL 或合法地址)
    2. 释放内存后立即将指针置为 NULL
    3. 避免指针越界访问

六、const 修饰指针(重点 + 难点)

const 修饰指针有三种形式,核心区别是指针的指向能不能改指针指向的值能不能改

1. 三种形式对比表

表格

形式名称指针指向指向的值示例
const 数据类型 *指针名;常量指针可以改不能改const int *p = &a;
数据类型 * const 指针名;指针常量不能改可以改int * const p = &a;
const 数据类型 * const 指针名;常量指针常量不能改不能改const int * const p = &a;

2. 记忆口诀

  • const 在前,值不能改(常量指针)
  • const 在后,指向不能改(指针常量)
  • const 前后都有,都不能改

3. 代码示例

#include <iostream>
using namespace std;

int main()
{
    int a = 10;
    int b = 20;

    // 1. 常量指针:值不能改,指向可以改
    const int *p1 = &a;
    // *p1 = 100;  // 错误:指向的值不能改
    p1 = &b;  // 正确:指向可以改

    // 2. 指针常量:指向不能改,值可以改
    int * const p2 = &a;
    *p2 = 100;  // 正确:指向的值可以改
    // p2 = &b;  // 错误:指向不能改

    // 3. 常量指针常量:都不能改
    const int * const p3 = &a;
    // *p3 = 100;  // 错误
    // p3 = &b;    // 错误

    return 0;
}

七、指针与数组

1. 数组名的本质

数组名是指向数组首元素的常量指针,不能被赋值。

int arr[5] = {1,2,3,4,5};
int *p = arr;  // 等价于 int *p = &arr[0];
// arr = &arr[1];  // 错误:数组名是常量指针,不能修改指向

2. 用指针遍历数组

利用指针的自增操作,可以遍历数组的所有元素,这是指针最常用的场景之一。

#include <iostream>
using namespace std;

int main()
{
    int arr[5] = {1,2,3,4,5};
    int *p = arr;

    // 用指针遍历数组
    for (int i = 0; i < 5; i++) {
        cout << *p << " ";
        p++;  // 指针自增,指向下一个元素
    }

    return 0;
}

3. 指针与数组的关系

  • arr[i] 等价于 *(arr + i) 等价于 *(p + i)
  • 数组下标本质上是指针的偏移量

八、指针与函数(地址传递)

1. 什么是地址传递

将变量的内存地址作为实参传递给函数,形参是接收地址的指针。通过地址传递,函数内部可以通过指针直接修改实参的值,这是值传递做不到的。

2. 经典示例:用地址传递交换两个数

#include <iostream>
using namespace std;

// 地址传递:形参是指针
void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main()
{
    int x = 10;
    int y = 20;
    cout << "交换前:x = " << x << ",y = " << y << endl;

    // 传递变量的地址
    swap(&x, &y);

    cout << "交换后:x = " << x << ",y = " << y << endl;

    return 0;
}

3. 值传递 vs 地址传递 核心区别

表格

传递方式本质能否修改实参适用场景
值传递拷贝实参的值给形参不能只需要使用实参的值,不需要修改
地址传递拷贝实参的地址给形参需要在函数内部修改实参的值

九、常见错误与避坑点

  1. 空指针解引用:永远不要对 NULL 或 nullptr 进行解引用
  2. 野指针访问:所有指针必须初始化,释放后置空
  3. const 修饰混淆:牢记口诀,区分常量指针和指针常量
  4. 指针越界:访问数组时不要超出下标范围
  5. 数组名赋值:数组名是常量指针,不能修改指向
  6. 忘记解引用:直接给指针赋值会修改指针的指向,而不是指向的值

十、今日学习总结

  1. 指针本质:存储内存地址的变量,通过解引用访问指向的内存

  2. 内存大小:32 位系统 4 字节,64 位系统 8 字节,与类型无关

  3. 空指针:指向地址 0,用于初始化,不能解引用

  4. 野指针:指向非法内存,必须避免

  5. const 修饰指针

    • 常量指针:const int *p,值不能改,指向可以改
    • 指针常量:int * const p,指向不能改,值可以改
    • 常量指针常量:都不能改
  6. 指针与数组:数组名是指向首元素的常量指针,可用指针遍历数组

  7. 地址传递:传递变量地址,函数内部可修改实参的值


✍️ 下节预告

下一篇将学习 C++ 结构体核心知识:结构体的定义与初始化、结构体成员访问、结构体数组、结构体指针、结构体作为函数参数(值传递与地址传递) ,掌握结构体可以将不同类型的数据封装成一个整体,是面向对象编程的基础雏形,为后续学习类和对象打下关键过渡基础。