web-dev-qa-db-ja.com

C ++で配列を引数として渡す

私はマージソート関数を書いていますが、今はテストケース配列を使用しています(入力はありません-これは今のところ静的です)。配列を引数として渡す方法がわかりません。これが今の私のコードです:

//merge sort first attempt

#include <iostream>

#include <algorithm>

#include <vector>

int mergeSort(int[]);
int main() {
    int originalarray[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 10 };
    mergeSort(originalarray[]);
}

int mergeSort(int[] originalarray) {
    int num = (sizeof(originalarray) / sizeof(int));
    std::vector < int > original(num);

    if (num > 2) {
        return num;
    }

    // Fill the array using the elements of originalarray
    // This is just for demonstration, normally original will be a parameter,
    // so you won't be filling it up with anything.
    std::copy(originalarray, originalarray + num, original.begin());

    // Create farray and sarray of the appropriate size
    std::vector < int > farray(num / 2);
    std::vector < int > sarray(num - farray.size());

    // Fill those using elements from original
    std::copy(original.begin(), original.begin() + farray.size(), farray.begin());
    std::copy(original.begin() + farray.size(), original.end(), sarray.begin());

    mergeSort(farray);
    mergeSort(sarray);
}

これらをマージする方法をまだ理解していないため、このmergeSort関数は機能しないことに注意してください(これが私の割り当てです)。それを処理する前に2つのベクトルをソートしたいのですが、配列を引数として渡す必要があるため、これをコンパイルできません。私はポインタを理解していないので、それが解決策であるならば、私の言い訳は無知です。私は現在、C++を第一言語としてプログラミングを学んでおり、言語の機能の基本的な知識しか持っていません。助けてくれてありがとう。

13
jkeys

これを少し拡張するために、C++配列は正確に C配列であることを忘れないでください。つまり、あなたが持っているのは、何かの配列であると(保証なしで)主張するメモリのアドレスだけです。

更新

さて、もう少し拡張します。

C(したがってC++)には、実際には「配列」自体はありません。持っているのはアドレスとポインタだけです。したがって、何かを「配列」にすると、実際に起こることは、ある変数がアドレスを表すことをコンパイラーに伝えることです。

Cで宣言定義を区別すると便利です。宣言では、単に名前と型を指定するだけです。定義では、実際にスペースを割り当てます。

したがって、次のような配列を定義することから始めると、

_int ar[100];
_

つまり、コンパイラに100 intのスペースが必要であり、すべてが1つのチャンクに割り当てられるようにし、名前arを使用することを意味します。 sizeof演算子は、型またはオブジェクトが使用するバイト数を指定するため、配列arは100×sizeof(int)バイトを使用します。ほとんどのマシンでは、400バイトになりますが、マシンごとに異なります。

変数を定義する場合

_int * ar_p;   // using '_p' as a reminder this is a pointer
_

アドレスを含む変数のスペースを定義しています。サイズはsizeof(int*)で、通常は4または8ですが、一部のマシンでは、すぐに遭遇する可能性が低い一部のマシンでは2から16のいずれかになります。

配列のnamearです。コンパイラはその名前をアドレスに変換するので、そのアドレスを次のように保存できます。

_ar_p = ar ;     // THIS WORKS
_

ここで、便宜上、配列arがたまたまメモリ内の位置1000から始まっているとしましょう。

その名前arnotスペースが割り当てられています。それは定数、数のようなものです。したがって、その割り当てを元に戻すことはできません

_ar = ar_p ;     // THIS WON'T WORK
_

同じ理由であなたは言うことができませんでした

_1000 = ar_p ;   // THIS WON'T WORK EITHER
_

つまり、1000の値を変更することはできません(FORTRANの初期バージョンでは、複雑な理由でこのトリックは機能していました。それは間違いでした。プログラムをデバッグしようとするまで生きたことがありません。 「2」の値は3です。)

Cの配列は常にゼロベースです。つまり、最初のインデックスは常にゼロです。その他のインデックスは、インデックスを使用して計算されたアドレスです。したがって、_ar[0]_はアドレス1000 + 0バイトのオフセットまたは1000です。_ar[1]_は1000 + intのサイズの1倍なので、 intover。そして実際、これはCでは常に当てはまります。

これは配列参照と呼ばれます。

構文_*ar_p_を使用すると、コンパイラーに、_ar_p_に含まれるアドレスAT。 `を取得するように指示します。

これはポインタの逆参照と呼ばれます。

私たちが言うなら

_ar_p = ar;
_

次に、_*ar_p_と_ar[0]_は同じものを参照します。

_ar[0]_と言うときは、arから0バイトのアドレスにあるものが必要であることをコンパイラーに伝えています。 _ar[1]_は、intから1ar、つまり4バイトのアドレスです。したがって、*(ar_p+3)は_ar[3]_と同じものを指します。 (最初にアドレスに3を追加してから内容を確認するため、括弧が必要です。_*ar_p+3_は、最初に_ap_p_が指す内容を取得し、次に3を追加します。

問題は、Cは配列が実際にどれほど大きいかを知らないか、あまり気にしないということです。私がやって来て_ar[365]_を実行すると、コンパイラはセル1000+(365×sizeof(int))を調べるコードを喜んで生成します。それが配列にある場合は問題ありませんが、ランダムメモリの場合は問題ありません。 Cは気にしません。

(Cは電話会社から来ていることを思い出してください。「私たちは気にしません。私たちはそうする必要はありません。私たちは電話会社です。」)

だから、今、私たちはここに移動したいくつかのルールを知っています。 「≡」を「と同等」または「と同じ」と読みます。

あなたが頼ることができるもの:

  • foo(TYPE t[])foo(TYPE * t)

Cはポインタと配列の違いを知らないので、どちらかを宣言できます。関数を定義するとき、あなたは書くことができます

_void foo(int[] ar){
_

または

_void foo(int* ar){
_

まったく同じ効果が得られます。

  • _t[i]_≡*(t+i)

これは上でした。 _ar[i]_と書くことができるところならどこでも、それを*(ar+i)に置き換えることができます。 (実際にはこれを壊す奇妙なサイドケースがありますが、初心者としてそれに遭遇することはありません。)

  • ここで、_TYPE *t_、_(t+i)_は、tのアドレスにi*sizeof(TYPE)を加えたものに等しくなります。

これについても上記で説明しました。 _ar[42]_のように配列にインデックスを付ける場合、開始アドレスから42番目が必要であることを意味します。したがって、intを使用している場合は、intの幅が42倍を超えて移動する必要があります。つまり、sizeof(int)です。

これですべてCになり、C++は「一種の」Cとして定義されているため、C++にもすべて当てはまります。を除いて

  • TYPEが_operator[]_および_operator*_をオーバーロードするユーザー定義型でない限り。

c ++では、他の型と同じように機能する新しい型を定義することを決定できますが、言語が特定のことを行う方法を変更することができます。したがって、プログラマーはcan「オーバーロード」することを決定します-つまり、配列参照およびポインター逆参照演算子のデフォルトの動作を独自の工夫で置き換えます。初心者として、すぐにそれに直面するべきではありませんが、あなたはそれを知っているべきです。

31
Charlie Martin

sizeof(originalarray)/sizeof(int)をそのように使用しないでください。静的に宣言された配列に対してのみ機能します(サイズはコンパイル時にわかっています)。あなたはそれと一緒にサイズを渡す必要があります。配列からvectorを作成し、代わりにそれを渡してみませんか?

サイドノート:経験則として、sizeofはコンパイル時に変換されることに常に注意してください。したがって、引数として渡された配列のサイズを知る方法はありません。

19
Mehrdad Afshari

<vector>が含まれているようです。配列の使用をすべて廃止し、vectorクラスのみを使用することをお勧めします。 vectorhere などのSTLコンテナの使用方法の例を見ることができます。

4
Assaf Lavie
  • 配列を関数に渡すと、表記に関係なく、配列の最初の要素へのポインターに減衰します。したがって、sizeofは期待どおりに機能しません。

  • 配列を渡すときは、どこで停止するかがわかるように、配列サイズを渡すのが最善です。追加のパラメータとして追加します。

2
dirkgently

Std :: vectorを使用するだけで十分だと思うのに、動的に割り当てられた配列とベクトルの両方を使用しているようです。

まず、入力配列をstd :: vectorに変更し、入力データを入力します。

int main()
{
   std::vector<int> originalarray;
   for (int data = 1; data <= 10; data++)
   {
      originalarray.Push_back(data);
   }
   mergeSort(originaldata);
}

ここで、std :: vectorへの参照を取得するようにマージソート関数を宣言することが重要です。

int mergeSort(std::vector<int>& originalarray)
{
   // The rest of your code, note that now you are passing 
   // in your array for sorting, so you can continue with your code to split
   // the vector into farray and sarray

   // then call sort on your halves.
   mergeSort(farray);
   mergeSort(sarray);

   // I'm guessing at this point you'd write code to combine your farray sarray, and
   // put it back into originalarray...don't forget to clear original array first!
}

注意点として、インプレースソートを行っていないように見えるので、大量のデータをコピーしているため、ソートにはしばらく時間がかかると予想してください。

0
Snazzer

上記のすべての回答に加えて、c-faq.comからアレイに関するQ&Aを確認することもできます。 http://c-faq.com/aryptr/index.html

0
Ye Liu

残念ながら、CまたはC++でやりたいことを正確に行うのは非常に困難です。次のように固定サイズの配列を渡すことができます。

int mergeSort(int originalarray[20])
{
    // do something
}

ただし、配列のサイズは数値ではなく、初期化リストの要素数で定義されます。

あなたの場合にすべきことは(それは本当に間違ったことですが)、2つのステップでそれを行うことです:

int originalarray[] = {1, 3, 5, 7, 9, 2, 4, 6, 8, 10};
const size_t arraySize = sizeof originalarray / sizeof originalarray[0];
int mergeSort(int array[arraySize])
{
    // do something
}

残念ながら、必要な処理が実行されません。このような関数に配列を渡すと、配列のコピーが作成されます。並べ替えのポイントは、元の配列を変更することです。

実は、「ポインタ」の概念を理解しなければ、これ以上先に進むことはできません。

開発する必要のある関数は、実際には次のようになります。

int originalarray[] = {1, 3, 5, 7, 9, 2, 4, 6, 8, 10};
const size_t arraySize = sizeof originalarray / sizeof originalarray[0];

int mergeSort(int *array, const size_t size)
{
    // do something
}

mergeSort(&(originalArray[0]), arraySize);

つまり、最初の要素へのポインタと要素の数を渡します。

または、ベクトルを処理することもできます。 Vectorは、同じ2つのもの(最初の要素とサイズへのポインター)を「オブジェクト」と呼ばれる単一のエンティティにカプセル化します。さらに、メモリを管理するため、必要に応じて要素の数を増やすことができます。これはC++の方法です。残念ながら、配列のように{...}でベクトルを初期化することはできません。

0
user3458