web-dev-qa-db-fra.com

Renvoyer un tableau dans une fonction

J'ai un tableau int arr[5] qui est passé à une fonction fillarr(int arr[]):

int fillarr(int arr[])
{
    for(...);
    return arr;
}
  1. Comment puis-je retourner ce tableau?
  2. Comment vais-je l'utiliser, dire que j'ai retourné un pointeur, comment vais-je y accéder?
165
Ismail Marmoush

Dans ce cas, votre variable de tableau arr peut également être traitée comme un pointeur au début du bloc de votre tableau en mémoire, par une conversion implicite. Cette syntaxe que vous utilisez:

int fillarr(int arr[])

Est-ce une sorte de sucre syntaxique? Vous pouvez vraiment le remplacer par ceci et cela fonctionnerait toujours:

int fillarr(int* arr)

Donc, dans le même sens, ce que vous voulez renvoyer de votre fonction est en réalité un pointeur sur le premier élément du tableau:

int* fillarr(int arr[])

Et vous pourrez toujours l'utiliser comme vous le feriez avec un tableau normal:

int main()
{
  int y[10];
  int *a = fillarr(y);
  cout << a[0] << endl;
}
164
Brent Writes Code

Les fonctions C++ ne peuvent pas renvoyer de tableaux de style C par valeur. La chose la plus proche est de retourner un pointeur. De plus, un type de tableau dans la liste d'arguments est simplement converti en un pointeur.

int *fillarr( int arr[] ) { // arr "decays" to type int *
    return arr;
}

Vous pouvez l'améliorer en utilisant un tableau de références pour l'argument et le retour, ce qui empêche la décroissance:

int ( &fillarr( int (&arr)[5] ) )[5] { // no decay; argument must be size 5
    return arr;
}

Avec Boost ou C++ 11, le passage par référence n’est que facultatif et la syntaxe est moins fastidieuse:

array< int, 5 > &fillarr( array< int, 5 > &arr ) {
    return arr; // "array" being boost::array or std::array
}

Le modèle array génère simplement une struct contenant un tableau de style C. Vous pouvez donc appliquer une sémantique orientée objet tout en conservant la simplicité d'origine du tableau.

94
Potatoswatter

8.3.5 $/8 états-

"Les fonctions ne doivent pas avoir de type de retour type tableau ni fonction, bien qu'elles puissent avoir un type de retour type pointeur ou référence à de telles choses. Il ne doit pas y avoir de tableaux de fonctions, bien qu'il puisse y avoir des tableaux de pointeurs sur des fonctions."

int (&fn1(int (&arr)[5]))[5]{     // declare fn1 as returning refernce to array
   return arr;
}

int *fn2(int arr[]){              // declare fn2 as returning pointer to array
   return arr;
}


int main(){
   int buf[5];
   fn1(buf);
   fn2(buf);
}
19
Chubsdad

En C++ 11, vous pouvez renvoyer std::array.

#include <array>
using namespace std;

array<int, 5> fillarr(int arr[])
{
    array<int, 5> arr2;
    for(int i=0; i<5; ++i) {
        arr2[i]=arr[i]*2;
    }
    return arr2;
}
15
cubuspl42

la réponse peut dépendre un peu de la manière dont vous prévoyez d’utiliser cette fonction. Pour la réponse la plus simple, décidons qu'au lieu d'un tableau, ce que vous voulez vraiment, c'est un vecteur. Les vecteurs sont sympas parce que le look du monde entier ressemble à des valeurs ennuyeuses et ordinaires que vous pouvez stocker dans les pointeurs habituels. Nous allons examiner d'autres options et pourquoi vous les voulez après: 

std::vector<int> fillarr( std::vector<int> arr ) {
    // do something
    return arr;
}

Cela fera exactement ce que vous vous attendez à faire. L'avantage, c'est que std::vector veille à ce que tout soit traité correctement. L'inconvénient est que cela copie une très grande quantité de données, si votre tableau est grand. En fait, il copie deux fois chaque élément du tableau. d’abord, il copie le vecteur afin que la fonction puisse l’utiliser en tant que paramètre. puis il copie à nouveau pour le renvoyer à l'appelant. Si vous pouvez gérer vous-même le vecteur, vous pouvez faire les choses un peu plus facilement. (il peut le copier une troisième fois si l'appelant a besoin de le stocker dans une variable quelconque pour effectuer plus de calcul)

On dirait que ce que vous essayez réellement de faire est simplement de renseigner une collection. Si vous n'avez pas de raison spécifique pour renvoyer une nouvelle instance d'une collection, ne le faites pas. on peut le faire comme ça

void fillarr(std::vector<int> &  arr) {
    // modify arr
    // don't return anything
}

ainsi, vous obtenez une référence au tableau transmis à la fonction, pas une copie privée de celui-ci. toute modification apportée au paramètre est vue par l'appelant. Vous pouvez y renvoyer une référence si vous le souhaitez, mais ce n'est pas vraiment une bonne idée, car cela implique en quelque sorte que vous obtenez quelque chose de différent de ce que vous avez adopté. 

Si vous avez vraiment besoin d'une nouvelle instance de la collection, mais que vous voulez éviter de l'avoir sur la pile (et toutes les copies que cela implique), vous devez créer un type de contrat pour le traitement de cette instance. Pour ce faire, le moyen le plus simple consiste à utiliser un pointeur intelligent, qui conserve l'instance référencée aussi longtemps que quelqu'un le tient. Il s'en va proprement s'il sort du champ d'application. Cela ressemblerait à ceci.

std::auto_ptr<std::vector<int> > fillarr( const std::vector<int> & arr) {
    std::auto_ptr<std::vector<int> > myArr(new std::vector<int>);
    // do stuff with arr and *myArr
    return myArr;
}

Pour la plupart, utiliser *myArr fonctionne de la même manière que d’utiliser un vecteur Plain Vanilla. Cet exemple modifie également la liste de paramètres en ajoutant le mot clé const. Maintenant, vous obtenez une référence sans la copier, mais vous ne pouvez pas la modifier. L'appelant sait que ce sera la même chose qu'avant la fonction. 

Tout cela est gonflé, mais idiomatic c ++ fonctionne rarement avec les collections dans leur ensemble. Plus normalement, vous utiliserez des itérateurs sur ces collections. cela ressemblerait plus à ça

template <class Iterator>
Iterator fillarr(Iterator arrStart, Iterator arrEnd) {
    Iterator arrIter = arrStart;
    for(;arrIter <= arrEnd; arrIter++)
       ;// do something
    return arrStart;
}

L’utiliser semble un peu bizarre si vous n’êtes pas habitué à voir ce style. 

vector<int> arr;
vector<int>::iterator foo = fillarr(arr.begin(), arr.end());

foo now 'pointe vers' le début de la arr modifiée. 

Ce qui est vraiment sympa, c’est que cela fonctionne aussi bien sur des vecteurs que sur des matrices C simples et de nombreux autres types de collections, par exemple

int arr[100];
int *foo = fillarr(arr, arr+100);

Ce qui ressemble maintenant beaucoup aux exemples de pointeurs simples donnés ailleurs dans cette question.

14

Ce:

int fillarr(int arr[])

est en fait traité de la même façon que:

int fillarr(int *arr)

Maintenant, si vous voulez vraiment retourner un tableau, vous pouvez changer cette ligne en

int * fillarr(int arr[]){
    // do something to arr
    return arr;
}

Ce n'est pas vraiment retourner un tableau. vous retournez un pointeur au début de l'adresse du tableau

Mais rappelez-vous que lorsque vous passez dans le tableau, vous ne faites que passer dans un pointeur . Ainsi, lorsque vous modifiez les données du tableau, vous modifiez en fait les données pointées par le pointeur Par conséquent, avant de passer dans le tableau, vous devez vous rendre compte que vous avez déjà à l’extérieur le résultat modifié.

par exemple.

int fillarr(int arr[]){
   array[0] = 10;
   array[1] = 5;
}

int main(int argc, char* argv[]){
   int arr[] = { 1,2,3,4,5 };

   // arr[0] == 1
   // arr[1] == 2 etc
   int result = fillarr(arr);
   // arr[0] == 10
   // arr[1] == 5    
   return 0;
}

Je suggère que vous souhaitiez peut-être envisager de mettre une longueur dans votre fonction fillarr comme This.

int * fillarr(int arr[], int length)

De cette façon, vous pouvez utiliser length pour remplir le tableau à sa longueur, peu importe ce que c'est.

Pour l'utiliser réellement correctement. Faites quelque chose comme ça:

int * fillarr(int arr[], int length){
   for (int i = 0; i < length; ++i){
      // arr[i] = ? // do what you want to do here
   }
   return arr;
}

// then where you want to use it.
int arr[5];
int *arr2;

arr2 = fillarr(arr, 5);

// at this point, arr & arr2 are basically the same, just slightly
// different types.  You can cast arr to a (char*) and it'll be the same.

Si tout ce que vous voulez faire est de définir le tableau sur certaines valeurs par défaut, envisagez d'utiliser La fonction intégrée de memset.

quelque chose comme: memset ((int *) & arr, 5, sizeof (int));

Pendant que je suis sur le sujet cependant. Vous dites que vous utilisez C++. Jetez un coup d'œil à l'utilisation de vecteurs stl. Votre code est susceptible d'être plus robuste.

Il y a beaucoup de tutoriels. En voici un qui vous donne une idée de la façon de les utiliser . http://www.yolinux.com/TUTORIALS/LinuxTutorialC++STL.html

8
Matt

pour renvoyer un tableau à partir d'une fonction, définissons ce tableau dans une structure;

struct Marks{
   int list[5];
}

Créons maintenant des variables de la structure type. 

typedef struct Marks marks;
marks marks_list;

Nous pouvons passer tableau à une fonction de la manière suivante et lui attribuer une valeur:

void setMarks(int marks_array[]){
   for(int i=0;i<sizeof(marks_array)/sizeof(int);i++)
       marks_list.list[i]=marks_array[i];
}

Nous pouvons également renvoyer le tableau. Pour renvoyer le tableau, le type de retour de la fonction doit être de type structure, c'est-à-dire de marques. C'est parce qu'en réalité nous passons la structure qui contient le tableau. Donc, le code final peut ressembler à ceci.

marks getMarks(){
 return marks_list;
}
5
Sandeep

C'est une question assez ancienne, mais je vais mettre dans mes 2 centimes car il y a beaucoup de réponses, mais aucune ne montre toutes les méthodes possibles de manière claire et concise (je ne suis pas sûr de la concision, car cela a mordu. TL; DR ????).

Je suppose que l'OP a voulu renvoyer le tableau qui a été passé sans être copié comme moyen de le transmettre directement à l'appelant afin qu'il soit transmis à une autre fonction afin de rendre le code plus joli.

Cependant, utiliser un tableau comme celui-ci revient à le laisser se décomposer en un pointeur et à laisser le compilateur le traiter comme un tableau. Cela peut entraîner des bugs subtils si vous transmettez un tableau comme, la fonction prévoyant qu'il comportera 5 éléments, mais votre appelant transmettra en réalité un autre numéro.

Il y a plusieurs façons de mieux gérer cela. Transmettez un std::vector ou std::array (vous ne savez pas si std::array était dans les environs en 2010 lorsque la question a été posée). Vous pouvez ensuite transmettre l'objet en tant que référence sans copier/déplacer l'objet.

std::array<int, 5>& fillarr(std::array<int, 5>& arr)
{
    // (before c++11)
    for(auto it = arr.begin(); it != arr.end(); ++it)
    { /* do stuff */ }

    // Note the following are for c++11 and higher.  They will work for all
    // the other examples below except for the stuff after the Edit.

    // (c++11 and up)
    for(auto it = std::begin(arr); it != std::end(arr); ++it)
    { /* do stuff */ }

    // range for loop (c++11 and up)
    for(auto& element : arr)
    { /* do stuff */ }

    return arr;
}

std::vector<int>& fillarr(std::vector<int>& arr)
{
    for(auto it = arr.begin(); it != arr.end(); ++it)
    { /* do stuff */ }
    return arr;
}

Cependant, si vous insistez pour jouer avec les tableaux C, utilisez un modèle qui conservera l'information sur le nombre d'éléments dans le tableau.

template <size_t N>
int(&fillarr(int(&arr)[N]))[N]
{
    // N is easier and cleaner than specifying sizeof(arr)/sizeof(arr[0])
    for(int* it = arr; it != arr + N; ++it)
    { /* do stuff */ }
    return arr;
}

Sauf que ça a l'air moche et super difficile à lire. J'utilise maintenant quelque chose pour aider avec ce qui n'était pas autour de 2010, que j'utilise aussi pour les pointeurs de fonction:

template <typename T>
using type_t = T;

template <size_t N>
type_t<int(&)[N]> fillarr(type_t<int(&)[N]> arr)
{
    // N is easier and cleaner than specifying sizeof(arr)/sizeof(arr[0])
    for(int* it = arr; it != arr + N; ++it)
    { /* do stuff */ }
    return arr;
}

Cela déplace le type où on pourrait s’y attendre, rendant ceci loin plus lisible. Bien sûr, utiliser un template est superflu si vous n'utilisez que 5 éléments, vous pouvez donc bien le coder en dur:

type_t<int(&)[5]> fillarr(type_t<int(&)[5]> arr)
{
    // Prefer using the compiler to figure out how many elements there are
    // as it reduces the number of locations where you have to change if needed.
    for(int* it = arr; it != arr + sizeof(arr)/sizeof(arr[0]); ++it)
    { /* do stuff */ }
    return arr;
}

Comme je l'ai dit, mon astuce type_t<> n'aurait pas fonctionné au moment où cette question a été posée. Le mieux que vous puissiez espérer à l'époque était d'utiliser un type dans une structure:

template<typename T>
struct type
{
  typedef T type;
};

typename type<int(&)[5]>::type fillarr(typename type<int(&)[5]>::type arr)
{
    // Prefer using the compiler to figure out how many elements there are
    // as it reduces the number of locations where you have to change if needed.
    for(int* it = arr; it != arr + sizeof(arr)/sizeof(arr[0]); ++it)
    { /* do stuff */ }
    return arr;
}

Ce qui redevient plutôt moche, mais au moins, reste plus lisible, bien que la variable typename puisse avoir été optionnelle à l'époque, en fonction du compilateur, ce qui entraîne:

type<int(&)[5]>::type fillarr(type<int(&)[5]>::type arr)
{
    // Prefer using the compiler to figure out how many elements there are
    // as it reduces the number of locations where you have to change if needed.
    for(int* it = arr; it != arr + sizeof(arr)/sizeof(arr[0]); ++it)
    { /* do stuff */ }
    return arr;
}

Et bien entendu, vous auriez pu spécifier un type spécifique plutôt que d'utiliser mon assistant.

typedef int(&array5)[5];

array5 fillarr(array5 arr)
{
    // Prefer using the compiler to figure out how many elements there are
    // as it reduces the number of locations where you have to change if needed.
    for(int* it = arr; it != arr + sizeof(arr)/sizeof(arr[0]); ++it)
    { /* do stuff */ }
    return arr;
}

À l'époque, les fonctions libres std::begin() et std::end() n'existaient pas, mais auraient pu être facilement implémentées. Cela aurait permis d'itérer le tableau de manière plus sécurisée, car ils ont un sens sur un tableau C, mais pas un pointeur.

En ce qui concerne l’accès au tableau, vous pouvez le transmettre à une autre fonction prenant le même type de paramètre ou lui attribuer un alias (ce qui n’aurait pas beaucoup de sens si vous avez déjà l’original dans cette étendue). Accéder à une référence de tableau revient à accéder au tableau d'origine.

void other_function(type_t<int(&)[5]> x) { /* do something else */ }

void fn()
{
    int array[5];
    other_function(fillarr(array));
}

ou

void fn()
{
    int array[5];
    auto& array2 = fillarr(array); // alias. But why bother.
    int forth_entry = array[4];
    int forth_entry2 = array2[4]; // same value as forth_entry
}

Pour résumer, il est préférable de ne pas autoriser une décomposition de tableau en un pointeur si vous avez l'intention de le parcourir. C'est juste une mauvaise idée car cela empêche le compilateur de vous protéger du tir et vous rend le code plus difficile à lire. Essayez toujours d'aider le compilateur à vous aider en conservant les types le plus longtemps possible, à moins que vous n'ayez une très bonne raison de ne pas le faire.

Modifier

Oh, et pour être complet, vous pouvez le laisser se dégrader en un pointeur, mais cela dissocie le tableau du nombre d'éléments qu'il contient. Cela se fait beaucoup en C/C++ et est généralement atténué en transmettant le nombre d'éléments dans le tableau. Cependant, le compilateur ne peut pas vous aider si vous faites une erreur et transmettez la mauvaise valeur au nombre d'éléments.

// separate size value
int* fillarr(int* arr, size_t size)
{
    for(int* it = arr; it != arr + size; ++it)
    { /* do stuff */ }
    return arr;
}

Au lieu de passer la taille, vous pouvez passer le pointeur de fin, qui pointera vers un point situé au-delà de la fin de votre tableau. Ceci est utile car cela crée un élément plus proche des algorithmes std, qui prennent un pointeur de début et de fin, mais ce que vous retournez est désormais uniquement un élément dont vous devez vous souvenir.

// separate end pointer
int* fillarr(int* arr, int* end)
{
    for(int* it = arr; it != end; ++it)
    { /* do stuff */ }
    return arr;
}

Alternativement, vous pouvez documenter que cette fonction ne prendra que 5 éléments et espérer que l'utilisateur de votre fonction ne fait rien de stupide.

// I document that this function will ONLY take 5 elements and 
// return the same array of 5 elements.  If you pass in anything
// else, may nazal demons exit thine nose!
int* fillarr(int* arr)
{
    for(int* it = arr; it != arr + 5; ++it)
    { /* do stuff */ }
    return arr;
}

Notez que la valeur de retour a perdu son type d'origine et est dégradée en un pointeur. Pour cette raison, vous êtes maintenant seul pour vous assurer que vous n'allez pas envahir le tableau.

Vous pouvez passer un std::pair<int*, int*>, que vous pouvez utiliser pour begin et end et le faire circuler, mais cela ne ressemble plus vraiment à un tableau.

std::pair<int*, int*> fillarr(std::pair<int*, int*> arr)
{
    for(int* it = arr.first; it != arr.second; ++it)
    { /* do stuff */ }
    return arr; // if you change arr, then return the original arr value.
}

void fn()
{
    int array[5];
    auto array2 = fillarr(std::make_pair(&array[0], &array[5]));

    // Can be done, but you have the original array in scope, so why bother.
    int fourth_element = array2.first[4];
}

ou

void other_function(std::pair<int*, int*> array)
{
    // Can be done, but you have the original array in scope, so why bother.
    int fourth_element = array2.first[4];
}

void fn()
{
    int array[5];
    other_function(fillarr(std::make_pair(&array[0], &array[5])));
}

Assez drôle, cela ressemble beaucoup au fonctionnement de std::initializer_list (c ++ 11), mais ils ne fonctionnent pas dans ce contexte.

4
Adrian

la façon la plus simple de le faire est de le renvoyer par référence, même si vous n'écrivez pas le symbole '&'

     void fillarr(int arr[5])
  {
       for(...);

  }
3
nada
int *fillarr(int arr[])

Vous pouvez toujours utiliser le résultat comme

int *returned_array = fillarr(some_other_array);
if(returned_array[0] == 3)
    do_important_cool_stuff();
2
Daniel

et à propos de:

int (*func())
{
    int *f = new int[10] {1,2,3};

    return f;
}

int fa[10] = { 0 };
auto func2() -> int (*) [10]
{
    return &fa;
}
0
Alexandr

En fait, lorsque vous passez un tableau à l'intérieur d'une fonction, le pointeur sur le tableau d'origine est passé dans le paramètre function. Les modifications apportées au tableau à l'intérieur de cette fonction sont donc réellement apportées au tableau d'origine. 

#include <iostream>

using namespace std;

int* func(int ar[])
{
    for(int i=0;i<100;i++) 
        ar[i]=i;
    int *ptr=ar;
    return ptr;
}


int main() {
    int *p;
    int y[100]={0};    
    p=func(y);

    for(int i=0;i<100;i++) 
        cout<<i<<" : "<<y[i]<<'\n';
}

Exécutez-le et vous verrez les changements

0
Abhishek gaur

Source: https://www.tutorialspoint.com/cplusplus/cpp_return_arrays_from_functions.htm

C++ ne permet pas de retourner un tableau entier en tant qu'argument d'une fonction. Cependant, vous pouvez renvoyer un pointeur sur un tableau en spécifiant le nom du tableau sans index.

  1. Si vous souhaitez renvoyer un tableau à une seule dimension à partir d'une fonction, vous devez déclarer une fonction renvoyant un pointeur comme dans l'exemple suivant:
int * myFunction()    {
   .
   .
   .
}
  1. C++ ne préconise pas de renvoyer l'adresse d'une variable locale en dehors de la fonction afin que vous deviez définir la variable locale en tant que variable statique.

En appliquant ces règles à la question actuelle, nous pouvons écrire le programme comme suit:

# include <iostream>

using namespace std;

int * fillarr( );


int main ()
{

   int *p;

   p = fillarr();

   for ( int i = 0; i < 5; i++ )
       cout << "p[" << i << "] : "<< *(p + i) << endl;

    return 0;
}


int * fillarr( )
{
    static int  arr[5];

    for (int i = 0; i < 5; ++i)
        arr[i] = i;

    return arr;
 }

La sortie sera: 

p[0]=0
p[1]=1
p[2]=2
p[3]=3
p[4]=4
0
MAQ
template<typename T, size_t N>
using ARR_REF = T (&)[N];

template <typename T, size_t N>
ARR_REF<T,N> ArraySizeHelper(ARR_REF<T,N> arr);

#define arraysize(arr) sizeof(ArraySizeHelper(arr))
0
Nozama