智能程序设计(C语言)


指针与数组 (上)

计算机学院    杨已彪

yangyibiao@nju.edu.cn

提纲

  • 指针的算术运算

  • 比较指针

  • 使用指针进行数组处理

  • 结合 * 和 ++ 运算符

  • 课堂编码

  • 课堂小测


指针的算术运算

int a[10], *p;
p = &a[0];

指针可以指向数组元素:

图示:

通过 p 可以访问 a[0]——比如把 5 存进 a[0]:

*p = 5;

如下图:

p 指向数组元素时, 对 p 做指针算术就能访问其他元素

C支持三种(且只有三种)指针运算形式:

  • 指针加上整数

  • 指针减去整数

  • 两个指针相减


指针加减整数

int a[10], *p, *q, i;
p = &a[2];
q = p + 3;
p += 6;
p = &a[8]; 
q = p - 3;
p -= 6;

将整数j添加到指针p会产生一个指向第j个元素的指针, 该元素位于p指向的元素之后.

准确地说: p 指向 a[i], 则 p + j 指向 a[i+j]; p 指向 a[i], 则 p - j 指向 a[i-j]。

假设声明如下:


两个指针相减

当一个指针从另一个指针中减去时, 结果是指针之间的距离(数组元素的个数).

p 指向 a[i]、q 指向 a[j], 则 \(p - q = i - j\)。示例:

p = &a[5];
q = &a[1];
i = p - q; /* i is  4 */
i = q - p; /* i is -4 */

对不指向数组元素的指针执行算术运算导致未定义的行为

只有两个指针指向同一个数组时, 把它们相减才有意义


比较指针

关系运算符(<, <=, >, >=)和判等运算符(==, !=)进行指针比较.

使用关系运算符仅对指向同一数组元素的指针有意义. 比较的结果取决于数组中两个元素的相对位置.

p = &a[5];
q = &a[1];
p <= q; // expression value: 0
p >= q; // expression value: 1
a 0 1 2 3 4 5 6 7 q p p <= q → 0(假) p >= q → 1(真) 同一数组中,地址越大元素越靠后

使用指针进行数组处理

#define N 10
int a[N] = {11, 34, 82, 7, 64, 98, 47, 18, 79, 20};
int sum = 0, *p;
…
for (p = &a[0]; p < &a[N]; p++)
    sum += *p;
  • 在第一次迭代结束时:p 指向 a[1],sum 为 11(已加上 a[0])

  • 在第二次迭代结束时:p 指向 a[2],sum 为 45(又加上 a[1])

  • 在第三次迭代结束时:p 指向 a[3],sum 为 127(又加上 a[2])

for 语句中的条件 p < &a[N] 值得特别提及。将地址运算符应用于 a[N] 是合法的,即使此元素不存在。


结合 * 和 ++ 运算符

p = &a[0];
while (p < &a[N])
    sum += *p++;
表达式 意义
*p++或*(p++) 表达式的值是*p在递增之前, 之后自增p
(*p)++ 表达式的值为*p在递增之前, 稍后增加*p
*++p或*(++p) 先递增p, 表达式的值为*p
++*p或++(*p) 先递增*p, 表达式的值为*p

C程序员经常结合使用*(间接寻址)和++运算符.

修改数组元素后前进到下一个元素的语句: a[i++] = j;

对应的指针版本: *p++ = j;

因为后缀++的优先级高于*, 编译器将其视为 *(p++) = j;

*和++的可能组合见左栏表格.

  • *(p++) 指针自增(指向下一个对象)

  • (*p)++ 指向的对象自增(p本身不变)

\*和++最常见的组合是*p++, 在循环中很常用. 上一页的求和 for 循环可以改写成左栏的 while 版.


结合 * 和 ++ 运算符 (续)

int contents[100];
int top = 0;

void push(int i) { contents[top++] = i; }
int pop(void)   { return contents[--top]; }
int *top_ptr = &contents[0];

void push(int i)
{
  if (is_full()) stack_overflow();
  else           *top_ptr++ = i;
}

int pop(void)
{
  if (is_empty()) stack_underflow();
  else            return *--top_ptr;
}

\*和--运算符的组合方式与\*和++相同.

看一个 * 和 -- 组合的例子——用数组模拟一个栈 (后进先出):

索引版:用整型变量 top 记录 contents 数组的下一个空位——push 把元素存到 top 处,再让 top 后移;pop 先让 top 前移,再返回该处的元素(见左栏)。

指针版:把 top 换成初始指向 contents[0] 的 top_ptr,它同样指向下一个空位,新的 push 和 pop 函数见左栏。

top_ptr 下一空位 7 8 9 push 后 ++ pop:先 -- 再读 后进先出(LIFO)

课堂编码

任务:用指针遍历数组,实现两个函数。

  • int find_sum(int *a, int n):返回数组所有元素之和。

  • int find_max(int *a, int n):返回数组元素的最大值。

要求:只用指针 *p 访问元素,不使用下标 a[i];循环变量在 for 中自声明。

样例输入:34 82 49 102 7 94 23 11 50 31

样例输出:sum = 483、max = 102

提示:求和让 p 从 a 走到 a + n,每次把 *p 累加;求最大值先把 max 初始化为 *a,再从 a + 1 开始逐个比较。

先想参数:函数需要数组首元素地址 a 和元素个数 n——指针本身不知道数组有多长。

⚡ 在 VS Code 中打开 skeleton.c (框架已备好)

课堂编码 · 参考实现

int find_sum(int *a, int n)
{
  int sum = 0;
  for (int *p = a; p < a + n; p++)
    sum += *p;
  return sum;
}
int find_max(int *a, int n)
{
  int max = *a;
  for (int *p = a + 1; p < a + n; p++)
    if (*p > max)
      max = *p;
  return max;
}
#include <stdio.h>

int main(void)
{
  int b[10] = {34, 82, 49, 102, 7, 94, 23, 11, 50, 31};
  printf("sum = %d\n", find_sum(b, 10));   /* 期望输出: sum = 483 */
  printf("max = %d\n", find_max(b, 10));   /* 期望输出: max = 102 */
  return 0;
}

两个循环都用 p < a + n 作条件,指针不会走到数组之外;调用时数组名 b 自动作为 &b[0] 传入。


课堂小测

  1. int a[5]; int *p = a; 中 p + 1 表示什么?p + 1 的地址比 p 大多少字节?
  2. p2 - p1 得到的是地址差还是元素个数差?
  3. 阅读代码:int a[3] = {1, 2, 3}, *p = a; (*p)++; *++p; 执行后,数组 a 的三个元素各是多少?p 指向哪个元素?
  4. 纠错: int a[10], *p; for (p = a; p <= a + 10; p++) ... 有什么问题?



未完待续

🔒 本课件尚未发布