質問
配列を引数として取り、値を追加し(必要に応じてサイズを大きくし)、アイテムの数を返す関数を作成しようとしています。 これまでのところ:
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の動的構造に対処するより良い方法はありますか?これらに対処するためにリンクリストを実装する必要がありますか?
解決
ここでの主な問題は、スタックに割り当てられた配列でreallocを使用しようとしていることです。あなたが持っている:
ent a[mSize];
これはスタック上の自動割り当てです。後でこれにrealloc()を使用する場合は、次のようにmalloc()を使用してヒープ上に配列を作成します。
ent *a = (ent*)malloc(mSize * sizeof(ent));
mallocライブラリ(およびrealloc()など)が配列を認識するようにします。この見た目からは、 C99可変長配列 true 動的配列を使用しているので、これを修正する前に必ず違いを理解してください。
しかし、実際に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);
この方法では、ユーザーは物を割り当てる方法や、現在の配列のサイズを正確に覚える必要はありません。それがあなたを始めることを願っています。
他のヒント
配列へのポインタへのポインタ、つまりent **a
が渡されるように、それをやり直してみてください。その後、配列の新しい場所で呼び出し元を更新できます。
これは、OOPを使用する良い理由です。はい、CでOOPを実行できます。正しく実行すれば、見栄えも良くなります。
この単純なケースでは、継承もポリモーフィズムも必要なく、カプセル化とメソッドの概念だけが必要です:
- 長さとデータポインタを使用して構造体を定義します。おそらく要素サイズ。
- その構造体へのポインタを操作するゲッター/セッター関数を作成します。
- 「grow」関数は構造体内のデータポインターを変更しますが、構造体ポインターはすべて有効です。
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++;
}
現在のバッファのサイズを変更して元のコードを作成する必要がある場合、reallocのほとんどの実装は内部的に2倍のメモリを割り当てると予想されます
size = size * 2;
不要。
値で配列ポインターを渡します。これが意味すること:
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の値を変更しても、mainのaの値は変更されません。 mainのaの値を変更するには、その参照を<=>に渡す必要があります。現在、aの値がコピーされ、<=>に渡されています。 useへの参照を渡すには:
int addValues (int **a, int mSize)
次のように呼び出します:
int main(int argc, char** argv) {
...
ent *a; // This...
...
addValues (&a, mSize);
}
<=>で、次のようなaの要素にアクセスします。
(*a)[element]
次のように配列を再割り当てします:
(*a) = calloc (...);
Xahtepは、realloc()が配列を新しい場所に移動する可能性があるという事実を呼び出し側がどのように処理できるかを説明しています。これを行う限り、大丈夫です。
realloc()は、大きな配列で作業を開始すると高価になる場合があります。それが、他のデータ構造(リンクリスト、バイナリツリーなど)の使用を検討し始めるときです
前述のように、ポインターにポインターを渡して、ポインター値を更新する必要があります。
ただし、この手法を再設計して回避することをお勧めします。ほとんどの場合、回避することができ、回避する必要があります。あなたが何を達成しようとしているかを正確に知ることなく、代替設計を提案することは困難ですが、私はそれが他の方法で実行可能であることを99%確信しています。そして Javier 悲しい-オブジェクト指向だと思う常により良いコードを取得できます。
本当にCを使用する必要がありますか?これは、C ++の<!> quot; std :: vector <!> quot;の優れたアプリケーションになります。これは、正確に動的なサイズの配列です(1回の呼び出しで簡単にサイズ変更可能で、自分で記述してデバッグする必要はありません)。