Passer un tableau 2D à une fonction C ++


324

J'ai une fonction que je veux prendre, en paramètre, un tableau 2D de taille variable.

Jusqu'à présent, j'ai ceci:

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

Et j'ai déclaré un tableau ailleurs dans mon code:

double anArray[10][10];

Cependant, appeler myFunction(anArray)me donne une erreur.

Je ne veux pas copier le tableau lorsque je le passe. Toute modification apportée à myFunctiondevrait modifier l'état de anArray. Si je comprends bien, je veux seulement passer en argument un pointeur vers un tableau 2D. La fonction doit également accepter des tableaux de différentes tailles. Ainsi, par exemple, [10][10]et [5][5]. Comment puis-je faire ceci?


1
ne peut pas convertir le paramètre 3 de «double [10] [10]» en «double **»
RogerDarwin

3
La réponse acceptée ne montre que 2 techniques [ses (2) et (3) sont les mêmes] mais il existe 4 façons uniques de passer un tableau 2D à une fonction .
legends2k

Strictement parlant, oui, ce ne sont pas des tableaux 2D, mais cette convention (bien que menant à UB) d'avoir un tableau de pointeurs, chacun pointant vers un tableau (1D), semble être courante :( Avoir un tableau 1D aplati de mxn la longueur, avec des fonctions d'assistance / classe pour émuler un tableau 2D est peut-être mieux.
legends2k

PLUS FACILE - 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)); }. Appelez-le commeint mat[3][5]; func(mat[0], 3, 5);
Minhas Kamal

Réponses:


413

Il existe trois façons de passer un tableau 2D à une fonction:

  1. Le paramètre est un tableau 2D

    int array[10][10];
    void passFunc(int a[][10])
    {
        // ...
    }
    passFunc(array);
  2. Le paramètre est un tableau contenant des pointeurs

    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. Le paramètre est un pointeur sur un pointeur

    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 Vous pouvez obtenir les éléments de arrayavec array[i][j]:)
shengy

14
Pour le 1er cas, le paramètre peut être déclaré comme int (*a)[10].
Zachary

9
Pour le 2ème cas, le paramètre peut être déclaré comme int **.
Zachary

1
@Zack: Tu as raison, il n'y a vraiment que deux cas; l'un est un pointeur vers pointeur et l'autre étant un pointeur unique vers un tableau entier de taille n c'est-à-dire int (*a) [10].
legends2k

3
Les cas 2 et 3 ne sont pas des tableaux 2D, donc cette réponse est trompeuse. Regardez ça .
Lundin

178

Taille fixe

1. Passer par référence

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 ++, passer le tableau par référence sans perdre les informations de dimension est probablement le plus sûr, car il n'est pas nécessaire de se soucier que l'appelant passe une dimension incorrecte (drapeaux du compilateur en cas de non-correspondance). Cependant, cela n'est pas possible avec les tableaux dynamiques (magasin libre); cela ne fonctionne que pour les tableaux automatiques ( généralement empilés ), c'est-à-dire que la dimensionnalité doit être connue au moment de la compilation.

2. Passer par le pointeur

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

L'équivalent C de la méthode précédente passe le tableau par pointeur. Cela ne doit pas être confondu avec le passage par le type de pointeur pourri du tableau (3) , qui est la méthode courante et populaire, bien que moins sûre que celle-ci mais plus flexible. Comme (1) , utilisez cette méthode lorsque toutes les dimensions du tableau sont fixes et connues au moment de la compilation. Notez que lors de l'appel de la fonction, l'adresse du tableau doit être transmise process_2d_array_pointer(&a)et non l'adresse du premier élément par décroissance process_2d_array_pointer(a).

Taille variable

Celles-ci sont héritées de C mais sont moins sûres, le compilateur n'a aucun moyen de vérifier, garantissant que l'appelant passe les dimensions requises. La fonction ne dépend que de ce que l'appelant passe comme dimension (s). Celles-ci sont plus flexibles que les précédentes, car des tableaux de différentes longueurs peuvent leur être transmis invariablement.

Il faut se rappeler qu'il n'y a rien de tel que de passer un tableau directement à une fonction en C [tandis qu'en C ++, ils peuvent être passés comme référence (1) ]; (2) transmet un pointeur au tableau et non au tableau lui-même. Toujours passer un tableau tel quel devient une opération de copie de pointeur qui est facilitée par la nature du tableau de se désintégrer en un pointeur .

3. Passez (valeur) un pointeur sur le type pourri

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

Bien que cela int array[][10]soit autorisé, je ne le recommanderais pas par-dessus la syntaxe ci-dessus, car la syntaxe ci-dessus indique clairement que l'identifiant arrayest un pointeur unique vers un tableau de 10 entiers, tandis que cette syntaxe ressemble à un tableau 2D mais est le même pointeur vers un tableau de 10 entiers. Ici, nous connaissons le nombre d'éléments dans une seule ligne (c'est-à-dire la taille de la colonne, 10 ici) mais le nombre de lignes est inconnu et doit donc être passé en argument. Dans ce cas, il y a une certaine sécurité car le compilateur peut signaler lorsqu'un pointeur vers un tableau avec une deuxième dimension non égale à 10 est passé. La première dimension est la partie variable et peut être omise. Voir ici pour la raison pour laquelle seule la première dimension peut être omise.

4. Passer un pointeur à un pointeur

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

Encore une fois, il existe une syntaxe alternative int *array[10]qui est la même que int **array. Dans cette syntaxe, le [10]est ignoré car il se désintègre en un pointeur devenant ainsi int **array. C'est peut-être juste un signal à l'appelant que le tableau passé doit avoir au moins 10 colonnes, même si le nombre de lignes est requis. Dans tous les cas, le compilateur ne signale aucune violation de longueur / taille (il vérifie uniquement si le type transmis est un pointeur vers un pointeur), nécessitant donc à la fois le nombre de lignes et de colonnes comme paramètre est logique ici.

Remarque: (4) est l'option la moins sûre car elle ne comporte quasiment aucun contrôle de type et est la plus gênante. On ne peut légitimement passer un tableau 2D à cette fonction; C-FAQ condamne la solution de contournement habituelle int x[5][10]; process_pointer_2_pointer((int**)&x[0][0], 5, 10);car cela peut potentiellement conduire à un comportement indéfini en raison de l'aplatissement de la baie. La bonne façon de passer un tableau dans cette méthode nous amène à la partie gênante, c'est-à-dire que nous avons besoin d'un tableau supplémentaire (de substitution) de pointeurs avec chacun de ses éléments pointant vers la ligne respective du tableau réel à passer; ce substitut est ensuite transmis à la fonction (voir ci-dessous); tout cela pour faire le même travail que les méthodes ci-dessus qui sont plus sûres, plus propres et peut-être plus rapides.

Voici un programme pilote pour tester les fonctions ci-dessus:

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

Qu'en est-il de la transmission de tableaux alloués dynamiquement aux fonctions en C ++? Dans la norme C11, cela peut être fait pour des tableaux alloués statiquement et dynamiquement comme ce fn (int col, int row, int array [col] [row]): stackoverflow.com/questions/16004668/… J'ai posé la question de ce problème : stackoverflow.com/questions/27457076/…
42n4

@ 42n4 Le cas 4 couvre (pour C ++ également) cela. Pour les tableaux alloués dynamiquement, à la ligne dans la boucle passerait de b[i] = a[i];, disons, b[i] = new int[10];. On peut également faire balloué dynamiquement int **b = int *[5];et cela fonctionnera toujours tel quel.
legends2k

1
Comment l'adressage array[i][j]fonctionne-t-il dans la fonction en 4) ? Parce qu'il a reçu ptr en ptr et ne connaît pas la valeur de la dernière dimension, qui est nécessaire pour effectuer un décalage pour un adressage correct?
user1234567

2
array[i][j]est simplement l'arithmétique du pointeur, c'est-à-dire à la valeur du pointeur array, il ajouterait iet déréférencerait le résultat comme int*, auquel il ajouterait jet déréférencerait cet emplacement, en lisant un int. Donc, non, il n'a pas besoin de connaître de dimension pour cela. Mais c'est tout l'intérêt! Le compilateur prend la parole du programmeur avec foi et si le programmeur était incorrect, un comportement indéfini s'ensuit. C'est la raison pour laquelle j'ai mentionné que le cas 4 est l'option la moins sûre.
legends2k

Dans de tels cas, une structure peut bien vous servir.
Xofo

40

Une modification de la première suggestion de shengy, vous pouvez utiliser des modèles pour que la fonction accepte une variable de tableau multidimensionnel (au lieu de stocker un tableau de pointeurs qui doivent être gérés et supprimés):

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

Les instructions print sont là pour montrer que les tableaux sont passés par référence (en affichant les adresses des variables)


2
Vous devez utiliser %ppour imprimer un pointeur, et même dans ce cas, vous devez le convertir en void *, sinon, printf()invoque un comportement non défini. De plus, vous ne devez pas utiliser l' &opérateur addressof ( ) lors de l'appel des fonctions, car les fonctions attendent un argument de type double (*)[size_y], alors que vous leur passez actuellement double (*)[10][10]et double (*)[5][5].

Si vous utilisez des modèles faisant les deux dimensions comme arguments de modèle, c'est plus approprié et mieux car l'accès au pointeur de bas niveau peut être complètement évité.
legends2k

3
Cela ne fonctionne que si la taille du tableau est connue au moment de la compilation.
jeb_is_a_mess

@Georg Code ci-dessus en réponse est exactement ce que j'avais suggéré. Cela fonctionne dans GCC 6.3 - démo en ligne . Avez-vous oublié de faire du paramètre une référence?
legends2k

21

Surpris que personne ne l'ait encore mentionné, mais vous pouvez simplement créer un modèle sur tout ce qui prend en charge la sémantique [] [] 2D.

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

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

Il fonctionne avec n'importe quelle infrastructure de données 2D de type tableau, telle que std::vector<std::vector<T>>, ou un type défini par l'utilisateur pour maximiser la réutilisation du code.


1
Ce devrait être la bonne réponse. Il résout tous les problèmes mentionnés et certains qui n'étaient pas mentionnés ici. Sécurité de type, incompatibilité de compilation des tableaux, pas d'arithmétique de pointeur, pas de conversion de type, pas de copie de données. Fonctionne pour C et C ++.
OpalApps

Eh bien, cela fonctionne pour C ++; C ne prend pas en charge les modèles. Le faire en C nécessiterait des macros.
Gunnar

20

Vous pouvez créer un modèle de fonction comme celui-ci:

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

Ensuite, vous avez les deux tailles de dimension via R et C. Une fonction différente sera créée pour chaque taille de tableau, donc si votre fonction est grande et que vous l'appelez avec une variété de tailles de tableau différentes, cela peut être coûteux. Vous pouvez cependant l'utiliser comme wrapper sur une fonction comme celle-ci:

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

Il traite le tableau comme unidimensionnel et utilise l'arithmétique pour déterminer les décalages des index. Dans ce cas, vous définiriez le modèle comme ceci:

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

2
size_test le meilleur type pour les index de tableau que int.
Andrew Tomazos

13

anArray[10][10]n'est pas un pointeur vers un pointeur, c'est un morceau de mémoire contigu approprié pour stocker 100 valeurs de type double, que le compilateur sait comment traiter car vous avez spécifié les dimensions. Vous devez le passer à une fonction sous forme de tableau. Vous pouvez omettre la taille de la dimension initiale, comme suit:

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

Cependant, cela ne vous permettra pas de passer des tableaux avec la dernière dimension autre que dix.

La meilleure solution en C ++ est d'utiliser std::vector<std::vector<double> >: elle est presque aussi efficace et nettement plus pratique.


1
Je préfère cette solution car la bibliothèque std est très efficace - par la façon dont j'aime dasblinkenlight; J'ai utilisé dasblikenlicht
mozillanerd le

Presque aussi efficace? Oui en effet. La chasse au pointeur est toujours plus chère que la chasse sans pointeur.
Thomas Eding,

8

Le tableau unidimensionnel se désintègre en un pointeur pointeur pointant vers le premier élément du tableau. Alors qu'un tableau 2D se désintègre en un pointeur pointant vers la première ligne. Ainsi, le prototype de fonction devrait être -

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

Je préfère std::vectorles tableaux bruts.


8

Vous pouvez faire quelque chose comme ça ...

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

Votre sortie sera la suivante ...

11.5  12.5
13.5  14.5

1
La seule raison pour laquelle je peux trouver la raison pour laquelle on manipulerait le tableau dans ce cas, c'est parce que l'on manque de connaissances sur le fonctionnement des pointeurs de tableau.
Lundin

3
la variable i doit être multipliée par des colonnes, et non par des lignes, sauf si les colonnes et les lignes sont égales comme dans ce cas
Andrey Chernukha

4

Voici un exemple de matrice de vecteurs

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

production:

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

Nous pouvons utiliser plusieurs façons de passer un tableau 2D à une fonction:

  • En utilisant un seul pointeur, nous devons transtyper le tableau 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;
    }
  • Utilisation du pointeur double De cette façon, nous avons également transtypé le tableau 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

Une chose importante pour passer des tableaux multidimensionnels est:

  • First array dimension n'a pas besoin d'être spécifié.
  • Second(any any further)dimension doit être spécifié.

1.Lorsque seule la deuxième dimension est disponible à l'échelle mondiale (sous forme de macro ou de constante globale)

`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.Utilisation d'un seul pointeur : Dans cette méthode, nous devons transtyper le tableau 2D lors du passage à la fonction.

`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

Vous pouvez utiliser la fonction de modèle en C ++ pour ce faire. J'ai fait quelque chose comme ça:

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

le problème avec cette approche est que pour chaque valeur de col que vous fournissez, la nouvelle définition d'une fonction est instanciée à l'aide du modèle. alors,

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

instancie le modèle deux fois pour produire 2 définitions de fonction (une où col = 3 et une où col = 5).


0

Si vous voulez passer int a[2][3]à void func(int** pp)vous avez besoin d'étapes auxiliaires comme suit.

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

func(pp);

Comme le premier [2]peut être implicitement spécifié, il peut être encore simplifié au fur et à mesure.

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

func(pp);

0

Dans le cas où vous souhaitez passer un tableau 2D de taille dynamique à une fonction, l'utilisation de certains pointeurs pourrait fonctionner pour vous.

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

Vous êtes autorisé à omettre la dimension la plus à gauche et vous vous retrouvez donc avec deux options:

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

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

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

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

C'est la même chose avec les pointeurs:

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

La désintégration d'un tableau à N dimensions vers un pointeur vers un tableau à N-1 dimensions est autorisée par la norme C ++ , car vous pouvez perdre la dimension la plus à gauche et toujours pouvoir accéder correctement aux éléments du tableau avec les informations de dimension N-1.

Détails ici

Cependant, les tableaux et les pointeurs ne sont pas les mêmes : un tableau peut se désintégrer en un pointeur, mais un pointeur ne porte pas d'état sur la taille / configuration des données vers lesquelles il pointe.

A char **est un pointeur vers un bloc de mémoire contenant des pointeurs de caractères , qui pointent eux-mêmes vers des blocs de mémoire de caractères. A char [][]est un bloc de mémoire unique qui contient des caractères. Cela a un impact sur la façon dont le compilateur traduit le code et sur les performances finales.

La source

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.