¿Inicialización de todos los elementos de una matriz a un valor predeterminado en C ++?

248

Notas de C ++: Array Initialization tiene una buena lista sobre la inicialización de arrays. tengo un

int array[100] = {-1};

esperando que esté lleno con -1's pero no, solo el primer valor es y el resto son 0's mezclados con valores aleatorios.

El código

int array[100] = {0};

funciona bien y establece cada elemento en 0.

¿Qué me estoy perdiendo aquí? ¿No se puede inicializar si el valor no es cero?

Y 2: ¿La inicialización predeterminada (como arriba) es más rápida que el ciclo habitual a través de toda la matriz y asigna un valor o hace lo mismo?

Milán
fuente
1
El comportamiento en C y C ++ es diferente. En C {0} hay un caso especial para un inicializador de estructura, sin embargo, AFAIK no para matrices. int array [100] = {0} debe ser lo mismo que array [100] = {[0] = 0}, que como efecto secundario pondrá a cero todos los demás elementos. El compilador de CA NO debe comportarse como se describe anteriormente, en su lugar, int array [100] = {- 1} debe establecer el primer elemento en -1 y el resto en 0 (sin ruido). En C si tiene una estructura struct x array [100], usar = {0} como inicializador NO es válido. Puede usar {{0}} que inicializará el primer elemento y cero a todos los demás, en la mayoría de los casos será lo mismo.
Fredrik Widlund
1
@FredrikWidlund Es lo mismo en ambos idiomas. {0}No es un caso especial para estructuras ni matrices. La regla es que los elementos sin inicializador se inicializan como si tuvieran 0un inicializador. Si hay agregados anidados (p struct x array[100]. Ej. ), Los inicializadores se aplican a los no agregados en orden "mayor de filas"; Opcionalmente, se pueden omitir llaves para hacer esto. struct x array[100] = { 0 }es válido en C; y válido en C ++ siempre que el primer miembro de struct Xacepte 0como inicializador.
MM
1
{ 0 }no es especial en C, pero es mucho más difícil definir un tipo de datos que no se pueda inicializar con él, ya que no hay constructores y, por lo tanto, no hay forma de evitar que 0se conviertan y se asignen implícitamente a algo .
Leushenko
3
Voté para volver a abrir porque la otra pregunta es sobre C. Hay muchas formas en C ++ para inicializar una matriz que no son válidas en C.
xskxzr
1
También votó por la reapertura - C y C ++ son idiomas diferentes
Pete

Respuestas:

350

Usando la sintaxis que usaste,

int array[100] = {-1};

dice "establecer el primer elemento en -1y el resto en 0" ya que todos los elementos omitidos se establecen en 0.

En C ++, para configurarlos a todos -1, puede usar algo como std::fill_n(desde <algorithm>):

std::fill_n(array, 100, -1);

En C portátil, tienes que rodar tu propio bucle. Hay extensiones de compilador o puede confiar en el comportamiento definido por la implementación como un acceso directo si eso es aceptable.

Evan Teran
fuente
14
Eso también respondió a una pregunta indirecta sobre cómo llenar la matriz con valores predeterminados "fácilmente". Gracias.
Milán
77
@chessofnerd: no es precisamente #include <algorithm>el encabezado correcto, <vector>puede o no incluirlo indirectamente, eso dependería de su implementación.
Evan Teran
2
No tiene que recurrir a inicializar la matriz durante el tiempo de ejecución. Si realmente necesita que la inicialización ocurra estáticamente, es posible usar plantillas y secuencias variadas para generar la secuencia deseada de ints y expandirla al inicializador de la matriz.
void-pointer
2
@ontherocks, no, no hay una forma correcta de usar una sola llamada fill_npara llenar una matriz 2D completa. Necesita recorrer una dimensión, mientras completa la otra.
Evan Teran
77
Esta es una respuesta a alguna otra pregunta. std::fill_nNo es inicialización.
Ben Voigt
133

Hay una extensión para el compilador gcc que permite la sintaxis:

int array[100] = { [0 ... 99] = -1 };

Esto establecería todos los elementos en -1.

Esto se conoce como "inicializadores designados". Consulte aquí para obtener más información.

Tenga en cuenta que esto no está implementado para el compilador gcc c ++.

Callum
fuente
2
Increíble. Esta sintaxis también parece funcionar en clang (por lo que se puede usar en iOS / Mac OS X).
JosephH
31

La página a la que enlazó ya dio la respuesta a la primera parte:

Si se especifica un tamaño de matriz explícito, pero se especifica una lista de inicialización más corta, los elementos no especificados se establecen en cero.

No hay una forma integrada de inicializar toda la matriz a un valor distinto de cero.

En cuanto a cuál es más rápido, se aplica la regla habitual: "El método que le da al compilador la mayor libertad es probablemente más rápido".

int array[100] = {0};

simplemente le dice al compilador "establezca estos 100 ints a cero", que el compilador puede optimizar libremente.

for (int i = 0; i < 100; ++i){
  array[i] = 0;
}

Es mucho más específico. Le dice al compilador que cree una variable de iteración i, le dice el orden en que deben inicializarse los elementos, y así sucesivamente. Por supuesto, es probable que el compilador optimice eso, pero el punto es que aquí está especificando en exceso el problema, obligando al compilador a trabajar más duro para obtener el mismo resultado.

Finalmente, si desea establecer la matriz en un valor distinto de cero, debe (al menos en C ++) usar std::fill:

std::fill(array, array+100, 42); // sets every value in the array to 42

Una vez más, podría hacer lo mismo con una matriz, pero esto es más conciso y le da al compilador más libertad. Solo está diciendo que desea que toda la matriz se llene con el valor 42. No dice nada sobre el orden en que debe hacerse, ni nada más.

jalf
fuente
55
Buena respuesta. Tenga en cuenta que en C ++ (no en C) puede hacer int array [100] = {}; y darle al compilador la mayor libertad :)
Johannes Schaub - litb
1
De acuerdo, excelente respuesta. Pero para una matriz de tamaño fijo, usaría std :: fill_n :-P.
Evan Teran
12

C ++ 11 tiene otra opción (imperfecta):

std::array<int, 100> a;
a.fill(-1);
Timmmm
fuente
ostd::fill(begin(a), end(a), -1)
doctorlai
9

Con {} asigna los elementos a medida que se declaran; el resto se inicializa con 0.

Si no hay = {}que iniciar, el contenido no está definido.

0x6adb015
fuente
8

La página que vinculaste

Si se especifica un tamaño de matriz explícito, pero se especifica una lista de inicialización más corta, los elementos no especificados se establecen en cero.

Problema de velocidad: cualquier diferencia sería insignificante para matrices tan pequeñas. Si trabaja con matrices grandes y la velocidad es mucho más importante que el tamaño, puede tener una matriz constante de los valores predeterminados (inicializados en el momento de la compilación) y luego memcpyllevarlos a la matriz modificable.

laalto
fuente
2
memcpy no es una muy buena idea, ya que eso sería comparable a solo establecer los valores directamente en cuanto a velocidad.
Evan Teran
1
No veo la necesidad de la copia y la matriz constante: ¿Por qué no crear la matriz modificable en primer lugar con los valores precompletados?
Johannes Schaub - litb
Gracias por la explicación de la velocidad y cómo hacerlo si la velocidad es un problema con un gran tamaño de matriz (que es en mi caso)
Milán
La lista de inicializadores se realiza en tiempo de compilación y se carga en tiempo de ejecución. No hay necesidad de ir copiando cosas.
Martin York
@litb, @Evan: por ejemplo, gcc genera una inicialización dinámica (muchos movimientos) incluso con las optimizaciones habilitadas. Para matrices grandes y requisitos de rendimiento estrictos, desea realizar el inicio en tiempo de compilación. memcpy probablemente esté mejor optimizado para copias grandes que muchos movimientos simples.
laalto
4

Otra forma de inicializar la matriz a un valor común, sería generar realmente la lista de elementos en una serie de definiciones:

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

La inicialización de una matriz a un valor común se puede hacer fácilmente:

#define LIST_MAX 6
static unsigned char List[ LIST_MAX ]= { DUP( 123, LIST_MAX ) };

Nota: DUPx introducido para habilitar la sustitución de macros en los parámetros de DUP

Steen
fuente
3

Para el caso de una matriz de elementos de un solo byte, puede usar memset para establecer todos los elementos en el mismo valor.

Hay un ejemplo aquí .

Steve Melnikoff
fuente
3

Utilizando std::array, podemos hacer esto de una manera bastante sencilla en C ++ 14. Es posible hacerlo solo en C ++ 11, pero un poco más complicado.

Nuestra interfaz tiene un tamaño de tiempo de compilación y un valor predeterminado.

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));
}

La tercera función es principalmente por conveniencia, por lo que el usuario no tiene que construir una std::integral_constant<std::size_t, size>, ya que es una construcción bastante prolija. El verdadero trabajo lo realiza una de las dos primeras funciones.

La primera sobrecarga es bastante sencilla: construye una std::arrayde tamaño 0. No es necesario copiar, solo la construimos.

La segunda sobrecarga es un poco más complicada. Reenvía el valor que obtuvo como fuente, y también construye una instancia de make_index_sequencey solo llama a alguna otra función de implementación. ¿Cómo se ve esa función?

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

Esto construye los argumentos del primer tamaño - 1 copiando el valor que pasamos. Aquí, usamos nuestros índices de paquete de parámetros variables como algo para expandir. Hay entradas de tamaño - 1 en ese paquete (como especificamos en la construcción de make_index_sequence), y tienen valores de 0, 1, 2, 3, ..., tamaño - 2. Sin embargo, no nos importan los valores ( así que lo anulamos, para silenciar cualquier advertencia del compilador). La expansión del paquete de parámetros expande nuestro código a algo como esto (suponiendo que size == 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) };

Usamos esos paréntesis para garantizar que la expansión del paquete variable ...expanda lo que queremos, y también para asegurarnos de que estamos utilizando el operador de coma. Sin los paréntesis, parecería que estamos pasando un montón de argumentos a la inicialización de nuestra matriz, pero realmente, estamos evaluando el índice, convirtiéndolo en nulo, ignorando ese resultado nulo y luego devolviendo el valor, que se copia en la matriz .

El argumento final, el que invocamos std::forward, es una optimización menor. Si alguien pasa una cadena std :: temporal y dice "haga una matriz de 5 de estos", nos gustaría tener 4 copias y 1 movimiento, en lugar de 5 copias. El std::forwardasegura que hacemos esto.

El código completo, incluidos los encabezados y algunas pruebas unitarias:

#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.");
}
David Stone
fuente
Su non_copyabletipo es realmente copiable por medio de operator=.
Hertz
Supongo non_copy_constructibleque sería un nombre más preciso para el objeto. Sin embargo, no hay ninguna asignación en este código, por lo que no importa para este ejemplo.
David Stone
1

1) Cuando usa un inicializador, para una estructura o una matriz como esa, los valores no especificados se construyen esencialmente por defecto. En el caso de un tipo primitivo como ints, eso significa que se pondrán a cero. Tenga en cuenta que esto se aplica de forma recursiva: podría tener una matriz de estructuras que contienen matrices y si especifica solo el primer campo de la primera estructura, todo el resto se inicializará con ceros y constructores predeterminados.

2) El compilador probablemente generará un código de inicializador que es al menos tan bueno como podría hacerlo a mano. Tiendo a preferir dejar que el compilador haga la inicialización por mí, cuando sea posible.

Boojum
fuente
1) La inicialización predeterminada de los POD no está sucediendo aquí. Usando la lista, el compilador generará los valores en el momento de la compilación y los colocará en una sección especial del ensamblado que se carga como parte de la inicialización del programa (como el código). Entonces el costo es cero en tiempo de ejecución.
Martin York
1
¿No veo dónde está equivocado? int a [100] = {} ciertamente se inicializa a todos 0, sin tener en cuenta dónde aparece, y struct {int a; } b [100] = {}; es demasiado. "esencialmente construido por defecto" => "valor construido", aunque. Pero esto no importa en el caso de ints, PODS o tipos con creadores declarados por el usuario. Solo importa para NON-Pods sin usuarios declarados, por lo que sé. Pero no rechazaría un voto (!) Debido a esto. de todos modos, +1 para que vuelvas a 0 :)
Johannes Schaub - litb
@Evan: Califiqué mi declaración con "Cuando usa un inicializador ..." No me refería a valores no inicializados. @ Martin: Eso podría funcionar para datos constantes, estáticos o globales. Pero no veo cómo funcionaría eso con algo como: int test () {int i [10] = {0}; int v = i [0]; i [0] = 5; volver v; } Es mejor que el compilador inicialice i [] a ceros cada vez que llame a test ().
Boojum
podría colocar datos en el segmento de datos estáticos y hacer que "i" se refiera a ellos :)
Johannes Schaub - litb
Es cierto: técnicamente, en este caso también podría eludir "i" por completo y simplemente devolver 0. Pero usar el segmento de datos estáticos para datos mutables sería peligroso en entornos de subprocesos múltiples. El punto que estaba tratando de hacer en respuesta a Martin era simplemente que no se puede eliminar por completo el costo de la inicialización. Copie un fragmento prefabricado del segmento de datos estáticos, claro, pero aún no es gratuito.
Boojum
0

En el lenguaje de programación C ++ V4, Stroustrup recomienda el uso de vectores o valarrays sobre matrices integradas. Con valarrary's, cuando los crea, puede iniciarlos a un valor específico como:

valarray <int>seven7s=(7777777,7);

Para inicializar una matriz de 7 miembros con "7777777".

Esta es una forma de C ++ de implementar la respuesta usando una estructura de datos C ++ en lugar de una matriz "C simple".

Cambié a usar el valarray como un intento en mi código para tratar de usar C ++ 'isms v. C'isms ...

Astara
fuente
Este es el segundo peor ejemplo de cómo usar un tipo que he visto ...
Steazy
-3

Debería ser una característica estándar, pero por alguna razón no está incluida en el estándar C ni en 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]);
    }
}

En Fortran puedes hacer:

program main
    implicit none

    byte a(100)
    data a /100*2/
    integer i

    do i = 0, 100
        print *, a(i)
    end do
end

pero no tiene números sin signo ...

¿Por qué C / C ++ no puede simplemente implementarlo? ¿Es realmente tan difícil? Es tan tonto tener que escribir esto manualmente para lograr el mismo resultado ...

#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]);
    }
}

¿Qué pasa si se trata de una matriz de 1,000,00 bytes? Necesitaría escribir un script para escribirlo o recurrir a hacks con ensamblaje / etc. Esto no tiene sentido.

Es perfectamente portátil, no hay razón para que no esté en el idioma.

Solo piratea como:

#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;
}

Una forma de hackearlo es mediante el preprocesamiento ... (El código a continuación no cubre casos extremos, pero está escrito para demostrar rápidamente lo que se puede hacer).

#!/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);
Dmitry
fuente
está imprimiendo en un bucle, ¿por qué no puede asignar en un bucle?
Abhinav Gauniyal
1
la asignación dentro de un bucle conlleva una sobrecarga de tiempo de ejecución; mientras que la codificación rígida del búfer es gratuita porque el búfer ya está incrustado en el binario, por lo que no pierde tiempo construyendo la matriz desde cero cada vez que se ejecuta el programa. tiene razón en que imprimir en un bucle no es una buena idea en general, es mejor agregarlo dentro del bucle y luego imprimir una vez, ya que cada llamada printf requiere una llamada al sistema, mientras que la concatenación de cadenas usando el montón / pila de la aplicación no. Dado que el tamaño en este tipo de programa no es un problema, es mejor construir esta matriz en tiempo de compilación, no en tiempo de ejecución.
Dmitry
"la asignación dentro de un bucle conlleva una sobrecarga de tiempo de ejecución" - Usted subestima severamente el optimizador.
Asu
Dependiendo del tamaño de la matriz, gcc y clang "codificarán" o engañarán el valor, y con matrices más grandes, directamente memset, incluso con la matriz "codificada".
Asu