C语言指针系列文章目录
入门篇
强化篇
进阶篇
最终篇一
最终篇二
文章目录
- C语言指针系列文章目录
- 1. sizeof 与 strlen
- 1.1 字符数组
- 1.2 二维数组
- 2. 指针运算笔试题解析
以上接指针最终篇一
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
处的数据。
- 0x1 其实就是16进制的 1 ,那么第一行就是 指针变量+常数,跳过指针指向的类型的大小,这里是 20,所以第一行输出应该是
0x100014
,注意是16进制的! - 第二个将 p 强制类型转换为
unsigned long
,所以现在参与运算的 p 是一个整数,加上一就是:0x100001
。(当然,在这里 VS 编译器会给出警告,我们只分析结果,不考虑它是否合规) - 第三个将 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 的每行的元素个数不一样这一点上了。
那么答案就很简单了,是 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;
}
ptr1
和 ptr2
分别指向什么位置是本题的关键。
ptr1
是&aa+1
,&aa 是取出整个数组的地址,指针变量+1 跳过指针指向的类型的大小,这里就是跳过了整个二维数组,指向了二维数组最后一个元素的后一个元素(无论这个位置存放的是什么),也可以理解为是aa[2][0]
,那么ptr1-1
就是arr[1][4]
,也就是数组的最后一个元素 10。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;
}
我们先来分析一个打印之前的内容:
- c 是一个指针数组,数组类型为
char*
,存放了 4 个字符串。 - cp 是一个指针数组,数组类型为
char**
,存放了 4 个指针,分别指向FIRST,POINT,NEW,ENTER
这四个字符串。 - 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语言指针全系列已更新完毕,感谢你的支持