C语言 | C指针强化

        前面我们学习了指针相关知识,本篇博客就带着大家一起梳理之前学过的知识,强化对指针的了解,本文主以指针相关面试题为主。

目录

1.一维数组

2.一级指针 

 3.二维数组

4.强化笔试题


1.一维数组

        观察下列代码,揣测结果。

//一维数组
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));

//一维数组

int a[] = {1,2,3,4};

printf("%d\n",sizeof(a));

        当sizeof中单独放数组名时,数组名代表整个数组,数组里有4个元素且都为整型,故输出16。

printf("%d\n",sizeof(a+0));

        sizeof中并没有单独存放数组名,故这里数组名代表首元素地址,加0依旧是地址,地址的大小为4字节(32位机器)或者8字节(64位机器)。a -- int*

printf("%d\n",sizeof(*a));

        这里a也没单独存在sizeof中,故为首元素地址,解引用后为整型,故为4个字节。

printf("%d\n",sizeof(a+1));

        这里a并未单独存在于sizeof中,故a代表首元素地址,a+1后指向数组下一个整型,依然是地址,故输出 4/8

printf("%d\n",sizeof(a[1]));

        这里访问的是数组中第二个元素,是整型,故输出4

printf("%d\n",sizeof(&a));

        &a是取出整个数组的地址,地址的大小为4/8个字节,其中&a -- int(*)[4]

printf("%d\n",sizeof(*&a));

        &a取出的是整个数组的地址,接着对数组的地址解引用,访问的是整个数组,故这里输出16,&a ---- int (*)[4]      *&a  ----  int [4]

printf("%d\n",sizeof(&a+1));

        &a是取出整个数组的地址,对其加1则是跳过整个数组,也就是16字节,但其本身还是地址,地址的大小就为 4/8。

printf("%d\n",sizeof(&a[0]));

        此处是取出数组中第一个元素的地址,故为4/8 字节。

printf("%d\n",sizeof(&a[0]+1));

        此处是取出数组第一个元素的地址并对其加1,得到的是第二个元素的地址,地址的大小还是4/8。

//字符数组
	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));  

//字符数组
    char arr[] = { 'a','b','c','d','e','f' };
    printf("%d\n", sizeof(arr));  

        数组名在sizeof中单独出现,表示整个数组,故输出为6
    printf("%d\n", sizeof(arr + 0));

        数组名没有在sizeof中单独出现代表首元素地址,加0还是首元素地址,地址的大小为4/8,故输出4/8
    printf("%d\n", sizeof(*arr));  

        数组名在sizeof中没有单独出现代表数组首元素地址,对其解引用得到一个字符,字符的大小为1,故输出1
    printf("%d\n", sizeof(arr[1]));

        直接获取数组第二个元素,输出1
    printf("%d\n", sizeof(&arr));  

        &数组名表示取整个数组的地址,数组的地址也是地址,故输出4/8
    printf("%d\n", sizeof(&arr + 1));  

        &数组名获取整个数组的地址,再加1即跳过一个数组,还是一个地址,故输出4/8
    printf("%d\n", sizeof(&arr[0] + 1));

        取第一个数组中第一个元素加1,跳过一个元素,取下个元素地址,故输出4/8
    printf("%d\n", strlen(arr)); 

        数组名代表首元素地址,strlen函数传过去首元素地址,由于strlen只有碰到\0会停止,并返回\0前的字符个数,由于题中数组并没有\0,故会越界直至碰到\0,所以最终生成随机值。
    printf("%d\n", strlen(arr + 0));  

        数组名代表首元素地址,加0同样代表首元素地址,传给strlen后依然如上,输出随机值。
    printf("%d\n", strlen(*arr));  

        数组名代表首元素地址,对其解引用,得到数组首元素a。将a的ASCII当作地址传给strlen函数,系统不允许访问这块内存,所以该代码会报错。
    printf("%d\n", strlen(arr[1]));

        获取第二个元素b,并将其ASCII当作地址传给strlen依然会报错。
    printf("%d\n", strlen(&arr)); 

        &数组名取得是整个数组的地址,然后传给strlen后,由于数组中没有\0,故最终产生随机值。
    printf("%d\n", strlen(&arr + 1));   

        &数组名取得整个数组的地址,再加一跳过一个数组,传入strlen后依旧是随机值,对比以上随机值会-6。
    printf("%d\n", strlen(&arr[0] + 1));  

        首元素的地址然后加1,跳过一个元素,传给strlen后依旧是随机值,不过会-1。

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));

char arr[] = "abcdef";
printf("%d\n", sizeof(arr));

        数组名代表整个数组,因为数组中除了保存字符abcdef还保存了字符串的结束字符\0,故数组大小为7,输出7。
printf("%d\n", sizeof(arr+0));

        数组名没有单独出现在sizeof中,故代表首元素地址,加0后,依然是首元素地址,地址的大小为4/8。
printf("%d\n", sizeof(*arr));

        数组名没有单独出现在sizeof中,故代表首元素地址,对其解引用后,得到字符a,字符的大小为1。
printf("%d\n", sizeof(arr[1]));

        取到的是第二个元素,故输出1。
printf("%d\n", sizeof(&arr));

        &数组名取到的是整个数组的地址,故输出4/8。
printf("%d\n", sizeof(&arr+1));

        &数组名取到的是整个数组的地址,对其加1跳过整个数组,但依然是地址,输出4/8。
printf("%d\n", sizeof(&arr[0]+1));

        取出数组第一个元素的地址,加1得到第二个元素的地址,输出4/8。
printf("%d\n", strlen(arr));

        数组名代表首元素地址,传给strlen后,碰到\0后返回\0前的字符数,输出6。
printf("%d\n", strlen(arr+0));

         数组名代表首元素地址,加0后依然是首元素地址,传给strlen后,碰到\0后返回\0前的字符数,输出6。
printf("%d\n", strlen(*arr));

        数组名代表首元素地址,解引用后得到字符a,传给strlen后会报错。
printf("%d\n", strlen(arr[1]));

        字符b传给strlen后依然会报错。
printf("%d\n", strlen(&arr));

        &数组名代表取出整个数组的地址,传给strlen后,算出字符长度依旧为6。
printf("%d\n", strlen(&arr+1));

        &数组名代表取出整个数组的地址,对其加一后,跳过整个数组,即跳到\0后,无法预知什么时候能碰到\0。因此此处输出为随机值。
printf("%d\n", strlen(&arr[0]+1));

        取出第一个元素的地址加1后,得到第二个元素的地址,传给strlen后,算出字符长度为5。

2.一级指针 

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));

 char *p = "abcdef";
printf("%d\n", sizeof(p));

        p是一个字符指针,指针的大小是4/8,故输出4/8。
printf("%d\n", sizeof(p+1));

        p是一个字符指针,加一后,跳过一个字符,依旧是一个指针,故输出4/8。
printf("%d\n", sizeof(*p));

        p是一个字符指针,解引用后得到字符a,故输出1。
printf("%d\n", sizeof(p[0]));

        p[0]与*(p+0)相似,取得是数组第一个元素,故输出1。
printf("%d\n", sizeof(&p));

        p是一个字符指针,取地址后,得到的是一个二级指针,二级指针也是指针,故输出4/8。
printf("%d\n", sizeof(&p+1));

        &p得到二级指针后,再进行加1,则是跳过一个字符指针,依旧是一个指针,故输出4/8。
printf("%d\n", sizeof(&p[0]+1));

        取出第一个元素的地址值后加一,得到第二个元素地址,故输出4/8。
printf("%d\n", strlen(p));

        p中储存的是字符串中第一个字符的地址,传给strlen后,计算\0前的字符个数并返回,故输出6。
printf("%d\n", strlen(p+1));

        p是一个字符指针,加1后跳过一个字符,因此输出5。
printf("%d\n", strlen(*p));

        对p进行解引用后得到字符a,把字符a当作地址传给strlen,程序报错。
printf("%d\n", strlen(p[0]));

        与上面相同,把字符a当作地址传给strlen,程序报错。
printf("%d\n", strlen(&p));

        &p得到的是指针p的地址,即二级指针,该二级指针变量储存的是p的地址,故何时碰到\0不确定,输出随机值。
printf("%d\n", strlen(&p+1));

        &p+1后,跳过的是一个字符指针的大小,何时碰到\0也不确定,故输出随机值。
printf("%d\n", strlen(&p[0]+1));

        取到a的地址后加1,得到b的地址,传给strlen后,输出5。

 3.二维数组

//二维数组
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]));

 //二维数组
int a[3][4] = {0};

        a是一个三行四列二维数组,我们可以将这个二维数组看作一维数组,这个一维数组中的每个元素都是一个一维数组,而这些一维数组的数组名分别为a[0]a[1]a[2],这样理解后面的题目会好做很多。
printf("%d\n",sizeof(a));

       sizeof中单独存放数组名,读取整个二维数组的大小,故输出48。
printf("%d\n",sizeof(a[0][0]));

        a[0][0]是二维数组中第一个元素,故输出4
printf("%d\n",sizeof(a[0]));

        a[0]可看作数组名,数组名单独放在sizeof中读取整个数组的大小,故输出16。
printf("%d\n",sizeof(a[0]+1));

        a[0]没有单独存在于sizeof中,代表a[0]数组中的第一元素的地址,加一后,跳过一个整型,故是第一行第二个元素的地址,故输出4/8。
printf("%d\n",sizeof(*(a[0]+1)));

        (a[0] + 1)是第二行的地址,那么解引用后,取到的是整行,因此输出16。
printf("%d\n",sizeof(a+1));

        a没有单独放在sizeof中,故代表首元素地址,即第一个子数组a[0]的地址,对其加一后,来到第二个子数组,也是地址,故输出4/8。
printf("%d\n",sizeof(*(a+1)));

        (a+1)代表第二个子数组的地址,对其解引用后,则访问整个数组,故输出16。
printf("%d\n",sizeof(&a[0]+1));

        取出第一个子数组的地址后加一,得到第二个子数组的地址,还是地址,故输出4/8。
printf("%d\n",sizeof(*(&a[0]+1)));

        (&a[0]+1)代表第二个子数组的地址,再对其解引用,访问整个数组,故输出16。
printf("%d\n",sizeof(*a));

         a没有单独放在sizeof中,故代表首元素地址,对其解引用后,得到第一个子数组,故输出16。
printf("%d\n",sizeof(a[3]));

        a[3]是一个不存在的子数组,可将其理解为*(a+3),访问的还是整个数组,故输出16。

总结:数组名的意义

1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。

2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。

3. 除此之外所有的数组名都表示首元素的地址。 

4.强化笔试题

笔试题1 

int main()
{
    int a[5] = { 1, 2, 3, 4, 5 };
    int *ptr = (int *)(&a + 1);
    printf( "%d,%d", *(a + 1), *(ptr - 1));
    return 0;
}
//程序的结果是什么?

        数组代表首元素地址,即1的地址,而&数组名代表整个数组的地址,他们指向同一块空间,但是由于他们的类型不同,加1有很大的差距。

        &a取出整个数组的地址后,加1,是跳过整个数组,即&a的类型为int(*)[5],加1则跳到5后面的那块地址,再强制类型转换为int*类型,最后赋值给整型指针ptr。

        而a代表首元素地址,是int*类型,对其加1即跳过一个整型,指向元素2。

        输出时,第一输出2,第二个时ptr指针-1,即减去一个整型,输出5

 

 笔试题2

//这里告知结构体的大小是20个字节
struct Test
{
 int Num;
 char *pcName;
 short sDate;
 char cha[2];
 short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
 p = (struct test*)0x100000;
 printf("%p\n", p + 0x1);
 printf("%p\n", (unsigned long)p + 0x1);
 printf("%p\n", (unsigned int*)p + 0x1);
 return 0;
}

        分析题目,我们可以看到有一个结构体类型的指针p,结构体的大小为20字节,此时p的值为0x100000,当我们输出p + 0x1时,即也就是指针加1,指针的类型决定指针加1的步长,故跳过一个结构体,输出0x100014;第二输出语句p转换为了无符号长整形,即数字与数字相加,直接加上就好了,即输出0x100001;第三个输出语句将p转换了无符号整型指针,并对其加1,指针的类型决定了的步长,故加4,输出0x100004;

笔试题3

int main()
{
    int a[4] = { 1, 2, 3, 4 };
    int *ptr1 = (int *)(&a + 1);
    int *ptr2 = (int *)((int)a + 1);
    printf( "%x,%x", ptr1[-1], *ptr2);
    return 0;
}

        ptr1:&是取整个数组的地址取整个数组地址加1,则跳过整个数组,故ptr1指向该数组最后一个元素的后面。

        prt2:a代表首元素地址,将首元素地址转换为整型,对整型加1即数字直接加1,再将这个给加1以后的整型转换为整型指针赋值给ptr2。以下指针如下图所示。

        之前我们学过,指针的类型可以决定步长,也可以决定解引用访问的字节数,输出中,我们输出ptr[-1]即输出*(ptr-1),ptr是整型指针,因此减1后是将ptr向前挪动四个字节,然后对其解引用打印,也是访问四个字节,即输出00000004,而ptr2也是如此解引用访问四个字节,即输出20000000。

 笔试题4

#include <stdio.h>
int main()
{
    int a[3][2] = { (0, 1), (2, 3), (4, 5) };
    int *p;
    p = a[0];
    printf( "%d", p[0]);
 return 0;
}

        首先我们仔细惯出,a是一个3行两列的数组,仔细观察储存的数据,里面放的是3个逗号表达式,即实际储存如下图所示;

         清楚数组中的数据以后,我们再来p指针中存放的数据,p是一个整型指针,a[0]我们可以理解为整个二维数组的第一个子数组名,即第一行数组名,而数组名代表首元素地址,即是元素1的地址,将这个给地址赋值给p,p[0]即*(p+0),最终输出结果即为1。

 笔试题5

int main()
{
    int a[5][5];
    int(*p)[4];
    p = a;
    printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
    return 0;
}

        指针p为数组指针且指针的类型为一个有四个元素的整型数组,接着把a即二维数组的首地址赋值给p。&p[4][2]和&a[4][2]的位置如下图所示。

        指针减指针即得到他们中间元素个数,因此结果为-4,而-4以地址的形式打印为0xFFFFFFFC,以整型打印的结果为-4是。

笔试题6 

int main()
{
    int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    int *ptr1 = (int *)(&aa + 1);
    int *ptr2 = (int *)(*(aa + 1));
    printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));
    return 0;
}

ptr1:&aa是取得整个二维数组的地址,故对其加1则跳过整个二维数组,即指向10后面的地址。

prt2:aa代表首元素地址,即第一个子数组的地址,加1后跳过一个子数组,指向第二个子数组,再对其解引用,即得到第二个子数组的数组名,子数字名代表首元素地址,即指向子元素元素6。

在输出的时候分别对两个指针减1解引用,因此分别输出10,5。

 笔试题7

#include <stdio.h>
int main()
{
 char *a[] = {"work","at","alibaba"};
 char**pa = a;
 pa++;
 printf("%s\n", *pa);
 return 0;
}

a是一个指针数组,数组中存放了三个字符指针,a代表首元素地址,即如下图所示

此时对pa++即跳过一个字符指针,指向第二个字符指针,最后输出at。

笔试题8

int main()
{
 char *c[] = {"ENTER","NEW","POINT","FIRST"};
 char**cp[] = {c+3,c+2,c+1,c};
 char***cpp = cp;
 printf("%s\n", **++cpp);
 printf("%s\n", *--*++cpp+3);
 printf("%s\n", *cpp[-2]+3);
 printf("%s\n", cpp[-1][-1]+1);
 return 0;
}

 首先根据代码的三个变量画出如下关系:

 执行完第一个printf语句后,输出POINT,上图发生如下改变:

 执行完第二个printf语句后,输出ER,上图发生如下改变:

执行完第三个printf语句后,输出ST,且并未改变任何指针的指向;

执行完第三个printf语句后,输出EW,且未改变任何指针指向。最终输出如下图

 

        本文小编就带着大家把题目做到这了,掌握上述题目,大家对指针的了解应该很透彻了,指针是C语言的灵魂,都在流传不学指针的C语言就不叫C语言,希望本文可以给大家带来收获,非常感谢各位粉丝不断地支持,看到这给个免费的关注呗。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值