各语言特有 · 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';}#include <stdio.h> void swap(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 *find_max(int *p, int n) { if (n == 0) return NULL; int *best = p; for (int i = 1; i < n; i++) if (p[i] > *best) best = p + i; return best;} int main(void) { int a, b, n, arr[100]; scanf("%d %d", &a, &b); swap(&a, &b); printf("%d %d\n", a, b); scanf("%d", &n); for (int i = 0; i < n; i++) scanf("%d", arr + i); printf("%d\n", sum(arr, n)); int *m = find_max(arr, n); if (m != NULL) printf("%d %td\n", *m, m - arr); return 0;}3 8 4 5 1 7 2
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)。引用是不能为空、也不能改指别处的别名,所以只在确实需要时才用指针 |
|---|---|
| C | int *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++ 不会给出错误信息,只会输出奇怪的值或者崩溃。
解引用指针之前,一定要先确认它指向的东西还活着。
解引用 NULL、已返回函数的局部变量或数组范围外的位置时,C 和 C++ 不会给出错误信息,只会输出奇怪的值或者崩溃。