数组与指针

10.3.1 通过指针引用一维数组中的元素


  在C语言中,指针和数组有着紧密的联系,其原因在于凡是由数组下标完成的操作皆可用指针来实现。在数组中我们已经知道,可以通过数组的下标唯一确定了某个数组元素在数组中的顺序和存储地址,这种访问方式也称为"下标方式"。例如:
int a[5] = {1, 2, 3, 4, 5}, x, y;
x=a[2]; /* 通过下标将数组a下标为2的第3个元素的值赋给x,x=3 */
y=a[4]; /* 通过下标将数组a下标为4的第5个元素的值赋给y,y=5 */
由于每个数组元素相当于一个变量,因此指针变量既然可以指向一般的变量,同样也可以指向数组中的元素,也就是可以用"指针方式"访问数组中的元素。


例10-6:分析程序的运行过程和结果。
#include <stdio.h>
main ( )
{ int a[ ] = {1, 2, 3, 4, 5} ;
  int x, y, *p;       /* 指针变量p */
  p = &a[0]; /* 指针p指向数组a的元素a[0],等价于p=a */
  x = *(p+2); /* 取指针p+2所指的内容,等价于x=a[2] */
  y = *(p+4); /* 取指针p+4所指的内容,等价于y=a[4] */
  printf ("*p=%d, x=%d, y=%d/n", *p, x, y);
}

    语句"p=&a[0]"表示将数组a中元素a[0]的地址赋给指针变量p,则p就是指向数组首元素a[0]的指针变量,"&a[0]"是取数组首元素的地址。
    C语言中规定,数组第1个(下标为0)元素的地址就是数组的首地址,同时C中还规定,数组名代表的就是数组的首地址,所以,该语句等价于"p=a;"。注意,数组名代表的一个地址常量,是数组的首地址,它不同于指针变量。
    对于指向数组首地址的指针p,p+i(或a+i)是数组元素a[i]的地址,*(p+i)(或*(a+i))就是a[i]的值,其关系如图10-5所示。

                图10-5 指针操作与数组元素的关系
    对数组元素的访问,下标方式和指针方式是等价的,但从C语言系统内部处理机制上讲,指针方式效率高。需要注意的是:指针方式不如下标方式直观。下标方式可以直截了当地看出要访问的是数组中的哪个元素;而对于指向数组的指针变量,进行运算以后,指针变量的值改变了,其当前指向的是哪一个数组元素不再是一目了然。

例10-7:分析程序。
main( )
{ int a[ ] = {1, 2, 3, 4, 5, 6};
  int *p;
  p = a;   /* 指针p为数组的首地址 */
  printf("%d", *p);
  printf(" %d/n", *(++p));   /* 以下两个语句等价 */
  printf("%d", *++p);
  printf(" %d/n", *(p--)); /* *(p--)等价于*p-- */
  p += 3;
  printf("%d %d/n", *p, *(a+3));
}
运行结果:
1 2
3 3
5 4

    此例中指向数组a与指针变量p的指向变化情况见图10-6。

  注意,第2个printf语句中的"*(++p)",是先使指针p自增加1,再取指针p值作"*"运算,它的含义等价于第3个printf语句中的"*++p"。而"*(p--)"是先取指针p值作"*" 运算,再使指针p自减减1。
    用指针方式实现对数组的访问是很方便的,可以使源程序更紧凑、更清晰。

10.3.2 指针基本运算


  对于指针的运算有三种:指针与正整数的加减运算、两个指针的关系运算,以及两个指针的减法运算。
    1. 指针与正整数的加减运算
       当指针p指向数组中的元素时,n为一个正整数,则表达式:
        p+n
       表示:指针p所指向当前元素之后的第n个元素。而表达式:
        p-n
       表示:指针p所指向当前元素之前的第n个元素。
    最常见的指针加减运算为p++的含义是:指针加1,指向数组中的下一个元素;p--的含义是:指针减1,指向数组中的前一个元素。
指针与整数进行加减运算后,它们的相对关系如图10-7所示。

  由于指针p所指的具体对象不同,所以对指针与整数进行加减运算时,C语言会根据所指的不同对象计算出不同的放大因子,以保证正确操作实际的运算对象。对于字符型,放大因子为1;对于整型,放大因子为2;对于长整型,放大因子为4;对于双精度浮点型,放大因子为8。不同数据类型的放大因子等于一个该数据类型的变量所占用的内存单元数。
    例10-8:编程将str1复制到str2中。
        #include <stdio.h>
        #include <string.h>
        main( )
        { char str1[80], str2[80], *p1, *p2;
          printf("Enter string 1:");
          gets(str1);
          p1=str1;
          p2=str2;
          while ( (*p2=*p1) != '/0' ) /* 指针p1的内容送到指针p2 */
           { p1++; p2++; } /* 指针p1和p2分别向后移动1个字符 */
          printf("String 2:");
          puts(str2);
        }

    程序中的关键是while语句,"(*p2=*p1)!='/0'"的含义是:先将指针p1的内容送到指针p2的内容中,即进行两个指针内容的赋值,然后再判断所赋值的字符是否是串结束标记'/0',如果不是串结束标记,则执行循环继续进行字符复制;如果是串结束标记,则退出循环,完成串复制。
    对于上面程序中的while循环,是可以进行优化的。优化后的循环可以如下:
    优化一:
        while ( *p2 = *p1 )
         { p1++; p2++; }
    优化二:
        while ( *p2++ = *p1++ ) ; /* 循环体为空 */
    2. 两个指针的关系运算
       只有当两个指针指向同一个数组中的元素时,才能进行关系运算。
       当指针p和指针q指向同一数组中的元素时,
       则:
        p<q 当p所指的元素在q所指的元素之前时,表达式的值为1;反之为0。
        P>q 当p所指的元素在q所指的元素之后时,表达式的值为1;反之为0。
        p==q 当p和q指向同一元素时,表达式的值为1;反之为0。
        p!=q 当p和q不指向同一元素时,表达式的值为1;反之为0。
    任何指针p与NULL进行"p==NULL"或"p!=NULL"运算均有意义,"p==NULL"的含义是当指针p为空时成立,"p!=NULL"的含义是当p不为空时成立。
不允许两个指向不同数组的指针进行比较,因为这样的判断没有任何实际的意义。

例10-9:编写程序将一个字符串反向。
#include <stdio.h>
main( )
{ char str[50], *p, *s, c;
printf("Enter string:");
gets(str);
p=s=str; /* 指针p和s指向str */
while ( *p )
p++; /* 找到串结束标记'/0' */
p--; /* 指针回退一个字符,保证反向后的字符串有串结束
标记'/0',指针p指向字符串中的最后一个字符 */
while ( s<p ) /* 当串前面的指针s<(小于)串后面的指针p时,进行循环 */
{ c = *s; /* 交换两个指针所指向的字符 */
*s++ = *p; /* 串前面的指针s向后(+1)移动 */
*p-- = c; /* 串后面的指针p向前(-1)移动 */
}
puts(str);
}


    3. 两个指针的减法运算
       只有当两个指针指向同一数组中的元素时,才能进行两个指针的减法运算,否则,没有意义。
    当两个指针指向同一数组中的元素时,p-q表示指针p和q所指对象之间的元素数量。利用这一意义,可以求出一个字符串的长度。
    例10-10:编写程序求字符串的长度。
        #include <stdio.h>
        main( )
        { char str[50], *p=str;
          printf("Enter string:");
          gets(str);
          while ( *p )
          p++; /* 找到串结束标记'/0'。退出循环时p指向'/0' */
          printf("String length=%s/n", p-str ); /* 指向同一字符数组的两个指针进行减法运算,求出串长 */

        }

10.3.3 通过指针引用二维数组中的元素

  在C语言中,二维数组是按行优先的规律转换为一维线性存放在内存中的,因此,可以通过指针访问二维数组中的元素。
    如果有:int a[M][N];
    则将二维数组中的元素a[i][j]转换为一维线性地址的一般公式是:
        线性地址=a+i×M+j
    其中:a为数组的首地址, M和N分别为二维数组行和列的元素个数。
    若有:int a[4][3], *p;
        p = &a[0][0];
    则二维数组a的数据元素在内存中存储顺序及地址关系如图10-8所示。

  这里,a表示二维数组的首地址;a[0]表示0行元素的起始地址,a[1]表示1行元素的起始地址,a[2]和a[3]分别表示2行和3行元素的起始地址。
    数组元素a[i][j]的存储地址是:&a[0][0]+i*n+j。
    我们可以说:a和a[0]是数组元素a[0][0]的地址,也是0行的首地址。a+1和a[1]是数组元素a[1][0]的地址,也是1行的首地址。
    由于a 是二维数组,经过两次下标运算[ ]之后才能访问到数组元素。所以根据C语言的地址计算方法,a要经过两次*操作后才能访问到数组元素。这样就有:*a是a[0]的内容,即数组元素a[0][0]的地址。**a是数组元素a[0][0]。a[0]是数组元素a[0][0]的地址,*a[0]是素组元素a[0][0]。
    例10-11:给定某年某月某日,将其转换成这一年的第几天并输出。
    此题的算法很简单,若给定的月是i,则将1、2、3、……、i-1月的各月天数累加,再加上指定的日。但对于闰年,二月的天数29天,因此还要判定给定的年是否为闰年。为实现这一算法,需设置一张每月天数列表,给出每个月的天数,考虑闰年非闰年的情况,此表可设置成一个2行13列的二维数组,其中第1行对应的每列(设1~12列有效)元素是平年各月的天数,第2行对应的是闰年每月的天数。程序中使用指针作为函数day_of_year的形式参数。

#include <stdio.h>
main( )
{ static int day_tab[2][13]={
{0,31,28,31,30,31,30,31,31,30,31,30,31},
{0,31,29,31,30,31,30,31,31,30,31,30,31} };
int y, m, d;
scanf("%d%d%d", &y, &m, &d);
printf("%d/n", day_of_year(day_tab,y,m,d) ); /* 实参为二维数组名 */
}
day_of_year(day_tab,year,month,day)
int *day_tab; /* 形式参数为指针 */
int year, month, day;
{ int i, j;
i = (year%4==0&&year%100!=0) || year%400==0;
for ( j=1; j<month; j++ )
day += *( day_tab+i*13+j );
/* day_tab+i*13+j:对二维数组中元素进行地址变换 */
return(day);
}

  由于C语言对于二维数组中的元素在内存中是按行存放的,所以在函数day_of_year 中要使用公式"day_tab+i*13+j"计算main函数的day_tab中元素对应的地址。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值