Modern C++ 공부 Day 7입니다.
오늘 공부할 내용은 C++ 변수 선언과 초기화입니다.
이전 포스트를 읽고 오시면 본 포스트를 공부하는 데 도움이 됩니다.
2023.06.15 - [Programming/C++] - [Modern C++ 공부 - Day6] C++ Semantics 시맨틱스
C++ 범용 초기화 Universal Initialization
기본적으로 Modern C++에서 괄호{ }를 사용하면 아래와 같이 어떤 타입의 변수도 초기화할 수 있습니다.
int x{10}; // int x = 10;과 동일
string str{"Hello World"}; // string str("Hello World");와 동일
vector<int> vec{1, 2, 3, 4, 5}; // std::vector 컨테이너 {1, 2, 3, 4, 5}로 초기화
// vector<int> vec;
// vec.push_back(1);
// vec.push_back(2);
// ...와 동일
위의 코드에서처럼 이전 버전에서 쓰던 방식으로 변수를 초기화할 수 있지만, { }을 이용해서 일관성있게 모든 타입의 변수를 초기화할 수 있다는 점이 기억하기 더 쉽습니다.
하지만, 아래와 같이 변수 선언 타입과 초기화하려는 값의 타입이 다른 경우에는 조심해야 합니다.
int x = 3.2; // 컴파일 성공, 하지만 경고가 뜰 수 있다.
int x{3.2}; // 컴파일 에러.
- 기존의 방식으로 변수를 초기화할 때, 위와 같이 int 로 선언했지만 double로 초기화하는 경우에는 int 형식이 허용하는 범위보다 큰 데이터는 무시됩니다. 그래도 Modern 컴파일러로 컴파일은 가능합니다. 경고 Warning이 뜰 수 있지만요.
- 반면에 { }을 이용하여 컴파일하는 경우에는, 변수 선언 타입과 초기화 데이터 타입이 다르면 아예 컴파일이 되지 않습니다.
Vector 초기화
Modern C++에서 많이 사용하는 std::vector를 초기화하는 방식은 아래와 같이 다양합니다.
vector<int> vec1(3); // vec1 요소가 {0,0,0}
vector<int> vec2(3, 2); // vec2 요소가 {2,2,2}
vector<int> vec3{3}; // vec3 요소가 {3}
vector<int> vec4{3, 2}; // vec4 요소가 {3,2}
제대로된 코드로 출력해서 다시 한 번 확인해봅시다.
#include <iostream>
#include <string>
#include <vector>
using namespace std;
void print(const vector<int>& vec) {
for (auto v : vec) {
cout << v << ", ";
}
cout << endl;
}
int main() {
vector<int> vec1(3);
vector<int> vec2(3, 2);
vector<int> vec3{3};
vector<int> vec4{3, 2};
cout << "vec1 = ";
print(vec1);
cout << "vec2 = ";
print(vec2);
cout << "vec3 = ";
print(vec3);
cout << "vec4 = ";
print(vec4);
return 0;
}
출력값은 다음과 같습니다.
vec1 = 0, 0, 0,
vec2 = 2, 2, 2,
vec3 = 3,
vec4 = 3, 2,
클래스 객체 초기화
다음으로, 클래스의 객체를 선언하고 초기화할 때는 어떻게 할까요? 이 경우에도 { }를 사용할 수 있습니다.
class Test {};
int main() {
Test test{}; // 클래스 Test의 객체 test 생성 및 초기화
...
Test test(); // 클래스 Test의 객체를 반환하는 함수 test 선언
}
- Test test{ }; 를 통해서 클래스 Test의 객체 test를 생성하고 기본 생성자를 이용해 초기화.
- Test test( ); 는 객체 생성이 아닌 test라는 함수 선언.
위 코드에서 { }를 이용해서 객체도 생성할 수 있다는 것을 알 수 있습니다. 여기서 주의할 점은 ( )를 이용하면 객체 생성이 아닌 함수 선언으로 컴파일러가 인식한다는 것입니다.
새로운 타입 정의
다음으로, 개발자가 직접 새로운 타입을 정의할 때 사용할 수 있는 문법을 소개해보겠습니다. 이전에는 typedef를 이용해서 다음과 같이 새로운 타입을 정의하곤 했습니다.
typedef vector<int> IntVec;
int main() {
IntVec vec;
}
Modern C++에서는 아래와 같이 using을 이용해서 새로운 타입을 정의할 수 있습니다.
using IntVec = vector<int>;
int main() {
IntVec vec;
}
Null Pointer
마지막으로, nullptr 널 포인터 (Null pointer)에 대해서 알아보겠습니다.
- NULL은 C++에서 사용되는 null 포인터의 오래된 표현입니다. 실제로 NULL은 0으로 정의되며, 이는 C++에서 "null 포인터 상수"로 간주됩니다. NULL은 C++보다 먼저 개발된 C 언어에서 파생되었으며, C++에도 계승되었습니다. 하지만 NULL이 정수 0으로 정의되어 있어서 함수 오버로딩 등의 상황에서 문제가 발생할 수 있습니다.
- 이에 비해 nullptr은 C++11에서 도입된 새로운 키워드로, 진정한 null 포인터를 나타냅니다. nullptr은 모든 포인터 타입에 대입할 수 있으며, 정수 타입이 아니기 때문에 함수 오버로딩에서 모호함을 방지할 수 있습니다.
- 따라서, Modern C++에서는 nullptr을 사용하여 null 포인터를 나타내는 것이 권장됩니다.
- Null pointer를 이용하면 아무 객체도 가리키고 있지 않는 포인터 초기화할 수 있습니다.
아래 코드를 보면서 제대로 이해했는지 확인해봅시다.
void func(int);
void func(int *);
func(nullptr) // func(int *)를 호출
func(NULL) // clang에서는 func(int *) 호출
// VC++에서는 func(int) 호출
// gcc에서는 컴파일되지 않음.
- nullptr은 포인터 타입을 가진 함수에 대한 호출에 대응됩니다. 따라서 func(nullptr)을 호출하면, void func(int*) 버전의 함수가 호출됩니다. nullptr은 모든 타입의 포인터에 대입할 수 있습니다.
- func(NULL)은 컴파일러에 따라 다르게 동작합니다. NULL은 기본적으로 정수 0으로 정의되지만, 많은 C++ 컴파일러들은 NULL을 null pointer constant로 취급합니다.
- 따라서 어떤 컴파일러(예: clang)는 func(NULL)을 func(int*) 함수를 호출하는 것으로 해석하지만, 다른 컴파일러(예: Visual C++)는 func(int) 함수를 호출하는 것으로 해석할 수 있습니다. 그리고 gcc와 같은 컴파일러는 모호함을 피하기 위해 이러한 호출을 허용하지 않을 수 있습니다.
- 따라서 C++11 이후에서는 nullptr을 사용하는 것이 가장 안전하며, 함수 호출의 모호성을 방지할 수 있습니다. NULL을 사용하는 것은 예상치 못한 결과나 모호성을 초래할 수 있으므로 가능한 한 피하는 것이 좋습니다.
제대로 컴파일해서 출력값을 통해 확인해봅시다.
#include <iostream>
using namespace std;
void func(int i) {
cout << "func(int) 호출됨." <<endl;
}
void func(int *i) {
cout << "func(*int) 호출됨." <<endl;
}
int main() {
func(NULL);
func(nullptr);
}
- GNU g++로 컴파일 할 때는 func(NULL);에서 컴파일 에러가 발생합니다. 왜냐하면 g++에서는 NULL은 다른 방식으로 구현되어 있고, 이 두 함수 중 어느 것도 실제로 일치하지 않기 때문입니다.
- Visual C++로 컴파일 시에는 아래와 같이 출력값이 나옵니다.
func(int) 호출됨.
func(*int) 호출됨.
지금까지 Modern C++에서의 선언과 초기화에 대해서 알아봤습니다.
다음 시간에는 C++ Class 클래스에 대해서 복습하는 시간을 가지겠습니다.
'Programming > C++' 카테고리의 다른 글
[Modern C++ 공부 - Day9] Files and Streams (0) | 2023.07.09 |
---|---|
[Modern C++ 공부 - Day8] String Operations (0) | 2023.07.05 |
[Modern C++ 공부 - Day6] C++ Semantics 시맨틱스 (0) | 2023.06.15 |
[Modern C++ 공부 - Day5] 상수 참조에 의한 전달 Pass by const Reference (0) | 2023.06.15 |
[Modern C++ 공부 - Day4] 참조에 의한 전달 Pass by Reference (0) | 2023.06.15 |