{ }codelper

学习 › C++

各语言特有 · 34

指针

带着值所在的位置(地址)走,修改调用方的变量。

本单元适用于 C++ · C。
Python、JavaScript、TypeScript、Java 和 Craykon 没有直接持有地址的指针,对象通过引用来访问(见函数一课中传列表的部分)。
C# 只能在 unsafe 代码里使用指针。

指针是保存值所在内存地址、而不是值本身的变量。
下面的例子通过传地址交换两个变量,再用指针接收数组,求出总和和最大的元素。

#include <iostream> void swapPtr(int* a, int* b) {    int t = *a;    *a = *b;    *b = t;} int sum(const int* p, int n) {  // 배열은 첫 칸의 포인터로 넘어와요    int s = 0;    for (const int* end = p + n; p < end; ++p) s += *p;    return s;} int* findMax(int* p, int n) {    if (n == 0) return nullptr;    int* best = p;    for (int i = 1; i < n; ++i)        if (p[i] > *best) best = p + i;    return best;} int main() {    int a, b, n, arr[100];    std::cin >> a >> b;    swapPtr(&a, &b);  // 실제 코드라면 std::swap(a, b)    std::cout << a << ' ' << b << '\n';    std::cin >> n;    for (int i = 0; i < n; ++i) std::cin >> arr[i];    std::cout << sum(arr, n) << '\n';    int* m = findMax(arr, n);    if (m != nullptr) std::cout << *m << ' ' << (m - arr) << '\n';}
stdin
3 8
4
5 1 7 2
stdout
8 3
15
7 2

要点

  • &x 是 x 的地址,*p(解引用)是 p 所指位置上的值。
    int *p = &x; 之后,*p = 5; 就等于 x = 5;。
    其实从给 scanf 传 &n 开始,你就已经在用地址了。
  • 传数字时会复制值,所以在函数里修改,调用方不受影响。
    想修改调用方的变量,就传地址:swap(&a, &b)。
  • 数组传给函数或在表达式中使用时,会变成指向第一个元素的指针(decay)。
    所以 p[i] 和 *(p + i) 完全一样。
  • 指针运算:p + 1 指向的不是下一个字节,而是下一个元素(sizeof(int),通常 4 字节)。
    同一数组里的两个指针相减(m - arr),得到元素个数,也就是下标。
  • 可以指向数组末尾的下一个位置(p + n),但不能读取那里。
  • 什么都不指向的指针是 NULL。
    例子中的 find_max 在元素个数为 0 时返回 NULL,调用方先检查再读取。

各语言的写法

C++指针和 C 一样,空指针是 nullptr(C++11 起)— 要修改调用方,用引用 void swap(int& a, int& b) 更简单,实际中直接用 std::swap(a, b)。
引用是不能为空、也不能改指别处的别名,所以只在确实需要时才用指针
Cint *p = &x;,通过 *p 读写 — 空指针是 NULL。
修改调用方的函数写成 void swap(int *a, int *b);接收数组时要另外接收长度,如 int sum(const int *p, int n)

常见错误

解引用了 NULL

int *m = find_max(arr, 0);
printf("%d\n", *m);

→ 未定义行为,通常以 Segmentation fault 崩溃。不会像 Java 的 NullPointerException 那样告诉你错在哪里

先用 if (m != NULL) 检查,再读 *m。

返回了局部变量的地址

int *f(void) {
    int x = 3;
    return &x;
}

→ 函数结束后 x 就消失了,拿到的指针指向一个已经不存在的位置(悬空指针)。GCC 会警告 function returns address of local variable,运行时会输出奇怪的值或崩溃

结果要放在调用方准备好的数组里,或者放进下一课的动态内存。

用 sizeof 求参数数组的长度

int count(int arr[]) {
    return sizeof(arr) / sizeof(arr[0]);
}

→ 在 64 位计算机上是 8 / 4 = 2。参数 int arr[] 实际上是 int *arr,长度信息丢失了(在 main 里的 int arr[5] 上得到 5)

C 另外接收长度 n,C++ 传递带着长度的 std::vector 或 std::span(C++20)。

各语言的差异
解引用指针之前,一定要先确认它指向的东西还活着。
解引用 NULL、已返回函数的局部变量或数组范围外的位置时,C 和 C++ 不会给出错误信息,只会输出奇怪的值或者崩溃。

练习题