目录
前言:
C 和 C++ 语言中,“% p” 通常用于以十六进制形式输出指针的值
“% x” 通常作为格式化输出的占位符,用于以十六进制形式输出整数
1.sizeof与strlen的区别
1.1sizeof
在学习操作符时我们学习了sizeof操作符,sizeof计算变量所占内存空间的大小单位是字节,如果操作数是类型的话,计算的是使用类型创建的变量所占内存的大小
sizeof 只关注占⽤内存空间的⼤⼩,不在乎内存中存放什么数据。
#include <stdio.h>
int main()
{
int a = 10;
printf("%d\n", sizeof(a));
printf("%d\n", sizeof a);
printf("%d\n", sizeof(int));
return 0;
}
2.2strlen
strlen是库函数,用来计算一个字符长度,其原理是:
从函数中的参数,地址一直向后开始数字符个数,一直数到\0。
strlen函数会一直向后找\0,直到找到为止,所以可能存在越界查找。
例如:
int main()
{
char a[] = "i love u";
char b[] = { 'i','l','o','v','e','u' };
printf("%d\n", strlen(a));
printf("%d\n", strlen(b));
return 0;
}
我们知道字符串的后面是包括"\0"的
但是b数组并没有\0那么strlen会一直找下去,所以我们知道第一个结果应该说8,第二个结果是随机值
2.关于数组与指针的题目细致分析
下面的题目只要搞懂一个重要概念:
数组名的含义:
1.arr(数组名)单独在sizeof中时,这里的数组名代表着整个数组;
2.&arr(数组名)时,这里的数组名也代表整个数组;
3.其余情况,数组名都代表数组首元素的地址。
2.1一维数组
题目:
int main()
{
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 main()
{
int a[] = { 1,2,3,4 };
printf("%d\n", sizeof(a));
//数组名a单独放在sizeof中,计算的是整个数组的大小
//int类型大小是4,一共有4个元素,大小是4*4 = 16
printf("%d\n", sizeof(a + 0));
//这里a并没有单独放在sizeof中,所以a代表的是首元素地址
//a+0代表第一个元素的地址,地址就是4/8个字节
printf("%d\n", sizeof(*a));
//a被解引用,a先和*结合的这里的a代表着首元素地址,解引用
//就是首元素,int类型大小,所以应该是4
printf("%d\n", sizeof(a + 1));
//a+1,a代表首元素地址,a+1则代表跳到下一个元素
//也就是a[1]的地址,是地址则大小为4/8个字节
printf("%d\n", sizeof(a[1]));
//a[1]就是第二个元素
//大小是4个字节
printf("%d\n", sizeof(&a));
//取地址a,这里的a是代表整个数组的大小
//是地址,所以大小是4/8个字节
printf("%d\n", sizeof(*&a));
//先取a的地址,这里a代表整个数组的地址
//再解引用整个数组的地址,得到整个数组大小为 4*4 = 16字节
printf("%d\n", sizeof(&a + 1));
//这里a取地址,是整个数组的大小
//再加一是跳过整个数组。我们在前面说过,sizeof只计算类型的大小,不会直接访问这个数组,所以不会越界
//是地址,所以大小是4/8个字节
printf("%d\n", sizeof(&a[0]));
//a[0]是首元素,取地址则得到首元素的地址
//是地址大小为4/8个字节
printf("%d\n", sizeof(&a[0] + 1));
//取首元素的地址加一,这里应该是第二个元素的地址
//是地址大小为4/8个字节
}
运算结果
2.2字符数组
题目一组:
int main()
{
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));
return 0;
}
题目解析
int main()
{
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", sizeof(arr));
//arr单独放在sizeof里面代表整个数组
//大小是 1*6 = 6
printf("%d\n", sizeof(arr + 0));
//arr没有单独放在sizeof里面也不是取地址,arr是数组首元素的地址,
//arr+0,就是第一个元素的地址,是地址就是4/8个字节
printf("%d\n", sizeof(*arr));
//arr直接被解引用,arr是首元素的地址,解引用是首元素
//char类型的数组,每一个元素都是1字节,大小是1字节
printf("%d\n", sizeof(arr[1]));
//arr[1]是数组第二个元素,char类型数组每一个元素都是1字节
//大小就是1字节
printf("%d\n", sizeof(&arr));
//取arr的地址,这arr就是整个数组的地址
//是地址大小就是4/8个字节
printf("%d\n", sizeof(&arr + 1));
//这里取整个数组的地址加一,则跳过整个数组
//我们前面说过sizeof不会直接访问数组,只会根据类型来计算大小,所以不会越界
//是地址,大小就是4/8个字节
printf("%d\n", sizeof(&arr[0] + 1));
//取arr[0]的地址就是取首元素的地址,加一就是arr[1](第二个元素)的地址
//是地址大小就是4/8个字节
printf("%d\n", strlen(arr));
//arr没有单独放在sizeof里面,也没有取地址,所以arr是数组首元素的地址
//从首元素开始往后查找\0,一直找到为止,数组里面并没有\0所以,这个数是一个随机值
printf("%d\n", strlen(arr + 0));
//arr没有单独放在sizeof里面,也没有取地址,所以arr是数组首元素的地址
//从首元素开始往后查找\0,一直找到为止,数组里面并没有\0所以,这个数是一个随机值
printf("%d\n", strlen(*arr));
//这里直接传过去的是一个字符,这个字符的ASCII码值被当成一个地址
//但是这个地址并不是*arr的地址,所以编译器会报错
printf("%d\n", strlen(arr[1]));
//这里直接传过去的是一个字符,这个字符的ASCII码值被当成一个地址
//但是这个地址并不是arr[1]的地址,所以编译器会报错
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr + 1));
printf("%d\n", strlen(&arr[0] + 1));
//arr没有单独放在sizeof里面,也没有取地址,所以arr是数组首元素的地址
//从首元素开始往后查找\0,一直找到为止,数组里面并没有\0所以,这个数是一个随机值
return 0;
}
我们把会报错的代码注释掉得到下面的结果
题目二组:
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));
//arr单独放在sizeof里面,代表整个数组,计算的是整个数组的大小
//这是char类型的数组,每个元素大小是1字节,所以大小是 1*(6+1) = 7字符串里面还有一个‘\0’不要忘记
printf("%d\n", sizeof(arr + 0));
//arr代表的是数组首元素的地址
//地址就是4/8个字节
printf("%d\n", sizeof(*arr));
//这里的arr数组名是首元素的地址,接引用就是首元素
//首元素大小就是1字节
printf("%d\n", sizeof(arr[1]));
//数组第二个元素的大小
//第二个元素的大小就是1字节
printf("%d\n", sizeof(&arr));
//这里的arr代表整个数组,再取地址我们就得到整个数组的地址
//是地址大小就是4/8个字节
printf("%d\n", sizeof(&arr + 1));
//这里的arr代表整个数组,再取地址我们就得到整个数组的地址
// 再加一就是跳过整个数组,但sizeof不会去真的访问数组,只根据类型来确定大小
//是地址大小就是4/8个字节
printf("%d\n", sizeof(&arr[0] + 1));
//这里取arr[0]的地址,加1就是访问下一个元素
//得到的就是第二个元素的地址,是地址大小就是4/8个字节
printf("%d\n", strlen(arr));
//arr这里没有放在sizeof里面,也没有&arr所以arr是首元素的地址
//从首元素的地址一直向后读取早知道读取到\0,这个数组存入一个字符串,字符串最后有一个隐藏的\0
//所以这个数就是6
printf("%d\n", strlen(arr + 0));
//这里的arr是数组首元素的地址,加0,向后访问0个,还是首元素的地址
//从首元素一直数到\0,结果还是6
printf("%d\n", strlen(*arr));
//这里arr是首元素的地址,解引用得到第一个字符,传过去的是字符的ASCII码值,strlen会把这个值当成一个地址
//但是这个地址不是*arr的地址,所以编译器会报错
printf("%d\n", strlen(arr[1]));
//这里arr[1]是第二个字符元素,传过去的是字符的ASCII码值,strlen会把这个值当成一个地址
//但是这个地址不是*arr的地址,所以编译器会报错
printf("%d\n", strlen(&arr));
//这里arr代表整个数组
//&arr是得到整个数组的地址,也就是最开头的地址开始数,数到\0为止,所以结果就是6
printf("%d\n", strlen(&arr + 1));
//这里arr代表整个数组,&arr得到整个数组的地址,加一跳过整个数组
//那么就超出了数组的范围,我们也不知道什么时候回遇到\0所以结果是一个随机值
printf("%d\n", strlen(&arr[0] + 1));
//这里是取出第一个元素的地址,加一访问下一个元素,
//也就是得到下一个元素的地址,从第二个元素开始数,所以结果应该是5
我们注释掉会报错的代码
题目三组
int main()
{
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));
return 0;
}
解析:
int main()
{
char* p = "abcdef";//字符串是把首元素的地址存入了指针之中
printf("%d\n", sizeof(p));
//这里p是一个地址
//地址的大小就是4/8个字节
printf("%d\n", sizeof(p + 1));
//p+1也是一个地址,p相当于数组首元素的地址,p+1就是第二个元素的地址
//是地址,大小就是4/8个字节
printf("%d\n", sizeof(*p));
//这里的p是首元素的地址
//解引用自然是首元素,就是字符'a'也就是1字节
printf("%d\n", sizeof(p[0]));
//p[0]就是第一个元素的大小
//大小就是1字节
printf("%d\n", sizeof(&p));
//这里取地址p,p就相当于数组名,p就代表整个数组,取出的是整个数组的地址
//是地址大小就是4/8字节
printf("%d\n", sizeof(&p + 1));
//这里p代表整个数组,加一就跳过整个数组
//是地址就是4/8个字节
printf("%d\n", sizeof(&p[0] + 1));
//这里就是取第一个元素的地址,加一就是调到第二个元素的地址
//是地址大小就是4/8个字节
printf("%d\n", strlen(p));
//字符串最后都会隐藏一个\0
//这里的p就是首元素的地址,就从开始数到\0
//大小就是6
printf("%d\n", strlen(p + 1));
//p是数组首元素的地址,首元素地址加一就是第二个元素的地址
//从第二个元素开始数,则是5
//printf("%d\n", strlen(*p));
//这里解引用得到的是首元素的值,也就是一个字符,传给strlen是他的ASCII码值
//strlen把这个值当成地址,但是他不是*p的地址,所以编译器会报错
//printf("%d\n", strlen(p[0]));
//这里和上面题一样,给strlen是一个字符的ASCII码值
//编译器会报错
printf("%d\n", strlen(&p));
//这里的p就是整个数组,这里取地址p取整个数组的地址,从起始开始数一直数到\0
//所以大小就是6
printf("%d\n", strlen(&p + 1));
//这里的p就是整个数组,取地址就是取整个数组的地址,加一就是跳过整个数组,我们也不知道在这个数组外什么时候能遇到\0
//所以这里是一个随机值
printf("%d\n", strlen(&p[0] + 1));
//这里是取第一个元素的地址+1就是第二个元素的地址
//所以就是从第二个元素开始数,所以就是5
return 0;
}
我们把不能运行的代码屏蔽掉,运行如下:
这组题的要点就是,字符串存入一个指针时是把首元素的地址存入这个指针变量里
这个指针p就相当于这个字符串的数组名,可以和数组名的用法来对比
2.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 main()
{
int a[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
printf("%d\n", sizeof(a));
//a作为数组名单独在sizeof里面,所以a代表的是整个数组,sizeof计算的是整个数组的大小,
//int 类型一共有3*4 = 12个元素,12*4 = 48,一共是48个字节
printf("%d\n", sizeof(a[0][0]));
//这里a[0][0]是第一个元素
//大小就是4个字节
printf("%d\n", sizeof(a[0]));
//二维数组就是一维数组的数组,数组名就是首元素的地址,数组名加[0]就是第一个元素,也就是第一行的数组名
//这个数组名单独放在sizeof里面所以是整个第一行数组的大小也就是4*4 =16个字节
printf("%d\n", sizeof(a[0] + 1));
//a[0]是a数组第一个元素的名字,也就是a[0][i]这个数组的数组名,数组名加一,就是数组第一个元素的地址调到
// 第二个元素的地址,也就是从a[0][0]地址跳到a[0][1]地址
//是地址大小就是4/8个字节
printf("%d\n", sizeof(*(a[0] + 1)));
//a[0]是a数组第一个元素的名字,也就是a[0][i]这个数组的数组名,数组名加一,就是数组第一个元素的地址调到
// 第二个元素的地址,也就是从a[0][0]地址跳到a[0][1]地址这时我们把这个地址解引用得到的就是
//a[0][1]这个元素,这个元素的大小是4个字节
printf("%d\n", sizeof(a + 1));
//这里a就是首元素的地址,首元素的地址加一跳到第二个元素的地址也就是a[1]
//是地址大小就是4/8个字节
printf("%d\n", sizeof(*(a + 1)));
//a是a[i]数组首元素的地址所以是a[0]的地址,这个地址加一就跳到了a[1]的地址
//解引用这个地址得到第二行的数组,大小就是4*4 = 16字节
printf("%d\n", sizeof(&a[0] + 1));
//a[0]是a[i]数组的首元素,也是a[0][i]数组的名字,这里直接取地址数组名,数组名就代表整个数组,整个数组的地址
// 被取出加一就是跳过这个数组,也就是a[1]数组的地址
//是地址大小就是4/8个字节
printf("%d\n", sizeof(*(&a[0] + 1)));
//a[0]是a数组第一个元素,也就是第一行的数组,这个数组名被取地址,代表整个第一个数组的地址,加一就是第二个数组的地址
//解引用第二个数组的地址就是,第二个数组大小16
printf("%d\n", sizeof(*a));
//a没有单独放在sizeof里面也没有被取地址,所以a是a数组的首元素的地址,也就是第一个数组的地址
//解引用第一个数组的地址结果就是第一个数组,大小16
printf("%d\n", sizeof(a[3]));
//a[3]的含义是a数组第四个元素,也就是第四个数组,虽然没有第四个数组,但sizeof不会直接访问这个数组,只根据类型来判断大小
//所以a[3]和a[0]是一样的,a[0]是第一个数组的名字,单独放在sizeof里面代表整个第一个数组,计算整个大小,大小就是16字节
return 0;
}
运算图:
总结:
数组名的意义:1. sizeof( 数组名 ) ,这里的数组名表示整个数组,计算的是整个数组的大小。2. & 数组名,这里的数组名表示整个数组,取出的是整个数组的地址。3. 除此之外所有的数组名都表示首元素的地址。
3.指针运算题目的分析
题目一:
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int *)(&a + 1);
printf( "%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
//程序的结果是什么?
答:
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int* ptr = (int*)(&a + 1);
//&a这里的a是整个地址。取整个地址加一就是跳过这个数组的地址,这时(&a+1)的类型就是int (*)[5],强制类型转换成int*存入ptr
printf("%d,%d", *(a + 1), *(ptr - 1));
//a这里代表首元素的地址,类型是int*,加一跳过一个元素,是第二个元素的地址就是2
//ptr的类型也是int*减一就是减去一个元素类型的地址大小,就是5
return 0;
}
题目二:
//由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
这里我们先有了设了一个结构体,然后我们给他定义了一个指针变量p
我们知道p的值0x100000(0x是代表了16进制),且我们知道这个结构体的类型大小是20字节
p+0x1,p指向的是结构体,大小是20个字节,指针在进行算术运算时,移动的字节数,取决于类型的大小 ,所以+1实际上是移动20个字节,有因为0x表示的是16进制,20的16进制就是14,所以p的值是0x100014
这里p指向的是结构体,把他强制类型转换成(unsigned long)在32位环境下是4个字节,我们把p里面的值转化成了一个整形,整形加1就是加一
p的值就是0x100001
我们把p的类型转换成了(unsigned int*)这个类型的大小在32位环境下是4个字节,指针点运算取决于指针指向的类型大小,+1就代表着跳过4个字节
p的值就是0x100004
题目三:
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;
}
解析如下:
int main()
{
int a[4] = { 1, 2, 3, 4 };
int* ptr1 = (int*)(&a + 1);
//我们取地址a,a就代表着整个数组,就是取整个数组的大小,类型就是int(*)[4],
//加一就是跳过整个数组,再给他强制转换为int*,让他变成了指向int类型的指针
int* ptr2 = (int*)((int)a + 1);
//a此时没有单独在sizeof里面,也没有取地址a,所以a代表的是数组首元素地址,把首元素地址
// 强制转换成int类型,再加一就是得到一个地址一样值,再加一,再强制类型
//转换成为int*类型的地址,让他指向一个整形,
printf("%x,%x", ptr1[-1], *ptr2);
//1:ptr1[-1] == *(ptr1 - 1),就是地址向数组内前面移动一个一个数所以是4,%x是以16进制打印整形,所以是4
//2:这里的a加一只是在地址整形含义上加一,并不是地址向后加一,所以他指向的数一个是奇怪的数
return 0;
}
还有一些特别的题目会在以后分享,喜欢的朋友可以点个关注,我会积极更新的
欢迎指正