10.数组和指针

数组和指针

10.1数组

10.1.2指定初始化器(C99)

C99增加了一个新特性:指定初始化器。利用该特性可以初始化指定的数组元素。
例如,只初始化数组中的最后一个元素:

int arr[6] = {[5] = 212};	// 把arr[5]初始化为212

其他元素都会被设置为0。另一方面:

#include <stdio.h>

#define MONTHS 12

int main(void) {
    int days[MONTHS] = {31, 28, [4] = 31, 30, 31, [1] = 29};
    int i;

    for (i = 0; i < MONTHS; i++) {
        printf("%2d  %d\n", i + 1, days[i]);
    }

    return 0;
}

/*output:
 1  31
 2  29
 3  0
 4  0
 5  31
 6  30
 7  31
 8  0
 9  0
10  0
11  0
12  0
*/

指定初始化器有两个重要的特性:

  1. 如果指定初始化器后面有更多的值,那么后面这些值将被用于初始化指定元素后面的元素。也就是说,在days[4]被初始化为31后,days[5]days[6]将分别被初始化为30和31。
  2. 如果再次初始化指定的元素,那么最后的初始化将会取代之前的初始化。

10.1.5指定数组的大小

在C99标准之前,声明数组时只能在方括号中使用整型常量表达式,即由整型常量构成的表达式。sizeof表达式被视为整型常量,但是(与c++不同)const值不是。另外,表达式的值必须大于0。
另一方面:

int n = 5;
int m = 8;

float a8[n];	// C99之前不允许
float a9[m];	// C99之前不允许

C99标准允许变长数组或简称VLA的存在(C11放弃了这一创新的举措,把VLA设定为可选,而不是语言必备的特性)。

10.2多维数组

10.2.2其他多维数组

可以把一维数组想象成一行数据,把二维数组想象成数据表,把三维数组想象成一叠数据表

10.3指针和数组

数组名是数组首元素的地址。在c中,指针加1指的是增加一个存储单元。对数组而言,这意味着加1后的地址是下一个元素的地址,而不是下一个字节的地址。这是为什么必须声明指针所指向对象类型的原因之一。只知道地址不够,因为计算机要知道储存对象需要多少字节(即使指针指向的是标量变量,也要知道变量的类型,否则*pt就无法正确地取回地址上的值)。
实际上,c语言标准在描述数组表示法时确实借助了指针。也就是说,定义ar[n]的意思是*(ar + n)。可以认为*(ar + n)的意思是:到内存的ar位置,然后移动n个单元,检索储存在那里的值。

10.4函数、数组和指针

int func(int ar[], int n);

其中,ar并不是数组本身,它是一个指向数组首元素的指针。因此,其大小是指针的大小。

10.7指针和多维数组

处理多维数组的函数要用到指针,所以在使用这种函数之前,先要更深入地学习指针。例如,int arr[4][2],数组名arr是该数组首元素的地址。在本例中,arr的首元素是一个内含两个int值的数组,所以arr是这个内含两个int值的数组的地址。下面,从指针的属性进一步分析:

  • 因为arr是数组首元素的地址,所以arr的值和&arr[0]的值相同。而arr[0]本身是一个内含两个整数的数组,所以arr[0]的值和它的首元素(一个整数)的地址(即&arr[0][0]的值)相同。简而言之,arr[0]是一个占用一个int大小对象的地址,而arr是一个占用两个int大小对象的地址。由于这个整数和内含两个整数的数组都开始于同一个地址,所以arrarr[0]的值相同。
  • 给指针或地址加1,其值会增加对应类型大小的数值。在这方面,arrarr[0]不同,因为arr指向的对象占用了两个int大小,而arr[0]指向的对象只占用一个int大小。因此,arr + 1arr[0] + 1的值不同。
  • 解引用一个指针或在数组名后使用[]运算符,得到引用对象代表的值。因为arr[0]是该数组首元素(arr[0][0])的地址,所以*(arr[0])表示储存在arr[0][0]上的值(即一个int类型的值)。与此类似,*arr代表该数组首元素(arr[0])的值,但是arr[0]本身是一个int类型值的地址。该值的地址是&arr[0][0],所以*arr就是&arr[0][0]。对两个表达式应用解引用运算符表明,**arr*&arr[0][0]等价,这相当于arr[0][0],即一个int类型的值。简而言之,arr是地址的地址,必须解引用两次才能获得原始值。
#include <stdio.h>

int main(void) {
    int arr[4][2] = {
            {2, 4},
            {6, 8},
            {1, 3},
            {5, 7}
    };

    printf("   arr = %p,    arr + 1 = %p\n",
           arr, arr + 1);
    printf("arr[0] = %p, arr[0] + 1 = %p\n",
           arr[0], arr[0] + 1);
    printf("  *arr = %p,   *arr + 1 = %p\n",
           *arr, *arr + 1);
    printf("arr[0][0] = %d\n", arr[0][0]);
    printf("  *arr[0] = %d\n", *arr[0]);
    printf("    **arr = %d\n", **arr);
    printf("      arr[2][1] = %d\n", arr[2][1]);
    printf("*(*(arr+2) + 1) = %d\n", *(*(arr + 2) + 1));

    return 0;
}
/*output:
   arr = 005DF9E8,    arr + 1 = 005DF9F0
arr[0] = 005DF9E8, arr[0] + 1 = 005DF9EC
  *arr = 005DF9E8,   *arr + 1 = 005DF9EC
arr[0][0] = 2
  *arr[0] = 2
    **arr = 2
      arr[2][1] = 3
*(*(arr+2) + 1) = 3
*/

10.7.3函数和多维数组

注意,下面的声明不正确:

int func(int ar[][], int rows);

由于编译器会把数组表示法转换成指针表示法。例如,编译器会把ar[1]转换成ar + 1。编译器对ar + 1求值,要知道ar锁指向的对象大小。下面的声明:

int func(int ar[][4], int rows);

表示ar指向一个内含4个int类型值的数组,所以ar + 1的意思是,该地址加上16个字节。如果第2对方括号是空的,编译器就不知道该怎样处理。
也可以在第1对方括号中写上大小,但是编译器会忽略该值。一般而言,声明一个指向N维数组的指针时,只能省略最左边方括号中的值:

int func(int ar[][12][20][30], int rows);

因为第1对方括号只用于声明这是一个指针,而其他的方括号则用于描述指针所指向数据对象的类型。下面的声明与该声明等价:

int func(int (*ar)[12][20][30], int rows);

10.8变长数组(VLA)

C99新增了变长数组(VLA),允许使用变量表示数组的维度,但是有一些限制,即变长数组必须是自动存储类别,这意味着无论在函数中声明还是作为函数形参声明,都不能使用staticextern存储类别说明符。而且,不能在声明中初始化它们。最终,C11把变长数组作为一个可选特性,而不是必须强制实现的特性。
注意,变长数组不能改变大小,其中的变不是指可以修改已创建数组的大小。一旦创建了变长数组,它的大小则保持不变。这里的变指的是:在创建数组时,可以使用变量指定数组的维度。

10.9复合字面量

对于数组,复合字面量类似数组初始化列表,前面是用括号括起来的类型名:

// 匿名数组,int[2]即是复合字面量的类型名。
(int[2]) {10, 20};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值