¿Cómo devolver una matriz desde una función?

Resuelto ewggwegw asked hace 14 años • 5 respuestas

¿Cómo puedo devolver una matriz a partir de un método y cómo debo declararla?

int[] test(void); // ??
ewggwegw avatar Nov 24 '10 14:11 ewggwegw
Aceptado

int* test();

pero sería "más C++" usar vectores:

std::vector< int > test();

EDITAR
Aclararé algún punto. Como mencionaste C++, usaré los operadores new[]y delete[], pero ocurre lo mismo con malloc/free.

En el primer caso, escribirás algo como:

int* test() {
    return new int[size_needed];
}

pero no es una buena idea porque el cliente de su función no sabe realmente el tamaño de la matriz que está devolviendo, aunque el cliente puede desasignarla de manera segura con una llamada a delete[].

int* theArray = test();
for (size_t i; i < ???; ++i) { // I don't know what is the array size!
    // ...
}
delete[] theArray; // ok.

Una mejor firma sería esta:

int* test(size_t& arraySize) {
    array_size = 10;
    return new int[array_size];
}

Y su código de cliente ahora sería:

size_t theSize = 0;
int* theArray = test(theSize);
for (size_t i; i < theSize; ++i) { // now I can safely iterate the array
    // ...
}
delete[] theArray; // still ok.

Dado que se trata de C++, std::vector<T>es una solución ampliamente utilizada:

std::vector<int> test() {
    std::vector<int> vector(10);
    return vector;
}

Ahora no tienes que llamar a delete[], ya que será manejado por el objeto y puedes iterarlo de forma segura con:

std::vector<int> v = test();
std::vector<int>::iterator it = v.begin();
for (; it != v.end(); ++it) {
   // do your things
}

lo cual es más fácil y seguro.

Simone avatar Nov 24 '2010 07:11 Simone

¿Cómo puedo devolver una matriz en un método de C++ y cómo debo declararla? int[] prueba(nulo); ??

Esto parece una pregunta sencilla, pero en C++ tienes bastantes opciones. En primer lugar, deberías preferir...

  • std::vector<>, que crece dinámicamente a cualquier cantidad de elementos que encuentre en tiempo de ejecución, o

  • std::array<>(introducido con C++ 11), que siempre almacena una cantidad de elementos especificados en el momento de la compilación,

...ya que gestionan la memoria por ti, garantizando un comportamiento correcto y simplificando considerablemente las cosas:

std::vector<int> fn()
{
    std::vector<int> x;
    x.push_back(10);
    return x;
}

std::array<int, 2> fn2()  // C++11
{
    return {3, 4};
}

void caller()
{
    std::vector<int> a = fn();
    const std::vector<int>& b = fn(); // extend lifetime but read-only
                                      // b valid until scope exit/return

    std::array<int, 2> c = fn2();
    const std::array<int, 2>& d = fn2();
}

La práctica de crear una constreferencia a los datos devueltos a veces puede evitar una copia, pero normalmente puede confiar en la optimización del valor de retorno o, aunque vectorno array, en la semántica de movimiento (introducida con C++ 11).

Si realmente desea utilizar una matriz incorporada (a diferencia de la clase de biblioteca estándar mencionada arrayanteriormente), una forma es que la persona que llama reserve espacio y le indique a la función que lo use:

void fn(int x[], int n)
{
    for (int i = 0; i < n; ++i)
        x[i] = n;
}

void caller()
{
    // local space on the stack - destroyed when caller() returns
    int x[10];
    fn(x, sizeof x / sizeof x[0]);

    // or, use the heap, lives until delete[](p) called...
    int* p = new int[10];
    fn(p, 10);
}

Otra opción es envolver la matriz en una estructura que, a diferencia de las matrices sin formato, es legal devolver por valor de una función:

struct X
{
    int x[10];
};

X fn()
{
    X x;
    x.x[0] = 10;
    // ...
    return x;
}

void caller()
{
    X x = fn();
}

Comenzando con lo anterior, si no puede usar C++03, es posible que desee generalizarlo a algo más cercano a C++11 std::array:

template <typename T, size_t N>
struct array
{
    T& operator[](size_t n) { return x[n]; }
    const T& operator[](size_t n) const { return x[n]; }
    size_t size() const { return N; }
    // iterators, constructors etc....
  private:
    T x[N];
};

Otra opción es hacer que la función llamada asigne memoria en el montón:

int* fn()
{
    int* p = new int[2];
    p[0] = 0;
    p[1] = 1;
    return p;
}

void caller()
{
    int* p = fn();
    // use p...
    delete[] p;
}

Para ayudar a simplificar la gestión de objetos del montón, muchos programadores de C++ utilizan "punteros inteligentes" que garantizan la eliminación cuando los punteros al objeto salen de su alcance. Con C++11:

std::shared_ptr<int> p(new int[2], [](int* p) { delete[] p; } );
std::unique_ptr<int[]> p(new int[3]);

Si está atascado en C++03, la mejor opción es ver si la biblioteca boost está disponible en su máquina: proporciona boost::shared_array.

Otra opción más es tener algo de memoria estática reservada por fn(), aunque esto NO ES SEGURO PARA HILO, y significa que cada llamada a fn()sobrescribe los datos vistos por cualquiera que guarde punteros de llamadas anteriores. Dicho esto, puede ser conveniente (y rápido) para código simple de un solo subproceso.

int* fn(int n)
{
    static int x[2];  // clobbered by each call to fn()
    x[0] = n;
    x[1] = n + 1;
    return x;  // every call to fn() returns a pointer to the same static x memory
}

void caller()
{
    int* p = fn(3);
    // use p, hoping no other thread calls fn() meanwhile and clobbers the values...
    // no clean up necessary...
}
Tony Delroy avatar Nov 24 '2010 07:11 Tony Delroy