Passando uma matriz 2D para uma função C ++


324

Eu tenho uma função que eu quero tomar, como parâmetro, uma matriz 2D de tamanho variável.

Até agora eu tenho o seguinte:

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

E eu declarei uma matriz em outro lugar no meu código:

double anArray[10][10];

No entanto, chamar myFunction(anArray)me dá um erro.

Não quero copiar a matriz quando a transmitir. Todas as alterações feitas myFunctiondevem alterar o estado de anArray. Se entendi corretamente, só quero passar como argumento um ponteiro para uma matriz 2D. A função também precisa aceitar matrizes de tamanhos diferentes. Então, por exemplo, [10][10]e [5][5]. Como posso fazer isso?


1
não é possível converter o parâmetro 3 de 'double [10] [10]' para 'double **'
RogerDarwin

3
A resposta aceita mostra apenas duas técnicas [its (2) e (3) são as mesmas], mas existem 4 maneiras exclusivas de passar um array 2D para uma função .
legends2k

Estritamente falando, sim, eles não são matrizes 2D, mas essa convenção (embora leve ao UB) de ter uma matriz de ponteiros, cada uma apontando para uma matriz (1D), parece prevalecer :( Ter uma matriz 1D achatada de mxn comprimento, com funções auxiliares / classe para emular uma matriz 2D é talvez melhor.
legends2k

MAIS 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)); }. Chame-o como-int mat[3][5]; func(mat[0], 3, 5);
Minhas Kamal

Respostas:


413

Existem três maneiras de passar uma matriz 2D para uma função:

  1. O parâmetro é uma matriz 2D

    int array[10][10];
    void passFunc(int a[][10])
    {
        // ...
    }
    passFunc(array);
  2. O parâmetro é uma matriz que contém ponteiros

    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. O parâmetro é um ponteiro para um ponteiro

    int **array;
    array = new int *[10];
    for(int i = 0; i <10; i++)
        array[i] = new int[10];
    void passFunc(int **a)
    {
        // ...
    }
    passFunc(array);

4
@Overflowh Você pode obter os elementos de arraycom array[i][j]:)
shengy

14
Para o 1º caso, o parâmetro pode ser declarado como int (*a)[10].
precisa

9
Para o segundo caso, o parâmetro pode ser declarado como int **.
precisa

1
@Zack: Você está certo, existem apenas dois casos; um é um ponteiro para o ponteiro e outro é um ponteiro único para uma matriz inteira de tamanho n ie int (*a) [10].
legends2k

3
Os casos 2 e 3 não são matrizes 2D, portanto, essa resposta é enganosa. Veja isso .
Lundin

178

Tamanho fixo

1. Passe por referência

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

No C ++, passar a matriz por referência sem perder as informações da dimensão é provavelmente o mais seguro, pois não é necessário se preocupar com o chamador passando uma dimensão incorreta (sinalizadores do compilador quando não correspondem). No entanto, isso não é possível com matrizes dinâmicas (armazenamento livre); ele funciona apenas para matrizes automáticas ( geralmente com vida útil da pilha ), ou seja, a dimensionalidade deve ser conhecida no momento da compilação.

2. Passe pelo ponteiro

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

O equivalente em C do método anterior está passando a matriz por ponteiro. Isso não deve ser confundido com a passagem pelo tipo de ponteiro deteriorado da matriz (3) , que é o método comum e popular, embora menos seguro que esse, mas mais flexível. Como (1) , use esse método quando todas as dimensões da matriz forem fixas e conhecidas em tempo de compilação. Observe que, ao chamar a função, o endereço da matriz deve ser passado process_2d_array_pointer(&a)e não o endereço do primeiro elemento por deterioração process_2d_array_pointer(a).

Tamanho variável

Eles são herdados de C, mas são menos seguros, o compilador não tem como verificar, garantindo que o chamador esteja passando as dimensões necessárias. A função baseia-se apenas no que o chamador passa como as dimensões. Elas são mais flexíveis do que as anteriores, já que matrizes de diferentes comprimentos podem ser passadas para elas invariavelmente.

Deve-se lembrar que não existe uma passagem direta de uma matriz para uma função em C [enquanto em C ++ elas podem ser passadas como referência (1) ]; (2) está passando um ponteiro para a matriz e não a própria matriz. Sempre passar uma matriz como está se torna uma operação de cópia de ponteiro, facilitada pela natureza da matriz de decair em um ponteiro .

3. Passe por (valor) um ponteiro para o tipo deteriorado

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

Embora int array[][10]seja permitido, eu não recomendaria a sintaxe acima, pois a sintaxe acima deixa claro que o identificador arrayé um ponteiro único para uma matriz de 10 números inteiros, enquanto essa sintaxe parece uma matriz 2D, mas é o mesmo ponteiro para uma matriz de 10 números inteiros. Aqui sabemos o número de elementos em uma única linha (ou seja, o tamanho da coluna, 10 aqui), mas o número de linhas é desconhecido e, portanto, deve ser passado como argumento. Nesse caso, há alguma segurança, pois o compilador pode sinalizar quando um ponteiro para uma matriz com segunda dimensão diferente de 10 é passado. A primeira dimensão é a parte variável e pode ser omitida. Veja aqui a justificativa de por que somente a primeira dimensão pode ser omitida.

4. Passe o ponteiro para um ponteiro

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

Novamente, há uma sintaxe alternativa int *array[10]da mesma int **array. Nesta sintaxe, o [10]é ignorado quando se decompõe em um ponteiro, tornando-se assim int **array. Talvez seja apenas uma sugestão para o chamador que a matriz passada tenha pelo menos 10 colunas; mesmo assim, a contagem de linhas é necessária. De qualquer forma, o compilador não sinaliza violações de tamanho / tamanho (apenas verifica se o tipo passado é um ponteiro para o ponteiro), exigindo, portanto, a contagem de linhas e colunas, pois o parâmetro faz sentido aqui.

Nota: (4) é a opção menos segura, pois quase não possui verificação de tipo e é a mais inconveniente. Não se pode legitimamente passar uma matriz 2D para essa função; O C-FAQ condena a solução usual de fazer int x[5][10]; process_pointer_2_pointer((int**)&x[0][0], 5, 10);, pois pode levar a um comportamento indefinido devido ao achatamento da matriz. A maneira correta de passar uma matriz nesse método nos leva à parte inconveniente, ou seja, precisamos de uma matriz adicional (substituta) de ponteiros, com cada um de seus elementos apontando para a respectiva linha da matriz real a ser passada; esse substituto é então passado para a função (veja abaixo); tudo isso para realizar o mesmo trabalho que os métodos acima, mais seguros, limpos e talvez mais rápidos.

Aqui está um programa de driver para testar as funções acima:

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

Que tal passar matrizes alocadas dinamicamente para funções em C ++? No padrão C11, isso pode ser feito para matrizes alocadas estaticamente e dinamicamente, como fn (int col, int row, int array [col] [row]): stackoverflow.com/questions/16004668/… Fiz a pergunta para esse problema : stackoverflow.com/questions/27457076/…
42n4

@ 42n4 O Caso 4 cobre (também para C ++) isso. Para matrizes alocadas dinamicamente, apenas a linha dentro do loop mudaria de b[i] = a[i];para, digamos b[i] = new int[10];,. Pode-se também fazer balocados dinamicamente int **b = int *[5];e ainda funcionará como está.
legends2k

1
Como o endereçamento array[i][j]funciona na função em 4) ? Porque ele recebeu ptr para ptr e não sabe o valor da última dimensão, o que é necessário para executar uma mudança para o endereçamento correto?
user1234567

2
array[i][j]é apenas aritmética do ponteiro, ou seja, para o valor do ponteiro array, ele adiciona ie desreferencia o resultado como int*, ao qual ele adiciona je desreferencia esse local, lendo um int. Portanto, não, ele não precisa conhecer nenhuma dimensão para isso. Mas, esse é o ponto! O compilador leva a palavra do programador em fé e, se o programador estiver incorreto, o comportamento indefinido se seguirá. Essa é a razão pela qual eu mencionei que o caso 4 é a opção menos segura.
legends2k

Nesses casos, uma estrutura pode servi-lo bem.
Xofo 16/11/18

40

Uma modificação na primeira sugestão do shengy, você pode usar modelos para fazer a função aceitar uma variável de matriz multidimensional (em vez de armazenar uma matriz de ponteiros que precisam ser gerenciados e excluídos):

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

As instruções print estão lá para mostrar que as matrizes estão sendo passadas por referência (exibindo os endereços das variáveis)


2
Você deve usar %ppara imprimir um ponteiro e, mesmo assim, deve convertê-lo void *, caso contrário, printf()invoca um comportamento indefinido. Além disso, você não deve usar o &operador addressof ( ) ao chamar as funções, pois as funções esperam um argumento do tipo double (*)[size_y], enquanto você as passa double (*)[10][10]e double (*)[5][5].

Se você estiver usando modelos, criar as duas dimensões como argumentos do modelo é mais apropriado e melhor, pois o acesso ao ponteiro de baixo nível pode ser completamente evitado.
legends2k

3
Isso funciona apenas se o tamanho da matriz for conhecido em tempo de compilação.
jeb_is_a_mess

@ O código Georg acima na resposta é exatamente o que eu sugeri. Funciona no GCC 6.3 - demonstração online . Você esqueceu de fazer o parâmetro uma referência?
Legends2k

21

Surpreendeu-se que ninguém tenha mencionado isso ainda, mas você pode simplesmente usar o modelo em qualquer coisa que suporte 2D [] [] semântica.

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

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

Ele funciona com qualquer estrutura de dados 2D "tipo matriz", como std::vector<std::vector<T>>um tipo definido pelo usuário, para maximizar a reutilização de código.


1
Essa deve ser a resposta certa. Resolve todos os problemas mencionados e alguns que não foram mencionados aqui. Segurança de tipo, incompatibilidade de arrays em tempo de compilação, sem aritmética de ponteiro, sem conversão de tipo, sem cópia de dados. Funciona para C e C ++.
OpalApps

Bem, isso funciona para C ++; C não suporta modelos. Fazer isso em C exigiria macros.
Gunnar

20

Você pode criar um modelo de função como este:

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

Então você tem os dois tamanhos de dimensão via R e C. Uma função diferente será criada para cada tamanho de matriz, portanto, se sua função for grande e você a chamar com uma variedade de tamanhos de matriz diferentes, isso poderá ser caro. Você pode usá-lo como um invólucro sobre uma função como esta:

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

Ele trata o array como unidimensional e usa aritmética para descobrir as compensações dos índices. Nesse caso, você definiria o modelo assim:

template<int C, int R>
void myFunction(double (&myArray)[R][C])
{
    myFunction(*myArray, R, C);
}

2
size_té o melhor tipo para índices de matriz do que int.
Andrew Tomazos

13

anArray[10][10]não é um ponteiro para um ponteiro, é um pedaço de memória contíguo adequado para armazenar 100 valores do tipo double, que o compilador sabe endereçar porque você especificou as dimensões. Você precisa passá-lo para uma função como uma matriz. Você pode omitir o tamanho da dimensão inicial, da seguinte maneira:

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

No entanto, isso não permitirá que você passe matrizes com a última dimensão diferente de dez.

A melhor solução em C ++ é usar std::vector<std::vector<double> >: é quase tão eficiente e significativamente mais conveniente.


1
Prefiro essa solução, pois a biblioteca std é muito eficiente - a propósito, gosto do dasblinkenlight; Eu costumava usar dasblikenlicht
mozillanerd

Quase tão eficiente? Okay, certo. A busca por ponteiro é sempre mais cara que a busca sem ponteiro.
Thomas Eding

8

A matriz unidimensional decai para um ponteiro apontando para o primeiro elemento na matriz. Enquanto uma matriz 2D se deteriora para um ponteiro apontando para a primeira linha. Portanto, o protótipo de função deve ser -

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

Eu preferiria std::vectormatrizes brutas.


8

Você pode fazer algo assim ...

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

Sua saída será a seguinte ...

11.5  12.5
13.5  14.5

1
A única razão pela qual posso sugerir por que alguém manipularia a matriz nesse caso é porque falta um conhecimento sobre como os ponteiros da matriz funcionam.
Lundin

3
a variável i deve ser multiplicado por colunas, e não por linhas a menos colunas e linhas são iguais como neste caso
Andrey Chernukha

4

Aqui está um vetor de exemplo de matriz de vetores

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

resultado:

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

0 0 0 0
0 0 0 0
0 0 0 0

2

Podemos usar várias maneiras de passar um array 2D para uma função:

  • Usando o ponteiro único , temos que converter a matriz em 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 o ponteiro duplo Dessa maneira, também criamos a 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;
   }

1

Uma coisa importante para a passagem de matrizes multidimensionais é:

  • First array dimension não precisa ser especificado.
  • Second(any any further)dimension deve ser especificado.

1.Quando apenas a segunda dimensão estiver disponível globalmente (como macro ou 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 um ponteiro único : Neste método, precisamos converter a matriz 2D ao passar para a função.

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

0

Você pode usar o recurso de modelo em C ++ para fazer isso. Eu fiz algo parecido com isto:

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

o problema com essa abordagem é que, para cada valor de col que você fornece, a nova definição de função é instanciada usando o modelo. tão,

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

instancia o modelo duas vezes para produzir 2 definições de função (uma em que col = 3 e outra em que col = 5).


0

Se você deseja passar int a[2][3]para void func(int** pp)você, precisa das etapas auxiliares a seguir.

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

func(pp);

Como o primeiro [2]pode ser especificado implicitamente, pode ser ainda mais simplificado como.

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

func(pp);

0

No caso de você desejar passar uma matriz 2D de tamanho dinâmico para uma função, o uso de alguns ponteiros pode funcionar para você.

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

0

Você tem permissão para omitir a dimensão mais à esquerda e, portanto, acaba com duas opções:

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

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

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

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

O mesmo acontece com os ponteiros:

// 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

A deterioração de uma matriz dimensional N para um ponteiro para a matriz dimensional N-1 é permitida pelo padrão C ++ , pois você pode perder a dimensão mais à esquerda e ainda conseguir acessar corretamente os elementos da matriz com informações de dimensão N-1.

Detalhes aqui

Porém, matrizes e ponteiros não são os mesmos : uma matriz pode se decompor em um ponteiro, mas um ponteiro não transmite estado sobre o tamanho / configuração dos dados para os quais aponta.

A char **é um ponteiro para um bloco de memória contendo ponteiros de caracteres , os quais apontam para blocos de caracteres de memória. A char [][]é um único bloco de memória que contém caracteres. Isso afeta a forma como o compilador traduz o código e como será o desempenho final.

Fonte

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.