447 votes

Transmettre un tableau 2D à une fonction C++

J'ai une fonction qui doit prendre, comme paramètre, un tableau 2D de taille variable.

Pour l'instant, 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 à myFunction devrait 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. Donc, par exemple, [10][10] y [5][5] . Comment puis-je faire ?

2 votes

Ne peut pas convertir le paramètre 3 de 'double [10][10]' en 'double **'.

4 votes

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

1 votes

Strictement parlant, oui, ce ne sont pas des tableaux 2D, mais cette convention (bien que menant à l'UB) d'avoir un tableau de pointeurs, chacun pointant vers un tableau (1D), semble être prévalente :( Avoir un tableau 1D aplati de longueur m x n, avec des fonctions/classes d'aide pour émuler un tableau 2D est peut-être mieux.

537voto

shengy Points 2623

Il existe trois façons de transmettre 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 vers 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);

0 votes

Puis-je demander si dans le troisième exemple, pour le tableau (aka matrice) est également disponible la notation array[i][j] ?

5 votes

@Overflowh Vous pouvez obtenir les éléments de array avec array[i][j] :)

21 votes

Dans le premier cas, le paramètre peut être déclaré comme suit int (*a)[10] .

250voto

legends2k Points 6380

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 l'information sur la dimension est probablement le plus sûr, puisqu'il n'y a pas à s'inquiéter que l'appelant passe une dimension incorrecte (le compilateur signale une mauvaise correspondance). Cependant, cela n'est pas possible avec les tableaux dynamiques (freestore) ; cela fonctionne pour les tableaux automatiques ( vivant généralement en pile ) uniquement, c'est-à-dire que la dimensionnalité doit être connue au moment de la compilation.

2. Passage 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 consiste à passer le tableau par pointeur. Ceci ne doit pas être confondu avec le passage par le type de pointeur décomposé du tableau (3) qui est la méthode la plus courante et la plus populaire, bien que moins sûre que celle-ci mais plus flexible. Comme (1) Cette méthode est utilisée 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 passée process_2d_array_pointer(&a) et non l'adresse du premier élément par désintégration process_2d_array_pointer(a) .

Taille variable

Ils sont hérités du C mais sont moins sûrs, le compilateur n'a aucun moyen de vérifier, de garantir que l'appelant passe les dimensions requises. La fonction dépend uniquement de ce que l'appelant transmet comme dimension(s). Elles sont plus flexibles que les précédentes puisque des tableaux de différentes longueurs peuvent leur être transmis de manière invariable.

Il faut se rappeler qu'il n'est pas possible de passer un tableau directement à une fonction en C [alors qu'en C++, on peut le passer comme une référence]. (1) ] ; (2) passe un pointeur sur le tableau et non le tableau lui-même. Transmettre un tableau tel quel devient une opération de type pointeur-copie qui est facilitée par la fonction la nature du tableau de se décomposer en un pointeur .

3. Passez par (valeur) un pointeur vers le type décomposé

// 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 int array[][10] est autorisé, je ne le recommanderais pas par rapport à la syntaxe ci-dessus, car cette dernière indique clairement que l'identifiant array est un pointeur unique vers un tableau de 10 entiers, alors que cette syntaxe regarde comme s'il s'agissait d'un tableau 2D mais c'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é puisque le compilateur peut signaler qu'un pointeur vers un tableau dont la deuxième dimension n'est pas égale à 10 est passé. La première dimension est la partie variable et peut être omise. Voir ici pour le raisonnement sur la raison pour laquelle seule la première dimension est autorisée à être omise.

4. Passer par 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;
    }
}

Là encore, il existe une syntaxe alternative de int *array[10] qui est identique à int **array . Dans cette syntaxe, le [10] est ignorée car elle se transforme en pointeur et devient ainsi int **array . Peut-être est-ce juste une indication à 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 pas les violations de longueur/taille (il vérifie seulement si le type passé est un pointeur vers un pointeur), donc exiger à la fois le nombre de lignes et de colonnes comme paramètre est logique ici.

Note : (4) est l'option la moins sûre puisqu'il n'a pratiquement aucun contrôle de type et qu'il est le plus gênant. On ne peut pas légitimement passer un tableau 2D à cette fonction ; C-FAQ condamne la solution de contournement habituelle consistant à faire int x[5][10]; process_pointer_2_pointer((int**)&x[0][0], 5, 10); comme il peut potentiellement conduire à un comportement non défini en raison de l'aplatissement du réseau. 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 (substitut) de pointeurs dont chaque élément pointe vers la ligne respective du tableau réel à passer ; ce substitut est ensuite passé à 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**
}

0 votes

Qu'en est-il du passage de tableaux alloués dynamiquement à des fonctions en C++ ? Dans le standard C11, cela peut être fait pour les tableaux alloués statiquement et dynamiquement comme ceci fn(int col,int row, int array[col][row]) : stackoverflow.com/questions/16004668/ J'ai fait la question pour ce problème : stackoverflow.com/questions/27457076/

0 votes

@42n4 Le cas 4 couvre (pour C++ également) cela. Pour les tableaux alloués dynamiquement, il suffit de changer la ligne à l'intérieur de la boucle pour passer de b[i] = a[i]; à, disons, b[i] = new int[10]; . On peut aussi faire b alloué dynamiquement int **b = int *[5]; et ça fonctionnera toujours tel quel.

1 votes

Comment le fait d'aborder array[i][j] dans la fonction dans 4) ? Parce qu'il a reçu ptr to 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 ?

49voto

Zrax Points 335

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

Les États imprimés sont là pour montrer que les baies sont obtenir passés par référence (en affichant les adresses des variables)

2 votes

Vous devez utiliser %p pour imprimer un pointeur, et même dans ce cas, vous devez le convertir en void * sinon printf() invoque un comportement non défini. En outre, vous ne devez pas utiliser l'adresseof ( & ) lors de l'appel des fonctions, puisque les fonctions attendent un argument de type double (*)[size_y] alors que vous les passez actuellement double (*)[10][10] y double (*)[5][5] .

0 votes

Si vous utilisez des modèles, il est plus approprié de faire des deux dimensions des arguments de modèle et c'est mieux car l'accès aux pointeurs de bas niveau peut être complètement évité.

6 votes

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

22voto

Benjamin Lindley Points 51005

Vous pouvez créer un modèle de fonction comme ceci :

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

Vous disposez alors des deux dimensions 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 tableaux différentes, cela peut être coûteux. Vous pouvez cependant l'utiliser comme un emballage pour une fonction comme celle-ci :

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

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

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

3 votes

size_t est le meilleur type pour les index de tableaux que int .

14voto

dasblinkenlight Points 264350

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

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

Toutefois, cela ne vous permettra pas de transmettre des tableaux dont la dernière dimension est différente de dix.

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

1 votes

Je préfère cette solution car la bibliothèque std est très efficace - d'ailleurs j'aime bien dasblinkenlight ; j'avais l'habitude d'utiliser dasblikenlicht

1 votes

Presque aussi efficace ? Oui, c'est ça. La chasse aux pointeurs est toujours plus coûteuse que la chasse aux non-pointeurs.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X