Inisialisasi semua elemen array ke satu nilai default di C ++?

248

C ++ Catatan: Array Inisialisasi memiliki daftar yang bagus atas inisialisasi array. Saya punya

int array[100] = {-1};

mengharapkannya menjadi penuh dengan -1 tetapi tidak, hanya nilai pertama dan sisanya adalah 0 dicampur dengan nilai acak.

Kode

int array[100] = {0};

berfungsi dengan baik dan menetapkan setiap elemen ke 0.

Apa yang saya lewatkan di sini .. Tidak bisakah seseorang menginisialisasi jika nilainya tidak nol?

Dan 2: Apakah inisialisasi default (seperti di atas) lebih cepat daripada loop biasa melalui seluruh array dan menetapkan nilai atau apakah itu melakukan hal yang sama?

Milan
sumber
1
Perilaku dalam C dan C ++ berbeda. Dalam C {0} adalah kasus khusus untuk initializer struct, namun AFAIK tidak untuk array. int array [100] = {0} harus sama dengan array [100] = {[0] = 0}, yang sebagai efek samping akan nol semua elemen lainnya. Kompiler AC TIDAK boleh berperilaku seperti yang Anda jelaskan di atas, sebaliknya int array [100] = {- 1} harus mengatur elemen pertama menjadi -1 dan sisanya menjadi 0 (tanpa noise). Dalam C jika Anda memiliki struct x array [100], menggunakan = {0} sebagai inisialisasi TIDAK valid. Anda dapat menggunakan {{0}} yang akan menginisialisasi elemen pertama dan nol semua yang lain, dalam banyak kasus akan menjadi hal yang sama.
Fredrik Widlund
1
@FredrikWidlund Sama dalam kedua bahasa. {0}bukan kasus khusus untuk struct atau array. Aturannya adalah elemen tanpa inisialisasi diinisialisasi seolah-olah mereka memiliki 0inisialisasi. Jika ada agregat bersarang (mis. struct x array[100]) Maka inisialisasi diterapkan pada non-agregat dalam urutan "baris-utama"; kawat gigi secara opsional dapat dihilangkan dengan melakukan hal ini. struct x array[100] = { 0 }valid dalam C; dan valid dalam C ++ selama anggota pertama struct Xaccepts 0sebagai initializer.
MM
1
{ 0 }tidak istimewa dalam C, tetapi jauh lebih sulit untuk menentukan tipe data yang tidak dapat diinisialisasi dengan itu karena tidak ada konstruktor dan dengan demikian tidak ada cara untuk berhenti 0dari yang secara implisit dikonversi dan ditugaskan ke sesuatu .
Leushenko
3
Memilih untuk membuka kembali karena pertanyaan lain adalah tentang C. Ada banyak cara C ++ untuk menginisialisasi array yang tidak valid dalam C.
xskxzr
1
Juga memilih untuk membuka kembali - C dan C ++ adalah bahasa yang berbeda
Pete

Jawaban:

350

Menggunakan sintaks yang Anda gunakan,

int array[100] = {-1};

mengatakan "atur elemen pertama ke -1dan sisanya ke 0" karena semua elemen yang dihilangkan diatur ke 0.

Di C ++, untuk mengatur semuanya -1, Anda dapat menggunakan sesuatu seperti std::fill_n(dari <algorithm>):

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

Di C portabel, Anda harus memutar loop Anda sendiri. Ada ekstensi kompiler atau Anda dapat bergantung pada perilaku yang ditentukan implementasi sebagai pintasan jika itu dapat diterima.

Evan Teran
sumber
14
Itu juga menjawab pertanyaan tidak langsung tentang bagaimana mengisi array dengan nilai default "dengan mudah". Terima kasih.
Milan
7
@chessofnerd: tidak tepat, #include <algorithm>adalah header yang tepat, <vector>mungkin atau tidak mungkin memasukkannya secara tidak langsung, yang akan tergantung pada implementasi Anda.
Evan Teran
2
Anda tidak perlu menggunakan inisialisasi array selama runtime. Jika Anda benar-benar membutuhkan inisialisasi terjadi secara statis, dimungkinkan untuk menggunakan templat variadic dan urutan variadic untuk menghasilkan urutan ints yang diinginkan dan memperluasnya ke inisialisasi array.
void-pointer
2
@ontherocks, tidak ada cara yang benar untuk menggunakan satu panggilan fill_nuntuk mengisi seluruh array 2D. Anda perlu mengulangi satu dimensi, sambil mengisi yang lain.
Evan Teran
7
Ini adalah jawaban untuk beberapa pertanyaan lain. std::fill_nbukan inisialisasi.
Ben Voigt
133

Ada ekstensi ke kompiler gcc yang memungkinkan sintaks:

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

Ini akan mengatur semua elemen ke -1.

Ini dikenal sebagai "Inisialisasi yang Ditunjuk" lihat di sini untuk informasi lebih lanjut.

Catatan ini tidak diterapkan untuk kompiler gcc c ++.

Callum
sumber
2
Luar biasa. Sintaks ini juga tampaknya berfungsi dalam dentang (sehingga dapat digunakan pada iOS / Mac OS X).
JosephH
31

Halaman yang Anda tautkan sudah memberi jawaban ke bagian pertama:

Jika ukuran array eksplisit ditentukan, tetapi daftar initiliaisasi yang lebih pendek ditentukan, elemen yang tidak ditentukan diatur ke nol.

Tidak ada cara bawaan untuk menginisialisasi seluruh array ke beberapa nilai non-nol.

Adapun yang lebih cepat, aturan yang biasa berlaku: "Metode yang memberikan kebebasan paling kompiler mungkin lebih cepat".

int array[100] = {0};

hanya memberitahu kompiler "atur 100 ints ini ke nol", yang dapat dioptimalkan kompiler secara bebas.

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

jauh lebih spesifik. Ini memberitahu kompiler untuk membuat variabel iterasi i, memberi tahu urutan di mana elemen harus diinisialisasi, dan sebagainya. Tentu saja, kompiler cenderung untuk mengoptimalkan itu, tetapi intinya adalah di sini Anda terlalu menentukan masalah, memaksa kompiler bekerja lebih keras untuk mendapatkan hasil yang sama.

Akhirnya, jika Anda ingin mengatur array ke nilai bukan nol, Anda harus (dalam C ++, setidaknya) menggunakan std::fill:

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

Sekali lagi, Anda bisa melakukan hal yang sama dengan array, tetapi ini lebih ringkas, dan memberi kompilator lebih banyak kebebasan. Anda hanya mengatakan bahwa Anda ingin seluruh array diisi dengan nilai 42. Anda tidak mengatakan apa-apa tentang urutan yang harus dilakukan, atau apa pun.

jalf
sumber
5
Jawaban yang bagus. Perhatikan bahwa dalam C ++ (bukan dalam C) Anda dapat melakukan array int [100] = {}; dan memberikan kompiler kebebasan paling :)
Johannes Schaub - litb
1
setuju, jawaban yang bagus. Tetapi untuk array berukuran tetap, itu akan menggunakan std :: fill_n :-P.
Evan Teran
12

C ++ 11 memiliki opsi lain (tidak sempurna):

std::array<int, 100> a;
a.fill(-1);
Timmmm
sumber
ataustd::fill(begin(a), end(a), -1)
doctorlai
9

Dengan {} Anda menetapkan elemen sebagaimana dinyatakan; sisanya diinisialisasi dengan 0.

Jika tidak ada = {}untuk memulai, konten tidak terdefinisi.

0x6adb015
sumber
8

Halaman yang Anda tautkan menyatakan

Jika ukuran array eksplisit ditentukan, tetapi daftar initiliaisasi yang lebih pendek ditentukan, elemen yang tidak ditentukan diatur ke nol.

Masalah kecepatan: Perbedaan apa pun akan diabaikan untuk array sekecil ini. Jika Anda bekerja dengan array besar dan kecepatan jauh lebih penting daripada ukuran, Anda dapat memiliki array const dari nilai default (diinisialisasi pada waktu kompilasi) dan kemudian memcpyke array yang dapat dimodifikasi.

laalto
sumber
2
memcpy itu bukan ide yang sangat bagus, karena itu akan sebanding dengan hanya mengatur nilai-nilai secara langsung dengan cepat.
Evan Teran
1
Saya tidak melihat perlunya copy dan array const: Mengapa tidak membuat array yang dapat dimodifikasi di tempat pertama dengan nilai yang sudah diisi sebelumnya?
Johannes Schaub - litb
Terima kasih atas penjelasan kecepatan dan bagaimana melakukannya jika kecepatan adalah masalah dengan ukuran array besar (yang ada dalam kasus saya)
Milan
Daftar penginisialisasi dilakukan pada waktu kompilasi dan dimuat pada saat runtime. Tidak perlu lagi menyalin sesuatu.
Martin York
@ litb, @Evan: Misalnya gcc menghasilkan inisialisasi dinamis (banyak gerakan) bahkan dengan optimasi yang diaktifkan. Untuk array besar dan persyaratan kinerja yang ketat, Anda ingin melakukan init pada waktu kompilasi. memcpy mungkin lebih baik dioptimalkan untuk salinan besar daripada banyak gerakan biasa saja.
laalto
4

Cara lain menginisialisasi array ke nilai umum, adalah dengan benar-benar menghasilkan daftar elemen dalam serangkaian definisi:

#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 )

Menginisialisasi array ke nilai umum dapat dengan mudah dilakukan:

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

Catatan: DUPx diperkenalkan untuk mengaktifkan substitusi makro dalam parameter ke DUP

Steen
sumber
3

Untuk kasus array elemen bita tunggal, Anda dapat menggunakan memset untuk mengatur semua elemen ke nilai yang sama.

Ada contoh di sini .

Steve Melnikoff
sumber
3

Dengan menggunakan std::array, kita bisa melakukan ini dengan cara yang cukup mudah di C ++ 14. Dimungkinkan untuk melakukannya hanya di C ++ 11, tetapi sedikit lebih rumit.

Antarmuka kami adalah ukuran waktu kompilasi dan nilai default.

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));
}

Fungsi ketiga terutama untuk kenyamanan, sehingga pengguna tidak harus membangun std::integral_constant<std::size_t, size>sendiri, karena itu adalah konstruksi yang cukup bertele-tele. Pekerjaan nyata dilakukan oleh salah satu dari dua fungsi pertama.

Kelebihan pertama cukup mudah: Ini membangun std::arrayukuran 0. Tidak perlu menyalin, kami hanya membangunnya.

Kelebihan kedua agak rumit. Ini meneruskan sepanjang nilai yang didapatnya sebagai sumber, dan juga membangun sebuah instance make_index_sequencedan hanya memanggil beberapa fungsi implementasi lainnya. Seperti apa fungsi itu?

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

Ini mengkonstruksi argumen ukuran - 1 pertama dengan menyalin nilai yang kami berikan. Di sini, kami menggunakan indeks paket parameter variad kami hanya sebagai sesuatu untuk diperluas. Ada entri ukuran - 1 dalam paket itu (seperti yang kami tentukan dalam konstruksi make_index_sequence), dan mereka memiliki nilai 0, 1, 2, 3, ..., ukuran - 2. Namun, kami tidak peduli dengan nilai-nilai tersebut ( jadi kami membatalkannya, untuk membungkam setiap peringatan kompiler). Ekspansi paket parameter memperluas kode kami ke sesuatu seperti ini (dengan asumsi ukuran == 4):

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) };

Kami menggunakan tanda kurung itu untuk memastikan bahwa ekspansi paket variadic ...memperluas apa yang kami inginkan, dan juga untuk memastikan kami menggunakan operator koma. Tanpa tanda kurung, akan terlihat seperti kami melewati banyak argumen untuk inisialisasi array kami, tapi sungguh, kami mengevaluasi indeks, membuangnya untuk membatalkan, mengabaikan hasil yang batal, dan kemudian mengembalikan nilai, yang kemudian disalin ke dalam array .

Argumen terakhir, yang kami panggil std::forward, adalah optimasi kecil. Jika seseorang lewat di std :: string sementara dan mengatakan "buat 5 array ini", kami ingin memiliki 4 salinan dan 1 langkah, bukannya 5 salinan. The std::forwardmemastikan bahwa kami melakukan ini.

Kode lengkap, termasuk tajuk dan beberapa tes unit:

#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.");
}
David Stone
sumber
non_copyableJenis Anda sebenarnya dapat disalin dengan cara operator=.
Hertz
Saya kira non_copy_constructibleakan menjadi nama yang lebih akurat untuk objek. Namun, tidak ada tugas di mana pun dalam kode ini, jadi tidak masalah untuk contoh ini.
David Stone
1

1) Ketika Anda menggunakan penginisialisasi, untuk struct atau array seperti itu, nilai yang tidak ditentukan pada dasarnya dibangun secara default. Dalam kasus tipe primitif seperti int, itu berarti mereka akan memusatkan perhatian. Perhatikan bahwa ini berlaku secara rekursif: Anda bisa memiliki array struct yang berisi array dan jika Anda menentukan hanya bidang pertama dari struct pertama, maka semua sisanya akan diinisialisasi dengan nol dan konstruktor default.

2) Compiler mungkin akan menghasilkan kode initializer yang setidaknya sebagus yang Anda bisa lakukan dengan tangan Saya cenderung lebih suka membiarkan kompiler melakukan inisialisasi untuk saya, bila memungkinkan.

Boojum
sumber
1) Inisialisasi default POD tidak terjadi di sini. Menggunakan daftar kompiler akan menghasilkan nilai-nilai pada waktu kompilasi dan menempatkannya di bagian khusus assembley yang baru saja dimuat sebagai bagian dari inisialisasi program (seperti kode). Jadi biayanya nol pada saat runtime.
Martin York
1
Saya tidak melihat di mana dia salah? int a [100] = {} tentu saja diinisialisasi ke semua 0, mengabaikan ke mana ia muncul, dan struct {int a; } b [100] = {}; juga. "dasarnya bawaan dibangun" => "nilai dibangun", tho. Tapi ini tidak masalah jika int, PODS, atau jenis dengan ctors yang dinyatakan pengguna. Itu hanya penting untuk NON-Pods tanpa ctors yang dinyatakan pengguna, untuk apa yang saya tahu. Tapi saya tidak akan memberikan suara (!) Karena ini. Lagi pula, +1 untuk Anda membuatnya 0 lagi :)
Johannes Schaub - litb
@ Evan: Saya memenuhi syarat pernyataan saya dengan "Ketika Anda menggunakan penginisialisasi ..." Saya tidak merujuk pada nilai yang tidak diinisialisasi. @ Martin: Itu mungkin bekerja untuk data yang konstan, statis, atau global. Tapi saya tidak melihat bagaimana itu akan bekerja dengan sesuatu seperti: int test () {int i [10] = {0}; int v = i [0]; i [0] = 5; return v; } Kompiler sebaiknya menginisialisasi i [] menjadi nol setiap kali Anda memanggil test ().
Boojum
itu bisa menempatkan data ke dalam segmen statis data, dan membuat "i" mengacu pada itu :)
Johannes Schaub - litb
Benar - secara teknis, dalam hal ini juga bisa menghilangkan "i" sepenuhnya dan hanya mengembalikan 0. Tetapi menggunakan segmen data statis untuk data yang dapat diubah akan berbahaya dalam lingkungan multi-utas. Poin yang saya coba sampaikan untuk menjawab Martin adalah bahwa Anda tidak dapat sepenuhnya menghilangkan biaya inisialisasi. Menyalin potongan yang dibuat sebelumnya dari segmen data statis, tentu saja, tetapi masih tidak gratis.
Boojum
0

Dalam bahasa pemrograman C ++ V4, Stroustrup merekomendasikan penggunaan vektor atau valarrays pada array bawaan. Dengan valarrary's, saat Anda membuatnya, Anda dapat memasukkannya ke nilai tertentu seperti:

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

Untuk menginisialisasi susunan 7 anggota dengan "7777777".

Ini adalah cara C ++ untuk mengimplementasikan jawaban menggunakan struktur data C ++ alih-alih susunan "biasa C lama".

Saya beralih menggunakan valarray sebagai upaya dalam kode saya untuk mencoba menggunakan C ++ 'isms v. C'isms ....

Astara
sumber
Ini adalah contoh terburuk kedua tentang cara menggunakan jenis yang pernah saya lihat ...
Steazy
-3

Seharusnya fitur standar tetapi karena alasan tertentu itu tidak termasuk dalam standar C atau C ++ ...

#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]);
    }
}

Di Fortran Anda bisa melakukan:

program main
    implicit none

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

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

tetapi tidak memiliki nomor yang tidak ditandatangani ...

Mengapa C / C ++ tidak bisa hanya mengimplementasikannya. Apakah ini sangat sulit? Sangat konyol harus menulis ini secara manual untuk mencapai hasil yang sama ...

#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]);
    }
}

Bagaimana jika itu adalah array 1.000,00 byte? Saya perlu menulis skrip untuk menulisnya untuk saya, atau menggunakan hacks dengan assembly / etc. Ini tidak masuk akal.

Ini sangat portabel, tidak ada alasan untuk itu tidak ada dalam bahasa.

Cukup retas seperti:

#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;
}

Salah satu cara untuk meretasnya adalah melalui preprocessing ... (Kode di bawah ini tidak mencakup kasus tepi, tetapi ditulis untuk dengan cepat menunjukkan apa yang bisa dilakukan.)

#!/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);
Dmitry
sumber
Anda mencetak dalam satu lingkaran, mengapa Anda tidak dapat menetapkan dalam satu lingkaran?
Abhinav Gauniyal
1
menugaskan di dalam satu loop menimbulkan overhead runtime; sedangkan hardcoding buffer gratis karena buffer sudah tertanam ke dalam biner, sehingga tidak membuang waktu untuk membangun array dari awal setiap kali program berjalan. Anda benar bahwa mencetak dalam satu lingkaran bukan ide yang baik secara keseluruhan, lebih baik untuk menambahkan di dalam lingkaran dan kemudian mencetak sekali, karena setiap panggilan printf memerlukan panggilan sistem, sedangkan penggabungan string menggunakan tumpukan / tumpukan aplikasi tidak. Karena ukuran dalam program semacam ini bukan masalah, yang terbaik adalah membuat array ini pada waktu kompilasi, bukan runtime.
Dmitry
"menugaskan di dalam satu lingkaran menimbulkan overhead runtime" - Anda sangat meremehkan pengoptimal.
Asu
Bergantung pada ukuran array, gcc dan dentang akan "hardcode" atau menipu nilai dalam, dan dengan array yang lebih besar, langsung saja memset, bahkan dengan array "hardcoded".
Asu