为什么用 memset 初始化 int 数组时值为 16843009?
这几天写代码的时候,我发现了一个问题。不管程序输入的是什么,Int 数组最终输出的数字都是一个不变的数字——16843009。查阅资料,发现是在用 memset 函数时出的问题,却突然一时想不明白。后来才发现,原来是很简单的事情。
然而还是水了一篇文
问题
这里附上测试的代码
1 |
|
输出是
1 | 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 |
原因
首先我们需要了解一下 memset。
memset 有三个参数,分别为:传入的数组(指针),要替换为的值,要修改的 字节数。
memset 来自 string.h 头文件,一般来说,当我们用 memset 操作 char 数组时,非常方便。
1 | char str[] = "Hello, world!"; |
此时数组 str 的值会变成
1 | -----, world! |
因为 memset 将此字符串的前五个字符每个设置为 '-'。
而当我们使用
1 | int i[10]; |
之类的语句来初始化数组时也不会出问题,然而为什么把值换成 1 就不行了呢。
原因就在于 memset 是按字节修改数组的,就像上面加粗提示的那样。这里我们可以用一个示意图来理解。
Int 类型变量的占用空间是 4 个字节,一个字节(Byte)是 8 个比特(bit),即一个字节可以放 8 个 0
或1
(二进制)。
而将一个字节设置为 1,而一个字节有 8 个比特,这个字节的二进制数据就是0000 0001
。一个字节一个字节设置后,一个 Int 的数据就会变成0000 0001 0000 0001 0000 0001 0000 0001
,而这个转换为十进制就是 16843009。
这就是为什么我们会得到 16843009 的输出,以及为什么我们不应该用 memset 以非零的数来初始化 Int 等类型的数据。
补充:事实上 memset 也可以初始化每个数据为 -1
为什么呢,因为 -1 的二进制表达全是 1,若是在一个字节内存储 -1,它的二进制值就是1111 1111
。当我们把 Int 的 4 个字节都设为 -1 时,就变成1111 1111 1111 1111 1111 1111 1111 1111
,而这恰好还是 -1 的值。
(为什么是 1111 1111?参考资料:维基百科:补码
替代方案
不能用 memset,我们总得用一个方法来初始化数组的。
用什么呢,有人可能想到这样写:
1 | int i[10] = {1}; |
然而这是行不通的,因为这样做只能让数组的第一个数变成 1。
一般来说我们能想到的最简单的方法是循环赋值,然而这样会增加代码长度,降低代码可读性。
这里介绍一个新函数:fill
fill 的头文件是<algorithm>
。
从 fill 的定义
1 | template <class ForwardIterator, class T> |
我们可以看出,参数分别是:第一个元素的迭代器,最后一个元素的迭代器,要赋的值。对于普通的数组,迭代器在这里可以理解为指向元素的指针。
那么文章最开头的代码可以改为:
1 | int i[10]; |
注意:这里第二个参数的指针要指向最后一个值的后面,因为这种函数要的范围是左闭右开,即 [first,second)。这里 i+10 即 i[10] 的开头(虽然事实上我们不能这样写)。
值的留意的是,fill 事实上等价于:
1 | template <class ForwardIterator, class T> |
也就是说这和我们循环赋值数组一样,时间复杂度都为 O(n)
(来源:http://www.cplusplus.com/reference/algorithm/fill/ )
类似功能的函数是 fill_n,具体来说 fill_n 为前 n 个元素每个都赋值。有兴趣的同学可以了解下,这里不再作介绍。
(参考资料:http://www.cplusplus.com/reference/algorithm/fill_n/ )