当前位置:C++技术网 > 资讯 > 超详细分析:如何正确计算数组字节大小?如何正确计算数组元素个数?

超详细分析:如何正确计算数组字节大小?如何正确计算数组元素个数?

更新时间:2016-03-17 11:47:54浏览次数:1+次

    数组元素个数如何求?sizeof(数组名)结果为什么会有两个?
    之前也没有仔细深入想这个问题,只是有一个基本印象,或者是记忆书本上的说法。在面试的时候,就被sizeof(数组名)的结果考了两次!!两家公司都考了这个题,考的我心虚呀!而且考官解释也是没有听明白。总的说就是,对这一个细节的理解,一塌糊涂。说不定还不如刚学习C语言的同学哦。
    是的,当时间久远了之后,以前记忆的知识点,都模糊了。靠记忆是无法清晰准确的记住这些细节的。知识沉淀之后都会变得模糊,沉淀之后就抽象化了,就只知道大概怎么回事,具体的就要查资料了。这也是为什么强行记忆学不好编程的原因。
    而我现在做的事就是将这些细节分析出来,把前因后果都分析出来,这样也就是在促使自己形成抽象的理解和抽象的记忆,时间久了,但是根据分析的思路,分分钟就可以清清楚楚的浮现知识点的细节,而且准确率极高。我想,喜欢看我写文章的同学,多半也是被我这种分析方法触动了神经,所以才会觉得感触很大,也会觉得我讲的知识细节非常好理解,而且学了之后都忘不了了。所以感觉很受用,和传统的记忆式学习完全不同。据一些朋友反映,我的这种写作方式,或者思考方式,一下子不太适应,但是适应之后特别喜欢,感觉思维都变活泼了,兴趣也大增了,对于编程的理解也是不一样了。
    今天在封装串口编程的时候,每一个命令都是由一系列的字节组成,然后我会将这个数组传递给另一个函数去操作。字符数组用作字节数组,可能会出现一个警告,即C4309: “初始化”: 截断常量值。这个警告的详细分析,在文章《警告C4309: “初始化”: 截断常量值 问题的分析和解决方案》已经说的很清楚了,就不在此赘述了。  
    为了方便函数操作数组,在将数组传递给函数的时候,也就传递了数组的元素个数。
    那么问题来了。可不可以在函数内部自动计算数组的大小呢?什么时候可以不传递数组的元素个数呢?
    这个问题的细节还挺多的。如果不仔细想想还真不知道。在这里分析,并不是简单的说出一个答案,而是寻求因果关系,要知道为什么是这样的答案。
    
1.计算数组大小
    如果你想计算数组的总的字节大小,如何做?我们知道,sizeof是可以用来计算数据类型、变量、常量等各种东西的工具。自然,数组的大小也是可以计算的。
    所以,
char url[]="www.cjjjs.com";
char bb[]={0,1,2,2,3,3,4};
int age[]={0,1,2,2,3,3,4};//sizeof(int)为4
int iSizeUrl = sizeof(url);//13,最后一个空字符也计算在内
int iSizeBB = sizeof(bb);//7
int iSizeAge = sizeof(age);//7*sizeof(int)=28

    你可以看到,sizeof(数组名)是可以计算得到数组的字节大小的。然而,这并不是全部的事实!如果你记住了这个点,将会遇到问题的。下面我们再看看一个使用场景。

int getsizec(char* arr)
{
    return sizeof(arr);
}
int getsizei(int arr[])
{
    return sizeof(arr);
}
char url[]="www.cjjjs.com";
char bb[]={0,1,2,2,3,3,4};
int age[]={0,1,2,2,3,3,4};
int iSizeUrl = getsizec(url);//4
int iSizeBB =  getsizec(bb); //4
int iSizeAge = getsizei(age);//4

    此时,无一例外,返回的计算结果都是4。虽然我们还是使用sizeof来计算的,但是结果却发生了变化。当然,如果你只是学习记忆知识的话,现在就可以记住这种场景了。然而,我们更需要进行一番探究,分析为什么会这样呢?
    在前面一种情况,我定义了一个数组,然后马上用sizeof来求取数组的字节大小,可以得到正确的结果。而将数组名传递到函数后,在函数内用sizeof来计算,就只能得到4了。
    我们在函数外直接计算,编译器很清楚此时的数组名的含义,它是一个连续内存的一个起始地址,而且也知道数组的长度。那么此时编译器就可以通过这些信息来计算数组的总字节大小了。既然是计算字节大小,所以字符数组也不例外。空字符也是一个字节,也是计算范围之内的。
    编译器可以通过定义的数组的类型信息如数组的元素类型,数组元素个数,来确定数组的大小。关键就是,直接定义数组之后,直接计算数组大小的时候,编译器是知道数组的类型信息的,所以可以正确计算。像下面的语句:
int a[5]={0};
    我们人工也可以计算:sizeof(int)*5=20。我们人工能够计算的前提就是我们知道int的类型的大小,同时也知道数组元素个数,自然通过乘积就得到了结果。这个过程对于编译器也是一样的。
    而当数组传递给函数之后,我们只知道数组的起始地址,我们无法知道数组的个数信息。所以,此时就只能将数组名当作一个起始地址,当作一个指针类型而已。那么
int isize = sizeof(指针);//4
    的结果就都是指针的类型的大小了。所以,直接定义数组后求数组大小和传递给函数后求数组的大小,问题的关键就在于数组的元素长度信息。那么为什么传递给函数的数组无法获取数组的长度呢?
    这得从两方面来说。如果是字符数组,也却是用作字符串,那么是可以计算字符串的大小。不过也不一定能够准确的计算字符数组的字节大小。我们知道,字符串的定义就是在字符串的最后一个字节存储一个空字符,也就以空字符来确定字符串的结尾。利用这个特点,在函数得到字符数组起始地址的时候,我们可以使用
int icharcount = strlen(字符数组名);

    得到字符串的字符个数。如果要得到字符串占用的字节数,加一即可。加一就是加上空字符所占的一个字节。然而,字符串的字节大小并不代表字符数组的大小哦。看下面的定义:
char name[]={'c','j','j','j','s','.','c','n','\0','n','o'};

    这个字符数组在空字符后面还存储了'n''o'两个字符,然而计算字符串长度的时候,读取到'\0'就认为字符串已经结束了。所以返回的字符串长度是8,加一后得到字符串所占字节数是9,而实际的字符数组所占的字节数是11个。所以说,字符数组传递给函数后,依然是无法正确计算数组所占的字节大小的。如果刚好字符数组的最后一个字节存储空字符,这样是可以计算出来正确的字符数组大小,然而这只是巧合罢了。
    而非字符数组传递给函数之后,编译器就懵了!因为非字符数组是没有确定结束标志的,不像字符数组,虽然不能准确的计算,但是好歹还可以知道字符个数呀。非字符数组就是一堆数字而已,就无从确定数组的长度了。
    所以,为了统一操作,所以在函数内部接收到数组名和指针类型时,统一当作指针类型来计算大小,所以就出现sizeof得到的结果都是4了。
    通过以上的分析,你就很清楚为什么在函数内部是无法正确计算数组大小的哦。关键就是,无法确定数组的长度哦。你可能会想,我们直接定义数组后可以马上计算出来数组的长度呢?我想说,在一个房间里放的东西,你还不知道它长啥样吗?在一个局部定义的变量,对于这个局部来说,类型信息都是可以知道的。此时的类型信息在这个局部是可以查询的。至于这些信息是如何存储的,这就要看编译器的实现了。比如可以将类型信息存储在定义数组的函数这一层的栈中。在函数执行的时候,就可以查询咯。这个具体的信息,只有查看编译原理了,我们知道这个思路就行了。

    所以我们经常将数组传递给函数的时候,要带上数组的大小,原因就是上面所讲的。在参数中带入数组的大小,就是来帮助确定数组元素个数信息的。可能你经常这么做,却没有深入想这个问题吧。


2.求数组元素个数
    基于计算数组的大小的分析,我们得到了正确的数组大小后,然后用:
int iSize = sizeof(数组的一个元素);//一个元素就是一个变量

    得到数组中一个元素的大小,然后用数组的大小除以数组中一个元素的大小,也就计算出来了数组元素的个数了,代码如下:
int iCount = sizeof(数组名)/sizeof(数组一个元素);//得到数组元素个数,限于编译器知道数组大小的场景

    限于编译器知道数组大小的场景是什么意思呢?我们将数组名传递给函数,同时也传递一个数组元素个数的参数。然而在函数内部,传递的数组元素个数的参数对于编译器来说是自定义的一个参数,并不能表示数组元素个数,万一你将这个参数用作他用,谁知道呢?所以,这种情况对于编译器来说,还是不知道数组元素个数哦。所以在函数内部计算数组的元素个数,是不正确的。通常要在函数内部知道传递过来的数组,也只有从参数中获取。

    我想,分析到这里,对于这一块的细节的把握,就非常清楚了。千万别忽视这些细节,你知道吗,细节有时候会决定成败的哦!程序员的工作就是非常具体而细节的,像这样的基础的细节,我们必须理解的非常清楚。我不相信看完了本文后你还能够忘记这个细节。