智能程序设计(C语言)


指针基础 (下): 传址调用

计算机学院    杨已彪

yangyibiao@nju.edu.cn

提纲

  • 指针作为参数: 传址调用

  • 传址调用要点与 scanf 的参数

  • 案例: 查找最大最小

  • const 保护参数与指针返回值

  • 课堂编码与小测


指针作为参数: 传址调用

void decompose(double x, long * int_part, double *frac_part)
{
    *int_part = (long) x;
    *frac_part = x - *int_part;
}
void decompose(double x, long * int_part, double *frac_part);
void decompose(double, long *, double *);
decompose(3.14159, &i, &d);

之前的 swap 演示还记得吗: 传值改不了实参。传递变量的地址而不是变量的值, 函数就能改到调用者的变量——decompose 就是靠这个思路起死回生的.

decompose的新定义:

decompose的原型可以是:

也可以是:

调用decompose:

作为调用的结果, 指向i的指针存储在int_part中, 指向d的指针存储在frac_part中:


指针作为参数: decompose 与 scanf

① 第一个赋值

把 x 转换为 long 类型, 存入 int_part 指向的对象中:

② 第二个赋值

把 x - *int_part 的值存储到 frac_part 指向的对象中:

scanf 调用中的参数是指针——scanf 需要地址, 才能把读入的结果写回调用者的变量:

int i;
…
scanf("%d", &i);

如果没有 &, 传递给 scanf 的将是 i 的值而不是地址——scanf 无处写回结果.


指针作为参数: 常见错误

int i, *p;
…
p = &i;
scanf("%d", p);
scanf("%d", &p); /*** 错误的 ***/
decompose(3.14159, i, d);

尽管scanf的参数必须是指针, 但并不总是需要&运算符:

使用&运算符是错误的:

传递指针失败可能导致灾难性后果; 缺少 & 的 decompose 调用:

当decompose在 *int_part 和 *frac_part 中存储值时, 它将修改未知的内存地址, 而不是修改 i 和 d . 若为 decompose 提供了原型, 编译器会检测到错误; scanf 的参数类型不匹配在开 -Wall 时也会被 -Wformat 检查出来——但仍不能完全依赖编译器, 传参前先想清楚类型

scanf("%d", p) p 2000 p 已保存 &i i 42 scanf 通过 p 写入 i scanf("%d", &p) p 2000 42 输入 42 i ? 未被写入 &p 是 p 自己的地址
程序: 查找数组中最大和最小的元素

现场演示: 输入 10 个数, 输出其中最大和最小值——用函数完成, 且函数要带回两个结果

样例:

输入 10 个数: 3 8 1 9 2 7 4 6 5 0
最大: 9
最小: 0
⚡ 在 VS Code 中打开 minmax.c

提示: return 只能带回一个值, 两个结果怎么传出来? (想想地址的作用)


程序: 查找数组中最大和最小的元素 (参考代码)

/* Finds the largest and smallest elements in an array */
#include <stdio.h>
#define N 10
void max_min(int a[], int n, int *max, int *min);
int main(void)
{
  int b[N], big, small;
  printf("输入 %d 个数: ", N);
  for (int i = 0; i < N; i++) {
    scanf("%d", &b[i]);
  }
  max_min(b, N, &big, &small);
  printf("最大: %d\n", big);
  printf("最小: %d\n", small);
  return 0;
}
void max_min(int a[], int n, int *max, int *min)
{
  *max = *min = a[0];
  for (int i = 1; i < n; i++) {
    if (a[i] > *max) {
      *max = a[i];
    } else if (a[i] < *min) {
      *min = a[i];
    }
  }
}
  • max_min(b, N, &big, &small)——把 big、small 的地址传给函数, 函数通过指针把结果写回去

  • *max = a[0] 先假设, 遍历中更大/更小就更新——指针参数让函数一次"带回"两个值

  • 这也是 return 单值限制的经典解法


使用const保护参数

f(&x);
void f(const int *p)
{
  *p = 0; /*** 错误 ***/
  p = &x; /*** 合法 ***/
}
void f(int * const p)
{
  *p = 0; /*** 合法 ***/
  p = &x; /*** 错误 ***/
}
void f(const int * const p)
{
  *p = 0; /*** 错误 ***/
  p = &x; /*** 错误 ***/
}

当参数是指向变量x的指针时, 我们通常假设x将被修改:

但是, 有可能 f 只需要检查 x 的值, 而不是更改它.

使用指针的原因可能主要是因为效率: 如果变量需要大量的存储空间, 则传递变量的值会浪费时间和空间.

可以使用const来表明函数不会更改指针参数所指向的对象.

把 const 写在类型说明符之前(const int *p)表示不能通过 p 修改 *p;const 位置不同,保护的对象不同,见下列三种写法

尝试修改*p, 编译器会报错.


指针作为返回值

int *max(int *a, int *b)
{
  if (*a > *b)
    return a;
  else
    return b;
}
int *f(void)
{
    int i;
    …
    return &i;
}
int *find_middle(int a[], int n) {
    return &a[n/2];
}

允许函数返回指针:

调用max函数: p = max(&i, &j);

调用后, p 指向 i 或 j .

尽管这个例子中max返回作为参数传递给它的指针之一, 但这不是唯一的可能性. 函数还可以返回指向全局变量(文件作用域变量)或静态局部变量的指针. 永远不要返回指向自动局部变量的指针:

f返回后变量i将不存在.

指针可以指向数组元素. 如果a是一个数组, 则&a[i]是指向a中元素i的指针. 函数返回指向数组中某个元素的指针有时很有用. 下面的函数假设a有n个元素, 返回一个指向数组中间元素的指针:


课堂编码

现场演示

  1. 写 void swap(int *a, int *b), 在 main 里验证真的交换了——之前传值版的悬念今天揭晓 (样例: 交换前 x=1, y=2, 调用后 x=2, y=1)
  2. 在第7页参考代码基础上增加 avg 指针参数, 写 void max_min(int a[], int n, int *max, int *min, double *avg), 一次带回最大、最小和平均值
  3. 挑战: 写 int *last_max(int *a, int n), 返回指向最后一个最大元素的指针

提示: 先想清楚"函数要改谁的什么", 再决定传什么地址

⚡ 在 VS Code 中打开 skeleton.c
课堂小测

  1. 概念: 为什么 void swap(int *a, int *b) 的参数要用指针?
  2. 概念: const int *p 与 int * const p 的区别是什么?
  3. 代码阅读: void f(int x, int *p) { x = 10; *p = 20; },执行 int a = 1, b = 2; f(a, &b); printf("%d %d", a, b); 输出什么?
  4. 纠错: int *f(void) { int i = 1; return &i; } 错在哪里?应该怎么改?



未完待续

🔒 本课件尚未发布