当我们开始编程时,就惊奇的发现
要让程序正确运转比想象的要难。我们
不得不使用调试技术。我还清楚地记得
那一刻,从那时开始我就领悟到,从我
自己的程序里寻找错误将成为我生活中
的一个重要组成部分。
         -- Maurice
Wilkes


                      1
高级语言程序设计


 主讲教师:贾彩燕
 计算机与信息技术学院
 计算机科学与技术系
 cyjia@bjtu.edu.cn


                     2
第七章 指针
   地址与指针
   指针变量的定义和使用
   指针与数组
   指针数组
   多维数组作为参数的通用函数
   动态存储管理
   定义类型
   指向函数的指针

                    3
要点回顾
   如何定义一个指针变量?
   指针变量在使用时为什么一定要指定指向的数据
    类型?
   指针变量在使用时为什么一定要初始化?
   未经初始化的指针变量称为?
   指针变量上的两种主要操作是什么?
   变量的两种访问方式是什么?
   指针变量做函数参数时传递的是什么?
       通过使用指针变量做为函数参数可以返回多于一返回值
       改变函数调用时的环境变量
   空指针有什么用?

                                   4
第七章 指针
   地址与指针
   指针变量的定义和使用
   指针与数组   指针(数值型 / 字符型)与一维数组
   指针数组    (数值型数组 / 字符串)的关系
   多维数组作为参数的通用函数
   动态存储管理
   定义类型
   指向函数的指针

                            5
7.3 指针与数组
C 指针与数组关系密切,以指针为媒介可以完
成各种数组操作
用指针做数组操作同样要特别注意越界错误。
指针和数组的关系是 C 语言特有的,除了由 C
派生出的语言(如 C++ ),一般语言中没有这
种关系。


如何利用指针访问数组。



                         6
指向数组元素的指针

int *p1, *p2, *p3, *p4;
int a[10] = {1,2,3,4,5,6,7,8,9,10};
                                  这个地址存在,但
可以写:                              写 *p4 是错误的。
p1 = &a[0];     p2 = p1;          P4 没有指向合法元
p3 = &a[5];     p4 = &a[10];      素

p1 = &a[0]; 可简写为: p1 =              区别
a; p1        p2   p3                ?      p4




   数组 a

              图 7.5   指向数组 a 的元 素的 4 个指针
数组名是表示数组首地址的地址常量                                7
指针运算
当指针 p 指向数组元素时说 p 指到了数组里。这时由 p
可以访问被 p 指的元素,还可访问数组的其他元素。
例: p1 = &a[0] ,
则 p1+1 合法,为 a[1] 的地址
  p1+2 、 p1+3 、…也合法,分别为 a[2] 、 a[3]
的地址
使用:        *(p1 + 2) = 3;/* 给 a[2] 赋值 */
       p2 = p1 + 5; /* 使 p2 指向 a[5] */
      *(p2 + 2) = 5; /* 给 a[7] 赋值 */
      *(p2 - 2) = 4; /* 给 a[3] 赋值 */
       p2 = p2 - 2; /* 这使 p2 改指向 a[3] */

通过指针访问数组元素时必须保证不越界。

                                           8
指针运算原理
一个指针指向某数组里的元素时,为什么能算出下一元素
位置?(这是指针运算的基础)
指针有指向类型, p 指向数组 a 时,由于 p 的指向类型与
a 的元素类型一致,数据对象的大小可以确定。
p+1 的值可根据 p 的值和数组元素大小算出。由一个数组
元素位置可以算出下一元素位置,或几个元素之后的元素
位置。指针运算的基础。

通用指针即使指到数组里,因没有确定指向类型,因此不
能做一般指针计算,只能做指针比较。




                              9
其他常用指针运算:
可进行增 / 减量操作(指针应指在数组里):
 p3 = p2;    ++p3;
 --p2;      p3 += 2;
如果两指针指在同一个数组里,可以求差,得到它们间的
数组 元素个数(带符号整数)。
 n = p3 – p2; /* 也可以求 p2 – p3 */
在同一个数组里的指针可以比较大小:
     if (p3 > p2) ....
当 p3 所指的元素在 p2 所指的元素之后时条件成立(值为
1 ),否则不成立(值为 0 )。两个指针不指在同一数组里
时,比较大小没有意义。

                                   10
两个同类型指针可用 == 和 != 比较相等或不等;
任何指针都能与通用指针比较相等或不等;
任何指针可与空指针值( 0 或 NULL )比较相等或不等
两指针指向同一数据元素,或同为空值时它们相等 。

当一个指针指向数组时的数组写法与指针写法:
指针的两种用法
设 p1 = &a[0] , p3 = &a[5]    p1 和 p3 相当于数组
名
可写: p1[3] = 5;    相当于 * ( p1+3)=5 ;或
a[3]=5 ;
     p3[2] = 8;   相当于 * ( p3+2)=8 ;或
a[7]=8 ;
p1[3] 称为数组写法, *(p1+3) 称为指针写法            11
[] 变址运算符
                      a[i] ⇔ *(a+i)
地址            元素             地址                元素

  a   a[0]     a[0] *a         p        a[0]     *p   p[0]
a+1   a[1]     a[1] *(a+1)   p+1        a[1]   *(p+1) p[1]
a+2   a[2]     a[2] *(a+2)   p+2        a[2]   *(p+2) p[2]
      a[3]                              a[3]
       ...




                                        ...
a+9   a[9]     a[9] *(a+9)   p+9        a[9]   *(p+9) p[9]




      下标法                          指针法
         a[i] ⇔ p[i] ⇔ *(p+i) ⇔*(a+i)
                                                       12
对数组名求值得到指向数组首元素的指针值
数组名可以“看作”常量指针,可参与一些指针运算,
与其他指针比大小,比较相等与不相等。
通过数组名的元素访问也可以采用指针写法。
 a[3] 可写为 *(a+3) 。
注意:数组名不是指针变量,特别是不能赋值,不能更
改。
    若 a 为数组,下面操作都是错误的:
   a++;
   a += 3;
   a = p;
有些运算虽不赋值但也可能没意义。如   a–3   不可能得
到合法指针值,因其结果超出数组界限
                              13
例      数组元素的引用方法( 1 )
int main()// 下标法和地址法
{ int a[5], *pa, i;
    for (i = 0; i < 5; i++)
           a[i] = i + 1;                                     pa
    pa = a;                                       a[0]   1
    for (i = 0; i < 5; i++)                       a[1]   2
           printf("*(pa+%d):%dn", i, *(pa+i));   a[2]   3
          // 通过指针找地址法
                                                  a[3]   4
    for (i = 0; i < 5; i++)
           printf("*(a+%d):%dn", i, *(a+i));     a[4]   5
          // 通过指针找地址法
    for(i = 0; i < 5; i++)
           printf("pa[%d]:%dn", i, pa[i]);
          // 下标法
    for (i = 0; i < 5; i++)
           printf("a[%d]:%dn", i, a[i]);
          // 下标法
   return 0;
}                                                            14
例      数组元素的引用方法( 2 )


// 指针法
int main()                               三种方法的比较:
{                                           下标法和地址法的执行效率相同
        int a[10];                           ,都是先计算数组元素的地址,
        int *p, i;                           再访问数组元素的值,费时。
        for (i = 0; i < 10; i++)            指针法利用指针变量直接访问数
                   scanf(“%d”, &a[i]);       组元素的值,不必计算数组元素
        printf(“n”);                        的地址,执行效率高。
        for (p = a; p < a+10; p++)          下标法直观,指针法与地址法不
        printf(“%d,”, *p);                   够直观。
        return 0;
}


                                                         15
基于指针的数组程序设计
指针运算是处理数组元素的一种有效方式。设有 int 数
组 a 和指针 p1,p2 ,下面代码都打印 a 的元素:

for (p1 = a; p1 < a+10; ++p1)
     printf("%dn", *p1);
for (p1 = a; p1 - a < 10; ++p1)
     printf("%dn", *p1);
for (p1 = a, p2 = a+10; p1 < p2; ++p1)
    printf("%dn", *p1);
for (p1 = p2 = a; p1 - p2 < 10; ++p1)
    printf("%dn", *p1);



                                         16
数组参数的意义
C 规定,数组参数就是相应类型的指针参数:
   int f(int n, int d[]) {... ...}
和  int f(int n, int *d) {... ...}
意义相同。

数组参数就是利用指针实现的!
这也使采用数组参数的函数能修改实参数组。




                                     17
函数里也可用指针方式做元素访问。
int intsum (int n, int a[])      m+=a[i];
{ int i, m = 0;
   for (i = 0; i < n; ++i) m += *(a+i);
   return m;
}

int intsum (int n, int *a)
{ int i, m = 0;
   for (i = 0; i < n; ++i) m += *(a+i);
   return m;
}

函数里不能用 sizeof 确定数组实参大小:函数的数组形
参实际是指针,求 sizeof 算出的是指针的大小。
另一方面, sizeof 的计算是在编译中完成的。实参是动
态运行中确定的东西。                   18
使用数组的一段元素
 以数组为参数的函数可处理一段元素。求元素和:
 double sum(int n, double a[]);
 设有双精度数组 b , 40 个元素已有值:

用 sum 可求 b 所有元素之和 / 前一段元素之和:
    x = sum(40, b);
    y = sum(20, b);
sum 不知道 b 的大小,它由参数得到数组首元素地址,从这
里开始求连续 40 或 20 个元素的和。
也可用 sum 求 b 中下标 12 到 24 的一段元素之和。
   z = sum(13, b+12);

                                   19
例 1 下面的程序的输出结果是什么 ?

#include <stdio.h>
int a[] = {2,4,7,8,9};
int main()
{ int i,*p = a;
   for (i = 0; i < 4; i++)
     a[i] = *(++p);
   printf("%dn", a[2]);
   return 0;
 }                        运行结果为: 8



                                     20
例 2 int a[] = {1,2,3,4,5,6,7,8,9,10}, *p = a, i;
        数组元素地址的正确表示:
( A ) &(a+1)        ( B ) a++         ( C ) &p     √( D ) &p[i]


             数组名是地址常量
             p++,p-- ()
             a++,a-- (×)
             a+1, *(a+2) ()




                                                            21
例 3 注意指针变量的运算

  例            int main()
      {       int a[] = {5,8,7,6,2,7,3};   a
                                           p   6   0
                                               5
              int y, *p = &a[1];           p
                                               8   1
              y = (*--p)++;
              printf(“%d ”, y);                7   2
              printf(“%d”, a[0]);              6   3
              return 0;                        2   4
          }                                    7   5
                       •*p++ ⇔*(p++)              3 6
                       •*p++ 、 *++p 、 (*p)++ 作用不同
  输出: 5 6                 *p++ 先取 p 指向变量的值再指针变量加 1
                          *++p 先使指针变量加 1 再取 *p
                          (*p)++ 使 p 指向的元素值加 1



                                                       22
例 4 注意指针的当前值
    int main()
    { int i, *p ,a[7];                a
                                      p
       p = a;                             5   0
                                      p
       for (i = 0; i < 7; i++)            8   1
                                      p
         scanf("%d", p++);                7   2
       printf("n");                  p
                                          6   3
       p = a;                         p
                                          2   4
       for (i = 0; i < 7; i++, p++)   p
                                          7   5
         printf("%d", *p);            p
       return 0;                          3   6
                                      p
    }
                 指针变量可以指到数组后的内存单元



                                                  23
要点:
1 、指针变量可以实现自身值的改变:
 如: p++;
 但数组名则不能进行改变: a++ 是错误的用法。
2 、应注意指针变量的当前值。
3 、指针变量可以指向数组以后的内存单元。
4 、注意以下的指针运算:
 若: int a[10],*p = a;
 则: p++ 等价于 &a[1]
    *p++ 等价于 *(p++)
    但 *(p++) 与 *(++p) 却不同
  ( *p ) ++ 等价于 a[0]++



                            24
例 1 利用指针,输出 int 数组里一段元素:
void prt_seq(int *begin, int *end)
{
    for (; begin != end; ++begin)    完备吗?
        printf("%dn", *begin);
}

prt_seq(a, a+10);
prt_seq(a+5, a+10);
prt_seq(a, a+3);
prt_seq(a+2, a+6);
prt_seq(a+4, a+4);
prt_seq(a+10, a+10);
最后两个调用对应空序列。


                                            25
“ 设置”函数:
void set_seq(int *b, int *e, int v)
{    for (; b != e; ++b) *b = v; }

把序列中每个元素都用其平方根取代:
void sqrt_seq (double *b, double *e)
{   for (; b!=e; ++b) *b = sqrt(*b);   }

求平均值:
double avrg(double *b, double *e)
{
   double *p, x = 0.0;
   if (b == e) return 0.0;
   for (p = b; p != e; ++p) x += *p;
   return x / (e - b);
}                                          26
数组名作函数参数
 数组名作函数参数,是地址传递
 数组名作函数参数,实参与形参的对应关系


     实参      形参
     数组名    数组名
     数组名    指针变量
     指针变量   数组名
     指针变量   指针变量




                       27
例 5 将数组 a 中的 n 个整数按相反顺序存放( 1 )

void inv(int x[], int n)
{ int t, i, j, m = (n-1)/2;
   for (i = 0; i <= m; i++)                              m=4
   {       j = n-1-i;
         t = x[i]; x[i] = x[j]; x[j] = t;   0 1 2 3 4 5 6 7 8 9
   }                                        3 4 5 11 0 0 11 95 7 2
                                            2 7 9 7 6 6 7      4 3
}
                                            i    i   i    i   i   j   j   j        j j
int main()
{ int i, a[10] = {3,7,9,11,0,6,7,5,4,2};
    inv(a, 10);
   printf("The array has been
      reverted:n");
   for (i = 0; i < 10; i++)
     printf("%d,", a[i]);                       实参与形参均用数组
   printf("n");
   return 0;
}
                                                                              28
例 5 将数组 a 中的 n 个整数按相反顺序存放( 2 )

void inv(int *x, int n)                     x i
                                                   a 数组
{ int t,*p,*i,*j, m = (n-1)/2;                       3 2 a[0]
                                              i
   i = x; j = x+n-1; p = x+m;                        7 4 a[1]
                                              i
                                                     9 5 a[2]
   for(;i <= p; i++, j--)                     i
                                                     11 7 a[3]
   { t = *i; *i = *j; *j = t; }           p=x+m
                                              i      0 6 a[4]
}                                             j
                                                     6 0 a[5]
int main()                                    j
                                                     7 11 a[6]
{ int i,a[10] = {3,7,9,11,0,6,7,5,4,2};       j
                                                     5 9 a[7]
                                              j
    inv(a, 10);                                      4 7 a[8]
                                               j
   printf("The array has been                        2 3 a[9]
          reverted:n");
   for (i = 0; i < 10; i++)
     printf("%d,", a[i]);               实参用数组 , 形参用指针变量
   printf("n");
   return 0;
}                                                         29
例 5 将数组 a 中的 n 个整数按相反顺序存放( 3 )
void inv(int *x, int n)
{ int t,*i,*j,*p,m=(n-1)/2;
  i=x; j=x+n-1; p=x+m;
  for(;i<=p;i++,j--)
  { t=*i; *i=*j; *j=t; }                      实参与形参均
}                                             用指针变量
int main()
{ int i, a[10], *p = a;
  for (i = 0; i < 10; i++, p++)
    scanf("%d", p);
  p=a;      inv(p, 10);
  printf("The array has been reverted:n");
  for (p = a; p < a+10; p++)
    printf("%d", *p);
  return 0;
}                                                  30
例 5 将数组 a 中的 n 个整数按相反顺序存放( 4 )
void inv(int x[], int n)
{ int t, i, j, m = (n-1)/2;
  for (i = 0;i <= m; i++)
  { j = n-1-i;
         t = x[i]; x[i] = x[j]; x[j] = t;
  }
}                                           实参用指针变量
int main()                                  形参用数组
{ int i, a[10], *p = a;
  for (i = 0; i < 10; i++, p++)
    scanf("%d", p);
  p = a;       inv(p, 10);
  printf("The array has been reverted:n");
  for (p = a; p < a+10; p++)
    printf("%d ", *p);
  return 0;
}                                                 31
例 6 分析程序写出结果
 #include <stdio.h>
 #define MAX 5
 void fun1();
 void fun2(int []);
 int a[MAX];

 int main()
 { fun1();
     fun2(a);
     printf("n");                                运行结果为: 0 4 8
     return 0;
   }
 void fun1()
 { int k, t = 0;
     for (k = 0; k < MAX; k++, t++) a[k] = t+t;
 }
 void fun2(int b[])
 { int k;
     for (k = 0; k < MAX; k += 2)
        printf("%4d", *(b+k));
 }                                                          32
例 7 分析程序写出结果
  #include <stdio.h>
  void fun(int *s)
  {
    static int j = 0;
    do{
        s[j] += s[j+1];
       } while(++j < 2);
  }
  int main()
  {int k, a[10] = {1,2,3,4,5};
                                 运行结果为: 3 5 7 4 5
    for (k = 1; k < 3; k++)
         fun(a);
    for (k = 0; k < 5; k++)
         printf(“%4d”, a[k]);
    return 0;
  }                                            33
一级指针变量与一维数组的关系

   int *p 与 int q[10]
   数组名是指针(地址)常量
   p = q; p+i 是 q[i] 的地址
   数组元素的表示方法 : 下标法和指针法,                  即
    若 p=q,            则   p[i] ⇔ q[i] ⇔ *(p+i)
    ⇔ *(q+i)
   形参数组实质上是指针变量,即 int q[ ] ⇔ int *q
   在定义指针变量(不是形参)时,不能把 int *p
    写成 int p[];
   系统只给 p 分配能保存一个指针值的内存区 ( 一
    般 4 字节);而给 q 分配 sizeof(int)*10 字节的内
    存区

                                            34
字符指针与字符数组
                 常用字符指针指向字
                   符数组元素
 定义字符指针时可用字符串常量初始化,如:
 char *p = "Programming";
 1 )定义了指针 p
 2 )建立了一个字符串常量,内容为 "Programming"
 3 )令 p 指向该字符串常量。图 (a)
 char a[] = "Programming";
 1 )定义了一个 12 个字符元素的数组
 2 )用 "Programming" 各字符初始化 a 的元素,图 (b)
指针 p                              数组 a

       p r o g r a m m i n g 0    p r o g r a m m i n g 0

                                                        35
           (a)                            (b)
指针 p                              数组 a

       p r o g r a m m i n g 0    p r o g r a m m i n g 0

           (a)                            (b)


1 )指针 p 可重新赋值(数组不能赋值 :a=“…” ):
   p = "Programming Language C";
2 ) p 和 a 类型不同,大小不同。 a 占 12 个字符的空间。
3 ) a 的元素可以重新赋值。如:
a[8]='e'; a[9]='r'; a[10]='0';
a 的内容现在变成“ Programmer”




                                                       36
// 字符串用字符数组实现                          string
                                                  I     string[0]
int main( )                                             string[1]
{ char string[] = " I love China! ";              l     string[2]
   printf(" %sn ", string);                      o     string[3]
   printf(" %sn ", string+7);                    v     string[4]
   return 0;                                      e     string[5]
                                                        string[6]
}
                                                  C     string[7]
// 字符串用字符指针实现                                     h     string[8]
int main( )                                       i     string[9]
{ char *string = "I love China!";                 n     string[10]
   printf("%sn", string);                        a     string[11]
   string += 7;                                   !     string[12]
   while (*string)                                0    string[13]
   {    putchar(string[0]);
        string++;                       I love China!
     }                                  China!
   return 0;
}                                                              37
例 1 分析以下程序的运行结果 



#include <stdio.h>
int main()
{
       char arr[] = "ABCDE";
       char *ptr;                         运行结果: ABCDE
       for(ptr = arr; ptr < arr+5; ptr++)          BCDE  
              printf("%sn", ptr);                 CDE
       return 0;                                   DE
 }                                             E



                                                    38
例 2 分析以下程序的运行结果 


#include <stdio.h>
int main()
{      char *p1 = "programming", *p2 = "language";
       int i;
       for (i = 0; i < 7; i++)
               if (*(p1+i) == *(p2+i))
                        printf("%c", *(p1+i));
       return 0;
}

 运行结果: ga


                                                     39
例 3 ,输入一行到数组里 , 并统计字符 e 的个数:

enum { NLINE = 256 };
char line[NLINE];
int count; char *p;
/*-----------------------------------------*/
p = line;
while(p–line<NLINE-1 && (*p = gerchar())!='n')
    ++p;
*p = '0'; /* 做成字符串 */

/*-----------------------------------------*/
/* 统计 e 的个数 */

for (count = 0, p = line; *p != '0'; ++p)
    if (*p == 'e') ++count;

                                                40
指针与数组操作函数实例
例 1 ,用指针方式实现字符串长度函数。一种方式:
int strLength (const char *s)
{
    int n = 0; /* 通过局部指针扫描串中字符 */
    while (*s != '0') { s++; n++; }
    return n;
}

参数类型 (char*) ,实参应是字符串或存字符串的数组
另一实现:
int strLength (const char *s)   指针使用非常灵活
{
    char *p = s;
    while (*p != '0') p++;
    return p - s;
}                                      41
例 2 ,用指针实现字符串复制函数。
void strCopy(char *s, const char *t)
{
    while ((*s = *t) != '0')
    {
        s++; t++;
    }
}
赋值表达式有值, '0' 就是 0 ,可简化:
void strCopy(char *s, const char *t)
{
    while(*s = *t){ s++; t++; }
}

把指针更新操作也写在循环测试条件里,
void strCopy (char *s, const char *t)
{
    while (*s++ = *t++) ; // 空语句
}                                       42
字符指针作函数参数                         a       a       b     b
                                  from     I       to    y     I
例   用函数调用实现字符串复制                                         o
( 1 )用字符数组作参数                              a             u    a
                                           m                  m
 void copy_string(char from[],char to[])
                                                         a
 { int i = 0;
    while (from[i] != '0')                 a            r     a
    { to[i] = from[i];                                   e
       i++;                                 t                  t
    }                                       e            a     e
    to[i] = '0';                           a                  a
 }                                          c            s     c
 int main()                                 h            t     h
 { char a[] = "I am a teacher.";            e            u     e
   char b[] = "You are a student.";
                                             r           d     r
   printf("string_a=%sn string_b=%sn", a, b);
   copy_string(a, b);                        .           e      .
                                           0
   printf("nstring_a=%snstring_b=%sn", a, b);         n    0
   return 0;                                             t     t
 }                                                        .     .
                                                                  43
                                                        0    0
字符指针作函数参数                           a       a     b     b
                                       from     I     to    y     I
例     用函数调用实现字符串复制                                          o
( 2 )用字符指针变量作参数                                 a           u    a
                                                m                m
    void copy_string(char *from, char *to)                  a
    { for (; *from != '0'; from++, to++)                   r     a
                                                a
       *to = *from;
                                                            e
      *to = '0';
                                                t                 t
    }
    int main()                                  e           a     e
    { char a[] = "I am a teacher.";             a                 a
      char b[] = "You are a student.";          c           s     c
                                                h
      printf("string_a=%snstring_b=%sn", a, b);           t     h
      copy_string(a, b);                        e           u     e
      printf("nstring_a=%snstring_b=%sn", a, b);
                                                 r          d     r
      return 0;                                  .          e      .
                             这里字符数
    }                                                       n    0
                             组直接替换             0
                             为指针变量                          t     t
                             会出错                             .     .
                                                                     44
                                                           0    0
例 3 分析以下程序的运行结果 

#include <stdio.h>
#include <string.h>
void fun(char *);
int main()
{
        char str[] = "abcdefghi";
        char *a = str;
        fun(a);
        puts(a);
        return 0;
}
void fun(char *s)
{                                               运行结果: ihgfedcba
        int x, y;
        char c;
        for (x = 0, y = strlen(s)-1; x < y; x++, y--)
        {c = s[y]; s[y] = s[x]; s[x] = c;}
}
                                                                  45
字符指针变量与字符数组
       char *cp; 与       char str[20];
       str 由若干元素组成,每个元素放一个字符;而 cp
        中存放字符串首地址
       char str[20]; str=“I love China!”; (×)
           char *cp;        cp=“I love China!”;     ()
       str 是地址常量; cp 是地址变量
       cp 接受键入字符串时 , 必须先开辟存储空间
例          char str[10];          改为 : char *cp, str[10];
        scanf(“%s”, str);   ()       cp = str;
                                      scanf(“%s”, cp);     ()
                                  或为: char *cp;
而          char *cp;
                                      cp = (char *)calloc(10, sizeof(char));
        scanf(“%s”, cp);    (×)
                                      scanf(“%s”, cp);     ()


                                                                        46
作业(要求用指针方式处理)

1.    输入 10 个整数,将其中最小的数与第一个数对换,
      把最大的数与最后一个数对换。要求写三个函数
     a.   输入 10 个数(用指针法)
     b.   进行处理(用指针操作)
     c.   输出 10 个数(用指针法)
2.    从键盘输入若干(如 10 )个数,写一个函数对这组
      数从第 begin (如 3 )个数到第 end (如 7 )个数
      之间的数进行排序,要求形参用指针,实参用指针
      或数组。
3.    有一个包含 n 个字符的字符串,写一个函数将这个
      字符串中从第 m 个字符开始的其余 n-m+1 个字符修
      改成为另一个特定的字符 c 。并在主程序中调用该
      函数进行测试。
          函数原型  void ChangStr(char *string, int n, int m, char
          c);                                                  47
4.    P261 页第 4 题
      Q & A!



               48

17第十七讲(第七章中)(2)

  • 1.
  • 2.
  • 3.
    第七章 指针  地址与指针  指针变量的定义和使用  指针与数组  指针数组  多维数组作为参数的通用函数  动态存储管理  定义类型  指向函数的指针 3
  • 4.
    要点回顾  如何定义一个指针变量?  指针变量在使用时为什么一定要指定指向的数据 类型?  指针变量在使用时为什么一定要初始化?  未经初始化的指针变量称为?  指针变量上的两种主要操作是什么?  变量的两种访问方式是什么?  指针变量做函数参数时传递的是什么?  通过使用指针变量做为函数参数可以返回多于一返回值  改变函数调用时的环境变量  空指针有什么用? 4
  • 5.
    第七章 指针  地址与指针  指针变量的定义和使用  指针与数组 指针(数值型 / 字符型)与一维数组  指针数组 (数值型数组 / 字符串)的关系  多维数组作为参数的通用函数  动态存储管理  定义类型  指向函数的指针 5
  • 6.
    7.3 指针与数组 C 指针与数组关系密切,以指针为媒介可以完 成各种数组操作 用指针做数组操作同样要特别注意越界错误。 指针和数组的关系是C 语言特有的,除了由 C 派生出的语言(如 C++ ),一般语言中没有这 种关系。 如何利用指针访问数组。 6
  • 7.
    指向数组元素的指针 int *p1, *p2,*p3, *p4; int a[10] = {1,2,3,4,5,6,7,8,9,10}; 这个地址存在,但 可以写: 写 *p4 是错误的。 p1 = &a[0]; p2 = p1; P4 没有指向合法元 p3 = &a[5]; p4 = &a[10]; 素 p1 = &a[0]; 可简写为: p1 = 区别 a; p1 p2 p3 ? p4 数组 a 图 7.5 指向数组 a 的元 素的 4 个指针 数组名是表示数组首地址的地址常量 7
  • 8.
    指针运算 当指针 p 指向数组元素时说p 指到了数组里。这时由 p 可以访问被 p 指的元素,还可访问数组的其他元素。 例: p1 = &a[0] , 则 p1+1 合法,为 a[1] 的地址 p1+2 、 p1+3 、…也合法,分别为 a[2] 、 a[3] 的地址 使用: *(p1 + 2) = 3;/* 给 a[2] 赋值 */ p2 = p1 + 5; /* 使 p2 指向 a[5] */ *(p2 + 2) = 5; /* 给 a[7] 赋值 */ *(p2 - 2) = 4; /* 给 a[3] 赋值 */ p2 = p2 - 2; /* 这使 p2 改指向 a[3] */ 通过指针访问数组元素时必须保证不越界。 8
  • 9.
    指针运算原理 一个指针指向某数组里的元素时,为什么能算出下一元素 位置?(这是指针运算的基础) 指针有指向类型, p 指向数组a 时,由于 p 的指向类型与 a 的元素类型一致,数据对象的大小可以确定。 p+1 的值可根据 p 的值和数组元素大小算出。由一个数组 元素位置可以算出下一元素位置,或几个元素之后的元素 位置。指针运算的基础。 通用指针即使指到数组里,因没有确定指向类型,因此不 能做一般指针计算,只能做指针比较。 9
  • 10.
    其他常用指针运算: 可进行增 / 减量操作(指针应指在数组里): p3 = p2; ++p3; --p2; p3 += 2; 如果两指针指在同一个数组里,可以求差,得到它们间的 数组 元素个数(带符号整数)。 n = p3 – p2; /* 也可以求 p2 – p3 */ 在同一个数组里的指针可以比较大小:      if (p3 > p2) .... 当 p3 所指的元素在 p2 所指的元素之后时条件成立(值为 1 ),否则不成立(值为 0 )。两个指针不指在同一数组里 时,比较大小没有意义。 10
  • 11.
    两个同类型指针可用 == 和!= 比较相等或不等; 任何指针都能与通用指针比较相等或不等; 任何指针可与空指针值( 0 或 NULL )比较相等或不等 两指针指向同一数据元素,或同为空值时它们相等 。 当一个指针指向数组时的数组写法与指针写法: 指针的两种用法 设 p1 = &a[0] , p3 = &a[5] p1 和 p3 相当于数组 名 可写: p1[3] = 5; 相当于 * ( p1+3)=5 ;或 a[3]=5 ; p3[2] = 8; 相当于 * ( p3+2)=8 ;或 a[7]=8 ; p1[3] 称为数组写法, *(p1+3) 称为指针写法 11
  • 12.
    [] 变址运算符 a[i] ⇔ *(a+i) 地址 元素 地址 元素 a a[0] a[0] *a p a[0] *p p[0] a+1 a[1] a[1] *(a+1) p+1 a[1] *(p+1) p[1] a+2 a[2] a[2] *(a+2) p+2 a[2] *(p+2) p[2] a[3] a[3] ... ... a+9 a[9] a[9] *(a+9) p+9 a[9] *(p+9) p[9] 下标法 指针法 a[i] ⇔ p[i] ⇔ *(p+i) ⇔*(a+i) 12
  • 13.
    对数组名求值得到指向数组首元素的指针值 数组名可以“看作”常量指针,可参与一些指针运算, 与其他指针比大小,比较相等与不相等。 通过数组名的元素访问也可以采用指针写法。 a[3] 可写为*(a+3) 。 注意:数组名不是指针变量,特别是不能赋值,不能更 改。 若 a 为数组,下面操作都是错误的: a++; a += 3; a = p; 有些运算虽不赋值但也可能没意义。如 a–3 不可能得 到合法指针值,因其结果超出数组界限 13
  • 14.
    数组元素的引用方法( 1 ) int main()// 下标法和地址法 { int a[5], *pa, i; for (i = 0; i < 5; i++) a[i] = i + 1; pa pa = a; a[0] 1 for (i = 0; i < 5; i++) a[1] 2 printf("*(pa+%d):%dn", i, *(pa+i)); a[2] 3 // 通过指针找地址法 a[3] 4 for (i = 0; i < 5; i++) printf("*(a+%d):%dn", i, *(a+i)); a[4] 5 // 通过指针找地址法 for(i = 0; i < 5; i++) printf("pa[%d]:%dn", i, pa[i]); // 下标法 for (i = 0; i < 5; i++) printf("a[%d]:%dn", i, a[i]); // 下标法 return 0; } 14
  • 15.
    数组元素的引用方法( 2 ) // 指针法 int main() 三种方法的比较: {  下标法和地址法的执行效率相同 int a[10]; ,都是先计算数组元素的地址, int *p, i; 再访问数组元素的值,费时。 for (i = 0; i < 10; i++)  指针法利用指针变量直接访问数 scanf(“%d”, &a[i]); 组元素的值,不必计算数组元素 printf(“n”); 的地址,执行效率高。 for (p = a; p < a+10; p++)  下标法直观,指针法与地址法不 printf(“%d,”, *p); 够直观。 return 0; } 15
  • 16.
    基于指针的数组程序设计 指针运算是处理数组元素的一种有效方式。设有 int 数 组a 和指针 p1,p2 ,下面代码都打印 a 的元素: for (p1 = a; p1 < a+10; ++p1) printf("%dn", *p1); for (p1 = a; p1 - a < 10; ++p1) printf("%dn", *p1); for (p1 = a, p2 = a+10; p1 < p2; ++p1) printf("%dn", *p1); for (p1 = p2 = a; p1 - p2 < 10; ++p1) printf("%dn", *p1); 16
  • 17.
    数组参数的意义 C 规定,数组参数就是相应类型的指针参数:    intf(int n, int d[]) {... ...} 和  int f(int n, int *d) {... ...} 意义相同。 数组参数就是利用指针实现的! 这也使采用数组参数的函数能修改实参数组。 17
  • 18.
    函数里也可用指针方式做元素访问。 int intsum (intn, int a[]) m+=a[i]; { int i, m = 0; for (i = 0; i < n; ++i) m += *(a+i); return m; } int intsum (int n, int *a) { int i, m = 0; for (i = 0; i < n; ++i) m += *(a+i); return m; } 函数里不能用 sizeof 确定数组实参大小:函数的数组形 参实际是指针,求 sizeof 算出的是指针的大小。 另一方面, sizeof 的计算是在编译中完成的。实参是动 态运行中确定的东西。 18
  • 19.
    使用数组的一段元素 以数组为参数的函数可处理一段元素。求元素和: doublesum(int n, double a[]); 设有双精度数组 b , 40 个元素已有值: 用 sum 可求 b 所有元素之和 / 前一段元素之和: x = sum(40, b); y = sum(20, b); sum 不知道 b 的大小,它由参数得到数组首元素地址,从这 里开始求连续 40 或 20 个元素的和。 也可用 sum 求 b 中下标 12 到 24 的一段元素之和。 z = sum(13, b+12); 19
  • 20.
    例 1 下面的程序的输出结果是什么? #include <stdio.h> int a[] = {2,4,7,8,9}; int main() { int i,*p = a; for (i = 0; i < 4; i++) a[i] = *(++p); printf("%dn", a[2]); return 0; } 运行结果为: 8 20
  • 21.
    例 2 inta[] = {1,2,3,4,5,6,7,8,9,10}, *p = a, i; 数组元素地址的正确表示: ( A ) &(a+1) ( B ) a++ ( C ) &p √( D ) &p[i] 数组名是地址常量 p++,p-- () a++,a-- (×) a+1, *(a+2) () 21
  • 22.
    例 3 注意指针变量的运算 例 int main() { int a[] = {5,8,7,6,2,7,3}; a p 6 0 5 int y, *p = &a[1]; p 8 1 y = (*--p)++; printf(“%d ”, y); 7 2 printf(“%d”, a[0]); 6 3 return 0; 2 4 } 7 5 •*p++ ⇔*(p++) 3 6 •*p++ 、 *++p 、 (*p)++ 作用不同 输出: 5 6    *p++ 先取 p 指向变量的值再指针变量加 1    *++p 先使指针变量加 1 再取 *p    (*p)++ 使 p 指向的元素值加 1 22
  • 23.
    例 4 注意指针的当前值 int main() { int i, *p ,a[7]; a p p = a; 5 0 p for (i = 0; i < 7; i++) 8 1 p scanf("%d", p++); 7 2 printf("n"); p 6 3 p = a; p 2 4 for (i = 0; i < 7; i++, p++) p 7 5 printf("%d", *p); p return 0; 3 6 p } 指针变量可以指到数组后的内存单元 23
  • 24.
    要点: 1 、指针变量可以实现自身值的改变: 如:p++; 但数组名则不能进行改变: a++ 是错误的用法。 2 、应注意指针变量的当前值。 3 、指针变量可以指向数组以后的内存单元。 4 、注意以下的指针运算: 若: int a[10],*p = a; 则: p++ 等价于 &a[1] *p++ 等价于 *(p++) 但 *(p++) 与 *(++p) 却不同 ( *p ) ++ 等价于 a[0]++ 24
  • 25.
    例 1 利用指针,输出int 数组里一段元素: void prt_seq(int *begin, int *end) { for (; begin != end; ++begin) 完备吗? printf("%dn", *begin); } prt_seq(a, a+10); prt_seq(a+5, a+10); prt_seq(a, a+3); prt_seq(a+2, a+6); prt_seq(a+4, a+4); prt_seq(a+10, a+10); 最后两个调用对应空序列。 25
  • 26.
    “ 设置”函数: void set_seq(int*b, int *e, int v) { for (; b != e; ++b) *b = v; } 把序列中每个元素都用其平方根取代: void sqrt_seq (double *b, double *e) { for (; b!=e; ++b) *b = sqrt(*b); } 求平均值: double avrg(double *b, double *e) { double *p, x = 0.0; if (b == e) return 0.0; for (p = b; p != e; ++p) x += *p; return x / (e - b); } 26
  • 27.
    数组名作函数参数 数组名作函数参数,是地址传递 数组名作函数参数,实参与形参的对应关系 实参 形参 数组名 数组名 数组名 指针变量 指针变量 数组名 指针变量 指针变量 27
  • 28.
    例 5 将数组a 中的 n 个整数按相反顺序存放( 1 ) void inv(int x[], int n) { int t, i, j, m = (n-1)/2; for (i = 0; i <= m; i++) m=4 { j = n-1-i; t = x[i]; x[i] = x[j]; x[j] = t; 0 1 2 3 4 5 6 7 8 9 } 3 4 5 11 0 0 11 95 7 2 2 7 9 7 6 6 7 4 3 } i i i i i j j j j j int main() { int i, a[10] = {3,7,9,11,0,6,7,5,4,2}; inv(a, 10); printf("The array has been reverted:n"); for (i = 0; i < 10; i++) printf("%d,", a[i]); 实参与形参均用数组 printf("n"); return 0; } 28
  • 29.
    例 5 将数组a 中的 n 个整数按相反顺序存放( 2 ) void inv(int *x, int n) x i a 数组 { int t,*p,*i,*j, m = (n-1)/2; 3 2 a[0] i i = x; j = x+n-1; p = x+m; 7 4 a[1] i 9 5 a[2] for(;i <= p; i++, j--) i 11 7 a[3] { t = *i; *i = *j; *j = t; } p=x+m i 0 6 a[4] } j 6 0 a[5] int main() j 7 11 a[6] { int i,a[10] = {3,7,9,11,0,6,7,5,4,2}; j 5 9 a[7] j inv(a, 10); 4 7 a[8] j printf("The array has been 2 3 a[9] reverted:n"); for (i = 0; i < 10; i++) printf("%d,", a[i]); 实参用数组 , 形参用指针变量 printf("n"); return 0; } 29
  • 30.
    例 5 将数组a 中的 n 个整数按相反顺序存放( 3 ) void inv(int *x, int n) { int t,*i,*j,*p,m=(n-1)/2; i=x; j=x+n-1; p=x+m; for(;i<=p;i++,j--) { t=*i; *i=*j; *j=t; } 实参与形参均 } 用指针变量 int main() { int i, a[10], *p = a; for (i = 0; i < 10; i++, p++) scanf("%d", p); p=a; inv(p, 10); printf("The array has been reverted:n"); for (p = a; p < a+10; p++) printf("%d", *p); return 0; } 30
  • 31.
    例 5 将数组a 中的 n 个整数按相反顺序存放( 4 ) void inv(int x[], int n) { int t, i, j, m = (n-1)/2; for (i = 0;i <= m; i++) { j = n-1-i; t = x[i]; x[i] = x[j]; x[j] = t; } } 实参用指针变量 int main() 形参用数组 { int i, a[10], *p = a; for (i = 0; i < 10; i++, p++) scanf("%d", p); p = a; inv(p, 10); printf("The array has been reverted:n"); for (p = a; p < a+10; p++) printf("%d ", *p); return 0; } 31
  • 32.
    例 6 分析程序写出结果 #include <stdio.h> #define MAX 5 void fun1(); void fun2(int []); int a[MAX]; int main() { fun1(); fun2(a); printf("n"); 运行结果为: 0 4 8 return 0; } void fun1() { int k, t = 0; for (k = 0; k < MAX; k++, t++) a[k] = t+t; } void fun2(int b[]) { int k; for (k = 0; k < MAX; k += 2) printf("%4d", *(b+k)); } 32
  • 33.
    例 7 分析程序写出结果 #include <stdio.h> void fun(int *s) { static int j = 0; do{ s[j] += s[j+1]; } while(++j < 2); } int main() {int k, a[10] = {1,2,3,4,5}; 运行结果为: 3 5 7 4 5 for (k = 1; k < 3; k++) fun(a); for (k = 0; k < 5; k++) printf(“%4d”, a[k]); return 0; } 33
  • 34.
    一级指针变量与一维数组的关系 int *p 与 int q[10]  数组名是指针(地址)常量  p = q; p+i 是 q[i] 的地址  数组元素的表示方法 : 下标法和指针法, 即 若 p=q, 则 p[i] ⇔ q[i] ⇔ *(p+i) ⇔ *(q+i)  形参数组实质上是指针变量,即 int q[ ] ⇔ int *q  在定义指针变量(不是形参)时,不能把 int *p 写成 int p[];  系统只给 p 分配能保存一个指针值的内存区 ( 一 般 4 字节);而给 q 分配 sizeof(int)*10 字节的内 存区 34
  • 35.
    字符指针与字符数组 常用字符指针指向字 符数组元素 定义字符指针时可用字符串常量初始化,如: char *p = "Programming"; 1 )定义了指针 p 2 )建立了一个字符串常量,内容为 "Programming" 3 )令 p 指向该字符串常量。图 (a) char a[] = "Programming"; 1 )定义了一个 12 个字符元素的数组 2 )用 "Programming" 各字符初始化 a 的元素,图 (b) 指针 p 数组 a p r o g r a m m i n g 0 p r o g r a m m i n g 0 35 (a) (b)
  • 36.
    指针 p 数组 a p r o g r a m m i n g 0 p r o g r a m m i n g 0 (a) (b) 1 )指针 p 可重新赋值(数组不能赋值 :a=“…” ):    p = "Programming Language C"; 2 ) p 和 a 类型不同,大小不同。 a 占 12 个字符的空间。 3 ) a 的元素可以重新赋值。如: a[8]='e'; a[9]='r'; a[10]='0'; a 的内容现在变成“ Programmer” 36
  • 37.
    // 字符串用字符数组实现 string I string[0] int main( ) string[1] { char string[] = " I love China! "; l string[2] printf(" %sn ", string); o string[3] printf(" %sn ", string+7); v string[4] return 0; e string[5] string[6] } C string[7] // 字符串用字符指针实现 h string[8] int main( ) i string[9] { char *string = "I love China!"; n string[10] printf("%sn", string); a string[11] string += 7; ! string[12] while (*string) 0 string[13] { putchar(string[0]); string++; I love China! } China! return 0; } 37
  • 38.
    例 1 分析以下程序的运行结果  #include<stdio.h> int main() { char arr[] = "ABCDE"; char *ptr; 运行结果: ABCDE for(ptr = arr; ptr < arr+5; ptr++)   BCDE   printf("%sn", ptr);   CDE return 0;   DE } E 38
  • 39.
    例 2 分析以下程序的运行结果  #include<stdio.h> int main() { char *p1 = "programming", *p2 = "language"; int i; for (i = 0; i < 7; i++) if (*(p1+i) == *(p2+i)) printf("%c", *(p1+i)); return 0; } 运行结果: ga 39
  • 40.
    例 3 ,输入一行到数组里, 并统计字符 e 的个数: enum { NLINE = 256 }; char line[NLINE]; int count; char *p; /*-----------------------------------------*/ p = line; while(p–line<NLINE-1 && (*p = gerchar())!='n') ++p; *p = '0'; /* 做成字符串 */ /*-----------------------------------------*/ /* 统计 e 的个数 */ for (count = 0, p = line; *p != '0'; ++p) if (*p == 'e') ++count; 40
  • 41.
    指针与数组操作函数实例 例 1 ,用指针方式实现字符串长度函数。一种方式: intstrLength (const char *s) { int n = 0; /* 通过局部指针扫描串中字符 */ while (*s != '0') { s++; n++; } return n; } 参数类型 (char*) ,实参应是字符串或存字符串的数组 另一实现: int strLength (const char *s) 指针使用非常灵活 { char *p = s; while (*p != '0') p++; return p - s; } 41
  • 42.
    例 2 ,用指针实现字符串复制函数。 voidstrCopy(char *s, const char *t) { while ((*s = *t) != '0') { s++; t++; } } 赋值表达式有值, '0' 就是 0 ,可简化: void strCopy(char *s, const char *t) { while(*s = *t){ s++; t++; } } 把指针更新操作也写在循环测试条件里, void strCopy (char *s, const char *t) { while (*s++ = *t++) ; // 空语句 } 42
  • 43.
    字符指针作函数参数 a a b b from I to y I 例 用函数调用实现字符串复制 o ( 1 )用字符数组作参数 a u a m m void copy_string(char from[],char to[]) a { int i = 0; while (from[i] != '0') a r a { to[i] = from[i]; e i++; t t } e a e to[i] = '0'; a a } c s c int main() h t h { char a[] = "I am a teacher."; e u e char b[] = "You are a student."; r d r printf("string_a=%sn string_b=%sn", a, b); copy_string(a, b); . e . 0 printf("nstring_a=%snstring_b=%sn", a, b); n 0 return 0; t t } . . 43 0 0
  • 44.
    字符指针作函数参数 a a b b from I to y I 例 用函数调用实现字符串复制 o ( 2 )用字符指针变量作参数 a u a m m void copy_string(char *from, char *to) a { for (; *from != '0'; from++, to++) r a a *to = *from; e *to = '0'; t t } int main() e a e { char a[] = "I am a teacher."; a a char b[] = "You are a student."; c s c h printf("string_a=%snstring_b=%sn", a, b); t h copy_string(a, b); e u e printf("nstring_a=%snstring_b=%sn", a, b); r d r return 0; . e . 这里字符数 } n 0 组直接替换 0 为指针变量 t t 会出错 . . 44 0 0
  • 45.
    例 3 分析以下程序的运行结果  #include<stdio.h> #include <string.h> void fun(char *); int main() { char str[] = "abcdefghi"; char *a = str; fun(a); puts(a); return 0; } void fun(char *s) { 运行结果: ihgfedcba int x, y; char c; for (x = 0, y = strlen(s)-1; x < y; x++, y--) {c = s[y]; s[y] = s[x]; s[x] = c;} } 45
  • 46.
    字符指针变量与字符数组  char *cp; 与 char str[20];  str 由若干元素组成,每个元素放一个字符;而 cp 中存放字符串首地址  char str[20]; str=“I love China!”; (×) char *cp; cp=“I love China!”; ()  str 是地址常量; cp 是地址变量  cp 接受键入字符串时 , 必须先开辟存储空间 例 char str[10]; 改为 : char *cp, str[10]; scanf(“%s”, str); () cp = str; scanf(“%s”, cp); () 或为: char *cp; 而 char *cp; cp = (char *)calloc(10, sizeof(char)); scanf(“%s”, cp); (×) scanf(“%s”, cp); () 46
  • 47.
    作业(要求用指针方式处理) 1. 输入 10 个整数,将其中最小的数与第一个数对换, 把最大的数与最后一个数对换。要求写三个函数 a. 输入 10 个数(用指针法) b. 进行处理(用指针操作) c. 输出 10 个数(用指针法) 2. 从键盘输入若干(如 10 )个数,写一个函数对这组 数从第 begin (如 3 )个数到第 end (如 7 )个数 之间的数进行排序,要求形参用指针,实参用指针 或数组。 3. 有一个包含 n 个字符的字符串,写一个函数将这个 字符串中从第 m 个字符开始的其余 n-m+1 个字符修 改成为另一个特定的字符 c 。并在主程序中调用该 函数进行测试。 函数原型  void ChangStr(char *string, int n, int m, char c); 47 4. P261 页第 4 题
  • 48.
        Q & A! 48