C언어 | 연산

CHOI·2021년 5월 26일
0

C 언어

목록 보기
4/28
post-thumbnail

컴퓨터가 생긴 이유가 무엇일까?

여러 가지 이유가 있지만 아무래도 인간이 하기 어려운 복잡한 계산을 빠르게 하기 위해서 탄생한 기계가

컴퓨터라고 말할 수 있다.

#include <stdio.h>
int main() {
  int a, b;
  a = 10;
  b = 3;
  printf("a + b 는 : %d \n", a + b);
  printf("a - b 는 : %d \n", a - b);
  printf("a * b 는 : %d \n", a * b);
  printf("a / b 는 : %d \n", a / b);
  printf("a %% b 는 : %d \n", a % b);
  return 0;
}
a + b 는 : 13
a - b 는 : 7
a * b 는 : 30
a / b 는 : 3
a % b 는 : 1

1. 산술 연산자

계산이라고 하면 가장 먼저 떠오르는 것이 바로 사칙연산이다.

C언어에서 사칙연산은 + , - , * , / 이렇게 표시한다.

그리고 그 외에 % 가 있는데 이는 나머지를 표시하는 연산기호이다. 예를 들어서 10 % 3 은 1이 나온다.

이러한 + , - , * , / ,% 를 산술 연산자(Arithmetic Opeator)라고 한다.

2. 대입 연산자

a = 10 은 앞에서도 배웠듯이 변수 a 에 10을 대입하는 것이다.

그렇다면 10 = a 도 가능할까?? 기본적으로 수학에서 a = 1010 = a 은 같은 뜻이지만 C언어 컴파일러 에서는 다른 의미이다. 컴파일러는 '='를 뒤에서 부터 해석합니다.

10 = a 는 변수 a를 10에 대입하라는 의미이기 때문에 오류가 발생한다.

이렇게 '=' 를 대입 연산자(Assignment Operator) 라고 한다. 왜냐하면 우측 값을 좌측에 대입하기 때문이다.


printf("a + b 는 : %d \n", a + b);
printf("a - b 는 : %d \n", a - b);
printf("a * b 는 : %d \n", a * b);
printf("a / b 는 : %d \n", a / b);
printf("a %% b 는 : %d \n", a % b);

이제 위 코드를 살펴보자.

기본적으로 앞서 배운 내용을 가지고 이해하는데 문제가 없다.

다만 마지막 줄에 %% 가 좀 이상하다고 생각할 수 있다.

%를 두번 사용한 이유는 %를 한번만 사용하면 %d %f 과 같이 컴퓨터가 인식할 수 있기 때문에

%% 를 사용함으로써 %를 출력하길 원한다는 의미를 나타내는 것이다.

또한 한가지 의문점이 드는 것이 있는데 바로 a / b 는 3.33333인데 3으로 출력된 것이다.

앞서 변수 abint 형으로 선언하였다. 즉 ab 는 오직 '정수'형 데이터만 담당한다.

즉, ab 는 모두 정수형 데이터만 처리하기 때문에 ab 를 나누면 3.33333이 되겠지만

정수부분인 3만 남게 되는 것이다.

그렇다면 %d 말고 %f로 하면 어떻게 될까?

#include<stdio.h>
int main(){
	int a,b;
	a = 10;
  b = 3;
	printf("a / b 는 : %f \n", a / b);
	return 0;
}
a / b 는 : 0.000000

이전에 %f 는 오직 실수형 데이터만 출력한다고 했다. 그런데 a / d 가 3.33333.. 이 된다고 해서 실수형 데이터가 되는 것이 아니다.

(정수형 변수) (연산) (정수형 변수) 를 하면 언제나 (정수) 로 유지된다.

실수형 데이터를 출력하는 %f 를 정수값에 대입하면 위와 같이 이상한 데이터가 나오게 된다.

3. 산술 변환

그러면 3.33333으로 나오게 하려면 어떻게 할까?

#include <stdio.h>
int main() {
  int a;
  double b;

  a = 10;
  b = 3;
  printf("a / b 는 : %f \n", a / b);
  printf("b / a 는 : %f \n", b / a);
  return 0;
}
a / b 는 : 3.333333
b / a 는 : 0.300000

a는 정수형 변수이고 b는 실수형 변수이다. 그런데 이 두 변수를 가지고 연산을 한 다음에 그 결과를 실수형 데이터로 출력하게 하였는데 정상적으로 출력이 된다.

왜냐하면 이 컴파일러는 산술 변환이라는 과정을 걸치기 때문이다.

즉 어떠한 두 변수가 연산을 할 때 숫자의 범위가 큰 자료형으로 변환이 된다.

a 는 int 형이고 b는 double 형 이기 때문에 둘의 연산은 숫자의 범위가 더 큰 double 형으로 변환되어

%f를 하면 정상적으로 출력이 된다.

4. 축약된 대입 연산자

a = 1
a = a + 1

위 연산은 수학적으로는 이상하지만 컴파일러에서는 자주 사용되는 연산이다.

앞서 대입 연산자에서 확인했듯이 '=' 의 의미는 우측의 값을 좌측에 대입한다는 의미이다.

즉, 위를 해석해보면 a 에 (a의 값 + 1 ) 를 대입한다는 뜻이다.

더 풀어서 설명해보자면 a 에 1+1 의 값을 대입한다는 뜻이다. 따라서 a 는 2가 된다.

위 같은 연산을 더 짧게 표현하는 방식이 있다.

a = 1
a += 1
a -= 1
a /= 1
a *= 1

위와 같이 a += 1a = a + 1 과 같은 의미이다. 이와 같이 다음 연산도 a = a - 1, a = a / 1, a = a * 1과 같은 의미이다.


위와 비슷하게 다른 대입 연산자가 있다.

#include <stdio.h>
int main() {
  int a = 1;

  printf("++a : %d \n", ++a);

  a = 1;
  printf("a++ : %d \n", a++);
  printf("a : %d \n", a);

  return 0;
}
++a : 2
a++ : 1
a : 2

위와 같이 ++ 연산을 증감 연산자이라고 한다. 증감 연산자가 숫자 앞에 있으면 전위형(prefix), 숫자 뒤에 있으면 후위형(postfix) 이라고 한다.

전위형은 먼저 1을 더한 다음에 그 결과를 돌려주고

후위형은 먼저 결과를 돌려준 다음에 1을 더한다.

참고로 위의 연산들 중에서 a++ 과 같은 증감 연산자가 제일 빠르게 연산된다.

5. 비트 연산자

비트 연산자는 조금 생소한 연산자라고 할 수 있다. 우선 비트에 개념에 대해서 알아보겠다.

비트(bit)는 컴퓨터에서 숫자의 최소 단위로 1비트는 이진법의 0과 1로 나타낼 수 있다. 쉽게 말해서 이진법의 한자리를 1 비트라고 한다.

보통 8비트가 모여서 1바이트(byte)라고 하는데 1바이트의 숫자의 범위는 이진법으로 00000000부터 11111111까지 표현 할 수 있다. 이를 10진법으로 표현하자면 0부터 255까지를 나타낼 수 있다.

비트 연산자에서는 & (And 연산), | (\ 위에 있는 것. 영문자 i 의 대문자가 아님. Or 연산), ^ (XOR 연산), <<, >> (쉬프트 연산) , ~ (반전) 등이 있다.

5-1 AND 연산( & )

& 연산은 다음과 같은 규칙을 따른다.

1 & 1 = 1

1 & 0 = 0

0 & 1 = 0

0 & 0 = 0

('='를 쓰는게 맞지 않을 수 있지만 편의상 결과값이라는 의미로 사용하겠다)

즉 두자리 숫자가 같으면 그 값을 출력하고 만약 다르면 0을 출력한다.

예를 들어서 1100 & 0101 의 경우 0100 이 출력된다.

만약 자리수가 다른 1110110 & 11 의 경우 11 앞에 0을 자리수에 맞춰서 추가하여

1110110 & 0000011 과 같은 결과가 나온다.

5-2 OR 연산 ( | )

OR연산은 다음과 같은 규칙을 따른다

1 | 1 = 1

0 | 1 = 1

1 | 0 = 1

0 | 0 = 0

즉 둘중에 하나라도 1이 있으면 1을 출력한다.

AND연산과 대조적이다. 예를들어서 1100 | 0011 의 결과 값은 1111 이다.

5-3 XOR 연산 ( ^ )

1 ^ 1 = 0

1 ^ 0 = 1

0 ^ 1 = 1

0 ^ 0 = 0

이 연산은 둘의 값이 다르면 1을 출력하고 같으면 0을 출력한다.

예를 들어서 1100 ^ 0101 의 결과 값은 1001이 된다.

마치 두 비트를 더한다고 생각하면 된다.

5-4 반전 연산 ( ~ )

이 연산은 간단하게 말해서 0을 1으로 1을 0으로 바꿔준다.

예를 들어서 ~1100 의 결과 값은 0011 이 된다.

5-5 쉬프트 연산 (<<, >>)

5-5-1 <<

먼저 << 를 살펴보면

위 연산을 보기에도 느껴지듯이 왼쪽으로 이동을 시킨다.

예를 들어서 a = 101011 이라고 하고 a << 1 을 하면

1 만큼 왼쪽으로 옮기고 만일 앞에 쉬프트된 숫자가 갈 곳이 없으면 삭제가 된다.

그리고 맨 오른쪽 자리에는 0이 온다.

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/5f84c709-8a36-4ccf-83c4-143fc4b7652d/Untitled.png

5-5-2>>

그 다음으로 >> 를 살펴보면 위와 비슷하지만 다른 방향 즉 오른쪽으로 이동시킨다.

그리고 오른쪽에 있던 갈 곳 없는 숫자들은 버려지고 왼쪽에 빈 공간에는 가장 왼쪽에 있었던 숫자가

채워지게 된다.

예를 들어서 a = 10110 이라고 하고 a >> 3 을 하면 11110 이 된다.

오른쪽에 있었던 3자리 110 은 사라지고 가장 앞에 있었던 숫자 1이 오른쪽 빈자리를 채우게 되면서

11110이 된다.

(참고로 어떤 시스템에서는 >> 시 무조건 왼쪽에 0이 채워지는 경우도 있다)

5-6 실제 예시

전체적으로 위에 배웠던 비트 연산들의 실제 코드에서의 표현과 결과값을 확인해보자

#include <stdio.h>
int main() {
  int a = 0xAF;  // 10101111
  int b = 0xB5;  // 10110101

  printf("%x \n", a & b);   // a & b = 10100101
  printf("%x \n", a | b);   // a | b =10111111
  printf("%x \n", a ^ b);   // a ^ b = 00011010
  printf("%x \n", ~a);      // ~a = 1....1 01010000
  printf("%x \n", a << 2);  // a << 2 = 1010111100
  printf("%x \n", b >> 3);  // b >> 3 = 00010110

  return 0;
}
a5
bf
1a
ffffff50
2bc
16

만약 성공적으로 컴파일이 되었다면 위처럼 결과값이 나올 것이다.

처음 3째 줄까지는 대체로 이해하는데 무리가 없을 것이다 그러나 ~a 의 결과값은 의야할 것이다.

printf("%x \n", ~a);  // ~a = 1....1 01010000

앞서 다른 페이지에서 여러 자료형들의 설명과 크기를 나타낸 것을 본 적이 있을 것이다.

그 표를 다시 가져와서 보자면


여기서 볼 수 있듯이 int 자료형은 하나의 데이터를 저장하기 위해서 메모리상의 4바이트 즉 32비트를 사용한다.(1 byte = 8 bits) 아까 하나의 비트가 0과 1 을 나타낸다고 했으니 (즉 1개의 비트에 이진수 한 자리를 나타낸다), 하나의 int 형 변수는 32자리의 이진수라고 볼 수 있다.

예를들어서 우리가 a = 1 을 하게 되면 실제로 컴퓨터에서는

a = 00000000 00000000 00000000 000000001 이렇게 저장이 된다.

즉 우리가 int a = 0xAF; 를 할 때 a = 10101111; 가 맞지만 실제로 컴퓨터 메모리상에서는 a 가 int 형 이기 때문에 a = 00000000 00000000 00000000 10101111 가 저장되는 것이다.

그렇기 때문에 ~a 를 하게 되면 이 숫자가 반전이 되어 a = 11111111 11111111 1111111 0101000 가 된다. 따라서 0xFFFFFF50 이 된다.

printf("%x \n", a << 2);  // a << 2 = 1010111100
printf("%x \n", b >> 3);  // b >> 3 = 00010110

위 두줄의 코드도 사실은 00000000 00000000 00000000 10101111 를 << 2 를 한 것이고

00000000 00000000 00000000 10110101 를 >> 3 한 것이기 때문에

각각 a = 00000000 00000000 00000010 10111100 이기 때문에 0x2BC 이고

b = 00000000 00000000 00000000 00010110 이 되어 0x16 이 된다.

6. 복잡한 연산

마지막으로 여러 연산들이 합쳐진 복잡한 연산들에 대해서 알아보자.

수학에서도 연산에서 우선순위가 되는 연산이 있듯이 컴퓨터에서도 어떤 연산을 먼저해야 할지 정해져 있는

우선순위가 있을 뿐더러 연산 방향까지 정해져 있다. 이를 알아보면 다음과 같다.

이와 같이 순서가 정해져 있다. 눈여겨보아야 할 점은 바로 괄호과 가장 1순위가 된다는 것이다.

그렇기 때문어 어떠한 연산을 우선적으로 해주고 싶으면 괄호를 감싸주면 되는 것이다.

마지막으로 결합 순위에 대해서 다루어 보자면 표의 오른쪽에 보면 결합 순위가 나와 있는데

대부분 '왼쪽 우선' 이지만 몇 가지는 '오른쪽 우선'이다. 이게 무슨 말이냐면 아래 계산이 수행될 때

계산하는 순위를 이야기 한다.

a = b + c + d + e;

위 표에서 볼 수 있듯이 덧샘의 결합 순서는 왼쪽 우선이기 때문에 위 연산은 아래와 같은 과정으로 연산이 된다.

  1. b + c 를 계산하여 그 값을 반환( 그 결과가 C 라고 하면)
  2. C + d 를 계산하여 그 값을 반환( 그 결과가 D 라고 하면)
  3. D + e 를 계산하여 그 값을 반환( 그 결과가 E 라고 하면)

위 식은

a = E

가 된다. E 의 값, 즉 b + c + d + ea 에 들어가게 되는 것이다.


이번에는 몇 안되는 '오른쪽 우선' 인 대입 연산자(=) 를 확인해보자. 만약 대입 연산자가 '왼쪽 우선' 이였다면

아래의 식이 어떻게 되었을지 확인해보자

a = b = c = d = 3;

만약 왼쪽 우선이였다면 a = b; b = c; c = d; d = 3; 순서대로 진행이 되어 a, b, c 에는 알 수 없는

값이 들어가게 되었을 것이다. 하지만 오른쪽 우선이기 때문에 d = 3; c = d; b = c; a = b; 순서대로 진행이 되어서 a, b, c, d 의 값에 모두 3이 될 수 있다.

profile
벨로그보단 티스토리를 사용합니다! https://flight-developer-stroy.tistory.com/

0개의 댓글