Каков безопасный способ заполнения многомерного массива с помощью std::fill?

#c

#c #массивы #алгоритм #многомерный массив #заполнить

Вопрос:

Вот что я использую:

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

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

(Обновление: я должен был раньше дать понять, что использую это внутри класса.)

Ответ №1:

Простой способ инициализации 0 массива находится в определении:

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

Если вы хотите использовать std::fill или хотите сбросить массив, я нахожу это немного лучше:

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

fill Выраженное в терминах размера массива позволит вам изменять размеры и сохранять fill их нетронутыми. sizeof(flags[0][0]) 1 Это в вашем случае ( sizeof(char)==1 ) , но вы можете оставить его там на случай, если захотите изменить тип в любой момент.

В данном конкретном случае (массив флагов — интегральный тип) Я мог бы даже рассмотреть возможность использования memset , даже если это наименее безопасная альтернатива (это приведет к сбою, если тип массива будет изменен на тип, отличный от pod):

 memset( amp;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( amp;array[0][0], amp;array[rows][0], 0 ); // [1]
      // memset( array, 0, sizeof(array) );
   }
private:
   char array[rows][cols];
};
int main() {
   Aggregate a = {};
   Constructor b;
}
 

Даже если array предполагается, что он должен быть общедоступным, использование конструктора может быть лучшим подходом, поскольку это гарантирует, что array он правильно инициализирован во всех экземплярах класса, в то время как внешняя инициализация зависит от пользовательского кода, не забывающего устанавливать начальные значения.

[1] Как упоминал @Oli Charlesworth в комментарии, использование констант — это другое решение проблемы необходимости указывать (и синхронизировать) размеры более чем в одном месте. Я использовал этот подход здесь с еще другой комбинацией: указатель на первый байт за пределами двумерного массива может быть получен путем запроса адреса первого столбца на одну строку за пределами двумерного массива. Я использовал этот подход только для того, чтобы показать, что это можно сделать, но он ничуть не лучше других, подобных amp;array[0][0] (rows*cols)

Комментарии:

1. 1. Однако альтернативой использованию sizeof является #define (или const int ) измерения. Это имеет дополнительное преимущество в том, что оно будет работать, если вы передали flags его в качестве аргумента функции, так что он распадается на указатель, поэтому sizeof больше не будет давать правильный результат.

2. Я не уверен, что смогу использовать инициализирующие фигурные скобки, потому что 2d-массив находится внутри класса. Я дам вам возможность обновить, прежде чем я соглашусь.

3. @DavidRodriguez: Можете ли вы объяснить, почему аналогичный синтаксис, т.е. fill(amp;arr[0], amp;arr[0] sizeof(arr), 0) Не работает для одномерного массива?

4. @DhruvMullick: sizeof оператор выдает вам размер в байтах, а не количество элементов, если arr содержит что-либо такое, что sizeof(*arr) != 1 указанное выше имеет неопределенное поведение и, вероятно, приведет к сбою (попробуйте получить доступ далеко за пределы конца массива). В противном случае (т.Е. Если массив сохраняется [unsigned|signed] char , он должен работать

5. На самом деле я не знал, что в синтаксисе для заполнения мы используем количество элементов.

Ответ №2:

Каков безопасный способ заполнения многомерного массива с помощью std::fill ?

Простой инициализацией по умолчанию было бы использование привязанной инициализации.

 char flags[26][80]{};
 

Приведенное выше действие инициализирует все элементы в символе flags по умолчанию.


Заполнение двумерного массива с использованием std::fill или std::fill_n

Однако для того, чтобы предоставить другое значение для инициализации, вышеуказанного недостаточно. Варианты std::fill и std::fill_n . (Предполагая, что массив flags находится public в вашем классе)

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

// or using `std::fill_n`
// std::fill_n(amp;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(amp;arr2D)[M][N], const Type val = Type{}) noexcept
{
   std::fill_n(amp;arr2D[0][0], M * N, val);
   // or using std::fill
   // std::fill(amp;arr2D[0][0], amp;arr2D[0][0]   (M * N ), val);
}
 

Теперь вы можете инициализировать свой flags подобный

 fill_2D_array(a.flags, '0'); // flags should be `public` in your class!
 

(Смотрите Онлайн в прямом эфире)


трехмерное заполнение массива с использованием std::fill или std::fill_n

Добавив еще один параметр размера, не относящийся к шаблону, к приведенной выше функции шаблона, это можно перенести и на 3d-массивы

 #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(amp;arr3D)[M][N][O], const Type val = Type{}) noexcept
{
   std::fill_n(amp;arr3D[0][0][0], M * N * O, val);
}
 

(Смотрите Онлайн в прямом эфире)

Ответ №3:

это безопасно, двумерный массив представляет собой массив массивов. Поскольку массив занимал непрерывное хранилище, то и вся многомерная вещь тоже. Так что да, это нормально, безопасно и переносимо. Предполагая, что вы НЕ спрашиваете о стиле, который рассматривается в других ответах (поскольку вы используете флаги, я настоятельно рекомендую std::vector<std::bitset<80> > myFlags(26) )

Комментарии:

1. Вы уверены, что набор битов подойдет? Я просто сохраняю либо ноль, либо единицу внутри каждого пространства в 2d-массиве. Эти флаги используются для отслеживания того, какие позиции на консоли были обновлены с помощью моей процедуры заполнения.

2. @Truncheon: вы храните 8 значений в символе, верно? И для того, чтобы получить, скажем, 7-е значение в 6-м символе, вы должны выполнить некоторый сдвиг / изменение бита и т.д. Набор битов сделает это за вас. То есть он будет хранить каждый флаг в бите, и вы можете устанавливать / снимать каждый флаг по его индексу, не беспокоясь о битовых шаблонах. Единственным недостатком является то, что размер набора битов должен быть константой времени компиляции. Если вы знакомы с boost, у них есть dynamic_bitset, который в значительной степени соответствует его названию.

3. Я считаю, что он хранит один бит в каждом байте. Это означает, что при использовании набора битов из 26 * 80 позиций и соответствующей (row,col)->index алгебры использование одного bitset будет более эффективным с точки зрения потребления памяти.

4. @David: Это определенно будет более эффективно с точки зрения памяти, но читаемость пострадает, то есть ему придется написать btst[numCols*row col], который менее читаем по сравнению с v[row][col] . Решение зависит от OP и зависит от его приоритетов.

Ответ №4:

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

Ответ №5:

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" /* ... */ };
 

Комментарии:

1. Это не пытается ответить на вопрос, просто дает совет, о котором никто не просил.