您现在的位置是:首页 >技术杂谈 >【C语言系列】深入理解指针(5)网站首页技术杂谈
【C语言系列】深入理解指针(5)
深入理解指针(5)
一、sizeof和strlen的对比
1.1sizeof
sizeof是单目操作符(绝不是函数!!!),sizeof计算变量所占内存空间的大小的,单位是字节。
如果操作数是类型的话,计算的是使用类型创建的变量所占内存空间的大小。
注:sizeof只关注占用内存空间的大小,不在乎内存中存放什么数据。
比如:
#inculde <stdio.h>
int main()
{
int a = 10;
printf("%d
", sizeof(a));
printf("%d
", sizeof a);
printf("%d
", sizeof(int));
return 0;
}
1.2strlen
strlen是C语言库函数,功能是求字符串长度。
函数原型是:
size_t strlen ( const char * str );
统计的是从strlen函数的参数str中这个地址开始向后, 之前字符串中字符的个数。
注:strlen 函数会一直向后找 字符,直到找到为止,所以可能存在越界查找。
代码如下:
#include <stdio.h>
int main()
{
char arr1[3] = {'a', 'b', 'c'};
char arr2[] = "abc";
printf("%d
", strlen(arr1));
printf("%d
", strlen(arr2));
printf("%d
", sizeof(arr1));
printf("%d
", sizeof(arr2));
return 0;
}
运行结果如下:
1.3sizeof和strlen的对比
sizeof | strlen |
---|---|
1.sizeof是操作符 | 1.strlen是库函数,使用需要包含头文件string.h |
2.sizeof计算操作数所占内存的大小,单位是字节 | 2.strlen是求字符串长度的,统计的是 之前字符的个数 |
3.不关注内存中存放什么数据 | 3.关注内存中是否有 ,如果没有 ,就会持续往后找,可能会越界 |
4.sizeof括号中有表达式的话,表达式是不参与计算的!!! |
用代码检验4,代码如下:
#include <stdio.h>
int main()
{
int a = 8;
short s = 4;
printf("%d
",sizeof(s = a + 2));//2
printf("%d
",s);//4
return 0;
}
运行结果如下:
那么为什么sizeof中的表达式不计算?
C语言是编译型语言,在编译期这个表达式并不会被执行,sizeof 运算的结果是在编译期间就已知的常数值,并不需要等到运行时才求解。因此,对于其中涉及到的操作数或者操作本身都不需要实际执行。
二、数组和指针笔试题解析
2.1 一维数组
*a == a[0] == *(a + 0)
数组名的理解:数组名是数组首元素(第一个元素)的地址。
但是有2个是例外:1.sizeof(数组名) —— 数组名表示整个数组,计算的是整个数组的大小,单位是字节。
2.&数组名 —— 数组名表示的是整个数组,取出的是整个数组的地址。
除此之外,所有的数组名是数组首元素(第一个元素)的地址。
笔试题代码和解析如下:
#include <stdio.h>
int main()
{
int a[] = {1,2,3,4};//数组有几个元素?//4
printf("%zd
",sizeof(a));//16
printf("%zd
",sizeof(a + 0));//a是首元素的地址 —— 类型是int*,a + 0还是首元素的地址,是地址大小就是4/8。
printf("%zd
",sizeof(*a));//a是首元素的地址,*a是首元素,大小就是4个字节。
printf("%zd
",sizeof(a + 1));//a是首元素地址,类型是int*,a + 1跳过1个整型,a + 1就是第二个元素的地址,是地址大小就是4/8。
printf("%zd
",sizeof(a[1]));//a[1]就是第二个元素,大小是4个字节。
printf("%zd
",sizeof(&a));//&a是数组的地址,数组的地址也是地址,是地址大小就是4/8字节。
printf("%zd
",sizeof(*&a));//1.*&互相抵消了,等价于sizeof(a),16
//2.&a是数组的地址,类型是int(*)[4],对数组指针解引用访问的是数组,计算的是数组的大小,16
//char* —— 解引用访问的是char
//int* —— 解引用访问的是int
printf("%zd
",sizeof(&a + 1));//&a + 1是跳过这个数组后的那个位置的地址,是地址大小就是4/8字节。
printf("%zd
",sizeof(&a[0]));//首元素的地址,大小就是4/8字节。
printf("%zd
",sizeof(&a[0] + 1));//&a[0] + 1 —— 数组第二个元素的地址,大小就是4/8字节。
return 0;
}
运行结果如下图:
2.2 字符数组
2.2.1代码1:
笔试题代码和解析如下:
#include <stdio.h>
int main()
{
char arr[] = {'a','b','c','d','e','f'};
printf("%d
",sizeof(arr));//数组名单独放在sizeof内部,计算的是数组的大小,单位为字节,6。
printf("%d
",sizeof(arr + 0));//arr是数组名表示首元素的地址,arr + 0还是首元素的地址,是地址就是4/8字节。
printf("%d
",sizeof(*arr));//arr是首元素的地址,*arr就是首元素,大小就是1个字节。
//*arr == arr[0] == *(arr + 0)
printf("%d
",sizeof(arr[1]));//arr[1]是第二个元素,大小也是1个字节。
printf("%d
",sizeof(&arr));//&arr是数组的地址,数组的地址也是地址,是地址大小就是4/8个字节。
//&arr —— char(*)[6]
printf("%d
",sizeof(&arr + 1));//4/8个字节,&arr + 1,跳过整个数组,指向了数组后边的空间。
printf("%d
",sizeof(&arr[0] + 1));//第二个元素的地址,是地址就是4/8个字节。
return 0;
}
运行结果如下图:
2.2.2代码2:
笔试题代码和解析如下:
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = {'a','b','c','d','e','f'};
printf("%d
",strlen(arr));//arr是首元素的地址,数组中没有 ,就会导致越界访问,结果就是随机的。
printf("%d
",strlen(arr + 0));//arr + 0是数组首元素的地址,数组中没有 ,就会导致越界访问,结果就是随机的。
printf("%d
",strlen(*arr));//arr是首元素的地址,*arr是首元素,就是'a','a'的ASCII码值是97,就相当于97作为地址传递给了strlen,strlen得到的就是野指针,代码是有问题的。
printf("%d
",strlen(arr[1]));//arr[1] —— 'b' —— 98,传给strlen函数也是错误的。
printf("%d
",strlen(&arr));//&arr是数组的地址,起始位置是数组的第一个元素的位置,随机值。
printf("%d
",strlen(&arr + 1));//随机值。
printf("%d
",strlen(&arr[0] + 1));//从第二个元素开始向后统计的,得到的也是随机值。
return 0;
}
运行结果如下图:
2.2.3代码3:
笔试题代码和解析如下:
#include <stdio.h>
int main()
{
char arr[] = "abcdef";
printf("%d
",sizeof(arr));//7,计算的是数组总大小,数组名单独放在sizeof内部。
printf("%d
",sizeof(arr + 0));//arr表示数组首元素的地址,arr + 0还是首元素的地址,4/8字节。
printf("%d
",sizeof(*arr));//arr表示数组首元素的地址,*arr是首元素,大小是1字节。
printf("%d
",sizeof(arr[1]));//arr[1]是第二个元素,大小是1个字节。
printf("%d
",sizeof(&arr));//&arr是数组的地址,是地址就是4/8字节。
printf("%d
",sizeof(&arr + 1));//&arr是数组的地址,是地址就是4/8字节。
printf("%d
",sizeof(&arr[0] + 1));//&arr是数组的地址,+1跳过整个数组,还是地址,是地址就是4/8字节。
return 0;
}
运行结果如下图:
2.2.4代码4:
笔试题代码和解析如下:
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = "abcdef";
printf("%d
",strlen(arr));//6
printf("%d
",strlen(arr + 0));//arr首元素的地址,arr + 0还是首元素的地址,向后统计在 之前的字符个数。//6
printf("%d
",strlen(*arr));//'a' —— 97,出错。
printf("%d
",strlen(arr[1]));//'b' —— 98.出错。
printf("%d
",strlen(&arr));//&arr是数组的地址,也是从数组第一个元素开始向后找,6。
printf("%d
",strlen(&arr + 1));//随机值。
printf("%d
",strlen(&arr[0] + 1));//5
//&arr —— char(*)[7]
//size_t strlen(const char*s);
return 0;
}
运行结果如下图:
2.2.5代码5:
笔试题代码和解析如下:
#include <stdio.h>
int main()
{
const char*p = "abcdef";
printf("%d
",sizeof(p));//p是指针变量,我们计算的是指针变量的大小,4/8个字节。
printf("%d
",sizeof(p + 1));//p + 1是b的地址,是地址大小就是4/8个字节。
printf("%d
",sizeof(*p));//p的类型是char*,*p就是char类型了,1个字节。
printf("%d
",sizeof(p[0]));//1.p[0]->*(p + 0)->*p->'a',大小1个字节。
//1.把常量字符串想象成数组。
//2.p可以理解为数组名,p[0],就是首元素。
printf("%d
",sizeof(&p));//取出的是p的地址,地址的大小就是4/8个字节。
printf("%d
",sizeof(&p + 1));//&p + 1是跳过p指针变量后的地址,地址的大小是4/8个字节。
printf("%d
",sizeof(&p[0] + 1));//4/8,取出首元素的地址,+1是第二个字符的地址。
return 0;
}
运行结果如下图:
2.2.6代码6:
笔试题代码和解析如下:
#include <stdio.h>
#include <string.h>
int main()
{
char*p ="abcdef";
printf("%d
",strlen(p));//6
printf("%d
",strlen(p + 1));//5
printf("%d
",strlen(*p));//*p就是'a' —— 97,err
printf("%d
",strlen(p[0]));//p[0]->*(p + 0)->*p//err
printf("%d
",strlen(&p));//&p是指针变量p的地址,和字符串“abcdef”关系就不大了,从p这个指针变量的起始位置开始向后数的,p变量存放的地址是什么,不知道,所以答案是随机值。
printf("%d
",strlen(&p + 1));//随机值
printf("%d
",strlen(&p[0] + 1));//5
return 0;
}
运行结果如下图:
2.3 二维数组
二维数组笔试题代码和解析如下:
#include <stdio.h>
int main()
{
int a[3][4] = {0};
printf("%d
",sizeof(a));//a是数组名,单独放在sizeof内部,计算的是数组的大小,单位是字节 —— 48 = 3*4*sizeof(int)。
printf("%d
",sizeof(a[0][0]));//a[0][0]第一行第一个元素,大小是4个字节。
printf("%d
",sizeof(a[0]));//a[0]是第一行的数组名,数组名单独放在sizeof内部了,计算的是数组的总大小16个字节。
printf("%d
",sizeof(a[0] + 1));//a[0]并没有单独放在sizeof内部,所以这里的数组名a[0]就是数组首元素的地址,即a[0]->&a[0][0],+1后是&a[0][1]的地址,大小是4/8个字节。
printf("%d
",sizeof(*(a[0] + 1)));//第一行第一个元素,大小是4
printf("%d
",sizeof(a + 1));//a作为数组名并没有单独放在sizeof内部,a表示数组首元素的地址,是二维数组首元素的地址,也就是第一行的地址,a + 1,跳过一行,指向了第二行,a + 1是第二行的地址,a + 1是数组指针,是地址大小就是4/8个字节。
printf("%d
",sizeof(*(a + 1)));//1.a + 1是第二行的地址,*(a + 1)就是第二行,计算的是第二行的大小 —— 16。2.*(a + 1) == a[1],a[1]是第二行的数组名,sizeof(*(a + 1))就相当于sizeof(a[1]),意思就是把第二行的数组名单独放在sizeof内部,计算的是第二行的大小。
printf("%d
",sizeof(&a[0] + 1));//a[0]是第一行的数组名,&a[0]取出的就是数组的地址,就是第一行的地址,&a[0] + 1就是第二行的地址,是地址大小就是4/8字节。
printf("%d
",sizeof(*(&a[0] + 1)));//对第二行地址解引用,访问的就是第二行,大小是16个字节。
printf("%d
",sizeof(*a));//a作为数组名并没有单独放在sizeof内部,a表示数组首元素的地址,是二维数组首元素的地址,也就是第一行的地址,*a就是第一行,计算的就是第一行的大小,16个字节。
printf("%d
",sizeof(a[3]));//a[3]无需真实存在,仅仅通过类型的推断就能算出长度,a[3]是第四行的数组名,单独放在sizeof内部,计算第四行的大小,16个字节。
//sizeof(int);//4
//sizeof(3+5);//4
return 0;
}
运行结果如下图:
三、指针运算笔试题解析
3.1题目1:
指针运算笔试题代码和解析如下:
#include <stdio.h>
int main()
{
int a[5] = {1,2,3,4,5};
int*ptr = (int*)(&a + 1);//&a —— int(*)[5]
printf("%d %d",*(a + 1),*(ptr - 1));//ptr跳过了原来a数组指向下一个位置,*(ptr-1)访问的就是数组a中的5。
return 0;
}
运行结果如下图:
3.2题目2:
在X86(32位)环境下,假设结构体的大小是20个字节,程序输出的结果是什么?
指针运算笔试题代码和解析如下:
#include <stdio.h>
struct Test
{
int Num;
char*PcName;
short sDate;
char cha[2];
short sBa[4];
}*p = (struct Test*)0x100000;//结构体指针+1,跳过一个结构体;整型值+1,就是+1。
//指针+-整数
int main()
{
printf("%p
",p + 0x1);//0x100000 + 20 -> 00100014
printf("%p
",(unsigned long)p + 0x1);//0x100000 + 1 ->0x100001 -> 00100001
printf("%p
",(unsigned int*)p + 0x1);//0x100000 + 4 -> 0x100004 -> 00100004
return 0;
}
运行结果如下图:
3.3题目3:
指针运算笔试题代码和解析如下:
#include <stdio.h>
int main()
{
int a[3][2] = {(0,1),(2,3),(4,5)};//1 3 5//初始化
int*p;
p = a[0];//&a[0][0];
printf("%d",p[0]);//1//*(p + 0) -> *p
return 0;
}
运行结果如下图:
3.4题目4:
假设环境是X86环境,程序的输出结果是什么?
指针运算笔试题代码和解析如下:
#include <stdio.h>
int main()
{//%d —— 是打印有符号的整数
//%p —— 是打印地址的
int a[5][5];//a —— 类型是:int(*)[5]
int(*p)[4];//p —— 类型是:int(*)[4]//p是一个数组指针,p指向的数组是4个整型元素的
p = a;//类型的差异 —— 警告
printf("%p,%d
",&p[4][2] - &a[4][2],&p[4][2] - &a[4][2]);//FFFFFFFC,-4
return 0;
}
//指针-指针绝对值得到的是指针和指针之间的元素个数
运行结果如下图:
3.5题目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));//10 5
return 0;
}
//*(aa + 1)->aa[1],aa[1]是第二行的数组名,数组名表示首元素的地址。
//aa[1]也是&aa[1][0]
//*(aa + 1)->aa[1],&aa[1]->第二行的地址
//sizeof(aa[1])->计算的是第二行的大小
运行结果如下图:
3.6题目6:
指针运算笔试题代码和解析如下:
#include <stdio.h>
int main()
{
char*a[] = {"work","at","alibaba"};//a是指针数组
char**pa = a;
pa++;
printf("%s
",*pa);//at//%s是打印字符串,给一个地址,从这个地址向后打印字符串,直到
return 0;
}
画图分析:
运行结果如下图:
3.7题目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
",**++cpp);//POINT
printf("%s
",*--*++cpp + 3);//ER
printf("%s
",*cpp[-2] + 3);//**(cpp - 2) + 3//ST
printf("%s
",cpp[-1][-1] + 1);//*c*(cpp-1) - 1//EW
return 0;
}
运行结果如下图:
四、总结
本文深入探讨了C语言中关于sizeof
、strlen
、数组和指针的一些基础概念,并通过代码示例进行了详细的讲解。以下是对主要内容的总结:
1. sizeof
与strlen
的区别
sizeof
是一个运算符,用于计算变量或类型所占的内存空间大小,单位是字节。它与数据存储内容无关,只关注内存的占用。例如,sizeof(int)
会返回一个整数类型的大小,而sizeof(a)
会返回数组a
的总字节数。需要注意的是,sizeof
中的表达式不会被计算,仅仅是编译时确定的常量。
与此不同,strlen
是C标准库中的一个函数,用于计算以'