前言
大家好呀,我是Humble
这是我们指针小专题的最后一篇博客,也是C语言初阶专栏的最后一篇博客。通过前三篇的 基础篇 以及四和五的 应用篇,相信大家对指针的理解都有了提升,那么我们本篇就来做一下练习
一.前置知识: sizeof和strlen的对比
在学习操作符的时候,我们学习了 sizeof ,知道它是一种单目操作符
sizeof 计算变量所占内存内存空间大小的
如果操作数是类型的话,计算的是使用类型创建的变量所占内存空间的大小
sizeof 只关注占用内存空间的大小,不在乎内存中存放什么数据
举个例子
int main()
{
int a = 10;
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(int));
return 0;
}
这里打印的结果是多少呢?
我们发现它们的大小是一样的
我们再对数组进行测试
int main()
{
int arr1[4] = { 0 };
char arr2[4] = { 0 };
printf("%zd\n",sizeof(arr1));
printf("%zd\n", sizeof(arr2));
return 0;
}
运行一下
我们发现sizeof计算大小时只关注占用内存空间的大小,不在乎里面放的数据
那我们再来复习一下strlen
strlen 是C语言库函数,功能是求字符串长度(注意,它只能用来求字符串长度!)
统计的是从 strlen 函数的参数 str 中这个地址开始向后, \0 之前字符串中字符的个数
strlen 函数会⼀直向后找 \0 字符,直到找到为止,所以可能存在越界查找
举个例子
int main()
{
char arr1[3] = { 'a', 'b', 'c' };
char arr2[] = "abc";
printf("%d\n", strlen(arr1));
printf("%d\n", strlen(arr2));
return 0;
}
运行一下
我们发现,第一个数据是一个随机数,因为arr1中不包含 \0 ,而strlen找的是\0之前出现的字符个数,所以会返回一个随机值
我们再跟sizeof求字符串进行一个对比
int main()
{
char arr1[3] = { 'a', 'b', 'c' };
char arr2[] = "abc";
printf("%zd\n", strlen(arr1));
printf("%zd\n", strlen(arr2));
printf("%d\n", sizeof(arr1));
printf("%d\n", sizeof(arr2));
return 0;
}
运行一下
因为arr1占3个字节,所以用sizeof打印的是3,而arr2有abc\0四个数,所以打印4
下面我们对sizeof 和 strlen进行一个总结:
sizeof计算操作数所占内存的大小, 单位是字节 3. 不关注内存中存放什么数据
srtlen是求字符串长度的,统计的是 \0 之前字符的个数
关注内存中是否有 \0 ,如果没有 \0 ,就会持续往后找,可能会越界
二.数组和指针笔试题
1.一维整型数组
请看下面代码,判断结果
int main()
{
int a[] = { 1,2,3,4 };
printf("%zd\n", sizeof(a));
printf("%zd\n", sizeof(a + 0));
printf("%zd\n", sizeof(*a));
printf("%zd\n", sizeof(a + 1));
printf("%zd\n", sizeof(a[1]));
printf("%zd\n", sizeof(&a));
printf("%zd\n", sizeof(*&a));
printf("%zd\n", sizeof(&a + 1));
printf("%zd\n", sizeof(&a[0]));
printf("%zd\n", sizeof(&a[0] + 1));
}
在分析之前,我们先来回忆一下关于数组名的理解
数组名一般表示数组首元素的地址,但是有2个例外
1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小
2. &数组名,这里的数组名表示整个数组,取出的是数组的地址
除此之外,所有遇到数组名都是数组首元素的地址
在sizeof(a)中,a作为1个数组名,单独放在sizeof中,属于例外1,所以计算的是整个数组的大小,结果是16
在sizeof(a+0)中,数组名a是数组首元素的地址,a的类型是int*,a+0还是首元素的地址
所以sizeof(a+0)算的是数组首元素的地址的大小,
我们之前学过,在64位环境下,一个地址的大小是8个字节
在32位环境下,一个地址的大小是4个字节,我们用X64环境打印,结果就是8
在sizeof(*a)中,数组名a是数组首元素的地址,*a就是首元素,所以计算的结果大小是4
在sizeof(a+1)中,数组名a是数组首元素的地址,a+1就是第二个元素的地址,所以计算的结果在64位环境下还是8
在 sizeof(a[1]) 中,计算的结果就是数组第二个元素的大小,是4
在 sizeof(&a) 中,属于例外2,&a表示一个数组的地址,但是数组的地址也是地址,所以计算的结果在64位环境下还是8
在 sizeof(*&a) 中,这里的*与&抵消,所以结果与 sizeof(a) 一样,算的是整个数组的大小,结果是16
在 sizeof(&a + 1) 中 ,属于例外2,&a表示一个数组的地址,所以&a+1跳过整个数组,但它指向的仍然是个地址,所以计算的结果在64位环境下还是8
在 sizeof(&a[0]) 中,&a[0]表示的是数组第一个元素的地址,结果在64位环境下还是8
在 sizeof(&a[0]+1) 中,&a[0]+1 是第二个元素的地址,所以计算的结果在64位环境下还是8
我们对上面的结果进行一个汇总,结果一个是16,8,4,8,4,8,16,8,8,8
我们运行一下
2.字符数组
int main()
{
char arr[] = { 'a','b','c','d','e','f' }; //arr数组中6个元素,没有\0
printf("%zd\n", sizeof(arr));
printf("%zd\n", sizeof(arr + 0));
printf("%zd\n", sizeof(*arr));
printf("%zd\n", sizeof(arr[1]));
printf("%zd\n", sizeof(&arr));
printf("%zd\n", sizeof(&arr + 1));
printf("%zd\n", sizeof(&arr[0] + 1));
}
有了上面对整型数组的讲解,这里的题目看起来应该轻松很多了
我们在64位环境下打印一下,验证一下结果与我们设想的是否相同
(特别注意,在下面的题目中,没有特别标明的话就是在X64环境下演示的)
继续练习,希望大家对这种题越来越熟练
我们看一下strlen,大家的大脑要切换成strlen了,现在不是sizeof了哦
int main()
{
char arr[] = { 'a','b','c','d','e','f' };
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;
}
我们来分析一下,这里的
strlen(arr) 和 strlen(arr + 0) 结果都是随机值,
第三个 strlen(*arr) 就比较有意思了,*arr是 ’a‘,传给 strlen 的就是a的ASCII值97
strlen就把97当成地址了,但97这个地址没有被分配,所以程序直接奔溃
同理,第四个 strlen(arr[1]),arr[1]是’b‘,
传给 strlen 的就是b的ASCII值98
strlen就把98当成地址了,所以程序也直接奔溃
第五个 strlen(&arr])也是随机值
第六个 strlen(&arr + 1),&arr + 1 跳过了整个数组,那它也是随机值
第七个 strlen(&arr[0] + 1) ,这里从 'b' 开始 , 但结果也是随机值(因为数组中没有\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));
这是结果
下一道题
char arr[] = "abcdef";
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));
这是结果
再下一道题
int main()
{
char* p = "abcdef";
printf("%zd\n", sizeof(p));
printf("%zd\n", sizeof(p + 1));
printf("%zd\n", sizeof(*p));
printf("%zd\n", sizeof(p[0]));
printf("%zd\n", sizeof(&p));
printf("%zd\n", sizeof(&p + 1));
printf("%zd\n", sizeof(&p[0] + 1));
return 0;
}
这是结果
下一道题
char* p = "abcdef";
printf("%zd\n", strlen(p));
printf("%zd\n", strlen(p + 1));
printf("%zd\n", strlen(*p));
printf("%zd\n", strlen(p[0]));
printf("%zd\n", strlen(&p));
printf("%zd\n", strlen(&p + 1));
printf("%zd\n", strlen(&p[0] + 1));
return 0;
这是结果
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]));
我们重点看一下最后一个
printf("%zd\n", sizeof(a[3]));
这个会不会越界?
答案是不会
因为sizeof内部的表达式不会真实计算的
结果还是16
打印结果如下
好,做完这些题后,我们再回顾一下数组名的意义:
1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小
2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址
3. 除此之外所有的数组名都表示首元素的地址
三.. 指针运算笔试题
例子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;
}
下面是结果:
例子2.
//在X86环境下
//结构体的大小是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;
}
下面是结果:
例子3.
注意:a数组中是用()不是 { } 哟
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int* p;
p = a[0];
printf("%d", p[0]);
return 0;
}
这是结果:
例子4.
//假设环境是x86环境,程序输出的结果是多少?
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;
}
这是结果:
例子5.
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;
}
这是结果:
例子6.
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
这是结果:
结语:
好了,看到这的小伙伴,恭喜你初步学完了指针的所有内容!
下一篇博客我会为大家带来关于字符与字符串函数的干货
在学习C语言的道路上与各位同行
希望大家点个赞或者关注吧(感谢感谢)
让我们在接下来的时间里一起成长,一起进步吧!