【C语言】深入理解指针(五):sizeof、strlen与数组指针的那些事儿
前言
在C语言的学习中,指针始终是一个让人又爱又恨的话题。它强大而灵活,但同时也充满了陷阱。今天,我们就来深入探讨一下指针相关的几个重要知识点:sizeof和strlen的区别,以及数组和指针在笔试题中的那些常见问题。希望通过这篇文章,能帮你更好地理解指针的精髓。
sizeof 与 strlen:你真的了解它们的区别吗?
sizeof:内存空间的度量衡
sizeof是一个操作符,它的作用是计算变量或类型所占用的内存空间大小,单位是字节。它只关心内存空间的大小,而不关心内存中存储了什么数据。比如下面的代码:
#include <stdio.h>
int main()
{
int a = 10;
printf("%d\n", sizeof(a)); // 输出变量a所占内存大小
printf("%d\n", sizeof a); // sizeof后可省略括号
printf("%d\n", sizeof(int)); // 输出int类型变量所占内存大小
return 0;
}
无论变量a中存储的是什么值,sizeof(a)的结果都是固定的,取决于int类型在当前系统中所占用的字节数。
strlen:字符串长度的探针
strlen是C语言库函数,用于计算字符串的长度。它的函数原型是size_t strlen(const char *str);,它从参数str指向的地址开始,统计到第一个\0字符之前的字符个数。需要注意的是,strlen会一直向后查找\0字符,如果字符串没有以\0结尾,可能会导致越界查找。比如:
#include <stdio.h>
#include <string.h>
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(arr1)可能会导致越界查找,而arr2是一个以\0结尾的字符串,strlen(arr2)会正确输出字符串长度。
sizeof vs strlen:本质区别
sizeof:操作符,计算操作数所占内存大小,单位是字节,不关注内存中存储的数据。
strlen:库函数,计算字符串长度,统计\0之前的字符个数,关注内存中是否有\0,可能会越界。
数组与指针:笔试题中的常客
一维数组的sizeof与指针操作
数组名在大多数情况下表示首元素的地址,但在sizeof和&操作中,数组名表示整个数组。比如:
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])); // 计算数组第二个元素的大小
这里sizeof(a)计算的是整个数组的大小,而sizeof(a+0)计算的是指针的大小,因为a+0是一个指针表达式。
字符数组的sizeof与strlen
字符数组在sizeof和strlen操作中表现得尤为复杂。比如:
char arr[] = {'a', 'b', 'c', 'd', 'e', 'f'};
printf("%d\n", sizeof(arr)); // 计算整个字符数组的大小
printf("%d\n", strlen(arr)); // 计算字符串长度,可能会越界
这里sizeof(arr)计算的是整个字符数组的大小,包括所有字符占用的空间。而strlen(arr)会从数组首地址开始查找\0字符,计算字符串长度。但如果数组中没有\0字符,strlen可能会越界。
二维数组的sizeof操作
二维数组的sizeof操作同样需要注意数组名的意义。比如:
int a[3][4] = {0};
printf("%d\n", sizeof(a)); // 计算整个二维数组的大小
printf("%d\n", sizeof(a[0])); // 计算二维数组第一行的大小
printf("%d\n", sizeof(a[0][0])); // 计算二维数组第一个元素的大小
这里sizeof(a)计算的是整个二维数组的大小,sizeof(a[0])计算的是二维数组第一行的大小,sizeof(a[0][0])计算的是二维数组第一个元素的大小。
指针运算笔试题解析:挑战你的思维极限
题目1:指针偏移的陷阱
#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;
}
这个题目中,&a + 1表示数组a的地址加上一个int数组的大小,然后强制转换为int指针。*(ptr - 1)实际上访问的是数组a的最后一个元素。
题目2:结构体指针的偏移
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 + 0x1会按照结构体Test的大小进行偏移,而(unsigned long)p + 0x1和(unsigned int*)p + 0x1则会按照unsigned long和unsigned int的大小进行偏移。
题目3:数组指针的初始化
#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;
}
这个题目中,a[0]是一个数组,直接赋值给指针p,p指向的是数组a[0]的首地址。p[0]访问的是数组a[0]的第一个元素。
题目4:指针数组的偏移
#include <stdio.h>
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;
}
这个题目中,p是一个指向int[4]的指针,&p[4][2]和&a[4][2]的偏移量计算需要注意指针类型和数组维度。
题目5:二维数组指针的复杂偏移
#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;
}
这个题目中,&aa + 1表示整个二维数组aa的地址加上一个int[2][5]的大小,*(aa + 1)表示访问二维数组aa的第二行。
题目6:指针数组的递增
#include <stdio.h>
int main()
{
char *a[] = {"work", "at", "alibaba"};
char **pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
这个题目中,pa是一个指向指针的指针,pa++会按照指针数组的大小进行偏移,*pa访问的是指针数组中的下一个字符串。
题目7:多级指针的复杂操作
#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;
}
这个题目中,cpp是一个指向指针数组的指针,**++cpp、–++cpp + 3等操作需要仔细分析指针的偏移和解引用顺序。
总结
通过今天的学习,我们深入探讨了sizeof和strlen的区别,以及数组和指针在笔试题中的常见问题。希望这些内容能帮助你在学习C语言指针时少走弯路。指针的学习需要多练习、多思考,只有通过不断地实践,才能真正掌握它的精髓。如果你对这些内容还有疑问,欢迎在评论区留言,我们一起探讨!