30

私はC++にかなり慣れておらず、ポインターを避けてきました。オンラインで読んだものから、配列を返すことはできませんが、配列へのポインタを返すことはできます。私はそれをテストするために小さなコードを作成し、これがこれを行うための通常の/正しい方法であるかどうか疑問に思いました:

#include <iostream>
using namespace std;

int* test (int in[5]) {
    int* out = in;
    return out;
}

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int* pArr = test(arr);
    for (int i = 0; i < 5; i++) cout<<pArr[i]<<endl;
    cout<<endl;
    return 0;
}

編集:これは良くないようです。どうすれば書き直せますか?

int* test (int a[5], int b[5]) {
    int c[5];
    for (int i = 0; i < 5; i++) c[i] = a[i]+b[i];
    int* out = c;
    return out;
}
4

7 に答える 7

22

現状のコードは正しいですが、実際のシナリオでどのように使用できるか、またはどのように使用されるかを理解するのに苦労しています。そうは言っても、関数からポインタを返すときは、いくつかの注意点に注意してください。

  • 構文を使用して配列を作成すると、その配列int arr[5];はスタックに割り当てられ、関数に対してローカルになります。
  • C ++では、この配列へのポインタを返すことができますが、このポインタが指すメモリをローカルスコープ外で使用することは未定義の動作です。現実世界のアナロジーを使用してこの素晴らしい答えを読んで、私がこれまでに説明できたものよりもはるかに明確な理解を得てください。
  • アレイのメモリがパージされていないことを保証できる場合は、スコープ外でアレイを使用できます。あなたの場合、これはあなたがに渡すときに当てはまりarrますtest()
  • メモリリークを気にせずに動的に割り当てられた配列へのポインタを渡したい場合は、std::unique_ptr/で読み取りを行う必要がありますstd::shared_ptr<>

編集-行列乗算のユースケースに答える

2つのオプションがあります。素朴な方法はstd::unique_ptr/を使用することstd::shared_ptr<>です。最新のC++の方法は、Matrixオーバーロードするクラスを作成することです。乗算の結果をコピーして関数から取得することを避けたい場合operator *は、絶対にnewを使用する必要があります。rvalue references、、を持っていることに加えて、copy constructorも持っている必要があります。この検索の質問と回答に目を通し、これを達成する方法についてより多くの洞察を得てください。operator =destructormove constructormove assignment operator

編集2-追加された質問への回答

int* test (int a[5], int b[5]) {
    int *c = new int[5];
    for (int i = 0; i < 5; i++) c[i] = a[i]+b[i];
    return c;
}

これをとして使用している場合はint *res = test(a,b);、コードの後半でdelete []res、関数に割り当てられたメモリを解放するために呼び出す必要がありtest()ます。に電話をかけるタイミングを手動で追跡するのが非常に難しいという問題がありますdelete。したがって、答えに概説されている場合、それに対処する方法に関するアプローチ。

于 2012-10-20T21:54:21.623 に答える
17

あなたのコードは大丈夫です。ただし、配列へのポインタを返し、その配列がスコープ外になった場合は、そのポインタを使用しないでください。例:

int* test (void)
{
    int out[5];
    return out;
}

戻ったoutときにもう存在しないため、上記は機能しません。test()返されたポインタはもう使用しないでください。使用する場合、使用すべきでないメモリに対して読み取り/書き込みを行うことになります。

元のコードでは、arr配列が戻るとスコープ外になりmain()ます。から戻るmain()ということは、プログラムが終了していることも意味するので、明らかにそれは問題ではありません。

固執して範囲外にならないものが必要な場合は、次のように割り当てる必要がありますnew

int* test (void)
{
    int* out = new int[5];
    return out;
}

返されたポインタは常に有効です。ただし、使い終わったら、次を使用して再度削除することを忘れないでくださいdelete[]

int* array = test();
// ...
// Done with the array.
delete[] array;

それを削除することは、それが使用するメモリを再利用する唯一の方法です。

于 2012-10-20T22:00:18.903 に答える
2

新しい質問に対する新しい答え:

関数から自動変数()へのポインタを返すことはできませんint c[5]。自動変数は、戻り値を囲むブロック(この場合は関数)でその存続期間を終了します。したがって、存在しない配列へのポインターを返します。

変数を動的にする:

int* test (int a[5], int b[5]) {
    int* c = new int[5];
    for (int i = 0; i < 5; i++) c[i] = a[i]+b[i];
    return c;
}

または、以下を使用するように実装を変更しますstd::array

std::array<int,5> test (const std::array<int,5>& a, const std::array<int,5>& b) 
{
   std::array<int,5> c;
   for (int i = 0; i < 5; i++) c[i] = a[i]+b[i];
   return c;
}

コンパイラが提供しない場合はstd::array、配列を含む単純な構造体に置き換えることができます。

struct array_int_5 { 
   int data[5];
   int& operator [](int i) { return data[i]; } 
   int operator const [](int i) { return data[i]; } 
};

古い質問に対する古い答え:

あなたのコードは正しいです、そして...うーん、まあ、...役に立たない。配列は追加の関数なしでポインターに割り当てることができるため(関数ですでにこれを使用していることに注意してください):

int arr[5] = {1, 2, 3, 4, 5};
//int* pArr = test(arr);
int* pArr = arr;

あなたの関数のより多くの署名:

int* test (int in[5])

と同等です:

int* test (int* in)

だからあなたはそれが意味をなさないことがわかります。

ただし、このシグニチャはポインタではなく配列を取ります。

int* test (int (&in)[5])
于 2012-10-20T22:09:13.693 に答える
2

配列を参照する変数は基本的にその最初の要素へのポインターです。したがって、基本的に同じものであるため、配列へのポインターを合法的に返すことができます。これを自分でチェックしてください:

#include <assert.h>

int main() {
  int a[] = {1, 2, 3, 4, 5}; 

  int* pArr = a;
  int* pFirstElem = &(a[0]);

  assert(a == pArr);
  assert(a == pFirstElem);

  return 0;
}

これは、配列を関数に渡すことは、ポインターを介して(ではなくint in[5])ポインターを介して、場合によっては配列の長さとともに実行する必要があることも意味します。

int* test(int* in, int len) {
    int* out = in;
    return out;
}

とは言うものの、ポインターを(完全に理解せずに)使用することはかなり危険です。たとえば、スタックに割り当てられてスコープ外になった配列を参照すると、未定義の動作が発生します。

#include <iostream>

using namespace std;

int main() {
  int* pArr = 0;
  {
    int a[] = {1, 2, 3, 4, 5};
    pArr = a; // or test(a) if you wish
  }
  // a[] went out of scope here, but pArr holds a pointer to it

  // all bets are off, this can output "1", output 1st chapter
  // of "Romeo and Juliet", crash the program or destroy the
  // universe
  cout << pArr[0] << endl; // WRONG!

  return 0;
}

したがって、十分な能力がないと感じた場合は、を使用してstd::vectorください。

[更新された質問への回答]

test関数を作成する正しい方法は次のいずれかです。

void test(int* a, int* b, int* c, int len) {
  for (int i = 0; i < len; ++i) c[i] = a[i] + b[i];
}
...
int main() {
   int a[5] = {...}, b[5] = {...}, c[5] = {};
   test(a, b, c, 5);
   // c now holds the result
}

またはこれ(を使用std::vector):

#include <vector>

vector<int> test(const vector<int>& a, const vector<int>& b) {
  vector<int> result(a.size());
  for (int i = 0; i < a.size(); ++i) {
    result[i] = a[i] + b[i];
  }
  return result; // copy will be elided
}
于 2012-10-20T22:15:00.960 に答える
0

実際のアプリでは、配列を返す方法はoutパラメーターを使用して呼び出されます。もちろん、実際に配列へのポインタを返す必要はありません。呼び出し元がすでにポインタを持っているため、配列に入力するだけです。配列がオーバーフローしないように、配列のサイズを指定する別の引数を渡すことも一般的です。

outパラメータを使用すると、呼び出し元が結果を格納するために必要な配列の大きさがわからない場合があるという欠点があります。その場合、std::vectorまたは同様の配列クラスインスタンスを返すことができます。

于 2012-10-20T22:08:21.480 に答える
0

あなたのコード(問題ないように見えます)は配列へのポインタを返しません。配列の最初の要素へのポインタを返します。

実際、それは通常あなたがやりたいことです。配列のほとんどの操作は、配列全体へのポインターではなく、個々の要素へのポインターを介して行われます。

配列へのポインタを定義できます。次に例を示します。

double (*p)[42];

psの42要素配列へのポインタとして定義しますdouble。これに関する大きな問題は、配列内の要素の数を型の一部として指定する必要があることです。その数はコンパイル時定数でなければなりません。配列を処理するほとんどのプログラムは、さまざまなサイズの配列を処理する必要があります。特定の配列のサイズは、作成後に変更されることはありませんが、コンパイル時に初期サイズがわかるとは限りません。また、配列オブジェクトが異なれば、サイズも異なる可能性があります。

配列の最初の要素へのポインターを使用すると、ポインター演算または索引付け演算子を使用[]して、配列の要素をトラバースできます。ただし、ポインタは配列に含まれる要素の数を示しません。通常、それを自分で追跡する必要があります。

関数が配列を作成し、その最初の要素へのポインターを返す必要がある場合は、いくつかの方法のいずれかで、その配列のストレージを自分で管理する必要があります。呼び出し元に、配列オブジェクト(の最初の要素)へのポインターを渡すことができます。おそらく、そのサイズを指定する別の引数とともに、呼び出し元は配列の大きさを知る必要があります。または、関数は、関数内で定義された静的配列(の最初の要素)へのポインターを返すことができます。これは、配列のサイズが固定されており、同じ配列が関数への2回目の呼び出しによって破壊されることを意味します。または、関数は配列をヒープに割り当てることができます。これにより、呼び出し元は後で配列の割り当てを解除する必要があります。

私がこれまでに書いたものはすべてCとC++に共通であり、実際、C++よりもCのスタイルにはるかに近いものです。comp.lang.c FAQのセクション6では、Cでの配列とポインターの動作について説明しています。

ただし、C ++で記述している場合は、C++イディオムを使用したほうがよいでしょう。たとえば、C ++標準ライブラリは、<vector>やなどのコンテナクラスを定義する多数のヘッダーを提供<array>します。これにより、これらのほとんどが処理されます。生の配列とポインターを使用する特別な理由がない限り、代わりにC++コンテナーを使用する方がおそらく良いでしょう。

編集:私がこの答えを入力していたときに、あなたはあなたの質問を編集したと思います。あなたの質問の最後にある新しいコードは、あなたが観察しているように、良くありません。関数が戻るとすぐに存在しなくなるオブジェクトへのポインタを返します。私は代替案をカバーしたと思います。

于 2012-10-20T22:18:00.293 に答える
0

あなたは(一種の)配列を返すことができます

それ以外の

int m1[5] = {1, 2, 3, 4, 5};
int m2[5] = {6, 7, 8, 9, 10};
int* m3 = test(m1, m2);

書きます

struct mystruct
{
  int arr[5];
};


int m1[5] = {1, 2, 3, 4, 5};
int m2[5] = {6, 7, 8, 9, 10};
mystruct m3 = test(m1,m2);

テストは次のようになります

struct mystruct test(int m1[5], int m2[5])
{
  struct mystruct s;
  for (int i = 0; i < 5; ++i ) s.arr[i]=m1[i]+m2[i];
  return s;
}

コピーしているのであまり効率的ではありませんそれは配列のコピーを提供します

于 2012-10-20T23:02:57.290 に答える