Инициализация обычного массива с одним значением по умолчанию
Примечания C++: Инициализация массива имеет хороший список по инициализации массивов. у меня есть
int array[100] = {-1};
ожидая, что он будет заполнен -1, но это не так, только первое значение равно нулю, а остальные 0 смешаны со случайными значениями.
Код
int array[100] = {0};
работает просто отлично и устанавливает каждый элемент на 0.
Чего мне здесь не хватает.. Разве нельзя инициализировать его, если значение не равно нулю?
2: Происходит ли инициализация по умолчанию (как указано выше) быстрее, чем обычный цикл по всему массиву и присваивается значение, или она делает то же самое?
14 ответов
Используя синтаксис, который вы использовали,
int array[100] = {-1};
говорит "установить первый элемент -1
а остальное 0
"поскольку все пропущенные элементы установлены в 0
,
В C++, чтобы установить их все -1
, вы можете использовать что-то вроде std::fill_n
(от <algorithm>
):
std::fill_n(array, 100, -1);
В переносном С вы должны свернуть свой цикл. Существуют расширения компилятора, или вы можете полагаться на поведение, определяемое реализацией, в качестве ярлыка, если это приемлемо.
У компилятора gcc есть расширение, которое допускает синтаксис:
int array[100] = { [0 ... 99] = -1 };
Это установит все элементы в -1.
Это известно как "Назначенные инициализаторы", смотрите здесь для получения дополнительной информации.
Обратите внимание, что это не реализовано для компилятора gcc C++.
Страница, на которую вы ссылались, уже дала ответ на первую часть:
Если указан явный размер массива, но указан более короткий список инициализации, неопределенные элементы устанавливаются в ноль.
Не существует встроенного способа инициализировать весь массив каким-либо ненулевым значением.
Что касается того, что быстрее, то применяется обычное правило: "Метод, который дает компилятору наибольшую свободу, вероятно, быстрее".
int array[100] = {0};
просто говорит компилятору "установить эти 100 дюймов на ноль", которые компилятор может свободно оптимизировать.
for (int i = 0; i < 100; ++i){
array[i] = 0;
}
намного конкретнее. Он говорит компилятору создать переменную итерации i
, он сообщает ему порядок, в котором элементы должны быть инициализированы, и так далее. Конечно, компилятор, скорее всего, оптимизирует это, но дело в том, что здесь вы чрезмерно уточняете проблему, заставляя компилятор работать усерднее, чтобы получить тот же результат.
Наконец, если вы хотите установить массив в ненулевое значение, вы должны (по крайней мере, в C++) использовать std::fill
:
std::fill(array, array+100, 42); // sets every value in the array to 42
Опять же, вы можете сделать то же самое с массивом, но это более кратко и дает компилятору больше свободы. Вы просто говорите, что хотите, чтобы весь массив был заполнен значением 42. Вы ничего не говорите о том, в каком порядке это должно быть сделано, или о чем-то еще.
В C++11 есть еще одна (несовершенная) опция:
std::array<int, 100> a;
a.fill(-1);
С помощью std::array
мы можем сделать это довольно простым способом в C++14. Это можно сделать только в C++11, но немного сложнее.
Наш интерфейс имеет размер во время компиляции и значение по умолчанию.
template<typename T>
constexpr auto make_array_n(std::integral_constant<std::size_t, 0>, T &&) {
return std::array<std::decay_t<T>, 0>{};
}
template<std::size_t size, typename T>
constexpr auto make_array_n(std::integral_constant<std::size_t, size>, T && value) {
return detail::make_array_n_impl<size>(std::forward<T>(value), std::make_index_sequence<size - 1>{});
}
template<std::size_t size, typename T>
constexpr auto make_array_n(T && value) {
return make_array_n(std::integral_constant<std::size_t, size>{}, std::forward<T>(value));
}
Третья функция предназначена в основном для удобства, поэтому пользователю не нужно создавать std::integral_constant<std::size_t, size>
сами по себе, так как это довольно многословная конструкция. Настоящая работа выполняется одной из первых двух функций.
Первая перегрузка довольно проста: она создает std::array
размера 0. Копирование не требуется, мы просто создаем его.
Вторая перегрузка немного сложнее. Это продвигается вдоль значения, которое он получил как источник, и это также создает экземпляр make_index_sequence
и просто вызывает какую-то другую функцию реализации. Как выглядит эта функция?
namespace detail {
template<std::size_t size, typename T, std::size_t... indexes>
constexpr auto make_array_n_impl(T && value, std::index_sequence<indexes...>) {
// Use the comma operator to expand the variadic pack
// Move the last element in if possible. Order of evaluation is well-defined
// for aggregate initialization, so there is no risk of copy-after-move
return std::array<std::decay_t<T>, size>{ (static_cast<void>(indexes), value)..., std::forward<T>(value) };
}
} // namespace detail
Это создает первый аргумент размера - 1 путем копирования значения, которое мы передали. Здесь мы используем наши индексы пакета переменных параметров просто как нечто для расширения. Есть размер - 1 записей в этом пакете (как мы указали в конструкции make_index_sequence
), и они имеют значения 0, 1, 2, 3, ..., size - 2. Однако мы не заботимся о значениях (поэтому мы приводим их к void, чтобы заглушить любые предупреждения компилятора). Расширение пакета параметров расширяет наш код до чего-то вроде этого (предполагая размер == 4):
return std::array<std::decay_t<T>, 4>{ (static_cast<void>(0), value), (static_cast<void>(1), value), (static_cast<void>(2), value), std::forward<T>(value) };
Мы используем эти скобки, чтобы гарантировать, что расширение пакета variadic ...
расширяет то, что мы хотим, а также убедиться, что мы используем оператор запятой. Без скобок было бы похоже, что мы передаем кучу аргументов в нашу инициализацию массива, но на самом деле мы оцениваем индекс, приводим его к void, игнорируем этот результат void, а затем возвращаем значение, которое копируется в массив,
Последний аргумент, который мы называем std::forward
на, это небольшая оптимизация. Если кто-то передает временную строку std::string и говорит: "сделайте массив из 5 из них", мы бы хотели иметь 4 копии и 1 перемещение вместо 5 копий. std::forward
гарантирует, что мы делаем это.
Полный код, включая заголовки и некоторые модульные тесты:
#include <array>
#include <type_traits>
#include <utility>
namespace detail {
template<std::size_t size, typename T, std::size_t... indexes>
constexpr auto make_array_n_impl(T && value, std::index_sequence<indexes...>) {
// Use the comma operator to expand the variadic pack
// Move the last element in if possible. Order of evaluation is well-defined
// for aggregate initialization, so there is no risk of copy-after-move
return std::array<std::decay_t<T>, size>{ (static_cast<void>(indexes), value)..., std::forward<T>(value) };
}
} // namespace detail
template<typename T>
constexpr auto make_array_n(std::integral_constant<std::size_t, 0>, T &&) {
return std::array<std::decay_t<T>, 0>{};
}
template<std::size_t size, typename T>
constexpr auto make_array_n(std::integral_constant<std::size_t, size>, T && value) {
return detail::make_array_n_impl<size>(std::forward<T>(value), std::make_index_sequence<size - 1>{});
}
template<std::size_t size, typename T>
constexpr auto make_array_n(T && value) {
return make_array_n(std::integral_constant<std::size_t, size>{}, std::forward<T>(value));
}
struct non_copyable {
constexpr non_copyable() = default;
constexpr non_copyable(non_copyable const &) = delete;
constexpr non_copyable(non_copyable &&) = default;
};
int main() {
constexpr auto array_n = make_array_n<6>(5);
static_assert(std::is_same<std::decay_t<decltype(array_n)>::value_type, int>::value, "Incorrect type from make_array_n.");
static_assert(array_n.size() == 6, "Incorrect size from make_array_n.");
static_assert(array_n[3] == 5, "Incorrect values from make_array_n.");
constexpr auto array_non_copyable = make_array_n<1>(non_copyable{});
static_assert(array_non_copyable.size() == 1, "Incorrect array size of 1 for move-only types.");
constexpr auto array_empty = make_array_n<0>(2);
static_assert(array_empty.empty(), "Incorrect array size for empty array.");
constexpr auto array_non_copyable_empty = make_array_n<0>(non_copyable{});
static_assert(array_non_copyable_empty.empty(), "Incorrect array size for empty array of move-only.");
}
С помощью {} вы назначаете элементы так, как они объявлены; остальное инициализируется 0.
Если нет = {}
чтобы инициализировать, содержание не определено.
На странице, на которую вы ссылались, указано
Если указан явный размер массива, но указан более короткий список инициализации, неопределенные элементы устанавливаются в ноль.
Проблема скорости: любые различия будут незначительными для таких маленьких массивов. Если вы работаете с большими массивами и скорость намного важнее размера, вы можете получить массив const со значениями по умолчанию (инициализирован во время компиляции), а затем memcpy
их в модифицируемый массив.
Другим способом инициализации массива с общим значением будет создание списка элементов в последовательности определений:
#define DUP1( X ) ( X )
#define DUP2( X ) DUP1( X ), ( X )
#define DUP3( X ) DUP2( X ), ( X )
#define DUP4( X ) DUP3( X ), ( X )
#define DUP5( X ) DUP4( X ), ( X )
.
.
#define DUP100( X ) DUP99( X ), ( X )
#define DUPx( X, N ) DUP##N( X )
#define DUP( X, N ) DUPx( X, N )
Инициализация массива к общему значению может быть легко выполнена:
#define LIST_MAX 6
static unsigned char List[ LIST_MAX ]= { DUP( 123, LIST_MAX ) };
Примечание: DUPx введен для включения подстановки макросов в параметрах в DUP
В случае массива однобайтовых элементов вы можете использовать memset, чтобы установить для всех элементов одинаковое значение.
Здесь есть пример.
Самый простой способ — использовать и написать шаблон функции, который будет возвращать требуемое значение.std::array
со всеми его элементами, инициализированными переданным аргументом, как показано ниже.
Версия С++ 11
template<std::size_t N> std::array<int, N> make_array(int val)
{
std::array<int, N> tempArray{};
for(int &elem:tempArray)
{
elem = val;
}
return tempArray;
}
int main()
{
//---------------------V-------->number of elements
auto arr = make_array<8>(5);
//------------------------^---->value of element to be initialized with
//lets confirm if all objects have the expected value
for(const auto &elem: arr)
{
std::cout << elem << std::endl; //prints all 5
}
}
Версия С++17
С С++17 вы можете добавитьconstexpr
в шаблон функции, чтобы его можно было использовать в контексте constexpr:
//-----------------------------------------vvvvvvvvv--->added constexpr
template<std::size_t N> std::array<int, N> constexpr make_array(int val)
{
std::array<int, N> tempArray{};
for(int &elem:tempArray)
{
elem = val;
}
return tempArray;
}
int main()
{
//--vvvvvvvvv------------------------------>constexpr added
constexpr auto arr = make_array<8>(5);
for(const auto &elem: arr)
{
std::cout << elem << std::endl;
}
}
В C++ также возможно использование метапрограммирования и шаблонов с переменными значениями. В следующем посте показано, как это сделать: Программно создавать статические массивы во время компиляции в C++.
1) Когда вы используете инициализатор, для структуры или подобного массива, неопределенные значения создаются по умолчанию. В случае примитивного типа, такого как int, это означает, что они будут обнулены. Обратите внимание, что это применяется рекурсивно: у вас может быть массив структур, содержащих массивы, и если вы укажете только первое поле первой структуры, то все остальные будут инициализированы нулями и конструкторами по умолчанию.
2) Компилятор, вероятно, сгенерирует код инициализатора, который по крайней мере так же хорош, как вы могли бы сделать вручную. Я предпочитаю, чтобы компилятор делал инициализацию для меня, когда это возможно.
В языке программирования C++ V4 Страуструп рекомендует использовать векторы или переменные над встроенными массивами. С valarrary, когда вы создаете их, вы можете инициировать их к определенному значению, например:
valarray <int>seven7s=(7777777,7);
Инициализировать массив длиной 7 элементов с "7777777".
Это способ реализации ответа в C++ с использованием структуры данных C++ вместо "простого старого массива C".
Я переключился на использование valarray как попытку в моем коде попытаться использовать C++'isms v. C'isms....
Должна быть стандартной функцией, но по какой-то причине она не включена ни в стандарт C, ни в C++...
#include <stdio.h>
__asm__
(
" .global _arr; "
" .section .data; "
"_arr: .fill 100, 1, 2; "
);
extern char arr[];
int main()
{
int i;
for(i = 0; i < 100; ++i) {
printf("arr[%u] = %u.\n", i, arr[i]);
}
}
В Фортране вы можете сделать:
program main
implicit none
byte a(100)
data a /100*2/
integer i
do i = 0, 100
print *, a(i)
end do
end
но у него нет чисел без знака...
Почему C/C++ не может просто реализовать это. Это действительно так сложно? Глупо писать это вручную, чтобы достичь того же результата...
#include <stdio.h>
#include <stdint.h>
/* did I count it correctly? I'm not quite sure. */
uint8_t arr = {
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
};
int main()
{
int i;
for(i = 0; i < 100; ++i) {
printf("arr[%u] = %u.\n", i, arr[i]);
}
}
Что если это был массив из 1000,00 байтов? Мне нужно написать скрипт, чтобы написать его для меня, или прибегнуть к хаки с ассемблером / и т.д. Это нонсенс.
Это совершенно портативно, нет причин не быть на языке.
Просто взломайте это как:
#include <stdio.h>
#include <stdint.h>
/* a byte array of 100 twos declared at compile time. */
uint8_t twos[] = {100:2};
int main()
{
uint_fast32_t i;
for (i = 0; i < 100; ++i) {
printf("twos[%u] = %u.\n", i, twos[i]);
}
return 0;
}
Один из способов взломать его - это предварительная обработка... (Код ниже не охватывает крайние случаи, а написан для быстрой демонстрации того, что можно сделать.)
#!/usr/bin/perl
use warnings;
use strict;
open my $inf, "<main.c";
open my $ouf, ">out.c";
my @lines = <$inf>;
foreach my $line (@lines) {
if ($line =~ m/({(\d+):(\d+)})/) {
printf ("$1, $2, $3");
my $lnew = "{" . "$3, "x($2 - 1) . $3 . "}";
$line =~ s/{(\d+:\d+)}/$lnew/;
printf $ouf $line;
} else {
printf $ouf $line;
}
}
close($ouf);
close($inf);