[Modern C++] 따배씨++ Chapter 2 정리

따베씨++ Chapter 2을 듣고 정리해보자!

Modern C++을 복습하기 위해 인프런에서 홍정모 교수님의 따라하며 배우는 C++를 정리해보자.
쳅터 2만 되어도 중간 중간 두루뭉술하게 잡힌 개념들이 있었다.
조금 시간이 걸리겠지만 기초를 탄탄히 한다는 마음으로 정리를 해보았다.

쳅터 2은 변수와 기본적인 자료형이라는 이름으로 되어있다.

이 포스팅에서는 강의에서 나눈 쳅터와 동일하게 쳅터를 나누었다.

1. 기본 자료형 소개

Chapter 1에서는 변수를 선언할 때 int 자료형을 사용했다.

이번 쳅터에서는 C++에 있는 다양한 자료형에 대해 배우고, 초기화 하는 방법을 배웠다.

자료형은 Data Type에 따라서 차지하는 bit수에 따라서도 다양하게 존재한다.
참고로 1byte = 8bit이다!

변수가 선언이 되면 메모리의 어떤 공간에 변수가 들어갈 공간이 생기는데,
이 때 자료형마다 차지하는 공간의 크기가 다르다.

주소 값을 보기 위해 아래와 같은 코드를 사용해보자.

#include <iostream>
#include <bitset>

using namespace std;

int main()
{
    int i = -1;
    cout << (uintptr_t)static_cast<void*>(&i) << endl;  // #1
    cout << static_cast<void*>(&i) << endl;             // #2
    cout << &i << endl;                                 // #3

    return 0;
}

출력 결과는 다음과 같다.

16119588    // #1 : 주소 값을 10진수로 출력  
00F5F724    // #2 : 주소 값을 16진수로 출력
00F5F724    // #3 : 주소 값을 16진수로 출력

static_cast는 형변환을 할 때 사용하고, void* 같은 경우는 자료형을 몰라도 주소 값을 출력할 때 사용한다.
(uintptr_t)의 경우, 주소값을 16진수에서 10진수로 바꾸어 출력하기 위해 사용한다.

bool형의 초기화 방법은 다음과 같다.
bool형은 내부적으로 숫자로 저장된다.

// bool형 초기화
bool bValue = true;
cout << (bValue ? 1 : -1) << endl;      // 삼항 연산자 사용 :    1 출력
cout << bValue << endl;			// bool형도 내부적으로 숫자로 저장 :   1 출력

문자(char형)의 초기화 방법은 다음과 같다.
문자도 내부적으로 숫자로 저장된다.

// 문자도 결국 숫자로 저장한다. 
char chValue = 65;                      // 아스키 코드 
cout << chValue << endl;		// A를 출력

Float형과 Double형을 이용해서 실수를 표현할 수 있다. Float형이 Double형보다 더 정밀하게 수를 표현할 수 있다. 이 두 자료형의 차이점에 대해서는 다음 쳅터에서 조금 더 자세히 다룬다고 하셨다.

// float형 double형 초기화
float fValue = 3.141592f;
double dValue = 3.141592;

cout << fValue << endl;     // 3.14159 출력
cout << dValue << endl;     // 3.14159 출력

위 코드의 경우 cout이 자동적으로 소수 5번째 자리까지만 출력하도록 되어있기 때문에 같은 수가 출력이 된다.

또한 Modern C++의 경우 Datatype을 자동적으로 잡아주는 auto 자료형이 추가되었다.

// Modern C++에 생긴 자료형 - 자동으로 자료형을 결정
auto aValue = 3.141592f;
auto aValue2 = 3.141592;  

// sizeof()는 Datatype, 변수 둘다 넣어줄 수 있다.
cout << sizeof(aValue) << endl;     // 4 출력	
cout << sizeof(aValue2) << endl;    // 8 출력
cout << sizeof(double) << endl;    // 8 출력

참고로 sizeof() 함수는 메모리 공간에 차지하는 Byte 수를 알려준다.
위 주석대로 변수뿐만 아니라 자료형도 Input으로 넣어줄 수 있다.

C++에서는 초기화 방법이 크게 3가지가 있다.

// C++ 초기화 방법 1 : Copy initialization
int a = 123;

// C++ 초기화 방법 2 : Dircet initialization
int b(224);

// C++ 초기화 방법 3 : Uniform initialization
int c{ 244 };

Copy initialization은 우리가 흔히 초기화할 때 사용하는 방법이고,
Direct initialization과 Uniform initalziation은 객체 지향을 배우고 그것을 초기화 할 때 종종 사용한다.

Uniform initialization의 방법이 Direct initialization보다 조금 더 엄격하다.
예를 들어 Datatype을 맞춰주지 않고 초기화를 할 경우 Direct 초기화 방법은 Warning이 뜨지만, Uniform 초기화 방법은 Error가 뜬다.

그렇다면 변수 선언은 언제 하는것이 좋을까?

최근에는 변수 사용 직전에 선언을 하는 것이 최근 Trend이다.

그 이유는 다음과 같다.

  • 디버깅을 하기 편하기 때문
  • 리팩토링이 편하기 때문

물론 개발하는 팀이나 스타일 가이드가 있을 경우 그를 따르는 것이 좋다.

2. 정수형 (Integers)

이번 강의를 통해서 여러가지 정수형 Type과 정수로 나눗셈을 할 때 유의해야 할점
그리고 표현할 수 있는 범위를 넘어가면 발생하는 문제인 Overflow에 대해서 배웠다.

int형이 2바이트라고 되어있는 이유는 최소 크기를 나타내기 위함이다.
실제로 코드에서는 출력을 하는 것이 더 이해하기 쉽다.

위 그림은 int형의 데이터 구조를 시각화한 자료이다.
1 Byte는 8bits로 되어 있고 맨 첫 번째 bit는 부호를 표현하기 위해 사용한다.

Unsigned int는 부호 bit가 없어 모든 비트가 수를 표현하는데 사용한다.

아래 코드를 통해 자료형의 Size를 출력해보자.

#include <iostream>
using namespace std;

int main()
{
    cout << sizeof(short) << endl;      // 2 출력
    cout << sizeof(int) << endl;        // 4 출력
    cout << sizeof(long) << endl;       // 4 출력
    cout << sizeof(long long) << endl;  // 8 출력
}

자료형 Short가 표현할 수 있는 수 중 가장 큰 수 출력해보자.

Short는 2바이트이므로 즉, 16비트로 수를 표현한다.
맨 첫번째 비트로 부호를 결정하고, 모든 비트가 0일 경우 숫자 0을 표현하므로,
가장 큰 수는 $(2 ^{sizeof(short)* 8 - 1} ) -1$이다.

최대값, 최솟값을 표현하는데 사용하는 헤더파일인 #include <limits>를 활용하여 구한 값이 맞는 지 확인해보자.

#include <iostream>
#include <cmath>	// pow()를 사용하기 위함
#include <limits>	// 컴파일러한테 최대로 최소로 표시할 수 있는 범위를 물어봄 

using namespace std;

int main()
{
    cout << std::pow(2,sizeof(short) * 8 -1) -1 << endl;    // 32767 출력
    cout << std::numeric_limits<short>::max() << endl;      // 32767 출력
}

그럼 여기서 +1을 한 값을 출력하게 되면 어떻게 될까?
아마 32768이 나오기를 기대할 것이다.

그렇다면 실제로 그렇게 되는지 확인해보자.

#include <iostream>

using namespace std;

int main()
{
    short s = std::numeric_limits<short>::max();
    s += 1; // s = s + 1;
    cout << s << endl;      // -32768 출력
}

우리의 예상과는 달리 -32768이라는 값이 출력되었다. 바로 최대로 표현할 수 있는 수의 범위를 넘어갔기 때문에 최소로 표현될 수 있는 수가 출력된 것이다. 이를 Overflow라고 한다.

조금 더 자세한 설명은 7.2 오버플로우와 언더플로우 알아보기 - C언어 코딩 도장을 참고하자.

Unsigned int형과 int형이 표현할 수 있는 수의 범위도 한번 출력해보자.

#include <iostream>
#include <limits>	// 컴파일러한테 최대로 최소로 표시할 수 있는 범위를 물어봄

using namespace std;

int main()
{
    unsigned int ui_max = std::numeric_limits<unsigned int>::max();
    unsigned int ui_min = std::numeric_limits<unsigned int>::min();
	
    cout << ui_max << endl;	// 4294967295 
    cout << ui_min << endl;	// 0

    int i_max = std::numeric_limits<int>::max();
    int i_min = std::numeric_limits<int>::min();

    cout << i_max << endl;	// 2147483647
    cout << i_min << endl;	// -2147483648
}

Unsigned int는 맨 앞 비트를 수를 표현하는데 사용하기 때문에 0 ~ 4294967295까지 표현을 할 수 있고, int형은 맨 앞 비트를 부호를 표현하는데 사용하기 때문에 -2147483648 ~ 2147483647까지 표현 할 수 있다.

Overflow는 큰 수를 다룰 때 한번 쯤은 만나게 될 수 있으므로 주의하면서 코딩을 하자!

다음은 나눗셈을 할 때 주의해야할 점이다. 정수끼리 나눗셈을 하면 몫만 저장이 된다. 따라서 소수점까지 표현을 하고 싶을 때는 float형이나 double형을 써야한다.

int div_i = 22 / 4;
cout << div_i << endl;  // 5 출력

만약 소숫점까지 계산을 하고 싶을 경우 형변환을 통해 float형으로 변환을 해야한다.

float div_f = (flaot)22 / 4;    // 정수끼리의 연산은 정수로 저장하려고 한다!
cout << div_f << endl;  // 5.5 출력

3. C++ 11 고정 너비 정수 (Fixed-width Integers)

C++의 소수를 표현할 때 플랫폼마다 컴파일러마다 실제 구현된 Data size는 다를 수 있다.
따라서 C++ 11에서 고정 너비 정수라는 것을 채택하여 똑같은 Data size를 정의해줄 수 있다.

추후 멀티 플랫폼 프로그래밍을 많이 한다면 중요하게 생각해야한다.
멀티 플랫폼이란 단어가 생소했는데, [별별 개발 용어] 크로스 플랫폼(Cross Platform)이란?에서 감을 잡을 수 있었다.

고정 너비 정수를 초기화 하는 방법은 다음과 같다.

// #include <cstdint>   
// 고정 너비 정수에 대한 헤더 파일<iostream>안에 고정 너비 정수를 사용할 수 있도록 되어있음
#include <iostream>

using namespace std;

int main()
{
	std::int16_t i(5);	        // 16비트 = 2바이트 정수형
	std::int8_t my_int(65);	        // 8비트 = 1바이트 정수형(= char형)

	cout << my_int << endl;     // char형이기 때문에 'A' 출력

	std::int_fast8_t fi(5);	        // 8비트 size 중에 가장 빠른 것
	std::int_least64_t li(5);	// 적어도 64비트를 갖는 Data type
}

4. 무치형 (Void type)

Void는 포인터를 다룰 때 많이 사용되는데 여기서는 간단하게만 짚고 넘어가셨다.
Void 형은 메모리를 차지하지 않기 때문에 Void로 변수를 선언을 할 수 없다.

void my_void;      // 이렇게 선언할 수 없다.

다만 Data type이 다르고 Data의 크기가 다르더라도 Data의 주소를 표현하는 Data의 양은 동일하다. 따라서 주소값을 알아볼 때 void*를 많이 활용한다.

int i = 123;
float f = 123.456;

void* my_void;

my_void = (void*)&i;    // 주소값을 알아보기 위해 사용
my_void = (void*)&f;

5. 부동소수점 수 (Floating point numbers)

소수점을 표현하는 방법을 한번 알아보자.

소수점을 표현할 수 있는 자료형은 3가지이다.
Double형보다 Float형이 최소 크기가 더 적기 때문에 메모리적인 측면에선 Float형을 사용하는 것이 좋다.

Float형이 숫자를 어떻게 저장할 때 메모리를 3 부분으로 나누어 이해를 할 수 있다.
Float형은 4 바이트이므로 32 비트를 사용한다.
맨 첫번째 비트는 부호비트, 8개의 비트를 활용해 지수, 나머지를 가수를 표현하는데 활용한다.

밑의 수를 분석해보면, 가수 부분을 표현하는데, $(1 + 0.75)$ 지수 부분을 표현하는데 $2^{7-127}$, 127은 Bias로 메모리에 저장하는 방식이라고 생각하면 된다.

따라서 이 복잡한 수를 통해 이해해야 할 것은 이진수, 부동 소수점 표현법을 거치면서 우리가 생각한 수와 다른 수가 저장될 수 있다는 것이다.

#include <limits>를 이용해서 각각 자료형이 나타낼 수 있는 최대, 최소 수를 한번 출력해보자.

#include <iostream>
#include <limits>

int main()
{
    using namespace std;    // 함수 안에서도 namespace 사용 정의 가능

    float f;
    double d;
    long double ld;

    cout << numeric_limits<float>::max() << endl;           // 3.40282e+38 출력
    cout << numeric_limits<double>::max() << endl;          // 1.79769e+308 출력
    cout << numeric_limits<long double>::max() << endl;     // 1.79769e+308 출력

    // min은 표현할 수 있는 가장 작은 숫자
    cout << numeric_limits<float>::min() << endl;           // 1.17549e-38 출력
    cout << numeric_limits<double>::min() << endl;          // 2.22507e-308 출력
    cout << numeric_limits<long double>::min() << endl;     // 2.22507e-308 출력

    // -부호를 활용해서 가장 작은 수를 보고 싶으면 lowest() 사용
    cout << numeric_limits<float>::lowest() << endl;        // -3.40282e+38 출력
    cout << numeric_limits<double>::lowest() << endl;       // -1.79769e+308 출력
    cout << numeric_limits<long double>::lowest() << endl;  // -1.79769e+308 출력
}

Float형을 초기화 할 때, e를 활용하는 방법도 알아야 한다.
e = 10이고 $3.14 * 100 = 3.14e2$ 라고 초기화를 하는 경우도 있다.

또한 Float형의 정밀도가 떨어지는 경우도 있는데 아래 예시 코드를 살펴보자.

#include <iostream>
#include <iomanip>	// 입출력을 조작할 때 사용하는 헤더파일 - setprecision() 사용

int main()
{
    using namespace std;

    float f(123456789.0f);	        // 숫자 뒤 f를 붙여야 float형 그렇지 않으면 double형
                                        // 10 significant digits

    cout << std::setprecision(9);	// cout할 때 출력되는 소숫점 조작
    cout << f << endl;                  // 123456792 출력
}

내부적으로 2진수를 이용해서 저장을 하기 때문에 우리가 생각하는 것처럼 저장이 안될 수 있다.
정밀도가 떨어질 수 있다는 것을 알고 있어야 한다!

Double 형은 어떤지 한번 알아보자.

#include <iostream>
#include <iomanip>	// 입출력을 조작할 때 사용하는 헤더파일 - setprecision() 사용

int main()
{
    using namespace std;

    double d(0.1);	        

    cout << d << endl;                  // 0.1 출력 (#1)    
    cout << std::setprecision(17);	// cout할 때 출력되는 소숫점 조작
    cout << d << endl;                  // 0.10000000000000001 출력 (#2)
}

#1#2가 차이가 나는 이유는 무엇일까?
2진수로 0.1을 표현할 때 부동소수점 방법을 사용해서 0.1을 표현할 수 있는 가장 가까운 수이기 때문이다.

이런 오차가 누적되면 우리가 생각하는 것과 다른 수가 나온다.
또 다른 예시를 살펴보자.

int main()
{
    using namespace std;

    double d1(1.0);
    double d2(0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1);   // 0.1을 10번 더함	        

    cout << std::setprecision(17);       // cout할 때 출력되는 소숫점 조작
    cout << d1 << endl;                  // 1.0 출력 (#3)    
    cout << d2 << endl;                   // 0.99999999999999989 출력 (#4)
}

오차가 누적되면 내가 원하는 대로 프로그램이 실행되지 않으니 꼭 알아두자.

또한 계산을 하는데 inf, nan(ind)와 같은 무한대 수가 나오면 이를 예외처리를 해주는 것이 좋은데, 그 때 쓰는 함수가 isnan()isinf()이다.

예시를 통해 살펴보자.

#include <iostream>
#include <cmath>    // // isNan() 함수를 사용하기 위해서

int main()
{
    double zero = 0.0;
    double inf = 5.0 / zero;
    double neginf = -5.0 / zero;
    double nan = zero / zero;

    cout << inf << "," << std::isnan(inf) << endl;
    cout << neginf << "," << std::isinf(neginf) << endl;
    cout << nan << "," << std::isnan(inf) << endl;
    cout << 1.0 << "," << std::isnan(1.0) << endl;
}

출력 결과는 다음과 같다.

inf, 0                                              
-inf,1
-nan(ind),0                     
1,0  

이 때 naninf는 다르다는 것도 알아둬야 한다!

6. 불리언 자료형과 조건문 if

Boolean은 표현할 수 있는 것이 True or False, 총 2가지이다.

Boolean을 초기화할 수 있는 방법은 앞장에서 배웠던 것과 같이 3가지이다.

bool b1 = true;		// copy initialization
bool b2(false);		// direct initialization
bool b3{ true }; 	// uniform initialization

이번 강의에서 총 3가지의 연산자를 배웠다.

  1. ! - Not operator : 피연산자의 값을 반전시킨다.
  2. && - AND operator : 조건들이 모두 true일 경우 true, 그렇지 않으면 false
  3. || - OR operator : 조건들 중 하나라도 true일 경우 true, 그렇지 않으면 false

아래 코드로 이해를 해보자.

#include <iostream>

using namespace std;

int main()
{
    bool b2(false);

    cout << std::boolalpha;	// 출력할 때 1,0이 아니라 true, false로 나오게 해줌.

    // NOT 연산자 !
    cout << b2 << endl;		// false 출력
    cout << !b2 << endl;	// true 출력

    cout << std::noboolalpha;	// 출력을 할 때 true, false가 아니라 1,0로 나오게 해줌
    // 논리 연산자 %%
    cout << (true && true) << endl;	// 1 출력
    cout << (true && false) << endl;	// 0 출력
    cout << (false && true) << endl;	// 0 출력
    cout << (false && false) << endl;	// 0 출력

    // OR 연산자 || 
    cout << (true || true) << endl;	// 1 출력
    cout << (true || false) << endl;	// 1 출력
    cout << (false || true) << endl;	// 1 출력
    cout << (false || false) << endl;	// 0 출력

    return 0;
}

std::noboolalphastd::boolalpha도 출력을 할 때 적절히 활용해보자.

boolean 타입에서 중요한 것은 0이 아니면 모두 true라고 인식하는 것이다.

// if문에 0이면 false, 나머지는 true
if (5)
    cout << "True" << endl;
else
    cout << "false" << endl;

위 코드는 True로 인식하고 True를 출력할 것이다.

또한 Boolean type으로 cin을 받고 false를 입력하면, 이를 숫자 0으로 인식하지 않기 때문에 참(true)값으로 이해를 하게 된다.

bool b;	// 숫자 0이 아니면 true
cin >> b;
cout << std::boolalpha;
cout << "Your input : " << b << endl;

위 코드에 false를 입력하게 되면 출력 값이 true로 나온다!
숫자 0을 입력해야 우리가 원하는 false값이 출력되게 된다.

7. 문자형 char type

컴퓨터는 문자 하나를 숫자로 변환하여 인식하게 된다.

변환을 할 때는 우리가 규칙에 따라 변환을 하는데 그 때 사용하는 Table이 “ASCII Table”이다.

char 자료형을 사용하면 숫자로 초기화를 해도 문자로 컴퓨터가 인식을 한다. 문자 하나를 초기화때는 ' '를 사용한다. 문자열을 초기화할 때는 " "를 사용한다.

char c1 = 65;
char c2('A');	// 한글자는 '', 문자열은 "" C++은 std::String을 사용

cout << c1 << " " << c2 << " " << (int)c1 << " " << (int)c2 << endl;    // A A 65 65 출력 

위와 같이 char를 숫자로 변환하여 출력하고 싶으면 형변환을 진행해야한다.

형변환을 하는 방법은 크게 3가지로 아래와 같다.

// c-style casting
cout << (char)65 << endl;               // A 출력
cout << (int)'A' << endl;               // 65 출력

// cpp style casting
cout << char(65) << endl;               // A 출력
cout << int('A') << endl;               // 65 출력

// 기본 타입들 변환할 때 사용
cout << static_cast<char>(65) << endl;  // A 출력
cout << static_cast<int>('A') << endl;  // 65 출력

char형은 한 글자밖에 담지 못하는 자료형인데 cin을 이용하여 여러 문자를 입력받으면 어떻게 될까?
나머지 문자들은 출력이 되지 않을 뿐 버퍼에 담겨있다!

#include <iostream>

using namespace std;

int main()
{    
    char c3;
    cin >> c3;
    cout << c3 << " " << static_cast<int>(c3) << endl;

    return 0;
}

위와 같은 코드에 abc를 입력으로 넣어주면 a만 출력된다.

반대로 버퍼에 문자가 있는데 cin으로 입력을 받으려고 한다면 어떻게 될까?

#include <iostream>

using namespace std;

int main()
{    
    char c3;
    cin >> c3;
    cout << c3 << " " << static_cast<int>(c3) << endl;

    cin >> c3;
    cout << c3 << " " << static_cast<int>(c3) << endl;

    cin >> c3;
    cout << c3 << " " << static_cast<int>(c3) << endl;

    cin >> c3;
    cout << c3 << " " << static_cast<int>(c3) << endl;

    return 0;
}

첫번째 cin에 abc를 입력한다면 두번째, 세번째 cin은 건너뛰고 네번째 cin에서 문자를 입력받고 출력한다!

char형과 unsigned char의 Size를 출력해보면 다음과 같다.

cout << sizeof(char) << endl;                           // 1 출력
cout << (int)std::numeric_limits<char>::max() << endl;  // 127 출력
cout << int(std::numeric_limits<char>::min()) << endl;  // -128 출력

cout << sizeof(unsigned char) << endl;                  // 1 출력
cout << (int)std::numeric_limits<unsigned char>::max() << endl; // 250 출력
cout << int(std::numeric_limits<unsigned char>::min()) << endl; // 0 출력

줄바꿈을 할 때 보통 \nstd::endl을 사용한다.
이 둘의 가장 큰 차이점은 \n은 단순히 줄바꿈을 하지만, std::endl는 버퍼에 담겨져 있는 것을 모두 출력한 후 줄바꿈을 한다는 것이다.

버퍼에 담겨져 있는 것을 쏟아낸 후 줄바꿈을 하지 않으려면 std::flush를 사용한다.

유니코드나 특수문자같은 8비트로 표현하기 어려운 문자들은 char32_t와 같은 자료형을 사용하기도 한다.

8. 리터럴 상수 literal constants

리터럴 상수란 말그대로 숫자를 직접 입력한 상수이다.

대부분의 경우는 숫자만 활용해서 초기화를 하지만, 자료형을 조금 더 명확하게 하기 위해 뒤에 문자를 사용하여 초기화하기도 한다.

float pi = 3.14f;	        // float 자료형
int i = 12334u;	                // unsigned int로 명확하게 하기
int j = (unsigned int)12334;    // 보통 형변환을 명시해주는 것이 좋다.

long n2 = 5L;                   // long 자료형
double d = 6e10;                // e는 10을 뜻함. d = 6x10^10

대부분은 10진수로 수를 나타내지만 8진수와 16진수를 이용해서 상수를 표현하기도 한다.
2진수를 활용해서 코딩을 할 때 보통 16진수로 표현한다.
8진수는 숫자 앞에 0을 추가하고, 16진수는 숫자 앞에 0x를 추가한다.

int x = 012;	    // 8진수로 표현하고 싶을 때
int y = 0xF;        // 16진수로 표현하고 싶을 때  

cout << x << endl;  // 10 출력
cout << y << endl;  // 15 출력

C++14부터 binary(2진법) literal 상수를 사용할 수 있다.

int z = 0b1010;	// 2진수
int z1 = 0b000'0001'0101;	// 사람이 읽기 편하도록 '를 추가 가능, 10진수도 사용가능

여기서 발견한 꿀팁은 숫자 중간 중간 '를 넣어도 컴파일러가 이를 무시하고 수로 인식을 한다는 것이다. 가독성을 높일 때 사용하면 좋을 것 같다.

또한 매직 넘버를 사용하지말고 const를 활용해서 리터럴 상수가 어떤 것을 의미하는지 먼저 선언하고 const로 선언한 변수를 사용하자.

매직 넘버란 컨텍스트(context)가 없는 코드 중간에서 하드 코딩된 리터럴(일반적으로 숫자)을 말한다.

int num_item = 123;
int price  = num_item * 30;     // 이렇게 사용하지 말자.

위 코드는 아래와 같이 사용하는 것이 좋다.

const int price_per_item = 30;
int num_item = 123;
int price  = num_item * price_per_item;

9. 심볼릭 상수 symbolic constants

심볼릭 상수는 변하지 않는 수인데 변수처럼 문자로된 이름을 가진 상수이다.

const를 활용해서 변하지 않는 수를 정의할 수 있다.

const double gravity;   // 잘못된 예  

double const gravity2{9.8};

gravity2 = 1.0;         // 값을 바꿀 수 없다.  

const를 활용해서 변수를 만들면 다시 값을 변경할 수 없으며 처음에 반드시 초기화를 진행해줘야한다.
자료형 뒤에 붙기도 하고, 앞에 붙기도 한다.

int doSometing(const int x)
{
    ...
    return new_number;
}

함수를 만들 때 입력에 const를 붙이는 경우가 많다. 함수 내에서 값이 변하는 것을 방지하고, 함수의 입출력을 명확하게 해주기 위해서다.

const int my_number(123);

int number;
cin >> number;
const int special_number(number);

위 코드에서 my_numberspecial_number의 차이점은 무엇일까?

컴파일 타임과 런 타임이 다르다는 것을 이해해야 한다.
my_number const 상수는 컴파일을 할 때, 변수가 어떤 변하지 않는 수로 결정이 되는데, special_number와 같은 상황에선 프로그램을 실행해야 const에 어떤 값이 들어가고 그 값에 의해 const 상수가 초기화가 된다.

이 두 const 상수의 차이를 명시해주기 위해 C++11에서 constexpr이라는 것이 추가되었다.

constexpr는 컴파일 타임에 값이 완전히 결정되는 컴파일 상수라는 것을 컴파일을 하면서 체크를 하겠다는 의미이다.

constexpr int my_number(123);           // 올바른 사용
constexpr int special_number(number);   // 잘못된 사용

따라서 위 예시에서 my_numberconstexpr를 활용할 수 있지만, special_number같은 경우는 constexpr를 사용할 수 없다.

매크로(#define)을 활용하여 심볼릭 상수를 만들기도 하는데, C++에서는 매크로를 활용해서 매직 넘버를 대체하는 경우는 거의 없다!

쓰지 않는 이유는 다음과 같다.

  1. 디버깅이 힘들다.
  2. #define을 활용해서 심볼릭 상수를 만들면 적용되는 범위가 너무 넓어진다.

그렇다면 const 상수는 어디에 선언하는 것이 좋을까?

내가 정의해둔 const 상수를 한 곳에 모아두는 것이 좋다. 따라서 헤더 파일을 하나 만들고 그 안에 namespace를 만든다음 그 안에 내가 정의한 const 상수를 적는 것이 추후 코딩을 할 때 도움이 된다. 재사용성이 있기 때문에 코딩하는 시간을 줄일 수 있고, 객체지향 프로그래밍 할 때 비슷한 스타일로 코딩을 하기 때문이다.


이렇게 쳅터 2를 모두 정리해보았다. 그동안 무심코 썼던 것들을 확실하게 짚고 넘어가는 느낌이다. 차곡차곡 정리하면 분명 도움이 될 것이다.

댓글남기기