web-dev-qa-db-fra.com

Passer un tableau de structures à la fonction C ++

Désolé pour la question noob, je suis juste un peu confus.
Si j'ai un tableau de structures en principal que je veux passer à une fonction:

struct MyStruct{
    int a;
    int b;
    char c;
    mayarray[5];
};  
MyStruct StructArray[10]; 

myFunction(StructArray[])

Passez à ceci à une fonction:

void myFunction(struct MyStruct PassedStruct[])
{
    PassedStruct[0].a = 1;
    PassedStruct[0].b = 2;
    // ... etc
}  

Ma question est la suivante: l'appel de la fonction comme celle-ci modifiera-t-il les données dans StructArray? J'en ai besoin. Serait-ce un appel par renvoi? Je suis un peu confus. Comment le changerais-je pour que lorsque je passe le tableau de structures à la fonction, la fonction modifie le tableau StructArray? J'utilise Visual Studio Btw.
Merci.

18
Nick Sinas
struct MyStruct PassedStruct[]

est surtout une syntaxe alternative pour:

struct MyStruct * PassedStruct

Alors oui, vous accéderez et modifierez la structure d'origine.

Juste un détail à changer, l'appel correct à la fonction n'est pas

myFunction(StructArray[]);

mais:

myFunction(StructArray);

Maintenant, je vais essayer d'expliquer pourquoi j'ai utilisé le mot surtout dans la phrase ci-dessus:

Je vais vous donner un indice sur la différence entre les tableaux et les pointeurs, pourquoi vous ne devriez pas les confondre (même si je ne dirais pas qu'ils ne sont pas liés, bien au contraire), et le problème avec le passage du paramètre MyStruct PassedStruct[] Ci-dessus syntaxe.

Ce n'est pas pour les débutants, et les experts de la norme C++ devraient également éviter de lire ceci (parce que je ne veux pas entrer dans une certaine - ISO Standard_ war lorsque j'entre dans ISO comportement indéfini territoire - aka territoire interdit ).

Commençons par les tableaux:

Imaginez une structure simple:

struct MyStruct{
    int a;
    int b;
    char c;
};  

MyStruct a1[3]; Est la déclaration d'un tableau dont les éléments sont du type de structure ci-dessus. La chose la plus importante que fait le compilateur lors de la définition d'un tableau est de lui allouer de l'espace. Dans notre exemple, il a réservé un espace pour 3 structures. Cet espace réservé peut être sur la pile ou à partir de ressources mémoire globales selon l'emplacement de l'instruction de déclaration.

Vous pouvez également initialiser la structure lors de sa déclaration comme dans:

struct MyStruct a1[3] = {{1, 2}, {3, 4}, {5, 6}};

Notez que dans cet exemple, je n'ai pas initialisé le champ c mais juste a et b. Ceci est permis. Je pourrais également utiliser la syntaxe du désignateur si mon compilateur le prend en charge comme dans:

struct MyStruct a1[3] = {{a:1, b:2}, {a:3, b:4}, {a:5, b:6}};

Maintenant, il existe une autre syntaxe pour définir un tableau en utilisant des backets carrés vides comme dans:

struct MyStruct a2[] = {{1, 2}, {3, 4}, {5, 6}};

Le point ici est que a2 Est un tableau parfaitement normal tout comme a1. La taille du tableau n'est pas implicite, elle est donnée via l'initialiseur: j'ai trois initialiseurs donc j'obtiens un tableau de trois structures.

Je pourrais définir un tableau non initialisé de taille connue avec cette syntaxe. Pour un tableau non initialisé de taille 3, j'aurais:

struct MyStruct a2[] = {{},{},{}};

L'espace est alloué, exactement comme avec la syntaxe précédente, aucun pointeur n'est impliqué ici.

Introduisons un pointeur:

MyStruct * p1;

Il s'agit d'un simple pointeur vers une structure de type MyStruct. Je peux accéder aux champs via la syntaxe habituelle du pointeur p1->a Ou (*p1).a. Il existe également une syntaxe de type tableau pour faire la même chose que ci-dessus p1[0].a. Toujours le même que ci-dessus. N'oubliez pas que p1 [0] est un raccourci pour (*(p1+0)).

Souvenez-vous également de la règle d'arithmétique du pointeur: ajouter 1 à un pointeur signifie ajouter le sizeof l'objet pointé à l'adresse mémoire sous-jacente (ce que vous obtenez lorsque vous utilisez le paramètre de format% p printf). L'arithmétique des pointeurs permet d'accéder à des structures identiques successives. Cela signifie que vous pouvez accéder aux structures par index avec p1[0], p1[2], Etc.

Les limites ne sont pas vérifiées. Ce qui est pointé en mémoire est la responsabilité du programmeur. Oui, je sais que l'ISO dit différemment, mais c'est ce que font tous les compilateurs que j'ai essayés, donc si vous en connaissez un qui ne le fait pas, dites-le moi.

Pour faire quelque chose d'utile avec p1, vous devez le faire pointer sur une structure de type MyStruct. Si vous avez un tableau de telles structures disponibles comme notre a1, Vous pouvez simplement faire p1=a1 Et p1 pointera vers le début du tableau. En d'autres termes, vous auriez pu aussi faire p1=&a1[0]. Il est naturel d'avoir une syntaxe simple disponible car c'est exactement ce pour quoi l'arithmétique des pointeurs est conçue: accéder à des tableaux d'objets similaires.

La beauté de cette convention est qu'elle permet d'unifier complètement la syntaxe d'accès au pointeur et au tableau. La différence n'est visible que par le compilateur:

  • quand il voit p1[0], il sait qu'il doit récupérer le contenu d'une variable dont le nom est p1 et qu'il contiendra l'adresse d'une structure mémoire.

  • quand il voit a1[0], il sait que a1 est une constante qui doit être comprise comme une adresse (pas quelque chose à récupérer en mémoire).

Mais une fois que l'adresse de p1 Ou a1 Est disponible, le traitement est identique.

Une erreur courante est d'écrire p1 = &a1. Si vous le faites, le compilateur vous donnera des mots de quatre lettres. Ok, &a1 Est aussi un pointeur, mais ce que vous obtenez en prenant l'adresse de a1 Est un pointeur vers le tableau entier. Cela signifie que si vous ajoutez 1 à un pointeur de ce type, l'adresse réelle se déplacera par étapes de 3 structures à la fois.

Le type réel d'un pointeur de ce type (appelons-le p2) Serait MyStruct (*p2)[3];. Vous pouvez maintenant écrire p2 = &a1. Si vous voulez accéder à la première structure MyStruct au début du bloc mémoire pointé par p2, Vous devrez écrire quelque chose comme p2[0][0].a Ou (*p2)[0].a ou (*(*p2)).a ou (*p2)->a ou p2[0]->a.

Grâce au système de type et à l'arithmétique des pointeurs, tout cela fait exactement la même chose: récupérer l'adresse contenue dans p2, utiliser cette adresse comme un tableau (une adresse constante connue) comme expliqué ci-dessus.

Vous pouvez maintenant comprendre pourquoi les pointeurs et les tableaux sont des types totalement différents qu'il ne faut pas confondre comme certains pourraient le dire. En clair, les pointeurs sont des variables qui contiennent une adresse, les tableaux sont des adresses constantes. S'il vous plaît ne me tirez pas sur les gourous du C++, oui je sais que ce n'est pas l'histoire complète et que les compilateurs gardent beaucoup d'autres informations avec l'adresse, la taille de l'objet pointu (adressé?) Par exemple.

Maintenant, vous pouvez vous demander pourquoi, dans le contexte de passage de paramètres, vous pouvez utiliser des crochets vides et cela signifie vraiment pointeur. ? Aucune idée. Quelqu'un a probablement pensé que ça avait l'air bien.

À propos, au moins avec gcc, vous pouvez également mettre une valeur entre crochets au lieu de les garder vides. Cela ne fera aucune différence, vous obtiendrez toujours un pointeur, pas un tableau, et les limites ou la vérification de type ne sont pas effectuées. Je n'ai pas vérifié si la norme ISO devait être effectuée et si elle est requise par la norme ou s'il s'agit d'un comportement spécifique.

Si vous souhaitez vérifier le type des limites, utilisez simplement une référence. Cela peut être surprenant, mais c'est un domaine où si vous utilisez une référence, le type réel du paramètre est changé de pointeur en tableau (et non de pointeur en référence en pointeur comme on peut s'y attendre).

MyStruct StructArray[10]; 
  • en-tête: void myFunction(struct MyStruct * PassedStruct)
  • appelant: myFunction(StructArray)
  • status: fonctionne, vous travaillez avec un pointeur dans PassedStruct
  • en-tête: void myFunction(struct MyStruct PassedStruct[])
  • appelant: myFunction(StructArray)
  • status: fonctionne, vous travaillez avec un pointeur dans PassedStruct
  • en-tête: void myFunction(struct MyStruct (& PassedStruct)[10])
  • appelant: myFunction(StructArray)
  • status: fonctionne, vous travaillez avec une référence à un tableau de taille 10
  • en-tête: void myFunction(struct MyStruct (& PassedStruct)[11])
  • appelant: myFunction(StructArray)
  • état: ne compile pas, type d'incompatibilité de tableau entre le prototype et le paramètre réel
  • en-tête: void myFunction(struct MyStruct PassedStruct[10])
  • appelant: myFunction(StructArray)
  • status: fonctionne, PassedStruct est un pointeur, la taille fournie est ignorée
  • en-tête: void myFunction(struct MyStruct PassedStruct[11])
  • appelant: myFunction(StructArray)
  • status: fonctionne, PassedStruct est un pointeur, la taille fournie est ignorée
24
kriss

Bien que les tableaux et les pointeurs soient des choses conceptuellement différentes, les eaux sont très brouillées par les paramètres de fonction. Vous ne pouvez pas passer directement un tableau à une fonction; vous ne pouvez passer qu'un pointeur. En conséquence, le langage convertit "utilement" un prototype tel que celui-ci:

void foo (char arr[])

dans ceci:

void foo (char *arr)

Et lorsque vous appelez la fonction, vous ne lui passez pas un tableau complet, vous passez un pointeur vers le premier élément. En conséquence, à l'intérieur de la fonction foo, elle aura un pointeur vers le tableau original, et l'assignation aux éléments de arr changera le tableau dans l'appelant comme bien.

Dans tous d'autres situations en dehors des paramètres de fonction, la syntaxe de tableau et de pointeur dans les déclarations est pas équivalente et fait référence à des choses conceptuellement différentes. Mais à l'intérieur des listes de paramètres de fonction, la syntaxe de tableau crée des types de pointeurs.

8
Philip Potter

Vous pouvez utiliser un std :: vector à la place. Les tableaux sont vraiment des constructions C, C++ a des classes wrapper pour éviter exactement ce genre d'ambiguïtés

2
Falmarri

Je crois que x [] signifie la même chose que x * qui signifie "pointeur vers le type". Puisque c'est le cas, vous allez modifier l'objet que vous passez (vous devrez l'appeler en utilisant l'opérateur &, ou 'adresse de'), et vous pouvez le considérer comme une référence.

1
Aaron Anodide

Lorsque vous passez un tableau en tant qu'argument à une fonction, le tableau se décompose en un pointeur vers le premier élément du tableau.

Ainsi, lorsque dans votre fonction vous utilisez [] pour accéder aux éléments du tableau, vous ne faites en réalité que des arithmétiques de pointeur avec votre pointeur initial pour obtenir les éléments du tableau ORIGINAL.

Donc, oui, vous modifiez le tableau d'origine. Et cette réponse est à peu près indépendante du compilateur que vous utilisez (bien que ce soit une bonne pratique, à mon humble avis, d'indiquer le compilateur dans la question comme vous l'avez fait)

1
cake