第20题  设有下面程序

c_choice20-01

则程序执行后的输出结果是

­      A    def,a,glmno,b,

­      B    abc,a,mno,b,

­      C    def,a,mno,b,

­      D    def,a,mno,x,

答案  C

程序解析

考查二维字符数组的访问方法。

下面变量声明:

­        char a[5][6] = {″def″, ″abc″, ″khij″, ″glmno″, ″abxy″};

定义5行6列二维字符型数组a,分配存储,该二维数组等价于长度为5的一维数组,该一维数组的元素是长度为6的一维字符型数组,而每个长度为6的一维字符型数组(称为行数组)可以存储长度不超过5的字符串(因串尾标记空字符′\0′不计入串长,但要占用一个字符位置),这里用5个字符串分别初始化5个行数组。至此,当前存储状态如下图所示,无意义垃圾值以空白表示。机器内存是一维连续字节阵列,数组元素连续占据一段内存,数组下标从0开始。二维数组a由5个长度为6的一维字符型数组组成,其存储结构如下图所示,这5个一维字符型数组连续占据一段内存,二维数组名a这个指针指向首元素即第0个长度为6的一维字符型数组,以红色矩形框表示。

c_choice20-02

C语言把数组名定义为指向首元素的常量指针,只供引用但不能被改变,该指针的存储空间是(在含有数组名的表达式求值过程中)临时自动分配的,并不是数组存储块的组成部分,因此,二维数组名a是指向其第0元素(长度为6的一维字符型数组)的常量指针,必须通过指针式∗(a+i)才能访问二维数组a的第i元素(长度为6的一维字符型数组),但也可采用下标式a[i]访问a的第i元素,a[i]会自动转换为∗(a+i)。因为∗(a+i)或a[i]仅代表a的第i元素(长度为6的一维字符型数组),是一维数组名(见下图最右边),可继续套用数组名定义,因此∗(a+i)或a[i]是指向其第0元素(字符型值)的常量指针(见上图),必须通过指针式∗(∗(a+i)+j)才能访问一维字符型数组∗(a+i)或a[i]的第j字符元素,但也可采用下标式(∗(a+i))[j]或a[i][j]访问一维字符型数组∗(a+i)或a[i]的第j字符元素,(∗(a+i))[j]或a[i][j]会自动转换为∗(∗(a+i)+j)。上面推理是比较费解的,对考生而言,只要记住下面的示意图,关于二维数组的访问问题均可迎刃而解!一维数组名a[0]、a[1]、…、a[4]都是字符型指针,分别指向各一维字符型数组的首字符(见上图),这些指针均可加上一个常数,如加1、2、3、…分别指向其后的第1、2、3、…个字符。

c_choice20-03

执行下面唯一语句

­        printf(″%s,%c,%s,%c″, ∗a, ∗∗(a+1), a[3]+2, ∗(a[4]+1));

第1个格式符%s匹配字符指针∗a,将∗a所指的字符串输出,即依次输出∗a所指字符及其后续字符直到遇见串尾标记′\0′为止,而∗a等价于∗(a+0)即a[0],从上图可知,字符指针∗a即a[0]指向字符串″def″的首字符d′,故输出def后跟逗号,即def,

第2个格式符%c匹配字符变量∗∗(a+1),将∗∗(a+1)所代表的字符输出,∗∗(a+1)等价于∗(∗(a+1)+0),即a[1][0],从上图可知,a[1][0]代表字符′a′,故输出a后跟逗号,即a,

第3个格式符%s匹配字符指针a[3]+2,将a[3]+2所指的字符串输出,即依次输出a[3]+2所指字符及其后续字符直到遇见串尾标记′\0′为止,从上图可知,字符指针a[3]指向字符串″glmno″的首字符′g′,而字符指针a[3]+2指向字符串″glmno″的首字符′g′后的第2个字符′m′,故输出mno后跟逗号,即mno,

第4个格式符%c匹配字符变量∗(a[4]+1),将∗(a[4]+1)所代表的字符输出,∗(a[4]+1)等价于∗(∗(a+4)+1),即a[4][1],从上图可知,a[4][1]代表字符′b′,故输出b后跟逗号,即b,

将上面的4个输出片段连在一起就是def,a,mno,b,故答案C正确。

知识点

① C语言把数组名定义为指向首元素的常量指针,只供引用但不能被改变,该指针的存储空间是(在含有数组名的表达式求值过程中)临时自动分配的,并不是数组存储块的组成部分,因此,二维数组名a是指向其第0元素(长度为6的一维字符型数组)的常量指针,必须通过指针式∗(a+i)才能访问二维数组a的第i元素(长度为6的一维字符型数组),但也可采用下标式a[i]访问a的第i元素,a[i]会自动转换为∗(a+i)。因为∗(a+i)或a[i]仅代表a的第i元素(长度为6的一维字符型数组),是一维数组名,可继续套用数组名定义,因此∗(a+i)或a[i]是指向其第0元素(字符型值)的常量指针,必须通过指针式∗(∗(a+i)+j)才能访问一维字符型数组∗(a+i)或a[i]的第j字符元素,但也可采用下标式(∗(a+i))[j]或a[i][j]访问一维字符型数组∗(a+i)或a[i]的第j字符元素,(∗(a+i))[j]或a[i][j]会自动转换为∗(∗(a+i)+j)。

② 一般地,若指针p指向字符数组的某个字符,k为非负整型值,则p+k仍为字符指针且指向p所指字符∗p后面的第k个字符,而p−k仍为字符指针且指向p所指字符∗p前面的第k个字符,这一准则对于任何类型的数组均成立(与数组元素所占字节数无关)!

③ 在C语言中,指针和数组的关系十分密切,表现为:只要数组名在表达式中出现,立即自动转换为指向首元素的指针,但该指针只能引用、不能被赋值,故称常量指针;数组元素一律通过指针访问,设k是非负整型值,若指针p指向某数组元素,则通过∗(p+k)访问指针p所指元素后面的第k个元素,通过∗(p−k)访问指针p所指元素前面的第k个元素,若k为0则退化为∗p,访问p所指元素;设k是整型值,指针引用式∗(p+k)等价于下标引用式p[k],p[k]只是形式,会自动转换为∗(p+k) 来实现。

④ 数组首元素的下标为0。

练习题

试改写printf语句格式串后的实参,为自己设计一道孪生题,举一反三。

孪生题1  设有下面程序

c_choice20-04

则程序执行后的输出结果是

­      A    effbccijjnooxyy

­      B    xyynooijjbcceff

­      C    fefcbcjijonoyxy

­      D    yxyonojijcbcfef

答案  D

程序解析

下面的声明

­        char a[5][6] = {″def″, ″abc″, ″khij″, ″glmno″, ″abxy″};
­        char ∗s[5];
­        int i, len;

定义5行6列二维字符型数组a,分配存储,该二维数组等价于长度为5的一维数组,该一维数组的元素是长度为6的一维字符型数组,而每个长度为6的一维字符型数组(称为行数组)可以存储长度不超过5的字符串(因串尾标记空字符′\0′不计入串长,但要占用一个字符位置),这里用5个字符串分别初始化5个行数组。定义长度为5的字符型指针数组s,分配存储,不作初始化,可以存储5个字符型指针。定义整型变量i、len,分配存储,未作初始化,i用于循环控制与索引,len用于存储字符串长度。至此,当前存储状态如下图所示,无意义垃圾值以空白表示。机器内存是一维连续字节阵列,数组元素连续占据一段内存,数组下标从0开始。二维数组a由5个长度为6的一维字符型数组组成,其存储结构如下图所示,这5个一维字符型数组连续占据一段内存,二维数组名a这个指针指向首元素即第0个长度为6的一维字符型数组,以红色矩形框表示。

c_choice20-07

C语言把数组名定义为指向首元素的常量指针,只供引用但不能被改变,该指针的存储空间是(在含有数组名的表达式求值过程中)临时自动分配的,并不是数组存储块的组成部分,因此,二维数组名a是指向其第0元素(长度为6的一维字符型数组)的常量指针,必须通过指针式∗(a+i)才能访问二维数组a的第i元素(长度为6的一维字符型数组),但也可采用下标式a[i]访问a的第i元素,a[i]会自动转换为∗(a+i)。因为∗(a+i)或a[i]仅代表a的第i元素(长度为6的一维字符型数组),是一维数组名,可继续套用数组名定义,因此∗(a+i)或a[i]是指向其第0元素(字符型值)的常量指针(见上图),必须通过指针式∗(∗(a+i)+j)才能访问一维字符型数组∗(a+i)或a[i]的第j字符元素,但也可采用下标式(∗(a+i))[j]或a[i][j]访问一维字符型数组∗(a+i)或a[i]的第j字符元素,(∗(a+i))[j]或a[i][j]会自动转换为∗(∗(a+i)+j)。一维数组名a[0]、a[1]、…、a[4]都是字符型指针,分别指向各一维字符型数组的首字符(见上图),这些指针均可加上一个常数,如加1、2、3、…分别指向其后的第1、2、3、…个字符。

下面执行唯一for循环语句

­      for (i = 4; i >= 0; i−−) {
­            s[i] = a[i];
­            len = strlen(s[i]);
­            printf(″%c″, ∗(s[i]+len−1));
­            printf(″%s″, s[i]+len−2);
­      }

这是递减型循环,循环索引变量i依次取值4、3、2、1、0、−1,当i取−1时,循环因条件i >= 0不再成立而终止,故循环体重复执行5轮。

第1轮对应i取4,执行赋值语句s[i] = a[i];即s[4] = a[4];使字符指针s[4]指向指针a[4]所指的字符′a′;执行语句len = strlen(s[i]);调用库函数strlen求得s[4]所指字符串″abxy″的长度4并赋于变量len;调用库函数printf(″%c″, ∗(s[i]+len−1));输出指针s[i]+len−1即s[4]+4−1所指的字符′y′,s[4]指向字符′a′,s[4]+4指向字符′a′后面第4个字符′\0′,s[4]+4−1指向字符′\0′前面第1个字符′y′,一般地,若指针p指向字符数组的某个字符,k为非负整型值,则p+k仍为字符指针且指向p所指字符∗p后面的第k个字符,而p−k仍为字符指针且指向p所指字符∗p前面的第k个字符,这一准则对于任何类型的数组均成立(与数组元素所占字节数无关)!∗(s[i]+len−1)是对指针s[i]+len−1所指字符变量的引用,恰好与格式串%c匹配,于是输出字符y;调用库函数printf(″%s″, s[i]+len−2);输出字符指针s[i]+len−2即s[4]+4−2所指的字符′x′及其后续字符直到遇见字符′\0′,于是输出xy,连在一起本轮输出yxy。格式串%s要求匹配字符指针,而格式串%c要求匹配字符变量。可参考下图理解执行过程:

c_choice20-05

第2轮对应i取3,执行赋值语句s[i] = a[i];即s[3] = a[3];使字符指针s[3]指向指针a[3]所指的字符′g′;执行语句len = strlen(s[i]);调用库函数strlen求得s[3]所指字符串″glmno″的长度5并赋于变量len;调用库函数printf(″%c″, ∗(s[i]+len−1));输出指针s[i]+len−1即s[3]+5−1所指的字符′o′,于是输出字符o;调用库函数printf(″%s″, s[i]+len−2);输出字符指针s[i]+len−2即s[3]+5−2所指的字符′n′及其后续字符直到遇见字符′\0′,于是输出no,连在一起本轮输出ono。可参考下图理解执行过程:

c_choice20-06

同理,第3轮对应i取2,输出jij,第4轮对应i取1,输出cbc,第5轮对应i取0,输出fef,全部连在一起就是正确答案yxyonojijcbcfef

返回