Pasar una matriz 2D a una función C ++

324

Tengo una función que quiero tomar, como parámetro, una matriz 2D de tamaño variable.

Hasta ahora tengo esto:

void myFunction(double** myArray){
     myArray[x][y] = 5;
     etc...
}

Y he declarado una matriz en otra parte de mi código:

double anArray[10][10];

Sin embargo, llamar myFunction(anArray)me da un error.

No quiero copiar la matriz cuando la paso. Cualquier cambio realizado myFunctiondebería alterar el estado de anArray. Si entiendo correctamente, solo quiero pasar como argumento un puntero a una matriz 2D. La función también debe aceptar matrices de diferentes tamaños. Entonces, por ejemplo, [10][10]y [5][5]. ¿Cómo puedo hacer esto?

RogerDarwin
fuente
1
no puede convertir el parámetro 3 de 'doble [10] [10]' a 'doble **'
RogerDarwin
3
La respuesta aceptada muestra solo 2 técnicas [sus (2) y (3) son iguales] pero hay 4 formas únicas de pasar una matriz 2D a una función .
legends2k
Estrictamente hablando, sí, no son matrices 2D, pero esta convención (aunque conduce a UB) de tener una matriz de punteros, cada uno apuntando a una matriz (1D), parece ser frecuente :( Tener una matriz aplanada 1D de mxn longitud, con funciones / clase de ayuda para emular una matriz 2D es quizás mejor.
legends2k
FÁCIL - func(int* mat, int r, int c){ for(int i=0; i<r; i++) for(int j=0; j<c; j++) printf("%d ", *(mat+i*c+j)); }. Llámalo como ...int mat[3][5]; func(mat[0], 3, 5);
Minhas Kamal

Respuestas:

413

Hay tres formas de pasar una matriz 2D a una función:

  1. El parámetro es una matriz 2D.

    int array[10][10];
    void passFunc(int a[][10])
    {
        // ...
    }
    passFunc(array);
  2. El parámetro es una matriz que contiene punteros

    int *array[10];
    for(int i = 0; i < 10; i++)
        array[i] = new int[10];
    void passFunc(int *a[10]) //Array containing pointers
    {
        // ...
    }
    passFunc(array);
  3. El parámetro es un puntero a un puntero.

    int **array;
    array = new int *[10];
    for(int i = 0; i <10; i++)
        array[i] = new int[10];
    void passFunc(int **a)
    {
        // ...
    }
    passFunc(array);
shengy
fuente
44
@Overflowh Puede obtener los elementos de arraycon array[i][j]:)
shengy
14
Para el primer caso, el parámetro puede declararse como int (*a)[10].
Zachary
99
Para el segundo caso, el parámetro puede declararse como int **.
Zachary
1
@Zack: Tienes razón, solo hay realmente dos casos; uno es un puntero a puntero y otro es un puntero único a una matriz entera de tamaño n, es decir int (*a) [10].
legends2k
3
Los casos 2 y 3 no son matrices 2D, por lo que esta respuesta es engañosa. Mira esto .
Lundin
178

Tamaño fijo

1. Pase por referencia

template <size_t rows, size_t cols>
void process_2d_array_template(int (&array)[rows][cols])
{
    std::cout << __func__ << std::endl;
    for (size_t i = 0; i < rows; ++i)
    {
        std::cout << i << ": ";
        for (size_t j = 0; j < cols; ++j)
            std::cout << array[i][j] << '\t';
        std::cout << std::endl;
    }
}

En C ++, pasar la matriz por referencia sin perder la información de la dimensión es probablemente la más segura, ya que uno no tiene que preocuparse de que la persona que llama pase una dimensión incorrecta (los indicadores del compilador no coinciden). Sin embargo, esto no es posible con arreglos dinámicos (de libre acceso); funciona para automático ( generalmente apilamiento arreglos ), es decir, la dimensionalidad debe conocerse en tiempo de compilación.

2. Pase por puntero

void process_2d_array_pointer(int (*array)[5][10])
{
    std::cout << __func__ << std::endl;
    for (size_t i = 0; i < 5; ++i)
    {
        std::cout << i << ": ";
        for (size_t j = 0; j < 10; ++j)
            std::cout << (*array)[i][j] << '\t';
        std::cout << std::endl;
    }    
}

El equivalente en C del método anterior es pasar la matriz por puntero. Esto no debe confundirse con pasar por el tipo de puntero decaído de la matriz (3) , que es el método común y popular, aunque menos seguro que este pero más flexible. Al igual que (1) , use este método cuando todas las dimensiones de la matriz sean fijas y conocidas en tiempo de compilación. Tenga en cuenta que al llamar a la función, se debe pasar process_2d_array_pointer(&a)la dirección de la matriz y no la dirección del primer elemento por descomposición process_2d_array_pointer(a).

Tamaño variable

Estos se heredan de C pero son menos seguros, el compilador no tiene forma de verificar, garantizando que la persona que llama está pasando las dimensiones requeridas. La función solo se basa en lo que la persona que llama pasa como dimensión (es). Estos son más flexibles que los anteriores ya que se pueden pasar invariablemente a ellos conjuntos de diferentes longitudes.

Debe recordarse que no hay tal cosa como pasar una matriz directamente a una función en C [mientras que en C ++ se pueden pasar como referencia (1) ]; (2) está pasando un puntero a la matriz y no a la matriz misma. Pasar siempre una matriz tal cual se convierte en una operación de copia de puntero que se ve facilitada por la naturaleza de la matriz de descomposición en un puntero .

3. Pase por (valor) un puntero al tipo decaído

// int array[][10] is just fancy notation for the same thing
void process_2d_array(int (*array)[10], size_t rows)
{
    std::cout << __func__ << std::endl;
    for (size_t i = 0; i < rows; ++i)
    {
        std::cout << i << ": ";
        for (size_t j = 0; j < 10; ++j)
            std::cout << array[i][j] << '\t';
        std::cout << std::endl;
    }
}

Aunque int array[][10]está permitido, no lo recomendaría sobre la sintaxis anterior, ya que la sintaxis anterior deja en claro que el identificador arrayes un puntero único a una matriz de 10 enteros, mientras que esta sintaxis parece que es una matriz 2D pero es el mismo puntero a Una matriz de 10 enteros. Aquí conocemos el número de elementos en una sola fila (es decir, el tamaño de la columna, 10 aquí) pero el número de filas es desconocido y, por lo tanto, debe pasarse como argumento. En este caso, hay cierta seguridad ya que el compilador puede marcar cuando se pasa un puntero a una matriz con una segunda dimensión no igual a 10. La primera dimensión es la parte variable y puede omitirse. Vea aquí la justificación de por qué solo se permite omitir la primera dimensión.

4. Pase por puntero a puntero

// int *array[10] is just fancy notation for the same thing
void process_pointer_2_pointer(int **array, size_t rows, size_t cols)
{
    std::cout << __func__ << std::endl;
    for (size_t i = 0; i < rows; ++i)
    {
        std::cout << i << ": ";
        for (size_t j = 0; j < cols; ++j)
            std::cout << array[i][j] << '\t';
        std::cout << std::endl;
    }
}

Nuevamente, hay una sintaxis alternativa int *array[10]que es la misma que int **array. En esta sintaxis, [10]se ignora, ya que se desintegra en un puntero y se convierte int **array. Quizás sea solo una señal para la persona que llama que la matriz pasada debe tener al menos 10 columnas, incluso cuando se requiere el recuento de filas. En cualquier caso, el compilador no marca ninguna infracción de longitud / tamaño (solo verifica si el tipo pasado es un puntero a puntero), por lo tanto, requiere recuentos de filas y columnas ya que el parámetro tiene sentido aquí.

Nota: (4) es la opción menos segura ya que casi no tiene ningún tipo de verificación y la más inconveniente. Uno no puede pasar legítimamente una matriz 2D a esta función; C-FAQ condena la solución habitual de hacer, int x[5][10]; process_pointer_2_pointer((int**)&x[0][0], 5, 10);ya que potencialmente puede conducir a un comportamiento indefinido debido al aplanamiento de la matriz. La forma correcta de pasar una matriz en este método nos lleva a la parte inconveniente, es decir, necesitamos una matriz adicional (sustituta) de punteros con cada uno de sus elementos apuntando a la fila respectiva de la matriz real a ser pasada; este sustituto se pasa a la función (ver más abajo); todo esto para realizar el mismo trabajo que los métodos anteriores, que son más seguros, limpios y quizás más rápidos.

Aquí hay un programa de controlador para probar las funciones anteriores:

#include <iostream>

// copy above functions here

int main()
{
    int a[5][10] = { { } };
    process_2d_array_template(a);
    process_2d_array_pointer(&a);    // <-- notice the unusual usage of addressof (&) operator on an array
    process_2d_array(a, 5);
    // works since a's first dimension decays into a pointer thereby becoming int (*)[10]

    int *b[5];  // surrogate
    for (size_t i = 0; i < 5; ++i)
    {
        b[i] = a[i];
    }
    // another popular way to define b: here the 2D arrays dims may be non-const, runtime var
    // int **b = new int*[5];
    // for (size_t i = 0; i < 5; ++i) b[i] = new int[10];
    process_pointer_2_pointer(b, 5, 10);
    // process_2d_array(b, 5);
    // doesn't work since b's first dimension decays into a pointer thereby becoming int**
}
legends2k
fuente
¿Qué pasa con pasar matrices asignadas dinámicamente a funciones en C ++? En el estándar C11 se puede hacer para arreglos asignados estática y dinámicamente como fn (int col, int row, int array [col] [row]): stackoverflow.com/questions/16004668/… He hecho la pregunta para este problema : stackoverflow.com/questions/27457076/…
42n4
@ 42n4 El caso 4 cubre (también para C ++) eso. Para las matrices asignados dinámicamente, sólo la línea dentro del bucle cambiaría a partir b[i] = a[i];de, por ejemplo, b[i] = new int[10];. También se puede basignar dinámicamente int **b = int *[5];y seguirá funcionando como está.
legends2k
1
¿Cómo funciona el direccionamiento array[i][j]en la función en 4) ? Debido a que ha recibido ptr a ptr y no conoce el valor de la última dimensión, ¿qué es necesario para realizar un cambio para el direccionamiento correcto?
user1234567
2
array[i][j]es solo aritmética de puntero, es decir, al valor del puntero array, agregaría iy desreferenciaría el resultado como int*, a lo que agregaría jy desreferenciaría esa ubicación, leyendo un int. Entonces, no, no necesita conocer ninguna dimensión para esto. Pero, ese es el punto! El compilador toma la palabra del programador con fe y si el programador era incorrecto, se produce un comportamiento indefinido. Esta es la razón por la que mencioné que el caso 4 es la opción menos segura.
legends2k
En tales casos, una estructura puede servirle bien.
Xofo
40

Una modificación a la primera sugerencia de shengy, puede usar plantillas para hacer que la función acepte una variable de matriz multidimensional (en lugar de almacenar una matriz de punteros que deben ser administrados y eliminados):

template <size_t size_x, size_t size_y>
void func(double (&arr)[size_x][size_y])
{
    printf("%p\n", &arr);
}

int main()
{
    double a1[10][10];
    double a2[5][5];

    printf("%p\n%p\n\n", &a1, &a2);
    func(a1);
    func(a2);

    return 0;
}

Las declaraciones de impresión están ahí para mostrar que las matrices se pasan por referencia (al mostrar las direcciones de las variables)

Zrax
fuente
2
Debería usarlo %ppara imprimir un puntero, e incluso entonces, debe lanzarlo void *, de lo contrario, printf()invoca un comportamiento indefinido. Además, no debe usar el &operador addressof ( ) al llamar a las funciones, ya que las funciones esperan un argumento de tipo double (*)[size_y], mientras que actualmente las pasa double (*)[10][10]y double (*)[5][5].
Si está utilizando plantillas, hacer ambas dimensiones como argumentos de plantilla es más apropiado y es mejor ya que el acceso de puntero de bajo nivel puede evitarse por completo.
legends2k
3
Esto solo funciona si el tamaño de la matriz se conoce en tiempo de compilación.
jeb_is_a_mess
@Georg Code arriba en respuesta es exactamente lo que sugerí. Funciona en GCC 6.3 - demostración en línea . ¿Olvidó hacer referencia al parámetro?
legends2k
21

Sorprende que nadie haya mencionado esto todavía, pero puede simplemente crear plantillas en cualquier cosa que admita semántica en 2D [] [].

template <typename TwoD>
void myFunction(TwoD& myArray){
     myArray[x][y] = 5;
     etc...
}

// call with
double anArray[10][10];
myFunction(anArray);

Funciona con cualquier estructura de datos 2D "tipo matriz", como std::vector<std::vector<T>>un tipo definido por el usuario para maximizar la reutilización del código.

LemonPi
fuente
1
Esta debería ser la respuesta correcta. Resuelve todos los problemas mencionados y algunos que no se mencionaron aquí. Tipo de seguridad, incompatibilidad en tiempo de compilación de matrices, sin aritmética de puntero, sin conversión de tipo, sin copia de datos. Funciona para C y C ++.
OpalApps
Bueno, esto funciona para C ++; C no admite plantillas. Hacerlo en C requeriría macros.
Gunnar
20

Puede crear una plantilla de función como esta:

template<int R, int C>
void myFunction(double (&myArray)[R][C])
{
    myArray[x][y] = 5;
    etc...
}

Luego tiene ambos tamaños de dimensión a través de R y C. Se creará una función diferente para cada tamaño de matriz, por lo que si su función es grande y la llama con una variedad de diferentes tamaños de matriz, esto puede ser costoso. Sin embargo, podría usarlo como envoltorio sobre una función como esta:

void myFunction(double * arr, int R, int C)
{
    arr[x * C + y] = 5;
    etc...
}

Trata la matriz como unidimensional y utiliza la aritmética para calcular los desplazamientos de los índices. En este caso, definiría la plantilla de esta manera:

template<int C, int R>
void myFunction(double (&myArray)[R][C])
{
    myFunction(*myArray, R, C);
}
Benjamin Lindley
fuente
2
size_tes el mejor tipo para los índices de matriz que int.
Andrew Tomazos
13

anArray[10][10]no es un puntero a un puntero, es una porción contigua de memoria adecuada para almacenar 100 valores de tipo double, que el compilador sabe cómo direccionar porque especificó las dimensiones. Debe pasarlo a una función como una matriz. Puede omitir el tamaño de la dimensión inicial, de la siguiente manera:

void f(double p[][10]) {
}

Sin embargo, esto no le permitirá pasar matrices con la última dimensión que no sea diez.

La mejor solución en C ++ es usar std::vector<std::vector<double> >: es casi tan eficiente y significativamente más conveniente.

dasblinkenlight
fuente
1
Prefiero esta solución ya que la biblioteca estándar es muy eficiente, por cierto me gusta dasblinkenlight; Solía ​​usar dasblikenlicht
mozillanerd
¿Casi tan eficiente? Sí claro. La búsqueda de punteros siempre es más costosa que la búsqueda sin punteros.
Thomas Eding
8

La matriz unidimensional decae a un puntero que apunta al primer elemento de la matriz. Mientras que una matriz 2D decae a un puntero que apunta a la primera fila. Entonces, el prototipo de la función debería ser:

void myFunction(double (*myArray) [10]);

Preferiría std::vectorsobre las matrices en bruto.

Mahesh
fuente
8

Puedes hacer algo como esto ...

#include<iostream>

using namespace std;

//for changing values in 2D array
void myFunc(double *a,int rows,int cols){
    for(int i=0;i<rows;i++){
        for(int j=0;j<cols;j++){
            *(a+ i*rows + j)+=10.0;
        }
    }
}

//for printing 2D array,similar to myFunc
void printArray(double *a,int rows,int cols){
    cout<<"Printing your array...\n";
    for(int i=0;i<rows;i++){
        for(int j=0;j<cols;j++){
            cout<<*(a+ i*rows + j)<<"  ";
        }
    cout<<"\n";
    }
}

int main(){
    //declare and initialize your array
    double a[2][2]={{1.5 , 2.5},{3.5 , 4.5}};

    //the 1st argument is the address of the first row i.e
    //the first 1D array
    //the 2nd argument is the no of rows of your array
    //the 3rd argument is the no of columns of your array
    myFunc(a[0],2,2);

    //same way as myFunc
    printArray(a[0],2,2);

    return 0;
}

Su salida será la siguiente ...

11.5  12.5
13.5  14.5
Sagar Shah
fuente
1
La única razón por la que puedo pensar por qué uno destrozaría la matriz en este caso, es porque uno carece de conocimiento sobre cómo funcionan los punteros de la matriz.
Lundin
3
la variable i debe multiplicarse por columnas, no por filas, a menos que las columnas y las filas sean iguales como en este caso
Andrey Chernukha
4

Aquí hay un ejemplo de matriz de vectores de vectores

#include <iostream>
#include <vector>
using namespace std;

typedef vector< vector<int> > Matrix;

void print(Matrix& m)
{
   int M=m.size();
   int N=m[0].size();
   for(int i=0; i<M; i++) {
      for(int j=0; j<N; j++)
         cout << m[i][j] << " ";
      cout << endl;
   }
   cout << endl;
}


int main()
{
    Matrix m = { {1,2,3,4},
                 {5,6,7,8},
                 {9,1,2,3} };
    print(m);

    //To initialize a 3 x 4 matrix with 0:
    Matrix n( 3,vector<int>(4,0));
    print(n);
    return 0;
}

salida:

1 2 3 4
5 6 7 8
9 1 2 3

0 0 0 0
0 0 0 0
0 0 0 0
edW
fuente
2

Podemos usar varias formas de pasar una matriz 2D a una función:

  • Usando un puntero único , tenemos que encasillar la matriz 2D.

    #include<bits/stdc++.h>
    using namespace std;
    
    
    void func(int *arr, int m, int n)
    {
        for (int i=0; i<m; i++)
        {
           for (int j=0; j<n; j++)
           {
              cout<<*((arr+i*n) + j)<<" ";
           }
           cout<<endl;
        }
    }
    
    int main()
    {
        int m = 3, n = 3;
        int arr[m][n] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
        func((int *)arr, m, n);
        return 0;
    }
  • Usando doble puntero De esta manera, también escribimos la matriz 2D

    #include<bits/stdc++.h>
    using namespace std;

   void func(int **arr, int row, int col)
   {
      for (int i=0; i<row; i++)
      {
         for(int j=0 ; j<col; j++)
         {
           cout<<arr[i][j]<<" ";
         }
         printf("\n");
      }
   }

  int main()
  {
     int row, colum;
     cin>>row>>colum;
     int** arr = new int*[row];

     for(int i=0; i<row; i++)
     {
        arr[i] = new int[colum];
     }

     for(int i=0; i<row; i++)
     {
         for(int j=0; j<colum; j++)
         {
            cin>>arr[i][j];
         }
     }
     func(arr, row, colum);

     return 0;
   }
rashedcs
fuente
1

Una cosa importante para pasar matrices multidimensionales es:

  • First array dimension No es necesario especificarlo.
  • Second(any any further)dimension debe especificarse

1.Cuando solo la segunda dimensión está disponible globalmente (ya sea como macro o como constante global)

`const int N = 3;

`void print(int arr[][N], int m)
{
int i, j;
for (i = 0; i < m; i++)
  for (j = 0; j < N; j++)
    printf("%d ", arr[i][j]);
}`

int main()
{
int arr[][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
print(arr, 3);
return 0;
}`

2.Utilizando un puntero único : en este método, debemos escribir la matriz 2D al pasar a la función.

`void print(int *arr, int m, int n)
{
int i, j;
for (i = 0; i < m; i++)
  for (j = 0; j < n; j++)
    printf("%d ", *((arr+i*n) + j));
 }

`int main()
{
int arr[][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
int m = 3, n = 3;

// We can also use "print(&arr[0][0], m, n);"
print((int *)arr, m, n);
return 0;
}`
sonoro
fuente
0

Puede usar la facilidad de plantilla en C ++ para hacer esto. Hice algo como esto:

template<typename T, size_t col>
T process(T a[][col], size_t row) {
...
}

El problema con este enfoque es que por cada valor de col que proporcione, se crea una nueva definición de función utilizando la plantilla. entonces,

int some_mat[3][3], another_mat[4,5];
process(some_mat, 3);
process(another_mat, 4);

crea una instancia de la plantilla dos veces para producir 2 definiciones de funciones (una donde col = 3 y otra donde col = 5).

vantony
fuente
0

Si desea pasar int a[2][3]a void func(int** pp)lo que necesita medidas auxiliares de la siguiente manera.

int a[2][3];
int* p[2] = {a[0],a[1]};
int** pp = p;

func(pp);

Como el primero [2]puede especificarse implícitamente, puede simplificarse aún más como.

int a[][3];
int* p[] = {a[0],a[1]};
int** pp = p;

func(pp);
Programador Orientado al Dinero
fuente
0

En el caso de que desee pasar una matriz 2D de tamaño dinámico a una función, utilizar algunos punteros podría funcionar para usted.

void func1(int *arr, int n, int m){
    ...
    int i_j_the_element = arr[i * m + j];  // use the idiom of i * m + j for arr[i][j] 
    ...
}

void func2(){
    ...
    int arr[n][m];
    ...
    func1(&(arr[0][0]), n, m);
}
Purusharth Verma
fuente
0

Se le permite omitir la dimensión más a la izquierda, por lo que termina con dos opciones:

void f1(double a[][2][3]) { ... }

void f2(double (*a)[2][3]) { ... }

double a[1][2][3];

f1(a); // ok
f2(a); // ok 

Esto es lo mismo con los punteros:

// compilation error: cannot convert ‘double (*)[2][3]’ to ‘double***’ 
// double ***p1 = a;

// compilation error: cannot convert ‘double (*)[2][3]’ to ‘double (**)[3]’
// double (**p2)[3] = a;

double (*p3)[2][3] = a; // ok

// compilation error: array of pointers != pointer to array
// double *p4[2][3] = a;

double (*p5)[3] = a[0]; // ok

double *p6 = a[0][1]; // ok

El estándar C ++ permite la descomposición de una matriz de N dimensiones en un puntero a una matriz de dimensiones N-1 , ya que puede perder la dimensión más a la izquierda y aún así poder acceder correctamente a los elementos de la matriz con información de dimensiones N-1.

Detalles aquí

Sin embargo, las matrices y los punteros no son lo mismo : una matriz puede decaer en un puntero, pero un puntero no tiene un estado sobre el tamaño / configuración de los datos a los que apunta.

A char **es un puntero a un bloque de memoria que contiene punteros de caracteres , que a su vez apuntan a bloques de memoria de caracteres. A char [][]es un bloque de memoria único que contiene caracteres. Esto tiene un impacto en cómo el compilador traduce el código y cómo será el rendimiento final.

Fuente

luca
fuente