변수는 데이터의 저장을 위해서 할당된 메모리 공간에 붙여진 이름이다.
하지만 메모리 공간 할당에 앞서 정수를 저장할건지, 실수를 저장할건지, 몇 바이트를 사용할 건지에 대한 내용이 결정되어야한다.
자료형이 미리 정의되어 있기에 자료형의 이름을 이용해서 쉽게 메모리 공간을 할당할 수 있다.
정수형에는
실수형에는
가령, 자료형 char는 1바이트이므로 나타낼 수 있는 데이터의 종류가 2^8 = 256개이다. -128부터 +127까지 표현하도록 정의된 자료형인 것이다.
하지만 C의 표준을 정하는 ANSI에서는 자료형 별 크기를 정확히 제한하고 있지 않으며, 자료형 별 크기는 컴파일러마다 차이를 보인다
메모리 공간의 효율적인 사용을 위해서 다양한 크기의 자료형이 필요하며, 적절한 자료형을 선택해 사용할 줄 알아야한다.
sizeof는 메모리 공간에서 소모하는 메모리의 크기를 바이트 단위로 계산해서 반환해주는 연산자이다. 또한 피연산자로 변수와 상수, 자료형의 이름도 올 수 있다.
<sizeof 사용방법>
int main(void)
{
int num = 10;
int sz1 = sizeof(num); // 변수 num의 크기를 계산하여 sz1을 초기화
int sz2 = sizeof(int); // 자료형 int의 크기를 계산하여 sz2를 초기화
}
sizeof 연산자의 피연산자가 자료형일 때는 소괄호가 필수
정수를 저장할 변수의 자료형을 선택할 때, 가장 먼저 생각할 문제는 '저장하고자 하는 값의 범위'이다.
가령 short형 변수가 저장할 수 있는 값의 범위는 -32,768이상 +32,767이하이다. 이 범위를 넘어서는 정수를 저장하려면 int형 변수를 선언해야 한다.
그렇다면 저장하고자 하는 값이 -32,768이상 +32,767이하의 범위 내에 든다면, short형 변수를 선언하는 것이 더 효율적인가?
상황에 따라 다르다.
일반적으로 CPU가 처리하기에 가장 적합한 크기의 정수 자료형을 int로 정의한다. 따라서 int형 연산의 속도가 다른 자료형의 연산속도에 비해서 동일하거나 더 빠르다.
하지만 데이터의 양이 많아서 연산속도보다 데이터의 크기를 줄이는 것이 더 중요한 데이터들에게는 char형이나 short형이 적절하다.
실수 자료형의 선택에 있어서 가장 중요한 요소는 정밀도이다.
여기서의 정밀도란 오차가 발생하지 않는 소수점 이하의 자릿수를 뜻한다.
컴퓨터에서의 실수 표현은 오차가 존재할 수 밖에 없는데, 데이터 표현에 사용되는 바이트의 수가 커지면 오차는 줄어든다.
보편적으로 사용되는 실수 자료형은 double형이다.
float보다 정밀도가 높으면서도, long double보다는 덜 부담스러운게 이유이다.
정수 자료형의 이름에 한해서 unsigned 선언을 추가하면, 0 이상의 값만 표현하는 자료형이 되어서 표현할 수 있는 값의 범위가 양의 정수 방향으로 두배 넓어진다.
unsigned를 붙이면 MSB도 값의 크기를 나타내는 비트로 사용하기 때문이다.
숫자를 이용해서 문자를 표현하도록 하려면 숫자를 문자에 매핑시키는 것이 유일한 방법이다. C언어는 '미국 표준 협회'에 의해서 제정된 아스키 코드를 사용해서 문자를 표현한다.
컴퓨터는 문자를 숫자로 표현하며, 숫자로 표현하기 위해 '아스키 코드'가 정의되었다.
int main(void)
{
char ch1 = 'A';
char ch2 = 'C';
}
int main(void)
{
char ch1 = 'A', ch2 = 65;
int ch3 = 'Z', ch4 = 90;
printf("%c %d \n", ch1, ch1);
printf("%c %d \n", ch2, ch2);
printf("%c %d \n", ch3, ch3);
printf("%c %d \n", ch4, ch4);
return 0;
}
실행결과:
A 65
A 65
Z 90
z 90
%c는 문자의 형태로 데이터를 출력하라는 의미를 가지는데, 실행결과를 보면 해당 정수를 아스키 코드 값으로 해석해서 아스키 코드 문자를 출력한다. 따라서 정수는 출력의 방법에 따라서 문자의 형태 또는 숫자의 형태로 출력이 가능하다.
문자를 저장하는 데에는 char형이 더 적합하다.
int형 은 정수형 연산을 할 때 빠른 것이다.
변수의 상대적 개념인 상수는 크게 이름이 있는 상수와 이름이 없는 상수로 나뉘며, 상수 역시 자료형을 근거로 표현된다.
상수란, 변경이 불가능한 데이터를 뜻한다.
int main(void)
{
int num = 30 + 40;
...
}
위 코드에서 변경이 불가능한 데이터는 30과 40이다.
메모리에는 정수 30과 40이 상수의 형태로 저장되며, 두 상수를 기반으로 덧셈이 진행되고, 덧셈의 결과로 얻어진 정수 70이 변수 num에 저장된다.
정수 30과 40은 변수 num과 달리 할당된 메모리 공간에 이름이 존재하지 않는데, 이름이 없는 상수를 '리터럴 상수'라고 한다.
메모리상에 저장되는 모든 데이터는 자료형이 결정되어야 하며, 상수 역시 자료형을 필요로 한다.
int형으로 표현 가능한 정수형 상수는 int형 메모리 공간에 저장하고, double형으로 표현 가능한 실수형 상수는 double형으로 저장하기로 약속되어있다.
다만, 대입 연산자 왼편의 변수 자료형에 따라 결정되는 것이 아니다. 변수 자료형은 오른편에 선언된 상수의 자료형에 어울리게 선언했을 뿐이다.
문자형 상수는 int형으로 저장된다.
컴파일러에 의해서 정수로 표현되기 때문이다.
int형, double형 이외의 자료형을 기반으로 상수를 표현하려면 해당 자료형을 의미하는 접미사를 붙여주면 된다.
int main(void)
{
float num1 = 5.789; //경고 메시지 발생
float num2 = 3.24 + 5.12; //경고 메시지 발생
return 0;
}
위의 코드는 컴파일이 가능하지만, 'double형 데이터를 float형 변수에 저장하였으니 데이터가 잘려나갈 수 있다'는 경고 메세지가 발생한다.
float형 상수를 float형 변수에 저장하려면 f를 상수의 뒤에 붙여주면 된다.
float num1 = 5.789f;
float num 3.24f + 5.12f; //대문자 F도 가능
이외에도 U(unsigned), L(long), UL(unsigned long), LL(long long)등의 접미사가 존재한다. 대소문자는 구분하지 않아도 된다.
심볼릭 상수는 변수와 마찬가지로 이름을 가지는 상수이다.
심볼릭 상수를 표현하는 방법에는 'const 키워드'와 '매크로 이용'이 있다.
매크로는 이후에
const 키워드를 이용해서 상수를 만드는 방법은 변수 선언 시 const 선언만 추가하면 된다. 단, 선언과 동시에 초기화를 해야한다. 그렇지 않으면 쓰레기 값으로 초기화되고 값의 변경이 불가해진다.
int main(void)
{
const int MAX = 100; //MAX의 값은 변경 불가
const double PI = 3.1415; //PI의 값은 변경 불가
}
상수의 이름은 모두 대문자로 표시하는 것이 변수와 상수의 구분을 용이하게 한다.
자료형의 변환은 데이터의 표현방식을 바꾸는 것이다. 자료형의 변환에는 크게 두 종류로 나뉜다.
대입 연산자의 왼편과 오른편에 존재하는 두 피연산자의 자료형이 일치하지 않으면, 왼편에 있는 피연산자를 대상으로 형 변환이 자동으로 일어난다.
double num1 = 245; //int형 정수 245를 double형으로 자동 변환
int num2 = 3.145; //double형 실수 3.145를 int형으로 자동 변환
이때 double형 실수를 int형으로 변환하는 과정에서 소수부의 손실이 일어난다. 3.145에서 3으로 저장되기 때문이다.
바이트의 크기가 큰 정수를 바이트 크기가 작은 정수로 형 변환을 하는 경우, 상위 바이트의 손실이 발생한다. 이는 변환하고자 하는 정수의 바이트 크기에 맞춰 사위 바이트를 소멸시키는 것이며, 부호가 바뀔 수도 있다.
보다 작은 자료형으로의 형 변환은, 그 과정에서 데이터의 손실이 발생할 수 있다.
int형의 연산 속도는 다른 자료형보다 빠르거다 동일하다. 따라서 int보다 작은 크기의 정수형 데이터는 int형 데이터로 자동 형 변환 되어서 진행된다.
int main(void)
{
short num1 = 15, num2 = 25;
short num3 = num1 + num2; //num1과 num2가 int로 형 변환
}
이러한 형 변환을 정수의 승격(Integral Promotion)이라 한다.
두 개의 피연산자가 필요한 기본적인 산술연산에서 자료형이 일치하지 않을 시에는 자료형의 일치를 목적으로 자동 형 변환이 일어난다.
이때의 자동 형 변환은 데이터의 손실을 최소화하는 방향으로 진행된다.
int > long > long long > float > double > long double
왜 4바이트의 크기의 float가 8바이트 크기의 long long보다 형 변환 우선순위가 높은가?
int main(void)
{
int num1 = 3, num2 = 4;
double divResult;
divResult = num1 / num2;
pirntf("나눗셈의 결과: %f \n", divResult);
return 0;
}
[실행결과]
나눗셈 결과: 0.000000
연산결과의 자료형은 피연산자의 자료형과 일치하기 때문에, 결과는 정수형 나눗셈의 결과의 몫인 0이되고, 이를 double형으로 변환하여 0.000000이 출력되는 것이다.
하지만 divResult = (double)num1 / num2;로 수정하여 실행한다면 0.75가 출력된다. 이처럼 소괄호는 형 변환의 의미로서도 사용되며 형 변환 연산자(type casting operator)라고 부른다.
위 식에서는 num1이 double형으로 강제 형 변환되는 것이며, 산술연산의 형 변환 규칙에 의해 num2도 double형으로 자동 형 변환된다. 따라서 실수형 나눗셈의 결과인 0.75가 divResult에 저장되는 것이다.