GESP 6级

指针基础

6级 · 指针/STL/复杂度
💡 为什么重要

指针是C++最强大也最难理解的特性之一。在GESP 6级考试中,指针几乎是必考内容。更关键的是,后面你要学的动态内存(new/delete)、链表、树等数据结构,全部依赖指针来实现。可以说,指针是通往高级C++的"钥匙"。如果你现在觉得难,不用担心——我们一步一步来。

📖 前置知识

在学习指针之前,你需要掌握以下内容(这些是GESP 1-4级的知识):

  • 变量的声明和赋值(如 int a = 5;)
  • 基本数据类型(int, double, char等)
  • cout 和 cin 的基本用法
  • 数组的基本用法(如 int arr[5] = {1,2,3,4,5};)
  • 函数的基本定义和调用
🔍 什么是"地址"和"指针"?

想象一栋大楼,每个房间都有一个门牌号(地址)。电脑内存也是一样的:每个变量住在内存里的某个"房间",每个房间有一个唯一的编号(地址)。

指针就是一个存着"门牌号"的变量。它不直接装数据,而是装"数据住在哪"这个地址。

打个比方:

  • int a = 10; → a 是一个房间,里面住着数字 10
  • int *p = &a; → p 是另一个房间,里面住着 a 的门牌号(地址)
📐 核心语法一览
int a = 10; // 声明一个整型变量a,值为10
int *p = &a; // 声明一个指针p,指向a(&取地址符)
cout << *p; // 输出10(*解引用符:通过地址找内容)
*p = 20; // 通过指针修改a的值,a变成20

关键符号:
&(取地址符):获取变量的内存地址
*(解引用符):通过地址访问对应的值
*(声明时):表示这是一个指针变量
💻 代码详解
📝 示例1:指针的基本使用
#include <iostream>
using namespace std;

int main() {
    int a = 5;           // 声明变量a,值为5
                         // 假设a的内存地址是 0x1000

    int *p = &a;         // 声明指针p,存的是a的地址(0x1000)
                         // &a 表示"取a的地址"
                         // 此时 p 的值 = 0x1000

    cout << p << endl;   // 输出a的地址(类似 0x7ffd5c3a)
    cout << *p << endl;  // 输出5
                           // *p 表示"通过p存的地址,找到里面的值"
                           // 即通过 0x1000 找到 a,取出5

    *p = 20;              // 通过指针修改a的值
                          // *p = 20 等价于 a = 20

    cout << a << endl;   // 输出20(a的值已经被改了)
    cout << *p << endl;  // 输出20

    return 0;
}
📝 示例2:指针与数组
#include <iostream>
using namespace std;

int main() {
    int arr[] = {10, 20, 30, 40, 50};
    // arr 本身就是首元素的地址,等价于 &arr[0]

    int *p = arr;        // p 指向 arr[0],即 p 存的是 arr[0] 的地址
    // 也可以写成:int *p = &arr[0];

    cout << *p << endl;      // 输出10(arr[0]的值)
    cout << *(p + 1) << endl; // 输出20(arr[1]的值)
    cout << *(p + 2) << endl; // 输出30(arr[2]的值)

    // ★ 关键点:p+1 不是地址+1,而是地址+sizeof(int)
    // 因为int占4字节,所以p+1跳过4个字节,刚好到下一个int
    // 这叫做"指针算术"

    // 等价写法:
    cout << p[0] << endl;  // 输出10,指针也可以用下标!
    cout << p[1] << endl;  // 输出20
    cout << p[2] << endl;  // 输出30

    // 用指针遍历数组
    for (int *q = arr; q != arr + 5; q++) {
        cout << *q << " ";  // 依次输出 10 20 30 40 50
    }
    cout << endl;

    return 0;
}
📝 示例3:指针作为函数参数
#include <iostream>
using namespace std;

// 普通传参:函数拿到的是值的副本,改不了原变量
void badSwap(int a, int b) {
    int t = a;
    a = b;       // 只改了副本,外面的变量没变
    b = t;
}

// 指针传参:函数拿到的是原变量的地址,可以直接改
void goodSwap(int *a, int *b) {
    int t = *a;   // 通过a的地址取出值
    *a = *b;      // 通过b的地址把值写到a指向的地方
    *b = t;       // 通过a的地址把旧值写到b指向的地方
}

int main() {
    int x = 1, y = 2;

    badSwap(x, y);
    cout << x << " " << y << endl;  // 输出 1 2(没变!)

    goodSwap(&x, &y);   // 传入x和y的地址
    cout << x << " " << y << endl;  // 输出 2 1(成功交换!)

    return 0;
}
⚠️ 易错点
  • 忘记初始化指针就使用:声明 int *p; 后不赋值,p 指向随机位置(野指针),使用它会导致程序崩溃。一定要在声明时就赋值,如 int *p = &a; 或 int *p = nullptr;
  • 混淆 * 和 & 的含义:* 在声明时表示"这是指针",在使用时表示"取指针指向的值";& 在使用时表示"取地址"
  • 指针+1 的误解:p+1 不是把地址加1,而是跳过一个元素的大小。int*+1 跳4字节,double*+1 跳8字节
  • 越界访问:指针指向数组后,不能访问超出数组范围的元素,否则会读到垃圾数据甚至崩溃
  • 声明多个指针时的陷阱:int *a, b; 只有 a 是指针,b 是普通 int!应该写 int *a, *b;
🎯 练习建议
  • 入门:写一个程序,声明一个 int 变量,用指针读取并打印它的值
  • 进阶:写一个用指针实现的 swap 函数,交换两个变量的值
  • 挑战:写一个函数,用指针遍历数组并求和,不使用下标
  • 调试练习:故意写错代码(如不初始化指针),观察运行结果,培养排错能力
  • 画内存示意图:在纸上画出变量、地址、指针的关系,帮助理解