소년코딩

02.10 - 상수 (const, constexpr, and symbolic constants)

상수 (constant)

상수란 그 값이 변하지 않는 불변 값이다. 지금까지 본 변수들은 모두 언제든지 값이 변할 수 있었다.

int x { 4 }; // initialize x with the value of 4
x = 5;       // change value of x to 5

그러나 변경할 수 없는 값으로 변수를 정의하는 것이 유용하다. 예를 들어, 지구의 중력은 9.8m/sec^2로 바뀌지 않는 불변 값이다. 이 값을 상수로 정의하면 이 값은 실수로 변경되지 않는다.

변수를 상수로 설정하려면 변수 자료형 앞이나 다음에 const 키워드를 사용하면 된다.

const double gravity { 9.8 }; // preferred use of const before type
int const sidesInSquare { 4 }; // okay, but not preferred

C++ 은 자료형 이전 또는 이후에 const를 허용하지만, 자료형 이전에 사용하는 것이 관습상 더 좋다.

상수 변수를 정의할 때 초기화(initialization)해야 하며, 할당(assignment)을 통해 값을 변경할 수 없다.

변수를 상수로 선언하면 다음과 같이 값을 바꿀 수 없다:
const double gravity { 9.8 };
gravity = 9.9; // not allowed, this will cause a compile error
상수(const) 변수를 초기화하지 않고 정의하면 컴파일 오류가 발생한다:
const double gravity; // compiler error, must be initialized upon definition
상수 변수는 일반 변수 값으로부터 초기화할 수 있다.
std::cout << "Enter your age: ";
int age;
std::cin >> age;

const int usersAge (age); // usersAge can not be changed
상수는 함수의 매개 변수(parameter)와 함께 사용하는 경우가 많다.
void printInteger(const int myValue)
{
    std::cout << myValue;
}

함수의 매개변수를 상수로 만드는 것은 두 가지 기능이 있다.

  1. 함수를 호출하는 사람에게 myValue 값을 변경하지 말라고 말한다.
  2. myValue 값을 변경하지 못한다.

컴파일 시간 VS 런타임 (Compile time vs runtime)

프로그램을 컴파일하는 과정에 있을 때, 컴파일 시간이라고 한다. 컴파일 시간 동안 컴파일러는 코드가 문법적으로 정확한지 확인하고 코드를 목적 파일(object file)로 바꾼다.

응용프로그램을 실행하는 과정에 있을 때, 런타임 이라고 한다. 런타임에 프로그램이 한 줄씩 실행된다.


constexpr

사실 C++ 에는 두 가지 다른 종류의 상수가 있다.

런타임 상수(runtime constant)는 초깃값을 런타임에서만 확인할 수 있는 상수다. 위 예제에서 usersAgemyValue는 컴파일러가 컴파일 시 값을 결정할 수 없으므로 런타임 상수다. userAge는 런타임 때 사용자 입력에 의존하며, myValue는 함수에 전달되는 값에 따라 달라진다.

컴파일 시간 상수(compile-time constant)는 컴파일 시간에 초깃값을 확인할 수 있는 상수다. 위 예제에서 gravity는 컴파일 시간 상수다. 예제에서 gravity가 사용될 때마다 컴파일러는 gravity 식별자를 double literal 9.8로 치환한다.

사실 상수 값이 런타임인지 컴파일 시간인지는 중요하지 않다. 그러나 C++ 에는 런타임 상수 대신에 컴파일 타임 상수를 요구하는 몇 가지 경우가 있다. (ex. 고정 크기 배열의 길이를 정의하는 경우)

더 많은 특수성을 제공하기 위해 C++ 은 constexpr을 도입했다. constexpr 키워드를 사용한 상수는 컴파일 시간 상수여야 한다.

constexpr double gravity (9.8); // ok, the value of 9.8 can be resolved at compile-time
constexpr int sum = 4 + 5; // ok, the value of 4 + 5 can be resolved at compile-time

std::cout << "Enter your age: ";
int age;
std::cin >> age;
constexpr int myAge = age; // not okay, age can not be resolved at compile-time

심볼릭 상수 (Symbolic constants)

이전 포스트에서 "매직 넘버(magic number)"를 배웠다. 매직 넘버는 나쁜 습관이기 때문에 심볼릭 상수(symbolic constant)를 사용해야 한다. 심볼릭 상수는 상수 리터럴 값이 지정된 이름이다. C++ 에는 심볼릭 상수를 선언하는 두 가지 방법이 있다. 하나는 좋고, 하나는 좋지 않다.

Bad: 객체와 유사한 매크로(object-like macro)를 사용한 방법은 좋지 않다.

#define identifier substitution_text

'전처리기' 포스트에서 배웠듯 전처리기가 이 지시자를 발견하면 'identifier'은 앞으로 'substitution_text' 텍스트로 대체된다.

#define MAX_STUDENTS_PER_CLASS 30
int max_students = numClassrooms * MAX_STUDENTS_PER_CLASS

위 코드를 컴파일하면 전처리기가 모든 MAX_STUDENTS_PER_CLASS를 리터럴 값 30으로 바꾼 후 컴파일한다.

다양한 이유로 매직 넘버를 사용하는 것보다 위 방법이 훨씬 직관적이다. 그러나 #define을 이용해서 심볼릭 상수를 만들어 사용하는 것은 두 가지 문제가 있다.

  1. 매크로(macro)를 사용한 심볼릭 상수는 디버거에 표시되지 않는다.
  2. #define된 값은 항상 파일 스코프(=범위)에 있으므로 나중에 #define된 값과 충돌할 수 있다.
#include <iostream>

void a()
{
// 정의된 값이 파일의 나머지 부분까지 유효하다.
#define x 5
    std::cout << x;
}

void b()
{
// 여기서 x를 6으로 정의하려 하지만..
// 함수 a() 내부에서 정의된 x와 충돌이 일어난다.
#define x 6
    std::cout << x;
}

int main() {

    a();
    b();

    return 0;
}

This outputs:
5
5
Good: const 변수를 사용한다.

심볼릭 상수를 생성하는 좋은 방법은 const(or constexpr) 변수를 사용하는 것이다.

constexpr int maxStudentsPerClass { 30 };
constexpr int maxNameLength { 30 };

위 변수들은 디버거에서 표시되고, 일반적인 변수 스코프(scope)를 따른다.


cpp 번역: 이 포스트의 원문은 http://www.learncpp.com/cpp-tutorial/2-9-symbolic-constants-and-the-const-keyword/ 입니다.

댓글 로드 중…

블로그 정보

소년코딩 - 소년코딩

소년코딩, 자바스크립트, C++, 물리, 게임 코딩 이야기

최근에 게시된 이야기