C语言非常道
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

5.1.4 认识变长数组

在C99之前,sizeof运算符并不求值它的操作数,而仅仅是提取它的类型。换句话说,该运算符的结果在程序翻译期间就已经得到了,而且是一个整型常量。

在下面的程序中,在D1处声明了变量siz,初始化为表达式sizeof 0的值。因为0是个整型常量,其类型为int,故该表达式等效于sizeof(int),也就是得到int类型的大小。

在语句S2中,sizeof运算符的操作数是一个赋值表达式,但实际上并不求值。也就是说,它并不会真的把1赋给左值c。

因为sizeof运算符的优先级高于赋值运算符,所以要将表达式c = 1用括号变成基本表达式(括住的表达式),否则它将等价于(sizeof c)= 1,这在语法上将导致一个错误,因为sizeof c的结果并不是一个左值。

另一方面,每个表达式都有类型,表达式的类型也是该表达式的值的类型。之所以可以提取赋值表达式c = 1的类型,是因为赋值表达式的类型就是赋值运算符的左操作数的类型。在这里,左值c的类型为signed char,故sizeof(c = 1)等效于sizeof(signed char)。

C语言规定,当sizeof运算符作用于char、signed char和unsigned char类型的操作数时,其结果为1。因此,sizeof(c = 1)的结果是1。

              /***************c0505.c***************/
              unsigned int var_arr(int n)
              {
                  signed char va [n];

                  return sizeof va;                                //S1
              }

              int main(void)
              {
                  unsigned int n = 30, siz = sizeof 0;     //D1

                  signed char c = 0;
                  siz = sizeof(c = 1);                           //S2

                  siz = sizeof(int [30]);                      //S3
                  siz = var_arr(n);                               //S4
                  siz = sizeof(int [++ n]);                   //S5

                  siz = sizeof sizeof c;                         //S6
              }

为了方便描述数组类型,我们也可以使用类型名。回忆一下什么是类型名,类型名可以从声明中抽取,数组类型是以元素类型和元素数量为特征的,利用这一特点不需要借助于声明就可以写出类型名。比如说,数组a有20个元素,且元素的类型是int,则我们称a的类型是int [20]。

C语言规定,如果sizeof的操作数是数组类型,则该运算符的结果是数组的大小,以字节计。在这里,操作数既可以是数组类型的变量,也可以是数组类型名。因此,在接下来的语句S3中,因为sizeof运算符的操作数是一个类型名,代表着“具有30个元素,且元素类型是int”的数组类型,故该运算符的结果是sizeof(int)×30。

在C99之前,声明一个数组时,数组的大小必须是一个整型常量。然而从C99开始这一规则被打破了,可以在数组的声明中使用变量来指定它的大小,以这种方式声明的数组称为变长数组。

在上面的程序中,我们在函数var_arr里声明了一个变长数组va,它的长度来自一个变量(函数的参数)n而不是常量。显然,因为数组的大小直接依赖于函数调用时传入的参数值,你无法预测传入的值会是什么。这就是说,变长数组的大小不能在程序翻译期间得到,而只有在程序实际运行时才能确定。

对于一个完整的数组类型来说,元素的数量和元素的类型缺一不可。要想从一个变长数组类型的操作数中提取类型信息(元素的数量和元素的类型),则sizeof运算符必须在程序实际运行时才能开始工作。因此,在语句S1中,表达式sizeof va的结果不是在程序翻译期间得到,而是要推迟到当前语句实际执行的时候。此时,变量va已经声明,n的值和数组的大小都已经确定。

无论如何,因为数组va的元素类型是signed char,所以在语句S4中,函数调用var_arr的返回值和变量n的值相同,都是30。然后,这个30被赋给变量siz。

接下来,在语句S5中,运算符sizeof的操作数是一个类型名,但它描述的是变长数组,其大小需要在程序运行期间求值表达式++ n才能确定。C语言规定,如果运算符sizeof的操作数是变长数组类型,则求值该操作数。

前缀++表达式的值是其操作数递增后的值,所以,表达式++ n的值是31,求值的副作用是变量n的值递增为31。既然元素的数量是31个,而元素的类型是int,所以运算符sizeof的结果是sizeof(int)×31。

运算符sizeof是从右往左结合的,因此,在最后一条语句S6中,表达式sizeof sizeof c等价于表达式sizeof(sizeof c)。但是,这里并不求值表达式c也不求值表达式sizeof c。我们说过,运算符sizeof有自己的结果类型(是一个无符号整数类型),所以左边那个sizeof将返回这个类型的大小。在我的机器上,这个类型等价于unsigned long long int,所以上述语句等效于

              siz = sizeof(unsigned long long int);

数组声明中的初始化器可以少于元素的数量,也可以等于元素的数量,但就是不能多于元素的数量。