c1数组和指针

http://hi.baidu.com/mayamedia/blog/item/0515cc520b693f0c0df3e354.html

我曾说过,在C语言中只有一维的数组(这是我对数组的看法),而且数组元素可以是任何类型的数据(或对象),自然也可以是另外的一个数组(因为数组也是一种数据类型)。所以如果你坚持要说有多维数组,那也不是不可能的事情。我们只要把一个数组赋值给另一个数组的元素就可以了。当然了,我们必须保证在程序编译期数组的大小是一个固定的常数。

  其实,数组的操作很简单的。只要我们确定一个数组的大小和指向该数组下标为0的元素的指针,其他的任何一个数组下标的运算都等同于一个对应的指针运算,所以我们说“数组和指针是可以相互操作的”。两者的本质是一样的。甚至我们还可以把数组看作是一个“指针”的集合。

  我可以通过如下的方式声明一个数组:

char name[10];

  这个语句声明了name是一个拥有10个字符型元素的数组。类似的

struct student{
int tid[4];
char name[10];
char sex;
char address[25];
} std[100];

  这里声明了std是一个拥有100个元素的数组,而且std中的每一个元素都定义了一名学生的基本信息,每一个元素都是一个结构,其中包括一个拥有4个整形元素的数组(tid[4]),用来记录学生的学好;还有一个拥有10个字符型元素的数组(name[10]),用来记录学生的名字;一个用来记录学生性别的字符(sex);还有一个记录学生住址,拥有25个字符型元素的数组(address[25])。数组是一个很灵活的结构。

  所谓的“二维数组”或“矩阵”是很容易声明的,例如:

int week[7][24];

  这里把声明week声明为一个拥有7个数组元素的数组(这样解释,不会感觉奇观吧),其中每一个元素都是拥有24个整数型元素的数组。注意了不能把week理解为一个拥有24个数组元素的数组,其中每一个元素是一个拥有7个整形元素的数组。 还有,如果week不是用于sizeof的操作数,那么它总是被一个指向week数组起始地址的指针。这里又和指针磨合了。 如果一个指向的是一个数组的一个元素,那么我们只需给这个指针加上一个自然数i(0 =<i <数组的上边界的值),那么就可以得到一个指向该数组的弟i个元素的指针。如果在此基础上减去1,那么就得到了一个指向前一个元素的指针。这样的操作很简单很灵活的。但是这儿也有一个误区:好多人都认为“给一个指针加一个整数,就等同于给该指针的二进制数表示加上一个同样的整数”。其实,这是一个很容易犯的错误了,至少在初学C语言的时候,我就犯过这个错误,而且不仅一次。其实,这两者的含义是截然不同的。假设我们有一个这样的指针声明语句:

int *p;

  那么p自然是一个指向整数指针了,那么p+1指向的是计算机内存的下一个整数,而不是指向指向地址的下一个内存位置。也就是说程序的逻辑地址一般都不同于实际的物理地址。

  如果有两个指向同一个数组的元素,那么我们可以通过这两个指针之间的算术运算得到一些有意义的表达式。 比如,

int *pointer;
int *ip = pointer + i;

  那么我们可以通过指ip-pointer得到i的值。如果ip和ponter指向的不是同一个元素,那么我们就无法保证这个操作的正确性,虽然他们在内存地址上相差一个整数倍。

  让我们通过下面的一个例子来看看数组和指针操作的等效性和灵活度:

  如果我们在程序中声明了以下两个语句,

int a[12];
int *p;

  那么我们可以对数组和指针进行相应的操作了:

(1) p = a;

  因为a = a[0],所以这里就有p=a[0]了,即p和a都指向数组的第一个元素;

(2) p = p + 1;

  这也是正确的。它等效于p = a[1];

(3) p++;

  这个语句等效于 p = a[2];

  还有:

  p = &a; 这样的语句ANSI C中是错误的,这一点在前一篇文章我已经声明过,因为这两个操作数的类型很显然是不匹配的,即&a是一个指向数组的指针而p是一个整型指针。所以此类操作是非法的。有时可能会侥幸的通过(因为有些编译器提供商不一定严格的按照ANSI C的保准来开发自己的编译器),但是我们不提倡这种做法。

  数组元素的引用

  这是一个足够让人糊涂的问题。先看一看下面这个语句是否正确:

a + i = a + i;

  也许你会说很显然是正确的,因为它特别象一个两元加法运算。虽然答案的前一半是正确的,但是问题的实质可不是这样的。为了回答之一个 问题我们需要从数组元素的引用说起。

  在前面我们声明了a为一个拥有12个整型元素的数组,而且我们知道a是一个指向该数组的第一个(0位元素)元素的指针,所以按照指针的性质我们可以知道*a就是对数组的第一个元素的引用。你可以通过如下的方式给数组的第一个元素赋值:

*a = 2005;

  明白了这一点,那么其他元素的赋值和引用也是类似的。*(a+3)是对数组的弟3个元素的引用,而其赋值可以是:

*(a+3) = 2006;

  然而,由加法的交换律,可以知道1+a = a+1,所以a+i = i+a;

  因为 *(a+i) = *(i+a)。

  现在让我们想一想如何用指针来操作我们的二维数组吧。

  前面我们声明了一个二维数组week,那么week[2]代表什么意思呢?我想如果明白了前面的讲解,那么这个问题就一如反掌了。week[2]代表的无非就是week数组的弟3个元素(数组下标从0开始),即一个拥有24个整型元素的数组。如果你还想知道week[2]的内存大小,那么你可以通过sizeof操作来实现:

int mem;
mem = sizeof(week[2]);

  其实,sizeof(week[2]) = 24 * sozeof(int)。

  如果你想通过指针来访问week[2]的元素(这里假设访问弟3个元素),那也是很简单的。请看下面的表达式:

int value;
p = week[3];

value = *(p+3);

  也可以是:

value = week[2][2];

  或者

value = *(week[2]+3);

  还可以是:

value = *(*(week+2)+3);


  由此我们可以看出来,数组和指针不是两个相互独立的结构,而是紧密紧密互不可分的整体。两者之间的互操作是最美的结合。我们提倡只在程序设计中才用数组和指针之间的互操作的实现方法。到这里我们的旅程也该结束了,如果说还有一些技术没有提及,我想那自然是数组和指针分别作为函数返回值类型和函数参数的情形罢了。其操作和上面提到的相当,在此就不提了。一般我们习惯于把指针当作函数返回值类型和函数参数来处理的,而不是处理数组,因为在这种情形下,对数组的操作显得很笨拙很底效。

十六、C语言数组和指针
2007-07-08 17:31

指针和数组有着密切的关系,任何能由数组下标完成的操作也都可用指针来实现,但程序中使用指针可使代码更紧凑、更灵活。

一、指向数组元素的指针
     我们定义一个整型数组和一个指向整型的指针变量:
         int a[10], *p;
     和前面介绍过的方法相同,可以使整型指针p指向数组中任何一个元素,假定给出赋值运算
         p=&a[0];
     此时,p指向数组中的第0号元素,即a[0],指针变量p中包含了数组元素a[0]的地址,由于数组元素在内存中是连续存放的,因此,我们就可以通过指针变量p及其有关运算间接访问数组中的任何一个元素。
     Turbo C中,数组名是数组的第0号元素的地址,因此下面两个语句是等价的
         p=&a[0];
         p=a;
     根据地址运算规则,a+1为a[1]的地址,a+i就为a[i]的地址。
     下面我们用指针给出数组元素的地址和内容的几种表示形式:
         (1). p+i和a+i均表示a[i]的地址, 或者讲,它们均指向数组第i号元素, 即指向a[i]。
         (2). *(p+i)和*(a+i)都表示p+i和a+i所指对象的内容,即为a[i]。
         (3). 指向数组元素的指针, 也可以表示成数组的形式,也就是说,它允许指针变量带下标, 如p[i]与*(p+i)等价。
              假若:         p=a+5;
                  则p[2]就相当于*(p+2), 由于p指向a[5], 所以p[2]就相当于a[7]。而p[-3]就相当于*(p-3), 它表示a[2]。

二、指向二维数组的指针
     1.二维数组元素的地址
         为了说明问题, 我们定义以下二维数组:
             int a[3][4]={{0,1,2,3}, {4,5,6,7}, {8,9,10,11}};
                 a为二维数组名,此数组有3行4列, 共12个元素。但也可这样来理解,数组a由三个元素组成:a[0],a[1],a[2]。而每个元素又是一个一维数组, 且都含有4个元素(相当于4列),例如,a[0]所代表的一维数组所包含的 4 个元素为a[0][0], a[0][1], a[0][2], a[0][3]。如图所示:
                      ______         _______________
                 a---| a[0] | ____ | 0 | 1 | 2 | 3 |
                     |______|       |___|___|___|___|
                     | a[1] | ____ | 4 | 5 | 6 | 7 |
                     |______|       |___|___|___|___|
                     | a[2] | ____ | 8 | 9 | 10| 11|
                     |______|       |___|___|___|___|

        
         但从二维数组的角度来看,a代表二维数组的首地址,当然也可看成是二维数组第0行的首地址。a+1就代表第1行的首地址,a+2就代表第2行的首地址。如果此二维数组的首地址为1000,由于第0行有4个整型元素,所以a+1为1008,a+2也就为1016。如图所示
                                    _______________
                       (1000) ____ | 0 | 1 | 2 | 3 |
                                   |___|___|___|___|
                       (1008) ____ | 4 | 5 | 6 | 7 |
                                   |___|___|___|___|
                       (1016) ____ | 8 | 9 | 10| 11|
                                   |___|___|___|___|

         既然我们把a[0],a[1],a[2]看成是一维数组名,可以认为它们分别代表它们所对应的数组的首地址,也就是讲,a[0]代表第 0 行中第 0 列元素的地址,即&a[0][0], a[1]是第1行中第0列元素的地址,即&a[1][0],根据地址运算规则,a[0]+1即代表第0行第1列元素的地址,即&a[0][1],一般而言,a[i]+j即代表第i行第j列元素的地址, 即&a[i][j]。
         另外,在二维数组中,我们还可用指针的形式来表示各元素的地址。如前所述,a[0]与*(a+0)等价,a[1]与*(a+1)等价,因此a[i]+j就与*(a+i)+j等价,它表示数组元素a[i][j]的地址。
         因此,二维数组元素a[i][j]可表示成*(a[i]+j)或*(*(a+i)+j),它们都与a[i][j]等价,或者还可写成(*(a+i))[j]。

         另外, 要补充说明一下, 果你编写一个程序输出打印a和*a,你可发现它们的值是相同的,这是为什么呢? 我们可这样来理解:
             首先,为了说明问题,我们把二维数组人为地看成由三个数组元素a[0],a[1],a[2]组成,将a[0],a[1],a[2]看成是数组名它们又分别是由4个元素组成的一维数组。因此,a表示数组第0行的地址, 而*a即为a[0], 它是数组名, 当然还是地址,它就是数组第0 行第0 列元素的地址。
     2.指向一个由n个元素所组成的数组指针
         在Turbo C中, 可定义如下的指针变量:
             int (*p)[3];
         指针p为指向一个由3个元素所组成的整型数组指针。在定义中,圆括号是不能少的, 否则它是指针数组, 这将在后面介绍。这种数组的指针不同于前面介绍的整型指针,当整型指针指向一个整型数组的元素时,进行指针(地址)加1运算,表示指向数组的下一个元素,此时地址值增加了2(因为放大因子为2),而如上所定义的指向一个由3个元素组成的数组指针,进行地址加1运算时,其地址值增加了6(放大因子为2x3=6),
         这种数组指针在Turbo C中用得较少,但在处理二维数组时, 还是很方便的。例如:
             int a[3][4], (*p)[4];
             p=a;
         开始时p指向二维数组第0行,当进行p+1运算时,根据地址运算规则,此时放大因子为4x2=8,所以此时正好指向二维数组的第1行。和二维数组元素地址计算的规则一样,*p+1指向a[0][1],*(p+i)+j则指向数组元素a[i][j]。
         例:
             int a[3][4]={
                           {1,3,5,7},
                           {9,11,13,15},
                           {17,19,21,23}
                         };

             main()
             {
                 int i,(*b)[4];
                 b=a+1;      /* b指向二维数组的第1行, 此时*b[0]是a[1][0] */
                 for(i=1;i<=4;b=b[0]+2,i++)    /* 修改b的指向, 每次增加2 */
                 printf("%d/t",*b[0]);
                 printf("/n");
                 for(i=0; i<3; i++)
                 {
                     b=a+i;        /* 修改b的指向,每次跳过二维数组的一行 */
                     printf("%d/t",*(b[i]+1));
                 }
                     printf ("/n");
             }
             程序运行结果如下:
                  9     13    17    21
                  3     11    19

三、字符指针
     我们已经知道,字符串常量是由双引号括起来的字符序列,例如:
         "a string"
     就是一个字符串常量,该字符串中因为字符a后面还有一个空格字符,所以它由8个字符序列组成。在程序中如出现字符串常量C编译程序就给字符串常量按排一存贮区域,这个区域是静态的,在整个程序运行的过程中始终占用, 平时所讲的字符串常量的长度是指该字符串的字符个数, 但在按排存贮区域时, C 编译程序还自动给该字符串序列的末尾加上一个空字符'/0',用来标志字符串的结束,因此一个字符串常量所占的存贮区域的字节数总比它的字符个数多一个字节。
     Turbo C中操作一个字符串常量的方法有:
     (1).把字符串常量存放在一个字符数组之中, 例如:
             char s[]="a string";
         数组s共有9个元素所组成,其中s[8]中的内容是'/0'。实际上,在字符数组定义的过程中,编译程序直接把字符串复写到数组中,即对数组s初始化。
     (2).用字符指针指向字符串,然后通过字符指针来访问字符串存贮区域。当字符串常量在表达式中出现时,
         根据数组的类型转换规则,它被转换成字符指针。因此,若我们定义了一字符指针cp:
             char *cp;
         于是可用:
             cp="a string";
         使cp指向字符串常量中的第0号字符a, 如图所示。
                      
                             ___________________________________
                 CP -----    | a |    | s | t | r | i | n | g | /0|
                            |___|___|___|___|___|___|___|___|___|
                                          
         以后我们可通过cp来访问这一存贮区域, 如*cp或cp[0]就是字符a,而cp[i]或*(cp+i)就相当于字符串的第i号字符,但企图通过指针来修改字符串常量的行为是没有意义的。

四、指针数组
     因为指针是变量,因此可设想用指向同一数据类型的指针来构成一个数组, 这就是指针数组。数组中的每个元素都是指针变量,根据数组的定义,指针数组中每个元素都为指向同一数据类型的指针。指针数组的定义格式为:
          类型标识 *数组名[整型常量表达式];
     例如:
          int *a[10];
     定义了一个指针数组,数组中的每个元素都是指向整型量的指针,该数组由10个元素组成,即a[0],a[1],a[2], ..., a[9],它们均为指针变量。a为该指针数组名,和数组一样,a是常量,不能对它进行增量运算。a为指针数组元素a[0]的地址,a+i为a[i]的地址,*a就是a[0],*(a+i)就是a[i]。
     为什么要定义和使用指针数组呢?主要是由于指针数组对处理字符串提供了更大的方便和灵活,使用二维数组对处理长度不等的正文效率低,而指针数组由于其中每个元素都为指针变量,因此通过地址运算来操作正文行是十分方便的。
     指针数组和一般数组一样,允许指针数组在定义时初始化,但由于指针数组的每个元素是指针变量,它只能存放地址,所以对指向字符串的指针数组在说明赋初值时,是把存放字符串的首地址赋给指针数组的对应元素,
     例如下面是一个书写函数month_name(n),函数返回一个指向包含第n月名字的字符指针(关于函数指针和指针函数,下一节将专门介绍)。
              
     例: 打印1月至12月的月名:
         char *month_name(int n)
         {
              static char *name[]={
                                     "Illegal month",
                                     "January",
                                     "February",
                                     "March",
                                     "April",
                                     "May",
                                     "June",
                                     "July",
                                     "August",
                                     "September",
                                     "October",
                                     "November",
                                     "December"
                                  };
              return((n<1||n>12)?name[0]:name[n]);
         }

         main()
         {
             int i;
             for(i=0; i<13; i++)
                 printf("%s/n", month_name(i));
         }
     对于指针这一节,一定要多练习一些题。指针是一个很重要的概念,必须多接触实际的问题才能掌握它。
 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值