[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个字节

简单来说,就是从一个地址 ptr 开始,将连续的 num 大小的内存设置为 value

(它其实是一字节一字节地把整个数组设置为一个指定的值)

举个栗子:

memset(arr, -1, sizeof(arr));

上面的代码,就是把数组 arr 的每一个元素赋值为 -1

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)。

所以就会像这样:

转换为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进制数来给数组赋值。

一般情况下所作为赋值使用的无穷大为 0x3f0x7f0x7f 是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 不能满足无穷大“无穷大加一个有穷的数依然为无穷大”的性质,它会变成一个很小的负数。

0x3f10^9 级别的,和 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));,通常作为无穷小使用