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 };
int const sidesInSquare { 4 };
C++ 은 자료형 이전 또는 이후에 const
를 허용하지만, 자료형 이전에 사용하는 것이 관습상 더 좋다.
상수 변수를 정의할 때 초기화(initialization)해야 하며, 할당(assignment)을 통해 값을 변경할 수 없다.
변수를 상수로 선언하면 다음과 같이 값을 바꿀 수 없다:
const double gravity { 9.8 }
gravity = 9.9
상수(const) 변수를 초기화하지 않고 정의하면 컴파일 오류가 발생한다:
const double gravity;
상수 변수는 일반 변수 값으로부터 초기화할 수 있다.
std::cout << "Enter your age: ";
int age;
std::cin >> age;
const int usersAge (age);
상수는 함수의 매개 변수(parameter)와 함께 사용하는 경우가 많다.
void printInteger(const int myValue)
{
std::cout << myValue;
}
함수의 매개변수를 상수로 만드는 것은 두 가지 기능이 있다.
- 함수를 호출하는 사람에게
myValue
값을 변경하지 말라고 말한다.
myValue
값을 변경하지 못한다.
컴파일 시간 VS 런타임 (Compile time vs runtime)
프로그램을 컴파일하는 과정에 있을 때, 컴파일 시간이라고 한다. 컴파일 시간 동안 컴파일러는 코드가 문법적으로 정확한지 확인하고 코드를 목적 파일(object file)로 바꾼다.
응용프로그램을 실행하는 과정에 있을 때, 런타임 이라고 한다. 런타임에 프로그램이 한 줄씩 실행된다.
constexpr
사실 C++ 에는 두 가지 다른 종류의 상수가 있다.
런타임 상수(runtime constant)는 초깃값을 런타임에서만 확인할 수 있는 상수다. 위 예제에서 usersAge
와 myValue
는 컴파일러가 컴파일 시 값을 결정할 수 없으므로 런타임 상수다. userAge
는 런타임 때 사용자 입력에 의존하며, myValue
는 함수에 전달되는 값에 따라 달라진다.
컴파일 시간 상수(compile-time constant)는 컴파일 시간에 초깃값을 확인할 수 있는 상수다. 위 예제에서 gravity
는 컴파일 시간 상수다. 예제에서 gravity
가 사용될 때마다 컴파일러는 gravity
식별자를 double literal 9.8로 치환한다.
사실 상수 값이 런타임인지 컴파일 시간인지는 중요하지 않다. 그러나 C++ 에는 런타임 상수 대신에 컴파일 타임 상수를 요구하는 몇 가지 경우가 있다. (ex. 고정 크기 배열의 길이를 정의하는 경우)
더 많은 특수성을 제공하기 위해 C++ 은 constexpr
을 도입했다. constexpr
키워드를 사용한 상수는 컴파일 시간 상수여야 한다.
constexpr double gravity (9.8);
constexpr int sum = 4 + 5;
std::cout << "Enter your age: ";
int age;
std::cin >> age;
constexpr int myAge = age;
심볼릭 상수 (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
을 이용해서 심볼릭 상수를 만들어 사용하는 것은 두 가지 문제가 있다.
- 매크로(macro)를 사용한 심볼릭 상수는 디버거에 표시되지 않는다.
#define
된 값은 항상 파일 스코프(=범위)에 있으므로 나중에 #define
된 값과 충돌할 수 있다.
#include <iostream>
void a()
{
#define x 5
std::cout << x;
}
void b()
{
#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)를 따른다.
번역: 이 포스트의 원문은 http://www.learncpp.com/cpp-tutorial/2-9-symbolic-constants-and-the-const-keyword/ 입니다.