C++에서 추가된 변수의 특징에 대해 알아보자!

안녕하세요! 두두코딩 널두 🥸 입니다 ✋
오늘은 C++ 변수 특징에 대해 알아보겠습니다.

🖇 소스코드에 마우스를 올리고 copy 버튼을 누를 경우 더 쉽게 복사할 수 있습니다!

궁금한 점, 보안점 남겨주시면 성실히 답변하겠습니다. 😁
+ 감상평 댓글로 남겨주시면 힘이됩니다. 🙇

Intro.

오늘 포스팅에서는 C++11 변수 특징 / uniform initialization / nullptr / using 사용법 / auto에 대해 알아보겠습니다.

C++11 추가된 변수특징

C++ 같은 경우 C++11을 기점으로 많은 변화가 일어났다. 그래서 C++11 시점이후 C++을 우리는 modern C++ 이라고 부른다.

Modern C++에는 다양한 기능들이 문법과 기능들이 추가됐는데, 그 중 우리는 변수에 관련한 변화된 특징 몇 가지를 살펴보고자 한다.

변화된 특징은 아래와 같다.

🌱 2진수 표기법과 자리수 표기법 추가
🌱 변수 중간 선언 가능하도록 변경
🌱 bool 타입 추가 (C언어 99버전에 추가됨)
🌱 구조체 만들 경우 멤버를 초기화 할 수 있음
🌱 구조체 사용 시, struct 표시 안해도 됨

위 특징을 코드에서 보면 다음과 같다.

struct Point
{
	int x;
	int y;
};

int main()
{
	// 1. 이진수 표현 추가
	int n1 = 0b10; // "0b"를 써주면 이진수로 인식됨

	// 2. 자리수 표기법
	int n2 = 1000000000; // 가독성이 떨어짐.

	// 자리를 끊어서 적을 수 있음 (') 표시를 적어주면 되는데, 인식은 따로 되지 않는다.
	int n3 = 1'000'000'000

	// 3. 변수 중간 선언
	int n4 = 0; // C언어의 legacy 컴파일러는 중간 선언자체가 불가능.

	// 4. bool 타입 추가
	bool b = true; // C99 에도 추가됨

	// 5. 구조체 만들 경우 초기화 가능, struct 키워드 없어도 됨.

	Point p1 = { 1,2 };
}

외에도 다양한 것들이 추가됐지만 차근차근 공부하면서 알아보도록 하자

uniform initialization

이번에는 새롭게 추가된 초기화 방법에 대해 알아보자.

struct Point {
	int x, y;
};

int main() {

	// 일반적 변수 초기화
	int n1 = 0;

	// C++에서는 함수형 초기화도 가능
	int n2(0);

	// 배열 같은 경우 '='이 아닌 '{}'를 활용해  초기화 됨.
	int x[2] = { 1, 2 };

	// C++에서 struct는 적을 필요 없고, '{}'를 활용해 초기화함.
	Point p = { 1, 2 };

	// ------------ 뭐가 이렇게 달라.. 😮💨 -----------
}

위 코드를 통해 알 수 있듯이 C++에서 초기화를 할 때, 구조체, 배열, 변수 다 다르게 초기화 해야된다.

C, C++를 지속적으로 사용해온 사람이라면 이것을 문제라고 느끼지 않을 텐데, 초심자는 어렵게 느낀다.

따라서, 이를 Modern C++ 에서는 일관성있게 통일하고자 {} 를 활용하는 uniform initialization 개념을 도입했다.

초기화는 크게 2가지로 나뉘게된다.

  1. ”=” 를 사용하지 않는 direct 초기화
  2. ”=” 를 통한 copy 초기화
struct Point {
	int x, y;
};

int main() {
	// '='를 사용하지 않고 직접 초기화.
	// direct initialization
	int n1 {0};
	int n2 {0};

	int x[2]{1,2};
	Point p {1,2};

	// '=' 활용한 복사 초기화
	// copy initialization
	int n3 = {0};
	int n4 = {0};

	int y[2] = { 1, 2 };
	Point p2 = { 1, 2 };
}

직/간접 초기화는 미묘한 차이가 존재한다. 그 이야기는 추후 알아보도록 한다.

uniform initialization의 또다른 매력

uniform initialization 초기화를 활용할 경우 에러를 줄일 수 있다는 장점이 있다.

#include <iostream>

int main()
{
	// 컴파일러 암시적 형변환으로 데이터 손실이 발생함.
	// n1은 3이 담김.
	// 컴파일은 잘됨.
	int n1 = 3.4;

	// 컴파일 부터 에러남.
	int n2 {3.4};
}

위 코드를 실행하면, n2를 초기화하는 과정에서 데이터 타입이 달라 컴파일 에러를 발생시킨다. 데이터 손실로 발생한 버그는 찾기가 너무어렵다. 따라서 애당초 초기화 할때 컴파일 에러를 발생시키면 개발자가 손실을 미리 방지할 수 있다.

#include <iostream>

int main()
{
	char c1 { 100 };

	// char 값을 벗어나는 값이 입력됨.
	// 이러면 초기화 에러남.
	char c2 { 300 };
}

위 코드와 같이, char 데이터 타입을 벗어나는 경우도 에러가 발생한다. char 타입 같은 경우 255까지만 가능하기 때문이다.

끝으로, {}에 아무것도 적어주지 않는다면 0으로 초기화 된다.

#include <iostream>

int main() {
	int n3{};// 0으로 초기화 됨.
	int n4 = {};// 0으로 초기화 됨.

	// 0을 출력함.
	std::cout << n3 << std::endl;
	std::cout << n4 << std::endl;
}

nullptr의 등장

우리는 습관적으로 * 값에 0을 넣을 경우, null 값으로 인식된다는 것을 알고 있고, 초심자 때 느낌적으로 배우곤한다.

int*에 0을 넣을 경우 컴파일러가 암시적 형변환을 통해 null로 인식하도록 한다. 하지만 이는 함수 오버로딩 / 템플릿 사용에서 문제가 발생하는데, 실제 발생하는 부분은 추후 다루도록 한다.

따라서, 이를 막기위해 등장한 개념이 nullptr 이다.

int main() {
	// null로 암시적 형변환이 발생함.
	int* p1 = 0;

	// 실제 포인터 값에 어떤 정수 값으로 초기화를 하려고 할 경우 "에러 발생"
	/* error 문구 */
	// error: cannot initialize a variable of type 'int *' with an rvalue of type 'int'
	int* p2 = 10;

	// 이는 정수가 아니라 포인터이기 때문에, 에러 발생.
	// nullptr 은 "포인터"이다.
	/* error 문구 */
	// error: use of undeclared identifier 'nullptr'
	int n = nullptr;
}

bool type with nullptr

bool type 같은 경우 nullptr로 초기화가 된다. 물론 warning은 발생함 ㅠ.. 하지만, direct initialization을 통해서 초기화가 가능하다고 표준문서에는 적혀있다.

즉, copy initialization은 가능하다 안하다가 정해져있지 않다. 즉 undefined 영역이다. 컴파일러마다 다를 수 있다.

#include <iostream>

int main() {
	// 에러
	bool b1 = nullptr;

	// ok direct 초기화
	bool b2(nullptr);
	bool b3{nullptr};
}

using의 활용

C++에서는 using이라는 지시어말고, 키워드가 존재한다. using의 키워드는 우리가 이제까지 사용해온 메크로 typedef를 대체하는 역핧을 한다.

우리는 타입에 다른 이름을 부여하기 위해 typedef를 사용해왔다. typedef를 적을 경우 컴파일러가 컴파일 타임에 해당 이름에 지정된 타입으로 변환하는 동작을 수행한다.

#include <iostream>

// using은 해당 문장을 정확히 대체한다.
// typedef int DWORD;
// typedef void(*F)(int);

// 가독성은 더 좋아짐.
using DWORD = int;
using F = void(*)(int);

int main() {
	int main()
	{
		DWORD n;
		F f;
	}
}

usingtypedef 기능을 정확히 대체한다. 그럼 using은 왜 사용하는가?

typedef 같은 경우 타입에 대해서만 오직 별칭이 가능했다. using 같은 경우 template에 대해 별칭을 만들 수 있다.

template에 대한 내용은 뒷부분 혹은 다른 강좌에서 다루도록 한다.

우선, using 키워드가 있고, typedef를 대체하며 더 넓은 기능을 제공하니 별칭을 사용해야된다면 using을 사용하자!

auto 타입

Modern C++ 에는 새로운 타입 auto가 등장한다.

#include <iostream>

int main()
{
	// int x[3] = { 1, 2, 3 };
	// int n1 = x[0];
	// int 형태를 double로 변경하려면? 직접 타입을 모두 바꿔줘야함.
	double x[3] = { 1, 2, 3 };
	double n1 = x[0];
}

위의 코드에서 보이는 것과 같이 기존에 int로 선언해 둔 배열을 double로 변경하게 될 경우 배열의 타입 뿐만아니라 n1과 같이 값을 꺼내는 부분을 모두 변경해줘야한다.

너무 귀찮은 작업이다. C++ 진영에서는 이런 귀찮은 작업을 막기위해 우변의 수식을 보고, 좌변의 타입을 결정하도록 auto 타입을 만들었다.

#include <iostream>

int main()
{
	double x[3] = { 1, 2, 3 };
	auto n1 = x[0];

	auto n2; // 컴파일 에러발생.
}

위의 코드와 같이 auto는 좌변의 값을 통해 우변의 타입을 결정한다. auto의 큰 특징은 반드시 초기값을 전달해야한다. 만약 초기값이 없다면 Error가 발생하게 된다.

타입을 결정하는 게 실행시간이 아니라 Compile 타임에 결정하도록 되어있기 때문에 성능상 문제가 없다.

decltype이란?

뒷 포스팅에서 자세하게 다루겠지만, decltype 같은 경우 초기값 유무와 관계 없이 괄호 내 들어있는 타입으로 만들어달라고 지시하는 키워드이다.

#include <iostream>

int main()
{
	double x[3] = { 1, 2, 3 };
	auto n1 = x[0];

	// decltype 괄호 내 있는 타입으로 만들어줘 ->  double
	decltype(n1) n2;
}

우선 문법적 내용만 보도록하자. 사실 더 심화내용이 있지만 그건 중급 내용 혹은 뒷 포스팅에서 다루도록 한다.

Outtro.

해당 포스팅은 Ecourse의 C++ Basic 강의를 참고해 작성되었습니다.

강의를 참고하실 분은 여기를 클릭해 확인해주세요!