使用std::fill安全地填充多维数组的方法是什么?

19

我正在使用以下内容:

class something
{
   char flags[26][80];
} a;

std::fill(&a.flags[0][0], &a.flags[0][0] + 26 * 80, 0);

(更新:我之前应该清楚地说明我是在类中使用这个函数的。)

5个回答

37

初始化数组为 0 的简单方法是在定义时进行:

char flags[26][80] = {};

如果你想使用std::fill,或者你想要重置这个数组,我认为这样会更好:

char flags[26][80];
std::fill( &flags[0][0], &flags[0][0] + sizeof(flags) /* / sizeof(flags[0][0]) */, 0 );

通过以数组大小为参考的fill,你可以更改维度并保持fill不变。在你的情况下(sizeof(char) == 1),sizeof(flags[0][0])1,但你可能希望保留它,以便在任何时候可以更改类型。

在这种特殊情况下(整数类型的flags数组),即使它是最不安全的选择,我甚至也可以考虑使用memset(如果将数组类型更改为非POD类型,则会出现错误):

memset( &flags[0][0], 0, sizeof(flags) );

需要注意的是,在这三种情况下,数组大小只需声明一次,编译器会自动推断其余部分。这种方式更加安全,因为这样可以减少程序员错误(在一个地方更改大小,却忘记在其他地方修改)。

编辑:你已经更新了代码,但它无法编译,因为数组是私有的,并且你试图从外部初始化它。根据你的类实际上是否是聚合的(并且想保持为聚合),或者是否要向类中添加构造函数,可以采用不同的方法。

const std::size_t rows = 26;
const std::size_t cols = 80;
struct Aggregate {
   char array[rows][cols];
};
class Constructor {
public:
   Constructor() {
      std::fill( &array[0][0], &array[rows][0], 0 ); // [1]
      // memset( array, 0, sizeof(array) );
   }
private:
   char array[rows][cols];
};
int main() {
   Aggregate a = {};
   Constructor b;
}
即使数组预计是公共的,使用构造函数可能是更好的方法,因为它将保证在类的所有实例中正确初始化数组,而外部初始化取决于用户代码不要忘记设置初始值。
[1] 正如@Oli Charlesworth在评论中提到的,使用常量是解决在多个位置声明(并保持同步)大小的问题的不同方法。我已经在这里使用了该方法,并采用了不同的组合:通过请求超出二维数组的第一行以下的第一列的地址,可以获得指向第一个字节外部的指针。我仅使用此方法来展示它可以完成,但与其他方法如&array[0][0]+(rows*cols)并没有任何区别。

1
@DavidRodriguez:你能解释一下为什么类似的语法,即fill(&arr[0], &arr[0] + sizeof(arr), 0)在一维数组中不起作用吗? - Dhruv Mullick
1
@DhruvMullick:sizeof 运算符会给出以字节为单位的大小,而不是元素数量。如果 arr 包含任何使得 sizeof(*arr) != 1 的内容,则上述代码行为未定义,并且可能会导致崩溃(尝试访问数组末尾之外的位置)。否则(即如果数组包含 [unsigned|signed] char),它应该可以正常工作。 - David Rodríguez - dribeas
这是一个相当老的答案,但是fill函数的第二个参数不应该是&array[rows][cols]吗? - 463035818_is_not_a_number
@tobi303:我不这么认为。&array[rows][cols]是整个数组的最后一个元素所在的一整行,而不仅仅是超出一个元素。 - David Rodríguez - dribeas
@DavidRodríguez-dribeas 我刚意识到我有多愚蠢;) 当然你的版本是正确的,我只是有点困惑。 - 463035818_is_not_a_number
显示剩余26条评论

5

如何使用 std::fill 安全地填充多维数组?

简单的默认初始化方法是 使用大括号初始化

char flags[26][80]{};

上述代码将会把flags中的所有元素初始化为默认字符。

使用std::fillstd::fill_n填充二维数组

然而,仅提供以上初始化值是不够的。可选的方法有std::fillstd::fill_n。(假设数组flags在您的类中为public)

std::fill(
   &a.flags[0][0],
   &a.flags[0][0] + sizeof(a.flags) / sizeof(a.flags[0][0]),
   '0');

// or using `std::fill_n`
// std::fill_n(&a.flags[0][0], sizeof(a.flags) / sizeof(a.flags[0][0]), '1');

为了将其推广到任何类型的任何初始化值的2d数组,我建议使用如下模板函数。这也将避免计算数组中总元素的sizeof
#include <algorithm> // std::fill_n, std::fill
#include <cstddef>   // std::size_t

template<typename Type, std::size_t M, std::size_t N>
constexpr void fill_2D_array(Type(&arr2D)[M][N], const Type val = Type{}) noexcept
{
   std::fill_n(&arr2D[0][0], M * N, val);
   // or using std::fill
   // std::fill(&arr2D[0][0], &arr2D[0][0] + (M * N ), val);
}

现在你可以像这样初始化你的flags
fill_2D_array(a.flags, '0'); // flags should be `public` in your class!

(在线查看)


使用std::fillstd::fill_n填充3-D数组

将上述模板函数添加一个非模板大小参数,即可将其应用于三维数组

#include <algorithm> // std::fill_n
#include <cstddef>   // std::size_t

template<typename Type, std::size_t M, std::size_t N, std::size_t O>
constexpr void fill_3D_array(Type(&arr3D)[M][N][O], const Type val = Type{}) noexcept
{
   std::fill_n(&arr3D[0][0][0], M * N * O, val);
}

(在线观看)


2

安全性是可以保证的, 二维数组是由数组组成的数组。由于数组占用连续的存储空间,所以整个多维数组也将如此。因此,是的,它是可以使用的,安全和可移植的。假设您不是在询问样式,这已经被其他答案涵盖(由于您正在使用标志,强烈建议使用std::vector<std::bitset<80> > myFlags(26)


你确定bitset适合吗?我只是在2D数组的每个空间内存储零或一。这些标志用于跟踪已使用我的泛洪填充例程更新的控制台上的哪些位置。 - Truncheon
@Truncheon:你是把8个值存储在一个char中,对吧?如果你想要获取第6个char中的第7个值,你必须进行一些位移/与操作等。而bitset会为你完成这个过程。也就是说,它将每个标志存储在一个位中,你可以通过索引设置/取消设置每个标志,而不必担心位模式。唯一的缺点是bitset的大小必须是编译时常量。如果你熟悉boost,它们有一个dynamic_bitset,基本上就是它的名字。 - Armen Tsirunyan
我相信他在每个字节中存储了一个单独的位。这意味着使用一个26*80位置的位集和适当的(row,col)->index代数,使用单个bitset将更有效地消耗内存。 - David Rodríguez - dribeas
@David:从内存效率来看,使用btst[numCols*row+col]会更高效,但可读性会受到影响,因为这种写法比v[row][col]难以阅读。决定权在于OP,并取决于他的优先考虑因素。 - Armen Tsirunyan

1
char flags[26][80];
std::fill((char*)flags, (char*)flags + sizeof(flags)/sizeof(char), 0);

0

char[80]是用来替代真正的字符串类型吗?如果是的话,我建议使用以下方式:

std::vector<std::string> flags(26);
flags[0] = "hello";
flags[1] = "beautiful";
flags[2] = "world";
// ...

或者,如果您有支持初始化列表的C++编译器,例如最近的g++编译器:

std::vector<std::string> flags { "hello", "beautiful", "world" /* ... */ };

3
这并没有试图回答问题,只是给出了没有人提过的建议。 - user14248283

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接