wellcome_공부일기

C++ | 02.10 기호 상수(Symbolic constants) 본문

프로그래밍/C++

C++ | 02.10 기호 상수(Symbolic constants)

ma_heroine 2020. 5. 11. 07:26

<목차>

1. Const variables

2. 기호 상수(symbolic constants)란?

3. 기호 상수(symbolic constants)의 나쁜 사용 예

4. 기호 상수(symbolic constants)의 좋은 사용 예

5. Using symbolic constants throughout a multi-file program(To be continued...)

 

 

 

Const variables란?

그 동안, 우리가 봐온 모든 변수들은 non-constant였습니다.

이 말은 모든 변수들의 값이 언제든지 바뀔 수 있음을 말합니다.

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

하지만, 때때로 변하지 않는 변수의 값을 정의하는 것은 유용합니다.

예를 들어, 지구의 중력을 생각해보면 9.8meter/second^2입니다.

위의 과학적 측정 값은 쉽게 바뀌지 않을 것입니다.

만약 바뀐다면 C++보다 더 큰 문제가 발생하고 있다는 뜻이겠죠?  (´•̥ω•̥`) 

이러한 값을 상수(constants)로서 정의하면, 이 값이 실수로 변경되지 않는 데 도움이 됩니다.

 

변수를 상수(constant)로 만들기 위해서는, 간단하게 const 키워드를 변수 타입 앞 혹은 뒤에 두면 됩니다.

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

비록 C++이 타입 전후에 const를 받아들인다고 하더라도, 표준 영어 관례( standard English language)는 단어에 수식어가 붙을 때, 해당 단어(object)보다 수식어가 먼저 나오도록 따르므로, 타입 이전에 const를 사용하는 것이 좋습니다. 

(e.g. a “green ball”, not a “ball green”).

 

 

const 변수는 정의와 동시에 초기화를 시켜주어야 하며, 대입(assignment)를 통해 값을 바꿀 수 없습니다. 

 

변수를 const로 선언하면 실수로 값을 변경하지 못합니다.

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

 

const 변수는 다른 변수로부터 초기화될 수도 있습니다.(non-const를 통해서도 가능)

std::cout << "Enter your age: ";
int age;
std::cin >> age;
 
const int usersAge { age }; // usersAge can not be changed

 

Const는 함수의 파라미터로서도 종종 사용됩니다. 

void printInteger(const int myValue)
{
    std::cout << myValue;
}

함수 파라미터를 const로 만드는 것은 두가지 경우입니다.

첫번째는 함수를 호출하는 사람에게 함수가 myValue의 값을 변경하지 않을 것임을 알려줍니다. 

둘째는 함수가 myValue의 값을 변경하지 못하도록 보장합니다. 

 

인수(arguments)가 값으로 전달될 때, 우리는 일반적으로 함수가 매개변수의 값을 바꾸는지 여부에 대해 상관하지 않습니다. (왜냐면 어찌됐든 함수의 마지막에는 없어져버릴 복사본이기 때문에)

이러한 이유로, 우리는 const 파라미터가 값을 전달받지 못하게 합니다.

하지만 후에, 다른 종류의 함수 차라미터에 대해 이야기를 할 건데, 여기서는 파라미터의 값이 변경되면, 전달된 인수의 값이 변경됩니다. 이러한 매개변수에 대해서는 const를 현명하게 사용하는게 중요합니다. 

 

Naming your const variables

어떤 프로그래머들은 const 변수에 모든 대문자 이름을 사용하는 것을 선호합니다.

다른 프로그래머들은 "k" 접두사가 있는 일반 변수 이름을 사용합니다.

그러나 우리는 일반 변수처럼 이름을 짓는 것을 규칙을 사용합니다. 

const 변수는 할당될 수 없다는 점을 제외하고는 모든 경우에 일반 변수와 똑같은 역할을 하므로 특별한 것으로 표시되어야 할 이유가 없습니다. 

 

기호 상수(symbolic constants)란?

그 전 Literals 포스팅에서 언급을 하진 않았지만, 프로그램에서 상수 값(constant value)을 표현하기 위해 리터럴(literals)로 쓰여지는 "magic number"라는 게 존재합니다. magic number는 쓰면 안되는 것 중 하나인데요.

이 대신 symbolic constants를 사용하면 됩니다. ٩( ᐛ )و 

/* when using "magic nimber" */
int num_items = 123;
int price = num_items * 10

/* when using symbolic constants */
const int price_per_item = 10;
int num_items = 123;
int price = num_items * price_per_item;
    

- symbolic constants은 상수 리터럴 값(constants literal value)에게 이름을 지어준 것을 말합니다. 

- const를 이용한 symbolic constants를 사용하면 상수명 자체가 숫자에 대한 설명을 해줄 수 있습니다.

 

 

C++에는 symbolic constants를 선언하는 두가지 방법이 있는데, 하나는 좋은 방법이고 다른 하나는 

좋지 않은 방법입니다. 좋지 않은, 나쁜 방법에 대해 먼저 작성하겠습니다.  (ง°̀ロ°́)ง 

 

 

 상징적 상수(symbolic constants)의 나쁜 사용 예

바로 매크로(macros)를 상징 기호로 사용하여 대체 파라미터를 사용하는 것입니다. 

전에 전처리기에 대해 이야기 하면서, object-like macros는 두가지 형식을 가진다고 언급을 했었습니다.

하나는 대체 파라미터를 가지지 않는 것이고 보통 조건부 컴파일에서 사용되고, 다른 하나는 대체 파라미터를 가지는 것으로 이번에 말할 나쁜 예시에는 대체 파라미터를 취하는 매크로에 대해서 이야기할 것입니다. 형식은 아래와 같습니다.

#define identifier substitution_text

전처리기가 이 지시문을 만날때 마다, 코드 내에서 식별자(identifier)를 만날 때마다 대체 텍스트( substitution_text)로 대체됩니다. 

식별자(identifier)는 전통적으로 대문자로 입력되며 밑줄을 사용하여 공백을 나타냅니다. 

 

아래는 macro를 사용한 코드블럭입니다.

#define MAX_STUDENTS_PER_CLASS 30
int max_students { numClassrooms * MAX_STUDENTS_PER_CLASS };

코드를 컴파일 할때, 전처리기는 모든 MAX_Students_PER_CLASS의 인스턴스를 리터럴 값 30으로 대체합니다.

이 값은 실행 파일에 컴파일됩니다. 

 

위의 macros 사용은 몇 가지 이유로 "magic number"를 사용하는 것보다 더 직관적이라고 느낄 수 있습니다.

첫째로 MAX_Students_PER_CLASS는 멘트가 없어도 어떤 프로그램이 일을 할려고 하는지에 대한 컨텍스트를 제공합니다.

둘째는 교실 당 최대 학생수가 바뀌면 MAX_Students_PER_CLASS의 값만 한 곳에서 변경하면 되고, MAX_Students_PER_CLASS의 모든 인스턴스는 다음 실행에서 새로운 리터럴 값으로 대체됩니다.

 

다음은 #define symbolic constants을 사용한 예입니다. 

#define MAX_STUDENTS_PER_CLASS 30
#define MAX_NAME_LENGTH 30
 
int max_students { numClassrooms * MAX_STUDENTS_PER_CLASS };
setMax(MAX_NAME_LENGTH);

이 경우, MAX_Students_PER_CLASS와 MAX_NAME_LENGE는 우연히 동일한 값(30)을 공유하더라도 독립적인 값으로 의도되었습니다. 그렇게 하면, 만약 우리가 교실 크기를 수정해야 한다면, 우리는 실수로 이름 길이도 바꾸지 않을 것입니다.

그렇다면 기호 상수(symbolic constants)를 만들기 위해 #define을 사용하는 것은 어떨까?

크게 세 가지 문제가 있습니다.

첫째, 매크로는 기호 이름(the symbolic name)을 정의된 값(the defined value)으로 대체하는 전처리에 의해 해결됩니다.

그렇기 때문에 #define 기호 상수(symbolic constants)는 디버거(실제 코드에는 표시됨)에 나타나지 않습니다.

따라서 비록 컴파일러가 max_students {numClassrooms * 30 };과

디버거에서는 볼 수 있듯이, max_Students {numClassrooms * MAX_Students_PER_CLASS};

그리고 MAX_Students_PER_CLASS를 컴파일하더라도 볼 수 없을 것입니다.

즉, MAX_Students_PER_CLASS의 정의를 찾아야만 실제 값이 무엇인지 알 수 있습니다.

이렇게 하면 프로그램을 디버깅하기가 더 어려워질 수 있다는 문제가 있습니다.

 

 

두번째는, macros가 일반 코드와 충돌될 수 있는 예입니다.

#include "someheader.h"
#include <iostream>
 
int main()
{
    int beta { 5 };
    std::cout << beta;
 
    return 0;
}

만약 "어떤 헤더.h"beta라는 매크로를 #define하게 되면, 이 간단한 프로그램은 부서질 것이다. 왜냐하면 전처리는 int(정수형) 변수 beta의 이름을 매크로의 값이 무엇이든 간에 대체하기 때문입니다.

셋째, 매크로는 일반적인 범위 지정 규칙(normal scoping rules)을 따르지 않는데, 이는 드물게 프로그램의 한 부분에 정의된 매크로가 상호 작용하지 않아야 하는 프로그램의 다른 부분에 작성된 코드와 충돌할 수 있다는 것을 의미합니다.

 

결론적으로, 기호 상수(symbolic constants)를 만들기 위해 #define 이용을 피해야 합니다. 

 

기호 상수(symbolic constants)의 좋은 사용 예

기호 상징(symbolic constants)을 만드는 더 좋은 방법은 constexpr 변수를 사용하는 것입니다.

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

이것들은 단지 정상적인 변수들이기 때문에 디버거에서 볼 수 있습니다.

또한 일반적인 범위를 가지며, 다른 이상한 행동들을 피할 수 있습니다.

그리고 magic number에 대한 이름과 컨텍스트를 제공하려면 constexpr 변수를 사용하는게 적극 권장됩니다.

 

 

 

 

 

 

 

 

* 해당 글은 홍정모님의 따라 배우는 C++과 learncpp에서 공부한 토대로 작성한 글입니다. 

Comments