一维数组

数组名

数组名的值是一个指针常量, 也就是数组第一个元素的地址. 类型取决于数组类型.

注意这个值是指针常量, 不能修改其值.

只有两种情况下数组名并不用指针常量表示----当数组名作为 sizeof 操作符或单目 操作符&的操作数时. sizeof 返回整个数组的长度, & 取一个数组名的地址所产生 的是一个指针指向数组的数值.

int     a[10];
int     b[10];
int     *c;
...
c = &a[0];

表达式 &a[0] 是一个指向数组第一个元素的指针. 但那正是数 组名本身的值, 所 以 c = a;c = &a[0] 执行的任务完全一样.

b = a;是非法的, 不能使用赋值符把一个数组的 所有元素复制到另一个数组. 你 必须使用一个循环.

a = c; 也是非法的, 因为 a 的值是一个常量.

下标引用

*(b+3)的意思是间接访问数组第一个元素向后移 3 个整数长度的位置.

除了优先级外数组的下标引用和间接访问完全相同

int array[10];

2[array] 是合法的, 它会转换为 *(2+(array)), 内层括号是冗余的, 同时, 加 法运算的两个操作数是可以交换位置的, 所以和下面这个表达式完全一样:

*(array + 2)

也就是说, 和 array[2] 是相等的.

指针与下标

下标更容易理解, 尤其对于多维数组. 但是下标可能会印象运行时效率.

假定两种方法都是正确的, 下标绝不会币指针更有效率, 但指针有时回避下标更有效率

指针的效率

指针币下标更有效率 取决于它们被正确的使用.

  1. 当根据某个固定数目的增量在一个数组中移动时, 指针变量比下标引用效率搞.
  2. 声明为寄存器变量的指针通常比位于静态内存和堆栈中的指针效率高
  3. 那么必须在运行时求值的表达式较之助于&array[SIZE] 或 array+SIZE 这样的常量表达式代价更高.

数组和指针

指针和数组并不是相等的.

声明一个数组时, 编译器将根据声明说指定的元素数量为数组保留内存空间, 然后 再创建数组名, 它的值是一个常量, 指这段空间的起始位置.

声明一个指针变量时, 编译器只为指针本身留空间, 并不为任何整型值分配内存空间 , 指针变量并未被初始化为指向任何现有的内存空间, 如果是一个自动变量, 它 甚至不会被初始化.

所以对未初始化的数组名解引用合法, 对未初始化的指针解引用非法

作为函数参数的数组名

函数如果对数组名参数执行了下标引用, 实际上是对这个指针执行间接访问操作, 并且通过这种间接访问, 函数可以访问和修改调用程序的数组元素.

数组名参数是传递给函数的一份指向数组起始位置的指针的拷贝, 所以函数可以 自由的操作它的指针形参, 而不必担心会修改对应作为实参的指针.

所有参数都是通过传值方式传递的, 如果你传递了一个指向某变量的指针, 而对该函数执行了间接访问操作 那么函数就可以修改那个变量.

声明数组参数

编译器也接受数组形式的函数形参, 因此下面两个函数原型是相等的:

int strlen( char *string );
int strlen( char string[] );
这两个声明确实相等, 但只是在 当前这个上下文环境中.

因为函数并部位数组参数分配内存空间, 所以函数原型中一维数组形参无需写明它的 元素数目, 形参只是一个指针, 它指向的是已经在其他地方分配好的内存空间.

这种实现方法使 函数无法知道数组的长度, 如需要, 则需要另一个参数传递

初始化

数组初始化需要一系列的值, 这个系列的值位于一堆花括号中, 每个值之间用逗号分 隔:

int vector[5] = {10, 20, 30, 40, 50};

数组初始化的方式类似与标量变量的初始化方式 ---- 也就是取决于它们的存储类型.

存储出静态内存的数组只初始化一次, 也就是程序开始执行之前. 如果数组未被初始化, 数组元素的初始值将会自动设置为零

自动变量在缺省情况下是未初始化的. 如自动变量声明中给出了初始值, 每次执行 流进入自动变量所在的作用域时, 变量就被一条隐式的赋值语句初始化. 开销很大, 所以应当自己考虑数组位于一个函数时, 每次对数组的重新初始化是不是值得.

不完整的初始化

int vector[5] = {1, 2, 3, 4, 5, 6};
int vector[5] = {1, 2, 3, 4};

上面, 第一个声明是错误的. 第2个是合法的, 没有提供初始值的元素初始化为 0.

只允许省略最后几个初始值

自动计算数组长度

int vector[] = {1, 2, 3, 4, 5};

如声明未指定数组长度, 编译器会自动计算刚好能够容纳所有初始值的长度. 如果初始值列表经常修改, 这个技巧尤其有用

字符数组初始化

C 语言标准提供了一种快速方法用于初始化字符数组:

char message[] = "Hello";

等价于:

char message[] = {'H', 'e', 'l', 'l', 'o'};

上面实际上不是字符串常量.

char message1[] = "Hello";
char *message2 = "Hello";

上面两个表达式,前者初始化一个字符数组的元素, 后者则是一个真正的字符常量. 后者会在最后追加 '\0' 元素.

多维数组

如果某个数组的维数不止一个, 它就被称为多维数组.

int     matrix[6][10];
上面创建了一个包含60个元素的矩阵. 要理解是 6 行每行 10个元素, 还是 10 行 每行 6 个元素. 考虑下面声明:
int         a;
int         b[10];
int         c[6][10];
int         d[3][6][10];
a 是一个简单的整型; b 包含了10个整型元素; c 包含了 6个元素, 每个元素本身 包含 10 个整型元素; d 是包含了3个元素的数组, 每个元素都是包含6个元素的数组, 而这6个元素中每一个都是包含 10 个整型元素的数组. 简洁的说, d 是一个 3 排 6 行 10 列的整型三维数组.

存储顺序

考虑下面这个数组:

int array[3];

它包含3个元素, 如下所示

+---------------+-------------------+-----------------+
|               |                   |                 |
+---------------+-------------------+-----------------+

下面是这个新的声明:

int array[3][6];

下面是他在内存中的存储形式:

+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|  +  +  +  +  +  |  +  +  +  +  +  |  +  +  +  +  +  +  |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

| 整个框代表第一维的三个元素, + 划分了第二维的6个元素. 按照从左到又的顺序, 上面每个元素的下标值分别是:

0,0     0,1     0,2     0,3     0,4     0,5
1,0     1,1     1,2     1,3     1,4     1,5
2,0     2,1     2,2     2,3     2,4     2,5

上面说明了数组元素的存储顺序(torage order). 在 C 中, 多维数组的元素存储顺序 按照最右边的下标率先变化的原则, 称为主序(row major order).

只要每次都坚持同一种方法 matrix[6][10] 是 6 行10列还是10行6列都是可行的.

数组名

多维数组和一维数组一样, 数组名的值是一个指针常量, 类型是指向元素类型的指针. 唯一的区别是多维数组的第一维实际上是另一个数组.

所以多维数组的数组名是一个指向第一维数组的指针.

下标

如果要标识多维数组的某个元素, 必须按照与数组声明相同顺序的为每一维都提供一 个下标, 而且位于单独的一堆方括号内.

下标实际上只是间接访问表达式的一种伪装形式, 即使在多维数组中也是如此

int matrix[3][10];

matirx 的类型是"指向包含10个整型元素数组的指针", 它指向包含10个整型元素的 第一个子数组.

matirx + 1 也是一个 "指向包含10个整型数组元素数组的指针", 但它指向 matrix 的另一行. 因为 1 这个值根据包含 10 个整型元素的数组的长度进行调整, 所以 它指向 matrix 的下一行

所以 *(matrix + 1) 事实上标识了一个包含10个整型元素的子数组. 它指向数组的 第1个元素.

*(matrix + 1) + 5 前一个表达式是一个指向整型值的指针, 所以 5 这个值是根据 整型的长度进行调整, 整个表达式的结果是一个指针, 它指向的位置比原先那个 表达式的位置向后移动了 5 个整型元素.

*(*(matrix + 1) + 5) 用来访问 matrix[1][5] 这个元素.

指向数组的指针

int vector[10], *vp = vector;
int matrix[3][10], *mp = matrix;

第一个声明是合法的.

第二个声明是非法的. 它正确的创建了 matrix 数组, 并把 mp 声明为一个指向整型 的指针. 但是 mp 的初始化不正确, 因为 matrix 并不是一个指向整型的指针, 而是一个指向整型数组的指针. 该如何声明呢:

int (*p)[10];

p 是一个指向整型数组的指针. 在声明中加上初始化:

int (*p)[10] = matrix;

它使p指向 matrix 的第一行. p 是一个指向拥有 10 个振兴元素的数组指针, 当你把 p 与一个整数相加时, 该整数 值首先根据 10 个整型值的长度进行调整, 然后再执行加法, 所以我们可以使用指针 一行行地在 matrix 中移动.

如果需要一个指针逐个访问整型元素该怎么办? 下面两个声明都创建了一个简单的 整型指针, 并已两种不同的方式初始化, 指向 matrix 的第一个整型元素

int *pi = &matrix[0][0];
int *pi = matrix[0];

增加这个指针的值使它指向下一个整型元素.

作为函数参数的多维数组

作为函数参数的多维数组名的传递方式和一维数组名相同----实际传递的是指向数组 第1个元素的指针. 两者的区别在于, 多维数组的每个元素本身是另外一个数组, 编译器需要知道它的位数, 以便函数相残的下标表达式进行求值.

一维数组的原型可以是下面任何一种:

void func1(int *vec);
void func1(int vec[]);

假设有二位数组

int matrix[3][10];
func2(matrix);
二维数组可以用下面两种形式的任何一种
void func2(int (*mat)[10]);
void func2(int mat[][10]);
编译器必须知道第二个以及以后各个维度的长度才能对各个下标进行求值, 因此原型 中必须声明这些维的长度.

把 func2 协程下面这样的原型是不正确的:

void func2(int **mat);

初始化

多维数组的初始化列表有两种形式, 第一种是只给出一个长长的初始值列表:

int matrix[2][3] = {100, 101, 102, 110, 111, 112};

多维数组的存存储顺序是根据最右边的下标率先变化的原则确定.

第二种方法是基于多维数组实际上的师傅元素的一维数组这个概念:

int tow_dim[3][5];

初始化为:

int tow_dim[3][5] = {
    {00, 01, 02, 03, 04},
    {10, 11, 12, 13, 14},
    {20, 21, 22, 23, 24}
};

数组长度自动计算

int tow_dim[][5] = {
    {00, 01, 02},
    {10, 11},
    {20, 21, 23, 23}
};

如果要省略后面的维数, 需要每个列表的子初始列表至少有一个要完整的形式出现.

指针数组

除了类型以外, 指针变量和其他变量很相似, 可以声明指针数组:

int *api[10];

就是一个包含 10 个 int 型指针的数组.