C语言指针超详解——最终篇二

C语言指针系列文章目录

入门篇
强化篇
进阶篇
最终篇一
最终篇二


以上接指针最终篇一

1. sizeof 与 strlen

1.1 字符数组

代码三:

#include<stdio.h>
int main()
{
	char arr[] = "abcdef";//后面有 \0
	printf("%zd\n", sizeof(arr));		//整个数组的大小				7
	printf("%zd\n", sizeof(arr + 0));	//指向第一个元素的指针		8
	printf("%zd\n", sizeof(*arr));		//第一个元素的大小			1
	printf("%zd\n", sizeof(arr[1]));	//第一个元素的大小			1
	printf("%zd\n", sizeof(&arr));		//指向整个数组的指针			8
	printf("%zd\n", sizeof(&arr + 1));	//跳过整个数组,还是指针		8
	printf("%zd\n", sizeof(&arr[0] + 1));//指向第二个元素的指针		8
	return 0;
}

代码四:

#include<stdio.h>
int main()
{
	char arr[] = "abcdef";//后面有 \0
	printf("%d\n", strlen(arr));		//从 arr 开始找 \0			6
	printf("%d\n", strlen(arr + 0));	//从 arr 开始找 \0			6
	printf("%d\n", strlen(*arr));		//传递给 strlen 一个字符		报错
	printf("%d\n", strlen(arr[1]));		//传递给 strlen 一个字符		报错
	printf("%d\n", strlen(&arr));		//从 arr 开始找 \0			6
	printf("%d\n", strlen(&arr + 1));	//跳过整个数组开始找 \0		随机值
	printf("%d\n", strlen(&arr[0] + 1));//从第二个元素开始找 \0		5
	return 0;
}

代码五:

#include<stdio.h>
int main()
{
	char* p = "abcdef";//字符串常量,有 \0,p 不是数组名
	printf("%zd\n", sizeof(p));			//指针变量				8
	printf("%zd\n", sizeof(p + 1));		//指向第二个元素的指针	8
	printf("%zd\n", sizeof(*p));		//第一个元素				1
	printf("%zd\n", sizeof(p[0]));		//第一个元素				1
	printf("%zd\n", sizeof(&p));		//数组的地址,指针		8
	printf("%zd\n", sizeof(&p + 1));	//跳过整个数组,指针		8
	printf("%zd\n", sizeof(&p[0] + 1));	//指向第二个元素的指针	8
	return 0;
}

代码六:

#include<stdio.h>
int main()
{
	char* p = "abcdef";
	printf("%d\n", strlen(p));			//6
	printf("%d\n", strlen(p + 1));		//5
	printf("%d\n", strlen(*p));			//报错		
	printf("%d\n", strlen(p[0]));		//报错
	printf("%d\n", strlen(&p));			//6
	printf("%d\n", strlen(&p + 1));		//随机值
	printf("%d\n", strlen(&p[0] + 1));	//5
	return 0;
}

1.2 二维数组

#include<stdio.h>
int main()
{
	int a[3][4] = { 0 };
	printf("%zd\n", sizeof(a));			
	//整个二维数组的大小,48
	printf("%zd\n", sizeof(a[0][0]));
	//第一行第一个的元素的大小,4
	printf("%zd\n", sizeof(a[0]));
	//第一行元素的大小,a[0]相当于第一行的数组名,16
	printf("%zd\n", sizeof(a[0] + 1));
	//指向第一行元素第二个元素的指针,8
	printf("%zd\n", sizeof(*(a[0] + 1)));
	//第一行第二个元素的大小,4
	printf("%zd\n", sizeof(a + 1));
	//跳过整个数组,指针变量,8
	printf("%zd\n", sizeof(*(a + 1)));
	//跳过第一行,只想第二行的指针变量,相当于数组名,16
	printf("%zd\n", sizeof(&a[0] + 1));
	//跳过第一行,指向第二行的指针变量,8
	printf("%zd\n", sizeof(*(&a[0] + 1)));
	//第二行所有元素的大小,16
	printf("%zd\n", sizeof(*a));
	//指向第一行的数组,相当于数组名,16
	printf("%zd\n", sizeof(a[3]));
	//越界访问,但类型为 int(*)[4],为指针变量,且相当于数组名,16
	return 0;
}

sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
&数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
除此之外所有的数组名都表示首元素的地址。

2. 指针运算笔试题解析

注意:这些题有些难度比较高,作为初学者,没搞懂也没关系,最重要的是记下思路!
当然,同时你可能会遇见一些没听说过的概念,欢迎阅读指针系列的前几篇文章。

题目一:

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

ptr 是什么是本道题的关键
ptr 是跳过整个数组后,强制类型转换成 int* 的指针变量,可以将它看做 a+5
那么 ptr-1 就是 a+4 ,也就是a[5].
*(a+1)很显然就是数组的第二个元素
所以这道题的输出结果为: 2,5

题目二:

//在X86环境下
//假设结构体的大小是20个字节(至于为什么是 20 将在之后的博客中解释)
//程序输出的结果是啥?
struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p = (struct Test*)0x100000;

int main()
{
	printf("%p\n", p + 0x1);
	printf("%p\n", (unsigned long)p + 0x1);
	printf("%p\n", (unsigned int*)p + 0x1);
	return 0;
}

我们来分析:
p 这个指针是以 struct Test 的类型指向 0x100000 处的数据。

  1. 0x1 其实就是16进制的 1 ,那么第一行就是 指针变量+常数,跳过指针指向的类型的大小,这里是 20,所以第一行输出应该是 0x100014,注意是16进制的!
  2. 第二个将 p 强制类型转换为 unsigned long ,所以现在参与运算的 p 是一个整数,加上一就是:0x100001。(当然,在这里 VS 编译器会给出警告,我们只分析结果,不考虑它是否合规)
  3. 第三个将 p 强制类型转换为 unsigned int*,那么参与运算的 p 仍然是一个指针,所以依然是 指针变量+常数,跳过指针指向类型的大小 4,所以第三个代码的结果为:0x100004

题目三:

#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;
}

看到这个代码,你可能下意识地会觉得是 0,但实际上,如果你仔细观察,会发现在第一层大括号里的本应该是大括号的位置被小括号代替了,那这是什么意思呢?
其实这里的()是一个操作符,之前的博客介绍过,它的功能就是改变运算顺序,那这里他改变了什么顺序呢?
()里面的是什么?实际上是逗号表达式,逗号表达式的结果是最后一个操作数,所以实际上这个二维数组 a 存放的数据为:

{1 , 3
 5 , 0
 0 , 0}

p[0]指向的地方就是 a 的第一行第一个数据,也就是 1

题目四:

//假设环境是x86环境,程序输出的结果是什么?
#include <stdio.h>
int main()
{
	int a[5][5];
	int(*p)[4];
	p = a;
	printf("%p,%d\n", &a[4][2] - &p[4][2], &a[4][2] - &p[4][2]);
	return 0;
}

这道题的关键在于:p 是什么?, p 是一个数组指针,指向一个有 4 个元素的 int 类型的数组,
当然这个 p 也可以当做一个一行有 4 个元素的二维数组来看待,(详见进阶篇)那么这就是这道题的关键就在于 a 和 p 的每行的元素个数不一样这一点上了。
a,p的对应关系
那么答案就很简单了,是 00000004,4,指针-指针得到的是指针之间的元素个数。

题目五:

#include <stdio.h>
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;
}

ptr1ptr2 分别指向什么位置是本题的关键。

  1. ptr1&aa+1,&aa 是取出整个数组的地址,指针变量+1 跳过指针指向的类型的大小,这里就是跳过了整个二维数组,指向了二维数组最后一个元素的后一个元素(无论这个位置存放的是什么),也可以理解为是 aa[2][0],那么 ptr1-1就是 arr[1][4],也就是数组的最后一个元素 10
  2. ptr2*(aa+1),aa 数组名,这里代表第一个元素的地址,二维数组的第一个元素是什么?是第一行数据,所以 aa+1就是 aa[1]aa[1]也是一个数组名,代表首元素地址,对它解引用,得到的就是 a[1][0],也就是 5

题目六:

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

注意这里的 a 是什么。 a 是一个指针数组,a 是一个数组名,数组中存储的元素是 char*,那么我们可以利用 typedef来简化一下这个代码,让它变得更好理解一些。

typedef char* ch;

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

那么这样看就很简单了, a 是一个数组名,指向首元素地址,将 a 的地址存储在 pa 中,pa++就是指向了数组的下一个元素,也就是 at

题目七:
本题比较复杂,由于本人讲题的实力有限,如果看不懂建议可以自己画图分析

#include <stdio.h>
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;
}

我们先来分析一个打印之前的内容:

  1. c 是一个指针数组,数组类型为 char* ,存放了 4 个字符串。
  2. cp 是一个指针数组,数组类型为 char**,存放了 4 个指针,分别指向FIRST,POINT,NEW,ENTER这四个字符串。
  3. cpp 是一个指针,类型为char***,指向的是cp[0],也就是 FIRST。

接下来我们分析这 4 个打印语句:

第一句:
++cpp会让它指向 cp[1],也就是 POINT,那么打印的结果就是 POINT
**注意这里 cpp 已经发生了变化,指向了 POINT **。

第二句:
*-- * ++cpp + 3,我们首先分析优先级,+ 的优先级最低,最后执行,那么除了 + 外,表达式从右向左指向。
++cpp,改变 cpp 使其指向 NEW,解引用得到的是cp[2],也就是 c+1
*--上一步得到的是 c + 1,自减得到的是 c注意这里是把 cp 里的第三个元素修改了,使其指向 c 的第一个元素),再解引用得到就是指向 E 的指针。
+ 3,指针+常数,那么就是跳过 3 个元素,指向的就是 E ,所以打印的结果是 ER

第三句
*cpp[-2] + 3
cpp现在指向的是 cp[2]cpp[-2]就是从 cp[2] 的地址往前找两个元素(cpp[-2]就相当于
cpp-2),找到的是 cp[0],也就是 FIRST
再解引用,找到的是一个指向 F 的指针,再+3,就找到的是 S ,那么打印的结果就是 ST

第四句
cpp[-1][-1] + 1):
cpp 现在指向的是 cp[2],那么和第三句同理, cpp[-1]指向的就是 cp[1],也就是 POINT
那么 再使用一次下标操作符,找到的是,POINT 前面的那一个元素,也就是 NEW ,再 +1 ,得到的是 E 的地址,那么打印的结果就是 EW

谢谢你的阅读,喜欢的话来个点赞收藏评论关注吧!
C语言指针全系列已更新完毕,感谢你的支持

  • 18
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值