본문 바로가기

Programming languages/C

2. C언어(변수와 자료형)

728x90
반응형

앞의 글에서 변수와 자료형이 뭔지 살펴봤다.

이처럼 자료형의 크기는 다양하고 용도에 맞게 써야 한다. 만약에 필요한 데이터의 크기보다 너무 큰 사이즈의 자료형을 선언하면 메모리의 낭비가 심할 것이고 데이터의 크기보다 작은 자료형을 선언하면 값이 제대로 출력되지 않거나 오버플로우 같은 오류가 발생할 것이다.

#include <stdio.h>

int main(){
    printf("%d %d %d %d %d %d", sizeof(int),sizeof(char),sizeof(short),sizeof(long),sizeof(float),sizeof(double));
}

sizeof 함수를 통해서 각 자료형의 크기를 알 수 있다.

sizeof 함수가 적용 가능한 점을 미루어 보아 다음과 같은 추측을 할 수 있다. c언어의 자료형은 메모리 상의 특정 크기의 공간을 할당해 주는 것이다. 이는 하나의 배열이라고 볼 수 있다. 따라서 sizeof 함수를 통해서 각 자료형의 크기를 볼 수 있는 것이다.

변수의 이름을 지을 때는 숫자나 기호로 시작하기보다는 영문으로 시작하는 것이 바람직하며 c언어의 키워드 즉 예약어를 변수명으로 지으면 안 된다.

변수의 이름은 역할을 잘 설명하는 것이 중요하다.

변수를 선언하는 의미는 컴파일러에게 어떤 변수를 사용하겠다고 미리 알리는 것이다.

변수의 초기화는 혹시 모를 오류를 위해 해주는 것이 좋다.

변수는 호출시점 이전에 선언이 되어있어야 한다.

 

#include <stdio.h>
#define PI 3.141592 //기호상수 선언

double Circle_Area(double r){
    double area = r * r * PI; //기호상수 호출
    return area;
}

int main(){
    double x = 0;
    printf("반지름의 길이를 입력하시오: ");
    scanf("%lf",&x);

    double ans = Circle_Area(x); //원의 넓이 구하는 함수 호출
    printf("%lf",ans);
    
    return 0;
}

위처럼 기호 상수와 함수를 사용하면 코드를 좀 더 깔끔하게 작성할 수 있다. 기호 상수는 #define을 통해서 선언할 수 있다.

 

음수 표현법)

우선 컴퓨터는 0과 1로 이루어져 있다는 말을 들어본 적이 있을 것이다. 이러한 말의 의미는 컴퓨터는 문자형, 정수형, 실수형 모두 0과 1을 통한 2진법으로 표현한다는 것이다. 위의 그림에서 첫 번째 방법을 보자. 맨 앞에 음수와 양수를 구분해 주는 비트가 존재한다고 하자. 이는 사람의 관점에서 만약 컴퓨터가 십진법 연산을 할 줄 안다면 굉장히 간편할 것이다. 그러나 컴퓨터는 2진법 연산을 한다 따라서 부정학환 결과가 나오는데 실제로 10000110 이러한 결과 값이 나온다. 양수 정수 판별을 위한 앞에 비트를 하나 제거하고 본다면 0000110인데 이를 0으로 컴퓨터에 인지 시키는 것이 쉽지 않다.

 

그러나 00000011과 이것의 보수인 11111101을 보자. 둘을 더하게 되면 100000000 이 된다. 이는 8비트를 넘어갔기 때문에 앞의 숫자를  하나 제거하고 보면 00000000 즉 십진법 기준 0으로 볼 수 있다. 따라서 컴퓨터에서 음수 표현법으로 2의 보수를 사용하는 것이다. 이쪽이 컴퓨터 입장에서의 2진법 연산 시 훨씬 효율적이기 때문이다.

 

#include <stdio.h>

int main(){
    int x = 8;
    int y = -8;
	
    //8자리 16진수로 표현
    printf("x = %08X\n",x); // x = 00000008 
    printf("y = %08X\n",y); // y = FFFFFFF8
    printf("x+y = %08X\n",x+y); // x+y = 00000000
}
#include <stdio.h>

int main(){
    int x = 8;
    int y = -4;

    printf("x = %08X\n",x); // x = 00000008
    printf("y = %08X\n",y); // y = FFFFFFFC
    printf("x+y = %08X\n",x+y); // x+y = 00000004
}

부동소수점형)

위의 그림을 간단히 설명하자면 1234000000.456789 라는 실수가 존재한다. 이는 각 숫자 하나가 4 비트라고 쳤을 때  64비트 + 1비트가 필요하다. 부동 소수점 형을 이용하면 1234e+6,456789로 훨씬 큰 수를 표현하는 것에도 수월하고  메모리의 사용량도 감소한다.

다만 해당 범위보다 작은 수를 표현하려고 하면 언더플로우가 발생하고, 해당 범위보다 큰 수를 표현하려고 하면 오버 플로우가 발생한다.

그리고 부동소수점의 가장 근본적인 문제는 실수의 정확한 표현이 불가능 하다는 것이다.

#include <stdio.h>

int main()
{
    float num1 = 0.0f;
    float num2 = 0.1f;

    // 0.1을 10번 더함
    for (int i = 0; i < 10; i++)
    {
        num1 = num1 + num2;
    }

    printf("%.15f\n", num1);    // 1.000000119209290: 1.0이 나와야 하지만 반올림 오차 발생

    return 0;
}

0.1을 10번 더했으나 1.0의 근삿값이 나왔다. 이러한 이유는 실수는 사실상 무한하다. 1.000000000000000000... 근데 이러한 숫자를 컴퓨터가 전부 비트로 표현하는 것은 불가능하다. 따라서 근삿값으로 계산을 하기 때문에 위와 같은 문제가 발생한다.

이를 통해서 실수형은 단순히 연산자 '==' 으로 비교를 해줘서는 안 된다. 정수형으로 타입 캐스팅을 통해서 바꿔서 비교하는 방법도 있겠지만 이것도 항상 정확한 방법은 아니다.

 

#include <stdio.h>
#include <float.h>    // float의 머신 엡실론 값 FLT_EPSILON이 정의된 헤더 파일
#include <math.h>     // float의 절댓값을 구하는 fabsf 함수를 위한 헤더 파일

int main()
{
    float num1 = 0.0f;
    float num2 = 0.1f;

    // 0.1을 10번 더함
    for (int i = 0; i < 10; i++)
    {
        num1 = num1 + num2;
    }

    // num1: 1.000000119209290
    if (fabsf(num1 - 1.0f) <= FLT_EPSILON)    // 연산한 값과 비교할 값의 차이를 구하고 절댓값으로
                                              // 만든 뒤 FLT_EPSILON보다 작거나 같은지 판단
                                              // 오차가 머신 엡실론 이하라면 같은 값으로 봄

        printf("true\n");    // 값의 차이가 머신 엡실론보다 작거나 같으므로 true
    else
        printf("false\n");

    return 0;
}

float.h 헤더파일에 존재하는 FLT_EPSILON을 통해서 비교하는 것이 좋다. 부동소수점의 오차를 고려해서 비교해준다.

먼저 연산한 값과 비교할 값의 차이를 구한 뒤 FLT_EPSILON보다 작거나 같은지 판단한다.

값의 차이는 math.h 헤더 파일의 fabsf 함수를 사용하여 절댓값으로 만들면 음수가 나오더라도 정상적으로 판단할 수 있다.

FLT_EPSILON float.h 헤더 파일에 정의되어 있으며 이 값을 머신 엡실론(machine epsilon)이라 부른다. 어떤 실수를 가장 가까운 부동소수점 실수로 반올림하였을 때 상대 오차는 항상 머신 엡실론 이하다. 즉, 머신 엡실론은 반올림 오차의 상한 값이며 연산한 값과 비교할 값의 차이가 머신 엡실론보다 작거나 같다면 두 실수는 같은 값이라 할 수 있다. 만약 double, long double을 사용한다면 머신 엡실론은 DBL_EPSILON, LDBL_EPSILON을 사용한다.

 

문자형)

문자형은 사람에게 유용하고 중요한 자료형이다. 이를 위해서 공통 규격이 존재하는데 이를 아스키(ASCII) 코드라고 한다. 8비트를 이용해서 영어 알파벳을 표현한다.

#include <stdio.h>

int main()
{
    char c1 = 'j';
    char c2 = 'o';
    char c3 = 'n';

    int a1 = 106; //아스키 코드표 Dec 기준 j
    int a2 = 111; //아스키 코드표 Dec 기준 o
    int a3 = 110; //아스키 코드표 Dec 기준 n

    printf("%c%c%c%c\n",c1,c2,c2,c3); //출력: joon
    printf("%c%c%c%c",a1,a2,a2,a3); //출력: joon
    
    return 0;
}

1. 정수형 변수를 정수형으로 출력

2. 정수형 변수를 문자형 변수로 출력

3. 정수로 이루어진 문자형 변수를 문자형 변수로 출력

4. 정수로 이루어진 문자형 변수를 정수형 변수로 출력

 

위 4가지의 차이를 헷갈리지 않고 알아두는 것이 중요하다.

 

728x90
반응형

'Programming languages > C' 카테고리의 다른 글

6. C언어(함수 & 변수)  (0) 2022.08.14
5. C언어(반복문)  (0) 2022.08.11
4. C언어(조건문)  (0) 2022.08.09
3. C언어(수식과 연산자)  (0) 2022.08.09
1. C언어(기본,함수,자료형)  (0) 2022.08.09