多维数组与指针

发布者:技术旅人最新更新时间:2015-05-05 来源: 51hei关键字:多维数组  指针  C语言 手机看文章 扫描二维码
随时随地手机看文章
    在C语言中数组和指针之间存在一些千丝万缕的联系,搞不清楚的情况下非常容易出错,在前一段时间我写过关于数组和指针的分析,但是还是存在很多不清楚的问题,特别是当出现一些复杂的问题时,这种情况更加的复杂。看了很多网友的博客,但是发现一些问题,做一下简要的总结吧。

 
    多维数组的数组名并不是很多网友描述的多级指针,我仅以二维数组作为研究对象,进行一定的分析。
 
    二维数组int A[M][N],可以认为是存在M个元素的数组,且每一个元素都是长度为N的int型数组,这样就能比较清晰的理解了数组。数组名在很多情况下转换为指针,且数组名是数组首个元素的指针,这是非常重要的概念,搞清楚了这个概念也就能够分析其他多维的数组情况啦。
 
    对于上面的数组A[M][N],数组名A实质上指向数组的首个元素的指针,也就是说这时的数组名A变质为一个指针,指向的类型为int[N],即指向的是一个数组,而不是一个简单的值,那么对数组名进行解引用*A,就是得到这个一维数组(这是指针解引用的定义,就如同 int *p = &a, *p 实质上是指a是一个道理),相当于得到了一维数组名,即*A实质上是一个数组,(*A)可以看做这个数组的数组名,这和int a[N]是同样的道理,即:a = *A。同样根据数组名是指向数组的首个元素的指针,可以将*A也可以被看做为指针,但此时的指针指向的是数组A[0]的首个元素,也就是指向了A[0][0],也就是说*(*A)就是A[0][0]。由此可知,二维数组并不是简单的指向指针的指针,而是每一次解引用对应的都是不同的指针类型。
 
    对于多维的数组,假设存在int B[N][M][S]这个三维的数组,那么数组名B是一个指针,其指向的类型是int[M][S]。*B也是指针,但是其指向的类型是int[S]。**B同样也是指针,但是指向的类型是int型。更多维的数组也可以采用类似方法的分析。所以说并不能说是多维数组的数组名多级指针,因为多维数组对数组名的解引用操作都得到了一个不同大小的数组空间的指针,并不是指向单一元素的指针。
 
    由上面的分析可知,二维数组名A是一个指向一维数组的指针,指针的加法是指在当前地址上加上类型的长度,得到指向的新地址,这里的类型就是数组int[N]。因此A+i实质上是指向二维数组的第i个元素,也就是第i个一维数组。对A+i解引用*(A+i)即得到一个数组A[i],*(A+i)就是这个数组的数组名(这样说可能不合适,但是方便理解),同时依据数组名就是指向这个数组的首个元素的地址,可知*(A+i)实质上还是一个指针,但是这个指针指向的是A[i][0]这个元素。
 
   下面总结一下二维数组中的一些结论:
    a    :指向一维数组的指针。a -> a[0]
 
    *a   :得到一维数组a[0],可认为(*a)是一维数组的数组名,而(*a)-> a[0][0]。
 
    *a+j :指向a[0][j]的指针,(*a)可视为一个一维数组名。
 
    a+i  :数组a[i]的指针,也就是说a+i->a[i]。
 
    *(a+i): 得到一维数组a[i],其中*(a+i)可以视为数组名。*(a+i)指向了a[i]的首个元素,即:*(a+i)->a[i][0]。
 
    *(a+i)+j :得到数组*(a+i)的第j个元素的指针,即:*(a+i)+j -> a[i][j]。
 
    *(*(a+i)+j) :就是得到a[i][j]。
 
    我之前一直将二维数组看做指向指针的指针,现在想起来是不合适的,假设存在一个指向指针的指针int **p。如果另p = A,通常对p的操作都会导致一些错误,由于p是一个指向指针的指针,那么p++实质上只是增加了4个字节,而A+1则增加了N*sizeof(int)。两者之间并不是等价的关系,因此指向指针的指针并不能表征二维数组。
 
    如果指向指针的指针a和二维数组名是等价的,那么下面的程序肯定能够实现数组元素的打印,但是非常不幸,下面的函数会产生段错误即访问了非法的内存空间。

 

    int print_array(int **a, int row, int col)
    {
        int i = 0 , j = 0;
        for(i = 0; i < row; ++ i)
        {
            for(j = 0; j < col; ++ j)
                 printf("%d ",a[i][j]);
            printf(" ");
        }
    }

   
    简要的说明一下,为什么会产生段错误吧,由于a是一个指向指针的指针,那么a[i]则是一个指针,他指向一个数据空间,由于数组名实际上对应一个地址,这个地址和&A等都是相同的,a[i]实质上是就是数组中的一个元素,但是其中的内容也是一个指针,这时候再次解引用就很有可能导致内存非法访问,产生段错误。比如A[2][3]={{1,2,3},{4,5,6}}。调用函数print_array(A,2,3)时,由于A实质上是一个地址,由于数组的特殊性,具有&A,A,A+0,&A[0][0]等对应的值是相同的。a = A,实质上a就是一个地址。这个地址就是数组的起始位置。a中的内容实质上就是数组的元素,因此对数组进行一次解引用(*a)得到的实质上就是数组的一个元素,也就是1,但是a是指向指针的指针,内容1也是一个地址,对地址1的访问肯定发生错误,因为地址1处是属于内核,不能直接进行访问,发生了段错误。这也就是为什么说二维数组和指向指针的指针之间并不是等价的。
 
    但是根据上面的分析,我们知道二维数组的数组名指明了数组的位置,这时候直接对位置进行访问就能够得到数组的元素。同时我们知道a++指向的地址恰好就是a+4。刚好也就是数组的下一个元素。因此我们可以采用解一次引用的方式实现数据的访问。因此上面的代码可以修改如下,这种方法是运用了指针加法的一些特性,也就是指向指针的指针的增长大小就是大小等于4,但是这种方法只能针对类型大小为4的数组问题,其他的类型不能运用。

 

    int print_array(int **a, int row, int col)
    {
        int i = 0 , j = 0;
        for(i = 0; i < row; ++ i)
        {
            for(j = 0; j < col; ++ j)
                 printf("%d ",*(a+i+j));
            printf(" ");
        }
    }

    字符串数组是比较常用的,字符串数组定义如下:

 

    char *str[]={
    "One",
    "Two",
    "Three",
    "Four"
    };

    字符串数组允许出现锯齿形的数组,也就是说各个字符串的长度可以是不相同的,我们知道字符串数组实质上是一个指针数组,和二维数组之间存在一定的差别,并不是同一种类型的问题,指针数组可以转换为指向指针的指针的问题。
    我们常见的主函数main的两种种定义方式为:

 

    int main(int argc, char *argv[])
    int main(int argc, char **argv)

    这两种定义方式使得很多的初学者认为二维数组和指向指针的指针有一定的联系,特别是第2种写法存在很大的误导性,实质上字符串数组和二维数组之间存在很大的差别,所以不能当做一类问题来讨论。
 
    二维数组作为函数的形参时也是一个应该注意的问题。我们已经知道二维数组不是指向指针的指针这个结论。那么如果需要传递一个二维数组时又该如何处理呢?我们知道数组名是一个指向数组的指针,将参数设置为这种形式即可:

 

    int print_array(int (*p)[N],int row, int col);
    int print_array(int a[M][N],int row, int col);

    上面的两种定义方式是比较常见的,当然也可以采用指向指针的指针这种方式,这种方式一般在动态分配二维数组的情况下使用。这时候的动态分配的二维数组和我们当前讨论的二维数组存在差别,更像是字符串指针数组问题。

 

    /*C++*/
    void array_create(int **array, int row, int col)
    {
        int i = 0, j = 0;

        array = new int *[row];

        for(i = 0; i < row; ++ i)
        {
             array[i] = new int[col];
        }
    }

    其中array首先分配行,然后行中保存了指针变量,这样就实现了二维数组的动态分配,这时候的二维数组不再是线性表,这是需要注意的。在C语言中也可以直接创建线性表,即采用一维数组访问。

关键字:多维数组  指针  C语言 引用地址:多维数组与指针

上一篇:计算机中信息的表示与处理
下一篇:比较型排序算法总结

推荐阅读最新更新时间:2024-03-16 14:00

Keil C51对C语言的关键词扩展之十三: sfr
sfr用来定义特殊功能寄存器。用法如下: sfr name = address; name 为寄存器名字 address 为寄存器的地址 示例: sfr P0 = 0x80; /* P0口,地址为0x80 */ sfr P1 = 0x90; /* P1口,地址为0x90 */ sfr P2 = 0xA0; /* P2口,地址为0xa0 */ sfr P3 = 0xB0; /* P3口,地址为0xb0 */ 等号右边必须为数字常量,不能包含+ -等操作符,数值也不是随意的,传统类型8051单片机支持的地址范围为0x80 - 0xFF。NXP 80C51MX提供扩展地址范围0x180 - 0x1FF。 假如8个发
[单片机]
如何写出好的单片机C语言代码
  程序能跑起来并不见得你的代码就是很好的c代码了,衡量代码的好坏应该从以下几个方面来看   1,代码稳定,没有隐患。   2,执行效率高。   3,可读性高。   4,便于移植。   下面发一些我在网上看到的技巧和自己的一些经验来和大家分享;   1、如果可以的话少用库函数,便于不同的mcu和编译器间的移植   2、选择合适的算法和数据结构   应该熟悉算法语言,知道各种算法的优缺点,具体资料请参见相应的参考资料,有很多计算机书籍上都有介绍。将比较慢的顺序查找法用较快的二分查找或乱序查找法代替,插入排序或冒泡排序法用快速排序、合并排序或根排序代替,都可以大大提高程序执行的效率。.选择一种合适的数据结构也很重要,比
[单片机]
基于C语言STC89C52单片机电子密码锁的设计与仿真
搜索: IC库存 认证库存 PDF 文章 用户名: 密码: 社区 企业 免费注册 iframe marginWidth=0 marginHeight=0 src="http://afp21ic.allyes.com/main/adfshow?user=Afp21ic|MCU|logo_left&db=afp21ic&border=0&local=yes" frameBorder=0 width=758 scrolling=no height=64 /iframe 首页 资讯: 新闻 应用 新品 eBooks 电路图
[单片机]
ARM之---在C语言中内嵌汇编语言
开发Arm程序的时候,大多数时候使用C/C++语言就可以了,但汇编语言在某些情况下能够实现一些C语言无法实现的功能,这时候就要调用一些汇编语言的程序.我们需要大概了解一下在C语言中如何嵌入汇编语言. 1.内嵌汇编语言的语法: __asm { 指令 ...... } 2.举例:使能/禁止IRQ中断 __inline void enable_IRQ(void) { int tmp; __asm //嵌入汇编代码 {
[单片机]
如何根据需要合理使用指针式和数字式万用表?
指针式和数字式万用表在结构和原理上不同,决定了它们在性能上各有差异,因此应根据实际需要合理使用不同类型的万用表,并注意取长补短,配合使用。 1.宜使用数字式万用表的场合 (1)在线测量电压时,万用表内阻越高越好,这样对电路的影响就越小,因此数字式万用表为首选,对于精度要求较高的测量尤其如此,如测量彩电的调谐电压、开关电源的振荡管、色解码集成电路等。但在测量彩电行输出级电压时,有些数字式万用表会出现显示读数失常的现象。 (2)测量小电阻时宜用数字式万用表,因为其输入阻抗高,对输入信号无衰减作用。测量大电阻时,指针式万用表完全能胜任。 但对精度要求较高的电阻,如测量限流电阻、彩电电源开关管的负反馈电阻等,则只能使用数字式万用
[测试测量]
单片机C语言程序与数据存储
一、五大内存分区 内存分成5个区,它们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。 1、栈区(stack):FIFO就是那些由编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区。里面的变量通常是局部变量、函数参数等。 2、堆区(heap):就是那些由new分配的内存块,它们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。 3、自由存储区:就是那些由malloc等分配的内存块,它和堆是十分相似的,不过它是用free来结束自己的生命。 4、全局/静态存储区:全局变量和静态变量被分配到同一块内存中,在
[单片机]
单片机<font color='red'>C语言</font>程序与数据存储
C语言在8051单片机上的扩展
直接访问寄存器和端口 定义 sfr P0 0x80 sfr P1 0x81 sfr ADCON; 0xDE sbit EA 0x9F 操作 ADCON = 0x08 ; P1 = 0xFF ; io_status = P0 ; EA = 1 ; 在使用了interrupt 1 关键字之后,会自动生成中断向量 在 ISR中不能 与其他 后台循环代码 (the background loop code) 共享局部变量 因为 连接器 会复用 在RAM中这些变量的 位置 ,所以它们会有不同的意义,这取决于当前使用的不同的函数 复用变量对 RAM有
[单片机]
小容量单片机系统的C语言程序结构
引 言:   2002年初,笔者着手写一个IC卡预付费电表的工作程序,该电表使用Philips公司的8位51扩展型单片机87LPC764,要求实现很多功能,包括熄显示、负荷计算与控制、指示闪烁以及电表各种参数的查询等,总之,要使用时间的单元很多。笔者当时使用ASM51完成了这个程序的编写,完成后的程序量是2KB多一点。后来,由于种种原因,这个程序并没有真正使用,只是作了一些改动之后用在一个老化设备上进行计时与负荷计算。约一年后,笔者又重新改写了这些代码。 1 系统的改进   可以说,这个用ASM51实现的代码是没有什么组织性可言的,要什么功能就加入什么功能,弄得程序的结构非常松散,其实这也是导致笔者最终决定重新改写这些代
[单片机]
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved