C++/카루의 C++ 강좌

[카루의 C++ 강좌] 2-4. 변수의 초기화 I

카루-R 2020. 3. 21. 23:53
반응형

 초기화는 변수에 처음 값을 대입하는 행위라고 했습니다.

초기화를 세분화하면, 값을 통한 초기화, 직접 초기화, 복사 초기화, 리스트 초기화, Aggregate 초기화, 참조 초기화가 있습니다. 어려운 건 아니니 아래만 보시면 금방 이해하실 겁니다.

 

Value initialization (값을 통한 초기화 - C++03부터)

임시 객체를 만들 때에도 사용하는 방법입니다. 임시 객체는 나중에 설명을 할 테니 일단 아래 코드를 봐 주세요.

int iVar = int();
double dVar{};
float fVar = float{};

 위 변수들은 모두 기본값으로 초기화됩니다. 값을 지정하려면 소괄호 또는 중괄호 안에 값을 넣으면 됩니다.

 변수의 초기화에서 소괄호와 중괄호는 그 역할이 같고, 웬만하면 중괄호로 통일하여 쓰는 것이 좋습니다. 일단 자료형 뒤에 소괄호/중괄호가 붙으면 그것은 괄호 안의 값을 가진(없으면 기본값) 이름 없는 임시 객체를 생성한다는 뜻입니다. 다시 말해서, int() 라고 쓰면 0의 값을 가진 임시 int 변수가 생성된다는 겁니다. 그리고 그 임시 변수를 iVar에다 대입하니 초기화가 되겠죠.

double dVar{};

이건 제가 항상 사용하라고 말씀드렸던 방식입니다. 단지 값이 없을 뿐 입니다. 자동으로 0.0 으로 초기화가 됩니다.

변수를 선언할 때, 습관적으로 이름 뒤에 중괄호 {}를 붙여 초기화를 하는 게 좋습니다. 최소한 이게 귀찮으면 안 됩니다. 단, 이때는 중괄호 대신 소괄호를 사용할 수 없는데, double dVar();이라고 쓰면 dVar은 함수라고 간주하기 때문입니다. 저번 시간에 괄호가 함수 호출 연산자라고 배웠었죠?

int();
double d = {};
short{};
char array[20] = "";

이렇게 하면 기본적으로 위의 4개의 변수 (이름 없는 임시 변수 포함)는 모두 0의 값을 갖게 됩니다. 참고로 정적 변수나 전역 변수는 따로 초기화를 하지 않아도 자동으로 0으로 초기화가 됩니다.

int arr[10]{};

이렇게 쓰면 배열 arr에 있는 모든 원소 (arr[0], arr[1], arr[2], ...arr[9])가 기본값인 0으로 초기화됩니다.

만약 초기화 방법이 이렇게 많아서 뭘 써야할 지 모르겠다! 하시는 분들을 위해 정리합니다.

Type object{};

이걸 쓰세요. Type은 자료형을 넣으면 되고, object엔 변수명을 넣으면 됩니다.

 

Direct Initialization - 직접 초기화

int num(30);
long long ll{24473};
auto pi = double(3.14);
auto ff = static_cast<float>(ll);

이건 값을 직접 대입하는 행위입니다. 기본값인 0이 아닌 다른 값으로 초기화하고 싶을 때 사용합니다.

첫 번째 줄의 num과 두 번째 줄의 ll은 그 형태가 완전히 같습니다. 다만, 가급적 {중괄호}를 사용해주시기 바랍니다.

세번째 줄은 3.14라는 값을 가진 double 임시 변수를 만들어서 pi에 대입하는 모습입니다.

pi는 당연히 double 타입으로 추론이 됩니다.

네번째 줄은 long long 타입인 ll 변수를 float으로 캐스팅하여 ff에 대입합니다. 역시 float으로 타입 추론이 됩니다.

자, 이것도 정리해드립니다. 아래와 같은 형태를 주로 쓰세요.

Type object{초기값};

*람다식도 이 형식의 초기화에 포함됩니다. 그리고, C++20에 추가된 배열 괄호 초기환는 하강 변환을 허용하지만, 클래스의 R-Value 레퍼런스 인스턴스를 초기화할 경우 이름없는 임시객체의 수명을 늘릴 수 없고, 댕글링 참조가 되어버립니다.

int arr[]{1.2, 3.2}; // 이건 오류

int arr[](1.2, 3.2); // 가능

 

Copy Initialization - 복사 초기화

int n{}; // Value Initialization
int cpn = n;
std::printf("%d", n); // 이때는 std::printf()의 매개변수로의 복사가 일어남
return cpn;
int array[] = {n, cpn, n, cpn};

위는 변수 n을 제외하면 모두 복사 생성이 되는 인스턴스들입니다. 우선 int cpn = n; 이 문장은 int cpn{n}; 으로 바꿀 수 있습니다. n을 복제한 변수가 cpn입니다.

std::printf()함수는 우리가 n을 넘김으로써 내부에서 그것을 매개변수로 받으면서 복사가 일어납니다. 이 부분은 함수 설명할 때 더 자세히 하겠습니다.

return cpn; 이건 main()함수에서 return 0; 대신에 0값을 가진 아무 변수나 대신 return 해도 상관 없습니다. 그러면 그 안에 있던 값이 복사되어 함수를 호출했던 원래 자리로 전달되게 됩니다.

마지막 줄은 배열을 생성할 때 이미 있는 변수를 복사해서 생성했네요.

여기는 별 거 없습니다. 딱히 몰라도 되고, 그냥 초기화할 때는 등호 없이 중괄호만 사용하는 게 통일성 있고 좋습니다. 넘어가세요.

 

List Initialization - 리스트 초기화

위에서 못다한 설명이 있습니다.

// Direct List Initialization - 직접 리스트 초기화
int object[]{1, 2, 3, 4, 5};
auto arr = double{2.3, 5.6, 2.7, 7.3};

// Copy List Initialization - 복사 리스트 초기화
int object2[] = {1, 2, 3, 4, 5};
auto arr = double{2.3, 5.6, 2.7, 7.3};

return {1, 2, 3, 4, 5};
object[{1, 2, 3, 4, 5}]
object = {1, 2, 3, 4, 5}
double[]({1, 2, 3})

 아래 네 줄은 무시하시기 바랍니다. 직접 리스트 초기화와 복사 리스트 초기화의 차이는 등호의 유무에 있습니다. 아까 등호를 빼라고 말씀드렸던 건 직접 초기화를 위해서입니다. 나중에 클래스 배우면서 변환생성자를 공부할 텐데, 등호를 쓸 이유가 딱히 없습니다. 통일성을 위해 등호 없이 중괄호만 쓰도록 합시다. 사실 중괄호가 나온 목적이 통일성을 위함입니다.

참고로, 이렇게 중괄호 초기화는 몇가지 제한이 있습니다.

- 실수를 정수로 변환할 수 없음

- long double에서 double이나 float으로, double에서 float으로 변환 불가. 단, 값이 잘리지 않는 선에서 상수는 변환 가능(3.14는 double이지만 float으로 묵시적 변환 가능)

- 정수에서 실수 변수로 변환 불가. 단, 값이 정확하게 보존되는 선에서 상수는 변환 가능 (즉 1은 실수로 암묵적으로 변환된다)

- 마찬가지로, 값이 정확하게 보존되는 선에서 정수에서 더 작은 정수로, enum(enum class는 불가능)에서 정수로 변환 가능.

- conversion from a pointer type or pointer-to-member type to bool

일단 아래는 딱히 중요한 내용이 아니라 번역하지 않았습니다. 조금 나중에 할게요. 지금 좀 피곤해서..

 

Aggregate Initialization

배열을 초기화합니다 (구조체나 클래스도 가능)

int intarr[] = {1, 2, 3, 4, 5};
double dbarr[] = {1.9, 2.4, 2.4, 1.};
float farr[](dbarr[0], 2.3);

 맨 마지막 줄은 C++20부터 추가된 기능인데, 중괄호 초기화의 제한을 받지 않습니다. 값이 잘리는 변환도 무시합니다. 이런 초기화는 문자 배열도 가능합니다.

char msg[]{"Nice to meet you!"};

 그동안 문자열은 auto로 저장했던 것 기억하시나요? 이제는 char 배열을 통해서도 문자열을 담을 수 있습니다. 사실 이건 C언어에서 내려오는 방식이라 말씀을 따로 안 드렸습니다만, 이제부터 이러한 문자열 관리도 해 볼 것입니다. 물론 당장은 아닙니다.

 

정리해드립니다.

// 변수를 선언만 하고 기본값으로 초기화할 때!
int a{}, b{};
double c{};

// 변수를 선언하고 원하는 값을 대입하고 싶을 때!
short d{3};
float e{30.f};

// 배열을 초기화 할 때!
long long f[20]{1, 2, 3, 4, 5, 6, 7, };

// 문자열을 초기화 할 때!
auto str1{"This is a sample string."}; // 문자열의 내용을 수정하지 않을 때
char str2[]{"This is another sample string."}; // 문자 배열로 담고 배열로 접근할 때
std::string str3{"아 그냥 다 귀찮아"}; // 다 귀찮고 C++ 스타일로 편리하게 쓰고 싶을 때

초기화도 종류가 참 많죠? 다 잊으시고 중괄호만 기억하시면 됩니다. 중괄호!

꼭 기억해 주세요. 다음 시간에 배울 참조와 이후에 배울 클래스 등에서도 쓰입니다.

 


 제가 이렇게 중괄호 초기화를 강조해 드리는 이유엔 여러가지가 있습니다.

궁극적으로는 프로그래머의 편의성을 위한 것임이 가장 큽니다. 그래도 중괄호 초기화를 사용하면 여러 변수나 클래스 등을 초기화하는 구문을 일관성있게 할 수 있으며, std::vector와 같은 STL에서도 배열을 초기화하듯 사용할 수 있습니다. 또한 값이 더 잘릴 수 있는 경우에 한해 암묵적 형 변환이 불가능해지고, 함수의 매개변수나 반환형을 더 간단하게 표현할 수 있다는 이점이 있습니다.

반응형