2017-02-05 9 views
11

一般に、名前で配列を渡すときは、でアドレスを呼び出します。つまり、配列の値をmain()以外に変更すると、main()に反映されます。配列を値渡しで渡す

したがって、関数の引数として配列を渡し、その関数の変更がmain()に反映されないようにmain()の中で呼び出す場合はどうすればよいですか?例えば

void Foo(int arr[]) //takes an integer array `arr` as argument 
{ 
    // do something with `arr 
} 

int main() 
{ 
    int abc[]={5,1,2,9}; 
    //do something to pass `abc` inside `Foo` so that changes inside `Foo` doesn't change the value of `abc` array. 
} 

は今、私は値によってFooabc配列を渡したいです。

+6

あなたは構造体でそれをラップすることができますが、それはハックのビットです。代わりに 'Foo'の中で' memcpy'を使うのはなぜですか? – Ryan

+2

"*' //引数として整数配列arrを取る '*"実際には 'int *'をとります。関数引数を定義するコンテキストでは、 'T t []'は 'T * t'と等価です。 – alk

答えて

12

structに配列をラップすることでこれを行うことができます。このパラメーターを明示的に渡す必要がないように、配列のサイズのフィールドを含めることができます。このアプローチには、余分なメモリ割り当てを回避して後で解放する必要があります。

Cはすでに引数に値を渡していますが、配列識別子はほとんどの式や特に関数呼び出しではポインタに崩壊します。しかし、structはポインタには崩壊しません。また、値は関数に渡されます。つまり、元の構造体のコピーとそのすべての内容が関数のスコープ内に表示されます。 structに配列が含まれている場合、これもコピーされます。代わりにstructに、たとえば、動的配列のintへのポインタが含まれている場合は、structが関数に渡されたときにポインタがコピーされますが、同じメモリがコピーと元のポインタの両方によって参照されます。このアプローチは実際の配列を含むstructに依存しています。

また、structには、不完全な型のメンバーを含めることはできないため、VLAを含めることはできません。ここでは、同じstructタイプの異なるサイズの配列を処理するためのスペースを提供するために、グローバル定数MAX_ARRを100と定義しました。

また、関数からstructを返すこともできます。私は、関数に渡された structを変更し、呼び出し元関数の別のArraystructに割り当てられるように変更されたstructを返します。これにより、呼び出し元は元の配列と変換された配列の両方にアクセスできます。

#include <stdio.h> 

#define MAX_ARR 100 

struct Array { 
    size_t size; 
    int array[MAX_ARR]; 
}; 

void print_array(struct Array local_arr); 
void func(struct Array local_arr); 
struct Array triple(struct Array local_arr); 

int main(void) 
{ 
    struct Array data = { 
     .size = 10, 
     .array = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 } 
    }; 
    struct Array transformed_data; 

    func(data); 
    transformed_data = triple(data); 

    printf("Original\n"); 
    print_array(data); 

    printf("Transformed\n"); 
    print_array(transformed_data); 

    return 0; 
} 

void print_array(struct Array local_arr) 
{ 
    for (size_t i = 0; i < local_arr.size; i++) { 
     printf("%5d", local_arr.array[i]); 
    } 
    putchar('\n'); 
} 

void func(struct Array local_arr) 
{ 
    for (size_t i = 0; i < local_arr.size; i++) { 
     local_arr.array[i] *= 2; 
    } 
    printf("Modified\n"); 
    print_array(local_arr); 
} 

struct Array triple(struct Array local_arr) 
{ 
    for (size_t i = 0; i < local_arr.size; i++) { 
     local_arr.array[i] *= 3; 
    } 
    return local_arr; 
} 

プログラムの出力:

Modified 
    2 4 6 8 10 12 14 16 18 20 
Original 
    1 2 3 4 5 6 7 8 9 10 
Transformed 
    3 6 9 12 15 18 21 24 27 30 
+0

まあ、これは私がこれまでのところ、一時変数などを使用していない最高の答えだと思う。 – ash12

0

(私はライアンは彼自身の答えを提供するために、ボランティアなかった理由がわからないけど、私は次のように動作する必要があることに同意:)

#include <stdlib.h> // In header 
#include <string.h> 

int Foo(size_t size, int arr[]) 
{ 
    // guard against bad arguments 
    if (arr == NULL || size <= 0) 
    { 
     return -1; 
    } 

    // create local array, since size is variable, allocate dynamically 
    int* arr_2 = NULL; 
    if ((arr_2 = malloc(n * sizeof(*arr_2)) == NULL) 
    { 
     return -1; // malloc allocation error 
    } 
    // otherwise if the size is constant: 
    // int arr_2[SOME_CONSTANT_SIZE]; 

    // copy from arr to arr_2 
    memcpy(arr_2, arr, n * sizeof(*arr_2)); 

    // some computation 

    free(arr_2); 

    return 0; 
} 

我々はスコープを離れるとarr_2がもはや存在しないということを覚えておいてくださいFooです。また、非プリミティブな配列要素に対しては、より多くのコピー作業を行う必要があります。

+0

Tackyは同じ行に戻ります(矛盾、ほとんどのコードショップでは許可されません)。演算子の周りの空白。明示的に、 '!'を使用する代わりにNULLに対してmalloc()をチェックしてください。 NOT演算子は技術的に機能します(0 /非ゼロについて知っているので)。*意味*はブール値*をチェックしていますが、実際にはNULLポインタをチェックしています。コードを一貫して記述し、可能な限りすべてのことを明確にすることをお勧めします。そして独特なフォーマットはバビロンの塔につながります。誰もが奇妙なことがあります。しっかりとしたCスタイルの標準を見つけ出してください。長期的にはより良い – clearlight

+0

また、関数の引数の間にコンマがありませんか?非常にきちんとした一貫したスタイルのもう一つの点は、バグを捕まえるのがずっと簡単で速いことです。 – clearlight

+1

@clearlightこの例では、私が持っているものを変えるつもりですが、一貫して一行の引数ガードリターンステートメントを使用しています(その目的のためだけです)。私はあなたがde参照を空白を必要とするものとして扱いますが、* ptrの代わりに* ptrを書くのは読みにくいと思います。 – synchronizer

5

一般に、あなたはできません。

発信者はこのようなことをすることができます。 Foo()が渡された配列の要素数についての情報を受信して​​いないことを念頭に置い

int main() 
{ 
    int abc[]={5,1,2,9}; 

    { 
     int temp[sizeof (abc)/sizeof (*abc)]; 
     memcpy(temp, abc, sizeof(abc)); 
     Foo(temp); 
    } 
} 

ベア。

Foo()に似たような処理をさせたい場合は、呼び出し側が必要としないように、要素数を別の引数として渡す必要があります。

void Foo(int arr[], size_t size) /* C99 or later */ 
{ 
     int temp[size]; // VLA 
     memcpy(temp, arr, size*sizeof(int)); 
     /* whatever */ 
} 

または(C99より前)。メモリリークを回避するために

void Foo(int arr[], size_t size) /* Before C99 */ 
{ 
     int *temp = malloc(size * sizeof (int)); 
     memcpy(temp, arr, size*sizeof(int)); 
     /* whatever */ 
     free(temp); 
} 

、関数がfree(temp)を呼び出す前に戻らないことを、後者の場合には、確保する必要があります。

上記のFoo()の両方のバージョンで、追加のエラーチェックが必要な場合があります(たとえばヌルポインタまたはゼロサイズが渡されたことを検出する場合、malloc()が成功するなど)。

+0

スタックの配列をmallocする必要がある人の助けを借りてください:-) – clearlight

+2

配列のコピーを保持するために動的に割り当てられたスタック配列をmallocingしていません。 C11からのVLAをサポートする必要がない実装についても、私のところから始めないでください。 – Peter

+0

ああ、私は急いで話をしました、ダイナミックスタックアレイを持っていないので、mallocingを意味しました。 – clearlight