c语言中,数组与sizeof()和strlen的问题

放在开头的话

博主最近在攻克c语言指针和数据存储,感到很难很抽象。但在学习了比特鹏哥视频后有了一点小小的收获,想跟各位做一个分享,也希望正在学习c的小伙伴们不要放弃。

数组

数组的定义

数组是数组是一组相同类型元素的集合,在元素的存储地址上也是连续的。

	#include<stdio.h>
    int main()
{
    int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%p\n", &arr[i]);
	}
return 0;
}

代码1

其中,%p是把地址按16进制输出,arr数组里面的元素都是int型,int型占4个字节,也就是4*8=32个字。

图1 数组内部元素是连续存储的

数组地址与数组元素地址与数组首元素地址

数组地址-------代表的是整个数组的地址。

数组元素地址-------代表的是数组各个元素的地址(如图1)

数组首元素地址------代表的是数组首个元素的地址(比如arr[0]的地址)直接用数组名字表示

其中数组元素地址,在此就不再过多解释,也不存在什么有歧义的地方。

#include <stdio.h>
int main()
{
   int arr[] = { 1, 2, 3 };
   printf("%p\n", arr);//数组首元素的地址
   printf("%p\n", &arr);//数组地址
   printf("%p\n", &arr[0]);//数组首元素的地址
   printf("%p\n", arr+1);
   printf("%p\n", &arr+1);
   printf("%p\n", &arr[0]+1);
   return 0;
}

代码2

图2 代码2运行结果

可以看到,数组首元素的地址其实跟数组地址是一样的。而且数组首元素地址&arr[0]也可以简化为直接写数组名arr。但是为什么进行首元素地址“加1”操作后与数组地址“加1”后就不一样了呢。其实这跟数组的存储结构和元素类型有关:

图3 

对于数组地址而言,虽然数值上与首元素地址相同,但含义完全不同,在“加1”操作中,是把整个数组都给跳过,地址增加数=元素个数*元素类型占字节数。而对于首元素地址,“加1”就只是跳过首元素指向下一个元素而已。在这里可能有人会注意到,我创建的元素只有三个,但是却出现了指向“第四个元素”的地址的情况,那是怎么回事呢?其实,计算机的地址是很多的,只是说创建数组,把003BFCE0~003BFCEC的地址分配给了arr,但不代表外面的地址不存在,只是地址内部不确定而已。

那该如何区分数组名到底是数组地址还是数组首元素地址呢

其实很简单,只有两种情况下数组名是数组地址,剩下全部是首元素地址。

1.&最先直接作用于数组名,如&arr,&arr+1,*(&arr)

2.sizeof(数组名)这里也是代表整个数组,具体细节往下看

除此之外数组名都只是表示数组首元素地址。比如&arr[1]--[]优先级高于&、arr+1、&(arr+1)等。

sizeof()与strlen()

sizeof()

#include <stdio.h>
int main()
{
	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
    char arr2[] = { 'a','b','c','d','e','f'};
    char arr3[] = "abcdef";
    printf("%d\n", sizeof(arr1) );
    printf("%d\n", sizeof(arr1[0]));
    printf("%d\n",sizeof(arr1) / sizeof(arr1[0]));
    printf("%d\n", sizeof(arr2));
    printf("%d\n", sizeof(arr2[0]));
    printf("%d\n", sizeof(arr2) / sizeof(arr2[0]));
    printf("%d\n", sizeof(arr3));
    printf("%d\n", sizeof(arr3[0]));
    printf("%d\n", sizeof(arr3) / sizeof(arr3[0]));

return 0;
}

代码3

图4 代码3结果

对于整型数组int arr1[] = { 1,2,3,4,5,6,7,8,9,10 }和字符数组char arr2[] = { 'a','b','c','d','e','f'}而言,sizeof(arr)为元素个数*元素字节数,其中int占四个字节,char占一个。但为什么char arr3[] = "abcdef"求sizeof()比arr2多了1呢?

图5 字符数组与字符串数组

字符串结束标志一定要有‘\0’,单字符没有。sizeof()会把‘\0’也算做数组元素。从调试窗口来看,确实arr3存在第七个元素'\0'。

strlen()

头文件记得加 #include <string.h>

相较于sizeof(),strlen()就更加专精一点,后者就是用来求字符串长度的,通过输入起始地址不断向后直至遇到'\0'为止,计算出之间元素个数(不包含'\0'),输出结果为整型数字。又因为输入必须是地址,就拿×86来说,地址是4个字节32位的。如果输入不为32位二进制数字,就会报错。

对于strlen接受的地址可以写成下边函数

int strlen(char* p)-----------------------------------------------只是为了方便理解而已

意思是输入的地址由char类型的指针p接收,最后返回一个int型数,所以即使你输入的是数组地址,按理说数组地址“加1”会直接跳出数组,但是strlen()会强制把地址转换成char型“加1”只会跳过一个字节,从而实现求字符串长度的功能

#include <stdio.h>
#include <string.h>
int main()
{
	char arr[] = "abcdef";
	printf("%d\n", strlen(arr));//输出为6


return 0;
}

代码4

练习题

首先本人用的是Visual Studio 2022版本,在上方其实有着×86和×64两个选项。简单解释一下,×86是32位的地址,×64是64位地址。所以有些结果我会标为4 or 8,意思就是代码在×86下运行是4,在×64下运行就是8,除此之外其他结果都不会有改变。

一维整型数组

#include <stdio.h>
int main()
{
	
	int a[] = { 1,2,3,4 };
    printf("%d\n", sizeof(a));//16
    printf("%d\n", sizeof(a + 0));//4 or 8
    printf("%d\n", sizeof(*a));//4
    printf("%d\n", sizeof(a + 1));//4 or 8
    printf("%d\n", sizeof(a[1]));//4
    printf("%d\n", sizeof(&a));//4 or 8
    printf("%d\n", sizeof(*&a));//16
    printf("%d\n", sizeof(&a + 1));//4 or 8
    printf("%d\n", sizeof(&a[0]));//4 or 8
    printf("%d\n", sizeof(&a[0] + 1));//4 or 8

return 0;
}

代码5

在这重点结束一下sizeof()括号里面的含义。

sizeof(a),求的就是数组所占字节数

sizeof(a+0),不再是以上曾讲过的两点任一情况,a只是首元素地址,a+0是第一个元素的地址即&a[0],地址在×86下运行是4,在×64下运行就是8。

sizeof(*a),a代表首元素地址,*解引用,即a[0],字节数为4。

sizeof(a+1)同sizeof(a+0),表示的是第二个元素的地址即&a[1],地址在×86下运行是4,在×64下运行就是8。

sizeof(a[1]) 求的是第二个元素所占字节数

 sizeof(&a) &a是数组地址,是地址,地址在×86下运行是4,在×64下运行就是8。

sizeof(*&a) &a是数组地址,再*解引用,可以理解为*与&相互抵消了,等同于sizeof(a)

sizeof(&a + 1)  &a是数组地址,“加1”操作可以参考上面我画的图3,但仍是个地址,地址在×86下运行是4,在×64下运行就是8。

sizeof(&a[0])  &a[0]是首元素地址,地址在×86下运行是4,在×64下运行就是8。

sizeof(&a[0] + 1) 首元素地址“加1”变为第二个元素地址,地址在×86下运行是4,在×64下运行就是8。

一维字符数组

先来看sizeof()

#include <stdio.h>
int main()
{
	
	char arr[] = { 'a','b','c','d','e','f' };
    printf("%d\n", sizeof(arr));//6
    printf("%d\n", sizeof(arr + 0));//4 or 8
    printf("%d\n", sizeof(*arr));//1
    printf("%d\n", sizeof(arr[1]));//1
    printf("%d\n", sizeof(&arr));//4 or 8
    printf("%d\n", sizeof(&arr + 1));// 4 or 8
    printf("%d\n", sizeof(&arr[0] + 1));//4 or 8


    return 0;
}

代码6

sizeof(arr) 就是求数组所占字节数

sizeof(arr+0) arr+0变成首元素地址,地址在×86下运行是4,在×64下运行就是8。

sizeof(*arr) *arr为首元素,字节为1

sizeof(arr[1])  第二个元素字节为1

sizeof(&arr)   &arr为数组地址。地址,4 or 8.

sizeof(&arr+1) 地址,4 or 8

sizeof(&arr[0]+1) 第二个 元素地址,地址,4 or 8。

在这里还有一个小小的问题,倒数第二个sizeof(&arr+1) ,这里的&arr+1难道不会造成数组越界吗?+1后不是已经指向的不再是数组中的的元素了吗。首先回答确实不会报错越界,其次也确实指向的不再是数组中的元素。您且听我慢慢解释:

在运行程序中,计算机其实是会“偷懒”的,举个栗子:

int a=1; int b=2; int c=3;  int d= (++a) || (++b) --(c);

你猜最终a,b,c,d的值各是多少?

再运行过程中,首先a加1变为2,进行或运算,a为2已是真就不会再进行后面的运算了,所以b还是2,c也还是3,d为1。这

再举个例子:

int a=2;int b=3; char c=0;

猜一下sizeof(c=a+b)的值是多少、c最终的值是多少?

是1、1,因为不管怎么计算,最终都是在算c的字节数,c是char型,直接为1,根本没有进行计算c=a+b,c还是1。

所以回到最开始,sizeof(&arr+1) ,程序根本不在乎此时地址在哪里,只关心它是地址,是地址就是 4 or 8。当然从理解的角度,我还是希望大家能知道&arr+1会指向哪个位置(图3)。

再来看strlen()

#include <stdio.h>
#include <string.h>
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));//err
    //printf("%d\n", strlen(arr[1]));//err
    printf("%d\n", strlen(&arr));//随机
    printf("%d\n", strlen(&arr + 1));//随机
    printf("%d\n", strlen(&arr[0] + 1));//随机

    return 0;
}

代码7

前面说过strlen是根据输入的地址,往后一直遇到'\0'计算之间除了'\0'的元素个数,但在字符数组的初始化中,末端不会有'\0'(可以去看图5),strlen会一直往后运行直到遇到为止。这个过程是完全随机的,所以就会得到完全随机的数字。

arr是数组首元素地址,arr+0也是数组首元素地址,但只知道起始位置并不知道终止位置'\0'在哪个位置,所以为止。

*arrarr[1]分别代表首元素和第二个元素,该数组是字符数组,字符元素只占一个字节,不够地址32位/64位,运行会直接报错

图6 strlen()报错

&arr、&arr+1、&arr[0]+1 分别表示数组地址、数组最后一个元素下一位地址和数组第二个元素地址,只能确定起始位置,但不能确定最终位置。但是这三个其实是有关系的(分别设为a、b、c),a=b+6、a=c+1。自己想一想为什么呢?

一维字符串数组

 先看sizeof()

int main()
{
	char arr[] = "abcdef";
	printf("%d\n", sizeof(arr));//7
	printf("%d\n", sizeof(arr + 0));//4 or 8
	printf("%d\n", sizeof(*arr));//1
	printf("%d\n", sizeof(arr[1]));//1
	printf("%d\n", sizeof(&arr));//4 or 8
	printf("%d\n", sizeof(&arr + 1));//4 or 8
	printf("%d\n", sizeof(&arr[0] + 1));//4 or 8
	

return 0;
}

代码8

首先记得字符串存储中是有'\0'的,忘记的回去看图5。

sizeof(arr)就是求数组元素个数,加上'\0'

除了arr数组地址,剩下的只要是地址都是4 or 8

char型元素占字节数都是1

在看strlen()

int main()
{
	char arr[] = "abcdef";
	printf("%d\n", strlen(arr));//6
	printf("%d\n", strlen(arr + 0));//6
	//printf("%d\n", strlen(*arr));//error
	//printf("%d\n", strlen(arr[1]));//err
	printf("%d\n", strlen(&arr));//6
	printf("%d\n", strlen(&arr + 1));//随机
	printf("%d\n", strlen(&arr[0] + 1));//5
	

return 0;
}

代码9

strlen()不会把’\0‘算进去

strlen(arr)就是算字符串长度,为6

arr+0为首元素地址,从首元素一直到末元素后面的'\0',之间为6

*arr和arr[1]都是1字节,运行报错

strlen(&arr)传入数组地址被转换为char型地址,可以求出字符串长度6

&arr+1 跳过数组元素,即从'\0'后面开始,但下一个'\0'不知道在哪里

&arr[0] + 1 数组第二个元素地址,求出字符串长度减一

指针指向字符串

先看sizeof()

int main()
{
	
	char* p = "abcdef";
	printf("%d\n", sizeof(p));//4 or 8
	printf("%d\n", sizeof(p + 1));//4 or 8
	printf("%d\n", sizeof(*p));//1
	printf("%d\n", sizeof(p[0]));//1
	printf("%d\n", sizeof(&p));//4 or 8
	printf("%d\n", sizeof(&p + 1));4 or 8
	printf("%d\n", sizeof(&p[0] + 1));//4 or 8
	

    return 0;
}

代码10

如图7,此时字符串首地址即a的地址存放在了指针p里

sizeof(p) p是地址,地址,4 or 8 。注意与数组arr区别开来sizeof(arr)!!!

p + 1是第二个元素'b'的地址,地址,4 or 8。

&p、&p + 1分别是指针的地址,指针后面一位的地址,地址, 4 or 8

&p[0] + 1  []优先级高于&,指针有着类似于数组的应用,p[0]表示第一个元素,p[1]可以表示第二个元素。所以这里是第二个元素的地址,地址, 4 or 8。

*p p[0] 同样类似于数组,表示首元素。字节为1.

图7 此时内部存储情况

再看strlen()

int main()
{
	
	char* p = "abcdef";
	printf("%d\n", strlen(p));//6
	printf("%d\n", strlen(p + 1));//5
	//printf("%d\n", strlen(*p));//err
	//printf("%d\n", strlen(p[0]));//err
	printf("%d\n", strlen(&p));//随机
	printf("%d\n", strlen(&p + 1));//随机
	printf("%d\n", strlen(&p[0] + 1));//随机

    return 0;
}

代码11

记得看图7。

strlen(p)从首元素a开始共6个元素

strlen(p+1)从第二个元素开始,共5个元素

*p和p[0]都是char型元素,地址要有4个字节,但char只有1个字节,报错。

&p是指针的p的地址,可以理解为存放着存放首元素a的地址的地址,有点绕,其实二级指针就是这意思--存放着一级指针的地址。但是不清楚该地址32位/64位什么样以及后面什么样,所以是随机。

&p+1 同上,为随机,且与上边没有明确的等式关系,因为不确定&p地址有没有0。

&p[0]+1,同上,为随机。

二维整形数组

图8 二维数组

补充一些二维数组的知识点(类比一维数组):

对于一维数组,除那两种情况外,数组名是首元素地址。

对于二维数组,除那两种情况外,数组名是首元素地址,首元素地址是第一行元素地址。

二维数组可以看成几个列数相等的一维数组组成,它们的元素地址也是连续的。

第一行a[0]就可以看成一个一维数组,含有4个元素。a[0]同一维数组arr相同规则:除开那两条,都表示首元素地址。

#include <stdio.h>
int main()
{
	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));//48
	printf("%d\n", sizeof(a[0][0]));//4
	printf("%d\n", sizeof(a[0]));//16
	printf("%d\n", sizeof(a[0] + 1));//4 or 8
	printf("%d\n", sizeof(*(a[0] + 1)));//4
	printf("%d\n", sizeof(a + 1));//4 or 8
	printf("%d\n", sizeof(*(a + 1)));//16
	printf("%d\n", sizeof(&a[0] + 1));//4 or 8
	printf("%d\n", sizeof(*(&a[0] + 1)));//16
	printf("%d\n", sizeof(*a));//16
	printf("%d\n", sizeof(a[3]));//16
	

return 0;
}

代码12

sizeof(a) 求数组所占字节数

sizeof(a[0][0]) 首元素所占字节数

sizeof(a[0]) 同一维数组,第一行所占字节数

sizeof(a[0] + 1) (a[0] + 1)表示第一行第二个元素的地址,地址, 4 or 8。

sizeof(a + 1)     a+1表示第二行的地址(类比一维数组),地址, 4 or 8。

sizeof(*(a + 1))   a+1先于*结合,表示的是第二行的首地址,再解引用,相当于a[1]。求的是第二行占的字节数

sizeof(&a[0] + 1)  第二行的地址,地址,4 or 8。

sizeof(*(&a[0] + 1)) 第二行的地址解引用相当于a[1]。求的是第二行占的字节数

sizeof(*a)  a与*结合,a表示首元素地址即第一行地址,求的是第一行所占字节数

sizeof(a[3])  16,有小伙伴可能会质疑,明明只有三行,怎么会有a[3],还是那句话,sizeof很“懒”

,不会真的去看是不是有,而是要个数字大小就行。前面也举过类似的例子,如果有疑问再去看看吧。

总结

一定要记得数组名字的两条原则

1.&最先直接作用于数组名

2.sizeof(数组名)这里也是代表整个数组

除此之外全部代表的是首元素的地址!!!!!!

学习之路任重道远,朋友们,一起加油。最后我要在感谢一下比特,以上内容都是我在比特上所学习到的,饮水思源,十分感谢!

  • 38
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值