C ++에서 배열의 모든 요소를 ​​하나의 기본값으로 초기화 하시겠습니까?


248

C ++ 참고 사항 : 배열 초기화 에는 배열 초기화 에 대한 유용한 목록이 있습니다. 나는

int array[100] = {-1};

-1로 가득 찼을 것으로 예상하지만 첫 번째 값만 있고 나머지는 0과 임의의 값이 혼합되어 있습니다.

코드

int array[100] = {0};

잘 작동하고 각 요소를 0으로 설정합니다.

내가 여기서 잃어버린 것 .. 값이 0이 아닌 경우 초기화 할 수 없습니까?

그리고 2 : 기본 초기화 (위와 같이)는 전체 배열을 통한 일반적인 루프보다 빠르며 값을 할당합니까, 아니면 같은 일을합니까?


1
C와 C ++의 동작은 다릅니다. C에서 {0}은 구조체 이니셜 라이저의 특별한 경우이지만 AFAIK는 배열이 아닙니다. int array [100] = {0}은 array [100] = {[0] = 0}과 같아야합니다. 부작용으로 다른 모든 요소는 0입니다. AC 컴파일러는 위에서 설명한대로 동작하지 않아야합니다. 대신 int array [100] = {-1}은 첫 번째 요소를 -1로 설정하고 나머지는 0 (노이즈없이)으로 설정해야합니다. C에서 struct x array [100]이있는 경우, 초기화 자로 = {0}을 사용하는 것은 유효하지 않습니다. {{0}}을 (를) 사용하여 첫 번째 요소를 초기화하고 다른 모든 요소를 ​​0으로 만들 수 있습니다. 대부분의 경우 동일한 것입니다.
Fredrik Widlund

1
@FredrikWidlund 두 언어 모두 동일합니다. {0}구조체 나 배열의 특별한 경우가 아닙니다. 규칙은 이니셜 라이저가없는 요소는 마치 이니셜 라이저에 대한 것처럼 0초기화됩니다. 중첩 된 집계가있는 경우 (예 struct x array[100]:) 이니셜 라이저가 "행 주요"순서로 집계되지 않은 개체에 적용됩니다. 중괄호는 선택적으로 생략 할 수 있습니다. struct x array[100] = { 0 }C에서 유효합니다. 첫 번째 멤버가 이니셜 라이저 로 struct X허용 0되는 한 C ++에서 유효합니다 .
MM

1
{ 0 }C에서는 특별하지 않지만 생성자가 없으므로 0암시 적으로 변환되어 무언가에 할당되는 것을 막을 방법이 없으므로 초기화 할 수없는 데이터 유형을 정의하는 것이 훨씬 어렵 습니다 .
Leushenko

3
다른 질문이 C에 관한 것이기 때문에 다시 열기로 투표했습니다. C에서는 유효하지 않은 배열을 초기화하는 많은 C ++ 방법이 있습니다.
xskxzr

1
C와 C ++는 다른 언어들입니다
Pete

답변:


350

사용한 구문을 사용하여

int array[100] = {-1};

모든 생략 된 요소가로 설정되었으므로 " 첫 번째 요소를 설정하고 -1나머지를 설정 0"이라고합니다 0.

C ++에서 그것들을 모두로 설정 -1하려면 std::fill_n(에서 <algorithm>) 와 같은 것을 사용할 수 있습니다 .

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

휴대용 C에서는 자체 루프를 굴려야합니다. 컴파일러 확장이 있거나 구현 정의 동작에 적합한 경우 바로 가기로 의존 할 수 있습니다.


14
또한 배열을 기본값으로 "쉽게"채우는 방법에 대한 간접적 인 질문에 대답했습니다. 감사합니다.
Milan

7
@chessofnerd : 정확하지는 않지만 #include <algorithm>올바른 헤더 <vector>입니다. 구현에 따라 간접적으로 포함되거나 포함되지 않을 수 있습니다.
Evan Teran

2
런타임 중에 배열을 초기화하지 않아도됩니다. 정적으로 발생하는 초기화가 실제로 필요한 경우, 가변성 템플릿과 가변성 시퀀스를 사용하여 원하는 시퀀스를 생성 int하고 배열의 초기화 자로 확장 할 수 있습니다.
void 포인터

2
@ontherocks에서 fill_n전체 2D 배열을 채우기 위해 단일 호출을 사용하는 올바른 방법은 없습니다 . 다른 차원을 채우면서 한 차원을 반복해야합니다.
Evan Teran

7
이것은 다른 질문에 대한 답변입니다. std::fill_n초기화가 아닙니다.
Ben Voigt 2016 년

133

구문을 허용하는 gcc 컴파일러의 확장이 있습니다.

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

모든 요소를 ​​-1로 설정합니다.

이것을 "지정된 이니셜 라이저"라고 합니다. 자세한 내용 은 여기 를 참조 하십시오 .

이것은 gcc c ++ 컴파일러를 위해 구현되지 않았습니다.


2
대박. 이 구문은 clang에서도 작동하는 것 같습니다 (iOS / Mac OS X에서 사용할 수 있음).
JosephH

31

링크 한 페이지는 이미 첫 번째 부분에 대한 답변을 제공했습니다.

명시 적 배열 크기를 지정했지만 더 짧은 초기화 목록을 지정하면 지정되지 않은 요소는 0으로 설정됩니다.

전체 배열을 0이 아닌 값으로 초기화하는 기본 제공 방법은 없습니다.

어느 쪽이 더 빠른가에 대한 일반적인 규칙이 적용됩니다 : "컴파일러에게 최대한의 자유를주는 방법이 더 빠를 것입니다."

int array[100] = {0};

컴파일러에게 "100 개의 정수를 0으로 설정"이라고 말하면 컴파일러는 자유롭게 최적화 할 수 있습니다.

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

훨씬 더 구체적입니다. 컴파일러에게 반복 변수를 작성하도록 i지시 하고 요소를 초기화 해야하는 순서 등을 알려줍니다 . 물론 컴파일러는이를 최적화 할 가능성이 있지만 여기서 중요한 것은 문제를 과도하게 지정하여 컴파일러가 동일한 결과를 얻기 위해 더 열심히 노력하도록하는 것입니다.

마지막으로, 배열을 0이 아닌 값으로 설정하려면 (C ++에서는) 다음을 사용해야합니다 std::fill.

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

다시 말하지만, 배열을 사용하여 동일한 작업을 수행 할 수 있지만 더 간결하며 컴파일러에 더 많은 자유를줍니다. 당신은 단지 전체 배열이 42 값으로 채워지기를 원한다고 말하고 있습니다. 어떤 순서로 수행되어야하는지, 다른 어떤 것도 말하지 않습니다.


5
좋은 대답입니다. C ++이 아닌 C ++에서는 int array [100] = {}; 컴파일러에게 최대한의 자유를 주라 :)
Johannes Schaub-litb 2016 년

1
동의, 훌륭한 답변. 그러나 고정 크기 배열의 경우 std :: fill_n :-P를 사용합니다.
Evan Teran 2016 년

12

C ++ 11에는 또 다른 (불완전한) 옵션이 있습니다.

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

또는std::fill(begin(a), end(a), -1)
doctorlai

9

{}을 사용하면 선언 된대로 요소를 할당합니다. 나머지는 0으로 초기화됩니다.

= {}초기화 하지 않으면 내용이 정의되지 않습니다.


8

연결 한 페이지

명시 적 배열 크기를 지정했지만 더 짧은 초기화 목록을 지정하면 지정되지 않은 요소는 0으로 설정됩니다.

속도 문제 :이 작은 배열에는 차이가 거의 없습니다. 큰 배열로 작업하고 크기보다 속도가 훨씬 더 중요한 경우 기본값의 const 배열 (컴파일 타임에 초기화 됨) memcpy을 수정 가능한 배열로 가질 수 있습니다 .


2
memcpy는 값을 직접 속도로 설정하는 것과 비교할 수 있기 때문에 좋은 생각이 아닙니다.
Evan Teran 2016 년

1
복사 및 const 배열이 필요하지 않습니다. 처음으로 채워진 값으로 수정 가능한 배열을 작성하지 않는 이유는 무엇입니까?
Johannes Schaub-litb

속도 설명과 속도가 큰 배열 크기 (제 경우)에 문제가있는 경우 수행 방법에 감사드립니다.
Milan

이니셜 라이저 목록은 컴파일 타임에 수행되고 런타임에로드됩니다. 사물을 복사 할 필요가 없습니다.
Martin York

@litb, @Evan : 예를 들어 gcc는 최적화가 활성화 된 경우에도 동적 초기화 (많은 mov)를 생성합니다. 들어 배열과 엄격한 성능 요구 사항, 당신은 컴파일시에 초기화를 수행합니다. memcpy는 많은 일반 movs보다 사본에 더 적합 할 것입니다.
laalto 2016 년

4

배열을 공통 값으로 초기화하는 또 다른 방법은 실제로 일련의 정의로 요소 목록을 생성하는 것입니다.

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

배열을 공통 값으로 초기화하는 것은 쉽게 수행 할 수 있습니다.

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

참고 : DUPx는 DUP에 대한 매개 변수에서 매크로 대체를 가능하게하기 위해 도입되었습니다.



3

를 사용하면 std::arrayC ++ 14에서 매우 간단한 방법으로이 작업을 수행 할 수 있습니다. C ++ 11에서만 가능하지만 약간 더 복잡합니다.

우리의 인터페이스는 컴파일 타임 크기와 기본값입니다.

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

세 번째 기능은 주로 편의를위한 것이므로 사용자는 std::integral_constant<std::size_t, size>자신 을 구성 할 필요가 없습니다 . 실제 작업은 처음 두 기능 중 하나에 의해 수행됩니다.

첫 번째 과부하는 매우 간단합니다. std::array크기가 0 인 구조입니다. 복사 할 필요는 없습니다. 단지 구성 할뿐입니다.

두 번째 과부하는 조금 까다 롭습니다. 소스로 얻은 값을 따라 전달하고 인스턴스를 구성하고 make_index_sequence다른 구현 함수를 호출합니다. 그 기능은 어떻게 생겼습니까?

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

전달 된 값을 복사하여 첫 번째 크기-1 개의 인수를 구성합니다. 여기서는 가변 변수 팩 인덱스를 확장 할 대상으로 사용합니다. 해당 팩에는 크기-1 개의 항목이 있으며 (의 구성에서 지정한대로 make_index_sequence) 값은 0, 1, 2, 3, ..., size-2입니다. 그러나 값에 대해서는 신경 쓰지 않습니다 ( 그래서 우리는 그것을 무효로 캐스팅하고 컴파일러 경고를 침묵시킵니다. 매개 변수 팩 확장은 코드를 다음과 같이 확장합니다 (크기 == 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) };

우리는이 괄호를 사용하여 variadic pack 확장이 ...원하는 것을 확장 하고 쉼표 연산자를 사용하도록합니다. 괄호가 없으면 배열 초기화에 많은 인수를 전달하는 것처럼 보이지만 실제로는 인덱스를 평가하여 void로 캐스팅하고 void 결과를 무시한 다음 값을 반환하여 배열에 복사합니다. .

우리가 부르는 마지막 논쟁 std::forward은 사소한 최적화입니다. 누군가 임시 std :: string을 전달하고 "이 중 5 개의 배열을 만드십시오"라고 표시되면 5 개의 사본 대신 4 개의 사본과 1 개의 이동을 원합니다. 이를 std::forward통해 우리는이를 수행 할 수 있습니다.

헤더 및 일부 단위 테스트를 포함한 전체 코드 :

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

귀하의 non_copyable유형의 방법으로 실제로 복사 가능한 것입니다 operator=.
Hertz

non_copy_constructible객체의 이름이 더 정확하다고 가정 합니다. 그러나이 코드의 어느 곳에도 할당이 없으므로이 예제에서는 중요하지 않습니다.
David Stone

1

1) 초기화자를 사용하면 구조체 또는 이와 같은 배열에 지정되지 않은 값이 기본적으로 기본 구성됩니다. int와 같은 기본 유형의 경우 0이됩니다. 이것은 재귀 적으로 적용됩니다 : 배열을 포함하는 구조체의 배열을 가질 수 있으며 첫 번째 구조체의 첫 번째 필드 만 지정하면 나머지는 모두 0과 기본 생성자로 초기화됩니다.

2) 컴파일러는 아마도 최소한 손으로 할 수있는 초기화 코드를 생성 할 것입니다. 가능한 경우 컴파일러에서 초기화를 수행하는 것을 선호합니다.


1) POD의 기본 초기화는 여기서 일어나지 않습니다. 컴파일러는이 목록을 사용하여 컴파일 타임에 값을 생성하고 코드와 같이 프로그램 초기화의 일부로로드 된 어셈블리의 특수 섹션에 값을 배치합니다. 따라서 런타임시 비용은 0입니다.
Martin York

1
그가 어디 있는지 모르겠어? int a [100] = {}는 어디에 나타나는지에 상관없이 0으로 초기화되고, struct {int a; } b [100] = {}; 너무. "필수 기본 구성"=> "값 구성", tho. 그러나 이것은 int, PODS 또는 사용자가 선언 한 ctor를 가진 유형의 경우 중요하지 않습니다. 사용자가 ctors를 선언하지 않은 NON-Pod의 경우에만 중요합니다. 그러나 나는 이것 때문에 다운 (!) 투표를하지 않습니다. 어쨌든, 당신이 다시 0으로 만들 수 있도록 +1 :)
Johannes Schaub-litb

@Evan : 나는 "초기화기를 사용할 때 ..."로 진술을 규정했다. 나는 초기화되지 않은 값을 언급하지 않았다. @Martin : 상수, 정적 또는 전역 데이터에 효과적 일 수 있습니다. 그러나 나는 그것이 어떻게 작동하는지 알지 못한다 : int test () {int i [10] = {0}; int v = i [0]; i [0] = 5; v를 반환; } test ()를 호출 할 때마다 컴파일러에서 i []를 0으로 초기화하는 것이 좋습니다.
Boojum 2016 년

그것은 정적 데이터 세그먼트에 데이터를 배치하고 "나는":) 그것을 참조 할 수
요하네스 SCHAUB을 - litb

사실-기술적으로이 경우 "i"를 완전히 제거하고 0 만 반환 할 수 있습니다. 그러나 가변 데이터에 정적 데이터 세그먼트를 사용하면 다중 스레드 환경에서 위험 할 수 있습니다. Martin에 대한 대답으로 만들려는 요점은 단순히 초기화 비용을 완전히 제거 할 수 없다는 것입니다. 정적 데이터 세그먼트에서 사전 작성된 청크를 복사하십시오. 그러나 여전히 여유 공간은 아닙니다.
Boojum 2016 년


0

C ++ 프로그래밍 언어 V4에서 Stroustrup은 내장 배열에 벡터 또는 valarray를 사용하도록 권장합니다. valarrary를 사용하여 만들 때 다음과 같은 특정 값으로 초기화 할 수 있습니다.

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

"7777777"을 사용하여 7 멤버 길이의 배열을 초기화합니다.

이것은 "plain old C"배열 대신 C ++ 데이터 구조를 사용하여 응답을 구현하는 C ++ 방식입니다.

C ++ 'isms v. C'isms ...를 사용하려는 코드에서 시도로 valarray를 사용하기로 전환했습니다.


이것은 내가 본 유형을 사용하는 방법의 두 번째 최악의 예입니다.
Steazy

-3

표준 기능이어야하지만 어떤 이유로 든 표준 C 또는 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]);
    }
}

포트란에서 할 수있는 일 :

program main
    implicit none

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

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

그러나 부호없는 숫자는 없습니다 ...

왜 C / C ++가 그것을 구현할 수 없는가? 정말 힘들어요? 동일한 결과를 얻기 위해 수동으로 작성 해야하는 것은 너무 어리 석습니다 ...

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

1,000,00 바이트의 배열이면 어떻게됩니까? 나를 위해 스크립트를 작성하거나 어셈블리 등을 사용하여 해킹에 의존해야합니다. 이것은 말도 안됩니다.

완벽하게 이식 가능하며 언어가 아닌 이유가 없습니다.

다음과 같이 해킹하십시오.

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

해킹하는 한 가지 방법은 전처리를 사용하는 것입니다 ...

#!/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);

루프로 인쇄하고 있는데 왜 루프로 할당 할 수 없습니까?
Abhinav Gauniyal

1
루프 내부에 할당하면 런타임 오버 헤드가 발생합니다. 버퍼가 이미 바이너리에 내장되어 있기 때문에 버퍼 하드 코딩은 자유 로워 지므로 프로그램이 실행될 때마다 처음부터 배열을 구성하는 데 시간을 낭비하지 않습니다. 루프에서 인쇄하는 것이 전반적으로 좋은 생각은 아니지만 각 printf 호출에는 시스템 호출이 필요하지만 응용 프로그램의 힙 / 스택을 사용하는 문자열 연결은 필요하지 않으므로 루프 내부에 추가 한 다음 한 번 인쇄하는 것이 좋습니다. 이런 종류의 프로그램의 크기는 문제가 아니기 때문에 런타임이 아닌 컴파일 타임 에이 배열을 구성하는 것이 가장 좋습니다.
Dmitry

"루프 내부에 할당하면 런타임 오버 헤드가 발생합니다"-최적화 프로그램을 심각하게 과소 평가합니다.
Asu

배열의 크기에 따라 gcc와 clang은 값을 "하드 코딩"하거나 더 큰 배열을 memset사용하여 "하드 코딩 된"배열로도 값을 직접 트릭합니다 .
Asu
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.