C语言的多维数组
    ANSI C标准表示:
    当几个“[]”修饰符连续出现时(方括号里面是数组的范围),就是定义一个多维数组。

但所有其他语言都把这称为“数组的数组”
    哪些人的意思是C语言没有像其他语言那样的多维数组。

在不同的语言中,“多维数组”的含义各有什么不同
    Ada语言标准明确说明数组的数组和多维数组是不一样的。
    Pascal语言标准明确说明数组的数组和多维数组是一样的。
    C语言里面只有一种别的语言称为数组的数组的形式,但C语言称它为多维数组。(哈哈)

C语言的方法多少有点独特:定义和引用多维数组的唯一方式就是使用数组的数组。尽管C语言把数组的数组当做是多维数组,但不能把几个下标范围如[i][j][k]合并成Pascal式的下标表达式风格如[i,j,k]。如果你清楚地明白自己在做什么,也介意产生不合规范的程序,可以把[i][j][k]这样的下标值计算为相应的偏移量,然后只用一个单一的下标[z]来引用数组。当然,这不是一种值得推荐的做法。同样糟糕的是,像[i,j,k]这样的下标形式(由逗号分隔)是C语言合法的表达形式,只是它并非同时引用这几个下标(它实际上所引用的下标值是k,也是就逗号表达式的值)。C语言支持其他语言中一般称作“数组的数组”的内容,但却称它为多维数组。

在C语言中,可以像下面这样声明一个10*20的多维字符数组:
    char carrot[10][20];
    或者声明一种看上去更像“数组的数组”形式:
    typedef char vegetable[20];
    vegetable carrot[10];
    不论那种情况,访问单个字符都是通过carrot[i][j]的形式,编译器在编译时会把它解析为*(*(carrot + i) + j)的形式。

但C语言实际上只支持“数组的数组”。如果你的思维模式可以把数组看做是一种向量(即某种对象的一维数组,它的元素可以是另一个数组),就能极大简化编程语言中的这个相当复杂的领域。

C语言中的数组就是一维数组
    当提到C语言中的数组时,就把它看做是一种向量(vector),也就是某种对象的一维数组,数组的元素可以是另一个数组。

如何分解多维数组
    我们声明如下的多维数组:
    int apricot[2][3][5];
    int (*p)[3][5] = apricot;
    int (*r)[5] = apricot[i];
    int *t = apricot[i][j];
    int u = apricot[i][j][k];
    正常情况下,赋值发生在两个相同的类型之间,可以看到在“数组的数组的数组”中,每一个单独的数组都可以看作是一个指针。这是因为在表达式中的数组名被编译器当做“指向数组第一个单元的指针”。换句话说,不能把一个数组赋值给另一个数组,因为数组作为一个整体不能成为赋值的对象。之所以可以把数组名赋值给一个指针,就是因为这个“在表达式中的数组名被编译器当作一个指针”的规则。
    指针所指向的数组的维度不同,其区别会很大。使用上面例子中的声明:
    r++;
    t++;
    将会使r和t分别指向它们各自的下一个元素(两者所指向的元素本身都是数组)。它们所增长的步长是很不相同的,因为r所指向的数组元素的大小是t所指向的数组的元素大小的3倍。

/*
    ** multi_array_code_2.c.
    */
    #include <stdio.h>
    #include <stdlib.h>
    
    int main( void ){
        int i = 0, j = 0, k = 0;
        int apricot[2][3][5];
        int (*p)[3][5] = apricot;
        int (*r)[5] = apricot[i];
        int *t = apricot[i][j];
        int u = apricot[i][j][k];
        int (*r2)[5] = r + 1;
        int *t2 = t + 1;
        int num = (r2 - r) * 5 * sizeof(int);
        int num_2 = (t2 - t) * 1 * sizeof(int);
        printf( "sizeof(*r) = %zd, sizeof(*t) = %zd\n", sizeof(*r), sizeof(*t) );
        printf( "r2 = %p, r = %p, t2 = %p, t = %p, num = %d, num_2 = %d\n",
        r2, r, t2, t, num, num_2 );
        
        return EXIT_SUCCESS;
    }
输出:

/*
    **编程挑战 
    */ 
    数组万岁!(哈哈,挺喜悦的呢)
    int apricot[2][3][5];
    int (*r)[5] = apricot[0];
    int *t = apricot[0][0];
    编写一个程序,打印出r和t的十六进制初始值(使用printf的%x转换符,打印十六进制值),对这两个指针进行自增(++)操作,并打印它们的新值。
    #include <stdio.h>
    #include <stdlib.h>

int main( void ){
        int apricot[2][3][5];
    
        int (*r)[5] = apricot[0];
        int *t = apricot[0][0];
        printf( "r = %x, t = %x\n", r, t );
        r++;
        t++;
        printf( "r = %x, t = %x\n", r, t );
    
        return EXIT_SUCCESS;
    }

输出:

内存中数组是如何布局的
    在C语言的多维数组中,最右边的下标是最先变化的,这个约定被称为“行主序”。由于“行/列主序”这个术语只适用于恰好是二维的多维数组,所以更确切的术语是“最右的下标先变化”。绝大部分语言都采用了这个约定,但Fortran却是一个主要的例外,它采用了“最左的下标先变化”,也就是“列主序”。在不同的下表变化中,多维数组在内存中的布局也不相同。事实上,如果把一个C程序的矩阵传递给一个Fortran程序,矩阵就会被自动转置---这是一个非常厉害的邪门秘笈,偶尔还真会用到。
    最低地址         <---->          最高地址
    C int a[2][3] a[0][0] a[0][1] a[0][2] a[1][0] a[1][1] a[1][2]  最右的下标先变化
    Fortran dim a(2, 3) a(1, 1) a(2, 1) a(1, 2) a(2, 2) a(1, 3) a(2, 3) 最左的下标先变化
                       图9-9 行主序vs列主序
    在C语言中,多维数组最大的用途是存储多个字符串。有人指出“最右边的下边先变化”在这方面具有优势(每个字符串中相邻的字符在内存中也相邻存储)。但在“最左边的下标先变化”的多维数组中,情况并不如此。

如何对数组进行初始化
    在最简单的情况下,一维数组可以通过把初始值都放在一对花括号内来完成初始化。如果在数组的定义里未标明它的长度,C语言约定按照初始化值的个数来确定数组的长度。
    float banans[5] = {0.0, 1.0, 2.72, 3.14, 25.625};
    float honeydew[] = {0.0, 1.0, 2.72, 3.14, 25.625};
    只能够在数组声明时对它进行整体的初始化。之所以存在这个限制,并没有过硬的理由。多维数组可以通过嵌套的花括号进行初始化:
    short cantaloupe[2][5] = {
        {10, 12, 3, 4, -5},
        {31, 22, 6, 0, -5},
    } 
    int rhubarb[][3] = {{0, 0, 0}, {1, 1, 1},};
    注意,既可以在最后一个初始化值的后面加一个逗号,也可以省略它。同时,也可以省略最左边下标的长度(也可能是最左边的下标),编译器会根据初始化值的个数推断出它的长度。
    如果数组的长度比所提供的初始化值的个数要多,剩余的几个元素会自动设置为0。如果元素的类型是指针,那么它们被初始化为NULL;如果元素的类型是float,那么它们被初始化为0.0。在流行的IEEE754标准浮点数实现中(IBM PC和Sun系统都使用了这个标准),0.0和0的位模式是完全一样的。

/*
    **编程挑战 
    */
    /*
    *检查位模式
    *写一个简单的程序,检查在你的系统中,浮点数0.0的位模式是否与整型数0的位模式相同。
    */
    #include <stdio.h>   
    #include <stdlib.h>
    #include <limits.h>
    
    unsigned int float_to_bit( float f );
    
    int main( void ){
        int int_size;
        int flt_size;
        int i;
        int int_bit_size;
        int flt_bit_size;
        int num;
        float num2;
        
        num = 0;
        num2 = 0.0;
        int_size = sizeof(int);
        flt_size = sizeof(float);
        int_bit_size = int_size * CHAR_BIT;
        flt_bit_size = flt_size * CHAR_BIT;
        for( i = int_bit_size - 1; i >= 0; --i ){
            printf( "%c", (num & (1u << i)) == 1 ? '1' : '0' );
        }
        printf( "\n" );
        unsigned int res = float_to_bit( num2 );
        printf( "res = %u\n", res );
        for( i = flt_bit_size - 1; i >= 0; --i ){
            printf( "%c", (res & (1u << i)) == 1 ? '1' : '0' );
        }
        printf( "\n" );
        
        return EXIT_SUCCESS;
    }
    
    unsigned int float_to_bit( float f ){
        union{
            float f;
            unsigned u;
        } temp;
        temp.f = f;
        return temp.u;
    }

输出:

下面是一种初始化二维字符串数组的方法:
    char vegetable[][9] = {"beet",
                       "barley",
                       "basil",
                       "broccoli",
                       "beans"};
    一种非常有用的方法是建立指针数组。字符串常量可以用作数组初始化值。编译器会正确地把各个字符存储于数组中的地址。因此:
    char *avegetable[] = {"carrot",
                      "celery",
                      "corn",
                      "cilantro",
                      "crispy friend patatoes"}; /*没问题*/
    注意它的初始化部分与字符“数组的数组”初始化部分是一样的。只有字符串常量才可以初始化指针数组。指针数组不能由非字符串的类型直接初始化:
    int *weights[] = { 
                    {1, 2, 3, 4, 5},
                    {6, 7},
                    {8, 9, 10} 
    }; /*无法成功编译*/
    如果想用这种方法对数组进行初始化,可以创建几个单独的数组,然后用这些数组名来初始化原先的数组。
    int row_1[] = {1, 2, 3, 4, 5, -1};
    int row_2[] = {6, 7, -1};
    int row_3[] = {8, 9, 10, -1};
    int *weight[] = {row_1, row_2, row_3};

/*
    ** doubly_dimension string array and string pointer array. 
    */
    #include <stdio.h>
    #include <stdlib.h>

int main( void ){
        const char *str = "beet";
        const char *str_2 = "barley";
        const char *str_3 = "basil";
        const char *str_4 = "broccoli";
        const char *str_5 = "beans";
    
        char vegetable[][9] = {
            "beet",
            "barley",
            "basil",
            "broccoli",
            "beans"
        };
        const char *vegetable_2[] = {
            "beet",
            "barley",
            "basil",
            "broccoli",
            "beans"
        }; /*没问题*/
    
        int i;
        int size = sizeof(vegetable) / sizeof(*vegetable);
        int size_2 = sizeof(vegetable) / sizeof(*vegetable);
        printf( "str = %p, str_2 = %p, str_3 = %p, str_4 = %p, str_5 = %p\n", str, str_2, str_3, str_4, str_5 );
        for( i = 0; i < size; ++i ){
            printf( "vegetable[%d] = %p, vegetable_2[%d] = %p\n", i, vegetable[i], i, vegetable_2[i] );
        }
    
        return EXIT_SUCCESS;
    }
输出:

C专家编程 第9章 再论数组 9.6 C语言的多维数组相关推荐

  1. C专家编程 第10章 再论指针 10.7 使用指针创建和使用动态数组

    使用指针创建和使用动态数组     当预先并不知道数据的长度时,可以使用动态数组.绝大多数具有数组的编程语言都能够在运行时设置数组的长度.它们允许程序员计算需要处理的元素的数目,然后创建一个刚好能容纳 ...

  2. C专家编程--读书笔记九 再论数组

    第九章 一.知识点 1.所有作为函数参数的数组名总是可以通过编译器转换成指针. 然而,数组和指针在编译器处理时是不同的,在运行时的表示形式也是不一样的,并可能产生不同的代码.对编译器而言,一个数组就是 ...

  3. 《C专家编程》第二章——这不是Bug,而是语言特性

    无论一门语言有多么流行或多么优秀,它总是存在一些问题,C语言也不例外.本章讨论的重点是C语言本身存在的问题,作者煞费苦心的用一个太空任务和软件的故事开头,也用另一个太空任务和软件的故事结尾,引人入胜. ...

  4. 二维数组转稀疏数组,写入文件后再读取文件,将内容转回二维数组

    该方法模拟的是将棋盘的位置保存到稀疏数组中,降低存储的数据量,通过写入磁盘做持久化,再读入后恢复棋盘内容. package com.moson.sparsearray;import java.io.* ...

  5. C专家编程 第6章 运动的诗章:运行时数据结构 6.1 a.out及其传说

    运动的诗章:运行时数据结构      编程语言理论的经典对立之一就是代码和数据的区别.有些语言(如Lisp)把二者视为一体.其他语言(如C语言)通常维持两者的区别.Internet蠕虫非常难以被人们所 ...

  6. C专家编程 第1章 C:穿越时空的迷雾 1.4 KR C

    时间:1978 人物:Steve Johnson 事件:编写了pcc这个可移植的C编译器 影响:形成了一代C编译器的基础. C语言的演化之路如图1-2所示. 1973-3(早期的C)--->19 ...

  7. C专家编程 第11章 你懂得C,所以C++不再话下 11.1 初识OOP

    C++之于C,就像Algol-68之于Algol          ---David L.Jones     如果你觉得C++还不够复杂,那你知道protected abstract virtual ...

  8. C专家编程 第1章 C:穿越时空的迷雾 1.9 阅读ANSI C标准,寻找乐趣和裨益

    阅读ANSI C标准,寻找乐趣和裨益      int foo(const char **p) {     }     编译这段代码,编译器会发出一条警告信息:      int main(int a ...

  9. C专家编程--读书笔记十 再论指针

    第十章 一.知识点 1.C标准规定%s说明符的参数必须是一个指向字符数组的指针.所以如: char *p = NULL; printf("%s", p); 这是不正确的.NULL是 ...

  10. C专家编程 第8章 为什么程序员无法分清万圣节和圣诞节 8.2 根据位模式构筑图形

    根据位模式构筑图形     图标(icon)或者图形(glyph)是一种小型的位模式映射于屏幕后产生的图像.一个位表示图像上的一个像素.如果一个位被设置,那么它所代表的像素就是"亮" ...

最新文章

  1. navicat 连接 mogodb 报错 requires authentication
  2. [你必须知道的.NET]第三十二回,,深入.NET 4.0之,Tuple一二
  3. 代码重构 防火墙 相关知识
  4. android notification自动消失,Notification点击事件和点击消失
  5. ubuntu 输入空白How to cd to folder name with spaces blank names (white space)
  6. python lxml模块解析html_用lxml解析HTML
  7. web pack的使用事项
  8. CSAPP-C1-计算机系统漫游
  9. bzoj 1132: [POI2008]Tro
  10. 百科知识 已知三角形三条边长,如何求解三角形的面积
  11. Mujoco xml建模
  12. 美容院预约管理系统管理员端用例测试
  13. 基于深度学习机器学习的中文期刊分类
  14. 标准单元库的corner简述
  15. 大陆引进《火影忍者》角色中文译名雷人出炉
  16. 深入浅出的web语义化理解
  17. 盘点中国未来最具潜力的IT培训学校前5名
  18. 对勾和叉怎么打_word文档中输入对号“√ ”和 叉号“×”的方法 word怎么打钩/打叉/半对半勾符号...
  19. OUC_软件工程_BLOG1
  20. StarRocks Parser 源码解析

热门文章

  1. python的十句名言_洗脑最厉害的10句名言名句
  2. Compose Modifier
  3. 前端 关于汇率的计算
  4. audio音频使用天坑
  5. FireFox更改缓存路径
  6. 尚来古籍——江氏族谱
  7. 使用php的curl爬去青果教务系统 课表(转)
  8. maven出现The server encountered an internal error () that prevented it from fulfilling this request.
  9. win7服务器未能登录怎么解决,win7系统出现User Profile Service服务未能登录的解决方法...
  10. SGG前台项目复习笔记