6

配列を引数として取り、それに値を追加し(必要に応じてサイズを増やします)、アイテムの数を返す関数を作成しようとしています。これまでのところ、私は持っています:

int main(int argc, char** argv) {
    int mSize = 10;
    ent a[mSize];
    int n;
    n = addValues(a,mSize);

    for(i=0;i<n;i++) {
       //Print values from a
    }
}

int addValues(ent *a, int mSize) {
    int size = mSize;

    i = 0;

    while(....) { //Loop to add items to array
        if(i>=size-1) { 
            size = size*2;
            a = realloc(a, (size)*sizeof(ent));
        }
        //Add to array
        i++;
    }
    return i;
}

これは、mSize が配列のすべての潜在的な要素を保持するのに十分な大きさである場合に機能しますが、サイズ変更が必要な場合は、セグメンテーション フォールトが発生します。

私も試しました:

int main(int argc, char** argv) {
    ...
    ent *a;
    ...
}

int addValues(ent *a, int mSize) {
    ...
    a = calloc(1, sizeof(ent);
    //usual loop
    ...
}

無駄に。

これは、realloc を呼び出すと、'a' のコピーが別の場所を指しているためだと思います。'a' が常に同じ場所を指すようにするにはどうすればよいでしょうか?

私はこれについて正しく行っていますか?C で動的構造を処理するより良い方法はありますか? これらに対処するためにリンクされたリストを実装する必要がありますか?

4

8 に答える 8

11

ここでの主な問題は、スタック割り当て配列で realloc を使用しようとしていることです。あなたが持っている:

ent a[mSize];

それがスタック上の自動割り当てです。後でこれに対して realloc() を使用する場合は、次のように malloc() を使用してヒープ上に配列を作成します。

ent *a = (ent*)malloc(mSize * sizeof(ent));

malloc ライブラリ (および realloc() など) が配列を認識できるようにします。この見た目から、 C99 可変長配列と真の動的配列を混同している可能性があるため、これを修正する前にその違いを理解しておいてください。

ただし、C で動的配列を作成する場合は、OOP 風の設計を使用して配列に関する情報をカプセル化し、それをユーザーから隠すようにしてください。配列に関する情報 (ポインタやサイズなど) を構造体に統合し、操作 (割り当て、要素の追加、要素の削除、解放など) を構造体で動作する特別な関数に統合したい。だからあなたは持っているかもしれません:

typedef struct dynarray {
   elt *data;
   int size;
} dynarray;

また、dynarray を操作する関数をいくつか定義することもできます。

// malloc a dynarray and its data and returns a pointer to the dynarray    
dynarray *dynarray_create();     

// add an element to dynarray and adjust its size if necessary
void dynarray_add_elt(dynarray *arr, elt value);

// return a particular element in the dynarray
elt dynarray_get_elt(dynarray *arr, int index);

// free the dynarray and its data.
void dynarray_free(dynarray *arr);

このようにして、ユーザーは、物事をどのように割り当てるか、または配列の現在のサイズを正確に覚えておく必要はありません。これで始められることを願っています。

于 2008-12-04T16:51:15.597 に答える
6

配列へのポインターへのポインターが渡されるように、それを作り直してみてくださいent **a。その後、配列の新しい場所で呼び出し元を更新できます。

于 2008-12-04T16:50:56.640 に答える
1

配列ポインタを値で渡しています。これが意味することは次のとおりです。

int main(int argc, char** argv) {
    ...
    ent *a; // This...
    ...
}

int addValues(ent *a, int mSize) {
    ...
    a = calloc(1, sizeof(ent); // ...is not the same as this
    //usual loop
    ...
}

そのため、addValues関数内の a の値を変更しても、メイン内の a の値は変更されません。メインの a の値を変更するには、それへの参照を に渡す必要がありますaddValues。現在、 a の値がコピーされ、 に渡されていaddValuesます。use への参照を渡すには:

int addValues (int **a, int mSize)

そしてそれを次のように呼び出します:

int main(int argc, char** argv) {
    ...
    ent *a; // This...
    ...
    addValues (&a, mSize);
}

addValues、次のように の要素にアクセスします。

(*a)[element]

次のように配列を再割り当てします。

(*a) = calloc (...);
于 2008-12-04T16:54:45.073 に答える
1

これは、OOP を使用する良い理由です。はい、C で OOP を実行できます。正しく実行すれば見栄えもします。

この単純なケースでは、継承もポリモーフィズムも必要なく、カプセル化とメソッドの概念だけが必要です。

  • 長さとデータ ポインターを持つ構造体を定義します。多分要素のサイズ。
  • その構造体へのポインターを操作する getter/setter 関数を作成します。
  • 「grow」関数は構造体内のデータ ポインターを変更しますが、構造体ポインターは有効なままです。
于 2008-12-04T16:57:31.743 に答える
1

main の変数宣言を次のように変更した場合

ent *a = NULL;

コードは、スタックに割り当てられた配列を解放しないことで、想像どおりに機能します。a を NULL に設定すると、realloc はユーザーが malloc(size) を呼び出したかのようにこれを処理するため、機能します。この変更により、addValue のプロトタイプを次のように変更する必要があることに注意してください。

int addValues(ent **a, int mSize)

そして、コードは realloc が失敗した場合を処理する必要があります。例えば

while(....) { //Loop to add items to array
    tmp = realloc(*a, size*sizeof(ent));
    if (tmp) {
        *a = tmp;
    } else {
        // allocation failed. either free *a or keep *a and
        // return an error
    }
    //Add to array
    i++;
}

現在のバッファのサイズを変更して元のコードの

size = size * 2;

不要。

于 2008-12-04T21:03:16.680 に答える
0

本当に C を使用する必要がありますか? これは、C++ の "std::vector" の優れたアプリケーションです。これは正確に動的サイズの配列です (1 回の呼び出しで簡単にサイズ変更でき、自分で記述してデバッグする必要はありません)。

于 2008-12-06T00:15:42.747 に答える
0

Xahtep は、 realloc() が配列を新しい場所に移動する可能性があるという事実に、呼び出し元がどのように対処できるかを説明しています。これをしている限り、あなたは大丈夫なはずです。

大きな配列で作業を開始すると、 realloc() のコストが高くなる可能性があります。それが、他のデータ構造 (リンク リスト、バイナリ ツリーなど) の使用について考え始めるときです。

于 2008-12-04T16:58:25.330 に答える
0

述べたように、ポインター値を更新するには、ポインターをポインターに渡す必要があります。
しかし、再設計してこの手法を回避することをお勧めします。ほとんどの場合、回避可能であり、回避する必要があります。正確に何を達成しようとしているのかを知らなければ、別の設計を提案するのは難しいですが、他の方法で実行できることは 99% 確信しています。そしてハビエルが悲しいように - オブジェクト指向を考えれば、常により良いコードが得られます。

于 2008-12-04T17:02:24.933 に答える