C, 자료형, 정수형, 실수형, 문자열


C의 자료형도 다른 언어에서 사용하는 자료형과 비슷합니다.

물론 문자열 부분에선 조금 다른 부분도 있지만…


다른 언어를 배워본 적이 있으신 분들은

매우 쉬우실 것입니다.

저는 처음 배운다는 초심의 자세로 설명해보겠습니다.




간단한 예제 코드


복잡한 설명대신 간단한 예제를 살펴보겠습니다.

#include <stdio.h>

int main(void) {
    int temp = 0;
    printf("temp = %d\n", temp);
    
    temp = 1;
    printf("temp = %d\n", temp);
    
    temp = temp + 1;
    printf("temp = %d\n", temp);
    
    temp++;
    printf("temp = %d\n", temp);
    
    return 0;
}

/*
 temp = 0
 temp = 1
 temp = 2
 temp = 3
*/

마지막에 주석으로 실행 결과를 적어놨습니다.

주석이란 소스코드안에 개발자가 남긴 메모입니다.

주석은 컴파일 되지 않으므로, 코드에 전혀 영향을 미치지 않습니다.


일단 이 코드를 보겠습니다.

int temp = 0;

temp라는 이름의 변수를 선언하고,

그곳에 0을 대입했습니다.

int는 정수 자료형 중 하나입니다.

즉, 저 구문의 뜻을 정리하면 다음과 같습니다.

int 자료형인 temp라는 변수를 선언하고 초기값으로 0을 대입한다.

temp는 int 자료형이니 정수가 아닌 다른 값을 대입하면 에러가 뜹니다.


그 다음 줄에서 printf() 함수를 이용해 temp값을 출력하고 있습니다.

여기서 첫번째 매개변수는 “temp = %d\n”이고,

두번쨰 매개변수는 위에서 선언한 temp입니다.

첫번째 매개변수에서 %d는 decimal을 뜻하고,

다음 정수 매개변수 값이 대입됩니다. 여기서는 temp값이 되겠죠.

실행결과를 보면 위에서 대입했던 대로 0값이 나옵니다.

참고로 \n은 줄넘김을 의미하는 개행문자입니다.

저걸 안쓰면 한줄로 계속 출력됩니다.


다음 구문은 다음과 같습니다.

temp = 1;

이 구문은 위에서 선언했던 temp값이 원래 0이었는데,

여기다 1이라는 값을 대입한다는 의미입니다.

이제 temp값은 1로 바뀌었고,

다음 줄에 있는 printf()구문을 통해 1이 나옵니다.


다음 구문은 다음과 같습니다.

temp = temp + 1;

아까 temp = 0 이 의미하는 바가

temp에 0이라는 값을 대입한다고 했습니다.

이것도 마찬가지로,

temp에 (temp+1)이라는 값을 대입한다는 뜻입니다.

근데 원래 temp값이 1이었으니, (temp+1)은 2가 되고,

temp에 2를 대입하니, 이제 temp값은 2가 됩니다.


다음 구문은 다음과 같습니다.

temp++;

이 구문은 바로 위 구문인

temp = temp + 1;

과 의미가 똑같습니다.

그러니, temp값을 출력하면 3이 나옵니다.


이처럼 변수를 하나 만들어놓으면,

그 변수를 가지고 계속 무언갈 할 수 있습니다.

근데 변수를 만들 때,

이 변수는 어떤 종류의 값을 저장하겠다라는 걸

지정해줘야하고, 그때 지정하는데 사용하는게 자료형입니다.

위에서는 int라는 정수 자료형을 사용했습니다.




두번째 예제코드


정수 자료형은 int만 있는게 아닙니다.

short라는 자료형도 있습니다.

이번에는 short와 int를 함께 사용해보겠습니다.

#include <stdio.h>

int main(void)
{
    short shortNum = 32766;
    int intNum = 32766;
    
    shortNum++;
    intNum++;
    
    printf("shortNum = %d\n", shortNum);
    printf("intNum = %d\n\n", intNum);
    
    shortNum++;
    intNum++;
    
    printf("shortNum = %d\n", shortNum);
    printf("intNum = %d\n", intNum);
    
    return 0;
}

/*
 shortNum = 32767
 intNum = 32767
 
 shortNum = -32768
 intNum = 32768
*/

코드를 살펴보겠습니다.

처음에 shortNum과 intNum이라는 두개의 변수를 선언했습니다.

그리고 두 변수에는 같은 값 32766이라는 값을 대입했습니다.

그리고 처음에 1씩 값을 증가시켰고, 잘 증가된걸 확인할 수 있습니다.

그런데 두번째로 1씩 값을 증가시켰더니,

intNum은 잘 증가됬지만, shortNum은 갑자기 -32768이 됐습니다.

이건 각 자료형마다 속성이 다르기 때문입니다.

이유를 알아보겠습니다.




각종 자료형


자료형 크기(byte) 값 범위
정수형 char 1 $$-2^7 \sim 2^7-1$$
short 2 $$-2^{15} \sim 2^{15}-1$$
int 4 $$-2^{31} \sim 2^{31}-1$$
long 4 $$-2^{31} \sim 2^{31}-1$$
long long 8 $$-2^{63} \sim 2^{63}-1$$
실수형 float 4 $$1.17 \cdot 10^{-38} \sim 3.4 \cdot 10^{38}$$
double 8 $$2.22 \cdot 10^{-308} \sim 1.79 \cdot 10^{308}$$
long double 8 이상 double 이상

참고로 C의 자료형은 시스템 환경(CPU)에 따라 다릅니다.

위의 표는 32비트 기준입니다.


정수 자료형


위 코드에서 이상했던 short를 예로 봐보겠습니다.

short의 범위를 보면 \(-2^{15} \sim 2^{15}-1\)인데,

이를 계산해서 표현하면 \(-32768 \sim 32767\)입니다.

제곱수인 15가 어떻게 나왔나 먼저 살펴보겠습니다.


먼저 컴퓨터의 모든 정보는 bit(0과 1)로 저장됩니다.

그리고 1byte는 8bit입니다.

short는 2byte이므로 16bit입니다.

즉, 16개의 bit로 정보를 저장합니다.

예를 들어 2를 저장한다고 하면 이진법으로

0000 0000 0000 0010

이 됩니다. 그런데 여기서 맨앞에 있는 bit는

음수인지 양수인지 나타내는 정보만 담고 있습니다.

양수면 0, 음수면 1 입니다. 그렇다고,

1000 0000 0000 0010

이 -2는 아닙니다.

예를 들어 16비트로 표현할 수 있는 양수 중에 가장 큰 수는

0111 1111 1111 1111

입니다. 마찬가지로, 음수 중에 가장 큰 수는

1111 1111 1111 1111

입니다. 음수 중에 가장 큰 수는 -1이죠.

즉, 1111 1111 1111 1111 은 십진법으로 -1입니다.

-2는 음수 중에 두번째로 큰 수이고, 이를 이진법으로 나타내면

1111 1111 1111 1110

이 되겠죠.

쉽게 음수를 구하는 법은 다음과 같습니다.

예를 들어 -2를 구하고 싶다 하면, 먼저 2를 이진법으로 표현합니다.

0000 0000 0000 0010

이 수를 0은 1로, 1은 0으로 각 값을 반전시킵니다.

1111 1111 1111 1101

이 수에 1을 더하면 됩니다. 그럼

1111 1111 1111 1110

이 되죠.

여하튼 맨앞의 비트는 부호 정보만을 표현하기 때문에

실제로 크기를 결정하는 비트는 15개 뿐이므로, 15가 지수가 됩니다.


여튼 다시 short의 범위를 계산해보면 \(-32768 \sim 32767\)입니다.

위에서는 shortNum 값이 32767일때 1을 더해주니 -32768이 됐습니다.

32767은

0111 1111 1111 1111

이고, 여기에 1을 더하면

1000 0000 0000 0000

이 되므로, 음수 중에 가장 작은 값인 -32768이 됩니다.

하지만, int는 값의 범위가 훨씬 넓으므로 제대로 계산이 된 것입니다.


참고로 보통 아무말없이 정수 자료형을 쓴다하면

int라고 보면되고, int가 가장 널리 쓰입니다.


char


char에 대해서는 조심만 자세히 살펴보겠습니다.

char는 정수 자료형이지만 주로 문자를 표현하는데 사용됩니다.

컴퓨터는 문자도 숫자로 나타내야하고,

이는 ASCII(아스키) 코드를 따릅니다.

간단한 예제 코드를 살펴보겠습니다.

#include <stdio.h>

int main(void)
{
    char ch = 'A';
    printf("ch = %c\n", ch);
    printf("(int)ch = %d\n\n", ch);
    
    ch++;
    printf("ch = %c\n", ch);
    printf("(int)ch = %d\n\n", ch);
    
    return 0;
}

/*
 ch = A
 (int)ch = 65
 
 ch = B
 (int)ch = 66
*/

변수 ch에 ‘A’라는 값을 넣었고,

아래 구문에서 이에 해당하는 아스키코드 수는 65라는 걸 알았습니다.

ch값에 1을 더하고 다시 출력하니 ‘B’가 나오는 걸 알 수 있습니다.


unsigned


일단 여러 자료형을 선언해본 코드를 봐보겠습니다.

#include <stdio.h>

int main(void)
{
    char ch = 'A';
    wchar_t wch = L'A';
    short sh = 7;
    int in = 77;
    
    // long과 long int 는 같습니다.
    long lo = 777L;
    long int loi = 776L;
    
    // long long과 long long int 는 같습니다.
    long long lolo = 77777LL;
    long long int loloi = 777776LL;
    
    unsigned char uch = 0;
    unsigned short ush = 65535U;
    unsigned int ui = 7U;
    unsigned long ulo = 77UL;
    unsigned long long ull = 1ULL;
    
    printf("End\n");
    
    return 0;
}

wchar_t는 윈도우에서 사용가능한 16비트 자료형입니다.

하단의 코드를 보면 unsigned라는 예약어가 붙은 자료형도 보입니다.

unsigned는 말그대로 부호가 없다는 뜻입니다.

무조건 0 이상인 값만 취급하고,

그래서 맨앞의 비트도 값을 설정하는데 쓰이게 됩니다.

즉, 16개의 비트가 모두 값을 설정하는데 쓰이게 되고,

unsigned int를 예로 들면 값의 범위가

\(0 \sim 2^{32}-1\) 입니다.


변수를 선언할 때 주의할 점은,

long을 하나 선언할 때마다 L을 붙여야하고,

unsigned인 변수를 선언할 때는 U를 붙여야합니다.


실수 자료형


위의 표에서 널리 쓰이는 실수 자료형인 float, double을 보겠습니다.

#include <stdio.h>

int main(void)
{
    //float
    float flo1 = 234.56f;
    flo1 = flo1 + 0.01f;
    float flo2 = 123.456788f;
    flo2 = flo2 + 0.000001f;
    
    printf("flo1 = %f\n", flo1);
    printf("flo2 = %f\n\n", flo2);
    
    //double
    double do1 = 234.567;
    do1 = do1 + 0.00001;
    double do2 = 123.456788;
    do2 = do2 + 0.000001;
    
    printf("do1 = %f\n", do1);
    printf("do2 = %f\n\n", do2);
    
    return 0;
}

/*
 flo1 = 234.569992
 flo2 = 123.456787
 
 do1 = 234.567010
 do2 = 123.456789
*/

float를 선언할 때는 뒤에 f를 붙이면 됩니다.

그런데 계산 결과가 float는 정확하지는 않습니다.

이는 컴퓨터에서 소수를 처리하는 방법에 있습니다.


예를 들어 0 ~ 2 사이의 정수는 0, 1, 2 이렇게 3개로

컴퓨터가 이진법으로 정확히 표현할 수 있습니다.

하지만, 0 ~ 2 사이의 소수는 셀 수 없이 많습니다.

이 많은 소수를 이진법으로 정확히 저장하기란 불가능 하기 때문에,

컴퓨터는 소수를 다룰 때 일정 수준의 오류를 전제합니다.

예를 들어 계산값이 1.16 ~ 1.25라 하면, 그냥 1.20으로 저장한다는 방식입니다.

조금 더 자세히 알아보면, 부동 소수점 방식을 사용하고, 각종 표준을 따르는건데

복잡하므로, 지금은 넘어가겠습니다.


다만, float는 4byte, double은 8byte, 즉

double이 더 메모리를 많이 차지하는 만큼 정확도가 높습니다.


참고로 다음 코드에서는 더블 자료형의 범위를 알 수 있습니다.

#include <stdio.h>
#include <float.h>

int main(void)
{
    printf("double range: %E ~ %E\n", DBL_MIN, DBL_MAX);
    
    return 0;
}

// double range: 2.225074E-308 ~ 1.797693E+308




문자와 문자열(문자배열)


JAVA에는 문자열 자료형이 String이라고 따로 있지만,

C에서는 char 자료형을 묶어서 관리합니다.

이렇게 묶음으로 관리하는 걸 배열이라고 합니다.

예제 코드를 살펴보겠습니다.

#include <stdio.h>

int main(void)
{
    char ch1 = 'T', ch2 = 'E', ch3 = 'E', ch4 = 'M', ch5 = 'O';
    char tm1[5] = { 'T', 'E', 'E', 'M', 'O' };
    char tm2[6] = { 'T', 'E', 'E', 'M', 'O' };
    char tm3[6] = { "TEEMO" };
    
    printf("ch1 = %c, ch2 = %c, ch3 = %c, ch4 = %c, ch5 = %c\n\n", ch1, ch2, ch3, ch4, ch5);
    printf("tm1[0] = %c\n", tm1[0]);
    printf("tm1[1] = %c\n", tm1[1]);
    printf("tm1[2] = %c\n", tm1[2]);
    printf("tm1[3] = %c\n", tm1[3]);
    printf("tm1[4] = %c\n", tm1[4]);
    printf("tm1[5] = %c\n", tm1[5]);
    printf("tm1: %s\n\n", tm1);
    
    printf("tm2[0] = %c\n", tm2[0]);
    printf("tm2[1] = %c\n", tm2[1]);
    printf("tm2[2] = %c\n", tm2[2]);
    printf("tm2[3] = %c\n", tm2[3]);
    printf("tm2[4] = %c\n", tm2[4]);
    printf("tm2[5] = %c\n", tm2[5]);
    printf("tm2[6] = %c\n", tm2[6]);
    printf("tm2: %s\n\n", tm2);
    
    printf("tm3[0] = %c\n", tm3[0]);
    printf("tm3[1] = %c\n", tm3[1]);
    printf("tm3[2] = %c\n", tm3[2]);
    printf("tm3[3] = %c\n", tm3[3]);
    printf("tm3[4] = %c\n", tm3[4]);
    printf("tm3[5] = %c\n", tm3[5]);
    printf("tm3[6] = %c\n", tm3[6]);
    printf("tm3: %s\n\n", tm3);
    
    return 0;
}

/*
 ch1 = T, ch2 = E, ch3 = E, ch4 = M, ch5 = O
 
 tm1[0] = T
 tm1[1] = E
 tm1[2] = E
 tm1[3] = M
 tm1[4] = O
 tm1[5] = O
 tm1: TEEMOOMEET
 
 tm2[0] = T
 tm2[1] = E
 tm2[2] = E
 tm2[3] = M
 tm2[4] = O
 tm2[5] =
tm2[6] = T
tm2: TEEMO

tm3[0] = T
tm3[1] = E
tm3[2] = E
tm3[3] = M
tm3[4] = O
tm3[5] =
tm3[6] = T
tm3: TEEMO
*/

위 코드에 주석은 MacOS에서 Xcode로 돌렸을 떄의 결과입니다.

윈도우에서 돌렸을 떄의 결과는 아래와 같습니다.

vs_code_run

코드를 살펴보겠습니다.

tm1, tm2, tm3가 배열을 사용하는 법을 보여주고 있습니다.

char tm1[5] = { 'T', 'E', 'E', 'M', 'O' };

이 구문의 뜻은 크기가 5인 char 자료형 배열 변수 tm1을 만들겠다입니다.

크기가 5인만큼 문자도 5개가 들어갑니다.

이 배열값을 참조하려면 그 아래 printf()에서 사용한 것처럼 접근합니다.

크기가 5인 배열이면 0번째 자리부터 시작해 4번째 자리까지 존재합니다.

즉, tm1[5]같이 0 ~ 4 범위의 수가 아닌 다른 수를 참조하려하면

이상한 값이 나옵니다.(개발 환경에 따라 차이는 있지만)

이건, tm2, tm3 모두 해당합니다.

게다가 이 코드를 살펴봅시다.

printf("tm1: %s\n\n", tm1);

이렇게 tm1 변수 자체를 문자열로 출력하려는데,

맥이든 윈도우든, 기대되는 TEEMO란 값은 안나오고, 이상하게 나옵니다.

teemo_img

그런데 tm2, tm3를 보면 TEEMO는 5개의 문자로 이루어져있지만,

크기가 6인 배열들입니다.

그러면 0번째 자리부터 5번째 자리까지 총 6개의 자리가 있겠죠.

그리고 printf()문을 보면 TEEMO가 제대로 출력된 것을 볼 수 있습니다.

즉, tm2, tm3가 알맞게 문자열을 다루는 것입니다.

tm2[5], tm3[5] 모두 아무것도 출력 안된 걸 볼 수 있습니다.

아무것도 없는 건 아니고 사실 NULL문자(‘\0’)이 할당되어 있습니다.

즉, 문자열의 끝은 NULL로 끝나야 합니다.

그래야 문자배열 전체를 출력하려 할때 c가 문자열의 끝을 인식하고,

온전한 문자열을 출력하게 됩니다.




몇가지 주의할 사항


변수명을 정할 때 주의할 점

  1. (필수)영문 대/소문자, _, 숫자로 만들어야 한다.
  2. (필수)첫 글자는 숫자가 될 수 없다.
  3. (필수)공백을 포함할 수 없다.
  4. (필수)C언어 예약어는 사용할 수 없다.
  5. (권고)너무 긴 이름은 좋지 않다.
  6. (권고)의미있게 이름을 부여한다.
  7. (권고)의미없는 이름을 남용하지 않는다.


프로그래밍 시, 변수 관련 권장 사항

  1. 변수의 개수는 적을 수록 좋다.
    • 변수가 많아지면, 생각해야 할 것들이 늘어나고 프로그램을 복잡하게 만든다.
  2. 변수는 메모리로 구현되며, 메모리는 주소를 가진다.
    • 메모리 사용량이 적은 프로그램이 효율적인 프로그램이다.