[C++杂谈] memset函数为什么只能给数组初始化为0或-1?
C++杂谈:memset函数为什么只能给数组初始化为0或-1?
1.什么是memset函数?
1.1 引入
在一开始初学C++时,如果你想给一个数组的全部元素都赋值为一个值,可能是这样的:
int a[105];
for (int i = 0; i < 105; i++)
{
a[i] = 0; //将a[i]赋值为0
}
利用循环遍历整个数组,把每个元素都赋值为一个值,这确实是一个简单的方法。
但是显而易见,这种方法很繁琐 (除非你压在一行里,那我没话说),对讲究代码简洁的OIer们非常的不友好。
小黑板总结!
- 优点:可以自定义赋值的长度和值,灵活度高。
- 缺点:代码较为繁琐。
当你学会了什么是全局变量后,你一定习惯把数组定义在全局变量中。
#include <bits/stdc++.h>
using namespace std;
int a[105]; //全局变量自动赋值为0
int main()
{
......
}
定义在全局变量中的变量、数组都会初始化为0,这很方便。
当然,显而易见,它只能把数组赋值为0。
小黑板总结!
- 优点:简单粗暴。
- 缺点:只能将全部元素都赋值为0。
问:如果我不想赋值为0,或者我不想定义为全局变量怎么办呢?
答:用memset。
1.2 memset函数
通俗来讲,memset 函数是一个在 <string.h> 的初始化函数,他的作用是将某一块内存中的内容全部设置为指定的值, 这个函数通常为新申请的内存做初始化工作。
tips: 在部分IDE中,memset函数可以直接调用,不需要导入
<string.h>头文件,但在洛谷(又是你谷)和许多地方都需要导入<string.h>头文件,不然会报错。当然,你也可以用万能头文件
<bits/stdc++.h>解决,这其中包含<string.h>头文件。
接下来来看使用方法(相信大家都会的辣~):
#include <string.h>
void *memset(void *p, int ch, size_t n);
起始地址 设定的值 n个字节
简单来说,就是从一个地址
(它其实是一字节一字节地把整个数组设置为一个指定的值)
举个栗子:
memset(arr, -1, sizeof(arr));
上面的代码,就是把数组
tips: 字节大小我们通常会使用
sizeof函数来获取,非常方便。
2. memset函数的原理
2.1 一个例子
上面介绍 memset 函数可以对数组初始化,我们来试着对数组初始化为1。
memset(arr, 1, sizeof(arr));
当你满怀期待试着输出这个数组是,你会看到:
数组里的元素都被赋值为了16843009?
如果你多尝试几次,你就会发现,除了0和-1不会出错,填入其他数值都会事与愿违。
到底发生了什么?
2.2 工作原理
我们来翻一下 memset 的源码:
void *(memset)(void *s, int c ,size_t n)
{
const unsigned char uc = c;
unsigned char *su;
for (su = s; 0 < n; ++su, --n)
*su = uc;
return (s);
}
不难看出, memset 函数每次是以 一个字节为单位来进行赋值的,而不是一次性赋值 4/8 个字节。
我们来模拟一下刚才的情况,当我们用 memset(arr, 1, sizeof(a)); 来对数组进行初始化时,因为一个 int 类型占用4个字节(32bit),所以它会将这32bit分成4 * 8bit(即4个字节),并对每个字节赋值1(即00000001)。
所以就会像这样:
-
想要的结果:
00000000\space 00000000\space 00000000\space 00000001\space -
实际的结果:
00000001\space 00000001\space 00000001\space 00000001
转换为10进制后即为16843009,也就是我们之前输出的那个值。
2.3 0和-1为什么不会出错?
0:
0很简单,一个int变量初始为0,每个字节原本也都为0,最低位填充为0后还是为0,合在一起还是为0,非常简单。
-1:
-1的低八位二进制码为11111111,填充4次仍为-1.
补码:
11111111\space 11111111\space 11111111\space 11111111
根据原反补码之间的关系,可知:
原码:
10000000\space 00000000\space 00000000\space 00000001
还是-1,因此赋值成功。
3. 设定技巧
看到这里,相信你应该已经知道为什么 memset 赋值0和-1不会出错了。
但 memset 只能给数组赋值0和-1吗?答案是:不。
上面已经说过,memset 函数每次是以 一个字节为单位来进行赋值的。
因此,如果我们想把数组赋值为无穷大或无穷小,可以填入16进制数来给数组赋值。
一般情况下所作为赋值使用的无穷大为 0x3f 和 0x7f。 0x7f 是32bit int的最大值,如果只用于一般的比较(比如求最小值min时变量的初始值),那么 0x7f 足够了。
但如果我们要使用它来运算,就会出现问题。比如大部分最短路径中的松弛操作:
if (d[u] + w[u][v] < d[v]) d[v] = d[u] + w[u][v];
我们知道,当u,v之间没有边,那么 w[u][v] = INF。如果此时的INF为 0x7f,那么 d[u] + w[u][v] 就会溢出而变成负数。或者说, 0x7f 不能满足无穷大“无穷大加一个有穷的数依然为无穷大”的性质,它会变成一个很小的负数。
0x3f 是 0x7f 一个数量级,但 0x3f + 0x3f = 2147483647 ,不会超过32-bit int的范围,所以 0x3f 可以完美解决 0x7f 会溢出的情况,也就是满足了无穷大“无穷大加上无穷大还是无穷大”的性质。
综上所述,当你想给一个数组初始化为无穷大时,不用定义INF而且写循环赋值了,只需要这样:
memset(a, 0x3f, sizeof(a));
即可。
这里给出其他的一些常用赋值:
memset(a, 0x7F, sizeof(a));,即为int所能表示的最大值(2139062143)
memset(a, 0x80, sizeof(a));,通常作为无穷小使用