1. NOT - true를 false로, false를 true로 바꾼다 2. AND - 양 값이 둘다 참이어야만 참이고 나머지는 거짓 3. OR - 양 값중 하나만 참이어여도 참, 둘다 거짓이면 거짓 4. XOR - 양 값이 서로 달라야 참, 서로 같으면 거짓 5. NAND - AND의 반전 6. NOR - OR의 반전 7. XNOR - XOR의 반전
5,6,7은 2,3,4의 각각 반전이므로 1,2,3,4만 알아보자.
유일하게 양값을 비교하는게 아니라 자신값만 비교하는 단항연산자이다.
그냥 진리값을 반전시키면된다. 간단하지만 강력하다.
특정 명제가 참이 되기위해서는 두개의 조건이 모두 참이 되어야한다.
"대한민국 국민이면서 남자면 군대를 가야한다"라는 명제가 있다면 이는 AND연산으로 묶여있다고 불 수 있다.
특정 명제가 참이 되기위해서는 두개의 조건 중 하나만 참이 되면된다.
"신분증이 있거나 여권이 있으면 신원을 확인할 수 있다"라는 명제가 있다면
이는 OR연산으로 묶여있다고 볼 수 있다.
물론 둘다 참이여도 참이다.
OR중에서 조금 특이한 상황인데 둘중 하나만 참이어야 하는 상황이다.
예를들어 현재 내가 1000원이 있고 소고기와 닭고기, 가스버너가 각각 500원이라고 가정해보자.
이 때 "닭고기만 사거나 소고기만 사야 고기를 먹을 수 있다"라는 명제가 존재한다면
이는 XOR연산으로 묶여있다고 볼 수 있다.
만약 소고기와 닭고기를 동시에 사면 가스버너를 못사서 고기를 어짜피 못먹게 되기 때문이다.
아니면 날걸로 먹던가.
#include <stdio.h>
#include <stdbool.h>
int main() {
bool hasGirlFriend = false;
bool hasMoney, isHandsome;
printf("1이면 여자친구 있는 거임\n0이면 없음\n\n");
hasMoney = true;
isHandsome = false;
hasGirlFriend = hasMoney || isHandsome;
printf("돈이 있지만 못생길 경우 : %d\n",hasGirlFriend);
hasMoney = false;
isHandsome = true;
hasGirlFriend = hasMoney || isHandsome;
printf("잘생겼지만 돈이 없는 경우 : %d\n", hasGirlFriend);
hasMoney = false;
isHandsome = false;
hasGirlFriend = hasMoney || isHandsome;
printf("못생기고 돈이 없는 경우 : %d\n", hasGirlFriend);
return 0;
}
진리값들은 이런식으로 사용할 수 있다. 위는 OR연산을 사용한 예제이다.
C언어에서 논리연산의 경우 XOR을 제외한 NOT과 AND, OR을 제공한다.
비트 단위에서는 XOR까지 제공하지만 논리연산에서는 제공하지 않는다.
만약 XOR이 필요하다면 직접 만들어서 써야한다.
#include <stdio.h>
#include <stdbool.h>
int main() {
bool a = true;
bool b = true;
bool result = (a != b) && (a || b);
printf("%d\n", result);
return 0;
}
그리고 추가적으로 정수 승격(Integer Promotion)이라는 개념도 배우게 될 것이다.
이 개념을 알려주는 경우도 있고 건너 뛰는 경우도 있지만 기왕이면 알고가는게 좋을 것이다.
우리는 먼저 타입군을 3가지를 정하자, 정수와 양의 정수와 실수로 타입군을 나눈다.
이 때의 경우 다른 타입군끼리의 연산이 어떠할지, 혹은 같은 타입군 끼리의 연산이 어떠할지를 알아보자.
먼저 다른 타입군끼리의 연산에 대해서 한번 알아보도록 하자.
다른 타입끼리의 연산이 어떻게 돌아가게 될지를 아는건 아주 중요하다.
사실 알고 넘어가는게 좋다.
왜냐하면 어떻게 서로 다른 녀석들을 통합시킬지에 대한 논의가 필요하기 떄문이다.
경우의 수는 3종류가 존재한다.
각각의 종류가 어떻게 진행될지 알아보도록 하자.
여기서 여러분들이 알아야할 놀라운 사실이 하나 있다...
바로 C언어는 사실은 양의 정수와 정수를 데이터적으로는 구별하지 않는다는 사실이다.
이 이야기를 하면 놀래는 사람들이 꽤 있다.
엄밀히 말하면 아예 구별을 하지 않는다는 것은 아니고 데이터는 동일하게 저장된다는 것이다.
이를 한번 우리가 확인해보도록 하자.
#include <stdio.h>
int main() {
int a = 10;
unsigned int b = 2147483647;
int c = 2147483647;
printf("%d\n", b);
printf("%d\n", c);
printf("%d\n", b + a);
printf("%d\n", c + a);
printf("%d\n", b - a);
printf("%d\n", c - a);
return 0;
}
위와 같은 코드를 보도록하자.
b와 c는 int가 표현할수 있는 최대의 수를 가지고 있다.
(2147483647은 int가 표현할 수 있는 최대의 수이다.)
여기서 1만 더해도 b의 경우는 표현범위 안이라서 상관없지만
c는 표현 범위를 넘어가므로 음수가 될 것이고 예측 못하는 수...가 된다고 아는 사람들이 있다.
하지만 실상은 이와 다르게 명백하게 예측 할 수 있다.
일단 위의 코드를 실행해보자.
그러나 왠걸?
둘은 완전하게 동일한 값을 출력하는걸 알 수 있다.
이는 %d때문 아니냐?라고 반문할 수 있다.
뭐 맞는말이긴한데 문제는 저장된 데이터자체가 같다는 뜻이다.
#include <stdio.h>
int main() {
int a = 10;
unsigned int b = 2147483647;
int c = 2147483647;
printf("%u\n", b);
printf("%u\n", c);
printf("%u\n", b + a);
printf("%u\n", c + a);
printf("%u\n", b - a);
printf("%u\n", c - a);
return 0;
}
그러면 이러한 코드로 바꿔보자.
크게 바뀐건 없고 출력을 양의 정수형인 %u로 바꾸어 보았다.
이제는 int형을 넘은 범위를 출력을 하는 것을 확인할 수 있다.
그런데 여기서도 둘의 데이터는 완전하게 동일한걸 알 수 있다.
이를통해서 우리가 알 수 있는것은 int와 unsigned int는 사실 내부적으로 unsigned int형의 연산으로 동작하게 된다는 것이다.
그리고 출력 형식에 맞춰서 거기에 맞는 값을 보여주게 된다.
출력형식을 정수형(%d)로 하면 해당 타입을 int의 범위인 -2147483648에서 2147483647로 표현하고
출력형식을 양의 정수형(%u)로 하면 해당 타입을 unsigned int의 범위인 0에서 4294967295로 표현하게 된다.
사실 여러분은 이게 좀 신기할 것이다. 어떻게 이런게 가능할까? 음수가 있는데도?
이는 음수는 사실 보수화 연산을 통해서 저장된다고 했는데 이게 절묘하게 맞아 떨어지게 된다.
이 사실을 잠깐 이해하고 넘어가자.
컴퓨터에서 왜 음수 표시를 2의 보수화를 채택을 해서 했는지를 알 수 있고
태초에 프로그래밍 언어를 설계한 사람들의 천재성을 여기서 엿볼 수 있다.
unsigned int a = -10; //4294967286
C언어에서는 위 같은 코드가 가능하다.
정말로 -10이 저장되는건 아니고 4294967286이 저장되는데
실질적으로 -10과 4294967286는 4바이트 정수(int, unsigned int 등)끼리의 처리에서는
#include <stdio.h>
int main() {
int a = 5;
int b = -10;
unsigned int c = -10;
unsigned int d = 4294967286;
printf("%u\n", a + b);
printf("%u\n", a + c);
printf("%u\n", a + d);
printf("%d\n", a + b);
printf("%d\n", a + c);
printf("%d\n", a + d);
return 0;
}
그럼 위와 같은 코드를 예상해보자.
b, c, d는 이론상으로는 셋다 같은 숫자가 저장되어 있다.
심지어 b는 int형이다. 이 경우 셋의 결과가 같을까?
셋다 결과가 같다.
이로서 프로그래밍에서 정수끼리의 연산에서 unsigned와 signed는 연산에서
차이가 없이 똑같은 로직이 적용된다는걸 알 수 있다.
물론 그렇다고 해서 unsigned와 signed가 동일하다는 이야기는 아니다.
둘은 사칙연산에서는 동일하게 작동하지만 그 외의 상황에서는 다르게 작동하는 경우도 있다.
가령 아래의 예를 들어보자.
#include <stdio.h>
int main() {
for (int a = 10; a >= 0; a--) {
printf("%d\n", a);
}
return 0;
}
정상적인 for문이다. 이러한 코드는 아무문제 없는 코드다.
굳이 해석하자면 a는 10부터 0이 될때까지 a를 출력하는 코드다.
우리 생각대로 0에서 정지하는걸 확인할 수 있다.
#include <stdio.h>
int main() {
for (unsigned int a = 10; a >= 0; a--) {
printf("%d\n", a);
}
return 0;
}
하지만 이 코드는 어떻게 될까?
unsigned int는 음수가 되지 않는다.
따라서 위 코드는 영원히 무한루프를 돌리게된다.
따라서 unsigned int와 int는 연산때는 동일하게 작용하지만
내부적으로 unsigned int는 뚜렷하게 양수로 인식되고 있음을 알 수 있다.
사실 이문제는 아주 중요한 문제이다.
for문에 unsigned형을 쓰게 되는건 아주 위험하다.
그렇기 때문에 만약 for문에 unsigned형을 써야한다면 int나 long long형으로 치환한 변수를 쓰는게 좋다.
정수와 실수의 경우 정수를 실수로 바꾼다.
해당 정수를 다른 피연산 타입의 실수로 변환한다.
그리고 실수와 실수 연산으로 바뀌게된다.
가령 int와 float의 연산이면 int를 먼저 float으로 변환하고
그 다음 float과 float의 연산을 수행하게 된다.
물론 int와 double의 연산이면 int를 먼저 double로 변환한다.
사실 크게 특이한건 없으므로 이건 넘어가겠다.
이 부분역시 양의 정수와 정수가 구별된은 부분이다.
양의 정수는 음수가 없기 때문에 음수 타입으로 지정해봤자 무조건 양수로 인식한다.
#include <stdio.h>
int main() {
int a = -10;
unsigned int b = -10;
float fa = 1.5f + a;
float fb = 1.5f + b;
printf("%f\n", fa);
printf("%f\n", fb);
return 0;
}
#include <stdio.h>
#pragma warning(disable:4996)
int main() {
int a = 10;
printf("%d\n", +a);
printf("%d\n", -a);
printf("%d\n", -(-a));
return 0;
}
부호 연산자는 숫자의 부호를 결정한다.
당연한 이야기이지만 일반적으로 +부호는 큰 의미는 없다.
만약 음수 부호를 두번 하고 싶다면 수학 수식처럼 괄호를 감싸줘야한다.
양수 부호는 실제로는 거의? 아예? 쓰이지 않는다고 보면된다.
산술 연산자(- , +, *, /, %) - 이항 연산자, 사칙 연산을 담당
#include <stdio.h>
#pragma warning(disable:4996)
int main() {
int a = 10;
int b = 3;
int sum = a + b;
int sub = a - b;
int mul = a * b;
int div = a / b;
int rem = a % b;
printf("%d + %d = %d\n", a, b, sum);
printf("%d - %d = %d\n", a, b, sub);
printf("%d * %d = %d\n", a, b, mul);
printf("%d / %d = %d\n", a, b, div);
printf("%d %% %d = %d\n", a, b, rem);
return 0;
}
사칙연산자는 우리가 아는 그 사칙 연산자 맞다.
다만 곱하기와 나누기는 ×와 ÷가 아닌 *와 /이다.
이 기호를 각각 애스터리스크(Asterisk)와 슬래시(Slash)라고 부른다.
sum은 더하기, sub은 빼기, mul은 곱하기, div는 나누기를 하는 변수이다.
그러면 결과를 보도록 하자.
결과를 보면 알겠지만 하나가 예상대로 되지 않았다.
바로 나누기이다.
원래라면 3.333333이 되야하겠지만 그렇게 되지 않았다.
이제 여러분은 여기에 대해서 익숙해져야한다.
그 이유에 대해서 설명하기 전에 이야기를 하자면
쉽게 이야기해서 일반적인 나누기는
결과적으로 초등학생때 쓰는 나누기 연산이라고 생각하면된다.
초등학교때 나누면 몫과 나머지를 남기는 나누기 연산을 한다.
컴퓨터의 int끼리 연산도 결과적으로는 그렇게 된다는 것이다.
그리고 나머지를 알 수 있는 연산까지 제공되어 있다.
%기호의 연산자이다, 이를 mod연산이라고 많이들 부른다.
이 연산을 시행하면 결과를 나눈 나머지 값을 얻을 수 있다.
그럼 여러분이 원하는게
3.3333이라고 하고 그 결과를 출력하고 싶다면 어디를 손봐야하는지 알려주겠다.
일단 %d도 정수형, div역시 정수형이다.
그렇기 때문에 설사 값이 실수로 나왔다고 할지라도 정수로 나올 수 밖에 없다.
출력을 실수형으로 해야하니 우리는 이를 실수형으로 바꿔준다.
#include <stdio.h>
#pragma warning(disable:4996)
int main() {
int a = 10;
int b = 3;
int sum = a + b;
int sub = a - b;
int mul = a * b;
float div = a / b;
printf("%d + %d = %d\n", a, b, sum);
printf("%d - %d = %d\n", a, b, sub);
printf("%d * %d = %d\n", a, b, mul);
printf("%d / %d = %f\n", a, b, div);
return 0;
}
#include <stdio.h>
#pragma warning(disable:4996)
int main() {
int a = 10;
int b = 3;
int sum = a + b;
int sub = a - b;
int mul = a * b;
float div = (float)a / b;
printf("%d + %d = %d\n", a, b, sum);
printf("%d - %d = %d\n", a, b, sub);
printf("%d * %d = %d\n", a, b, mul);
printf("%d / %d = %f\n", a, b, div);
return 0;
}
캐스팅 연산자는 강제로 타입을 바꿔버린다.
항상... 강제라는 것에 주의해야하지만 지금은 고민할 필요 없다.
캐스팅 연산자는 현재 사용할 수 있는 어떠한 타입도 다 가져다가 사용할 수 있다.
이를 변수 앞에 붙혀주면 강제로 타입이 변형된다.
위의 경우 a는 int지만 float으로 강제로 바꾸었다.
이제 a/b는 더이상 정수, 정수의 연산이 아니라 실수와 정수의 연산이다.
따라서 결과는 float이 된다.
드디어 우리가 원하는 값을 얻을 수 있게 되었다.
다른 타입 끼리의 변환과 거기에 대한 이해는 조금 내용이 많다.
그래서 부록으로 언급할 테니 여기서는 여기까지만 다루도록 하겠다.
비트 연산자(&, |, ^, <<, >>, ~) - 대체적으로 이항 연산자, 비트 단위로 연산을 함, 단 ~는 유일한 단항 연산자
& - AND연산을 비트 단위로 시행한다. |= OR연산을 비트 단위로 시행한다. ^ = XOR연산을 비트 단위로 시행한다. << = 비트를 왼쪽으로 한칸씩 민다(Left Shift). 새로운 칸에는 0을 채운다. >> = 비트를 오른쪽으로 한칸씩 민다(Right Shift). 새로운 칸에는 0을 채운다. ~ = NOT연산을 비트 단위로 시행한다. 결과적으로 보수가 된다.
#include <stdio.h>
#pragma warning(disable:4996)
int main() {
int a;
a = 10;
printf("= 10 결과 : %d\n", a);
a += 3;
printf("+= 3 결과 : %d\n", a);
a -= 2;
printf("-= 2 결과 : %d\n", a);
a *= 4;
printf("*= 4 결과 : %d\n", a);
a /= 2;
printf("/= 2 결과 : %d\n", a);
a %= 7;
printf("%%= 7 결과 : %d\n", a);
return 0;
}
AND연산 - C언어에서는 &&로 표현됨, 두 조건이 모두 참이어야 참이된다. ex) 밥을 많이 먹고 일찍 자면 키가 큰다. 여기서 밥만 많이먹으면 돼지가 되고, 일찍 자기만하면 멸치가 된다. 따라서 키가 크려면 밥을 많이 먹는 조건도 참이고 일찍 자야되는 조건도 참이어야 한다.
OR연산 - C언어에서는 ||로 표현됨, 두 조건 중 하나라도 참이면 참이된다. ex) 잘생기거나 돈이 많으면 여자친구가 생긴다. 여기서 잘생기지 않아도 돈이 많으면 여자친구가 생기고, 돈이 없어도 잘생기면 여자친구가 생긴다. 그래서 난 여자친구가 없다.
NOT연산 - C언어에서는 !로 표현됨, 단항 연산자이며 진리값을 반대로 바꾼다.
#include <stdio.h>
#include <stdbool.h>
#pragma warning(disable:4996)
int main() {
bool hasGirlFriend = false;
bool hasMoney, isHandsome;
printf("1이면 여자친구 있는 거임\n0이면 없음\n\n");
hasMoney = true;
isHandsome = false;
hasGirlFriend = hasMoney || isHandsome;
printf("돈이 있지만 못생길 경우 : %d\n",hasGirlFriend);
hasMoney = false;
isHandsome = true;
hasGirlFriend = hasMoney || isHandsome;
printf("잘생겼지만 돈이 없는 경우 : %d\n", hasGirlFriend);
hasMoney = false;
isHandsome = false;
hasGirlFriend = hasMoney || isHandsome;
printf("못생기고 돈이 없는 경우 : %d\n", hasGirlFriend);
return 0;
}
코드를 보면 bool을 사용하기 위해서 stdbool.h를 가져온것을 볼 수 있다.
bool값은 화면에 출력하면 true, false로 출력되지 않고 1, 0으로 출력된다.
즉 1이면 true고 0이면 false이다.
위의 예제는 or연산자를 사용한 예제이다.
보다시피 정직한 결과를 얻을 수 있다.
and와 or은 이항 연산자로 두 조건이 모두 참인걸 확인하고 싶다면 and를,
두 연산자중 하나만 참인걸 알고 싶다면 or을 사용하면 된다.
#include <stdio.h>
#include <stdbool.h>
#pragma warning(disable:4996)
int main() {
bool b = true;
int num = 10;
printf("bool을 뒤집었을 경우 : %d\n", !b);
printf("int를 뒤집었을 경우 : %d\n", !num);
return 0;
}
위의 예제는 !연산자를 사용한 예제이다.
그런데 이 녀석은 조금 조심해서 사용해야하는데,
논리 연산자는 결과값이 항상 true와 false, 즉 1과 0으로 나온다.
C언어에서는 0을 제외한 모든 값을 참으로 간주한다.
따라서 10이라는 숫자도 참이라고 간주해서 !를 먹이면 0이 된다.
비교 연산자(==, !=, <=, >=, <, >) - 이항 연산자, 둘의 동등성을 비교
#include <stdio.h>
#include <stdbool.h>
#pragma warning(disable:4996)
int main() {
int a = 10;
int b = 10;
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);
printf("a <= b : %d\n", a <= b);
return 0;
}
아까 =는 대입 연산자라고 했었다.
그러면 비교를 하려면 다른 연산자를 사용해야한다.
바로 == 이다. 그 외의 다른 연산자는 여러분이 생각한 그대로의 효과를 가지고 있다.
결과를 보면 똑똑한 여러분은 이해할 수 있겠지만 그래도 다시 한번 이야기하도록 하자.
== - 양쪽의 값이 같으면 참이다. 다만 나중에 나올 문자열을 이 연산자로 비교하면 절대 안된다. != - 양쪽의 값이 다르면 참이다. 마찬가지로 나중에 나올 문자열을 이 연산자로 비교하면 절대 안된다. > - 왼쪽이 오른쪽 보다 크면 참이다. >= - 왼쪽이 오른쪽 보다 크거나 같으면 참이다. < - 왼쪽이 오른쪽 보다 작으면 참이다. <= - 왼쪽이 오른쪽 보다 작거나 같으면 참이다.
양변이 같은지, 작은지, 큰지를 비교할 수 있다.
다만 주의 해야할점은 아직까지 배우진 않았지만 문자열을 이 연산자로 비교하면안된다.
아직 문자열을 배우지 않았으므로 이렇게 짧막하게 이야기 하고 넘어가겠다.
이까지 보다 보면 알겠지만 사실 <와 >는 취향차이이다.
A>B로 쓰는 사람이 있고 B<A로 쓰는 사람이 있다.
그래서 코드를 보다보면 일관되게 하나로만 쓰는 사람이 많다.
그리고 A!=B라는 코드는 !(A==B)와 동일하다.
그래서 이 녀석도 취향을 좀 타는것 같다.
조건 연산자((<조건>? <조건이 참일 때>: <조건이 거짓일 때>)) - 삼항 연산자, 조건부로 데이터를 출력
sizeof(<타입 혹은 변수 혹은 값>) - 단항연산자, 해당 피연산자가 시스템에서 몇 바이트인지 출력
#include <stdio.h>
int main() {
int a = 10;
printf("%d\n", sizeof(int));
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(10));
return 0;
}
C언어에서는 특정 변수 혹은 타입 혹은 값의 크기가 얼마인지 확인할 수 있는 변수가 있다.
바로 sizeof연산자이다.
해당 연산자는 타입으로 사용할 경우(sizeof(int)) 해당 타입이 시스템에서 몇 바이트인지 알려준다.
그리고 변수 이름이나 값으로 할 경우에도 해당 변수나 값이 현재 시스템에서 몇 바이트에
저장이 되고 있는지를 확인할 수 있다.
사실 이런 확인차 쓰는 용도보다 더 실용적으로 사용하는 상황이 있긴 하지만
이는 그 때가서 확인하도록 하자.
#include <stdio.h>
#include <stdbool.h>
#pragma warning(disable:4996)
int main() {
bool a, b, c, d, e, f;
a = b = c = d = e = f = 2;
printf("%d\n", a++ + b-- * c / (d + e));
return 0;
}
위와 같은 식이 있다고 가정해보자
과연 결과를 알 수 있을까?
처음 보는 사람은 이해를 못할 거다.
사실 복잡해지면 필자도 잘 모른다.
세세한 규칙이 있긴한데 필자도 다 모르기 때문에 아는것만 이야기하려고한다.
연산자 우선순위
1- 대부분의 단항 연산자 2 - *, /, % 3 - +, - 4 - 대부분의 이항 연산자 5 - 관계 연산자(부등호가 있는 식) 6 - 관계 연산자(부등호가 없는 식) 7 - 비트 연산자 8 - && 9 - || 10 - 삼항 연산자 11 - 대입 연산자
※ 같은 등급의 연산자는 왼쪽에서 오른쪽으로 우선순위가 결정된다. 그리고 괄호는 항상 우선순위가 앞선다. 그리고 대입 연산자와 삼항 연산자는 오른쪽에서 왼쪽으로 우선순위가 결정된다. 이건 단항 연산자들도 마찬가지