C++ 프로그래밍에서 상수를 선언하는 방법은 여러 가지가 있습니다. 그중 대표적인 세 가지가 바로 static const, #define, enum입니다. 각 방식은 동작 원리와 용도가 다르기 때문에 상황에 맞게 선택하는 것이 중요합니다. 이 글에서는 세 가지 방식의 개념과 차이점을 예제 코드와 함께 자세히 살펴보겠습니다.
"static const"란 무엇인가?
static const는 저장 지정자(storage specifier)인 static과 타입 한정자(type qualifier)인 const의 조합입니다.
static은 변수의 수명(lifetime)과 접근 범위(visibility)를 결정합니다. 변수를 static으로 선언하면 프로그램이 실행되는 동안 메모리에 계속 유지됩니다. 반면 일반(auto) 변수는 해당 변수가 정의된 함수가 종료되면 함께 소멸합니다.
const는 타입 한정자로, 타입 시스템을 통해 값에 대한 추가 정보를 표현합니다. const로 초기화된 변수는 이후 값을 변경할 수 없습니다.
따라서 두 키워드를 결합한 static const로 선언된 변수는 프로그램이 종료될 때까지 값을 유지하면서, 한 번 초기화되면 그 값을 절대 변경할 수 없는 특성을 갖게 됩니다.
static const 예제
#include<iostream>
using namespace std;
int main(){
int input = 10;
static const int value = 5;
input += value;
cout << input;
return 0;
}
위 예제에서 value는 5로 고정되며, 프로그램 전체 실행 기간 동안 메모리에 존재하지만 값을 수정할 수는 없습니다. 출력 결과는 15입니다.
"#define"이란 무엇인가?
전처리기(preprocessor) 명령어를 디렉티브(directive)라고 부르며, 샵(#) 또는 해시(#) 기호로 시작합니다. # 앞에는 공백이 있으면 안 되고, 문장 끝에 세미콜론(;)을 붙이지 않는다는 점에 유의해야 합니다.
전처리 단계에서 할 수 있는 작업은 다음과 같습니다.
#include디렉티브를 통한 다른 파일 포함#define디렉티브를 통한 심볼릭 상수 및 매크로 정의
또한 일부 전처리기 디렉티브를 사용하면 조건부 컴파일이나 조건부 전처리 작업도 수행할 수 있습니다.
참고: C++ 프로그램의 전처리 단계는 컴파일 이전에 진행됩니다. 즉, C++ 전처리기는 소스 코드가 컴파일되기 전에 실행되는 별도의 프로그램입니다.
헤더 파일을 포함할 때 사용하는 #include 디렉티브는 이미 익숙할 것입니다. 이번 섹션에서는 #define 디렉티브에 집중해 보겠습니다.
#define 전처리기를 사용하면 심볼릭 이름과 상수를 정의할 수 있습니다. 예를 들면 다음과 같습니다.
#define PI 3.14159
이 문장은 프로그램 내에서 PI가 등장하는 모든 위치를 3.14159로 치환합니다.
C++ #define 예제
다음은 PI가 등장할 때마다 #define으로 정의한 값 3.14159로 치환되는 완전한 예제입니다.
Input: PI 3.14159 Output: Area of Circle 314.15
#include<iostream>
using namespace std;
#define PI 3.14159
int main() {
int r = 10;
float cir;
cir = PI * (r * r);
cout<<"Area of Circle: "<<cir<<endl;
}
반지름이 10인 원의 넓이가 314.15로 출력되는 것을 확인할 수 있습니다.
"enum"이란 무엇인가?
enum(열거형)은 사용자가 직접 정의하는 데이터 타입으로, 변수가 가질 수 있는 값의 집합을 지정하고 해당 변수는 그 집합 중 하나의 값만 가질 수 있습니다. 열거형은 enum 키워드로 정의합니다.
enum direction {East, West, North, South}dir;
여기서 열거형 이름은 direction이며, 네 가지 지정된 값 중 하나만 가질 수 있습니다. 선언 끝에 있는 dir은 enum 변수입니다.
간단한 예제를 통해 이해해 보겠습니다. enum 변수 dir에 West 값을 할당하고 출력하면 1이 표시됩니다. 그 이유는 enum 값이 기본적으로 0부터 시작하여 순차적으로 증가하기 때문입니다. 즉, East는 0, West는 1, North는 2, South는 3입니다.
Input: direction {East, West, North, South} dir=West
Output: 1
enum 예제
#include<iostream>
using namespace std;
enum direction {East, West, North, South} dir;
int main(){
dir = West;
cout<<dir;
return 0;
}
enum 변수를 선언하는 또 다른 방법
앞선 예제에서는 enum 선언과 동시에 변수 dir을 선언했습니다. 하지만 enum 타입을 먼저 정의한 후, 나중에 변수를 따로 선언하는 방법도 있습니다.
#include <iostream>
using namespace std;
enum direction {East, West, North, South};
int main() {
direction dir;
dir = South;
cout<<dir;
return 0;
}
출력 결과
3
C++에서 enum을 사용하는 이유
enum의 개념과 사용법을 이해했다면, 왜 enum을 사용해야 하는지 알아보겠습니다.
첫째, 컴파일 타임 검증 강화입니다. enum은 변수가 가능한 값 집합 중 하나만 가질 것으로 예상되는 경우에 사용합니다. 예를 들어 방향을 저장하는 dir 변수가 있다면, 네 가지 방향 중 하나의 값만 가져야 합니다. 만약 엉뚱한 임의의 값을 할당하려 하면 컴파일 오류가 발생합니다. 이를 통해 잘못된 상수 전달로 인한 오류를 사전에 방지할 수 있습니다.
둘째, switch-case 문에서의 활용입니다. switch문의 case 블록에서 기대하는 모든 값을 enum으로 정의하면, switch 괄호 안에 전달되는 enum 변수가 허용되지 않는 임의의 값을 갖지 않도록 보장할 수 있습니다. 이는 코드의 안정성과 가독성을 크게 높여줍니다.
마무리: 어떤 것을 선택해야 할까?
세 가지 방식을 요약하면 다음과 같습니다.
static const: 타입 검사가 적용되고 스코프 규칙을 따르는 안전한 상수 선언 방식. C++에서 가장 권장됩니다.
#define: 전처리 단계에서 단순 텍스트 치환이 이루어지므로 타입 검사가 없고 디버깅이 어려울 수 있습니다.
enum: 연관된 상수들의 집합을 정의할 때 유용하며, switch문과 함께 사용하면 코드 안정성이 향상됩니다.
현대 C++에서는 타입 안정성과 디버깅 편의성 때문에 static const나 enum(또는 enum class)을 사용하는 것이 일반적으로 더 좋은 선택입니다.