[잼민이도 이해하는 C언어 강의] 6. 연산자 / #산술연산자, #관계연산자, #논리연산자
안녕하세요
정고리즘의 정고쌤입니다.
지난 시간에는
scanf 입력 함수를 이용하여
데이터를 입력받는 방법에 대해서 알아보았습니다.
이번 강의에서는
입력받은 데이터를 가공하고 연산하기 위한
다양한 연산자들에 대해서 배워보겠습니다.
1. 산술 연산자 |
2. 관계 연산자 |
3. 논리 연산자 |
4. 그 외 연산자 |
5. 연산자 우선순위 |
이번에 알아볼 연산자들은
C 계열의 프로그래밍 언어(C++, C#) 뿐만 아니라 다양한 언어에서도
동일하게 적용 가능하니 반드시 알고계셔야하는
필수 개념입니다.
1. 산술 연산자
산술 연산자는 더하거나 빼거나
곱하거나 나누는 연산자들을 말합니다.
기본적으로 초등학생 때 다 배웠던 내용과 동일하기 때문에
전혀 어렵지 않습니다.
기능 | 연산자 | 사용 예시) |
더하기 | + | c = a + b; |
빼기 | - | c = a - b; |
곱하기 | * | c = a * b; |
나누기 | / | c = a / b; |
나머지 | % | c = a % b; |
지난 강의에서
변수에 대해서 알아보면서
= 대입 연산자라는 것을 기억하실 겁니다.
위 표에서 사용 예시를 보면
모든 연산자에 대입 연산자가 사용된 것을 볼 수 있는데
대입 연산자를 통해 연산한 결과를
저장해주기 위해서 입니다.
이미 아는 내용들이라 쉽게 이해 가능하지만
나누기 연산은 조금 다릅니다.
여러분,
5 나누기 2는 얼마인가요?
아마 정규 교육 과정을 받은 분들이라면
2.5라고 답변하시거나
몫은 2 나머지는 1
이라고 답변하실겁니다.
둘 다 정답입니다.
하지만 C 나누기 연산에서
5/2는 무조건 2가 됩니다.
하지만 5.0 / 2.0 연산 결과는
2.5가 됩니다.
이것은 자료형 때문입니다.
정수 / 정수는 그 결과도 정수입니다.
실수 / 실수는 그 결과도 실수입니다.
따라서
5/2 -> 2
5.0/2.0 -> 2.5
라는 결과가 나오게 되는것이죠.
"실수 / 정수 처럼 서로 다른 자료형이면 어떻게 되나요?"
이렇게 서로 다른 두 자료형이 섞이면 내부적으로 문제가 생깁니다.
왜냐하면 C언어를 비롯한 대부분의 프로그래밍 언어는
자료형에 따라서 다른 연산 방법을 사용하고 있기 때문입니다.
그래서 이처럼 서로 다른 자료형이 섞였을 때에는
자동 형 변환 이라는 작업을 수행합니다.
두 자료형을 일치 시켜주기 위한 작업입니다.
이때, 두 데이터의 자료형을 일치시키기 위해
실수를 정수로 바꿀수도 있고,
정수를 실수로 바꿀수도 있을 것입니다.
여기에는 프로그래밍 언어에 따라 다른 규칙들이 적용됩니다.
이에 대해서 설명드리자면 너무 복잡해지고 어렵기 때문에
여러분들은 C언어에서는
정수가 실수로 바뀐다는 것만 기억하시면 됩니다.
따라서,
정수 / 실수 or 실수 / 정수의 경우에는 실수 / 실수로 계산되어
결과값도 실수로 계산되게 됩니다.
그리고 %나머지 연산은 처음보실텐데
말 그대로 정수 / 정수가 몫을 구할 수 있었다면
정수 % 정수를 통해 나머지를 구할 수 있는 연산자입니다.
이때, 실수 % 실수 연산은 당연히 불가능합니다.
아래 코드를 한번 작성해보고
어떻게 이런 결과가 나왔는지 생각해봅시다.
#include<stdio.h>
int main()
{
int a,b,c;
printf("정수 두 개를 입력하시오. ");
scanf("%d %d",&a,&b);
c = a + b;
printf("%d 더하기%d = %d\n",a,b,c);
c = a - b;
printf("%d 빼기%d = %d\n",a,b,c);
c = a * b;
printf("%d 곱하기%d = %d\n",a,b,c);
c = a / b;
printf("%d 나누기%d = %d\n",a,b,c);
c = a % b;
printf("%d 나머지%d = %d\n",a,b,c);
return 0;
}
기능 | 연산자 | 사용 예시) |
1을 더해줌 | ++ | a++; |
1을 빼줌 | -- | a--; |
이제부터는 수학 시간에 배운 적 없는
생소한 연산자들이 나올 겁니다.
위 연산자들은 증감 연산자라고 부르는 연산자입니다.
증감 연산자는 1을 더해주거나 1을 빼주는 연산을 수행합니다.
아래 코드를 한번 봅시다.
#include<stdio.h>
int main()
{
int a = 10;
a + 1;
printf("%d",a);
return 0;
}
연산 결과를 직접 출력하기 전에 코드 결과를 예상해봅시다.
어떤 값이 출력될까요?
11?
위에서도 강조했던 내용입니다.
연산의 결과를 = 대입 연산자를 통해 저장하지 않는다면
연산자를 사용하는 것은 아무 의미가 없습니다.
( printf("%d",a+b); 와 같은 상황이 아니라면말이죠)
만일 11이라는 결과를 출력하고 싶었다면
아래와 같이 코드를 작성해야 합니다.
#include<stdio.h>
int main()
{
int a = 10;
a = a + 1;
printf("%d",a);
return 0;
}
위 코드대로라면
a + 1의 결과를 a에 대입하여
a의 값을 10에서 11로 1 증가시킬 수 있습니다.
이번에는 증감 연산자를 사용한 아래의 코드를 한번 작성해봅시다.
#include<stdio.h>
int main()
{
int a = 10;
int b = 10;
a++;
b--;
printf("%d %d",a,b);
return 0;
}
위 표에서 설명드렸던 것 처럼
정말 정직하게 1을 증가시키고, 1을 감소시키고 있죠?
여기서 대부분
두 가지의 의문점이 생길겁니다.
"증감 연산자는 대입 연산자가 없는데 왜 값이 변하죠?"
"a = a + 1이랑 똑같은 연산인데 왜 굳이 증감 연산자를 사용하죠?"
첫 번째 질문에 대한 답은
증감 연산자에는 이미 대입 연산자가 포함되어 있기 때문입니다.
따라서 a++ 는 a = a + 1과 같은 연산을 할 수 있습니다.
두 번째 질문에 대한 답은
"귀찮기"때문입니다.
프로그래머들은 항상 효율을 추구합니다.
가장 빠른 코드
가장 짧은 코드
가장 메모리를 적게 소비하는 코드
.
.
.
코딩을 계속하시다보면
1을 더하거나 1을 빼는 연산을 굉장히 많이 하시게 될겁니다.
그럴 때마다 a = a + 1을 쓰는 것보다
a++를 사용하는 게 당연히 더 짧고 간편합니다.
증감연산자는 코드를 간단히 표현하기위해
필요한 연산이라고 생각하시면 됩니다.
이번에는 아래의 코드를 한번 작성해 봅시다.
#include<stdio.h>
int main()
{
int a = 10;
printf("%d\n",++a);
printf("%d\n",a);
int b = 10;
printf("%d\n",b++);
printf("%d\n",b);
return 0;
}
증감 연산자는 이처럼
피연산자의 앞이나 뒤에 작성할 수 있습니다.
앞에 쓰냐 뒤에 쓰냐에 따라
전위 증감 연산자
후위 증감 연산자
로 분류됩니다.
이 둘의 차이점은 연산의 타이밍에 있습니다.
먼저 증감하느냐 마지막에 증감하느냐
printf("%d\n",++a);
printf("%d\n",a);
10으로 초기화되어 있던 a는
전위 증감 연산자에 의해 먼저 1이 증가하여 11이 저장된 후 출력됩니다.
그 이후 a를 출력하여도 당연히 a는 11이 출력됩니다.
printf("%d\n",b++);
printf("%d\n",b);
동일하게 10으로 초기화되어 있던 b의 경우
후위 증감 연산자에 의해 마지막에 1이 증가합니다.
따라서 우선 b의 값인 10을 먼저 출력한 뒤, 11이 저장됩니다.
그 이후 b를 출력하면 11이 출력됩니다.
전위와 후위는 사소해보이지만
상황에 따라 코드의 결과를 뒤바꿀 수 있을 만큼 중요하기 때문에
강의에 포함하였습니다.
2. 관계 연산자
관계 연산자는
두 피연산자의 관계를 연산하는 연산자입니다.
여기서 관계란 크거나 작거나
두 피연산자를 비교할 수 있습니다.
기능 | 연산자 | 사용 예시) |
왼쪽보다 오른쪽이 큰 가? | < | c = a < b; |
왼쪽보다 오른쪽이 작은 가? | > | c = a > b; |
왼쪽보다 오른쪽이 같거나 큰 가? | <= | c = a <= b; |
왼쪽보다 오른쪽이 같거나 작은 가? | >= | c = a >= b; |
왼쪽과 오른쪽이 같은 가? | == | c = a == b; |
왼쪽과 오른쪽이 다른 가? | != | c = a != b; |
이러한 관계 연산자들은 결과값으로
거짓일 때는 0
참일 때는 1
을 갖게 됩니다.
예를 들어
5 < 10 의 결과는 1
5 > 10의 결과는 0
입니다.
따라서 이러한 결과 값을 저장하는 변수도
정수형으로 선언되어야 합니다.
0 과 1 모두 정수이기 때문입니다.
#include<stdio.h>
int main()
{
int a, b, c;
printf("비교할 두 정수를 입력하시오. ");
scanf("%d %d",&a,&b);
c = a < b;
printf("%d < %d = %d\n",a,b,c);
c = a <= b;
printf("%d <= %d = %d\n",a,b,c);
c = a > b;
printf("%d > %d = %d\n",a,b,c);
c = a >= b;
printf("%d >= %d = %d\n",a,b,c);
c = a == b;
printf("%d == %d = %d\n",a,b,c);
c = a != b;
printf("%d != %d = %d\n",a,b,c);
return 0;
}
3. 논리 연산자
논리 연산자는
관계 연산자와 거의 함께 사용되는 연산자입니다.
두 개의 참 거짓 관계에 대해서
논리 판단을 통해 참 거짓을 연산합니다.
기능 | 연산자 | 사용 예시) |
그리고 | && | d = (a<b) && (b<c); |
또는 | || | d = (a<b) || (c<b); |
부정 | ! | d = !(c<b); |
이는 이산 수학에 익숙하지 않은
비전공자분들에게는
매우 생소하실 겁니다.
아래 표와 예제 코드를 참고해주세요
연산 | 결과 |
참 && 참 | 참 |
참 && 거짓 | 거짓 |
거짓 && 참 | 거짓 |
거짓 && 거짓 | 거짓 |
&& 그리고 연산자는
양 항이 둘 다 참일 때만 결과를 참으로 만드는 연산자입니다.
그 외는 모두 거짓입니다.
&& 연산이 가장 많이 사용되는 상황은
5 < a < 10 처럼
사잇값인지 구하는 연산에 많이 사용됩니다.
입문자분들이 관계 연산에서 가장 많이
실수하는 부분이
코드를 위 식 그대로 작성을 해버립니다.
b = 5 < a < 10;
처럼 말이죠
하지만 관계 연산자는 양 항을 연산하는 연산자입니다.
따라서
b = ((5 < a) < 10); 순서로 연산이 되어서
결국 a에 어떤 값이 오던
0 또는 1 과 10을 연산해야 되는 오류가 발생됩니다.
따라서 c에는 무조건 1이 저장되어버립니다.
이러한 표현은 && 연산자를 통해
b = (5 < a) && (a < 10);
과 같이 작성해주어야
5보다 크고 10보다 작은 지 구할 수 있습니다.
양 항이 둘 다 참이라면 a는 5보다 크고 10보다 작은 게 되겠지요.
#include<stdio.h>
int main()
{
int a, b;
printf("정수를 입력하시오. ");
scanf("%d",&a);
b = (5 < a) && (a < 10);
printf("%d는 5보다 크고 10보다 작다.(참:1/거짓:0) : %d",a,b);
return 0;
}
연산 | 결과 |
참 || 참 | 참 |
참 || 거짓 | 참 |
거짓 || 참 | 참 |
거짓 || 거짓 | 거짓 |
|| 또는 연산자는
양 항 둘 중에 하나라도 참이면 결과를 참으로 만드는 연산자입니다.
양 항이 둘 다 거짓이라면 당연히 결과도 거짓입니다.
#include<stdio.h>
int main()
{
int a, b;
printf("정수를 입력하시오. ");
scanf("%d",&a);
b = (a < 0) || (10 < a);
printf("%d는음수이거나 10보다 크다.(참:1/거짓:0) : %d",a,b);
return 0;
}
연산 | 결과 |
!참 | 거짓 |
!거짓 | 참 |
! 부정 연산자는
논리 연산자 중 유일하게
단항 연산자입니다.
단항 연산자란
피연산자가 하나인 연산자를 말합니다.
위 식 처럼 ! 연산자 오른쪽에 관계식을 작성합니다.
결과로는 원래 참 거짓 관계를 반대로 바꾸어버리는 연산을 수행합니다.
#include<stdio.h>
int main()
{
printf("!(5<2) = %d\n",!(5<2));
printf("!(5>2) = %d",!(5>2));
return 0;
}
4. 그 외 연산자
그 외 연산자에서는
산술 연산자, 관계 연산자, 논리 연산자 외
유용하게 사용되는 다른 연산자들을 다룹니다.
기능 | 연산자 | 사용 예시) |
a 와 b를 더해서 a에 대입 | += | a += b; |
a 와 b를 빼서 a에 대입 | -= | a -= b; |
a 와 b를 곱해서 a에 대입 | *= | a *= b; |
a 와 b를 나눠서 a에 대입 | /= | a /= b; |
a 와 b를 나눈 나머지를 a에 대입 | %= | a %= b; |
복합 대입 연산자입니다.
복합 대입 연산자도
위에서 다뤘던 증감 연산자를 사용하는 이유와
비슷한 맥락인데
a = a + b; 와 a += b; 는 같은 연산입니다.
이를 간단하게 표현하기 위해 만들어진 식이
복합 대입 연산자 입니다.
연산과 동시에 대입까지 수행하는 연산자입니다.
왼쪽과 오른쪽을 연산하여 왼쪽에 대입한다.
라고 생각하면 이해가 빠릅니다.
이를 복잡하게 응용하면
a *= b + c;
와 같은 식도 가능합니다.
왼쪽과 오른쪽을 더하는 연산이 대입 연산자이기 때문에
이를 일반식으로 바꾸자면
a = a * (b + c);
로 표현할 수 있습니다.
#include<stdio.h>
int main()
{
int a = 10;
a += 5;
printf("%d\n",a);
a -= 3;
printf("%d\n",a);
a *= 2;
printf("%d\n",a);
a /= 3;
printf("%d\n",a);
a %= 3;
printf("%d\n",a);
return 0;
}
기능 | 연산자 | 사용 예시) |
정수로 변환 | (int) | int a = (int)3.14; |
실수로 변환 | (double) | double a = (double)5; |
형 변환 연산자입니다.
형 변환 연산자는 말 그대로
자료형을 변환 시켜주는 연산자로
자료형에 따라 계산 결과가 달라지는
/ 나누기나 % 나머지 연산에서 많이 사용되는 연산자입니다.
아래 코드를 작성해봅시다.
#include<stdio.h>
int main()
{
int a = 5;
int b = 2;
double c = a / b;
printf("%d / %d = %.2lf",a,b,c);
return 0;
}
2.5라는 결과를 출력하고 싶었지만
a와 b가 정수로 선언되어 있는 상황이라면
이러한 연산이 불가능합니다.
이럴 때 형 변환 연산자를 사용한다면
#include<stdio.h>
int main()
{
int a = 5;
int b = 2;
double c = (double)a / (double)b;
printf("%d / %d = %.2lf",a,b,c);
return 0;
}
이렇게 정수를 실수로 변환하여 계산이 가능합니다.
그럼 당연히 / 나누기 연산 결과도
실수가 나오게 됩니다.
"그냥 a, b 변수 자료형을 바꾸면 되지 않나요?"
추후에 알게되겠지만
자료형에 따라서 메모리의 크기가 다릅니다.
이때 정수보다 실수가 메모리를 더 많이 차지하는데
메모리를 많이 차지하는 것은
비효율적인 코드입니다.
뿐만 아니라
자료형에 따라 연산 결과가 달라지는 연산자들이 많이 있기 때문에
정수를 저장할 목적으로 만든 변수를
추후에 실수로 수정했다가
코드를 작성하다보면
연산이 잘못되는 부분들이 생길 수 있습니다.
따라서 정수를 저장할 목적이라면 정수형으로 변수를 만드는 것이
가장 안전합니다.
5. 연산자 우선순위
프로그래밍 언어에는
다양한 연산자가 존재합니다.
물론 제가 설명드린 내용보다 더 많은 종류가 있지만
비전공자분들도 이해하기 쉽게 설명드리기 위해
정말 필요한 내용만 강의했습니다.
연산자 우선순위란
사칙 연산처럼
다양한 연산자들이 섞여있을 때
어떤 연산자를 먼저 연산할지에 대한 규칙입니다.
"같은 종류의 연산자들이 섞여서 사용되면 어떻게 되나요?"
만일 같은 연산자들이 섞여서 사용된다면
일반적으로 왼쪽에서 오른쪽으로 순서대로 연산합니다.
물론 이 부분에도 추가적인 규칙이 있지만
본 강의에서 배운 연산자들은 그렇습니다.
단, 산술 연산자들은
사칙 연산에 의거하여 연산됩니다.
#include<stdio.h>
int main()
{
int a;
a = 5 + 2 * 3;
printf("%d\n",a);
a = 5 < 6 && 3 < 5;
printf("%d\n",a);
a = 5 + 1 < 3 || 3 < 2 + 2;
printf("%d\n",a);
return 0;
}
a = 5 + 2 * 3;
산술 연산자들이 사용되었습니다.
이런 경우에는
사칙 연산에 의해
a = 5 + (2 * 3);
으로 연산되어 연산 결과는 11이 됩니다.
a = 5 < 6 && 3 < 5;
관계 연산자와 논리 연산자가 사용되었습니다.
이런 경우에는
관계 연산자가 논리 연산자보다 우선순위가 높기 때문에
a = (5<6) && (3<5);
으로 연산되어 양 항이 모두 참이므로 연산 결과는 1이 됩니다.
a = 5 + 1 < 3 || 3 < 2 + 2;
산술 관계 논리 연산자가 모두 사용되었습니다.
이런 경우에는
산술 - 관계 - 논리 순으로 우선순위가 높기 때문에
a = ((5+1) < 3) || (3 < (2+2));
으로 연산되어 왼쪽 항은 거짓이나 오른쪽 항이 참이므로 연산 결과는 1이 됩니다.
이렇게 연산자 우선순위라는 개념을 알고있다면
코드를 작성함에 있어서
큰 도움이 될 것입니다.
하지만 그렇다고
연산자 우선순위만 믿고 코드를 위 예제처럼
복잡하게 작성하는 것은 좋지 않습니다.
왜냐하면
일단 가독성이 좋지 않고,
코드를 작성하는 본인도 실수하기가 쉽기 때문입니다.
심할때는 스파게티 소스라고 불리는 좋지 않은 코드 소스가 되어버립니다.
스파게티 소스란,
코드는 정상적으로 작동하지만
너무 복잡하게 얽혀 쉽게 알아볼 수 없는 코드를 일컷는 말입니다.
따라서,
항상 먼저 연산되어야하는 부분은 () 괄호로 묶어
코드를 작성하는 습관이 좋습니다.
오늘은
연산자라는 것을 배워보았습니다.
연산자를 통해
다양한 데이터를 입력받아 원하는 연산을 구현해보며
연산자 사용 방법을 익혀봅시다.
다음 강의에서는
조건문에 대해서 학습해보겠습니다.
'프로그래밍 > C언어' 카테고리의 다른 글
[잼민이도 이해하는 C언어 강의] 8. 반복문 / #while, #for, #do-while (0) | 2022.05.20 |
---|---|
[잼민이도 이해하는 C언어 강의] 7. 조건문 / #if, #else if, #else (0) | 2022.04.18 |
[잼민이도 이해하는 C언어 강의] 5. 입력하기 / #scanf (3) | 2022.04.09 |
[잼민이도 이해하는 C언어 강의] 4. 변수란? / #변수 (1) | 2022.04.08 |
[잼민이도 이해하는 C언어 강의] 3. 자료형과 변환문자 / #자료형, #변환문자 (0) | 2022.04.08 |