안동민 개발노트 아이콘

안동민 개발노트

2장 : 변수와 데이터 타입

상수와 리터럴

이번 장에서는 이렇게 변하지 않는 값들을 좀 더 심도 있게 다루겠습니다. 바로 상수(Constants)리터럴(Literals)입니다.

상수와 리터럴은 프로그램 내에서 고정된 값을 표현하는 방식입니다.

이들을 정확히 이해하는 것은 코드의 가독성과 안정성을 높이는 데 매우 중요합니다.


상수(Constants)의 이해

상수와 리터럴은 값의 형태, 평가 순서, 경계 입력에서 먼저 확인합니다.

우리는 이미 const 키워드를 사용하여 상수를 선언하는 방법을 간략하게 살펴보았습니다.

상수는 이름이 있는 메모리 공간에 저장된 값이지만 변수와 달리 프로그램 실행 중 그 값을 변경할 수 없습니다.

왜 상수를 사용하는가?

상수를 사용하는 주된 이유는 다음과 같습니다.

의미 부여 및 가독성 향상: 3.141592라는 숫자만 보면 그것이 원주율인지, 다른 어떤 값인지 알기 어렵습니다. 하지만 const double PI = 3.141592;라고 선언하면 PI라는 이름 자체가 그 숫자의 의미를 명확하게 전달하여 코드의 가독성을 높입니다.

오류 방지: 실수로 값을 변경하려는 시도를 컴파일 시점에서 방지할 수 있습니다. 예를 들어, 원주율 PI의 값을 실수로 변경하려 한다면 컴파일러가 오류를 발생시켜 알려줄 것입니다. 이는 프로그램의 안정성을 높이는 데 기여합니다.

유지보수 용이성: 만약 프로그램의 여러 곳에서 동일한 고정 값을 사용하고 있다면, 이 값을 상수로 선언해두고 사용하는 것이 좋습니다. 나중에 이 값을 변경해야 할 때, 상수의 정의만 한 번 변경하면 모든 사용처에 자동으로 반영되므로 유지보수가 매우 편리해집니다.

상수를 선언하는 방법
  • const 키워드: 가장 일반적이고 권장되는 방법입니다. 변수 선언 시 타입 앞에 const를 붙여 상수로 만듭니다. 상수는 반드시 선언과 동시에 초기화되어야 합니다.

    const 상수 선언 예시
    const double PI = 3.1415926535; // 원주율
    const int MAX_USERS = 100;     // 최대 사용자 수
    const char NEWLINE = '\n';     // 개행 문자

    const를 사용하면 컴파일러가 해당 값이 변경되지 않음을 알기 때문에 최적화를 수행하는 데도 도움이 될 수 있습니다.

  • #define 전처리기 지시자 (레거시 방식): #define은 C++보다는 C 언어에서 상수를 정의할 때 많이 사용되던 방식입니다. 이는 컴파일 이전에 단순한 텍스트 치환(macro substitution)을 수행합니다.

    #define 매크로 예시
    #define PI_MACRO 3.1415926535 // 세미콜론을 붙이지 않습니다!
    #define MAX_BUFFER_SIZE 1024
    const#define의 차이점
    • 타입 안전성: const는 타입 정보를 가지므로 컴파일러가 타입 체크를 수행하여 오류를 방지할 수 있습니다. #define은 단순 텍스트 치환이라 타입 체크가 이루어지지 않습니다.
    • 디버깅: const 상수는 디버거에서 심볼 정보로 확인 가능하지만, #define 매크로는 컴파일 전에 사라지므로 디버깅이 어렵습니다.
    • 스코프: const 상수는 변수와 동일하게 스코프(유효 범위)를 가질 수 있어 특정 블록이나 함수 내에서만 유효하게 만들 수 있습니다. #define 매크로는 정의된 이후부터 파일의 끝까지 전역적으로 유효합니다.
    • 메모리 사용: const 상수는 실제 메모리 공간을 차지할 수 있지만, 컴파일러 최적화에 따라 레지스터에 저장되거나 직접 값이 삽입되어 메모리를 차지하지 않을 수도 있습니다. #define은 텍스트 치환이므로 메모리를 차지하지 않습니다. 이러한 이유로 현대 C++에서는 #define을 사용하여 상수를 정의하는 대신 const 키워드를 사용하는 것을 강력히 권장합니다.
  • enum (열거형): enum은 주로 관련된 정수 값들을 의미 있는 이름으로 묶을 때 사용됩니다. 상수의 집합을 정의하는 데 유용합니다.

    enum 예시
    enum Color { RED, GREEN, BLUE }; // RED는 0, GREEN은 1, BLUE는 2의 값을 가짐 (기본적으로 0부터 시작)
    enum State { IDLE = 0, RUNNING = 1, PAUSED = 2, STOPPED = 3 }; // 특정 값 지정 가능
    // 사용 예시
    Color myColor = RED;
    if (myColor == RED) {
        // ...
    }

    enum에 대해서는 나중에 더 자세히 다룰 기회가 있을 것입니다.


리터럴(Literals)의 이해

리터럴은 소스 코드에 직접적으로 표현되는 고정된 값 자체를 의미합니다.

즉, 변수에 저장되지 않고 코드 내에 문자 그대로 쓰여진 값입니다.

리터럴은 특정 데이터 타입을 가집니다.

리터럴의 종류
정수 리터럴 (Integer Literals)
  • 십진수: 우리가 가장 흔히 사용하는 형태입니다. (예: 10, 123, -5)
  • 이진수 (Binary): C++14부터 0b 또는 0B 접두사를 사용하여 이진수를 표현할 수 있습니다. (예: 0b1010은 십진수 10)
  • 팔진수 (Octal): 0 접두사를 사용하여 팔진수를 표현합니다. (예: 012은 십진수 10)
  • 십육진수 (Hexadecimal): 0x 또는 0X 접두사를 사용하여 십육진수를 표현합니다. (예: 0xA은 십진수 10)

타입 접미사: 정수 리터럴 뒤에 특정 접미사를 붙여 타입을 명시할 수 있습니다.

  • U 또는 u: unsigned (부호 없는) 정수. (예: 100U)
  • L 또는 l: long 정수. (예: 100L)
  • LL 또는 ll: long long 정수. (예: 100LL)
  • UL, ull 등 조합도 가능합니다.
정수 리터럴 예시
int dec = 10;
int bin = 0b1010; // C++14
int oct = 012;
int hex = 0xA;

long long veryBig = 123456789012345LL;
unsigned int flags = 0xFFU; // 십육진수 255 (unsigned int)

부동 소수점 리터럴 (Floating-Point Literals): 소수점이나 지수 표기(e 또는 E)를 포함한 숫자입니다.

기본적으로 double 타입으로 간주됩니다.

타입 접미사
  • F 또는 f: float 타입. (예: 3.14F)
  • L 또는 l: long double 타입. (예: 3.14L)
부동 소수점 리터럴 예시
double dval = 3.14;
float fval = 3.14F;
long double ldval = 3.14L;

double sci_notation = 6.02e23; // 6.02 * 10^23

문자 리터럴 (Character Literals) 작은따옴표('')로 묶인 단일 문자입니다. 기본적으로 char 타입입니다.

접미사/접두사
  • L: wchar_t (와이드 문자). (예: L'가')
  • u: char16_t (UTF-16 문자). (예: u'글')
  • U: char32_t (UTF-32 문자). (예: U'자')

이스케이프 시퀀스 (Escape Sequences): 특정 특수 문자는 백슬래시(\)와 함께 조합하여 표현합니다.

  • \n: 줄 바꿈 (개행)
  • \t: 탭
  • \\: 백슬래시 자체
  • \': 작은따옴표
  • \": 큰따옴표
  • \0: 널 문자 (문자열의 끝을 나타내는 데 사용)
문자 리터럴 예시
char ch = 'A';
char newline = '\n'; // 줄 바꿈 문자
char backslash = '\\'; // 백슬래시 문자

문자열 리터럴 (String Literals) 큰따옴표("")로 묶인 일련의 문자 시퀀스입니다.

C-스타일 문자열(character array)로 취급되며, 항상 마지막에 널 문자(\0)가 자동으로 추가됩니다.

접두사
  • L: wchar_t 문자열. (예: L"안녕하세요")
  • u: char16_t 문자열. (예: u"Hi")
  • U: char32_t 문자열. (예: U"Hello")
  • R: 원시 문자열 (Raw String Literal). 이스케이프 시퀀스를 해석하지 않고 문자 그대로를 사용합니다. 파일 경로 등을 표현할 때 유용합니다. (예: R"(C:\new\folder)"C:\new\folder 그대로 해석)
문자열 리터럴 예시
const char* message = "Hello, C++!"; // C-스타일 문자열
std::string name = "나 혼자 C++"; // C++ 표준 라이브러리의 string 타입

// 원시 문자열 리터럴
const char* path = R"(C:\Users\Admin\Documents\my_file.txt)";
std::cout << path << std::endl; // 출력: C:\Users\Admin\Documents\my_file.txt

std::string은 C++ 표준 라이브러리에서 제공하는 문자열 클래스로, C-스타일 문자열보다 유연하고 안전한 문자열 처리를 제공합니다. 이에 대해서는 나중에 별도의 장에서 자세히 다룰 것입니다.

논리 리터럴 (Boolean Literals) 논리 값을 직접 표현하는 리터럴은 두 가지뿐입니다.

  • true: 참
  • false: 거짓
논리 리터럴 예시
bool isComplete = true;
bool isValid = false;
포인터 리터럴 (Pointer Literal)
  • nullptr: C++11부터 도입된 포인터 리터럴입니다. 아무것도 가리키지 않는 널 포인터를 명시적으로 나타냅니다. C에서는 NULL이나 0을 사용하기도 했지만, nullptr은 타입 안전성을 제공하여 더 바람직합니다.
포인터 리터럴 예시
int* ptr = nullptr; // 아무것도 가리키지 않는 정수형 포인터

아래 다이어그램은 상수 선언 방식과 리터럴 표기법을 선택할 때 확인할 기준을 한 번 더 압축해 보여줍니다.


리터럴 상수 (Literal Constants)

리터럴은 그 자체로 상수입니다.

10, 3.14, 'A', "Hello"와 같은 값들은 프로그램 내에서 고정된 값을 가지며 변경할 수 없습니다.

이들은 이름이 없는 상수라고 볼 수 있습니다.

우리가 변수를 선언하고 const 키워드로 상수를 만드는 것은 이렇게 이름 없는 리터럴에 의미 있는 이름을 부여하여 코드의 가독성과 유지보수성을 높이기 위함입니다.

상수와 리터럴 예시
#include <iostream>
#include <string> // std::string을 사용하기 위해 포함

int main() {
    // 상수 선언 (이름 있는 상수)
    const int PROGRAM_VERSION = 2;
    const double DISCOUNT_RATE = 0.15; // 15% 할인
    const std::string WELCOME_MESSAGE = "환영합니다!";

    // 리터럴 직접 사용 (이름 없는 상수)
    int score = 95; // 95는 정수 리터럴
    double tax = score * 0.05; // 0.05는 부동 소수점 리터럴

    std::cout << WELCOME_MESSAGE << std::endl;
    std::cout << "프로그램 버전: " << PROGRAM_VERSION << std::endl;
    std::cout << "할인율: " << DISCOUNT_RATE * 100 << "%" << std::endl;
    std::cout << "원래 점수: " << score << std::endl;
    std::cout << "세금: " << tax << std::endl;

    // 문자 리터럴과 이스케이프 시퀀스
    char tabChar = '\t';
    std::cout << "이것은" << tabChar << "탭 문자입니다." << std::endl;
    std::cout << "큰따옴표(\")와 백슬래시(\\)를 사용할 수 있습니다." << std::endl;

    return 0;
}

아래 다이어그램은 값 표현을 리터럴로 둘지, 이름 있는 상수로 뽑을지 판단하는 기준을 정리합니다.

아래 다이어그램은 리터럴 표기에서 타입 정보를 읽고, 반복되는 값을 상수로 올리는 흐름을 함께 정리합니다.