CC++ & Algorithm

C++指针的概念与定义

困难39
语言版本:C++Python(暂无)
概述:指针就像一张写着房间号码的小纸条,通过它可以找到存储数据的位置。

C++指针:用“地址纸条”找到数据房间

同学们,你们有没有在体育馆存包的经历?管理员会给你一张小纸条,上面写着储物柜的号码。你拿着纸条,就能找到对应的柜子取出自己的东西。在C++中,指针(Pointer)就是类似这样的小纸条——它不直接保存数据,而是保存数据在内存中的“门牌号”(地址)。你手里的纸条可以帮你在成千上万的柜子里精准找到你的物品;同样,指针能帮你在密密麻麻的内存中找到某个变量。

一、指针是什么——像一张写着房间号码的小纸条

当我们定义一个普通变量时,比如 int age = 10;,计算机就在内存中开辟一个小房间(4个字节),并贴上标签“age”,里面放上数字10。而指针变量存储的是这个房间的门牌号,而不是房间里的东西。打个比方:普通变量就像你的书包,里面放着课本;指针变量就像一张写着“书包位置”的便签,你拿着便签就能找到书包,然后从里面拿课本。

二、指针的定义——告诉你这个纸条将来要指向哪种房间

定义指针的格式很简单:先写出指针要指向的数据类型,再加上星号 *,最后给指针取个名字。例如:

int *ptr;      // 声明一个可以指向整数房间的指针,名字叫ptr
double *p;     // 声明一个可以指向双精度小数房间的指针
char *cp;      // 声明一个可以指向字符房间的指针

注意,这里的星号紧紧挨着变量名(也可以挨着类型,但初学者最好挨着变量名)。这行代码只是告诉编译器:“我准备了一个指针变量,将来它里面会存一个整数的地址。”此时指针还没有指向任何房间,就像一张空白的纸条。我们通常会在声明的同时给它赋值,或者先声明再赋值。

指针的大小在内存中是固定的(32位系统是4个字节,64位系统是8个字节),不管它指向的数据类型是什么。可以把它想象成:无论房间是放苹果还是放西瓜,门牌号的纸条长度都是一样的。

三、让指针指向一个变量——用“地址打印机” &

上节课我们认识了指针这个“门牌号纸条”,但纸条上还空着。要让它有用,必须先让它指向一个已有的变量。怎么做到呢?用取地址符 &,它就像一台“地址打印机”,可以打印出任何变量的门牌号。

例如,有一个整数变量 int score = 95;,我们可以定义一个指针 int *p;,然后让 p 指向 score:p = &score;。这样,p 这个纸条上就写上了 score 房间的门牌号。

#include <iostream>
using namespace std;

int main() {
    int score = 95;      // 定义一个整数变量,里面放95
    int *p;              // 定义一个整型指针p
    p = &score;          // 把score的地址赋值给p

    cout << "score的地址是: " << &score << endl;   // 输出一串像0x...的数字
    cout << "指针p里存的地址: " << p << endl;      // 和上面一样
    return 0;
}

注意:取地址符 & 只能用在变量上,不能用在字面常量上(比如 &95 是错误的,因为95没有固定房间)。

四、通过指针访问数据——用“钥匙” *(解引用)

有了指向的地址后,我们如何通过指针来访问或修改房间里的数据?用解引用操作符 *。它就像一把钥匙,用指针变量前加上 * 就能打开房间门。注意,这里的星号和声明指针时的星号含义不同,同学们要区分开:声明时的星号是“我要定义一个指针变量”,使用时的星号是“我要访问这个指针所指向的空间内容”。

#include <iostream>
using namespace std;

int main() {
    int score = 95;      // 定义变量,成绩95分
    int *p;              // 定义整型指针p
    p = &score;          // 让p指向score

    cout << "直接用变量查看score的值: " << score << endl;         // 输出95
    cout << "通过指针p访问score的值: " << *p << endl;             // 输出95

    *p = 100;            // 通过指针修改score房间里的东西
    cout << "修改后直接用变量查看score: " << score << endl;       // 输出100
    cout << "修改后通过指针查看*p: " << *p << endl;               // 输出100

    return 0;
}

在这个例子中,*p 就相当于 score 这个变量。如果指针没有指向任何变量(比如刚声明时),千万不能直接解引用,否则程序会崩溃,就像拿一张空白纸条去开柜子——肯定找不到。

五、指针与数组——游标卡尺一样自由移动

我们已经知道数组是一串连续的房间,比如 int arr[5] = {10, 20, 30, 40, 50}; 在内存中开了5个相邻的整数房间。C++里有一个秘密:数组名 arr 本身就代表数组第一个房间的门牌号(即 &arr[0])。所以你可以直接把数组名赋值给指针。

int arr[5] = {10, 20, 30, 40, 50};   // 定义数组,存放五个成绩
int *p = arr;                         // p指向arr[0],等价于 int *p = &arr[0];

现在指针指向了第一个元素。如果想访问第2个元素呢?指针支持加减运算:p + 1 表示跳到下一个房间(注意不是加1个字节,而是加 sizeof(int) 个字节)。所以 *(p+1) 就是 arr[1],即20。同样,p + 2 指向 arr[2]

我们可以用指针遍历整个数组,就像用脚标一样方便:

#include <iostream>
using namespace std;

int main() {
    int arr[5] = {10, 20, 30, 40, 50};   // 定义数组
    int *p = arr;                        // p指向数组首元素

    // 方法1:用指针加法遍历
    cout << "方法1——用*(p+i)遍历:" << endl;
    for (int i = 0; i < 5; i++) {
        cout << "arr[" << i << "] = " << *(p + i) << endl;
    }

    // 方法2:直接移动指针,一个一个看
    cout << "方法2——移动指针遍历: ";
    int *temp = p;      // 用临时指针,避免丢失原位置
    for (int i = 0; i < 5; i++) {
        cout << *temp << " ";   // 输出当前指向的元素
        temp++;                 // 指针移到下一个房间
    }
    cout << endl;

    return 0;
}

注意:第二个循环结束后,p 还在原位置(因为我们用了临时指针 temp),这是安全的。如果直接对 p 自增,那么循环结束后 p 已经指向了数组末尾之后的位置,不能再解引用它。所以如果后面还要用,最好保留原来的指针或者备份一个。

指针和数组的紧密关系还可以用在字符串上:char str[] = "Hello"; 中的 str 也是首字符的地址。你可以用指针操作字符串里的每个字符。

六、常见错误与注意事项

1. 未初始化指针就解引用

int *p;       // 指针p没有指向任何变量
cout << *p;   // 危险!p里是随机地址,程序可能崩溃

应对:每次定义指针后尽快给它赋值,或者初始化为 nullptr(C++11引入的空指针)。

2. 指针越界

int arr[5] = {1,2,3,4,5};
int *p = arr;
*(p+100) = 999;   // 访问了数组范围之外的内存,可能覆盖其他数据或导致崩溃

应对:确保指针加减后仍然在有效范围内。

3. 混淆声明与使用时的星号

int *p;   // 声明:p是一个指针
*p = 10;  // 使用:通过p修改所指变量

初学者容易把 *p = 10; 理解为“把10赋给指针p”,实际上是“把10赋给指针p指向的变量”。

4. 指针没有指向同一类型变量

double d = 3.14;
int *p = &d;   // 错误!int*指针不能指向double变量(类型不匹配)

应对:指针的类型必须和所指向变量的类型一致。

七、完整示例:用指针修改期末考试分数

下面是一个综合例子,展示指针的基本用法:存储学生分数,用指针修改其中一个分数。

#include <iostream>
using namespace std;

int main() {
    // 定义三个学生的成绩
    int score1 = 85;      // 小明
    int score2 = 92;      // 小红
    int score3 = 78;      // 小刚

    // 定义一个指针,指向小明的成绩
    int *ptr = &score1;

    cout << "小明的原成绩: " << score1 << endl;          // 85
    cout << "通过指针看到的小明成绩: " << *ptr << endl;  // 85

    // 老师发现少加了5分,通过指针修改
    *ptr = *ptr + 5;   // 相当于 score1 = score1 + 5;

    cout << "修改后小明的成绩: " << score1 << endl;      // 90
    cout << "修改后通过指针看到的: " << *ptr << endl;    // 90

    // 换个指针,指向小红的成绩
    ptr = &score2;
    *ptr = 95;         // 把小红的成绩改成95
    cout << "小红的成绩现在是: " << score2 << endl;      // 95

    return 0;
}

运行结果:

小明的原成绩: 85
通过指针看到的小明成绩: 85
修改后小明的成绩: 90
修改后通过指针看到的: 90
小红的成绩现在是: 95

八、相关指引

学完指针的概念、赋值、解引用以及指针与数组的关系,你已经掌握了最基础也最重要的指针用法。接下来可以继续学习:

  • 指针和函数:如何把指针作为参数传给函数,从而在函数内部修改外部变量。
  • 动态内存分配:用 newdelete 在程序运行时创建和销毁变量。
  • 指针数组和数组指针:更复杂的指针组合,用于处理字符串数组或二维数组。
  • 空指针和野指针:如何安全地初始化指针(nullptr)以及避免产生野指针。

指针是C++的“双刃剑”,用好了能写出高效灵活的代码,用不好容易出错。多动手写几个小例子,比如用指针交换两个变量的值、用指针遍历数组找出最大值,很快你就能熟练驾驭这把“钥匙”了。

例题精讲

1单选题

在C++中,已知int a = 10; int *p = &a;,则*p的值是?

A变量a的地址
B变量a的值,即10
C指针p的地址
D随机值
2判断题

在C++中,int *p; 会直接初始化指针p为nullptr。

3填空题
以下代码声明了一个整型变量a和一个整型指针p,并将p指向a。请补全代码:
int a = 5;
int *p = ___;
4单选题

在C++中,假设int *p; 则sizeof(p)的值通常是?

Asizeof(int)
B4字节或8字节(取决于系统)
C1字节
D编译错误
5判断题

在C++中,空指针(nullptr)可以安全地使用解引用操作(*ptr)。