第十章   
课题:
第十章 指针  §1-§2
教学目的:
1、了解指针与地址的概念
2、掌握指针变量的定义、初始化及指针的运算
教学重点:
教学难点:
指针变量的定义、初始化及指针的运算
指针的运算
步骤一 复习引导
    指针是C语言的一个重要概念,也是C语言的一个重要特。正确而灵活地运用它,可以有效地表示复杂的数据结构;能动态分配内存;能方便地使用字符串;有效而方便地使用数组;在调用函数时能得到多于1个的值;能直接处理内存地址等,这对设计系统软件是很必要的。
    指针的概念比较复杂,使用也比较灵活,因此初学者时常会出错,务请在学习本章内容时十分小心。
 
步骤二 讲授新课
§10.1 地址和指针的概念
计算机的主存储器被分成一个个存储单元,为了区分各存储单元,要为每个存储单元编号,这个编号即地址。
    例:i =3;          或 scanf(“%d”, &i);
      是将3送给 i所在的空间。
  例:将3送到变量I_pointer所“指向”的单元(即I所标志的单元)。
      所谓“指向”,是通过地址来体现的,I_pointer中的值为2000,它是变量I 的地址,这样就在I_pointer和变量之间建立起一种联系,即通过I_pointer能知道I的地址,从而到变量I的内存单元。因而在C语言中,将地址形象化地称为“指针”。
    意思是通过它能到以它为地址的内存单元。一个变量的地址称为该变量的“指针”。
    内存单元的地址内存单元的内容是两个不同的概念。
    指针:就是地址,即内存单元的编号。
    指针变量:用来存放另一变量的地址(即指针)的变量。
  如:地址2000是变量 i的指针;i_pointer是指针变量,其值就是指针2000。
 
§10.2变量的指针和指向变量的指针变量
    变量的指针就是变量的地址。
    存放变量地址的变量是指针变量,用来指向另一个变量。
      *i_pointer 表示 i_pointer 所指向的变量。
 
一、定义一个指针变量
指针变量的定义包含三个方面的内容:
⑴ 指针类型说明,即定义变量为一个指针变量
⑵ 指针变量名
⑶ 变量值(指针)所指向的变量的数据类型。
格式:  存储类型  基类型  *指针变量名;
例:int  *pointer_1, *pointer_2;
    float  *pointer_3;
    char  *pointer_4;
二、指针的引用
指针变量有两个运算符:
& :取地址运算符
        功能:取变量地址;单目,右结合。
    * :取内容运算符(“间接访问”运算符)
        功能:只能跟地址,取变量所指向单元的内容;单目,右结合。
  例:&a为变量a的地址,
      *p 为指针变量p所指向的存储单元。
  例:int  a=5, *p=&a;
        printf ( “%d”, *p );
main()
{ int  a,b;
  int  *pointer_1,*pointer_2;
  a=100;b=10;
  pointer_1=&a;            /*把变量a的地址赋给pointer_1*/
  pointer_2=&b;            /*把变量b的地址赋给pointer_2*/
  printf(“%d,%d\n”,a,b);
  printf(“%d,%d\n”,*pointer_1, *pointer_2);
}
输出结果:100, 10
          100, 10
 
评注:
  1、在第3行虽然定义了两个指针变量,只是提供了两个指针变量,但并未指向任何一个整型变量。称为指针“悬空”。
  2、最后一行的*pointer_1和pointer_2就是变量a和b。
  3、程序中两处出现*pointer_1等,含义不同。程序第3行中的*pointer_1表示定义指针变量pointer_1。它前面的*只是表示该变量是指针变量。程序最后一行中的*pointer_1则代表变量,即pointer_1所指向的变量。
  4、第5行中的pointer_1=&a 是将a的地址赋给指针变量pointer_1,而不是*pointer_1。
      注意:不应写成:*pointer_1=&a;
  5、从上例中可看出,*pointer_1等价于a,*pointer_2等价于b,故凡在程序中出现a的地方均可用 *pointer_1 代替。所以,若有: int  x, y, *px=&x ;
则下面的运算均是正确的: y=*px+1;    /*把 x 的内容加1 送变量y*/
printf(“%d\n”, px );    /*打印当前x  的内容*/
d=sqrt((double) px);  /*把x的平方根送变量d*/
px=0;              /*把 x 置为0*/
*px+=1;        /*把 x 的值加 1*/
(*px)++;        /*使 x 的值加 1*/
y=(*px)++;      /*即y=x, x++*/
6、假设px和py都被定义为指向int 对象的指针,而且px当前已指向了int 型变量x,则执行:
        py=px;      /*把指针px的内容拷贝到指针py中去*/
即  py和px 这两个不同的指针指向了同一对象x
7、指针不是整数,不能将它与整数混为一谈。
例:
# include <stdio.h>
main()
{ int  x=100,*p;
p=x;
printf(“%d”, p );
}
例如:
# include <stdio.h>
main()
{ int  a, b, *d=&a;
    b = d;
    printf( “%d \n”, b );
  ……
}——编译不出错,但得不到希望的值
 
关于&*运算符的说明:
  假设已执行    pointer_1=&a;
  1、&*pointer_1含义是什么?
      &*pointer_1与&a相同,即变量a的地址。
  2、*&a的含义是什么?
      先进行&a运算,得a的地址,再进行*运算。
      *&a、*pointer_1及变量a等价。
  3、(*pointer_1) + + 相当于a + +。
      它与*pointer_1 + + 不同。
  4、*pointer_1 + + 等价于*(pointer_1 + +),即先进行*运算,得到a的值,然后使pointer_1的值改变,这样pointer_1不再指向a了。
例10.2:输入a和b两个整数,按先大后小的顺序输出a和b。
main()
{ int  *p1,*p2,*p, a, b;
  scanf(“%d,%d”,&a,&b);
  p1=&a; p2=&b;
  if (a<b)
      {p=p1; p1=p2; p2=p;}
  printf(“\n a=%d,b=%d\n”,a,b);
  printf(“max=%d,min=%d\n”,*p1,*p2);
}
运行情况:
5,9<Enter>
a=5,b=9
max=9,min=5
 
三、指针变量作为函数参数
例10.3对输入的两个整数按大小顺序输出。
先考察如下程序,看是否能得到预期的结果
  swap(int  p1, int  p2)
{ int  temp;
temp = p1;
p1 = p2;
p2 =temp;
}
main()
{ int  a, b;
  scanf(“%d, %d”, &a, &b);
  if(a<b) swap(a, b);
  printf(“\n%d,%d\n”,a,b);
}
不能得到预期的结果。
改为:
swap(int *p1,int *p2)
{ int  temp;
  temp = *p1;
  *p1 = *p2;
  *p2 =temp;
}
main()
{ int  a,b;
  int  *pointer_1,*pointer_2;
  scanf(“%d,%d”,&a,&b);
  pointer_1=&a;
  pointer_2=&b;
  if(a<b)    swap(pointer_1,pointer_2);
  printf(“\n%d,%d\n”,a,b);
}
 
注:如果想通过函数调用得到n个改变的值,可以:
1、在主调函数中设n 个变量,用n个指针变量指向它们;
2、然后将指针变量作实参,将这n 个变量的地址传给所调用的函数的形参;
3、通过形参指针变量,改变该n个变量的值;
4、主调函数中就可以使用这些改变了值的变量。
四、指针(变量)的初始化
指针置初始化值的格式:
存储类型  基类型  *指针名=初始化值;
如:main()
        { static  int  a;
          int  *p=&a, *p1=p;
          …… }
再如: int  *p = 0;
      或  int  *p = NULL;
 五、指针的运算
1、指针的算术运算
指针仅能进行加、减算术运算
如:p+n ,  p-n ,  p++ ,  p-- ,  ++p ,  --p
        p-= n , p+= n , p1-p2
其中n是整数,p、p1、p2均为指针;
施行加法运算时,指针向地址增大的方向移动;
施行减法运算时,指针向地址减小的方向移动;
移动长度取决于指针的基类型,由计算机决定;
如有:int  a,b,c, *pt =&a;
    则  pt++ 则指针向后移动两个字节;
再如:main()
            { int *p1,a=8,b=3;
              p1=&a;
              printf(“%d,%d\n”, (*p1)++, *p1++);
              printf(“%d,%d\n”,a, *p1);
            }
运行结果:3 , 8
          8 , 4
注:p1+k = p1+k*sizeof(p1的基类型);
        p1- k = p1- k*sizeof(p1的基类型);
如:
  strlen(char  *s)
  {  char  *p=s;
      while(*p!=‘\0’)p++;
      return(p-s);
  }
 2、指针的关系运算
设指针p1,p2指向同一数组中的元素,则
    p1<p2为真:表示p1在p2的前面;
    p1<p2为假:表示p2在p1的前面;
    p1= =p2:      表示p1、p2指向数组中的同一元素;
    同理可推出>、>=、<=、!=比较的意义;
不可将指针与其他类型的对象作比较;
若两指针指向不同数组中的元素,也不可比较;
允许将指针与NULL或数值0进行= =或!=的比较,以便判定一个指针是否为空指针。
 步骤三 课堂小结
本课介绍了指针与地址的概念,指针变量的定义、初始化及指针的运算。
    指针:就是地址,即内存单元的编号。
    指针变量:用来存放另一变量的地址(即指针)的变量。
    例如:int  a=5, *p=&a;
          printf ( “%d”, *p );
注意:运算符*和&的用法,指针变量的自加自减运算。
步骤四 布置作业
    课后作业:第十章课后练习 10.1 10.2
课题:
第十章 指针  §3
教学目的:
掌握指针与数组的知识
教学重点:
教学难点:
指向数组的指针变量
指向二维数组的指针
步骤一 复习引导
上节课介绍了指针变量的定义及其赋值。一个变量有地址,一个数组包含若干元素,每个数组元素都在内存中占用存储单元,它们都有相应的地址。指针变量既然可以指向变量,当然也可以指向数组和数组元素(把数组起始地址或某一元素的地址放到一个指针变量中)。
 
步骤二 讲授新课
所谓数组的指针是指数组的起始地址,数组元素的指针是数组元素的地址。
    引用数组元素可以用下标法(如a[3]),也可以用指针法,即通过指向数组元素的指针到所需的元素。使用指针法能使目标程序质量高(占内存少,运行速度快)。
一、指向一维数组的指针
  定义形式:
    int  a[10];
    int  *p;
    p=&a[0];    或  p=a;     
  含义:把数组的首地址赋给指针变量p。
  也即: int  *p=&a[0];          或  int *p=a;
 
二、通过指针引用数组元素
    按C的规定:如果指针变量p已指向数组中的一个元素,则p+1指向同一个数组中的下一个元素(而不是简单地加1)。
    如果p的初值为&a[0],则:
          p+i a+i &a[i],即指向a数组的第i个元素。
          *(p+i) *(a+i)   a[i]
    指向数组的指针变量也可以带下标,如p[i]与*(p+i)等价
引用数组元素时,可以用:
    1、下标法,如:a[i]
    2、指针法,如:*( a+i )    或  *( p+i )
      其中,a是数组名,p是指向数组的指针
 
105:输出数组中的全部元素
  假设有一个a数组,整型,有10个元素。用三种方法输出各元素的值:
1、下标法:
main()
{ int  a[10], i ;
  for( i=0; i<10 ; i++)
      scanf(“%d”,&a[i]);
    printf(“\n”);
    for( i=0 ; i<10; i++)
        printf(“%d”,a[i]);
}
2、利用数组名计算数组元素地址,出元素的值。
main()
{ int  a[10], i ;
  for( i=0; i<10 ; i++)
      scanf(“%d”,&a[i]);
    printf(“\n”);
    for( i=0 ; i<10; i++)
        printf(“%d”,*(a+i));
}
3、用指针变量指向数组元素。
main()
{ int  a[10], *p,  i ;
  for( i=0; i<10 ; i++)
      scanf(“%d”,&a[i]);
  printf(“\n”);
  for( p=a; p<(a+10); p++)
        printf(“%d”,*p );
}
评注:
1、第1和2种方法执行效率是相同的。
2、第3种方法效率高。
3、用下标法比较直观。
 
在使用指针变量时,有几个问题要注意:
1、 指针变量可以实现使本身的值改变。 如:for(p=a; p<(a+10); p++)
 
2、要注意指针变量的当前值
如:要通过指针变量输出a数组的10个元素。
main()
{ int  a[10], i , *p=a;
  for( i=0; i<10 ; i++)
      scanf(“%d”, p++);
    printf(“\n”);
    for( i=0 ; i<10; i++,p++)
        printf(“%d”, *p);
}
这个程序输出的并不是a数组中各元素的值。因为第一个 for 循环结束时,p已经指向数组的末尾。再继续下去,p指向的是a数组下面的10个元素,是不可预料的。
可加一句为:p=a;
3、用指针变量p指向数组元素,可以指到数组以后的内存单元。C编译程序不作下标越界检查。
 
4、注意指针变量的运算。如果p指向数组a,
p++ ( p+=1 )使p指向下一元素a[1]。
*p++  等价 *(p++)。作用是先得到p指向的变量的值(即*p),然后再使p+1→p。
*(p++)*(++p) 不同。前者为a[0],后者为a[1]
(*p)++表示p指向的元素值加1,即(a[0])++
⑸ 如果p当前指向a数组中第i个元素,则:
    *(p- -)相当于a[i- -]先对p进行*运算,再使p自减;
    *(+ + p)相当于a[+ +i]先使p自加,再作*运算。
    *(- - p)相当于a[- -i]先使p自减,再作*运算。
 
三、数组名作函数参数
    用数组名作实参,在调用函数时是把数组的首地址传送给形参。即实参数组与形参数组共占同一段内存。
   
例10.7  将数组a中的n个整数按相反顺序存放。
分析:将a[0]与a[n-1]对换,再将a[1]和a[n-2]对换……,直到将a[(n-1)/2]与a[n-int((n-1)/2]对换。设i和j,i的初值为0,j的初值为n-1。将a[i]和a[j]交换,然后使i的值加1,j的值减1,再将a[i]和a[j]交换,直到 i=(n-1)/2为止。
void  inv(int  x[], int n)
{ int temp, i, j,m=(n-1)/2;
  for( i=0; i<=m; i++)
      { j=n-1-i  ;
          temp=x[i];
          x[i]=x[j];
          x[j]=temp;
      }
    return;
}
main()
{ int i, a[10]={3,7,9,11, 0,6,7,5,4,2};
  printf(“The array: \n ”);
  for( i=0; i<10; i++)  printf(“%d”,a[i]);
  printf(“\n”);
  inv(a,10);
  printf(“The array : \n”);
  for( i=0; i<10; i++)  printf(“%d”,a[i]);
  printf(“\n”);
  } 
例10.8 10个数中出其中最大值和最小值。
int  max,min;
void max_min_vlue(int  array[], int n)
{ int *p,*array_end;
  array_end=array+n;
  max=min=*array;                  /*等价max=min=array[0]*/
  for(p=array+1;p<array_end;p++)
      if(*p>max) max=*p;
      else if (*p<min) min=*p;
  return;
}
main()
{ int i, number[10];
  printf(“enter 10 integer number:\n”);
  for( i=0; i<10; i++)
      scanf(“%d”,&number[i]);
  max_min_value(number,10);
    printf(“\n max=%d,min=%d\n”,max.min);
}
如果有一个实参数组,想在函数中改变此数组的元素的值,实参与形参的对应关系有以下4种情况:
1、形参和实参都用数组名;
    main()
      { int  a[10];
        f (a,10);……
      }
    f ( int x[],int  n )
      {    ……      }
    ax指的是同一个数组
 
2、实参用数组名,形参用指针变量;
    main()
      { int  a[10];
        f( a, 10 );
        ……
      }
    f( int  *x, int  n )
      {  …… }
开始时,x指向a[0]
3、实参形参都用指针变量;
main()
{ int  a[10], *p;
  p = a;
  f ( p, 10 );
  ……
  }
f ( int  *x,  int  n )
{ ……  }
实参p和形参x都指向数组a
 
4、实参为指针变量,形参为数组名。
main()
{ int  a[10], *p;
  p = a;
  f( p, 10 );
  ……
}
f ( int x[], int  n )
{ ……  }
p指向数组axa共用同一段内存单元   
 
例10.9 用实参指针变量将a数组中n 个整数按相反顺序存放.
void  inv(int  *x, int n)
{ int *p, m, temp, *i, *j;
  m=(n-1)/2;
  i=x; j=x+n-1; p=x+m;
  for( ; i<=p; i++, j- - )
      { temp=*i;
          *i = *j;
          *j =temp;
      }
    return;
}
main()
{ int i, a[10],*p=a;
  printf(“The original array: \n ”);
  for( i=0; i<10; i++,p++)    scanf(“%d”, p);
  printf(“\n”);
  p=a;
  inv(p,10);
  printf(“The array is : \n”);
    for( p=a; p<a+10; p++)
        printf(“%d”, *p);
    printf(“\n”);
}
 
10.10:用选择法对10个整数排序。
main()
{int  *p, i, a[10];
  p=a;
  for( i=0; i<10; i++)  scanf(“%d”,p++);
  p=a;
  sort(p,10);
  for( p=a, i=0; i<10; i++)
    {  printf(“%d”,*p);p++;}
}
sort(int  x[],int n)
{ int  i, j, k, t;
  for( i=0; i<n-1; i++)
      { k=i;
        for(j=i+1; j<n; j++)
          if(x[j]>x[k]) k=j;
        if( k !=i)
            { t = x[i];
              x[i] = x[k];
              x[k] = t; }
}
 
四、指向二维数组的指针和指针变量
1、 二维数组的地址如:
int  a[3][4]
A    a[0]        a[0] [0]  a[0] [1]  a[0] [2]  a[0] [3]
    A+1  a[1]        a[1] [0]  a[1] [1]  a[1] [2]  a[1] [3]
    A+2  a[2]        a[2] [0]  a[2] [1]  a[2] [2]  a[2] [3]
例:int  a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23};
        设首地址为2000。
 
a[k]+p ( 0<=k< i , 0<=p<j )等价于&a[k][p],
  由此得到: *(a[k]+p) 等价于a[k][p];
a[0]与&a[0][0]等价;
a+k与&a[k][0]等价,而a[0]+k与&a[0][k]等价;
*(a+i)可理解为a[i],故有如下等价关系:
    a+0    等价于 a[0]、  a[0]+0、    &a[0][0]、    *(a+0) 
    a+1    等价于 a[1]、  a[1]+0、    &a[1][0]、    *(a+1)
    a+(i-1)  等价于 a[i-1]、a[i-1]+0、  &a[i-1][0]、  *(a+i-1)
 
*(a+k)与*a[k]是不同的,前者相当于a[k],是一个地址值;后者相当于*(a[k]+0)或*&a[k][0],是数组元素a[k][0]中存储的值。
数组元素a[k][p]就是*(a[k]+p),即*(*(a+k)+p)
 
 
步骤三 课堂小结
    本课介绍了指向数组的指针,主要是指向一维数组的指针。用指针变量p指向数组a,指针变量p可以++、--,表示指向数组的上一元素或下一元素。但C编译程序不作下标越界检查。使用指针既方便有灵活,但初学者容易搞错。
 
步骤四 布置作业
课后作业:第十章课后练习 10.31 10.5
课题:
第十章 指针  §3-§4
教学目的:
在掌握指针与数组的基础上,掌握字符串的指针与指向字符串的指针变量
教学重点:
教学难点:
指向字符串的指针变量
用指针处理字符串
步骤一 复习引导
上节课介绍了指向一维数组的指针及二维数组的地址,指向一维数组的指针也即指向元素的指针,那么指向二维数组的指针是怎样的呢?它有两种类型。
 
步骤二 讲授新课
2、指向二维数组的指针变量
——(1)指向数组元素的指针变量
设指针变量p=a[0]  (&a[0][0]、a、a+0、*(a+0) )
计算a[i][j]在n*m数组中的位置:
  例:……
    for( i=0; i<3; i++)
          {for( j=0; j<4; j++ )  printf (“%4d”, *(p+i*m+j));
            printf(“\n”); }
        ……
 
例:用指针变量输出数组元素的值。
main()
{ int  a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23};
  int  *p;
  for( p=a[0]; p<a[0]+12; p++)
      {if((p-a[0])%4 == 0) printf(“\n”);
        printf(“%4d”, *p);
      }
}
计算a[i][j]在n*m数组中的位置:a[0]+i*m+j
 
——(2)行指针(指向由m个元素组成的一维数组的指针变量)
  定义:指针基类型  (*指针名)[m]
    其中,m表示二维数组每行有m列;
  如: int  (*p)[3];
  于是,p    指向第0行元素的起始存储地址;
        p+1  指向第1行元素的起始存储地址;……
        p+I  指向第i 行元素的起始存储地址;
  而每一行的元素的表示形式为:
        0行元素的表示为 (*p)[j]
        1行元素的表示为 (*(p+1))[j]……
        i 行元素的表示为 (*(p+ I))[j]
 例 :
#include <stdio.h>
main()
{ int  (*p)[4], j;
  int  a[4][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,15,16}};
for(p=a; p<a+4; p++)
  for(j=0;j<4; j++)
    if( j==3 ) printf(“%4d\n”, (*p)[j] );
    else  printf(“%4d” , (*p)[j] );
  }
改写为:
#include <stdio.h>
main()
{ int  (*p)[4], i,  j;
  int  a[4][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,15,16}};
  p=a;
  for( i=0; i<4; i++)
    for(j=0;j<4; j++)
      printf(j==3?“%4d\n”: “%4d” , *(*(p+i )+j) );
  }
 
3、多维数组的指针作函数参数
    一维数组的地址可以作为函数参数传递,多维数组的地址也可作函数参数传递。在用指针变量作形参以接受实参数组名传递来的地址时,有两种方法:
        1、用指向变量的指针变量;
        2、用指向一维数组的指针变量。
例;有一个班,3个学生,各学4门课,计算总平均分数,以及第n个学生的成绩。
main()
{ void  average(float  *p ,int  n);
  void  search(float  (*p)[4], int  n);
  float score[3][4]={{65,67,70,60},{80,87,90,81}, {90,99,100,98}};
  average( *score, 12);    /*求12个分数的平均分*/
  search(score , 2);          /*求第2个学生成绩*/
}
void  average(float  *p , int  n)
{float  *p_end;
  float  sum= 0,aver;
  p_end = p+n-1;
  for(; p<=p_end; p++)  sum =sum+(*p);
aver = sum/n;
printf(“average=%5.2f\n”,aver);
}
void search(float  (*p)[4], int n)
{ int  i;
  printf(“the score of No %d  are:\n”,n);
  for( i=0; i<4; i++)
      printf(“%5.2f”, *(*(p+n)+i));
}
 
10.15 在上题基础上,查一门以上课程不及格的学生,打印出他们的全部课程的成绩。
main()
{ void search(float  (*p)[4], int n);
  float score[3][4]={{65,57,70,60},{58,87,90,81),{90,99,100,98}};
  search(score, 3);
}
void  search(float  (*p)[4], int n)
{int  i, j, flag;
  for( j=0; j<n; j++)
      { flag = 0;
  for( i=0; i<4; i++)
        if(*(*(p+j)+i)<60) flag =1;
  if(flag==1)
      { printf(“No %d fails,his scores are:\n”,j+1);
        for( i=0; i<4 ; i++)
              printf(“%5.1f”, *(*(p+j)+i) );
        printf(“\n”);
      }
    }
}
 
§10.4字符串的指针和指向字符串的指针变量
一、字符串的表示形式
1、用字符数组存放一个字符串。
如:main()
        { char  string[ ]=“I love China!”;
          printf(“%s\n”, string);  }
2、用字符指针指向一个字符串。
如:main()
        { char  *string=“I love China!”;
          printf(“%s\n”, string);  }
例10.18 将字符串a复制为字符串b
main()
{ char a[]=“I am a boy.” , b[20];
  int  i;
  for( i=0; *(a+i)!=‘\0’; i++) *(b+i) = *(a+i);
  *(b+i) = ‘\0’;
  printf(“string a is : %s\n”, a);
  printf(“string b is :”);
  for( i=0; b[i]!=‘\0’; i++ )
        printf(“%c”, b[i] );
  printf(“\n”);
}
 
例10.19 用指针变量来处理上例。
main()
{ char a[ ]=“I am a boy.” , b[20], *p1, *p2;
  int  i;
  p1= a; p2= b;
  for( ; *p1!=‘\0’; p1++,p2++ )  *p2 = *p1;
  *p2 = ‘\0’;
  printf(“string a is : %s\n”, a);
  printf(“string b is :”);
  for( i=0; b[i]!=‘\0’; i++)  printf(“%c”, b[i]);
  printf(“\n”);
}
 
二、字符串指针作函数参数
例10.20:用函数调用实现字符串的复制。
——(1)用字符数组作参数。
void copy_string(char from[],char to[])
{ int  i=0;
  while( from[i]!=‘\0’ )
      { to[i] = from[i]; i++;}
  to[i]=‘\0’;
}
main()
{ char a[]=“I am a teach.”;
  char b[]=“you are a student.”;
  printf(“string a= %s\nstring b=%s\n”,a,b);
  copy_string(a,b);
  printf(“\nstring a=%s\nstring b=%s\n”,a,b);
}
——(2)形参用字符指针变量
void copy_string(char *from,char *to)
{ for(; * from!=‘\0’; from++, to++ )
      *to = *from;
  *to=‘\0’;
}
——(3)对copy_string函数还可以简化。
① void copy_string(char  *from, char *to)
      { while((*to =* from)!=‘\0’)
            { to ++; from++;}
      }
② void copy_string(char  *from, char *to)
    { while((*to ++ =* from++)!=‘\0’);  }
 
③void copy_string(char  *from, char *to)
      { while(* from!=‘\0’)  *to++ = *from ++;
        *to = ‘\0’;
      }
④void copy_string(char  *from, char *to)
      { while(*to++ = *from ++); }
 
⑤void copy_string(char  *from, char *to)
      { for( ;*to++ = *from ++;);  }
 
⑥void copy_string(char  from[], char to[])
      {  char *p1,*p2;
          p1=from; p2=to;
        while((*p2++ = *p1++)!=‘\0’);
      }
 
三、字符指针变量和字符数组的讨论
1、 字符数组由若干个元素组成,每个元素中放一个字符,而字符指针变量中存放的是地址,决不是将字符串放到字符指针变量中。
2、赋值方式。对字符数组只能对各个元素赋值,不能用以下办法对字符数组赋值;
      char  str[14];
      str=“I love China.”;对字符指针变量,可以采用下面方法赋值:
      char  *a;
      a= “I love China.”;      /*赋给a的是串的首地址*/
 
3、对字符指针变量赋初值:
      char  *a=“I love China.”;    等价于      char  *a;
                                            a=“I love  China.”;
  而对数组的初始化:
      char  str[14]={“I love China.”}; 不等价于  char  str[14];
                                            str[]=“I love  China.”;
  即数组可以在变量定义时整体赋初值,但不能在赋值语句中整体赋值。
 
4、如果定义了一个字符数组,在编译时为它分配内存单元,它有确定的地址。 而定义一个字符指针变量时,给指针变量分配内存单元,在其中可以放一个地址值,也就是说,该指针变量可以指向一个字符型数据,但如果未对它赋一个地址值,则它并未具体指向一个确定的字符数据。这很危险。
      如: char  str[10];
            scanf(“%s”,str);  是可以的
            char  *a;
            scanf(“%s”,a);  能运行,但危险,不提倡,在a单元中是一个不可预料的值。
    应当 char  *a,str[10];  a=str;  scanf(“%s”,a);
 
5、指针变量的值是可以改变的,数组名虽然代表地址,但它的值是不能改变的。可以下标形式引用所指的字符串中的字符。
        如:main()
              {char  *a=“I love China.”;
              a=a+7;
              printf(“%s”,a);  }
    又如:char str[]={“I love China.”};  str=str+7;  printf(“%s”,str); 是错的
 
6、用指针变量指向一个格式字符串,可以用它代替printf函数中的格式字符串。也可以用字符数组实现,但由于不能采用赋值语句对数组整体赋值。
如:char  *format;  format=“a=%d,b=%f\n”;  printf(format,a,b);
  等价于:printf(“a=%d,b=%f\n”,a,b);
  也可以:char format[ ]=“a=%d,b=%f\n”; printf(format,a,b);
步骤三 课堂小结
    本课介绍了指针与二维数组、指针与字符串,指向二维数组的指针有指向元素的指针和行指针,使用时应注意它们的区别。我们既要掌握用数组处理字符串,也要掌握用指针变量处理字符串。要区分这两种方法的不同之处。
 
步骤四 布置作业
    课后作业:第十章课后练习 10.61 10.7
课题:
第十章 指针  §5-§7
教学目的:
了解指针与函数的概念
掌握指针数组,二级指针等知识
教学重点:
教学难点:
掌握指针数组,二级指针等知识
指针数组,二级指针
步骤一 复习引导
前面介绍了指针与维数组、指针与字符串,我们可以用指针变量指向整型变量、字符串、数组,也可以指向一个函数。
 
步骤二 讲授新课
§10.5函数的指针和指向函数的指针变量
    函数的地址:函数存储区域的首地址就是该函数的入口点,其函数名表示了入口地址。
一、函数指针变量的定义:
        存储类型  数据类型  (*函数指针名)( );
  例:static  int  (*p)( );
例10.23 求a和b中的大者。
  main()
  { int max(int, int);
    int a,b,c;
    scanf(“%d,%d”,&a,&b);
    c=max(a,b);
    printf(“a=%d,b=%d,\
            max=%d”,a,b,c);
  }
max(int x, int y)
{ int  z;
  if(x>y) z=x;
  else  z=y;
  return(z);
}
法2:main()
    {  int max(int, int);
        int  (*p)( );
        int a,b,c;
    p=max;        /*将地址送入p */
    scanf(“%d,%d”,&a,&b);
    c=(*p)(a,b);        /*与max(a,b)等价*/
    printf(“a=%d,b=%d,max=%d”,a,b,c);
  }
注:int  (*p)()定义p是一个指向函数的指针变量,此函数带回整型的返回值。
 
说明:
1、 函数的调用可以通过函数名调用,也可以通过函数指针调用。
2、(*p)() 表示定义一个指向函数的指针变量,它不是固定指向哪一个函数的,而只是表示定义了这样一个类型的变量,它是专门用来存放函数的入口地址的。
3、在给函数指针变量赋值时,只需给出函数名而不必给出参数,如:p=max;  。
4、用函数指针变量调用函数时,只需将(*p)代替函数名即可(p为指针变量名),在(*p)之后的括号中根据需要写上实参。如:c=(*p)(a,b);
5、对指向函数的指针变量,像p+n、p++、p--等运算是无意义的。
 
二、用指向函数的指针作函数参数
    函数的参数可以是变量、指向变量的指针变量、数组名、指向数组的指针变量等。现介绍指向函数的指针也可以作为参数,以便实现函数地址的传递,也就是将函数名传给形参。
    它的原理可以简述如下:有一个函数(假设函数为sub),它有两个形参(x1和x2),定义x1和x2为指向函数的指针变量。在调用函数sub时,实参用两个函数名f1和f2给形参传递函数地址。这样在函数sub中就可以调用f1和f2函数了。
    有人会问,既然在sub函数中要调用f1和f2函数,为什么不直接调用f1和f2而用函数指针变量呢?的确,如果只是用到f1和f2,完全可以直接在sub函数中直接f1和f2,而不必设指针变量x1、x2。但是,如果在每次调用sub函数时,要调用的函数是不固定的,这次调用f1和f2,而下次要调用f3和f4,第三次要调用的名作为实参即可,sub函数不必作任何修改。这种方法是符合结构化程序设计方法原则的,是程序设计中常使用的。
 
例10.24  设一个函数process,在调用它的时候,每次实现不同的功能。
    main()
{ int  max(int ,int );
  int  min(int , int);
  int  add(int , int);
  int  a, b;
  printf(“enter a and b:”);
  scanf(“%d,%d”, &a, &b);
  printf(“max=”);  process(a,b,max);
  printf(“min=”);  process(a,b, min);
  printf(“sum=”);  process(a,b, add);
}
          max(int  x, int  y)
            { int  z;
              if(x>y) z = x;  else  z = y;
              return(z);
            }
        min(int  x, int  y)
            { int  z;
              if(x<y) z = x;
              else  z = y;
            return(z);
}
add( int  x, int y)
{ int  z;
  z = x+y;
  return(z);
}
 
process(int x, int y, int (*fun)(int ,int))
{int  result;
  result = (*fun)(x,y);
  printf(“%d\n” , result );
}
    在函数process定义中,int (*fun)(int,int)表示fun是指向函数的指针,该函数是一个整型函数,有两个整型形参。
    在三次调用中,分别将函数名max、 min、 add作为实参将其入口地址送给process函数中的形参fun(fun是指向函数的指针变量)。例:process函数中的(*fun)(x,y)相当于max(x,y)。
    注:在使用时,应将函数声明,这样编译系统将它按函数名处理(把函数入口地址作实参值),而不是作为变量名,不致出错。
Process函数无固定功能。如果max、 min、 add换成其它函数,此process函数不需改变,只要修改每函数的函数体。
 
§10.6  返回指针值的函数
一个函数可以带回一个整型值、字符值、实型值等,也可以带回指针型的数据,即地址。其概念与以前类似,只是带回的值的类型是指针类型而已。
格式: 类型名  *函数名(参数表);
例: int  *a(int  x,  int  y);
    a是函数名,调用它以后能得到一个指向整型数据的指针(地址)。
   
例10.25  有若干个学生的成绩(每个学生有4门课程),要求在用户输入学生序号以后,能输出该学生的全部成绩。用指针函数来实现。
float  *search( float (*pointer)[4], int n)
  {float  *pt;
    pt = *(pointer +n);
    return( pt );  }
main()
{ float score[][4]={{60,70,80,90},{56,89,67,88),{34,78,90,66}};
  float  *p;  int  i, m;
  printf(“enter the number of  student:”);
  scanf(“%d”,&m);
  printf(“The score of No.%d are:\n”, m);
  p=search(score, m);
  for( i=0; i<4; i++)  printf(“%5.2\t”, *(p + i));  }
例10.26 对上例中的学生,出其中有不及格课程的学生及其学生号。
float  *search(float  (*pointer)[4])
{ int  i;
  float  *pt;
  pt = *(pointer+1);
  for( i=0; i<4 ; i++)
      if(*(*pointer+i)<60) pt = *pointer;
  return(pt);
}
main()
{ score[][4]={{60,70,80,90},{56,89,67,88),{34,78,90,66}};
  float  *search(float  (*pointer)[4], int n);
  float  *p;    int  i, m;
  for( i=0; i<3; i++)
{ p=search(score+i);
        if( p==*(score + i))
          { printf(“ No.%d score:”, i);
            for(j=0; j<4; j++)    printf(“%5.2\t”, *(p + i));
            printf(“\n”); }
      }
}
 
关于函数的返回值是指针的情况,程序设计时应注意:
1、因数组名是地址常量,用于接受这种返值的对象不能是数组名,这与把数组名作为实在参数传递给形式参数的情况不同(作为形式参数的数组名总被视为指针)。
2、 不应将局部于被调用函数的指针作为返值返回给调用者,理由是局部于被调用函数的数据对象执行返回语句离开被调用函数后,原来分配的被调用函数的所有局部对象的存储空间立即被收回(释放),虽然调用者已经获得了正确的地址值,但此时它指向的存储区域的内容可能已经发生了变化,或许已经分配给其他函数了。如果调用函数中仍然使用这个指针去存取那个区域中的数据,得到的可能并非原先的数据。对于这种情况的正确做法是应该把所处理的对象定义成全局对象或static型对象。
 
§10.7 指针数组和指向指针的指针
一、指针数组的概念
    一个数组中的元素均为指针类型,称为指针数组。
  形式:  存储类型  类型名  *数组名[数组长度]
  例如:  static  int    *p[4]
  定义指针数组时也可以进行初始化,如:
  static char ch[][20]={“Beijing”,“Nanjing”,“Shanghai”,“Guangzhou”};
  char *p[ ]={ch[0],ch[1],ch[2],ch[3]};
该例也可以等价定义为:
  char *p[ ]={“Beijing”,“Nanjing”,“Shanghai”,“Guangzhou”};
 
例如:
main( )
{ int i, min, j;
  char *temp, *p[ ]={“Beging”,“Nanjing”,“Shanghai”, “Guangzhou”};
  for( i=0; i<3; i++)
    { min=i;
      for( j=i+1; j<4; j++)
            if (strcmp(p[j], p[min])<0) min=j;
      temp=p[i]; p[i]=p[min]; p[min]=temp;
    }
  for(i=0; i<4; i++)  printf(“%s\n”, p[i]);
}
注意:不能把一个二维数组与一个指针数组混淆;
    如:int a[10][10];  与  int *b[10];的异同点
访问形式相同;如a[5][5],b[5][5];
占用的存储空间数不同;
每一个b[i]必须置初值者能使用;
使用b优越
    不需进行复杂的下标计算;
    b[i]指向的数组并非一定要10个元素,但a中,每一行上的元素个数必须相同;
因指针指向的是一个地址,故对b而言,各个b[i]指向的存储区域之间不必连续;而对a而言,必须存储100个连续的存储int型数据对象的区域。
 
例10.27 将若干字符串按字母顺序(由小到大)输出。
main()
{ void  sort(char  *name[], int n);
  void  print(char  *name[], int n);
  char  *name[]={“Follow me”, “Basic”, “Great Wall”, “Fortran”, “Computer”};
    int  n=5;
    sort(name, n);
    print(name, n);
  }
void  sort(char  *name[], int n)
{ char  *temp;
  int  i, j, k;
  for( i=0; i<n-1; i++)
      { k=i ;
        for( j=i+1; j<n; j++)
            if( strcmp(name[k], name[j])>0) k=j;
if( k!=i )
            {temp=name[i];
              name[i]=name[k];
        name[k]=temp;}
      }
}
void  print(char  *name[], int n);
{  int  i;
  for( i=0; i<n; i++)  printf(“%s\n”, name[i]);
}
 二、指向指针的指针
    在本章开头已提到“间接访问”变量的方式。利用指针变量访问另一个变量就是“间接访问”。
    如果在一个指针变量中存放一个目标变量的地址,这就是“单级间址”。指向指针的指针用的是“二级间址”方法。从理论上讲,间址方法可以延伸到更多的级。但实际上在程序中很少有超过二级间址的。级数愈多,愈难理解,容易产生混乱,出错机会也多。
 二级指针的定义:  char  **p;
含义:表示指针变量p是指向一个字符指针变量(即指向字符型数据的指针变量)的。
main( )
{ int i, min, j;
  char  *temp, *p[ ]={“Beging”,“Nanjing”,“Shanghai”, “Guangzhou”};
  char  **pp;
  pp=p; 
  for(i=0; i<3; i++)
    { min=i;
      for( j=i+1; j<4; j++)
            if (strcmp(*(pp+j),*(pp+min))<0)  min=j;
      temp=*(pp+i);
      *(pp+i)=*(pp+min);
      *(pp+min)=temp;
      }
  for (i=0; i<4; i++)  printf(“%s\n”,*pp++);
}
 例10.28  使用指向指针的指针。
main()
{ char  *name[]={“Follow me”, “Basic”, “Great Wall”, “Fortran”, “Computer”};
  char  **p;
  int  i;
  for( i=0; i<5; i++)
      {  p=name+i;
        printf(“%s\n”, *p);  }
}
 例
main()
{ static int a[5]={1,3,5,7,9};
  int  *num[5]={&a[0],&a[1],&a[2],&a[3],&a[4]};
指针变量的定义格式  int  **p, i;
  p= num;
  for( i=0; i<5 ; i++)
      { printf(“%d\t”, **p);
        p++;
        }
  }
运行结果:
1  3  5  7  9
 步骤三 课堂小结
本课介绍了指针数组、二级指针、指针与函数。要搞清它们的定义及应用;
注意区分:  char  a[5];        char  (*a)[5];
                int  *p(int  x);  与  int  (*p)( );
 
步骤四 布置作业
《C语言习题集》同步练习
课题:
第十章 指针  §7-§8
教学目的:
了解指针数组作main函数的形参
掌握指针的应用
教学重点:
教学难点:
掌握指针的应用
指针的应用
步骤一 复习引导
    上节课介绍了二级指针、指针数组,而指针数组的一个重要应用是作为main函数的形参。main()函数是我们C语言程序必不可少的,以往使用时main()是不带参数的。实际上是可带参数的,如:main(argc, argv) 。
     
步骤二 讲授新课
  三、指针数组作main函数的形参
    带参数的main原型:
            main( int  argc, char *argv[ ] )
        {  ……    }
    说明:
      第1个参数是指命令行中参数的个数,含文件名本身。
      第2个参数是一个指向字符串的指针数组。
   
main函数是由系统调用的。当处于操作命令状态下,输入main所在的文件名(经过编译、连接后得到的可执行文件名),系统就调用main函数。参数应和命令一起给出。
命令形式:  命令名  参数1    参数2  ……参数n
 
例如:有一个目标文件名file1,今想将两个字符串“China”, “Beijing”作为传送给main函数的参数。可写成:  file1  China  Beijing
 
例:编写一程序echo.c,实现将命令行上除程序名之外的所有给出的其他参数都回显到显示器上。
main(int argc, int  *argv[ ])
{ while(argc>1)
{ ++argv;
  printf(“%s”, *argv);
  -- argc; }
}
 
若将该程序编译、连接、装配成,则在命令行上输入:
        echo hello,  world!<enter>
则通过虚实结合后得:argc=3,argv[0]指向echo,argv[1]指向hello,argv[2]指向world!
结果为:hello, world!
 
§10.8有关指针的数据类型和指针运算的小结
一、 有关指针的数据类型的小结
见书中的表

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。