首页 > 其他分享 >C语言之指针进阶(5),sizeof和strlen的数组计算以及指针运算笔试难题详解

C语言之指针进阶(5),sizeof和strlen的数组计算以及指针运算笔试难题详解

时间:2024-06-03 23:58:39浏览次数:21  
标签:arr 进阶 zd C语言 int printf sizeof strlen 指针


目录

前言

一、sizeof和strlen 的区分比较

二、sizeof,strlen与数组的计算

三、指针运算,笔试难题解析

总结


前言

        本文作为指针进阶的最后一篇文章,给大家带来了丰富的例题,这其中包括区分比较sizeof和strlen计算各种花样的数组指针表达式,如果你能答对所有的关于sizeof和strlen的计算例题,那么关于sizeof和strlen的计算你就无敌了。另外最主要的还是指针的运算笔试难题,这些笔试真题就能帮我们更深入的理解指针,最终成为C语言大佬,当然指针还未结束,最后还是需要自己理解和积累,希望本文对大家有所帮助


一、sizeof和strlen 的区分比较

sizeofstrlen
1.sizeof是操作符1.strlen是库函数,使用时需包含头文件<string.h>

2.sizeof计算操作数所占的内存大小,

单位是字节,返回类型为size_t

2.strlen是计算字符串长度的,统计的是\0之前字符

的个数,返回类型为size_t

3.sizeof不关注内存中存放的数据,sizeof中

如果是表达式也不会真正的被计算

3.关注内存中是否有\0,如果没有\0,就会

继续往后找,可能会导致越界访问

4.sizeof 传入的参数可以是变量名,

可以是类型名,也可以是整数、浮点数等

4.strlen 的形参是一个字符指针,也就是

需要计算的字符串首元素地址

strlen的形参

sizeof不关注数据内容体现在以下代码:

#include <stdio.h>

int main()
{
	int a = 10;
	printf("%zd\n", sizeof(a));//计算a大小
	printf("%zd\n", sizeof(int));//直接计算类型大小
	printf("%zd\n", sizeof(10));//甚至直接计算整数大小

	return 0;
}

运行结果:

strlen关注内存中是否有\0体现在以下代码:

#include <stdio.h>
#include <string.h>

int main()
{
	char ch1[] = "abcdef";//字符串赋值末尾自带一个\0
	char ch2[] = { 'a','b','c','d','e','f' };//末尾没有\0
	char ch3[] = { 'a','b','c','d','e','f' ,'\0' };//末尾手动添加\0

	printf("%zd\n", strlen(ch1));
	printf("%zd\n", strlen(ch2));
	printf("%zd\n", strlen(ch3));

	return 0;
}

运行结果:

出现38的结果就是因为 ch2 数组中没有\0,strlen只能在数组后面的内存中去寻找\0,也就是越界访问了,最终会返回一个随机值


二、sizeof,strlen与数组的计算

以下就是使用 sizeof 和 strlen 计算数组的题目,你能答对几道?可不要小看这些计算,一不小心就会犯错误,重要的还是理解。

注意:以下涉及的知识与我主页指针进阶(1)数组与指针有关,即数组名为数组首元素地址,但有两个例外:

1. sizeof(数组名),数组名单独放在sizeof中,此时数组名表示整个数组,计算的是整个数组大小

2. &数组名,取出的是整个数组的地址,也就是一个数组指针

如不了解,可前去预览,以便更好的理解以下代码

注:以下代码中行末尾注释的数字为每一行的答案,4/8表示在x86或x64位平台下不同的结果

例1:小试牛刀

#include <stdio.h>

int main()
{
	int a[] = { 1,2,3,4 };

	printf("%zd\n", sizeof(a));//16,a单独放在sizeof中表示计算整个数组大小
	printf("%zd\n", sizeof(a + 0));//4/8,非单独表示指针
	printf("%zd\n", sizeof(*a));//4,解引用首元素地址
	printf("%zd\n", sizeof(a + 1));//4/8 等价于&a[1]
	printf("%zd\n", sizeof(a[1]));//4 
	printf("%zd\n", sizeof(&a));//4/8,&a表示取出的是整个数组的地址,是一个数组指针
	printf("%zd\n", sizeof(*&a));//16,*与&抵消,相当于a单独放在sizeof中
	printf("%zd\n", sizeof(&a + 1));//4/8,数组指针加1,表示跳过整个数组的后一个数组指针 
	printf("%zd\n", sizeof(&a[0]));//4/8,取出第一个元素地址
	printf("%zd\n", sizeof(&a[0] + 1));//4/8,等价于&a[1]

	return 0;
}

例2:

#include <stdio.h>

int main()
{
	char arr[] = { 'a','b','c','d','e','f' };

	printf("%zd\n", sizeof(arr));//6,数组名单独放在sizeof里,表示计算整个数组大小
	printf("%zd\n", sizeof(arr + 0));//4/8,非单独放,表示首元素地址
	printf("%zd\n", sizeof(*arr));//1,解引用首元素地址,指向字符a
	printf("%zd\n", sizeof(arr[1]));//1,指向字符b
	printf("%zd\n", sizeof(&arr));//4/8,取出的是一个数组指针
	printf("%zd\n", sizeof(&arr + 1));//4/8,还是一个数组指针
	printf("%zd\n", sizeof(&arr[0] + 1));//4/8,相当于&arr[1]

	return 0;
}

例3:

#include <stdio.h>
#include <string.h>

int main()
{
	char arr[] = { 'a','b','c','d','e','f' };

	printf("%zd\n", strlen(arr));//随机值
	printf("%zd\n", strlen(arr + 0));//随机值,等于第一行
	printf("%zd\n", strlen(*arr));//访问地址为97处的内存,程序崩溃
	printf("%zd\n", strlen(arr[1]));//访问地址为98处的内存,程序崩溃
	printf("%zd\n", strlen(&arr));//随机值,等于第一行
	printf("%zd\n", strlen(&arr + 1));//随机值,等于第一行减6
	printf("%zd\n", strlen(&arr[0] + 1));//随机值,等于第一行减1

	return 0;
}

例4:

#include <stdio.h>

int main()
{
	char arr[] = "abcdef";//以字符串字面量进行赋值,末尾有隐藏了的\0

	printf("%zd\n", sizeof(arr));//7,计算包括了\0在内的7个字符
	printf("%zd\n", sizeof(arr + 0));//4/8,非数组名单独放在sizeof里,等价于&arr[0]
	printf("%zd\n", sizeof(*arr));//1,解引用首元素的地址,指向字符a
	printf("%zd\n", sizeof(arr[1]));//1,指向b
	printf("%zd\n", sizeof(&arr));//4/8,取出的是数组指针
	printf("%zd\n", sizeof(&arr + 1));//4/8,还是一个数组指针
	printf("%zd\n", sizeof(&arr[0] + 1));//4/8,等价于&arr[1]

	return 0;
}

例5:

#include <stdio.h>
#include <string.h>

int main()
{
	char arr[] = "abcdef";//末尾有\0

	printf("%zd\n", strlen(arr));//6
	printf("%zd\n", strlen(arr + 0));//6
	printf("%zd\n", strlen(*arr));//访问地址为97处的内存空间,程序崩溃
	printf("%zd\n", strlen(arr[1]));//访问地址为98处的内存空间,程序崩溃
	printf("%zd\n", strlen(&arr));//6
	printf("%zd\n", strlen(&arr + 1));//跳过该数组,越界访问,返回随机值
	printf("%zd\n", strlen(&arr[0] + 1));//5

	return 0;
}

例6:

#include <stdio.h>

int main()
{
	char* p = "abcdef";//p接收的是字符串的首元素地址
	
	printf("%zd\n", sizeof(p));//4/8  注意指针变量就是指针,数组名是数组名,这两者这不一样
	printf("%zd\n", sizeof(p + 1));//4/8,等价于&p[1]
	printf("%zd\n", sizeof(*p));//1,指向字符a
	printf("%zd\n", sizeof(p[0]));//1,字符a
	printf("%zd\n", sizeof(&p));//4/8,指针变量的地址,相当于一个二级指针
	printf("%zd\n", sizeof(&p + 1));//4/8,还是一个二级指针
	printf("%zd\n", sizeof(&p[0] + 1));//4/8,字符串中b的地址

	return 0;
}

例7:

#include <stdio.h>
#include <string.h>

int main()
{
	char* p = "abcdef";

	printf("%zd\n", strlen(p));//6
	printf("%zd\n", strlen(p + 1));//5
	printf("%zd\n", strlen(*p));//访问地址为97处的内存空间,程序崩溃
	printf("%zd\n", strlen(p[0]));//访问地址为97处的内存空间,程序崩溃
	printf("%zd\n", strlen(&p));//传入的是p变量本身的地址,返回随机值
	printf("%zd\n", strlen(&p + 1));//传入的是跳过b变量地址的地址,返回随机值
	printf("%zd\n", strlen(&p[0] + 1));//5

	return 0;
}

例8:二维数组

#include <stdio.h>

int main()
{
	int a[3][4] = { 0 };

	printf("%zd\n", sizeof(a));//48,数组名单独放在sizeof中,计算的是整个数组大小
	printf("%zd\n", sizeof(a[0][0]));//4,表示第一行第一个元素
	printf("%zd\n", sizeof(a[0]));//16,a[0]表示第一行数组的数组名,单独放在sizeof中
	printf("%zd\n", sizeof(a[0] + 1));//4/8,等价于&a[0][1]
	printf("%zd\n", sizeof(*(a[0] + 1)));//4,等价于a[0][1]
	printf("%zd\n", sizeof(a + 1));//4/8,a表示数组首元素地址也就是&a[0],a+1就是&a[1]
	printf("%zd\n", sizeof(*(a + 1)));//16,继上一行,a+1再解引用相当于a[1]数组名单独放sizeof中
	printf("%zd\n", sizeof(&a[0] + 1));//4/8,等价于&a[1]
	printf("%zd\n", sizeof(*(&a[0] + 1)));//16,继上一行,&与*抵消,相当于a[1]单独放在sizeof中
	printf("%zd\n", sizeof(*a));//16,等价于*&a[0],相当于a[0]数组名单独放在sizeof中
	printf("%zd\n", sizeof(a[3]));//16,这里一定记住:sizeof中的表达式不会真正计算,这里不会越界        
    访问,因此依旧表示数组名a[3]单独放在sizeof中,计算的是a[3]整个数组大小

	return 0;
}

注意:sizeof()中的表达式不会真正的被计算

例如:


三、指针运算,笔试难题解析

题目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;
}

运行结果:

画图解析:

  1. 因为&a得到一个数组指针,+1就跳过一个数组,指向了a[5]的末尾,因此(&a+1)应指向如图所示的位置,因为此时(&a+1)还是一个数组指针,因此强制转换为 int* ,再传给ptr
  2. int* 类型指针-1往低地址处移动4个字节,所以ptr-1指向的就是5,而 *(a+1) 就等价于 a[1],指向的是数组第二个元素2


题目2:

//在X86环境下
//假设结构体的⼤⼩是20个字节
//程序输出的结果是啥?
#include <stdio.h>

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;
}

运行结果:

解析:

  1. 首先定义了一个结构体指针p,p储存的地址就是被强转为结构体指针类型的0x100000
  2. printf("%p\n", p + 0x1),0x1表示16进制数字1,所以p+0x1就是p+1,p为一个结构体类型的指针变量,+1就是跳过一个结构体大小的字节,而结构体大小就是20个字节,20转换为16进制就是0x14,所以p+1 = 0x100000+0x14 = 0x100014,%p打印,会打印完整地址,因此不足位前面补0,最终结果就是 00100014
  3. printf("%p\n", (unsigned long)p + 0x1), (unsigned long)p将结构体指针类型的p强制转换为无符号整形p,其中储存的地址就会变为无符号整数,因此最终结果就是两个整数相加,也就是 0x100000+0x1 = 0x100001 ,最后以地址的格式打印出来就是 00100001
  4. printf("%p\n", (unsigned int*)p + 0x1),这里就是将p强转为无符号整形指针类型,+1跳过一个无符号整形大小的字节,也就是4个字节,因此最终结果就是 00100004


题目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;
}

运行结果:

解析:

  1. 首先我们需要关注{ (0, 1), (2, 3), (4, 5) },这里面是3个逗号表达式,逗号表达式结果取决于其最后一位,所以大括号里面实际只有 1,3,5,这三个数
  2. a[0]就为第一行元素的首地址,所以p[0] 等价于 *(p+0),也就是指向第一行第一个元素1


题目4:

//假设环境是x86环境,程序输出的结果是啥?
#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;
}

运行结果:

解析:

  1. 如图所示,a本为 int (*)[5] 类型,却强制赋值给p int (*)[4] 类型,导致两者出现上图分配情况,通过画图我们不难找到 p[4][2] 和 a[4][2] 的位置
  2. 我们知道数组中两指针相减,那么得到的就是两指针之间的元素个数,因为p[4][2]地址小于a[4][2],所以得到的是 -4,-4以%d的格式打印就是-4,但是-4以%p打印就不一样了
  3. -4以%p打印,打印的是-4在内存中的补码,以地址的格式打印。-4的补码就为1111 1111 1111 1111 1111 1111 1111 1100,每四个二进制位以地址的16进制打印就是 FFFFFFFC


题目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;
}

运行结果:

解析:

  1. (int*)(&aa + 1),&aa+1取出整个数组的地址,可以理解为二维数组的数组指针,+1就跳过整个数组,来到数组的末尾,然后强转为(int*)类型,赋给ptr1
  2. (int*)(*(aa + 1)),可以直接理解为(int*)aa[1],*(aa+1)就表示跳过一个元素解引用,也就是aa[1],指向的就是第二个数组元素的首地址,赋给ptr2
  3. 因此,ptr1,ptr2都被强转为int*类型,这样-1就往地址跳过一个整形大小的字节,也就是分别指向10和5


题目6:

//程序运行的结果是啥?
#include <stdio.h>

int main()
{
	char* a[] = { "work","at","alibaba" };
	char** pa = a;
	pa++;
	printf("%s\n", *pa);

	return 0;
}

运行结果:

解析:

  1. a是一个字符指针数组,它里面3个元素分别对应后面三个字符串字面量的首字符地址
  2. 因为a是数组首元素地址,它指向的是一个字符指针,因此接收a需要一个二级指针变量,也就是pa,给pa赋值a,pa开始指向的是a[0]的地址,pa++后,pa向后移动一个地址,指向了a[1]的地址,因此*pa就等于a[1],以%s打印字符串需要字符串首元素地址,a[1]储存的是at\0的首元素地址,因此最终打印at


题目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是三级指针,接收二级指针变量的地址)

  1. 首先第一处打印,**++cpp,*与前置++优先级相同,结合性从右到左,因此cpp先自增1,cpp就指向cp[1]的地址,然后*++cpp,解引用得到的就是cp[1]的内容c+2,最后* *++cpp,再解引用,就是解引用c+2,指向的就是c[2]的内容,也就是POINT\0的首字符地址,最后以%s打印就是POINT。由于cpp指向发生变化,上图的需进行修改
  2. 第二处打印,*-- * ++cpp + 3,我们来一步一步分析,以优先级和结合性,首先是++cpp,那么cpp自增1就指向了cp[2]的地址了,然后*++cpp,解引用cp[2]的地址,得到的就是cp[2]指向的内容c+1,然后--*++cpp,c+1自减1,就是把cp[2]的内容从c+1修改为c,然后*--*++cpp,再解引用,这时解引用的是c,指向的就是c[0]的内容,也就是ENTER\0的首字符地址,最后*--*++cpp+3,加3表示跳过3个字节(因为指针为char类型),此时指向的就是字符E的地址,因此最终打印的结果就是ER。由于以上变化,我们再重新绘图
  3. 第三处打印,*cpp[-2] + 3 ==>(等价于) **(cpp-2)+3,首先cpp-2,改变指向的内容为cp[0]的地址,然后*(cpp-2),解引用得到cp[0]的内容c+3,然后**(cpp-2),再解引用得到的就是c+3也就是c[3]所指向的内容,也就是FIRST\0的首字符地址,最后**(cpp-2)+3,加3跳过3个字节,指向字符S的地址,最终打印的就是ST。由于上述操作并未实质改变指针指向的内容,只是表达式的计算,所以不需重新绘图
  4. 第四处打印,cpp[-1][-1] + 1 ==> *(*(cpp-1))-1)+1,首先cpp-1,指向的是cp[1]的地址,然后*(cpp-1),解引用得到cp[1]指向的内容c+2,然后*(cpp-1)-1,减一表示把c+2减1,导致改变cp[1]中的内容从c+2变为c+1,然后*(*(cpp-1)-1),再解引用c+1,也就是解引用c[1]的内容,c[1]的内容指向的是字符串字面量NEW\0的首字符地址,最后*(*(cpp-1))-1)+1,加1表示跳过一个字节,此时指向的就是字符E的地址,最终打印的结果就是EW。


总结

        至此,我就解析完了本文的所有题目,希望对大家有所帮助,也很感谢大家的支持,大家有什么疑问欢迎评论区指出

标签:arr,进阶,zd,C语言,int,printf,sizeof,strlen,指针
From: https://blog.csdn.net/x_p96484685433/article/details/139398196

相关文章

  • 数据在内存中的存储<C语言>
    导言       在计算机中不同类型的数据在计算机内部存储形式各不相同,弄懂各种数据在计算机内部存储形式是有必要的,C语言的学习不能浮于表面,更要锻炼我们的“内功”,将来在写程序的时候遇见各种稀奇古怪的bug时,也便能迎刃而解,所以本文将着重介绍,整数在内存中的存储、大小......
  • java多态——面向对象进阶
    学习多态之前要先了解继承定义:    对象的多种形态。(就是爸爸管儿子)例子:Fatherf=newSon(); 这里的Father是父类,Son是继承父类Father的子类应用场景/好处:    使用父类型作为参数,可以接受所有子类对象,体现多态的拓展性与遍历(儿子太多,不好管,没事,可以找......
  • 杨辉三角C语言的超简单解决办法
    #include<stdio.h>#include<stdlib.h>intmain(){intarr[10][10]={0};//十行的杨辉三角intsize=sizeof(arr)/sizeof(arr[0]);//求一共有几行for(inti=0;i<size;i++){for(intj=0;j<=i;j++)//对角线{if(i==j||j=......
  • 浙大翁恺《C语言程序设计》课程笔记
    1.1计算机与编程语言设计算法->编写程序->计算机执行程序执行的两种方式1.解释:借助一个程序(解释器),那个程序能试图理解你的程序,然后按照你的要求让计算机执行2.编译:借助一个程序(编译器),把你的程序翻译成机器语言,然后让计算机执行编程语言本身没有解释型和编译型之......
  • [22] 虚幻引擎知识拓展 智能指针、JSON解析、插件
    Day1大纲虚幻智能指针  共享指针  共享引用JSON解析对象型、数组型、解析Json文件、书写Json、读取场景Actor保存到Json任务:封装高德地图天气系统插件给蓝图使用内容虚幻智能指针创建共享指针////创建共享指针//TSharedPtr<FMyClass>pMyClass=MakeShareab......
  • 什么是二级指针,为什么要有二级指针.
    什么是二级指针?一个二级指针是一个指向指针的指针。简单来说,如果int*p是一个指向整数的指针,那么int**pp就是一个指向p的指针。inta=10;int*p=&a;//p是一个指向整数a的指针int**pp=&p;//pp是一个指向p的指针二级指针的作用众所周知,要......
  • C语言程序设计第二讲:顺序程序设计
    一、数据类型1.基本数据类型C语言中提供了一些基本数据类型,用于表示各种不同类型的数据:整数类型:int:表示整数,通常占用4个字节。shortint:表示短整数,通常占用2个字节。longint:表示长整数,通常占用4或8个字节。longlongint:表示更长的整数,通常占用8个字节。unsignedi......
  • c语言中,结构体变量交换改写为堆空间申请内存
            在这里我实现的功能为:输入三个人的信息,每个人的信息分别为姓名和三个成绩,我分别计算三个人的成绩和,并通过经典的三杯水案例完成对三个人的成绩从小到大的排列打印。重点:     我这里使用的为堆空间申请内存的形式 第一步:        定义一个......
  • 初识C语言(02)—学习笔记
    转义字符转义字符释义\0结束标志\n换行\'打印单引号\"打印双引号\\打印一个反斜杠\t水平制表符\a警告字符,蜂鸣?在书写连续多个问号时使用,防止它们被解析成三字符\dddddd表示1~3个八进制的数字\xdddd表示2个十六进制数字\v垂直......
  • JavaEE初阶--锁进阶理解
    目录一、引言二、锁的分类1.乐观锁vs悲观锁2.重量级锁vs轻量级锁3.自旋锁vs挂起等待锁4.公平锁vs非公平锁5.可重入锁vs不可重入锁6.读写锁三、CAS1.什么是CAS?2.CAS伪代码3.CAS的实现4.CAS的应用5.CAS的ABA问题四、总结一、引言 前面的博客我们......