변수와 자료형
네이버 사전에 따르면 변수는 '계속 변하는 값이면서, 그 값을 저장하는 공간'이라고 정의되어 있다. 처음 접하는 사람 입장에서는 어려울 수 있는데, 쉽게 말해 변수는 어떠한 값을 담는 바구니라고 생각하자. 근데 그 바구니 안에 담긴 값들은 계속해서 바뀔 수 있는 값인 것이다.
여태까지 정수 자료형, 실수 자료형, 문자 자료형을 포스팅했는데 여기서 '정수형', '실수형', '문자형'과 같은 자료형은 바구니의 크기를 결정짓고 바구니에 담길 값들의 형식을 지정해준다고 생각하면 쉽다.
어제 포스팅했던 문자 자료형을 예로 들어보자. 음수까지 담을 수 있는 char 자료형보다는 이해를 쉽게 돕기 위해 unsigned char 자료형으로 설명해보겠다. unsigned char 자료형은 총 1byte(8bit)만큼의 크기를 가진 바구니다. 정밀하게 bit로 표현하여 말하자면, 00000000(10진수로 0)부터 11111111(10진수로 255)이라는 범위 안에 있는 값들을 담을 수 있는 바구니다.
위와 같이 0 이상 255 이하의 정수라면 unsigned char 바구니에 담을 수 있다. 물론 정수이기 때문에 10진수 말고도 16진수나 8진수로도 담을 수 있다. 반면, 0 이상 255 이하의 정수가 아닌 10진법/16진법/8진법 정수들은 unsigned char 바구니에 담을 수 없고, 정수만 담을 수 있기 때문에 0 이상 255 이하의 범위에 속하더라도 실수는 담을 수 없다.
만약 바구니가 허용하는 범위를 벗어난 정수를 담게 된다면 언더플로우(underflow)나 오버플로우(overflow) 현상이 발생하며, 실수를 담으면 소수점 이하의 값들이 없어지고 내림하여 정수로 적용된다. 즉, 원래 의도한 값들이 바구니에 담기지 않게 된다. (언더플로우나 오버플로우에 대한 개념은 [정수 관련 링크]와 [실수 관련 링크]에서 확인할 수 있다.)
배열은 나중에 포스팅하겠지만, 배열이 아닌 이상은 한 바구니에 하나의 값만 담을 수 있다. 하지만 언제든지 바구니에 들어있는 값을 지우고 새로운 값을 넣을 수 있다. 이렇게 바뀔 수 있는 값이기 때문에 변수(Variable, 變數)라고 부른다.
이해가 됐다면 프로그래밍스러운 용어로 접근해보기 위해 바구니를 '변수(Variable)'로, 담는다는 행위를 '할당(assignment)'으로 치환하여 생각하면 된다. 위와 같은 개념은 어느 자료형에든 적용된다.
상수
상수(Constant)는 변수와 반대되는 개념으로, 변하지 않는 값을 의미한다. 즉, 한번 상수로서 정의한 것은 값을 변경시킬 수 없다.
우리 삶 속에서 화폐는 고정된 가치를 가지고 있다. 1,000원이라고 지정된 종이는 어디를 가든 1,000원이라고 인식하며 우리가 아무리 뒤에 0을 하나 더 써서 10,000원이라고 적는다고 한들 그 종이의 가치는 변하지 않는다. 말하면서도 이렇게 설명해도 되는가 싶지만 그 이상으로 쉽게 설명하기는 어려울 것 같다.
이번에는 직접 컴파일하여 확인해보도록 하자.
#include <stdio.h>
int main()
{
const int a = 4;
a += 1;
printf("%d", a);
return 0;
}
/****************************** 결과 ******************************
In function 'main': [Error] assignment of read-only variable 'a'
라는 오류가 뜬다.
******************************************************************/
소스코드에서 상수를 정의하는 방법은 차차 알아보도록 하고, 이것부터 먼저 봐보자.
정수 자료형 int로 a라는 상수를 정의했고 그 상수에는 4라는 값을 할당해줬다. 이제 a는 죽었다 깨어나도 4라는 값밖에 가지지 못한다. 그런데 a에 1을 더하는 행위를 하니 컴파일하는 과정에서 "a는 읽기만 가능하니 할당은 하지 말아 줄래?"라는 에러를 반환하며 컴파일을 하지 않는다. 이처럼 상수를 정의하면 해당 상수는 이후 소스코드에서 변경은 불가능하다.
상수는 왜 필요할까?
만약 코딩하는 과정에서 절대로 변경되면 안 되는 값들이 있다. 가장 진부하고 널리 알려진 예시로는 원주율(π)이 있다. 원주율을 줄줄이 다 쓰긴 뭐하니 3.14로 줄여쓴다고 치고, 그 원주율을 아주 많이 사용해야 되는 소스코드가 있다고 가정해보자.
일단 3.14라는 값은 절대 변하지 않을 것이다. 그래서 소스코드 안에 3.14 라는 값을 바로 써도 상수라고 할 수 있으며, 그것을 보통은리터럴 상수(literal constant)라고 부른다. 근데 리터럴 상수는 문제가 있다. 원주율이 필요한 라인마다 3.14를 일일이 쓰기가 상당히 귀찮다. 그래서 상수를 따로 정의해줬으면 하는데 이렇게 따로 정의한 상수들을 심볼릭 상수(symbolic constant)라고 한다. 비록 "상수명을 PI로 정의해서 써도 리터럴 상수인 3.14와 단 두 글자밖에 차이가 안 나는데 게을러서 어떻게 사냐"라고 반문할 수도 있지만, 그래도 두 글자를 줄일 수 있다는 장점이 있다... :(
그리고 만약 3.14가 아니라 3.1415로 값을 변경해야 한다면? 리터럴 상수로 3.14를 일일이 쓴 경우, 리터럴 상수를 사용한 부분을 하나하나 전부 수정해줘야 한다. 소스코드가 방대할수록 개발자의 삽질 시간은 늘어날 것이다. 하지만 상수명을 PI로 정의하고 소스코드 내에 원주율이 필요한 부분에 PI를 이용했다면, const double PI = 3.14를 const double PI = 3.1415로만 수정해주면 된다.
사실 그것보다도 더 중요한 점은 따로 있는데, 다른 사람들이 나의 소스코드를 봤을 때, 혹은 나 자신마저도 나중에 내 소스코드를 봤을 때 3.14가 무엇을 의미하는지 모를 가능성이 상당히 높다는 것이다. 그래서 해당 값이 어떤 것을 의미하는지 상수로 정의해주는 편이 정신 건강에 이롭다. 갑자기 하고 싶은 말이 생각났는데, 상수든 변수든 항상 그 값이 어떤 값을 의미하는 것인지 명.확.히. 정의해줘야 나중에 동료들로부터 사랑받고, 나도 나 자신을 미워하지 않게 된다. 꼭 명심하자!
심볼릭 상수 정의 방법
(1) #define
main 함수 바깥에서 #define을 사용하여 상수를 정의하는 방법이 있다. 백문이 불여일견. 소스코드로 확인하자.
# include <stdio.h>
# define PI 3.14
int main()
{
printf("%.2f", PI);
return 0;
}
/* 결과 *
3.14
********/
main 함수 바깥에 #define를 사용한 이유는 컴파일 시 전처리기에게 인식시켜주기 위해서다. 소스코드에서 사용된 상수들은 컴파일할 때 전처리기에 의해 #define을 통해 정의된 값으로 전부 변환된다. 전처리기에서 처리해버리기 때문에 const와 달리 해당 상수가 메모리에 올라가지 않는다는 장점은 있다. 하지만 자료형을 명확히 할 수 없어 불편한 부분이 많다.
(2) const
# include <stdio.h>
int main()
{
const double PI = 3.14;
printf("%.2f", PI);
return 0;
}
/* 결과 *
3.14
********/
웬만하면 const 키워드를 이용하여 상수를 선언하라고 하고 싶다. 그 이유는 #define으로 정의하는 것과 달리 자료형을 정해줄 수 있다는 장점이 있기 때문이다. 자료형을 정할 수 있다면 해당 상수의 허용 범위를 명확하게 지정할 수 있는데, 개발하는 입장에서는 디버깅할 때 에러를 확인하기 더 쉽기 때문에 장점이라고 생각한다.
다만 문제점이 조금 있다. C++의 enum 키워드를 이용하면 심볼릭 상수가 메모리에 올라가지 않아도 되지만, C언어에서의 const 키워드를 이용하여 심볼릭 상수를 정의하면 일반적인 변수들처럼 메모리에 올라간다는 단점이 있다는 것이다.
'Dev > C' 카테고리의 다른 글
[C언어] 코드로 알아보는 산술 연산자 (0) | 2020.06.26 |
---|---|
[C언어] scanf 정리 (0) | 2020.06.25 |
[C언어] 문자 자료형과 아스키 코드(ASCII) (1) | 2020.06.19 |
[C언어] 실수형 변수 출력 시 소수점 자릿수 조절 (반올림/올림/내림) (0) | 2020.06.17 |
[C언어] 실수 자료형 정리 (64bit Windows 기준) (0) | 2020.06.16 |