web-dev-qa-db-ja.com

関数に配列を返す

関数fillarr(int arr[])に渡される配列int arr[5]があります。

int fillarr(int arr[])
{
    for(...);
    return arr;
}
  1. どうすればその配列を返すことができますか?
  2. どうやってそれを使うのでしょうか、どうやってそれにアクセスしようとしているのかポインタを返したとしましょう。
176
Ismail Marmoush

この場合、暗黙の変換により、配列変数arrは実際にはメモリ内の配列のブロックの先頭へのポインタとしても扱えます。あなたが使用しているこの構文:

int fillarr(int arr[])

ちょっと構文糖の一種です。あなたは本当にこれでこれを置き換えることができ、それでもまだ動作するでしょう:

int fillarr(int* arr)

したがって、同じ意味で、関数から返すものは実際には配列の最初の要素へのポインタです。

int* fillarr(int arr[])

それでも、通常の配列と同じように使用できます。

int main()
{
  int y[10];
  int *a = fillarr(y);
  cout << a[0] << endl;
}
167

C++関数は、値によってCスタイルの配列を返すことはできません。最も近いのはポインタを返すことです。さらに、引数リストの配列型は単純にポインタに変換されます。

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

引数と戻り値に配列参照を使用することでこれを改善できます。これにより、減衰を防ぐことができます。

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

BoostまたはC++ 11では、参照による受け渡しはオプションであり、構文はそれほど気にする必要はありません。

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

arrayテンプレートは単純にCスタイルの配列を含むstructを生成するので、オブジェクト指向のセマンティクスを適用しながらも配列の元々の単純さを保つことができます。

98
Potatoswatter

8.3.5ドル/ 8州 -

「戻り型のポインタやそのようなものへの参照を持つことができますが、戻り型の配列や関数を持つことはできません。関数へのポインタの配列はあり得ますが、関数の配列はありません。」

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

C++ 11では、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;
}
16
cubuspl42

その答えは、あなたがその関数をどのように使うつもりであるかに少し依存するかもしれません。最も単純な答えとしては、配列ではなく、あなたが本当に欲しいのはベクトルであると決めましょう。ベクトルは、つまらない、普通の値を通常のポインタに格納できるなど、すべての世界を対象にしているため、素敵です。私たちは他のオプションとあなたが後でそれらが欲しい理由を見ます:

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

これはあなたが期待していることと全く同じです。利点は、std::vectorが、すべてが正しく処理されるようにすることです。欠点は、配列が大きい場合、これによって非常に大量のデータがコピーされることです。実際には、配列のすべての要素を2回コピーします。最初に関数をパラメータとして使用できるようにベクトルをコピーします。それからそれを再びコピーして呼び出し側に返します。自分でベクトルを管理することができれば、もっと簡単にできるようになります。 (呼び出し側がより多くの計算を行うために何らかの変数に格納する必要がある場合は、3度目にコピーされる可能性があります)

実際にやろうとしているのは、単にコレクションを作成することです。もしあなたがコレクションの新しいインスタンスを返す特別な理由がないのなら、そうしないでください。これができる

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

このようにして、関数のプライベートコピーではなく、関数に渡された配列への参照を取得します。パラメータに加えた変更はすべて、呼び出し元に表示されます。必要に応じてそれへの参照を返すことができますが、それはあなたが渡されたものとは異なる何かを得ていることを意味するので、それは本当に素晴らしいアイデアではありません。

本当にコレクションの新しいインスタンスが必要だが、それをスタック上に置かないようにしたい(そしてそれに伴うすべてのコピーを避けたい)場合は、そのインスタンスがどのように処理されるかについて何らかの契約を作成する必要があります。これを行う最も簡単な方法は、スマートポインタを使用することです。スマートポインタは、参照されているインスタンスを誰かが保持している限り保持します。範囲外になるとそれはきれいに消えます。それはこのようになります。

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

ほとんどの場合、*myArrの使用は、単純なVanillaベクトルの使用と同じように機能します。この例では、constキーワードを追加してパラメータリストを変更しています。コピーせずに参照を取得するようになりましたが、変更することはできません。呼び出し元は、関数が参照する前と同じになることを認識しています。

これらすべてが盛り上がっていますが、慣用的なC++がコレクション全体で機能することはめったにありません。通常は、これらのコレクションに対してイテレータを使用します。それはこのような何かもっと見えるでしょう

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

あなたがこのスタイルを見ることに慣れていないなら、それを使うことは少し変に見えます。

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

fooは修正されたarrの始まりを 'ポイント'します。

これに関して本当に素晴らしいことは、普通のCの配列や他の多くのタイプのコレクションと同じようにベクトルでもうまく機能するということです。

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

これは、この質問の別の場所に示されている単純なポインターの例と非常によく似ています。

この:

int fillarr(int arr[])

実際には次のように扱われます。

int fillarr(int *arr)

もし本当に配列を返したいのであれば、その行を以下のように変更することができます。

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

実際には配列を返すわけではありません。配列アドレスの先頭へのポインタを返しています。

しかし、配列を渡すときは、ポインタを渡すだけであることを忘れないでください。そのため、配列データを変更すると、実際にはポインタが指しているデータも変更されます。したがって、配列を渡す前に、修正済みの結果がすでに外側にあることに気付く必要があります。

例えば.

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

このようにfillarr関数に長さを入れることを検討したいと思うかもしれません。

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

そうすることで、長さを使って配列をその長さに合わせることができます。

実際に使いこなすために。このようなことをしなさい:

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.

あなたがしたいことがすべていくつかのデフォルト値に配列を設定することであれば、組み込みのmemset関数を使うことを検討してください。

memset((int *)&arr、5、sizeof(int));のようなものです。

私は話題ですが。あなたはあなたがC++を使っていると言います。 stlベクトルの使い方を見てください。あなたのコードはもっと堅牢になるでしょう。

たくさんのチュートリアルがあります。ここにそれらを使用する方法の考えを与えるものがあります。 http://www.yolinux.com/TUTORIALS/LinuxTutorialC++STL.html

8
Matt

関数から配列を返すには、その配列を構造体で定義しましょう。だからそれはこのようなものになります

struct Marks{
   int list[5];
}

それでは、型構造の変数を作成しましょう。

typedef struct Marks marks;
marks marks_list;

次の方法で配列を関数に渡し、それに値を割り当てることができます。

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

配列を返すこともできます。配列を返すためには、関数の戻り型は構造体型、すなわちmarksであるべきです。これは、実際には配列を含む構造体を渡しているためです。そのため、最終的なコードは次のようになります。

marks getMarks(){
 return marks_list;
}
5
Sandeep

これはかなり古い質問ですが、たくさんの答えがあるので2セントを入れますが、すべての可能な方法を明確かつ簡潔に表示するものはありません(簡潔なビットについてはわからないので、 TL; DR ????)。

私は、コードをきれいに見せるために別の関数に渡すためにこれを呼び出し元に直接渡すいくつかの手段として、OPがコピーせずに渡された配列を返したいと思っています。

ただし、このような配列を使用することは、ポインターに減衰させ、コンパイラーにそれを処理させることですlike配列。これは、配列に5つの要素があることを期待している関数を渡す場合、微妙なバグを引き起こす可能性がありますが、呼び出し元は実際には他の数を渡します。

これをうまく処理する方法がいくつかあります。 std::vectorまたはstd::arrayを渡します(質問が行われたときにstd::arrayが2010年にあったかどうかはわかりません)。その後、オブジェクトをコピー/移動することなく、オブジェクトを参照として渡すことができます。

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

ただし、C配列を使用する場合は、配列内のアイテム数に関する情報を保持するテンプレートを使用してください。

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

例外として、それはお尻がいように見え、非常に読みにくいです。私は現在、2010年にはなかったものを支援するために何かを使用しますが、これは関数ポインタにも使用します:

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

これにより、タイプが予想される場所に移動し、これによりfarが読みやすくなります。もちろん、5つの要素以外を使用しない場合、テンプレートを使用することは不要なので、もちろんハードコーディングすることができます。

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

私が言ったように、この質問が出された時点では、私のtype_t<>トリックはうまくいきませんでした。当時望んでいた最善の方法は、構造体で型を使用することでした。

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

これはかなりprettyいように見え始めますが、少なくともまだ読みやすいですが、typenameはコンパイラーによっては当時はオプションだったかもしれません。

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

そしてもちろん、私のヘルパーを使用するのではなく、特定のタイプを指定することもできます。

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

当時、無料の関数std::begin()およびstd::end()は存在しませんでしたが、簡単に実装できました。これにより、ポインターではなくC配列で意味をなすため、より安全な方法で配列を反復処理できます。

配列へのアクセスに関しては、同じパラメーター型をとる別の関数に渡すか、エイリアスを作成することができます(元のスコープが既にあるのであまり意味がありません)。配列参照へのアクセスは、元の配列へのアクセスに似ています。

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

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

または

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
}

要約すると、配列を反復処理する場合は、配列がポインターに減衰しないようにすることが最善です。コンパイラーがあなたを自分の足で撃つことを防ぎ、コードを読みにくくするので、それは悪い考えです。コンパイラーがタイプをできるだけ長く維持することで、そうしない理由がない限り、常にコンパイラーが手助けするようにしてください。

編集

ああ、そして完全を期すために、ポインターに分解することを許可することができますが、これは配列を保持する要素の数から切り離します。これはC/C++で多く行われ、通常は配列内の要素の数を渡すことで軽減されます。ただし、間違いを犯して要素の数に間違った値を渡した場合、コンパイラは支援できません。

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

サイズを渡す代わりに、エンドポインターを渡すことができます。エンドポインターは、配列の終わりを過ぎた場所を指します。これは、開始および終了ポインタを使用するstdアルゴリズムに近いものを作成するので便利ですが、返されるものは覚えておく必要のあるものになります。

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

あるいは、この関数は5つの要素のみを取り、関数のユーザーが愚かなことをしないことを願っています。

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

戻り値は元の型を失い、ポインタに劣化していることに注意してください。このため、アレイをオーバーランさせないようにするために、あなたは自分で作業します。

std::pair<int*, int*>を渡すことができます。これは、開始と終了に使用でき、それを渡すことができますが、実際には配列のように見えなくなります。

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

または

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

面白いことに、これはstd::initializer_listの動作方法(c ++ 11)と非常に似ていますが、このコンテキストでは動作しません。

4
Adrian

これを行う最も簡単な方法は、 '&'記号を書かなくても参照によって返すことです。それは自動的に参照によって返されます

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

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

あなたはまだ結果を使うことができます

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

そして何について:

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

    return f;
}

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

実際には、関数内で配列を渡すと、元の配列へのポインタが関数パラメータに渡されるため、その関数内の配列に対する変更は実際に元の配列に対しても行われます。

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

それを実行してください、そして、あなたは変化を見るでしょう

0
Abhishek gaur

ソース: https://www.tutorialspoint.com/cplusplus/cpp_return_arrays_from_functions.htm

C++では、配列全体を関数の引数として返すことはできません。ただし、インデックスなしで配列の名前を指定することで、配列へのポインタを返すことができます。

  1. 関数から1次元配列を返したい場合は、次の例のようにポインタを返す関数を宣言する必要があります。
int * myFunction()    {
   .
   .
   .
}
  1. C++はローカル変数のアドレスを関数の外部に返すことを推奨していないので、ローカル変数を静的変数として定義する必要があります。

現在の質問にこれらの規則を適用して、以下のようにプログラムを書くことができます。

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

出力は以下のようになります。

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