全文目录
引言
在进行指针题目的练习之前,我们先来回顾一下以前学习的关于数组和指针的知识:
数组名的含义
数组名是首元素地址。但是在两种情况下数组名代表整个数组:1、数组名单独放在sizeof后的括号内,表示计算整个数组的大小;2、数组名单独放在&后面,表示取出整个数组的地址。
sizeof与strlen
szieof是一个关键字,作用是计算数据大小(字节数) 。括号里面可以放类型、数组、变量、字符串等数据。
strlen是一个库函数,作用是计算字符串长度。括号里面的类型必须是一个char*型的指针。并且,计算字符串长度时以’\0’作为字符串结束的标志,即从这个char*型的指针向后计算到’\0’截止。('\0’不计入总长度)。
指针
指针类型决定着指针的步长(指针加减1时所移动的距离)和指针解引用是所能够访问到的内存的大小。
指针的大小与指针类型无关。在32位机器上是4字节,在64位机器上是8字节。
这些内容可以都参考我之前的博客:初识C语言
指针和数组笔试题
一维数组
//下面代码输出的结果是什么
int main()
{
int a[] = { 1, 2, 3, 4 };
printf(" 1:%d\n", sizeof(a));
printf(" 2:%d\n", sizeof(a + 0));
printf(" 3:%d\n", sizeof(*a));
printf(" 4:%d\n", sizeof(a + 1));
printf(" 5:%d\n", sizeof(a[1]));
printf(" 6:%d\n", sizeof(&a));
printf(" 7:%d\n", sizeof(*&a));
printf(" 8:%d\n", sizeof(&a + 1));
printf(" 9:%d\n", sizeof(&a[0]));
printf("10:%d\n", sizeof(&a[0] + 1));
return 0;
}
a是一个一维数组,存有四个元素,每个元素的类型是int型的。
第一行:sizeof后面括号里是数组名a,数组名表示整个数组。sizeof计算整个数组的长度,为4*4=16个字节;
第二行:数组名并没有单独在sizeof后,表示首元素地址。a是第一个元素的地址,a+0还是第一个元素的地址。sizeof计算地址的大小就是4或8个字节;
第三行:a没有单独放在sizeof内部,所以a就是首元素地址,*a就是数组a的第一个元素1。数组a里的元素都是int型的,计算一个int型变量的大小就是4个字节;
第四行:数组名并没有单独在sizeof后,表示首元素地址。a是第一个元素的地址,a+1就是第二个元素的地址。sizeof计算地址的大小就是4或8个字节;
第五行:a[1]就是用下标访问数组的第二个元素,其本质上就是*(a+1)。sizeof计算第二个元素的大小就是计算一个int型数据的大小就是4个字节;
第六行:数组名a单独放在&后代表整个数组,&a就是整个数组的地址,它的类型是指向4个int型元素数组的数组指针。sizeof计算一个地址的大小就是4或8个字节;
第七行:&a取出的是整个数组的地址,是指向4个int型元素数组的数组指针。那么给这个数组指针解引用就能访问到整个数组也就是4*4=16个字节。sizeof计算整个数组的大小就是16个字节;
第八行:a单独放在&后表示整个数组。&a是整个数组的地址,是指向4个int型元素数组的数组指针。所以&a的步长就是数组a的长度即4*4=16个字节。&a+1就是数组指针加1,也是一个数组指针。跳过整个数组,指向的是数组a后的一块大小为16字节的空间。sizeof计算一个指针的大小就是4或8个字节;
第九行:a[0]就是通过下标访问数组a的第一个元素1,它本质上就是*(a+0)。是一个整型元素。给a[0]再取地址取出的就是数组第一个元素的地址,也就是一个int*型的指针。sizeof计算一个指针变量的大小就是4或8个字节;
第十行:&a[0]相当于数组第一个元素的地址,它是一个int*型的指针。这个指针加1移动的就是一个int的字节数即4个字节。&a[0]+1就是数组a的第二个元素的地址,也是int*型的。sizeof计算一个指针变量的大小就是4或8个字节;
最后,我们可以将这段代码放在编译器中运行:
字符数组
无’\0’的字符数组与sizeof
//下面代码输出的结果是什么
int main()
{
char arr[] = { 'a', 'b', 'c', 'd', 'e', 'f' };
printf("1:%d\n", sizeof(arr));
printf("2:%d\n", sizeof(arr + 0));
printf("3:%d\n", sizeof(*arr));
printf("4:%d\n", sizeof(arr[1]));
printf("5:%d\n", sizeof(&arr));
printf("6:%d\n", sizeof(&arr + 1));
printf("7:%d\n", sizeof(&arr[0] + 1));
return 0;
}
字符数组arr内有6个char类型的元素,分别是’a’、‘b’、‘c’、‘d’、‘e’、‘f’。
第一行:数组名arr单独放在sizeof内部,表示整个数组。sizeof计算的是整个数组的大小,就是1*6=6个字节;
第二行:数组名arr没有单独放在sizeof内部,表示首元素地址。arr+0还是首元素的地址,这个指针的类型是char*。sizeof计算一个指针的大小就是4或8个字节;
第三行:数组名arr没有单独放在arr内部,表示首元素地址。* arr就是第一个元素a,元素的类型是char。sizeof计算一个char类型的变量的大小就是1个字节;
第四行:arr[1]是根据下标访问数组下标为1的元素,本质上就是*(arr+1)。arr的类型是char*,指向的是数组第一个元素,arr+1就跳过一个char的距离,指向数组的第二个元素。*(arr+1)就是数组的第二个元素b,元素类型是char。sizeof计算一个char类型的变量的大小就是1个字节;
第五行:数组名arr单独放在&后,表示整个数组。&arr就是整个数组的地址,是指向6个char型元素数组的数组指针。sizeof计算一个指针的大小就是4或8个字节;
第六行:&arr是整个数组的地址,是指向6个char型元素数组的数组指针。所以&arr的步长就是数组arr的长度即1*6=6个字节。&arr+1就是数组指针加1,也是一个数组指针。跳过整个arr数组,指向的是数组arr后的一块大小为6字节的空间。sizeof计算一个指针的大小就是4或8个字节;
第七行:数组名没有单独放在sizeof内部或&后,是首元素地址。arr[0]就是*(arr+0)就是数组第一个元素,类型是char。&arr[0]就是第一个元素的地址,类型是char*。&arr[0]+1移动一个char类型的大小也就是1个字节,指向数组arr的第二个元素。sizeof计算一个指针的大小就是4或8个字节;
最后,在编译器中运行:
无’\0’的字符数组与strlen
//下面代码输出的结果是什么
#include<string.h>
int main()
{
char arr[] = { 'a', 'b', 'c', 'd', 'e', 'f' };
printf("1:%d\n", strlen(arr));
printf("2:%d\n", strlen(arr + 0));
printf("3:%d\n", strlen(*arr));
printf("4:%d\n", strlen(arr[1]));
printf("5:%d\n", strlen(&arr));
printf("6:%d\n", strlen(&arr + 1));
printf("7:%d\n", strlen(&arr[0] + 1));
return 0;
}
字符数组arr内有6个char类型的元素,分别是’a’、‘b’、‘c’、‘d’、‘e’、‘f’。
由于这组题目的字符数组与指针指向与上组题目完全相同,就不图示表示了。
第一行:数组名arr是首元素地址,类型是char*。可以作为strlen的参数。但是由于字符数组arr没有结束标志’\0’,strlen在计算字符串长度的时候不知道什么时候截止。所以结果是随机值;
第二行:arr+0也是首元素的地址,和上面一样。打印的是随机值;
第三行:数组名arr是首元素地址,解引用得到的就是数组首元素’a’,类型是char。与strlen的参数类型char*不匹配,不能作为strlen的参数,所以不输出值,编译器会挂掉;
第四行:数组名arr是首元素地址,arr[1]本质上就是*(arr+1),即数组arr的第二个元素,类型是char。与strlen的参数类型char*不匹配,不能作为strlen的参数,所以不输出值,编译器会挂掉;
第五行:数组arr单独在&后,表示整个数组。&arr就是整个数组的地址,是指向6个char型元素数组的数组指针,类型是char(*)[6]。虽然类型与char*不匹配,但数组的地址与首元素的地址值相同,所以在编译时虽然会报警告,但是不影响结果。但是在这里,strlen在计算字符串长度的时候不知道什么时候截止。所以结果是随机值;
第六行:&arr是整个数组的地址,是指向6个char型元素数组的数组指针。所以&arr的步长就是数组arr的长度即1*6=6个字节。&arr+1就是数组指针加1,也是一个数组指针。跳过整个arr数组,指向的是数组arr后的一块大小为6字节的空间,类型是char(*)[6]。虽然类型与char*不匹配,但数组的地址与首元素的地址值相同,所以在编译时虽然会报警告,但是不影响结果。但是在这里,strlen在计算字符串长度的时候不知道什么时候截止。所以结果是随机值;
第七行:数组名没有单独放在sizeof内部或&后,是首元素地址。arr[0]就是*(arr+0)就是数组第一个元素,类型是char。&arr[0]就是第一个元素的地址,类型是char*。&arr[0]+1移动一个char类型的大小也就是1个字节,指向数组arr的第二个元素,类型是char*。可以作为strlen的参数。但是由于字符数组arr没有结束标志’\0’,strlen在计算字符串长度的时候不知道什么时候截止。所以结果是随机值;
最后,我们可以在编译器中看一下结果(由于在第三行与第四行编译时会挂掉,所以我们需要将这两行代码注释掉再查看结果):
含’\0’的字符串与sizeof
//下面代码输出的结果是什么
int main()
{
char arr[] = "abcdef";
printf("1:%d\n", sizeof(arr));
printf("2:%d\n", sizeof(arr + 0));
printf("3:%d\n", sizeof(*arr));
printf("4:%d\n", sizeof(arr[1]));
printf("5:%d\n", sizeof(&arr));
printf("6:%d\n", sizeof(&arr + 1));
printf("7:%d\n", sizeof(&arr[0] + 1));
return 0;
}
字符串arr内有7个char类型的元素,分别是’a’、‘b’、‘c’、‘d’、‘e’、‘f’、‘\0’。
对于转义字符’\0’,大小为1字节。sizeof计算字符串大小时,'\0’也计算在内。
第一行:数组名arr单独放在sizeof内部,表示整个数组。sizeof计算的是整个数组的大小,就是1*7=7个字节(需要特别注意转义字符’\0’);
第二行:数组名arr没有单独在sizeof内部,表示首元素地址。arr+0也是首元素的地址,类型是char*。sizeof计算一个指针变量的大小就是4或8个字节;
第三行:数组名arr没有单独在sizeof内部,表示首元素的地址。*arr就是数组第一个元素a,类型是char。sizeof计算一个字符的大小就是1个字节;
第四行:arr[1]是根据下标访问数组下标为1的元素,本质上就是*(arr+1)。arr的类型是char*,指向的是数组第一个元素,arr+1就跳过一个char的距离,指向数组的第二个元素。*(arr+1)就是数组的第二个元素b,元素类型是char。sizeof计算一个char类型的变量的大小就是1个字节;
第五行:数组名arr单独放在&后,表示整个数组。&arr就是整个数组的地址,是指向7个char型元素数组的数组指针。sizeof计算一个指针的大小就是4或8个字节;
第六行:&arr是整个数组的地址,是指向7个char型元素数组的数组指针。所以&arr的步长就是数组arr的长度即1*7=7个字节。&arr+1就是数组指针加1,也是一个数组指针。跳过整个arr数组,指向的是数组arr后的一块大小为7字节的空间。sizeof计算一个指针的大小就是4或8个字节;
第七行:数组名没有单独放在sizeof内部或&后,是首元素地址。arr[0]就是*(arr+0)就是数组第一个元素,类型是char。&arr[0]就是第一个元素的地址,类型是char*。&arr[0]+1移动一个char类型的大小也就是1个字节,指向数组arr的第二个元素。sizeof计算一个指针的大小就是4或8个字节;
最后,在编译器中运行来验证:
含’\0’的字符串与strlen
//下面代码输出的结果是什么
#include<string.h>
int main()
{
char arr[] = "abcdef";
printf("%1:d\n", strlen(arr));
printf("2:%d\n", strlen(arr + 0));
printf("3:%d\n", strlen(*arr));
printf("4:%d\n", strlen(arr[1]));
printf("5:%d\n", strlen(&arr));
printf("6:%d\n", strlen(&arr + 1));
printf("7:%d\n", strlen(&arr[0] + 1));
return 0;
}
字符串arr内有7个char类型的元素,分别是’a’、‘b’、‘c’、‘d’、‘e’、‘f’、‘\0’。
strlen计算字符串长度是以’\0’作为结束标志,函数参数是char*型的。
第一行与第二行:数组名arr都表示首元素地址,类型为char*。可以作为strlen的参数。从首元素开始计算,到’\0’停止(‘\0’不计入),共6个元素。故此时strlen的返回值就是6;
接下来的第三、四行的分析与前面无’\0’的字符串与strlen一致,这里就不再赘述。
第五行:数组arr单独在&后,表示整个数组。&arr就是整个数组的地址,是指向7个char型元素数组的数组指针,类型是char(*)[7]。虽然类型与char*不匹配,但数组的地址与首元素的地址值相同,所以在编译时虽然会报警告,但是不影响结果。这里从首元素开始计算,到’\0’停止('\0’不计入),共6个元素。故此时strlen的返回值就是6;
第六行:&arr是整个数组的地址,是指向7个char型元素数组的数组指针。所以&arr的步长就是数组arr的长度即1*7=7个字节。&arr+1就是数组指针加1,也是一个数组指针。跳过整个arr数组,指向的是数组arr后的一块大小为7字节的空间,类型是char(*)[7]。虽然类型与char*不匹配,但数组的地址与首元素的地址值相同,所以在编译时虽然会报警告,但是不影响结果。但是在这里,strlen在计算字符串长度的时候不知道什么时候截止。所以结果是随机值;
第七行:数组名没有单独放在sizeof内部或&后,是首元素地址。arr[0]就是*(arr+0)就是数组第一个元素,类型是char。&arr[0]就是第一个元素的地址,类型是char*。&arr[0]+1移动一个char类型的大小也就是1个字节,指向数组arr的第二个元素,类型是char*。可以作为strlen的参数。从数组第二个元素开始计算,到’\0’停止('\0’不计入),共5个元素。故此时strlen的返回值就是5;
最后,我们可以在编译器中看一下结果(由于在第三行与第四行编译时会挂掉,所以我们需要将这两行代码注释掉再查看结果):
常量字符串与sizeof
//下面代码输出的结果是什么
int main()
{
char *p = "abcdef";
printf("1:%d\n", sizeof(p));
printf("2:%d\n", sizeof(p + 1));
printf("3:%d\n", sizeof(*p));
printf("4:%d\n", sizeof(p[0]));
printf("5:%d\n", sizeof(&p));
printf("6:%d\n", sizeof(&p + 1));
printf("7:%d\n", sizeof(&p[0] + 1));
return 0;
}
对于常量字符串p,p就是一个字符指针,指向常量字符串的第一个元素。所以当p单独再sizeof内部或单独再&后时,也不能代表整个数组。
第一行:常量字符串p是一个char*类型的指针,指向常量字符串的第一个元素。sizeof计算一个指针变量的大小就是4或8个字节;
第二行:常量字符串p是一个char*类型的指针,指向常量字符串的第一个元素,p+1就移动一个char类型的大小,指向常量字符串的第二个元素。sizeof计算一个指针变量的大小就是4或8个字节;
第三行:p是常量字符串首元素地址,*p访问的就是常量字符串的第一个元素a,类型是char。sizeof计算一个char类型变量的大小就是1个字节;
第四行:p[0]本质上就是*(p+0),等于*p。即常量字符串的第一个元素a,类型是char。sizeof计算一个char类型变量的大小就是1个字节;
第五行:p是一个char*型的字符数组,&p就是取出p的地址,即一个二级字符指针,类型为char**。sizeof计算一个指针变量的大小就是4或8个字节;
第六行:&p是一个char**的二级数组指针,当它加1时,跳过的就是char大小的字节(4个或8个)。类型也是char**。sizeof计算一个指针变量的大小就是4或8个字节;
第七行:p[0]就相当于*(p+0),即常量字符串的第一个元素a。给p[0]再取地址就是第一个元素的地址,类型为char。再给&p[0]加1跳过一个char的大小也就是1个字节,指向的就是第二个元素。sizeof计算一个指针变量的大小就是4或8个字节;
最后,我们可以在编译器中验证一下:
常量字符串与strlen
//下面代码输出的结果是什么
#include<string.h>
int main()
{
char *p = "abcdef";
printf("1:%d\n", strlen(p));
printf("2:%d\n", strlen(p + 1));
printf("3:%d\n", strlen(*p));
printf("4:%d\n", strlen(p[0]));
printf("5:%d\n", strlen(&p));
printf("6:%d\n", strlen(&p + 1));
printf("7:%d\n", strlen(&p[0] + 1));
return 0;
}
第一行:p都表示常量字符串首元素地址,类型为char*。可以作为strlen的参数。从首元素开始计算,到’\0’停止(‘\0’不计入),共6个元素。故此时strlen的返回值就是6;
第二行:给p加1跳过一个char的大小也就是1个字节,指向的是第二个元素。类型为char*。可以作为strlen的参数。从第二个元素开始计算,到’\0’停止('\0’不计入),共5个元素。故此时strlen的返回值就是5;
第三行与第四行:表示的都是常量字符串的首元素,类型为char。与strlen的参数类型char*不匹配,不能作为strlen的参数,所以不输出值,编译器会挂掉;
第五行:p是一个char*型的字符数组,&p就是取出p的地址,即一个二级字符指针,类型为char**。虽然类型与char*不匹配,但毕竟是个地址,所以在编译时虽然会报警告,但是不影响结果。但是在这里,&p指向的空间中存的是个地址,strlen在由这个地址的第一个字节开始计算字符串长度的时候不确定什么时候截止。所以结果是随机值;
第六行:&p是一个char**的二级数组指针,当它加1时,跳过的就是char大小的字节(4个或8个)。类型也是char**。虽然类型与char*不匹配,但毕竟是个地址,所以在编译时虽然会报警告,但是不影响结果。但是在这里,&p指向的空间中存的是个地址,strlen在由这个地址的第一个字节开始计算字符串长度的时候不确定什么时候截止。所以结果是随机值;
第七行:p[0]就相当于*(p+0),即常量字符串的第一个元素a。给p[0]再取地址就是第一个元素的地址,类型为char。再给&p[0]加1跳过一个char的大小也就是1个字节,指向的就是第二个元素。类型为char*。可以作为strlen的参数。从第二个元素开始计算,到’\0’停止('\0’不计入),共5个元素。故此时strlen的返回值就是5;
最后,我们可以在编译器中看一下结果(由于在第三行与第四行编译时会挂掉,所以我们需要将这两行代码注释掉再查看结果):
二维数组
需要注意的是:二维数组在内存中是连续存放的;二维数组数组名代表首元素地址时是一个数组指针指向第一行的数组。
//下面代码输出的结果是什么
int main()
{
int a[3][4] = { 0 };
printf(" 1:%d\n", sizeof(a));
printf(" 2:%d\n", sizeof(a[0][0]));
printf(" 3:%d\n", sizeof(a[0]));
printf(" 4:%d\n", sizeof(a[0] + 1));
printf(" 5:%d\n", sizeof(*(a[0] + 1)));
printf(" 6:%d\n", sizeof(a + 1));
printf(" 7:%d\n", sizeof(*(a + 1)));
printf(" 8:%d\n", sizeof(&a[0] + 1));
printf(" 9:%d\n", sizeof(*(&a[0] + 1)));
printf("10:%d\n", sizeof(*a));
printf("11:%d\n", sizeof(a[3]));
return 0;
}
首先我们来图示一下这个二维数组。这个a二维数组由三个一维数组组成,每个一维数组有4个元素,每个元素都是int型:
第一行:二维数组名a单独放在sizeof内部,表示整个数组。sizeof计算整个二维数组的大小就是4*3*4=48个字节;
第二行:a[0][0]本质上就是*(*(a+0)+0),即二维数组中第一个数组的第一个元素,类型为int。sizeof计算一个int型的变量就是4个字节;
第三行:二维数组名没有单独在sizeof内部,表示首元素地址,即一个数组指针。a[0]本质上就是*(a+0),给这个数组指针解引用就是二维数组的首元素,也就是一个一维数组。这个一维数组有4个int型元素。一维数组单独放在sizeof内部表示的是整个数组,sizeof计算整个一维数组的大小就是4*4=16个字节;
第四行:a[0]是二维数组中第一个维数组,这个数组没有单独位于sizeof内部,表示的是首元素的地址,也就是第一个一维数组中的第一个元素的地址。类型是int*。当这个整型指针加1时,向后移动一个int的大小也就是4个字节。指向第一个一维数组的第二个元素。类型依然时int*。sizeof计算一个指针变量的大小就是4或8个字节;
第五行:a[0]+1是指向第一个一维数组的第二个元素的指针,类型是int*。解引用就得到了第一个一维数组的第二个元素0,类型是int。sizeof计算一个int型的变量结果就是4;
第六行:二维数组名没有单独放在sizeof内部,表示的是首元素地址,即第一个一维数组的地址,类型是int(*)[4]。这个数组指针加1,移动的就是4*4=16个字节,即指向二维数组的第二个元素(第二个一维数组),类型依旧是int(*)[4]。sizeof计算一个指针变量的大小就是4或8个字节;
第七行:a+1是指向二维数组中第二个一维数组的数组指针,类型是int(*)[4]。解引用时就得到了这个一维数组。sizeof计算这个一维数组的大小就是4*4=16个字节;
第八行:二维数组名a没有单独放在sizeof内部也没有单独放在&后,表示首元素的地址,即第一个一维数组的地址。a[0]就相当于*(a+0),即解引用这个数组指针,得到的是第一个一维数组。再对其取地址取出的就是第一个一维数组的地址,类型是int(*)[4]。这个地址加1,向后移动16个字节,指向的就是二维数组中的第二个一维数组,类型依旧是int(*)[4]。sizeof计算一个指针变量的大小就是4或8个字节;
第九行:&a[0]+1是二维数组中的第二个一维数组的地址,类型是int(*)[4]。对这个数组指针解引用就是二维数组中的第二个一维数组。sizeof计算这个一维数组的大小就是4*4=16个字节;
第十行:二维数组名a没有单独放在sizeof内部也没有单独放在&后,表示首元素的地址,即第一个一维数组的地址。*a就是二维数组中的第一个一维数组。sizeof计算这个一维数组的大小就是4*4=16个字节;
第十一行:a[3]本质上就是*(a+3),但是二维数组a只有3个元素,a向后移动3个一维数组的长度很明显是越界了。但是,二维数组名a没有单独放在sizeof内部也没有单独放在&后,表示首元素的地址,即第一个一维数组的地址。对这个int(*)[4]型的数组指针偏移后,依然是一个数组指针,指向的是二维数组a后的一块大小为4*4=16个字节的空间。对这个数组指针解引用得到的就是一块16字节的空间。sizeof计算这块空间的大小结果当然是16:
最后,我们可以用编译器来验证一下:
总结
在这篇博客中,我们解析了一些关于数组与指针的题目。
这是前半部分,很快就会更新后续内容
如果对本文的内容有任何问题,欢迎在评论区进行讨论哦
希望与各位共同进步!!!