C ++ ' da diziler nasıl kullanılır?

C++, neredeyse her yerde kullanıldıkları C'den devralınan diziler. C++ kullanımı daha kolay ve daha az hata eğilimli olan soyutlamalar sağlar ( std::vector<T> C++98 ve std::array<T, n> C++11 ), bu nedenle diziler için ihtiyaç C'de olduğu kadar sık ortaya çıkmaz.

bu SSS beş parçaya bölünür:

  1. tür düzeyinde diziler ve erişim elemanları
  2. dizi oluşturma ve başlatma
  3. atama ve parametre geçişi
  4. çok boyutlu diziler ve işaretçiler diziler
  5. diziler kullanırken ortak tuzaklar

bu SSS'DE önemli bir şey eksik olduğunu düşünüyorsanız, bir cevap yazın ve ek bir bölüm olarak buraya bağlayın.

aşağıdaki metinde, " dizi "" C dizisi " anlamına gelir, sınıf şablonu std::array değil . Temel bilgi C bildirimci sözdizimi varsayılır. Aşağıda gösterildiği gibi new ve delete manuel kullanımı istisnalar karşısında son derece tehlikeli olduğunu unutmayın, ama bu konu başka bir SSS .

(Not: Bu yığın taşması C ++ SSS için bir giriş olması gerekiyordu. Bu formda bir SSS sağlama fikrini eleştirmek isterseniz, meta üzerinde tüm bu başlatan gönderme bunu yapmak için bir yer olacaktır. Bu sorunun cevapları , SSS fikrinin ilk etapta başladığı C++ sohbet odası de izlenir, yani cevabınız fikri ile geldi olanlar tarafından okunması çok muhtemeldir.)

431
tarihinde sordu Community 2011-01-27 01:14:35
kaynak

5 ответов

tür düzeyinde Diziler

bir dizi türü olarak gösterilir T[n] burada T eleman türü ve n pozitif boyutu , dizideki öğelerin sayısı. Dizi türü öğe türü ve boyutu bir ürün türüdür. Bu bileşenlerin bir veya her ikisi farklı ise, farklı bir tür elde:

#include <type_traits>

static_assert(!std::is_same<int[8], float[8]>::value, "distinct element type");
static_assert(!std::is_same<int[8],   int[9]>::value, "distinct size");

boyutu bir parçası olduğunu unutmayın tür, diğer bir deyişle, farklı boyuttaki dizi türleri birbiriyle kesinlikle hiçbir ilgisi olmayan uyumsuz türlerdir. sizeof(T[n]) n * sizeof(T) eşdeğerdir .

array-to-pointer '

T[n] ve T[m] arasındaki tek "bağlantı", her iki türün de örtülü olarak ile T* arasında dönüştürülmesi ve bu dönüşümün sonucu dizinin ilk öğesine işaretçi olmasıdır. yani, her yerde bir T* gereklidir, bir T[n] sağlayabilir ve derleyici sessizce bu işaretçi sağlayacaktır:

                  +---+---+---+---+---+---+---+---+
the_actual_array: |   |   |   |   |   |   |   |   |   int[8]
                  +---+---+---+---+---+---+---+---+
                    ^
                    |
                    |
                    |
                    |  pointer_to_the_first_element   int*

bu dönüşüm "array-to-pointer decay" olarak bilinir ve büyük bir karışıklık kaynağıdır. Dizinin boyutu bu süreçte kaybolur, çünkü artık türün bir parçası değildir ( T* ). Yazın düzeyinde bir dizi boyutunu Unutmadan Pro: bir işaretçi 1519580920 'bir dizinin ilk elemanı işaret sağlar herhangi bir boyutu. Con: bir dizinin ilk (veya başka) öğesine işaretçi verildiğinde, bu dizinin ne kadar büyük olduğunu veya işaretçinin dizinin sınırlarına göre tam olarak nerede işaret ettiğini tespit etmenin bir yolu yoktur. işaretçileri son derece aptal .

Diziler işaretçiler

değildir

derleyici, yararlı sayıldığı zaman, yani bir dizinin ilk öğesine bir işaretçi sessizce üretecektir işlem Bir dizide başarısız olur, ancak bir işaretçide başarılı olur. Ortaya çıkan işaretçi değeri sadece dizinin adresi olduğundan, diziden işaretçiye bu dönüşüm önemsİzdİr. İşaretçinin değil dizinin kendisinin bir parçası olarak (veya bellekte başka bir yerde) saklandığını unutmayın. bir dizi bir işaretçi değil.

static_assert(!std::is_same<int[8], int*>::value, "an array is not a pointer");

bir dizinin olmadığı önemli bir bağlam , & işlecinin kendisine uygulandığı ilk öğeye bir işaretçi haline gelir. Bu durumda, & operatör tüm dizisi, sadece bir işaretçi ilk öğe için bir işaretçi verir. Bu durumda değerleri (adresler) aynıdır, bir dizinin ilk öğesine bir işaretçi ve tüm diziye bir işaretçi tamamen farklı türlerdir:

static_assert(!std::is_same<int*, int(*)[8]>::value, "distinct element type");

aşağıdaki ASCII sanatı bu ayrımı açıklıyor:

      +-----------------------------------+
      | +---+---+---+---+---+---+---+---+ |
+---> | |   |   |   |   |   |   |   |   | | int[8]
|     | +---+---+---+---+---+---+---+---+ |
|     +---^-------------------------------+
|         |
|         |
|         |
|         |  pointer_to_the_first_element   int*
|
|  pointer_to_the_entire_array              int(*)[8]

, ilk öğeye işaretleyicinin yalnızca tek bir tamsayıya (küçük bir kutu olarak tasvir edilmiştir) işaret ettiğini, tüm dizinin işaretçisi 8 tamsayıya (büyük bir kutu olarak tasvir edilmiştir) işaret ettiğini unutmayın.

aynı durum sınıflarda ortaya çıkar ve belki de daha açıktır. Bir nesneye işaretçi ve ilk veri üyesi için bir işaretçi aynı değerine sahiptir (aynı Adres), ancak tamamen farklı tiplerdir.

C bildirimci sözdizimine aşina değilseniz, int(*)[8] türünde parantez gereklidir:

  • int(*)[8] 8 tamsayılar bir dizi için bir işaretçi.
  • int*[8] , int* türünde her bir öğe olan 8 işaretçi dizisidir .

bağlantı elemanları

C++, Bir dizinin tek tek öğelerine erişmek için iki sözdizimsel varyasyon sağlar. İkisi de diğerinden üstün değildir ve her ikisine de aşina olmalısınız.

işaretçi aritmetik

bir dizinin ilk öğesine p işaretçisi verildiğinde, p+i ifadesi dizinin ı-th öğesine bir işaretçi verir. Bu işaretçiyi daha sonra dereferencing tarafından tek tek öğelere erişebilirsiniz:

std::cout << *(x+3) << ", " << *(x+7) << std::endl;

Eğer x bir dizisini gösteriyorsa, diziden işaretçiye çürüme devreye girer, çünkü bir dizi ve bir tamsayı eklemek anlamsızdır (dizilerde artı işlem yoktur), ancak bir işaretçi ve bir tamsayı eklemek mantıklı:

   +---+---+---+---+---+---+---+---+
x: |   |   |   |   |   |   |   |   |   int[8]
   +---+---+---+---+---+---+---+---+
     ^           ^               ^
     |           |               |
     |           |               |
     |           |               |
x+0  |      x+3  |          x+7  |     int*

(örtülü olarak oluşturulan işaretçinin adı olmadığını unutmayın, bu yüzden onu tanımlamak için x+0 yazdım.)

If, öte yandan, x , işaretçisi dizisinin ilk (veya başka herhangi bir) öğesine işaret eder, daha sonra diziden işaretçi çürümesi gerekli değildir, çünkü i işaretçisi eklenecektir zaten var:

   +---+---+---+---+---+---+---+---+
   |   |   |   |   |   |   |   |   |   int[8]
   +---+---+---+---+---+---+---+---+
     ^           ^               ^
     |           |               |
     |           |               |
   +-|-+         |               |
x: | | |    x+3  |          x+7  |     int*
   +---+

, gösterilen durumda, x , değişken ( x yanındaki küçük kutu ile ayırt edilebilir) bir işaretçi olduğunu unutmayın, ama aynı zamanda bir işlevin sonucu olabilir. işaretçi (veya T* türünde başka bir ifade).

dizin işleci

sözdizimi *(x+i) biraz beceriksiz olduğundan, C++ x[i] alternatif sözdizimi sağlar:

std::cout << x[3] << ", " << x[7] << std::endl;

ekin değişmeli olması nedeniyle, aşağıdaki kod tam olarak aynı şeyi yapar:

std::cout << 3[x] << ", " << 7[x] << std::endl;

dizin oluşturma operatörünün tanımı aşağıdaki ilginç eşdeğerliğe yol açar:

&x[i]  ==  &*(x+i)  ==  x+i

ancak, &x[0] genellikle değil eşdeğer x . Birincisi bir işaretçi, ikincisi bir dizidir. Yalnızca bağlam, diziden işaretçiye çürüme tetiklediğinde x ve &x[0] birbirinin yerine kullanılabilir. Örneğin:

T* p = &array[0];  // rewritten as &*(array+0), decay happens due to the addition
T* q = array;      // decay happens due to the assignment

ilk satırda, derleyici önemsiz başarılı bir işaretçi, bir işaretçi bir atama algılar. İkinci satırda, bir dizisinden bir atamayı bir işaretçiye algılar. Bu anlamsız olduğu için (ancak işaretçi işaretçi atama mantıklı), diziden işaretçiye çürüme her zamanki gibi başladı.

-

'1519570920 T[n] türünde bir dizi n öğelerine sahiptir ve 0 den n-1 ye endekslenir; n öğesi yoktur. Ve yine de, yarım açık aralıkları desteklemek için (başlangıç nerede ( ve sonu ( ), C++ (varolmayan) n-th elemanı için bir işaretçi hesaplama sağlar, ancak bu işaretçi dereference yasadışı:

   +---+---+---+---+---+---+---+---+....
x: |   |   |   |   |   |   |   |   |   .   int[8]
   +---+---+---+---+---+---+---+---+....
     ^                               ^
     |                               |
     |                               |
     |                               |
x+0  |                          x+8  |     int*

örneğin, bir diziyi sıralamak istiyorsanız, aşağıdakilerden her ikisi de eşit derecede iyi çalışır:

std::sort(x + 0, x + n);
std::sort(&x[0], &x[0] + n);

bu eşdeğer olduğu için ikinci argüman olarak &x[n] sağlamak yasadışı olduğunu unutmayın &*(x+n) ve alt ifade *(x+n) teknik olarak tanımsız davranış C++ ' (ancak c99 değil) çağırır.

ayrıca, ilk argüman olarak x sağlayabileceğinizi de unutmayın. Bu benim zevkim için biraz fazla terse ve aynı zamanda derleyici için biraz daha zor bir argüman yapar, çünkü bu durumda ilk argüman bir dizidir, ancak ikinci argüman bir işaretçidir. (Yine, diziden işaretçiye çürüme başladı için.)

267
cevap fredoverflow 2017-05-23 15:26:24
kaynak

programcılar genellikle çok boyutlu dizileri işaretçiler dizileriyle karıştırırlar.

çok boyutlu diziler

çoğu programcı adlı çok boyutlu diziler aşina, ancak birçok çok boyutlu dizi de anonim oluşturulabilir gerçeğinden habersiz. Çok boyutlu diziler genellikle "dizilerin dizileri" veya " true çok boyutlu diziler " olarak adlandırılır.

adlı çok boyutlu diziler

adlandırılmış çok boyutlu diziler kullanırken, tüm boyutları derleme zamanında bilinmelidir:

int H = read_int();
int W = read_int();

int connect_four[6][7];   // okay

int connect_four[H][7];   // ISO C++ forbids variable length array
int connect_four[6][W];   // ISO C++ forbids variable length array
int connect_four[H][W];   // ISO C++ forbids variable length array

bu, adlandırılmış çok boyutlu bir dizinin bellekte nasıl göründüğü:

              +---+---+---+---+---+---+---+
connect_four: |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+

yukarıdaki gibi 2D ızgaraları sadece yararlı görselleştirme olduğunu unutmayın. C ++ açısından, bellek" düz " bayt dizisidir. Çok boyutlu unsurları dizi satır-büyük sırayla saklanır. Yani, connect_four[0][6] ve connect_four[1][0] bellekte komşular. Aslında, connect_four[0][7] ve connect_four[1][0] aynı elemanı gösterir! Bu, çok boyutlu diziler alabilir ve büyük, tek boyutlu diziler olarak onları tedavi anlamına gelir:

int* p = &connect_four[0][0];
int* q = p + 42;
some_int_sequence_algorithm(p, q);

Anonim çok boyutlu diziler

Anonim çok boyutlu dizilerle

, ilk dışındaki tüm Boyutlar bilinmelidir derleme zamanında:

int (*p)[7] = new int[6][7];   // okay
int (*p)[7] = new int[H][7];   // okay

int (*p)[W] = new int[6][W];   // ISO C++ forbids variable length array
int (*p)[W] = new int[H][W];   // ISO C++ forbids variable length array

bu, anonim çok boyutlu bir dizinin bellekte nasıl göründüğü:

              +---+---+---+---+---+---+---+
        +---> |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |
      +-|-+
   p: | | |
      +---+

dizinin kendisinin hala bellekte tek bir blok olarak tahsis edildiğini unutmayın.

işaretçilerin dizileri

başka bir düzeltme seviyesi sunarak sabit genişliğin kısıtlanmasının üstesinden gelebilirsiniz.

işaretçiler' 1519290920 Diziler adlı'

burada, farklı uzunluklardaki anonim dizilerle başlatılan beş işaretçi adlı bir dizidir:

int* triangle[5];
for (int i = 0; i < 5; ++i)
{
    triangle[i] = new int[5 - i];
}

// ...

for (int i = 0; i < 5; ++i)
{
    delete[] triangle[i];
}

ve işte bellekte nasıl görünüyor:

          +---+---+---+---+---+
          |   |   |   |   |   |
          +---+---+---+---+---+
            ^
            | +---+---+---+---+
            | |   |   |   |   |
            | +---+---+---+---+
            |   ^
            |   | +---+---+---+
            |   | |   |   |   |
            |   | +---+---+---+
            |   |   ^
            |   |   | +---+---+
            |   |   | |   |   |
            |   |   | +---+---+
            |   |   |   ^
            |   |   |   | +---+
            |   |   |   | |   |
            |   |   |   | +---+
            |   |   |   |   ^
            |   |   |   |   |
            |   |   |   |   |
          +-|-+-|-+-|-+-|-+-|-+
triangle: | | | | | | | | | | |
          +---+---+---+---+---+

her satır şimdi ayrı ayrı tahsis edildiğinden, 2D dizileri 1D dizileri olarak görüntülemek artık çalışmıyor.

İşaretçilerin Anonim dizileri

burada 5 (veya başka herhangi bir sayı) anonim bir dizidir) farklı uzunluklardaki anonim dizilerle başlatılan işaretçiler:

int n = calculate_five();   // or any other number
int** p = new int*[n];
for (int i = 0; i < n; ++i)
{
    p[i] = new int[n - i];
}

// ...

for (int i = 0; i < n; ++i)
{
    delete[] p[i];
}
delete[] p;   // note the extra delete[] !

ve işte bellekte nasıl görünüyor:

          +---+---+---+---+---+
          |   |   |   |   |   |
          +---+---+---+---+---+
            ^
            | +---+---+---+---+
            | |   |   |   |   |
            | +---+---+---+---+
            |   ^
            |   | +---+---+---+
            |   | |   |   |   |
            |   | +---+---+---+
            |   |   ^
            |   |   | +---+---+
            |   |   | |   |   |
            |   |   | +---+---+
            |   |   |   ^
            |   |   |   | +---+
            |   |   |   | |   |
            |   |   |   | +---+
            |   |   |   |   ^
            |   |   |   |   |
            |   |   |   |   |
          +-|-+-|-+-|-+-|-+-|-+
          | | | | | | | | | | |
          +---+---+---+---+---+
            ^
            |
            |
          +-|-+
       p: | | |
          +---+

-

diziden işaretçiye, doğal olarak dizilerin dizilerine ve işaretçilerin dizilerine kadar uzanır:

int array_of_arrays[6][7];
int (*pointer_to_array)[7] = array_of_arrays;

int* array_of_pointers[6];
int** pointer_to_pointer = array_of_pointers;

bununla birlikte, T[h][w] den T** e örtülü bir dönüşüm yoktur . Böyle örtük bir dönüşüm varsa, sonuç, h işaretçileri T dizisinin ilk öğesine (her biri orijinal 2D Dizideki bir çizginin ilk elemanına işaret eden) bir işaretçi olacaktır, ancak bu işaretçi dizisi henüz bellekte herhangi bir yerde mevcut değildir. Böyle bir dönüşüm istiyorsanız, gerekli işaretçi dizisini el ile oluşturmanız ve doldurmanız gerekir:

int connect_four[6][7];

int** p = new int*[6];
for (int i = 0; i < 6; ++i)
{
    p[i] = connect_four[i];
}

// ...

delete[] p;

bunun orijinal çok boyutlu dizinin bir görünümünü oluşturduğunu unutmayın. Bunun yerine bir kopyaya ihtiyacınız varsa, oluşturmanız gerekir ekstra diziler ve verileri kendiniz kopyalayın:

int connect_four[6][7];

int** p = new int*[6];
for (int i = 0; i < 6; ++i)
{
    p[i] = new int[7];
    std::copy(connect_four[i], connect_four[i + 1], p[i]);
}

// ...

for (int i = 0; i < 6; ++i)
{
    delete[] p[i];
}
delete[] p;
128
cevap fredoverflow 2012-05-28 13:20:29
kaynak

-

belirli bir nedenle, diziler birbirlerine atanamaz. Bunun yerine std::copy kullanın:

#include <algorithm>

// ...

int a[8] = {2, 3, 5, 7, 11, 13, 17, 19};
int b[8];
std::copy(a + 0, a + 8, b);

bu, gerçek dizi atamasının sağlayabileceğinden daha esnektir, çünkü daha büyük dizilerin dilimlerini daha küçük dizilere kopyalamak mümkündür. std::copy genellikle ilkel türleri için maksimum performans vermek için uzmanlaşmıştır. std::memcpy daha iyi performans göstermesi olası değildir. Şüpheniz varsa ölçün.

dizileri doğrudan atayamazsınız, ancak içeren yapıları ve sınıfları atayabilirsiniz dizi üyeleri. Bunun nedeni, dizi üyeleri, derleyici tarafından varsayılan olarak sağlanan atama işleci tarafından üyesi kopyalanır. Atama işlecini kendi yapı veya sınıf türleri için el ile tanımlarsanız, dizi üyeleri için manuel kopyalamaya geri dönmelisiniz.

parametre geçişi

Diziler değer tarafından geçirilemez. Onları işaretçi veya referansla geçirebilirsiniz.

işaretçi tarafından pas

dizilerin kendileri değer tarafından geçirilemediğinden, genellikle ilk öğelerine bir işaretçi yerine değer tarafından geçirilir. Buna genellikle "işaretçi tarafından geç" denir. Dizinin boyutu bu işaretçi aracılığıyla alınamaz olmadığından, ikinci bir parametre geçmeniz gerekir dizinin boyutunu (klasik C çözümü) veya dizinin son öğesinden sonra işaret eden ikinci bir işaretçiyi (C++ yineleyici çözümü) gösteren:

#include <numeric>
#include <cstddef>

int sum(const int* p, std::size_t n)
{
    return std::accumulate(p, p + n, 0);
}

int sum(const int* p, const int* q)
{
    return std::accumulate(p, q, 0);
}

sözdizimsel bir alternatif olarak, parametreleri T p[] olarak da bildirebilirsiniz ve parametre listeleri bağlamında T* p ile tam olarak aynı şey anlamına gelir sadece :

int sum(const int p[], std::size_t n)
{
    return std::accumulate(p, p + n, 0);
}

derleyiciyi T p[] olarak yeniden yazmayı düşünebilirsiniz T *p parametre listeleri bağlamında sadece . Bu özel kural, diziler ve işaretçiler hakkındaki tüm karışıklıklardan kısmen sorumludur. Diğer her bağlamda, bir dizi olarak veya bir işaretçi olarak bir şey bildirmek büyük farkı yaratır.

ne yazık ki, derleyici tarafından sessizce göz ardı edilen bir dizi parametresinde bir boyut da sağlayabilirsiniz. Yani, aşağıdaki üç imza tam olarak eşdeğerdir, derleyici hataları tarafından belirtildiği gibi:

int sum(const int* p, std::size_t n)

// error: redefinition of 'int sum(const int*, size_t)'
int sum(const int p[], std::size_t n)

// error: redefinition of 'int sum(const int*, size_t)'
int sum(const int p[8], std::size_t n)   // the 8 has no meaning here

referansla geçiş

Diziler de referansla geçirilebilir:

int sum(const int (&a)[8])
{
    return std::accumulate(a + 0, a + 8, 0);
}

bu durumda, dizi boyutu önemlidir. Tam olarak 8 element dizilerini yalnızca kabul eden bir işlev yazdığından, programcılar genellikle şablonlar gibi işlevler yazırlar:

template <std::size_t n>
int sum(const int (&a)[n])
{
    return std::accumulate(a + 0, a + n, 0);
}

sadece böyle bir işlev şablonu çağırabileceğinizi unutmayın gerçek bir tamsayılar dizisi ile, bir tamsayıya işaretçi ile değil. Dizinin boyutu otomatik olarak çıkarılır ve her boyut n için şablondan farklı bir işlev oluşturulur. Ayrıca oldukça kullanışlı işlev şablonları hem öğe türü hem de boyutu soyut yazabilirsiniz.

82
cevap fredoverflow 2017-05-23 14:47:20
kaynak

dizi oluşturma ve başlatma

C++ nesnesinin diğer türlerinde olduğu gibi, diziler doğrudan adlandırılmış değişkenlerde saklanabilir (daha sonra boyut bir derleme zamanı sabiti olmalıdır; C++ VLAs yi desteklemez) veya yığın üzerinde anonim olarak saklanabilir ve dolaylı olarak işaretçiler aracılığıyla erişilebilir (ancak o zaman boyut çalışma zamanında hesaplanabilir).

otomatik diziler

otomatik diziler ("yığın üzerinde" yaşayan diziler), kontrol akışının statik olmayan bir yerel dizi değişkeninin tanımından geçtiği her seferinde oluşturulur:

void foo()
{
    int automatic_array[8];
}

başlatma artan sırada gerçekleştirilir. İlk değerlerin T eleman türüne bağlı olduğunu unutmayın:

  • T ise (yukarıdaki örnekte int gibi), hiçbir başlatma gerçekleşir.
  • aksi takdirde, T varsayılan oluşturucu tüm öğeleri başlatır.
  • T erişilebilir bir varsayılan oluşturucu sağlıyorsa, program derlenmez.

alternatif olarak, ilk değerler açıkça dizi başlatıcı , kıvırcık parantez ile çevrili virgülle ayrılmış bir liste belirtilebilir:

    int primes[8] = {2, 3, 5, 7, 11, 13, 17, 19};

bu durumda beri dizi başlatıcısı öğelerin sayısı el ile gereksiz boyutunu belirterek, dizinin boyutuna eşittir. Derleyici tarafından otomatik olarak çıkarılabilir:

    int primes[] = {2, 3, 5, 7, 11, 13, 17, 19};   // size 8 is deduced

boyutu belirtmek ve daha kısa bir dizi başlatıcı sağlamak da mümkündür:

    int fibonacci[50] = {0, 1, 1};   // 47 trailing zeros are deduced

bu durumda, kalan öğeler sıfır başlatıldı . C++ boş bir dizi başlatıcı izin verdiğini unutmayın (tüm öğeler sıfır başlatılmış), c89 ise (en az bir değer gereklidir) değil. Ayrıca, dizi başlatıcılarının yalnızca dizilerini başlatmak için kullanılabileceğini unutmayın; daha sonra Atamalarda kullanılamazlar.

statik diziler

statik diziler ("veri segmentinde" yaşayan diziler), static anahtar sözcüğü ve ad alanı kapsamındaki dizi değişkenleri ("global variables") ile tanımlanan yerel dizi değişkenleridir:

int global_static_array[8];

void foo()
{
    static int local_static_array[8];
}

(ad alanı kapsamındaki değişkenlerin örtülü olarak statik olduğunu unutmayın. static anahtar kelimesini tanımlarına eklemek tamamen farklı, kullanım dışı anlamındadır .)

statik dizilerin otomatik dizilerden nasıl farklı davrandığı:

  • bir dizi başlatıcısı olmayan statik diziler, başka bir potansiyel başlatmadan önce sıfır başlatılır.
  • statik POD diziler tam olarak bir kez başlatılır ve ilk değerler genellikle yürütülebilir dosya içine atılır, bu durumda çalışma zamanında başlatma maliyeti yoktur. Bununla birlikte, bu her zaman en uzay-verimli çözüm değildir ve standart tarafından gerekli değildir.
  • statik POD olmayan diziler, ilk kez kontrol akışı tanımlarından geçer. Durumunda yerel statik diziler, işlev asla çağrılmazsa asla gerçekleşmeyebilir.

(Yukarıdakilerin hiçbiri dizilere özgüdür. Bu kurallar diğer statik nesneler için eşit derecede iyi uygulanır.)

dizi veri üyeleri

kendi sahibi nesne oluşturulduğunda dizi veri üyeleri oluşturulur. Ne yazık ki, C ++ 03 üye başlatıcısı listesinde diziler başlatmak için hiçbir yol sağlar, böylece başlatma atamaları ile sahte olmalıdır:

class Foo
{
    int primes[8];

public:

    Foo()
    {
        primes[0] = 2;
        primes[1] = 3;
        primes[2] = 5;
        // ...
    }
};

alternatif olarak, oluşturucu gövdesinde otomatik bir dizi tanımlayabilir ve öğeleri kopyalayabilirsiniz:

class Foo
{
    int primes[8];

public:

    Foo()
    {
        int local_array[] = {2, 3, 5, 7, 11, 13, 17, 19};
        std::copy(local_array + 0, local_array + 8, primes + 0);
    }
};

C++0x, diziler üye başlatıcısı listesinde üniforma başlatma sayesinde başlatılabilir:

class Foo
{
    int primes[8];

public:

    Foo() : primes { 2, 3, 5, 7, 11, 13, 17, 19 }
    {
    }
};

bu öğe türleri ile çalışan tek çözümdür varsayılan oluşturucuya sahip.

dinamik diziler

dinamik dizilerin isimleri yok, dolayısıyla bunlara erişmenin tek yolu işaretçiler aracılığıyla. Çünkü isimleri yok, şu andan itibaren onlara "anonim diziler" olarak değineceğim.

C, anonim diziler malloc ve arkadaşlar aracılığıyla oluşturulur. İçinde C++, anonim diziler kullanılarak oluşturulur new T[size] bir işaretçi döndürür sözdizimi ilk öğesi bir anonim dizi:

std::size_t size = compute_size_at_runtime();
int* p = new int[size];

aşağıdaki ASCII sanatı, boyut çalışma zamanında 8 olarak hesaplanırsa bellek düzenini tasvir eder:

             +---+---+---+---+---+---+---+---+
(anonymous)  |   |   |   |   |   |   |   |   |
             +---+---+---+---+---+---+---+---+
               ^
               |
               |
             +-|-+
          p: | | |                               int*
             +---+

açıkçası, anonim diziler ayrı ayrı saklanması gereken ekstra işaretçi nedeniyle adlandırılmış dizilerden daha fazla bellek gerektirir. (Ayrıca ücretsiz mağazada bazı ek yük vardır.)

burada no diziden işaretçiye bozulma olduğunu unutmayın. Rağmen değerlendirme new int[size] aslında bir dizi tamsayılar, ifade new int[size] sonucu zaten tek bir tamsayıya bir işaretçi (ilk öğe), değil tamsayılar bir dizi veya bilinmeyen boyutta tamsayılar bir dizi için bir işaretçi. Bu imkansız olurdu, çünkü statik tür sistemi derleme zamanı sabitleri olmak için dizi boyutları gerektirir. (Dolayısıyla, anonim diziyi açıklamadım resimde statik tip bilgileri.)

öğeler için varsayılan değerlerle ilgili olarak, anonim diziler otomatik dizilere benzer davranır. Normalde, anonim POD dizileri başlatılmaz, ancak özel sözdizimi değeri başlatmayı tetikleyen bir

vardır
int* p = new int[some_computed_size]();

(noktalı virgülden hemen önce parantez sondaki çifti not edin.) Yine, C ++ 0x kuralları basitleştirir ve başlangıç değerlerini belirtmeye izin verir anonim diziler üniforma başlatma sayesinde:

int* p = new int[8] { 2, 3, 5, 7, 11, 13, 17, 19 };

anonim bir dizi kullanıyorsanız, sisteme geri bırakmanız gerekir:

delete[] p;

her anonim diziyi tam olarak bir kez serbest bırakmanız ve daha sonra tekrar dokunmamanız gerekir. Bir bellek sızıntısı (veya daha genel olarak, öğe türüne, bir kaynak sızıntısı bağlı olarak) tüm sonuçlarda bırakmadan ve birden çok kez serbest bırakmaya çalışırken tanımsız sonuçlanır davranış. Diziyi serbest bırakmak için delete[] yerine delete (veya free ) olmayan dizi formunu kullanmak da tanımsız davranış .

68
cevap fredoverflow 2017-05-23 14:54:50
kaynak

5. Diziler kullanırken ortak tuzaklar.

5.1 Pitfall: güvenen tip güvensiz bağlantı.

Tamam, size söylendi, ya da kendiniz öğrendim, bu globals (ad alanı çeviri birimi dışında erişilebilen kapsam değişkenleri) şunlardır Kötülük.™ Ama ne kadar kötü olduklarını biliyor muydun? Düşünün aşağıdaki program, iki dosya [ana.cpp] ve [sayılar.cpp]:

// [main.cpp]
#include <iostream>

extern int* numbers;

int main()
{
    using namespace std;
    for( int i = 0;  i < 42;  ++i )
    {
        cout << (i > 0? ", " : "") << numbers[i];
    }
    cout << endl;
}

// [numbers.cpp]
int numbers[42] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
Windows 7'de

bu derlemeler ve bağlantılar hem MinGW g++ 4.4.1 hem de iyi Visual C ++ 10.0.

türler eşleşmediğinden, çalıştırdığınızda program çöker.

The Windows 7 crash dialog

resmi açıklama: programın tanımsız davranışı (UB) vardır ve bunun yerine çökmesini bu nedenle sadece asmak, ya da belki hiçbir şey yapmak, ya da başkanlarına e-postalar threating gönderebilirsiniz ABD, Rusya, Hindistan, Çin ve İsviçre ve burun cinleri burnunuzdan uçmak olun.

uygulama içi açıklama: main.cpp de Dizi bir işaretçi olarak kabul edilir, yerleştirilir diziyle aynı adreste. 32-bit yürütülebilir için bu ilk anlamına gelir int dizideki değer, bir işaretçi olarak kabul edilir. Yani... main.cpp numbers değişken içerir veya (int*)1 içeren görünür . Bu, hafızaya erişmek için program adres alanının çok alt kısmı, geleneksel olarak ayrılmış ve tuzağa neden olan. Sonuç: bir kaza olsun.

Derleyiciler, bu hatayı teşhis etmemek için tamamen kendi hakları içindedir, çünkü C ++ 11 §3.5/10, uyumlu türlerin gereksinimi hakkında diyor bildirimler için

[N3290 §3.5/10]

Bu kuralın tür kimliği üzerinde ihlali bir teşhis gerektirmez.

aynı paragraf izin verilen varyasyonu ayrıntıları:

... bir dizi nesnesi için bildirimler, bağlı büyük bir dizinin varlığı veya yokluğu ile farklılık gösterir (8.3.4).

bu izin verilen varyasyon, Bir dizide bir ad bildirmeyi içermez çeviri birimi ve başka bir çeviri birimi bir işaretçi olarak.

5.2 Pitfall: erken optimizasyon yapmak ( memset ve arkadaşlar).

henüz yazılmamış

5.3 Pitfall: öğelerin sayısını almak için C deyimini kullanma.

derin C deneyimi ile yazmak doğal ...

#define N_ITEMS( array )   (sizeof( array )/sizeof( array[0] ))

bir array , gerektiğinde ilk öğeye işaret etmek için bozunur ifade sizeof(a)/sizeof(a[0]) olarak da yazılabilir sizeof(a)/sizeof(*a) . Bu aynı anlamına gelir ve nasıl olursa olsun dizinin sayı öğelerini bulmak için C deyim yazılı.

ana pitfall: C deyim typesafe değildir. Örneğin, kod ...

#include <stdio.h>

#define N_ITEMS( array ) (sizeof( array )/sizeof( *array ))

void display( int const a[7] )
{
    int const   n = N_ITEMS( a );          // Oops.
    printf( "%d elements.\n", n );
}

int main()
{
    int const   moohaha[]   = {1, 2, 3, 4, 5, 6, 7};

    printf( "%d elements, calling display...\n", N_ITEMS( moohaha ) );
    display( moohaha );
}

N_ITEMS için bir işaretçi geçer ve bu nedenle büyük olasılıkla yanlış üretir sonuç. Windows 7'de 32-bit yürütülebilir dosya olarak derlenmiş ...

7 elemanları, arama ekranı...

1 element.

  1. derleyici int const a[7] sadece int const a[] yeniden yazar .
  2. derleyici int const a[] için int const* a yeniden yazar .
  3. N_ITEMS bu nedenle bir işaretçi ile çağrılır.
  4. 32-bit yürütülebilir dosya için sizeof(array) (bir işaretçinin boyutu) daha sonra 4'tür.
  5. sizeof(*array) eşdeğerdir sizeof(int) , 32-bit yürütülebilir dosya için de 4.

bu hatayı çalışma zamanında tespit etmek için ...

#include <assert.h>
#include <typeinfo>

#define N_ITEMS( array )       (                               \
    assert((                                                    \
        "N_ITEMS requires an actual array as argument",        \
        typeid( array ) != typeid( &*array )                    \
        )),                                                     \
    sizeof( array )/sizeof( *array )                            \
    )

7 elemanları, arama ekranı...

Onaylama işlemi başarısız oldu: ("n_ıtems bağımsız değişken olarak gerçek bir dizi gerektirir", typeıd (a)!= typeıd (&*a)), dosya runtime_detect iyon.cpp, hat 16

bu uygulama için çalışma zamanı talep etti alışılmadık bir şekilde sonlandırın.

Daha fazla bilgi için uygulamanın Destek ekibine başvurun.

çalışma zamanı hata algılama hiçbir algılama daha iyidir, ama biraz atıklar işlemci zamanı ve belki de çok daha fazla programcı zamanı. Algılama ile daha iyi derleme zamanı! Ve C++98 ile yerel tür dizilerini desteklememekten mutluluk duyarsanız, sonra bunu yapabilirsiniz:

#include <stddef.h>

typedef ptrdiff_t   Size;

template< class Type, Size n >
Size n_items( Type (&)[n] ) { return n; }

#define N_ITEMS( array )       n_items( array )

. bu tanım, g++ile ilk tam programa ikame edilmiştir, Ben ...

M:\count > g++ compile_time_detection.cpp

compile_time_detection.cpp: işlev ' geçersiz ekran (const ınt*)':

compile_time_detection.cpp: 14: hata: 'n_ıtems(const int*&)'

çağrısı için eşleşen işlev yok

M:\count > _

nasıl çalışır: dizi ' n_items referansla geçirilir ve bu yüzden yapar ilk öğeye işaretçi için değil, ve fonksiyon sadece döndürebilirsiniz Türüne göre belirtilen öğelerin sayısı.

C ++ 11 ile bunu yerel tür diziler için de kullanabilirsiniz ve bu tür güvenli C++ deyim bir dizinin elemanlarının sayısını bulmak için.

5.4 c ++ 11 & C ++ 14 pitfall: constexpr dizi boyutunu kullanma işlev.

C++11 ile ve daha sonra doğal, ama tehlikeli görürsünüz!, için C ++ 03 işlevini değiştirin

typedef ptrdiff_t   Size;

template< class Type, Size n >
Size n_items( Type (&)[n] ) { return n; }

ile

using Size = ptrdiff_t;

template< class Type, Size n >
constexpr auto n_items( Type (&)[n] ) -> Size { return n; }

önemli değişimin constexpr kullanımı olduğu yer derleme zamanı sabiti üretmek için bu işlev .

örneğin, C++03 işlevinin aksine, böyle bir derleme zamanı sabiti için kullanılabilir

: başka bir aynı boyutta bir dizi beyan
// Example 1
void foo()
{
    int const x[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 4};
    constexpr Size n = n_items( x );
    int y[n] = {};
    // Using y here.
}

ancak constexpr sürümünü kullanarak bu kodu düşünün:

// Example 2
template< class Collection >
void foo( Collection const& c )
{
    constexpr int n = n_items( c );     // Not in C++14!
    // Use c here
}

auto main() -> int
{
    int x[42];
    foo( x );
}

pitfall: Temmuz 2015 itibariyle yukarıdaki mıngw-64 5.1.0 ile derlenir -pedantic-errors ve, de çevrimiçi Derleyiciler ile test gcc.godbolt.org / , ayrıca clang 3.0 ile ve clang 3.2, ama clang ile değil 3.3, 3.4.1, 3.5.0, 3.5.1, 3.6 (rc1) veya 3.7 (deneysel). Ve önemli Windows platformu için derlenmez Visual C++ 2015 ile. Bunun nedeni, C++11/C++14 ifadesinin kullanımı ile ilgili olmasıdır constexpr ifadelerinde referanslar:

C ++ 11 C ++ 14 $ 5.19 / 2 dokuz th çizgi

a koşullu ifade e bir çekirdek sabit ifade değerlendirme sürece e , kuralları takip soyut makine (1.9), birini değerlendirecek aşağıdaki ifadeler:

        # 1519540920 #

  • an ID-ifade bir değişken veya başvuru türü veri üyesi anlamına gelir başvuru bir önceki başlatma ve ya yoksa
    • sabit bir ifade veya
    • ile başlatılır
    • bir nesnenin statik olmayan bir veri üyesidir kimin ömrü içinde başladı E'nin değerlendirilmesi;

her zaman daha ayrıntılı

yazabilirsiniz
// Example 3  --  limited

using Size = ptrdiff_t;

template< class Collection >
void foo( Collection const& c )
{
    constexpr Size n = std::extent< decltype( c ) >::value;
    // Use c here
}

... ancak Collection ham bir dizi olmadığında bu başarısız olur.

olmayan diziler olabilir koleksiyonları ile başa çıkmak için bir overloadability ihtiyacı n_items işlevi, aynı zamanda, derleme zamanı kullanımı için bir derleme zamanı gerekir temsil dizi boyutu. Ve iyi çalışan klasik C++03 çözümü ayrıca C++11 ve C ++ 14'te, işlevin sonucunu bir değer olarak bildirmesine izin vermektir ancak işlev sonucu türü ile . Örneğin şöyle:

// Example 4 - OK (not ideal, but portable and safe)

#include <array>
#include <stddef.h>

using Size = ptrdiff_t;

template< Size n >
struct Size_carrier
{
    char sizer[n];
};

template< class Type, Size n >
auto static_n_items( Type (&)[n] )
    -> Size_carrier<n>;
// No implementation, is used only at compile time.

template< class Type, size_t n >        // size_t for g++
auto static_n_items( std::array<Type, n> const& )
    -> Size_carrier<n>;
// No implementation, is used only at compile time.

#define STATIC_N_ITEMS( c ) \
    static_cast<Size>( sizeof( static_n_items( c ).sizer ) )

template< class Collection >
void foo( Collection const& c )
{
    constexpr Size n = STATIC_N_ITEMS( c );
    // Use c here
    (void) c;
}

auto main() -> int
{
    int x[42];
    std::array<int, 43> y;
    foo( x );
    foo( y );
}

için dönüş türü seçimi hakkında static_n_items : bu kod std::integral_constant kullanmaz çünkü std::integral_constant ile sonuç temsil edilir doğrudan bir constexpr değeri olarak, orijinal sorunu yeniden. Yerine Size_carrier sınıfı bir fonksiyonun doğrudan A'ya dönmesine izin verebilir bir dizi için referans. Ancak, herkes bu sözdizimine aşina değildir.

adlandırma hakkında: constexpr için bu çözümün bir parçası-geçersiz-due-to-reference sorun derleme zamanı sabit açık seçim yapmaktır.

umarım oops-there-was-a-reference-in-in-your - constexpr sorunu ile giderilecektir C ++ 17, ancak o zamana kadar bir makro gibi yukarıdaki STATIC_N_ITEMS taşınabilirlik verir, örneğin, clang ve Visual C++ derleyicilerine, tutma türü güvenliği.

ilgili: makrolar kapsamları saygı yok, bu yüzden adı çarpışmaları önlemek için bir olabilir bir isim öneki kullanmak için iyi bir fikir, örneğin MYLIB_STATIC_N_ITEMS .

65
cevap Cheers and hth. - Alf 2015-07-31 03:15:12
kaynak