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

2024-09-24 16:05:02 浏览数 (2)

以上接指针最终篇一

1. sizeof 与 strlen

1.1 字符数组

代码三:

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

代码四:

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

代码五:

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

代码六:

代码语言:javascript复制
#include<stdio.h>
int main()
{
	char* p = "abcdef";
	printf("%dn", strlen(p));			//6
	printf("%dn", strlen(p   1));		//5
	printf("%dn", strlen(*p));			//报错		
	printf("%dn", strlen(p[0]));		//报错
	printf("%dn", strlen(&p));			//6
	printf("%dn", strlen(&p   1));		//随机值
	printf("%dn", strlen(&p[0]   1));	//5
	return 0;
}

1.2 二维数组

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

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

2. 指针运算笔试题解析

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

题目一:

代码语言:javascript复制
#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

题目二:

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

int main()
{
	printf("%pn", p   0x1);
	printf("%pn", (unsigned long)p   0x1);
	printf("%pn", (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

题目三:

代码语言:javascript复制
#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 存放的数据为:

代码语言:javascript复制
{1 , 3
 5 , 0
 0 , 0}

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

题目四:

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

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

题目五:

代码语言:javascript复制
#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

题目六:

代码语言:javascript复制
#include <stdio.h>
int main()
{
	char* a[] = { "work","at","alibaba" };
	char** pa = a;
	pa  ;
	printf("%sn", *pa);
	return 0;
}

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

代码语言:javascript复制
typedef char* ch;

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

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

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

代码语言:javascript复制
#include <stdio.h>
int main()
{
	char* c[] = { "ENTER","NEW","POINT","FIRST" };
	char** cp[] = { c   3,c   2,c   1,c };
	char*** cpp = cp;
	printf("%sn", **  cpp);
	printf("%sn", *-- *   cpp   3);
	printf("%sn", *cpp[-2]   3);
	printf("%sn", 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语言指针全系列已更新完毕,感谢你的支持

0 人点赞