카테고리 없음

c언어 5강 chap.5 상수와 기본 자료형(p119~)

킹차니 2021. 3. 14. 12:56

상수에 대한 이해

 

상수는 크게 이름이 있는 상수와 이름이 없는 상수로 나뉘며,  상수 역시 앞서 설명한 int, double과 같은 자료형을 근거로 표현이 된다.

 

이름을 지니지 않는 리터럴 상수 

 

상수란 변경이 불가능한 데이터를 뜻한다.

ex) int main(void)

     {

             int num = 30+40;   //  30과40은 상수임.

      }

 

cpu는 메모리에 있는 수를 연산할 수 있다. 그리하여 위의 예시는 아래와 같이 진행된다.

1. 정수 30과 40이 메모리 공간에 상수의 형태로 저장된다.

2. 두 상수를 기반으로 덧셈이 진행된다.

3. 덧셈의 결과로 얻어진 정수 70이 변수 num에 저장된다.

이렇게 상수(30, 40)는 변수 num과 다르게 메모리 공간에 이름이 없다. 이러한 상수를 가리켜 '리터럴 상수', '리터럴'이라고 부른다.

 

 

리터럴 상수의 자료형

 

자료형은 상수를 위해서도 존재한다. 모든 데이터는 자료형이 결정되어야한다. 그래야 저장하는 방법이 정해지기 때문이다.

int main(void) {
    int inum = 5; // 정수형 상수
    double dnum = 7.15; // 실수형 상수
}

int형으로 표현 가능한 정수형 상수는 int형으로 메모리 공간에 저장하기로 약속됨.

double형으로 표현 가능한 실수형 상수는 double형으로 저장하기로 약속됨.

하지만 대입연사자의 왼편에 있는 변수의 자료형에 따라서 상수형의 자료형이 장해지는 것은 아니다!

다만 대입 연산자의 오른편에 선언된 상수의 자료형에 어울리게 변수의 자료형을 선언했을 뿐이다.

 

그렇다면 char ch ='A'; 의 문자형 상수는 어떻게 표현이 될까? 저 문장은 컴파일러에 의해

char ch = 65; 로 바뀌고, 65는 정수이니 4바이트 크기의 int형으로 표현된다. 이를 위해 다음예제를 보자. 

int main(void) {
    printf("literal int size:%d \n",sizeof(7));
    printf("literal double size:%d \n",sizeof(7.14));
    printf("literal char size:%d \n",sizeof('A'));
    return 0;
}

출력결과:

literal int size:4 

literal double size:8 

literal char size:4

위 예제처럼 sizeof 연산자를 이용해서 리터럴 상수의 크기를 확인해 볼 수도있다. 그리고 문자형의 상수의 크기가 int형 크기인 4바이트로 표현됨을 확인할 수 있다.

 

접미사를 이용한 다양한 상수의 표현

int와 double 이외의 자료형을 기반으로 상수를 표현하려면 해당 자료형을 의미하는 접미사를 붙여주면 된다.

float num1 = 5.789f;

float num2 = 3.24F + 5.12F; (대소문자 구별없음)

c언어는 다양한 자료형의 상수를 표현할 수 있도록 다음과 같이 접미사를 활용하고 있다.

 

<정수형 상수의 표현을 위한 접미사>

접미사            자료형                                       사용의 예

U                unsigned int                         unsigned int n = 1025U

L                 long                                     long n = 2467L

UL              unsigned long                      unsigned long n = 3456UL 

LL               long long                             long long n = 5765LL

ULL             unsigned long long             unsigned long long n = 8979ULL

 

<실수형 상수의 표현을 위한 접미사>

접미사                      자료형                            사용의 예

F                             float                           float f = 3.14F

L                             long double               long double f = 5.789L

 

 

이름을 지니는 심볼릭(Symbolic)상수: const 상수

 

심볼릭 상수는 변수와 마찬가지로 이름을 가지는 상수이다. 심볼릭 상수를 표현하는 방법에는 const키워드를 사용하는 방법과 메크로를 이용하는 두가지 방법이 있는데, 여기서는 const키워드 방법만 알아보자.

const 키워드를 이용하는 방법은 변수 선언시 const 선언만 추가하면 된다. 단, 이는 상수이기 때문에 선언과 동시에 초기화를 해야한다.

int main(void) {
    const int MAX = 100; //MAX는 상수, 따라서 값이 변경 불가
    const double PI = 3.141592; // PI도 상수 값의 변경 불가.
}

 

아래는 잘못된 예시이다.

int main(void){
	const int MAX; // 이렇게 하면 쓰레기 값으로 초기화됨
	MAX = 100; 
}

주석을 통해 언급하고 있듯이 const상수는 변수 선언만 하고 초기화 하지 않으면 쓰레기 값으로 초기화 되어 이후에 값을 변경하려 할 때 컴파일 에러가 발생한다.

const 상수 선언시 " 상수의 이름은 모두 대문자로 표시하고 둘 이상의 단어는 _(언더바)로 연결한다."

 

 

자료형 변환

 

char형 ----> int형으로 바꾸거나

int형    ----> double로 바꾸는 것이 자료형 변환이다.

 

자료형 변환은 두 가지로 나뉜다. 

1. 자동 형 변환 (묵시적 형 변환)

2. 강제 형 변환 (명시적 형 변환)

 

대입연산의 전달과정에서 발생하는 자동 형 변환

 

대입 연산자의 왼편과 오른편에 존재하는 두 피연사자의 자료형이 일치하지 않으면, 왼편에 있는 피연산자를 대상으로 형 변환이 일어난다.(저장소의 자료형에 맞춰서 형 변환이 일어나야 값의 저장이 가능하므로)

 

double num1 = 245; //int형 정수 245를 double형으로 자동 형 변환.

이 경우 왼편의 num1은 double형이지만 245는 int형이다. 따라서 int형 정수 245가 double형 실수 245.0으로 형 변환되어 num1에 저장된다.

 

int num2 = 3.1415; // double형 살수 3.1415를 int형으로 자동 형 변환.

이 경우 double형 변수 3.1415가 int형으로 변환되어 변수 num2에 저장된다. 그런데 int형으로는 소수점이하의 값을 표현할 수 없으므로 형 변환 과정 중 3.1415가 3으로 변환되어서 그 결과가 num2에 저장된다. 이렇듯 실수형 데이터를 정수형 데이터로 변환하는 과정에서 소수부의 손실이 발생한다.

 

int num3 = 129;
char ch = num3;//int형 변수 num3에 저장된 값이 char형으로 자동형 변환

 129가 저장된 변수 num3의 비트열은 다음과 같다.

00000000 00000000 00000000 10000001

그런데 이 수를 데이터 변수ch에 저장하기 위해서는 1바이트 크기로 줄여야 한다. 따라서 이경우는 상위 바이트의 손실​이 발생하여 그 결과는 다음과 같다.

10000001  //이는 정수로 -127

이렇듯 상위 바이트의 손실로 인해 부호가 바뀌는 경우가 있으니 주의해야 한다. 이를 정리하면

 

1. 정수를 실수로 형 변환하는 경우 :

    실수의 표현범위가 정수에 비해 훨씬 넓기 때문에 데이터의 손실은 잃어나지 않는다. 다만, 실수의 표현이기에 오차는 존재한다.

 

2. 실수를 정수로 형 변환하는 경우 :

    정수는 소수점 이하의 값을 표현하지 못하므로, 소수점 이하의 값은 버려진다.

 

3. 바이트 크기가 큰 정수를 바이트 크기가 작은 정수로 형 변환하는 경우 :

    변환하고자 하는 정수의 바이트 크기에 맞춰서 상위 바이트를 단순히 소멸시킨다. 그리고 이로 인해 부호가 바뀔 수도 있다.

 

다음 예제를 통해 위의 내용을 실제로 살펴보자.

int main(void) {
    double num1 = 245;
    int num2 = 3.1415;
    int num3 = 129;
    char ch = num3; 
    printf("정수 245를 실수로:%f \n",num1);
    printf("실수 3.1415를 정수로:%d \n",num2);
    printf("큰 정수 129를 작은 정수로:%d \n",ch);
}

출력결과:

정수 245를 실수로:245.000000 

실수 3.1415를 정수로:3 

큰 정수 129를 작은 정수로:-127 

 

정수의 승격(integral promotion)에 의한 자동 형 변환

 

앞서 "일반적으로 cpu가 처리하기에 적합한 자료형을 int형으로 정의한다. 따라서 int형 연산의 속도가 다른 자료형의 연산속도에 비해 동일하거나 더 빠르다."

따라서 int형 보다 작은 크기의 정수형 데이터는 int형 데이터로 형 변환이 되어서 연산이 진행된다 하였다.

이러한 형태의 형 변환을 가리켜 '정수의 승격'이라 한다.

 

 

피연산자의 자료형 불일치로 인한 자동 형변환

사칙 연산 같은 두개의 피연산자가 필요한 산술연산에는 이 둘의 자료형이 같아야 한다. 만약 일치하지 않는다면 자료형의 일치를 목적으로 자동 형 변환이 일어난다.

아래의 문장을 보자.

double num1 = 5.15 + 19;

이 문장에서 실수형 데이터와 정수형 데이터의 합을 요구한다. 그런데 실수형 데이터와 정수형 데이터는 표현방식이 다르므로 덧셈이 불가능하다. cpu는 같은 자료형의 두 피연산자를 대상으로만 연산이 가능하도록 설계되었기 때문이다. 따라서

(1)  5.15를 정수형으로 변환하거나

(2) 19를 실수로 형 변환해야 한다.

하지만 1의 경우 15.15가 15로 바뀌면서 데이터의 손실이 발생한다. 

2의 경우 정수가 실수로 바뀌면서 오차가 존재할 여지가 있지만 정확한 연산의 결과가 나온다. 즉, 데이터의 손실을 최소화하려면 다음과 같이 형 변환이 이뤄져야 한다.

" int형 정수를 double형 실수로 형 변환한다. "

 

int -> long -> long long -> float -> double -> long double       이러한 방향으로 자동 형 변환 진행됨.

 

             (4바이트의 float가 8바이트 크기의 long long보다 형 변환의 우선 순위가 높은 것은 데이터 손실의 최소     

                          화 기준에 맞추기 위해서임)

 

char형과 short형은 둘다 연산시, 정수의 승격에 의해 int형 정수로 변환된다.

 

 

명시적 형 변환: 강제로 일어나는 형 변환

 

​명시적 형 변환은 형 변환 연산자를 이용해 강제로 형 변환을 명령하는 것이다.

 

int main(void) {
    int num1=3, num2=4;
    double divResult;
    divResult = num1/num2;
    printf("나눈셈 결과:%f \n",divResult);
    return 0;
}

출력결과: 나눈셈 결과:0.000000 

 

원래 0.75가 나와야 하지만 연산결과의 자료형은 피연산자의 자료형과 일치하기 때문에, 나눗셈의 결과는 0이 되고(정수형 나눗셈 결과의 몫), 이 값이 double 형으로 자동 변환되어 변수 divResult에 저장된다.

그렇다면 위의 4행을 변경하여 출력해보자.

int main(void) {
    int num1=3, num2=4;
    double divResult;
    divResult = (double)num1/num2;
    printf("나눈셈 결과:%f \n",divResult);
    return 0;
}

출력결과: 나눈셈 결과:0.75000

 

​c언어에서 (소괄호)는  연산의 순서를 위해서도 사용되지만(수학적 의미), 형 변환을 명령할 때에도 사용이 된다.

즉, (double)num1은 변수 num1에 저장된 값을 double형으로 변환하라는 뜻이다. 이렇게 사용되는 소괄호를 가리켜 '형 변환 연산자'라 하며 연산의 결과로는 변환된 값이 반환된다. 즉 위의 문장에서 제일 먼저 진행되는 연산은 형 변환 연산이고, 이 연산의 결과로 위의 문장은 다음과 같은 형태가 된다.

divResult = (double)num1/num2;
---> divResult = 3.0 / num2;

이어서 나눗셈을 진행해야 하는데, 산술연산의 형 변환 규칙에 의해 num2에 저장된 값도 double형으로 자동 형 변환된다. 즉 위의 문장은 다시

divResult = 3.0 / 4.0;

이렇게 바뀐다. 그다음 나눗셈이 진행되고, 그 결과가 변수 divResult에 저장된다.

다음과 같은 예시를 하나 더 보면,

int main(void) {
    int num1 = 3;
    double num2 = 2.5 * num1;
}

위처럼 자동 형변환이 발생하는 경우에는

int main(void) {
    int num1 = 3;
    double num2 = 2.5 * double(num1);
}

요렇게 형 변환을 명시해 주는 것이 좋다. 이는 형 변환이 발생하는 위치를 표시해서 코드의 분석을 돕는 효과가 있기 때문이다.