C언어 (5) 연산자
컴퓨터의 연산이란 사칙연산을 포함해 대입, 지수, 조건 등이 있다.
상황에 맞는 연산을 할 수 있도록 연산자를 알고 있어야한다.
분류 | 연산자 | 설명 |
---|---|---|
대입 연산자 | = | 대입 |
산술 연산자 | + , - , * , / , % | 덧셈, 뺄셈, 곱셈, 나눗셈, 나머지 연산자 |
복합 대입 연산자 | += , -= , *= , /= , %= | 연산 후 대입 |
증감 연산자 | ++ , -- | 전위/후위 증가, 감소 |
관계 연산자 | == , != , > , < , >= , <= | 비교 연산자 |
논리 연산자 | && , || , ! | 논리 AND, OR, NOT 연산자 |
조건 연산자 | ? : | 삼항 연산자 |
비트 논리 연산자 | & , | , ^ , ~ | 비트 AND, OR, XOR, NOT 연산자 |
비트 이동 연산자 | << , >> | 비트 왼쪽, 오른쪽 시프트 연산자 |
C언어의 자주 쓰이는 연산자는 이 정도로 알아두면 된다.
대입 연산자
’=’은 대입 연산자로 이미 변수에 값을 넣을 때 자연스럽게 사용했다.
‘==’은 같다라는 의미로 관계 연산자에 있으니 2개를 혼동하면 안된다.
산술 연산자
’+’, ‘-‘, ‘*’, ‘/’는 각각 더하기, 빼기, 곱하기, 나누기 사칙연산을 한다.
‘/’ 연산에서 (정수) / (정수)를 한다면 소수점 이하는 버려지니 주의하자.
‘%’는 나머지를 구하는 연산이다.
복합 대입 연산자
생김새에서도 볼 수 있듯이 산술 연산자와 대입 연산자를 합친 것이다.
a = a + b라는 식을 a += b로 줄일 수 있다.
증감 연산자
++와 –기호를 이용해 값을 1증가 하거나 1감소시킨다.
복합 대입 연산자와 비슷하게 a++ 은 a=a+1과 같은 역할을 한다.
1
2
3
4
5
6
7
8
9
10
11
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main() {
int a, b;
int num1 = 10, num2 = 10;
a = ++num1;
printf("전위 : %d %d\n", a, num1);
b = num2++;
printf("후위 : %d %d", b, num2);
return 0;
}
이 ++의 위치에 따라 전위 방식과 후위 방식으로 나뉘는데 코드를 실행했을 때 결과가 다르다.
전위 방식은 num1에 1을 더하고 a에 넣고, 후위는 b에 num2를 넣은 뒤 num2에 1을 더한다.
관계 연산자
크기를 비교하는 연산자로는 >, <, >=, <=, ==, !=가 있다.
의외로 <=, =< 중 무엇이 맞는지 헷갈려한다.
말로 할 때 크거나 같다라고 하지 같거나 크다라고는 안하니까
그 순서대로 크거나(<) 같다(=) 그래서 <= 가 맞다고 이해하면 쉽다.
위에서도 말했지만 a와 b가 같냐는 것을 판단하는 것은 a==b이다. a=b와 헷갈리면 안된다.
‘!=’는 다르냐?를 판단하는 것으로 쓰인다.
c언어에서 참은 1, 거짓은 0으로 쓰인다.
1
2
3
4
5
6
7
8
9
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main() {
int a = 10, b = 10;
printf("%d\n", a == b);
a++;
printf("%d", a == b);
return 0;
}
을 실행해보면 위의 식은 참이기에 1이 출력되고, 밑에는 거짓이므로 0이 출력된다.
논리 연산자
논리 연산자는 AND(&&), OR(||), NOT(!) 3가지가 있다.
A | B | A && B |
---|---|---|
false | false | false |
false | true | false |
true | false | false |
true | true | true |
A && B는 둘 다 참이어야 참인 연산
A | B | A || B |
---|---|---|
false | false | false |
false | true | true |
true | false | true |
true | true | true |
A || B는 둘 중 하나만 참이어도 참인 연산
A | !A |
---|---|
false | true |
true | false |
!A는 반대로 결과를 낸다.
나중에 조건문을 할 때 자주 쓰게 된다.
조건 연산자
?와 :를 이용한 문장으로 조건문의 하나이다.
result = (num1>num2) ? num1 : num2; 라는 문장처럼 사용한다.
?앞에 있는 조건 문장이 참이면 num1로 가고, 거짓이면 num2로 가는 것이다.
비트 연산자
bit이란 2진수 값 하나를 저장하는 메모리 공간이라고 했다. 1byte = 8bit이다.
우선 비트 논리 연산자는 1bit끼리의 연산을 하는 것이다.
A | B | A & B |
---|---|---|
0 | 0 | 0 |
0 | 1 | 0 |
1 | 0 | 0 |
1 | 1 | 1 |
A | B | A | B |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 1 |
A | B | A ^ B |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
A | ~A |
---|---|
0 | 1 |
1 | 0 |
관계는 아까의 논리 연산자와 같다. 하나 늘어난 것은 xor(^)이 있는데
이는 2개가 같으면 거짓, 다르면 참으로 연산한다.
7 ^ 5를 계산하면 어떻게 될까? 우선 이진수로 바꾸면 111^101이다.
각 자리수끼리 계산을 하면 1^1=0, 1^0=1, 1^1=0이므로 010이 되어 2가 된다.
비트 이동 연산자는 ‘«‘와 ‘»‘가 있다. 화살표의 방향으로 비트를 이동시키는 것이다.
예를 들어 7«2는 이진수 111을 왼쪽으로 2번 이동시키니까 11100이 되어 28을 뜻한다.
반대로 28»2는 11100을 오른쪽으로 2번 이동시키니까 111이 되어 7을 뜻한다.
이진수 방향으로 1씩 옮긴다는 것은 왼쪽으로 가면 2가 곱해지고, 오른쪽은 2로 나누는 것과 같다.
연산자 우선 순위
이 많은 연산자들의 우선 순위는 어떻게 될까?
곱하기가 더하기보다 먼저 되는 것처럼 각 연산자에도 우선 순위가 있다.
우선순위 | 연산자 | 설명 |
---|---|---|
1 | () , [] , . , -> | 왼쪽에서 오른쪽으로 연산 |
2 | ++ , -- | 전위 증가/감소 (왼쪽에서 오른쪽으로 연산) |
3 | ! , ~ | 논리 NOT, 비트 NOT (왼쪽에서 오른쪽으로 연산) |
4 | * , / , % | 곱셈, 나눗셈, 나머지 (왼쪽에서 오른쪽으로 연산) |
5 | + , - | 덧셈, 뺄셈 (왼쪽에서 오른쪽으로 연산) |
6 | << , >> | 비트 왼쪽/오른쪽 시프트 (왼쪽에서 오른쪽으로 연산) |
7 | < , <= , > , >= | 비교 연산자 (왼쪽에서 오른쪽으로 연산) |
8 | == , != | 동등 연산자 (왼쪽에서 오른쪽으로 연산) |
9 | && | 논리 AND (왼쪽에서 오른쪽으로 연산) |
10 | || | 논리 OR (왼쪽에서 오른쪽으로 연산) |
11 | ? : | 삼항 연산자 (왼쪽에서 오른쪽으로 연산) |
12 | = , += , -= , *= , /= , %= | 대입 연산자 (오른쪽에서 왼쪽으로 연산) |
13 | , | 쉼표 연산자 (왼쪽에서 오른쪽으로 연산) |
자주 쓰게 될테니 잘 알아두자.
오버플로우와 보수법
정수의 overflow에서 최대 크기를 넘어가면 범위의 가장 작은 값으로 돌아갔다.
1
2
3
4
5
6
#include <stdio.h>
#include <limits.h>
int main() {
printf("%d %d", INT_MAX, INT_MAX+1);
return 0;
}
2147483647 -2147483648 라는 결과가 나올 것이다.
어째서 이런 현상이 발생하는지 정수 음수의 저장 방법에 대해 알아보자.
지난 번에는 이렇게 부호 비트와 절댓값 비트로 나눠진 모양으로 보았었다.
하지만 문제가 생기는 것이 0을 표현하는 방법이 부호가 1인 0과 부호가 0인 0으로 2가지가 생긴다.
컴퓨터는 0과 비교하는 연산을 굉장히 많이하기에 모든 경우에 부호가 1인 0과 부호가 0인 0 둘다 비교를 한다면 많은 자원이 낭비될 것이다.
그렇기에 부호 비트를 더 효율적으로 사용하는 방법이 2의 보수 표현법이다.
4bit 체계에서 7을 이진수로 표현하면 0111이다.
2의 보수 표현은 -7을 7을 전부 반전시킨 1000에다 1을 더해 1001로 쓰는 것이다.
이렇게 쓰면 +0과 -0은 같아진다. 0000을 반전시키면 1111이고, 1을 더하면 다시 0000이 된다.
10000에서 범위를 벗어난 1은 버려지기 때문이다.
2의 보수 표기법에서 음수와 양수를 전환하는 방법은 모든 자리수의 이진수를 반전하고 1을 더한다.
음수와 양수를 판단하는 방법은 최상위비트가 1이면 음수, 0이면 양수이다.
정리하면 음수나 양수간의 덧셈 시 부호를 고려하지 않아도 되고, 0의 표현이 1개다.
또 최상위 비트를 사용해서 부호를 빠르게 알아낼 수 있기에 음수를 표현할 때 2의 보수법을 사용한다.
이제 오버플로우가 발생할 때 어떻게 숫자가 바뀌는지 확인하자.
1
2
3
4
5
6
7
8
9
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main() {
char c = 127;
printf("%d\n", c);
c++;
printf("%d", c);
return 0;
}
8bit인 char에서 오버플로우를 발생시키면 각각 127, -128이 출력된다.
127은 0111 1111이고, 여기에 1을 더하면 1000 0000이 된다.
그러면 c에 저장된 1000 0000을 2의 보수 표기법으로 몇인지 확인을 해보자.
최상위 비트가 1인 음수이므로 절댓값을 구하기 위해 반전을 하면 0111 1111이고, 1을 더하면 1000 0000이다.
1000 0000 = 256이고, 즉 c에 저장된 1000 0000을 2의 보수 표기법으로 보면 -256이 되는 것이다.
이런 원리로 오버플로우가 발생했을 때 가장 작은 값으로 돌아가게 된다.
비트 연산
이 2의 보수 표기법 때문에 ‘«‘와 ‘»‘에 약간의 차이가 생긴다.
‘«‘는 왼쪽으로 bit를 하나 미는 연산으로 7«1 은 111을 1110으로 만든다고 했다.
이 왼쪽으로 미는 연산은 오른쪽에 생긴 공간에 0으로 채우면 된다.
그런데 오른쪽 연산은 좀 다르다. 양수와 음수일 때 왼쪽에 생긴 공간에 채우는 숫자가 다르다.
14»1은 1110을 111로 만들고, 양수이므로 왼쪽에 생긴 칸에 0을 채운다.
음수인 -10의 -10 » 1의 결과는 2로 나누는 것이니까 -5인데 왼쪽에 무엇을 채워야할까?
-10은 8bit에서 2진수로 1111 0110이다.
(1은 부호, 11110110을 반전하면 00001001), +1을 하면 00001010으로 10이다.)
-5는 2진수로 1111 1011이다. 즉 음수면 오른쪽으로 민 다음 왼쪽에 생긴 칸에 1을 채운다.
2의 보수 표기법을 위에서 했으니까 이해가 될 것이다.
음수가 어떻게 저장되는지 더 자세히 알아보았다.
여러가지 연산자들을 이용해 특정 조건에 따라 작동하는 프로그램을 만들어보자.