一. 指针和一维数组典型笔试题解析
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所示。
&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所示。
- 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))的打印结果解析
在对后面几个printf进行解析之前,我首讲解求解字符串长度库函数stren。strlen后面括号中的变量实际上是一个地址(指针变量),strlen处该地址开始查找,直到遇见'\0'就终止查找。也就是说,括号里的变量不一定就是数组名,也可以是数组某个元素的地址或是其余的指针变量。但要注意,如果传入的不是字符串中某个元素的地址,则有可能在向后寻找的过程中找不到'\0',从而导致sizeof的返回值为不可预测的结果。
- 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+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.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))的打印结果解析
- 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打印数值之间的关系,可以分为两种情况来讨论。
- 如图1.6所示,&p所指向的内存空间和&p+1所指向的内存空间之间存储有字符'\0',在这种情况下printf("%d\n",strlen(&p))的打印结果会小于4,printf("%d\n",strlen(&p+1))的打印结果无法确定。
- 如图1.7所示,&p所指向的内存空间和&p+1所指向的内存空间之间没有存储'\0',此时printf("%d\n",strlen(&p))的打印结果比printf("%d\n",strlen(&p+1))的打印结果大4。
- 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。
- 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))的打印结果解析
- 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))的打印结果解析
&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))打印的结果为随机值。
二. 指针和二维数组典型笔试题解析
下段代码中每个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所示。
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所示。
- printf("%d\n", sizeof(*(&a[0] + 1)))和printf("%d\n", sizeof(*a))的打印结果解析
*(&a[0]+1)和*a的解应用权限均为二维数组一整行的元素(如图2.3所示)。因此,这两个printf的打印结果均为16。
- printf("%d\n",sizeof(a[3]))的打印结果解析
这里肯能会有很多人认为程序会报错,因为a[3]表示二维数组a的第四行元素,但是二维数组a仅有三行元素,所有这里存在越界访问,因此程序报错,无法正常打印。
那么,结果真的是这样吗?其实并不是,这里的打印结果为16,是不是很出乎意料?这是因为sizeof(a[3])表示假定认为a的第四行存在,包含4个整型元素并计算大小。sizeof后面括号中的表达式在编译阶段就完成了计算,这里并没有对a[3]进行解应用操作或使用a[3]中的任何元素,因此,并不存在越界访问的问题,更不会报错,程序会正常执行,打印结果为16。
全文结束,感谢大家的阅读,敬请批评指正,希望能与大家共同进步。
下期预告:C语言指针就应该这么学(指针和数组典型笔试题解析)第二弹。