Bir dizinin tüm öğelerinin C ++ 'da bir varsayılan değere başlatılması?


248

C ++ Notları: Dizi Başlatma , dizilerin başlatılması üzerinde güzel bir listeye sahiptir. Sahibim

int array[100] = {-1};

-1 ile dolu olmasını beklemek ama değil, sadece ilk değer ve geri kalanı 0 rastgele değerlerle karıştırılır.

Kod

int array[100] = {0};

gayet iyi çalışır ve her öğeyi 0 olarak ayarlar.

Ben burada ne eksik .. Değeri sıfır değilse kimse başlatamaz mı?

Ve 2: Varsayılan başlatma (yukarıdaki gibi) tüm dizi boyunca olağan döngüden daha hızlı mıdır ve bir değer atar mı yoksa aynı şeyi yapar mı?


1
C ve C ++ 'da davranış farklıdır. C {0} 'da bir yapı başlatıcısı için özel bir durumdur, ancak AFAIK diziler için değildir. int dizi [100] = {0}, bir yan etki olarak diğer tüm öğeleri sıfırlayacak olan dizi [100] = {[0] = 0} ile aynı olmalıdır. AC derleyici yukarıda açıklandığı gibi davranmamalıdır, bunun yerine int dizi [100] = {- 1} ilk öğeyi -1'e, geri kalanını 0'a (gürültü olmadan) ayarlamalıdır. C'de bir yapı x diziniz [100] varsa, başlatıcı olarak = {0} kullanmak geçerli DEĞİLDİR. İlk öğeyi başlatacak ve diğerlerini sıfırlayacak {çoğu {0}} kullanabilirsiniz, çoğu durumda aynı şey olacaktır.
Fredrik Widlund

1
@FredrikWidlund Her iki dilde de aynı. {0}yapılar veya diziler için özel bir durum değildir. Kural, başlatıcısı olmayan öğelerin, bir başlatıcısıymış gibi 0başlatılmalarıdır. İç içe toplamalar (örn. struct x array[100]) Varsa, başlatıcılar toplanmayanlara "satır-büyük" sırayla uygulanır; bunu yaparken isteğe bağlı olarak parantezler atlanabilir. struct x array[100] = { 0 }C'de geçerlidir; ve ilk üye başlatıcı olarak struct Xkabul ettiği sürece C ++ 'da geçerlidir 0.
MM

1
{ 0 }C'de özel değildir, ancak herhangi bir kurucu olmadığından ve 0dolaylı olarak bir şeye dönüştürülüp atanmaktan vazgeçmenin bir yolu olmadığından, kendisiyle başlatılamayacak bir veri türü tanımlamak çok daha zordur .
Leushenko

3
Diğer soru C ile ilgili olduğu için yeniden açılması için oy verildi. C'de geçerli olmayan bir diziyi başlatmak için birçok C ++ yolu vardır.
xskxzr

1
Ayrıca yeniden açılmak için oy verdi - C ve C ++ farklı dillerdir
Pete

Yanıtlar:


350

Kullandığınız sözdizimini kullanarak,

int array[100] = {-1};

" atlanan tüm öğeler " olarak ayarlandığından "ilk öğeyi -1ve geri kalanını 0" olarak ayarlayın 0.

C ++ 'da hepsini ayarlamak için (from ) -1gibi bir şey kullanabilirsiniz :std::fill_n<algorithm>

std::fill_n(array, 100, -1);

Taşınabilir C'de, kendi döngünüzü yuvarlamanız gerekir. Derleyici uzantıları vardır veya kabul edilebilir durumdaysa, kısayol olarak uygulama tanımlı davranışa güvenebilirsiniz.


14
Bu aynı zamanda dizinin varsayılan değerlerle "kolayca" doldurulması hakkında dolaylı bir soruyu da yanıtladı. Teşekkür ederim.
Milan

7
@chessofnerd: tam olarak değil #include <algorithm>, doğru başlık, <vector>uygulamanıza bağlı olacak şekilde dolaylı olarak içerebilir veya içermeyebilir.
Evan Teran

2
Çalışma zamanı sırasında diziyi başlatmak için başvurmanıza gerek yoktur. Statik olarak gerçekleşmesi için gerçekten başlatmaya ihtiyacınız varsa, istenen ints dizisini oluşturmak ve dizinin başlatıcısına genişletmek için varyasyon şablonları ve varyasyon dizileri kullanmak mümkündür .
void-pointer

2
@ontherocks, hayır fill_n2B dizinin tamamını doldurmak için tek bir çağrı kullanmanın doğru bir yolu yoktur . Diğerini doldururken bir boyuta geçmelisiniz.
Evan Teran

7
Bu başka bir sorunun cevabı. std::fill_nbaşlatma değil.
Ben Voigt

133

Gcc derleyicisine sözdizimine izin veren bir uzantı var:

int array[100] = { [0 ... 99] = -1 };

Bu, tüm öğeleri -1 olarak ayarlar.

Bu, "Özel Başlatıcılar" olarak bilinir . Daha fazla bilgi için buraya bakın .

Bunun gcc c ++ derleyicisi için uygulanmadığını unutmayın.


2
Muhteşem. Bu sözdizimi clang'da da çalışıyor gibi görünüyor (bu nedenle iOS / Mac OS X'te kullanılabilir).
JosephH

31

Bağlantı verdiğiniz sayfa zaten ilk bölüme cevap verdi:

Açık bir dizi boyutu belirtilir, ancak daha kısa bir başlatma listesi belirtilirse, belirtilmemiş öğeler sıfıra ayarlanır.

Tüm diziyi sıfır dışında bir değere başlatmak için yerleşik bir yol yoktur.

Hangisi daha hızlı ise, olağan kural geçerlidir: "Derleyiciye en fazla özgürlüğü veren yöntem muhtemelen daha hızlıdır".

int array[100] = {0};

derleyiciye "bu 100 inti sıfıra ayarla" der ve derleyicinin serbestçe optimize edebileceğini söyler.

for (int i = 0; i < 100; ++i){
  array[i] = 0;
}

çok daha spesifik. Derleyiciye bir yineleme değişkeni yaratmasını isöyler, ona öğelerin başlatılma sırasını vb. Söyler. Tabii ki, derleyici bunu optimize edecek gibi görünüyor, ancak mesele burada sorunu fazla tahmin ediyorsunuz, derleyiciyi aynı sonuca ulaşmak için daha fazla çalışmaya zorluyor.

Son olarak, diziyi sıfırdan farklı bir değere ayarlamak istiyorsanız, (en azından C ++ 'da) şunu kullanmalısınız std::fill:

std::fill(array, array+100, 42); // sets every value in the array to 42

Yine, bir dizi ile aynı şeyi yapabilirsiniz, ancak bu daha özlüdür ve derleyiciye daha fazla özgürlük verir. Sadece tüm dizinin 42 değeriyle doldurulmasını istediğinizi söylüyorsunuz. Hangi sırayla yapılması gerektiği ya da başka bir şey hakkında hiçbir şey söylemiyorsunuz.


5
İyi cevap. C ++ 'da (C değil) int array [100] = {}; ve derleyiciye en özgürlüğü ver :)
Johannes Schaub - litb

1
kabul, mükemmel cevap. Ancak sabit boyutlu bir dizi için std :: fill_n :-P kullanır.
Evan Teran

12

C ++ 11 başka bir (kusurlu) seçeneğe sahiptir:

std::array<int, 100> a;
a.fill(-1);

veyastd::fill(begin(a), end(a), -1)
doctorlai

9

{} İle öğeleri bildirildikleri gibi atarsınız; geri kalanı 0 ile başlatılır.

= {}Canlandırılacak bir şey yoksa , içerik tanımsızdır.


8

Bağlantı kurduğunuz sayfa

Açık bir dizi boyutu belirtilir, ancak daha kısa bir başlatma listesi belirtilirse, belirtilmemiş öğeler sıfıra ayarlanır.

Hız sorunu: Bu küçük diziler için herhangi bir fark göz ardı edilebilir. Büyük dizilerle çalışıyorsanız ve hız boyuttan çok daha önemliyse, varsayılan değerlerin bir sabit dizisine (derleme zamanında başlatılır) ve sonra memcpybunları değiştirilebilir diziye sahip olabilirsiniz.


2
memcpy çok iyi bir fikir değildir, çünkü bu sadece değerleri doğrudan akıllıca ayarlamakla karşılaştırılabilir.
Evan Teran

1
Kopyalama ve const dizi ihtiyacını görmüyorum: Neden önceden doldurulmuş değerleri ile ilk etapta değiştirilebilir dizi oluşturmuyorsunuz?
Johannes Schaub - litb

Hız açıklaması ve hızın büyük bir dizi boyutuyla ilgili bir sorun olması durumunda (benim durumumda) nasıl yapacağınız için teşekkürler
Milan

Başlatıcı listesi derleme zamanında yapılır ve çalışma zamanında yüklenir. Bir şeyleri kopyalamaya gerek yok.
Martin York

@litb, @Evan: Örneğin gcc, optimizasyonlar etkin olsa bile dinamik başlatma (birçok movs) üretir. İçin büyük diziler ve sıkı performans gereksinimleri, derleme zamanında init yapmak istiyorum. memcpy, büyük kopyalar için tek başına çok sayıda düz movs'tan daha iyi optimize edilmiştir .
20'de laalto

4

Diziyi ortak bir değere başlatmanın bir başka yolu, bir dizi tanımdaki öğelerin listesini oluşturmaktır:

#define DUP1( X ) ( X )
#define DUP2( X ) DUP1( X ), ( X )
#define DUP3( X ) DUP2( X ), ( X )
#define DUP4( X ) DUP3( X ), ( X )
#define DUP5( X ) DUP4( X ), ( X )
.
.
#define DUP100( X ) DUP99( X ), ( X )

#define DUPx( X, N ) DUP##N( X )
#define DUP( X, N ) DUPx( X, N )

Bir diziyi ortak bir değere başlatmak kolayca yapılabilir:

#define LIST_MAX 6
static unsigned char List[ LIST_MAX ]= { DUP( 123, LIST_MAX ) };

Not: DUP ile parametrelerde makro değiştirmeyi etkinleştirmek için DUPx tanıtıldı


3

Bir dizi tek baytlık öğe söz konusu olduğunda, tüm öğeleri aynı değere ayarlamak için memset komutunu kullanabilirsiniz.

Burada bir örnek var .


3

std::arrayBunu kullanarak , C ++ 14'te oldukça basit bir şekilde yapabiliriz. Sadece C ++ 11'de yapmak mümkündür, ancak biraz daha karmaşıktır.

Arayüzümüz derleme zamanı boyutu ve varsayılan değerdir.

template<typename T>
constexpr auto make_array_n(std::integral_constant<std::size_t, 0>, T &&) {
    return std::array<std::decay_t<T>, 0>{};
}

template<std::size_t size, typename T>
constexpr auto make_array_n(std::integral_constant<std::size_t, size>, T && value) {
    return detail::make_array_n_impl<size>(std::forward<T>(value), std::make_index_sequence<size - 1>{});
}


template<std::size_t size, typename T>
constexpr auto make_array_n(T && value) {
    return make_array_n(std::integral_constant<std::size_t, size>{}, std::forward<T>(value));
}

Üçüncü işlev esas olarak kolaylık sağlamak içindir, bu nedenle kullanıcının bir std::integral_constant<std::size_t, size> kendini , çünkü bu oldukça garip bir yapıdır. Gerçek çalışma ilk iki fonksiyondan biri tarafından yapılır.

İlk aşırı yükleme oldukça basittir: std::array0 boyutunda bir yapı oluşturur. Kopyalamaya gerek yoktur, sadece inşa ederiz.

İkinci aşırı yük biraz daha hileli. Kaynak olarak aldığı değer boyunca ilerler ve aynı zamanda make_index_sequencebaşka bir uygulama işlevinin bir örneğini oluşturur ve sadece bazı uygulama işlevlerini çağırır. Bu işlev neye benziyor?

namespace detail {

template<std::size_t size, typename T, std::size_t... indexes>
constexpr auto make_array_n_impl(T && value, std::index_sequence<indexes...>) {
    // Use the comma operator to expand the variadic pack
    // Move the last element in if possible. Order of evaluation is well-defined
    // for aggregate initialization, so there is no risk of copy-after-move
    return std::array<std::decay_t<T>, size>{ (static_cast<void>(indexes), value)..., std::forward<T>(value) };
}

}   // namespace detail

Bu, geçirdiğimiz değeri kopyalayarak birinci boyut - 1 bağımsız değişkenlerini oluşturur. Burada, varadik parametre paketi dizinlerimizi genişletmek için bir şey olarak kullanıyoruz. Bu pakette boyut - 1 giriş var (yapımında belirtildiği gibi make_index_sequence) ve 0, 1, 2, 3, ..., boyut - 2 değerlerine sahipler. Ancak, değerleri umursamıyoruz ( böylece derleyici uyarılarını susturmak için geçersiz kıldık). Parametre paketi genişletmesi, kodumuzu böyle bir şeye genişletir (size == 4 varsayarak):

return std::array<std::decay_t<T>, 4>{ (static_cast<void>(0), value), (static_cast<void>(1), value), (static_cast<void>(2), value), std::forward<T>(value) };

Bu parantezleri, varyasyon paketi genişletmesinin ...istediğimizi genişletmesini sağlamak ve virgül operatörünü kullandığımızdan emin olmak için kullanıyoruz. Parantezler olmadan dizi başlangıcımıza bir grup argüman geçiriyor gibi görünebiliriz, ancak gerçekten dizini değerlendiririz, boşluğa çeviririz, bu geçersiz sonucu yok sayarız ve sonra diziye kopyalanan değeri döndürürüz .

Son argüman, dediğimiz std::forwardşey, küçük bir optimizasyon. Birisi geçici bir std :: string'i geçerse ve "bunlardan 5'ini bir dizi oluştur" diyorsa, 5 kopya yerine 4 kopya ve 1 hamle yapmak istiyoruz. std::forwardBu işi olmasını sağlar.

Başlıklar ve bazı birim testleri de dahil olmak üzere tam kod:

#include <array>
#include <type_traits>
#include <utility>

namespace detail {

template<std::size_t size, typename T, std::size_t... indexes>
constexpr auto make_array_n_impl(T && value, std::index_sequence<indexes...>) {
    // Use the comma operator to expand the variadic pack
    // Move the last element in if possible. Order of evaluation is well-defined
    // for aggregate initialization, so there is no risk of copy-after-move
    return std::array<std::decay_t<T>, size>{ (static_cast<void>(indexes), value)..., std::forward<T>(value) };
}

}   // namespace detail

template<typename T>
constexpr auto make_array_n(std::integral_constant<std::size_t, 0>, T &&) {
    return std::array<std::decay_t<T>, 0>{};
}

template<std::size_t size, typename T>
constexpr auto make_array_n(std::integral_constant<std::size_t, size>, T && value) {
    return detail::make_array_n_impl<size>(std::forward<T>(value), std::make_index_sequence<size - 1>{});
}

template<std::size_t size, typename T>
constexpr auto make_array_n(T && value) {
    return make_array_n(std::integral_constant<std::size_t, size>{}, std::forward<T>(value));
}



struct non_copyable {
    constexpr non_copyable() = default;
    constexpr non_copyable(non_copyable const &) = delete;
    constexpr non_copyable(non_copyable &&) = default;
};

int main() {
    constexpr auto array_n = make_array_n<6>(5);
    static_assert(std::is_same<std::decay_t<decltype(array_n)>::value_type, int>::value, "Incorrect type from make_array_n.");
    static_assert(array_n.size() == 6, "Incorrect size from make_array_n.");
    static_assert(array_n[3] == 5, "Incorrect values from make_array_n.");

    constexpr auto array_non_copyable = make_array_n<1>(non_copyable{});
    static_assert(array_non_copyable.size() == 1, "Incorrect array size of 1 for move-only types.");

    constexpr auto array_empty = make_array_n<0>(2);
    static_assert(array_empty.empty(), "Incorrect array size for empty array.");

    constexpr auto array_non_copyable_empty = make_array_n<0>(non_copyable{});
    static_assert(array_non_copyable_empty.empty(), "Incorrect array size for empty array of move-only.");
}

Sizin non_copyabletipi vasıtasıyla aslında copyable olduğunu operator=.
Hertz

Sanırım non_copy_constructiblenesne için daha doğru bir isim olurdu. Ancak, bu kodun hiçbir yerinde atama yoktur, bu nedenle bu örnek için önemli değildir.
David Stone

1

1) Bir başlatıcı kullandığınızda, bir yapı veya böyle bir dizi için, belirtilmemiş değerler esasen varsayılan olarak yapılandırılır. Ints gibi ilkel tipte, bu sıfırlanacakları anlamına gelir. Bunun özyinelemeli olarak geçerli olduğunu unutmayın: diziler içeren bir dizi yapıya sahip olabilirsiniz ve ilk yapının yalnızca ilk alanını belirtirseniz, geri kalanların tümü sıfırlar ve varsayılan kurucularla başlatılır.

2) Derleyici muhtemelen en azından elle yapabileceğiniz kadar iyi bir başlangıç ​​kodu üretecektir. Mümkünse derleyicinin başlatmayı benim için yapmasına izin verme eğilimindeyim.


1) POD'ların varsayılan başlatılması burada gerçekleşmiyor. Listeyi kullanarak derleyici değerleri derleme zamanında oluşturur ve bunları, program başlatmanın bir parçası olarak (kod gibi) yüklenen özel bir bölüme yerleştirir. Böylece maliyet çalışma zamanında sıfırdır.
Martin York

1
Nerede yanlış olduğunu görmüyorum? int a [100] = {} kesinlikle göründüğü yere bakılmaksızın 0'ın tümüne başlatılır ve {int a; } b [100] = {}; de öyle. msgstr "temelde varsayılan inşa edilmiş" => "inşa edilmiş değer", tho. Ancak, kullanıcı tarafından bildirilen ctorlara sahip ints, PODS veya türlerde bu önemli değildir. Sadece bildiğim kadarıyla kullanıcı tarafından bildirilen ctor'lar olmadan NON-Pod'lar için önemlidir. Ama bu yüzden aşağı (!) Oy kullanmam. neyse, tekrar 0 yapmak için +1 :)
Johannes Schaub - litb

@Evan: İfademi "Başlatıcı kullandığınızda ..." ile nitelendirdim. Başlatılmamış değerlere başvurmuyordum. @Martin: Bu sabit, statik veya genel veriler için işe yarayabilir. Ancak bunun nasıl bir şeyle çalışacağını göremiyorum: int test () {int i [10] = {0}; int v = i [0]; I [0] 5 =; dönüş v; } Derleyici, test () işlevini her çağırışınızda i [] değerini sıfırlara sıfırlamalıydı.
Boojum

verileri statik veri segmentine yerleştirebilir ve "i" yi referans gösterebilir :)
Johannes Schaub - litb

Doğru - teknik olarak, bu durumda tamamen "i" yi seçip sadece 0'a dönebilir. Ancak değişken veri için statik veri segmentini kullanmak çok iş parçacıklı ortamlarda tehlikeli olabilir. Martin'e cevap vermeye çalıştığım nokta, başlatma maliyetini tamamen ortadan kaldıramayacağınızdı. Önceden yapılmış bir yığını statik veri segmentinden kopyalayın, ama yine de ücretsiz değil.
Boojum


0

C ++ programlama dilinde V4, Stroustrup yerleşik diziler üzerinde vektörlerin veya valarraların kullanılmasını önerir. Valarrary's ile, bunları oluşturduğunuzda, bunları aşağıdaki gibi belirli bir değere başlatabilirsiniz:

valarray <int>seven7s=(7777777,7);

7 77 uzunluğunda bir dizi başlatmak için "7777777".

Bu, cevabı "düz eski C" dizisi yerine bir C ++ veri yapısı kullanarak uygulamanın bir C ++ yoludur.

Valarray kodumu C ++ 'isms v. C'isms kullanmaya çalışmak için bir girişim olarak kullanmaya geçti ....


Bu şimdiye kadar gördüğüm bir tür nasıl kullanılır ikinci en kötü örnek ...
Steazy

-3

Standart bir özellik olmalı, ancak bir nedenden dolayı standart C veya C ++ 'da yer almıyor ...

#include <stdio.h>

 __asm__
 (
"    .global _arr;      "
"    .section .data;    "
"_arr: .fill 100, 1, 2; "
 );

extern char arr[];

int main() 
{
    int i;

    for(i = 0; i < 100; ++i) {
        printf("arr[%u] = %u.\n", i, arr[i]);
    }
}

Fortran'da şunları yapabilirsiniz:

program main
    implicit none

    byte a(100)
    data a /100*2/
    integer i

    do i = 0, 100
        print *, a(i)
    end do
end

ama imzasız numaraları yok ...

Neden C / C ++ bunu uygulayamıyor? Gerçekten bu kadar zor mu? Aynı sonucu elde etmek için bunu manuel olarak yazmak çok saçma ...

#include <stdio.h>
#include <stdint.h>

/* did I count it correctly? I'm not quite sure. */
uint8_t arr = {
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
};    

int main() 
{
    int i;

    for(i = 0; i < 100; ++i) {
        printf("arr[%u] = %u.\n", i, arr[i]);
    }
}

Ya 1.000,00 baytlık bir dizi olsaydı? Benim için yazmak için bir senaryo yazmam veya montaj / vb. Bu saçmalık.

Tamamen portatiftir, dilde olmamasının bir nedeni yoktur.

Sadece hackleyin:

#include <stdio.h>
#include <stdint.h>

/* a byte array of 100 twos declared at compile time. */
uint8_t twos[] = {100:2};

int main()
{
    uint_fast32_t i;
    for (i = 0; i < 100; ++i) {
        printf("twos[%u] = %u.\n", i, twos[i]);
    }

    return 0;
}

Onu hacklemenin bir yolu önişlemdir ... (Aşağıdaki kod kenar vakaları kapsamaz, ancak neler yapılabileceğini hızlı bir şekilde göstermek için yazılmıştır.)

#!/usr/bin/perl
use warnings;
use strict;

open my $inf, "<main.c";
open my $ouf, ">out.c";

my @lines = <$inf>;

foreach my $line (@lines) {
    if ($line =~ m/({(\d+):(\d+)})/) {
        printf ("$1, $2, $3");        
        my $lnew = "{" . "$3, "x($2 - 1) . $3 . "}";
        $line =~ s/{(\d+:\d+)}/$lnew/;
        printf $ouf $line;
    } else {
        printf $ouf $line;
    }
}

close($ouf);
close($inf);

bir döngüde yazdırıyorsunuz, neden bir döngüde atayamazsınız?
Abhinav Gauniyal

1
bir döngü içine atamak, çalışma zamanı yüküne neden olur; arabellek kodlaması zaten serbesttir çünkü tampon zaten ikiliye gömülüdür, bu nedenle program her çalıştığında diziyi sıfırdan oluşturmak için zaman kaybetmez. bir döngüde yazdırmanın genel olarak iyi bir fikir olmadığı doğru, her bir printf çağrısı bir sistem çağrısı gerektirdiğinden, uygulamanın yığınını / yığınını kullanarak dize birleştirme gerektirmediğinden, döngü içine eklemek ve bir kez yazdırmak daha iyidir. Bu tür bir programdaki boyut bir özensizlik olduğundan, bu diziyi çalışma zamanında değil derleme zamanında oluşturmak en iyisidir.
Dmitry

"bir döngü içinde atama çalışma zamanı yükü oluşturur" - Optimize ediciyi ciddi şekilde küçümsersiniz.
Asu

Dizinin boyutuna bağlı olarak, gcc ve clang, değeri "hardcode" olarak ya da kandırır ve daha büyük dizilerle, memset"hardcoded" dizisi ile bile, sadece bunu yapar.
Asu
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.