C kullanarak bir dizi döndürmek


158

C konusunda nispeten yeniyim ve dizilerle ilgili yöntemlerde biraz yardıma ihtiyacım var. Java programlamasından geldiğimde, int [] method()bir dizi döndürmek için söyleyebilmeye alıştım. Bununla birlikte, C ile, dizileri döndürdüğünüzde diziler için işaretçiler kullanmanız gerektiğini öğrendim. Yeni bir programcı olarak, baktığım birçok forumda bile bunu gerçekten anlamıyorum.

Temel olarak, C'de bir char dizisi döndüren bir yöntem yazmaya çalışıyorum. Yöntemi bir dizi ile (bunu returnArray çağıralım) sağlayacağım. Önceki diziden yeni bir dizi oluşturacak ve ona bir gösterici döndürecektir. Bunun nasıl başlatılacağı ve diziden gönderildiğinde göstericinin nasıl okunacağı konusunda yardıma ihtiyacım var. Bunu açıklayan herhangi bir yardım takdir edilmektedir.

Dizi Döndürme İşlevi için Önerilen Kod Biçimi

char *returnArray(char array []){
 char returned [10];
 //methods to pull values from array, interpret them, and then create new array
 return &(returned[0]); //is this correct?
} 

Fonksiyonu Arayan

int main(){
 int i=0;
 char array []={1,0,0,0,0,1,1};
 char arrayCount=0;
 char* returnedArray = returnArray(&arrayCount); ///is this correct?
 for (i=0; i<10;i++)
  printf(%d, ",", returnedArray[i]);  //is this correctly formatted?
}

Bunu henüz test etmedim çünkü C derleyicim şu anda çalışmıyor, ancak bunu çözmek istiyorum


Dönüş dizisi kod örneğinizde belirtildiği gibi bilinen bir boyutta mı? Cevaplarda bahsedilen yığın sorunlarının yanı sıra gördüğüm diğer tek şey, eğer dönüş diziniz belirsiz bir boyutsa, işaretçilerin / dizilerin C'de nasıl çalıştığı göz önüne alındığında, ne kadar büyük olduğunu bilemeyeceğinizdir.
strangefreeworld

Evet, gelen dizinin boyutunu her zaman biliyorum. Girdi ve çıktı dizisinin boyutu değişmeyecek.
user1506919

1
C Dilinin Gelişimi * - bell-labs.com/usr/dmr/www/chist.html
x4444

Yanıtlar:


237

C'deki işlevlerden dizi döndüremezsiniz. Bunu da yapamazsınız (yapmamalısınız):

char *returnArray(char array []){
 char returned [10];
 //methods to pull values from array, interpret them, and then create new array
 return &(returned[0]); //is this correct?
} 

returned otomatik depolama süresi ile oluşturulur ve buna yapılan başvurular, bildirim kapsamından çıktığında, yani işlev geri döndüğünde geçersiz hale gelir.

Belleği işlevin içine dinamik olarak ayırmanız veya arayan tarafından sağlanan önceden tahsis edilmiş bir arabelleği doldurmanız gerekecektir.

Seçenek 1:

Fonksiyonun içindeki belleği dinamik olarak tahsis edin (serbest bırakmadan sorumlu arayan ret)

char *foo(int count) {
    char *ret = malloc(count);
    if(!ret)
        return NULL;

    for(int i = 0; i < count; ++i) 
        ret[i] = i;

    return ret;
}

Şöyle diyelim:

int main() {
    char *p = foo(10);
    if(p) {
        // do stuff with p
        free(p);
    }

    return 0;
}

Seçenek 2:

Arayan tarafından sağlanan önceden tahsis edilmiş bir arabelleği doldurun (arayan buf, işlevi tahsis eder ve işleve geçer)

void foo(char *buf, int count) {
    for(int i = 0; i < count; ++i)
        buf[i] = i;
}

Ve şöyle deyin:

int main() {
    char arr[10] = {0};
    foo(arr, 10);
    // No need to deallocate because we allocated 
    // arr with automatic storage duration.
    // If we had dynamically allocated it
    // (i.e. malloc or some variant) then we 
    // would need to call free(arr)
}

35
Seçenek 3: (statik bir dizi)
moooeeeep

5
@moooeeeep: Evet, işleri basit tutmak için bunu kasıtlı olarak bıraktım, ancak evet, işlevin içinden bildirilen statik verilere bir işaretçi döndürebilirsiniz.
Ed S.

3
@ user1506919: Belleği kimin ayırdığı ve ayırdığı açık olduğu için aslında seçenek 2'yi tercih ederim, ancak sizin için bir örnek ekleyeceğim.
Ed S.

8
Seçenek 4: Sabit boyutlu bir dizi içeren bir yapı döndür.
Todd Lehman

2
Seçenek 5: Sabit boyutlu bir dizi içeren bir birleşim döndür.
sqr163

27

C'nin dizilere yönelik yaklaşımı Java'lardan çok farklıdır ve düşüncenizi buna göre ayarlamanız gerekir. C'deki diziler birinci sınıf nesneler değildir (yani, bir dizi ifadesi çoğu bağlamda "dizi-niteliğini" korumaz). C'de, "N elemanlı dizi T" türündeki bir ifade T, dizi ifadesinin sizeofveya tekli &operatörlerin bir işleneni olması veya dizi ifadesi, bir bildirimde başka bir diziyi başlatmak için kullanılan bir dizedir.

Diğer şeylerin yanı sıra, bu, bir dizi ifadesini bir işleve aktaramayacağınız ve onu bir dizi türü olarak alamayacağınız anlamına gelir ; işlev aslında bir işaretçi türü alır:

void foo(char *a, size_t asize)
{
  // do something with a
}

int bar(void)
{
  char str[6] = "Hello";
  foo(str, sizeof str);
}

Görüşmede için foo, ifade strtipine dönüştürülmüştür char [6]için char *ilk parametresi, bu yüzden de fooilan edilir char *ayerine char a[6]. İçinde sizeof str, dizi ifadesi sizeofoperatörün bir işleneni olduğundan, bir işaretçi türüne dönüştürülmez, dolayısıyla dizideki bayt sayısını alırsınız (6).

Eğer ediyorsanız gerçekten ilgilenen, Dennis Ritchie'nin okuyabilir C Dili The Geliştirme bu tedavi nereden geldiğini anlamak için.

Sonuç, işlevlerin dizi türlerini döndürememesidir, bu sorun değildir, çünkü dizi ifadeleri de bir atamanın hedefi olamaz.

En güvenli yöntem, arayanın diziyi tanımlaması ve adresini ve boyutunu kendisine yazması gereken işleve iletmesidir:

void returnArray(const char *srcArray, size_t srcSize, char *dstArray, char dstSize)
{
  ...
  dstArray[i] = some_value_derived_from(srcArray[i]);
  ...
}

int main(void)
{
  char src[] = "This is a test";
  char dst[sizeof src];
  ...
  returnArray(src, sizeof src, dst, sizeof dst);
  ...
}

Diğer bir yöntem, işlevin diziyi dinamik olarak tahsis etmesi ve işaretçiyi ve boyutu döndürmesidir:

char *returnArray(const char *srcArray, size_t srcSize, size_t *dstSize)
{
  char *dstArray = malloc(srcSize);
  if (dstArray)
  {
    *dstSize = srcSize;
    ...
  }
  return dstArray;
}

int main(void)
{
  char src[] = "This is a test";
  char *dst;
  size_t dstSize;

  dst = returnArray(src, sizeof src, &dstSize);
  ...
  free(dst);
  ...
}

Bu durumda, arayıcı, freekütüphane işlevi ile dizinin serbest bırakılmasından sorumludur .

Not bu dstyukarıdaki kodu için basit bir gösterici char, bir dizi için bir işaretçi char. C'nin işaretçi ve dizi semantiği, indis operatörünü []dizi tipi veya işaretçi tipinin bir ifadesine uygulayabileceğiniz şekildedir ; her ikisi de src[i]ve dizinin 'inci öğesine dst[i]erişir i(yalnızca srcdizi türüne sahip olsa bile ).

Sen edebilir bir N elemanlı diziye gösterici beyan Tve benzeri bir şey yapmak:

char (*returnArray(const char *srcArr, size_t srcSize))[SOME_SIZE]
{
  char (*dstArr)[SOME_SIZE] = malloc(sizeof *dstArr);
  if (dstArr)
  {
    ...
    (*dstArr)[i] = ...;
    ...
  }
  return dstArr;
}

int main(void)
{
  char src[] = "This is a test";
  char (*dst)[SOME_SIZE];
  ...
  dst = returnArray(src, sizeof src);
  ...
  printf("%c", (*dst)[j]);
  ...
}

Yukarıdakilerle ilgili birkaç dezavantaj. Her şeyden önce, C'nin eski sürümleri SOME_SIZEbir derleme zamanı sabiti olmayı bekler , yani işlev sadece tek bir dizi boyutuyla çalışacaktır. İkinci olarak, alt simgeyi uygulamadan önce işaretçinin referansını kaldırmanız gerekir, bu da kodu karıştırır. Dizilere işaretçiler, çok boyutlu dizilerle uğraşırken daha iyi çalışır.


2
"C'nin gelişimi" bağlantınız koptu ... bizi buraya yönlendirmesi gerekiyor gibi görünüyor: bell-labs.com/usr/dmr/www/chist.html
Dr.Queso

@Kundor: Elde edilen barşey bir işaretçi, dizi değil. Bir işlev parametresi bildirimi bağlamında T a[N]ve T a[]her ikisi de olarak kabul edilir T *a.
John Bode

@JohnBode: Haklısın! Bazı nedenlerden dolayı, sabit boyutlu dizilerin yığın üzerinde aktarıldığını düşündüm. Yıllar önce, bir dizinin boyutunun parametre imzasında belirtilmesi gerektiğini bulduğum bir olayı hatırlıyorum, ancak kafam karışmıştı.
Nick Matteo

@JohnBode, ikinci kod bölümünde ilk satır: void returnArray(const char *srcArray, size_t srcSize, char *dstArray, char dstSize)son parametre size_ttüründe olmamalıdır char.
Seyfi

11

Bunun verilen soruna en iyi çözüm veya tercih edilen bir çözüm olduğunu söylemiyorum. Bununla birlikte, işlevlerin yapıları döndürebileceğini hatırlamak yararlı olabilir. İşlevler dizileri döndüremese de, diziler yapılara sarılabilir ve işlev yapıyı döndürebilir ve böylece diziyi onunla birlikte taşıyabilir. Bu, sabit uzunluklu diziler için çalışır.

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>

    typedef
    struct 
    {
        char v[10];
    } CHAR_ARRAY;



    CHAR_ARRAY returnArray(CHAR_ARRAY array_in, int size)
    {
        CHAR_ARRAY returned;

        /*
        . . . methods to pull values from array, interpret them, and then create new array
        */

        for (int i = 0;  i < size; i++ )
            returned.v[i] = array_in.v[i] + 1;

        return returned; // Works!
    } 




    int main(int argc, char * argv[])
    {
        CHAR_ARRAY array = {1,0,0,0,0,1,1};

        char arrayCount = 7;

        CHAR_ARRAY returnedArray = returnArray(array, arrayCount); 

        for (int i = 0; i < arrayCount; i++)
            printf("%d, ", returnedArray.v[i]);  //is this correctly formatted?

        getchar();
        return 0;
    }

Bu tekniğin güçlü ve zayıf yönleri hakkında yorum yapmaya davet ediyorum. Ben zahmet etmedim.


1
Bunun neden kabul edilen cevap olmadığı belirsiz. Soru, diziye bir gösterici döndürmenin mümkün olup olmadığı değildi.
Frank Puck

Bellek CHAR_ARRAY returnedyığın için ayrılmış mı ? Kesinlikle yığın üzerinde olamaz ( returnArray()sağdaki yığın çerçevesinde mi?
Minh Tran

9

Bu nefis şeytani uygulamaya ne dersiniz?

dizi.h

#define IMPORT_ARRAY(TYPE)    \
    \
struct TYPE##Array {    \
    TYPE* contents;    \
    size_t size;    \
};    \
    \
struct TYPE##Array new_##TYPE##Array() {    \
    struct TYPE##Array a;    \
    a.contents = NULL;    \
    a.size = 0;    \
    return a;    \
}    \
    \
void array_add(struct TYPE##Array* o, TYPE value) {    \
    TYPE* a = malloc((o->size + 1) * sizeof(TYPE));    \
    TYPE i;    \
    for(i = 0; i < o->size; ++i) {    \
        a[i] = o->contents[i];    \
    }    \
    ++(o->size);    \
    a[o->size - 1] = value;    \
    free(o->contents);    \
    o->contents = a;    \
}    \
void array_destroy(struct TYPE##Array* o) {    \
    free(o->contents);    \
}    \
TYPE* array_begin(struct TYPE##Array* o) {    \
    return o->contents;    \
}    \
TYPE* array_end(struct TYPE##Array* o) {    \
    return o->contents + o->size;    \
}

main.c

#include <stdlib.h>
#include "array.h"

IMPORT_ARRAY(int);

struct intArray return_an_array() {
    struct intArray a;
    a = new_intArray();
    array_add(&a, 1);
    array_add(&a, 2);
    array_add(&a, 3);
    return a;
}

int main() {
    struct intArray a;
    int* it;
    int* begin;
    int* end;
    a = return_an_array();
    begin = array_begin(&a);
    end = array_end(&a);
    for(it = begin; it != end; ++it) {
        printf("%d ", *it);
    }
    array_destroy(&a);
    getchar();
    return 0;
}

2
Bu merakımı uyandıracak kadar şeytanca lezzetli. Orada yaptıklarınızı biraz daha açıklayabilir misiniz veya belki de dediğiniz bu lezzet için bir okuma önerebilir misiniz? Şimdiden teşekkürler.
Unheilig

1
@Unheilig - Bunda sime potansiyel hatalar olduğuna dikkat edin, bu sadece Konseptin bir kanıtıydı. Bununla birlikte, işin püf noktası structbir dizi konteyneri / nesnesi olarak döndürmektir . Bunu bir C ++ std :: vektörü gibi düşünün. Önişlemci, bunun intsürümünü şu şekilde genişletecektir struct intArray { int* contents; int size; };.
pyrospade

1
Yaklaşımı beğendim. pro: bu genel bir çözümdür; kontra: bellek yoğun çözüm. Bilinen boyutlardaki vektörler için uygun değildir. Her neyse, bu başlangıç ​​boyut tahsisi ile yükseltilebilir. Kesinlikle biraz tahsis kontrolü eklerdim. Başlamak için çok güzel bir teklif :)
urkon

Nesne yönelimli esk hazır karışım karışımı. Bunu sevdim.
Jack Giffin

6

Sizin durumunuzda, yığın üzerinde bir dizi oluşturuyorsunuz ve işlev kapsamından çıktığınızda, dizi serbest bırakılacaktır. Bunun yerine, dinamik olarak ayrılmış bir dizi oluşturun ve ona bir işaretçi döndürün.

char * returnArray(char *arr, int size) {
    char *new_arr = malloc(sizeof(char) * size);
    for(int i = 0; i < size; ++i) {
        new_arr[i] = arr[i];
    }
    return new_arr;
}

int main() {

    char arr[7]= {1,0,0,0,0,1,1};
    char *new_arr = returnArray(arr, 7);

    // don't forget to free the memory after you're done with the array
    free(new_arr);

}

2
newC'de operatör yoktur . Bu C ++ 'dır.
Eric Postpischil

1
Ve sizeof(char)olması garantilidir 1, bu durumda bu durumda onu bırakabilirsiniz malloc.
Ed S.

ok so Eğer yeni dizinin içeriğini yazdırmak istersem, sadece 'printf' deyimimi yapıp 'returnArray' yerine 'arr' koyabilir miyim?
user1506919

İşlevi düzgün bir şekilde çağırmıyorsunuz (imza iki gerektirdiğinde yalnızca bir bağımsız değişken).
Ed S.

İçeri giriyorsun &arr. Bir arrolmak char *ve onu kullanmaya geçmek istiyorsunuz arr.
chris

4

Bunu, burada bildirilen diğer yanıtlar gibi yığın bellek kullanarak ( malloc () çağırma yoluyla ) yapabilirsiniz , ancak her zaman belleği yönetmelisiniz ( işlevinizi her çağırdığınızda free () işlevini kullanın ). Bunu statik bir diziyle de yapabilirsiniz:

char* returnArrayPointer() 
{
static char array[SIZE];

// do something in your array here

return array; 
}

Bellek yönetimi konusunda endişelenmeden de kullanabilirsiniz.

int main() 
{
char* myArray = returnArrayPointer();
/* use your array here */
/* don't worry to free memory here */
}

Bu örnekte, dizi yaşam süresini uygulama boyu olarak ayarlamak için dizi tanımında static anahtar sözcüğü kullanmanız gerekir, böylece return ifadesinden sonra yok olmaz. Elbette, bu şekilde, tüm uygulama ömrü boyunca belleğinizde SIZE bayt yer kaplarsınız, bu yüzden uygun şekilde boyutlandırın!


2

Metodunuz kötü bir şekilde başarısız olacak yerel bir yığın değişkeni döndürecektir. Bir dizi döndürmek için, işlevin dışında bir tane oluşturun, bunu adrese göre işleve geçirin, sonra onu değiştirin veya yığın üzerinde bir dizi oluşturun ve bu değişkeni geri döndürün. Her ikisi de çalışır, ancak ilki, düzgün çalışmasını sağlamak için herhangi bir dinamik bellek ayırma gerektirmez.

void returnArray(int size, char *retArray)
{
  // work directly with retArray or memcpy into it from elsewhere like
  // memcpy(retArray, localArray, size); 
}

#define ARRAY_SIZE 20

int main(void)
{
  char foo[ARRAY_SIZE];
  returnArray(ARRAY_SIZE, foo);
}

0

Kodu şu şekilde kullanabilirsiniz:

char *MyFunction(some arguments...)
{
    char *pointer = malloc(size for the new array);
    if (!pointer)
        An error occurred, abort or do something about the error.
    return pointer; // Return address of memory to the caller.
}

Bunu yaptığınızda, bellek daha sonra adresi serbest bırakarak serbest bırakılmalıdır.

Başka seçenekler de var. Bir rutin, bazı mevcut yapının parçası olan bir diziye (veya dizinin bir kısmına) bir gösterici döndürebilir. Arayan, bir diziyi iletebilir ve rutin, yeni bir dizi için alan ayırmak yerine yalnızca diziye yazar.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.