C语言指针就应该这么学(指针和数组典型笔试题解析)第一弹

一.  指针和一维数组典型笔试题解析

1.1 指针和整型数组典型笔试题解析

判断下面的程序中,每个printf打印的值是多少?

int main()

{

        int a [] = { 1 , 2 , 3 , 4 };
        printf ( "%d\n" , sizeof ( a ));  
        printf ( "%d\n" , sizeof ( a + 0 ));  
        printf ( "%d\n" , sizeof ( * a )); 
        printf ( "%d\n" , sizeof ( a + 1 ));
        printf ( "%d\n" , sizeof ( a [ 1 ]));
        printf ( "%d\n" , sizeof ( & a ));
        printf ( "%d\n" , sizeof ( *& a ));
        printf ( "%d\n" , sizeof ( & a + 1 ));
        printf ( "%d\n" , sizeof ( & a [ 0 ]));
        printf ( "%d\n" , sizeof ( & a [ 0 ] + 1 ));
        return 0;

}

  • printf("%d\n",sizeof(a))打印结果的解析

上述程序中,定义了a为包含4个整型元素的整型数组。数组名一般情况下表示数组首元素的地址,但在两种情况下表示整个数组,其一是sizeof(数组名),其二是&(数组名)。在第一个printf中,数组名a直接放入sizeof后面的括号中,a表示整个数组,故应打印整个数组的大小(字节为单位)。

a中包含4个整型元素,每个元素占4个字节,故打印结果为16。

  • printf("%d\n",sizeof(a+0))打印结果的解析 

刚才提到,sizeof(数组名)是计算整个数组元素的大小。但是,请注意,当且仅当数组名直接单独放入sizeof后面的括号中时,数组名才代表整个数组元素的大小。这里的printf要求打印sizeof(a+0)的值,将a+0放入到sizeof后面的括号,而非仅放入a。因此,此时的a表示数组首元素地址,a+0也是数组首元素地址。

地址的大小,即指针变量的大小,在32位编译环境下为4byte,在64位编译环境下为8byte。因此,这里的printf打印的结果为4或8。

  • printf("%d\n",sizeof(*a))的打印结果解析

*a就是对指针变量进行解应用操作,这里的a表示数组首元素地址,*a取得数组首元素,为整型变量,大小为4byte,打印结果为4。

  • printf("%d\n",sizeof(a+1))的打印结果解析

a+1表示数组第二个元素的地址,打印结果为4 或 8。

  • printf("%d\n",sizeof(a[1]))的打印结果解析

a[1]表示数组的第二个元素,整型变量,大小为4byte,打印结果为4。

  • printf("%d\n",sizeof(&a))的打印结果解析

&a表示整个数组元素的地址,对于地址(指针变量),无论指向的变量是何种元素,其大小均为4或8,故这里的打印结果为4或8。

  • printf("%d\n",sizeof(*&a))的打印结果解析

&a表示整个数组元素的地址,整个数组元素占16byte的内存空间,*&a表示对整个数组指针进行解应用,解应用的权限为整个数组元素占用内存空间的大小,即解应用权限为16byte。因此,这里的打印结果为16。

  • printf("%d\n",sizeof(&a+1))的打印结果解析

&a表示取出整个数组元素的地址,&a+1表示指针向着高地址走1步,由于数组a占16byte的内存空间,+1操作使指针变量的值增大16,故&a+1执行数值最后一个元素后面那个内存空间的位置,如图1.1所示。

图1.1 &a+1指向位置解析

 &a+1本质是为地址(指针变量),因此这里的打印结果为4或8。

注意:这里应该着重理解&a+1指向哪个位置,而不是仅仅指导&a+1表示地址(指针变量)

  • printf("%d\n",sizeof(&a[0]))的打印结果解析

&a[0]取出数组首元素地址,打印结果为4或8。

  • printf("%d\n",sizeof(&a[0]+1))打印结果解析

取出数组第二个元素的地址,打印结果为4或8。

1.2 指针和字符型数组典型笔试题解析

1.2.1 指针和字符型数组题目1

判断下面的程序中,每个printf打印的值是多少?

int main()

{

        char arr [] = { 'a' , 'b' , 'c' , 'd' , 'e' , 'f' };
        printf ( "%d\n" , sizeof ( arr ));
        printf ( "%d\n" , sizeof ( arr + 0 ));
        printf ( "%d\n" , sizeof ( * arr ));
        printf ( "%d\n" , sizeof ( arr [ 1 ]));
        printf ( "%d\n" , sizeof ( & arr ));
        printf ( "%d\n" , sizeof ( & arr + 1 ));
        printf ( "%d\n" , sizeof ( & arr [ 0 ] + 1 ));
        printf ( "%d\n" , strlen ( arr ));
        printf ( "%d\n" , strlen ( arr + 0 ));
        printf ( "%d\n" , strlen ( * arr ));
        printf ( "%d\n" , strlen ( arr [ 1 ]));
        printf ( "%d\n" , strlen ( & arr ));
        printf ( "%d\n" , strlen ( & arr + 1 ));
        printf ( "%d\n" , strlen ( & arr [ 0 ] + 1 ));
        return 0;

}

  •  printf("%d\n", sizeof(arr))打印结果的解析

打印整个arr数组的大小(byte),arr为字符型数组,有6个元素,每个元素占1byte的内存空间,故这里的打印结果为6。

注意:要区分字符型数组初始化的两种方法

  • 方法1:arr[ ] = { 'a', 'b', 'c', 'd', 'e', 'f' },使用这种方法初始化会在内存中为arr数组开辟6byte的空间(后面不会补'\0'),如图1.2所示。
  • 方法2:arr[ ] = "abcdef",使用这种方法初始化会在最后这个字符后面补'/0',如图1.3所示。
图1.2  使用方法1初始化字符型数组的内存空间示意图
图1.3  使用方法2初始化字符型数组的内存空间示意图
  • printf("%d\n", sizeof(arr+0))的打印结果解析

打印数组首元素地址的大小,即打印结果为4或8。

  • printf("%d\n", sizeof(*arr)) 和 printf("%d\n", sizeof(arr[1])) 的打印结果解析

解应用,均是获取数组中的某个元素,打印结果均为1。

  • printf("%d\n", sizeof(&arr))和printf("%d\n", sizeof(&arr+1))的打印结果解析

&arr是获取数组arr的地址,&arr+1指向数组最后面那个元素后面的内存空间,也是地址。故两个printf的打印结果均为4或8。

  • printf("%d\n", sizeof(&arr[0]+1))的打印结果解析
&arr[0]+1获取数组第二个元素的地址,打印结果为4或8。
在对后面几个printf进行解析之前,我首讲解求解字符串长度库函数stren。strlen后面括号中的变量实际上是一个地址(指针变量),strlen处该地址开始查找,直到遇见'\0'就终止查找。也就是说,括号里的变量不一定就是数组名,也可以是数组某个元素的地址或是其余的指针变量。但要注意,如果传入的不是字符串中某个元素的地址,则有可能在向后寻找的过程中找不到'\0',从而导致sizeof的返回值为不可预测的结果。
  • printf("%d\n", strlen(arr))和printf("%d\n", strlen(arr+0))的打印结果解析
arr和arr+0均为数组首元素的地址,arr在内存中的存储情况如图1.2所示,调用strlen函数时,函数从数组首元素开始向后寻找‘\0’,但是,由于无法确定arr后面的内存空间存放了什么元素,因此无法确定strlen查找到那个位置时终止查找,故这里个printf的打印结果均为随机值,打印结果一致。

  • printf("%d\n", strlen(*arr))和printf("%d\n", strlen(arr[1]))的打印结果解析
*arr和arr[1]均表示数组首元素,将字符型变量传出strlen是一种典型的传参错误(应当传入地址),故这两个printf无打印结果。
  • printf("%d\n", strlen(&arr+1))和printf("%d\n", strlen(&arr[0]+1))的打印结果解析

&arr+1指向数组最后一个元素后面那个内存位置,&arr[0]+1为数组第二个元素的地址。将这两个地址传入strlen函数,以这两个地址为起始点,分别向后寻找'\0',打印结果均为随机值。并且,printf("%d\n", strlen(&arr[0]+1))的打印结果会比printf("%d\n", strlen(&arr+1))的打印结果大5。这是因为arr中存放有6个字符型元素,strlen(&arr[0]+1)从第二个元素的地址处开始查找,strlen(&arr+1)从数组最后一个元素后面那个内存位置开始查找,前者比后者多查找5个元素,因此printf("%d\n", strlen(&arr[0]+1))的打印结果会比printf("%d\n", strlen(&arr+1))的打印结果大5。

图1.4为给strlen传入不同参数时的图解。

图1.4  给strlen传入不同参数时的图解

 1.2.2 指针和字符型数组题目2

判断下面的程序中,每个printf打印的结果是多少?

int main()

{

        char * p = "abcdef" ;
        printf ( "%d\n" , sizeof ( p ));
        printf ( "%d\n" , sizeof ( p + 1 ));
        printf ( "%d\n" , sizeof ( * p ));
        printf ( "%d\n" , sizeof ( p [ 0 ]));
        printf ( "%d\n" , sizeof ( & p ));
        printf ( "%d\n" , sizeof ( & p + 1 ));
        printf ( "%d\n" , sizeof ( & p [ 0 ] + 1 ));
        printf ( "%d\n" , strlen ( p ));
        printf ( "%d\n" , strlen ( p + 1 ));
        printf ( "%d\n" , strlen ( * p ));
        printf ( "%d\n" , strlen ( p [ 0 ]));
        printf ( "%d\n" , strlen ( & p ));
        printf ( "%d\n" , strlen ( & p + 1 ));
        printf ( "%d\n" , strlen ( & p [ 0 ] + 1 ));
        return 0;
}
  • printf("%d\n", sizeof(p))的打印结果解析
这里有一个很迷惑人的地方。这里我们很有可能就认为p是一个数组名,其实不然。char* p = "abcdef" 的意思是现在内存中存放一个常量字符串"abcdef",然后定义一个字符型指针变量p,其值常量字符串"abcdef"首元素地址,置于字符型指针变量p存储在了哪里,我们并不知道。
由此可见,p在题目代码中并不能代表字符型数组,它仅仅是一个字符型指针变量。因此,打印结果为4或者8。
  • printf("%d\n", sizeof(p + 1))的打印结果解析
p+1本质上为指向常量字符串 "abcdef" 第二个字符元素的指针变量,表示一个地址,因此打印结果为4或8。
  • printf("%d\n", sizeof(*p))和printf("%d\n", sizeof(p[0]))的打印结果解析
*p和p[0]本质上均为常量字符串 "abcdef" 的第一个元素,字符型元素占用1byte的内存空间,因此这两个printf的打印结果均为1。
  • printf("%d\n", sizeof(&p))和printf("%d\n", sizeof(&p+1))的打印结果解析
&p和&p+1均代表地址,两个printf的打印结果均为4或8,这个不会有什么问题。
但是,对于这两个printf,最终要的不是明白会打印什么内容,而是明确&p和&p+1的内在含义。p为一个指针变量,其值为常量字符串首元素地址,至于p存储在内存中的什么位置,我们不得而知。因此,&p的值和p的值并没有直接关系。
对于&p和&p+1,由于&p并没有给出是什么类型的指针,编译器默认安装整型指针进行处理。因此,&p+1的值比&p的值大4。(这里建议读者亲自去编译器上运行一下代码)
&p、&p+1、p 以及常量字符串在内存中的存储和指向关系如图1.5所示。
图1.5  &p、&p+1、p 以及常量字符串在内存中的存储和指向关系
  •  printf("%d\n", sizeof(&p[0]+1))的打印结果解析
&p[0]+1为指向常量字符串第二个元素的指针,本质上为地址, 打印结果为4或8。
  • printf("%d\n", strlen(p))和printf("%d\n", strlen(p+1))的打印结果解析
p表示常量字符串首元素地址,p+1表示常量字符串第二个元素的地址。strlen(p)和strlen(p+1)分别从常量字符串第一个和第二个元素的地址开始向后寻找'\0',前者比后者多查找一个内存空间,因此前者共查找了6字符,后者共查找到了5个字符。因此, printf("%d\n", strlen(p))的打印结果为6,printf("%d\n", strlen(p+1))的打印结果为5。
 
  • printf("%d\n", strlen(*p))和printf("%d\n", strlen(p[0]))的打印结果解析
*p和p[0]均为字符串常量的第一个元素,这就相当于向strlen传入了字符作为参数,而非传入指针(地址),传入参数错误,无法打印结果。
  • printf("%d\n", strlen(&p))和printf("%d\n", strlen(&p+1))的打印结果解析
由于&p、&p+1和p在内存中的位置没有直接关系,因此,我们无法确定&p和&p+1所表示的内存地址的后面什么位置会存储'\0',因此两个printf均打印随机值。
至于这两个printf打印数值之间的关系,可以分为两种情况来讨论。
  • 如图1.6所示,&p所指向的内存空间和&p+1所指向的内存空间之间存储有字符'\0',在这种情况下printf("%d\n",strlen(&p))的打印结果会小于4,printf("%d\n",strlen(&p+1))的打印结果无法确定。
图1.6  &p所指向的内存空间和&p+1所指向的内存空间之间存储有字符'\0'时的内存图示
  • 如图1.7所示,&p所指向的内存空间和&p+1所指向的内存空间之间没有存储'\0',此时printf("%d\n",strlen(&p))的打印结果比printf("%d\n",strlen(&p+1))的打印结果大4。
图1.7  &p所指向的内存空间和&p+1所指向的内存空间之间没有存储字符'\0'时的内存图示

  • printf("%d\n", strlen(&p[0]+1))的打印结果解析

&p[0]+1即为常量字符串第二个元素的地址,打印结果为5。

1.2.3 指针和字符型数组题目3

下段代码中每个printf的打印结果是多少?

int main()

{

        char arr [] = "abcdef" ;
        printf ( "%d\n" , sizeof ( arr ));
        printf ( "%d\n" , sizeof ( arr + 0 ));
        printf ( "%d\n" , sizeof ( * arr ));
        printf ( "%d\n" , sizeof ( arr [ 1 ]));
        printf ( "%d\n" , sizeof ( & arr ));
        printf ( "%d\n" , sizeof ( & arr + 1 ));
        printf ( "%d\n" , sizeof ( & arr [ 0 ] + 1 ));
        printf ( "%d\n" , strlen ( arr ));
        printf ( "%d\n" , strlen ( arr + 0 ));
        printf ( "%d\n" , strlen ( * arr ));
        printf ( "%d\n" , strlen ( arr [ 1 ]));
        printf ( "%d\n" , strlen ( & arr ));
        printf ( "%d\n" , strlen ( & arr + 1 ));
        printf ( "%d\n" , strlen ( & arr [ 0 ] + 1 ));
        return 0;
}
  •  printf("%d\n", sizeof(arr))的打印结果解析

数组arr在内存中的存储情况如图1.8所示,sizeof在计算数组大小是包含了尾部的'\0',因此,这里的printf打印结果为7

图1.8  数组arr在内存中的存储
  •  printf("%d\n", sizeof(arr + 0))的打印结果解析

arr+0表示数组首元素地址,打印结果为4或8。

  • printf("%d\n", sizeof(*arr))和printf("%d\n", sizeof(arr[1]))的打印结果解析

*arr和arr[1]分别为字符型数组第一个和第二个元素,字符型变量的大小为1byte,故两个printf的打印结果均为1。

  • printf("%d\n", sizeof(&arr))、printf("%d\n", sizeof(&arr+1))和printf("%d\n", sizeof(&arr[0]+1))的打印结果解析
&arr、&arr+1以及&arr[0]+1的指向位置如图1.9所示,这三个参数均为地址,因此三个printf的打印结果均为4或8。
图1.9  &arr、&arr+1以及&arr[0]+1的指向位置示意图
  • printf("%d\n", strlen(arr))和printf("%d\n", strlen(arr+0))的打印结果解析
arr和arr+0均为数组首元素地址,将数组首元素地址传入到strlen函数中,向后寻找'\0'。打印结果均为字符串arr中的有效字符数,即均打印6。
  • printf("%d\n", strlen(*arr))和printf("%d\n", strlen(arr[1]))的打印结果解析
将字符型变量传入到strlen中,并非传入地址,无法打印结果。
  •  printf("%d\n", strlen(&arr))、printf("%d\n", strlen(&arr+1))以及printf("%d\n", strlen(&arr[0]+1))的打印结果解析

&arr、&arr+1以及&arr[0]+1指向内存中的位置以及内存中存储变量的情况如图1.10所示,&arr指向数组的第一个元素,&arr[0]+1指向数组的第二个元素,printf("%d\n", strlen(&arr))和printf("%d\n", strlen(&arr[0]+1))的打印结果分别为6和5。&arr+1指向字符型数组最后一个元素后面那个内存位置,由于无法确定数组后面的内存空间存储了什么内容,故printf("%d\n",strlen(&arr+1))打印的结果为随机值。

图1.10  &arr、&arr+1以及&arr[0]+1指向内存中的位置以及内存中存储变量的情况

二.  指针和二维数组典型笔试题解析        

下段代码中每个printf的打印结果是多少?

int main( )

{

        int a [ 3 ][ 4 ] = { 0 };
        printf ( "%d\n" , sizeof ( a ));
        printf ( "%d\n" , sizeof ( a [ 0 ][ 0 ]));
        printf ( "%d\n" , sizeof ( a [ 0 ]));
        printf ( "%d\n" , sizeof ( a [ 0 ] + 1 ));
        printf ( "%d\n" , sizeof ( * ( a [ 0 ] + 1 )));
        printf ( "%d\n" , sizeof ( a + 1 ));
        printf ( "%d\n" , sizeof ( * ( a + 1 )));
        printf ( "%d\n" , sizeof ( & a [ 0 ] + 1 ));
        printf ( "%d\n" , sizeof ( * ( & a [ 0 ] + 1 )));
        printf ( "%d\n" , sizeof ( * a ));
        printf ( "%d\n" , sizeof ( a [ 3 ]));
        return 0;

}

  • printf("%d\n",sizeof(a))打印结果的解析

a直接放入sizeof后面的括号里,此时的a表示整个数组,二维数组a含有12个整型元素,每个元素占用4byte的内存空间,整个数组占用48byte的内存空间。因此,打印结果为48。

  • printf("%d\n",sizeof(a[0][0]))打印结果的解析

a[0][0]表示二维数组第一行第一列的元素,整型元素的大小为4byte,打印结果为4。

  • printf("%d\n",sizeof(a[0]))和printf("%d\n",sizeof(a[0]+1))的打印结果解析 

二维数组可以看做有n个一维数组作为元素构成的一维数组。二维数组的数组名也表示数组首元素地址,但要注意,二维数组的首元素并不是二维数组第一行第一列的元素,而是整个第一行的元素。在二维数组a[ROW][COL]中,a[0]、a[1]、... 、a[ROW-1]可分别视为二维数组第一行、第二行、... 、第ROW行对应一维数组的数组名,即a[0]、a[1]、... 、分别表示二维数组每行首元素的地址。

二维数组在内存中是连续存放的,具体表现为:行间连续、跨行连续。对于上面程序中定义的整型数组int a[3][4] = { 0 },其在内存中的存储情况和每行对应一维数组的数组名如图2.1所示。

图2.1 整型二维数组a在内存中的存储情况

a[0]可以理解为二维数组首行对应一维数组的数组名,将数组名直接放入sizeof后面的括号,表示整个数组元素的大小。二维数组的第一行有4个整型元素,故printf("%d\n", sizeof(a[0]))的打印结果为16。

a[0]+1可以表示二维数组第二行对应一维数组的数组名,但要注意,这里并不是将数组名直接放入到sizeof后面的括号里。因此,这里不能将a[0]+1理解为整个数组,a[0]+1应理解为第二行首元素的地址,地址(指针变量)的大小为4byte或8byte,因此printf("%d\n", sizeof(a[0]+1))的打印结果为4或8。

  • printf("%d\n",sizeof(*(a[0]+1)))的打印结果解析

*(a[0]+1)相当于获取了二维数组a的第一行第二列的元素,因此sizeof(*(a[0]+1))获取的是一个整型变量的大小,为4byte,打印结果为4。

  • printf("%d\n",sizeof(a+1))的打印结果解析

这里的a可以表示为二维数组首元素的地址,即二维数组第一行一维数组的地址,因此可以认为a本质上是一个数组指针变量。a+1表示二维数组第二行对应一维数组的地址,那么,a+1的大小为4字节或8字节。因此,打印结果为4或8。

  • printf("%d\n",sizeof(*(a+1)))的打印结果解析

a+1表示指向二维数组a第二行对应一维数组的数组指针,指向一个包含4个整型元素的一维数组。因此,其解应用的权限为16byte,因此打印结果为16。

  • printf("%d\n",sizeof(&a[0]+1))的打印结果解析

&a[0]+1为二维数组第二行元素的地址,因此打印结果为4或8。&a[0]+1指向的位置和*(&a[0]+1)可以访问的权限如图2.2所示。

图2.2  &a[0]+1指向的位置和*(&a[0]+1)的访问权限示意图

 

  •  printf("%d\n", sizeof(*(&a[0] + 1)))和printf("%d\n", sizeof(*a))的打印结果解析

*(&a[0]+1)和*a的解应用权限均为二维数组一整行的元素(如图2.3所示)。因此,这两个printf的打印结果均为16。

图2.3  *(&a[0]+1)和*a的解应用权限示意图

 

  •  printf("%d\n",sizeof(a[3]))的打印结果解析

这里肯能会有很多人认为程序会报错,因为a[3]表示二维数组a的第四行元素,但是二维数组a仅有三行元素,所有这里存在越界访问,因此程序报错,无法正常打印。

那么,结果真的是这样吗?其实并不是,这里的打印结果为16,是不是很出乎意料?这是因为sizeof(a[3])表示假定认为a的第四行存在,包含4个整型元素并计算大小。sizeof后面括号中的表达式在编译阶段就完成了计算,这里并没有对a[3]进行解应用操作或使用a[3]中的任何元素,因此,并不存在越界访问的问题,更不会报错,程序会正常执行,打印结果为16。

全文结束,感谢大家的阅读,敬请批评指正,希望能与大家共同进步。

下期预告:C语言指针就应该这么学(指针和数组典型笔试题解析)第二弹。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值