-
[C] 연산자② - 관계와 논리, 조건과 비트연산자C 2023. 4. 1. 18:57
관계와 논리, 조건과 비트연산자
관계 연산자(ralational operator)
- 두 피연산자의 크기를 비교하기 위한 연산자
- 비교 결과가 참이면 1, 거짓이면 0
- 관계연산자의 종류와 사용
연산자 연산식 의미 예제 연산(결과)값 > x > y x가 y 보다 큰가? 3 > 5 0 (거짓) >= x >= y x가 y 보다 크거나 같은가? 5 - 4 >= 0 1(참) < x < y x가 y 보다 작은가? 'a' < 'b' 1(참) <= x <= y x가 y 보다 작거나 같은가? 3.43 <= 5.862 1(참) != x != y x와 y가 다른가? 5 - 4 != 3 / 2 0 (거짓) == x == y x와 y 가 같은가? '%' == 'A' 0 (거짓) #include <stdio.h> int main(void) { int speed = 80; printf("60 <= speed : %d\n", (60 <= speed)); printf("60 > spee : %d\n\n", (60 > speed)); printf("'a' > 'b' : %d\n", ('a' > 'b')); printf("'Z' <= 'a' : %d\n\n", ('Z' <= 'b')); printf("4 == 4.0 : %d\n", (4 == 4.0)); printf("4.0F != 4.0 : %d", (4.0F != 4.0)); return 0; }
60 <= speed : 1 60 > spee : 0 'a' > 'b' : 0 'Z' <= 'a' : 1 4 == 4.0 : 1 4.0F != 4.0 : 0
논리 연산자(logical operator)
- 세 가지의 논리연산자 &&, ||, !을 제공 and, or, not의 논리연산
- 결과가 참이면 1, 거짓이면 0을 반환
- C 언어에서 참과 거짓의 논리형은 따로 없음
- 0, 0.0, 'w0'은 거짓을 의미
- 0이 아닌 모든 정수와 실수, 그리고 널(null) 문자 '\0'가 아닌 모든 문자와 문자열은 모두 참을 의미
- &&
- 두 피연산자가 모두 참(0이 아니여야)인면 결과가 1(참)이며 나머지 경우는 모두 0
- ||
- 두 피연산자 중에서 하나만 참(0이 아니여야)이면 1이고, 모두 0(거짓)이면 0
- !
- 단항연산자로 피연산자가 0이면 결과는 1이고 참(0이 아닌 값)이면 결과는 0
논리연산자의 연산 결과
x y x && y x || y !x 0 (거짓) 0 (거짓) 0 0 1 0 (거짓) Ø (0이 아닌 값) 0 1 1 Ø (0이 아닌 값) 0 (거짓) 0 1 0 Ø (0이 아닌 값) Ø (0이 아닌 값) 1 1 0 21 && 3 : 1 !2 && 'a' : 0 3>4 && 4>=2 : 0 1 || '\0' : 1 2 >=1 || 3<=0 : 1 0.0 || 2-2 : 0 !0 : 1
- 단축 평가(short-circuit evaluation)
- 논리연산자 &&와 ||
- 피연산자 두 개 중에서 왼쪽 피연산자만으로 논리연산 결과가 결정된다면 오른쪽 피연산자는 평가하지 않음.
- 예를 들어 (x && y) 연산식
- x의 값이 0(거짓)이라면 y의 값을 평가하지 않고 연산(x&&y) 결과는 0
- x || y 수식 : x가 0이 아니(참)라면 더 이상 y의 값을 평가하지 않고 연산식(x||y)는 1이라고 평가
- 논리연산자 &&와 ||
&&의 왼쪽 (a < b)가 0이므로 오른쪽 연산을 하지 않고 0으로 판별되며, m은 변하지 않음. ||의 왼쪽 (a < b)가 1이므로 오른쪽 연산을 하지 않고 1로 판별되며, m은 변하지 않음. #include <stdio.h> int main(void) { int a = 10, b = 5, m = 1; int result; result = (a < b) && (m++ == 1); printf("m=%d result=%d\n", m, result); result = (a < b) || (--m == 0); printf("m=%d result=%d\n", m, result); return 0; }
m=1 result=0 m=0 result=1
조건 연산자(conditional expression)
- 연산자 ? :
- 조건에 따라 주어진 피연산자가 결과값이 되는 삼항연산자
- 즉 연산식 ( x ? a : b )에서 피연산자는 x, a, b 세 개
- 피연산자인 x가 참이면(0이 아니면) 결과는 a이며, x가 0이면(거짓) 결과는 b
max = (a > b) ? a : b; : 최대값 반환 조건연산 max = (a < b) ? b : a; : 최대값 반환 조건연산 min = (a > b) ? b : a; : 최소값 반환 조건연산 min = (a < b) ? a : b; : 최소값 반환 조건연산 absolute = (a > 0) ? a : -a; : 절대값 반환 조건연산 absolute = (a < 0) ? -a : a; : 절대값 반환 조건연산
조건 삼항연산자의 두 번째와 세 번째 피연산자는 문장도 가능 조건 삼항연산자의 두 번째와 세 번째 피연산자는 문자열을 비롯하여 모든 자료형도 가능 #include <stdio.h> #define _CRT_SECURE_NO_WARNINGS int main(void) { int a = 0, b = 0; printf("두 정수 입력>> "); scanf("%d%d", &a, &b); printf("최대값: %d ", (a > b) ? a : b); printf("최소값: %d\n", (a < b) ? a : b); printf("절대값: %d ", (a > 0) ? a : -a); printf("절대값: %d\n", (b > 0) ? b : -b); ((a % 2) == 0) ? printf("짝수 ") : printf("홀수 "); printf("%s", ((b % 2) == 0) ? "짝수" : "홀수"); return 0; }
두 정수 입력>> 8 -9 최대값: 8 최소값: -9 절대값: 8 절대값: 9 짝수 홀수
비트 연산자
- 정수의 비트 중심(bitwise) 연산자를 제공
- 비트 논리연산자와 이동연산자가 제공
- 비트 논리 연산자
- 피연산자 정수값을 비트 단위로 논리 연산을 수행하는 연산자
- &, |, ^, ~ 4가지
- 연산자 ~
- ~5와 같이 연산자 ~가 피연산자인 5 앞에 위치하는 전위인 단항 연산자
- 나머지는 모두 (3 | 4)처럼 피연산자가 두 개인 이항 연산자
- 피연산자의 자료형은 정수형에 해당하는 char, int, long, long long
- 각 피연산자를 int 형으로 변환하여 연산하며 결과도 int 형
- 연산자 ~
연산자 연산자 이름 사용 의미 & 비트 AND op1 & op2 비트가 모두 1이면 결과는 1, 아니면 0 | 비트 OR op1 | op2 비트가 적어도 하나 1이면 결과는 1, 아니면 0 ^ 비트 배타적 OR(XOR) op1 ^ op2 비트가 서로 다르면 결과는 1, 같으면 0 ~ 비트 NOT(Negation) 또는 보수(complement) ~op1 비트가 0이면 결과는 1, 0이면 1 - 보수 연산자(bitwise complement operator)~
- 각 비트에서 0은 1, 1은 0이 결과
피연산자 보수 연산 수 비트표현(2진수) 보수 연산 결과 10진수 1 00000000 00000000 00000000 00000001 11111111 11111111 11111111 11111110 ~1 = - 2 4 00000000 00000000 00000000 00000100 11111111 11111111 11111111 11111011 ~4 = -5 - 컴퓨터에서 정수의 음수 표현 방법
- 보수를 이용하는 방법을 사용
- 즉 양수 정수 a에서 음수인 -a의 비트 표현은 2의 보수 표현인 (~a + 1)
- 즉 -1은 ((~1)+1)로, 정수 -1을 비트로 표현하면 32비트가 모두 1인 정수
- 비트 논리 연산식 x & -1
- 정수 x를 -1로 논리 and 연산을 수행하는 식으로 결과는 x
- 비트 논리 연산식 x | 0
- 정수 x를 0으로 논리 or 연산을 수행하는 식으로 결과는 x
연산식 설명 연산값 연산식 설명 연산값 1 & 2 0001 & 0010 0 1 & 3 0001 & 0011 1 3 | 4 0011 | 0100 7 3 | 5 0011 | 0101 7 3 ^ 4 0011 ^ 0100 7 3 ^ 5 0011 ^ 0101 6 ~2 -2 == ~2 + 1 -3 ~3 -3 == ~3 + 1 -4 비트 이동 연산자(shift operator) >>, <<
- 연산자의 방향인 왼쪽이나 오른쪽으로, 비트 단위로 줄줄이 이동시키는 연산자
- 실제 연산에서는 int 형의 크기인 32비트의 비트에서 연산을 수행
연산자 이름 사용 연산 방법 새로 채워지는 비트 >> right shift op1>>op2 op1 오른쪽으로 op2 비트만큼 이동 가장 왼쪽 비트인 부호 비트는 원래의 부호 bit로 채움 << left shift op1<<op2 op1 왼쪽으로 op2 비트만큼 이동 가장 오른쪽 비트를 모두 0으로 채움 #include <stdio.h> int main(void) { int x = 16391; printf("%6d → %08x\n", x, x); # 정수 16391은 십육진수로 4007 # 정수 >> n은 정수를 n번 2로 나눈 효과 printf("x >> 1 → %d, %08x\n", x >> 1, x >> 1); printf("x >> 2 → %d, %08x\n", x >> 2, x >> 2); printf("x >> 2 → %d, %08x\n", x >> 3, x >> 3); # 정수 << n은 정수를 n번 2로 나눈 효과 printf("x << 2 → %d, %08x\n", x >> 2, x >> 2); printf("x << 2 → %d, %08x\n", x >> 3, x >> 3); return 0; }
16391 → 00004007 x >> 1 → 8195, 00002003 x >> 2 → 4097, 00001001 x >> 2 → 2048, 00000800 x << 2 → 4097, 00001001 x << 2 → 2048, 00000800
- 왼쪽 이동연산자에 의해 최상위 부호 비트가 0에서 1로, 1에서 0으로 바뀔 수 있으므로 왼쪽이 이동연산자에 의해 항상 2배씩 커지지는 않음.
- 음수 홀수에 대한 오른쪽 이동연산자 >>도 -1을 한 짝수를 2로 나눈 결과
연산식 설명 연산값 연산식 설명 연산값 15 << 1 0000 1111 << 1
0001 111030 15 << 2 0000 1111 << 2
0011 110060 0x30000000 << 2 최상위 4비트 :
0011 0... << 2
1100 0...- 1073741824
0XC0000000-30 << 2 음수로 4배 커짐 -120 -30 >> 1 짝수이면 2로 나누기 -15 -30 >> 2 한 비트 이동마다. 음수 홀수는
-(a+1)한 수를 2로 나누기-8 LAB 표준입력으로 받은 두 정수의 비트 연산 수행 출력
- 표준입력으로 받은 두 정수의 6개 비트 연산을 수행하여 결과를 출력하는 프로그램
- 비트 연산은 &, |, ^, ~, >>, << 연산을 수행
- 단항 연산은 첫 번째 변수에 대한 연산 수행
- 결과
- 비트 연산이 가능한 두 정수를 입력하세요
- 40 3
- 40 & 3 → 0
- 40 | 3 → 43
- 40 ^ 3 → 43
- ~ 40 → -41
- 40 >> 3 → 5
- 40 << 3 → 320
형변환 연산자와 연산자 우선순위
내림변환과 올림변환
- 자료형 변환은 크기 자료형의 범주 변화에 따른 구분
- 올림변환
- 작은 범주의 자료형(int)에서 보다 큰 범주인 형(double)으로의 형변환
- 올림변환은 형 넓히기라고도 부름
- 올림변환은 정보의 손실이 없으므로 컴파일러에 의해 자동으로 수행 가능
- 컴파일러가 자동으로 수행하는 형변환 : 묵시적 형변환(implicit type conversion)
- 작은 범주의 자료형(int)에서 보다 큰 범주인 형(double)으로의 형변환
# 다양한 올림변환의 예 'a' + 2 → int + int # 문자 'a'의 아스키 코드값의 int로 변환 3 * 4.1F → float * float 4.45F / 3.81 → double / double 9.34 - 2 → double - double 3 * 4.28 → double * double
- 내림변환
- 큰 범주의 자료형(double)에서 보다 작은 범주인 형(int)으로의 형변환
- 대입연산 int a = 3.4에서 내림변환이 필요
- 컴파일러가 스스로 시행하는 무시적 내림변환의 경우 정보의 손실이 일어날 수 있으므로 경고를 발생
- 프로그래머의 명시적 형변환이 필요
형변환 연산자
- 명시적 형변환(explicit type conversion)
- 형변환 연산자 '(type) 피연산자'는 뒤에 나오는 피연산자의 값을 괄호에서 지정한 자료형으로 변환하는 연산자
- 내림변환에서는 형변환 연산자(type cast)를 사용하여 내림변환을 직접 수행
# 다양한 형변환연산 예 (int) 'A' → 65 (int) 3.14 → 3 (double) 9 → 9.0 (double) 3.4F → 3.4 (double) 7 / 2 → 3.5
- 올림변환
- 상수나 변수의 정수값을 실수로 변환하려면 올림변환을 사용
- 연산식 (double) 7의 결과는 7.0
- 내림변환
- 실수의 소수부분을 없애고 정수로 사용하려면 내림변환을 사용
- (int) 3.8의 결과는 3
- 단항연산자인 형변환 연산자는 모든 이항연산자보다 먼저 계산
# 다양한 형변환 연산 예 (int) 3.8 + 5.7 → 8.7 3.8 + (int) 5.7 → 8.7 (int) 3.8 + (int) 5.7 → 8 (int) (3.8 + 5.7) → 9 7 / 2 → 3 7.0 / 2 → 3.5 7 / (double) 2 → 3.5 (double) (7 / 2) → 3.0
연산자 sizeof
- 연산값 또는 자료형의 저장장소의 크기를 구하는 연산자
- 결과값은 바이트 단위의 정수
- 피연산자가 int와 같은 자료형인 경우 반드시 괄호를 사용
- 피연산자가 상수나 변수 또는 연산식이면 괄호는 생략 가능
sizeof (int) → 결과는 4 sizeof (3.14) → 결과는 8 sizeof a → 결과는 2 (short a;)
콤마연산자 ,
- 콤마연산자 ,는 왼쪽과 오른쪽 연산식을 각각 순차적으로 계산
- 결과값은 가장 오른쪽에서 수행한 연산의 결과
- 연산식 2, 4의 결과값은 4
- 또한 3 + 4, 2 * 5의 결과값은 10
- 콤마연산자가 연속으로 나열된 식에서는 마지막에 수행된 가장 오른쪽 연산식의 결과가 전체 식의 결과값
exp1 , exp2 → 결과값은 나중에 계산된 exp2의 결과값이다. 3 + 4, 5 - 10 → 결과는 -5 3 + 4, 5 - 10, 2 * 3 → 결과는 6
연산자 sizeof 와 콤마연산자 , 활용
#include <stdio.h> int main(void) { int a = 100, b = 50, c; printf("%d ", sizeof(short)); # 연산자 sizeof (자료형)에서 괄호는 필수 printf("%d ", sizeof a); printf("%d ", sizeof 3.5F); printf("%d\n", sizeof 3.14); c = ++a, b++; # (c=++a), (b++); 이므로 변수 a, b, c에는 각각 101, 51, 101 printf("%d %d %d\n", a, b, c); c = (3 + a, b * 2); # 변수 c에 저장되는 값은 b * 2 이므로 102 printf("%d %d %d\n", a, b, c); return 0; }
2 4 4 8 101 51 101 101 51 102
복잡한 표현식의 계산
- 연산자 우선순위와 결합성
- 규칙 3개를 적용
- 첫 번째 규칙은 괄호가 있으면 먼저 계산
- 괄호 우선 규칙: '괄호가 있으면 먼저 계산한다'라는 규칙
- 두 번째 규칙으로 연산의 우선순위(priority)
- 즉 '곱하기와 나누기는 더하기와 빼기보다 먼저 계산한다.'라는 규칙
- 세 번째 규칙은 동일한 우선순위인 경우
- 연산을 결합하는 방법인 결합성(또는 결합규칙)
- 연산자 결합성 : '괄호가 없고 동일한 우선 순위라면, 덧셈과 뺄셈, 곱셈과 나눗셈과 같은 일반적인 연속된 연산은 왼쪽부터 오른쪽으로 차례로 계산
- 다만 제곱승과 같은 정해진 연산은 오른쪽에서 왼쪽으로 차례로 계산한다'라는 규칙
- 연산을 결합하는 방법인 결합성(또는 결합규칙)
- 첫 번째 규칙은 괄호가 있으면 먼저 계산
연산자의 우선순위(priority)
- 우선순위가 1위
- 함수호출과 괄호로 사용되는 ()
- 후위 증감연산자 a++와 a-- 등의 단항연산자
- 여러 개 있으면 결합성에 따라 왼쪽에서 오른쪽 순으로 계산
- 우선순위가 2위
- 전위 증감연산자 ++a와 -a
- 주소연산자 &
- 오른쪽에서 왼쪽으로 차례로 계산
- 모든 단항연산자는 우선순위가 1, 2위
- 우선순위가 16위
- 콤마연산자
우선
순위연산자 설명 분류 결합성(계산방향) 1 ( )
[ ]
.
->
a++ a--함수 호출 및 우선 지정
인덱스
필드 (유니온) 멤버 지정
필드 (유니온) 포인터 멤버 지정
후위 증가, 후위 감소→ (좌에서 우로) 2 ++a --a
! ~
sizeof
- +
&
*전위 증가, 전위 감소
논리 NOT, 비트 NOT(보수)
변수, 자료형, 상수의 바이트 단위 크기
음수 부호, 양수 부호
주소
간접, 역참조← (우에서 좌로) 3 (형변환) 형변환 4 * / % 곱하기 나누기 나머지 산술 → (좌에서 우로) 5 + - 더하기 빼기 → (좌에서 우로) 6 << >> 비트 이동 이동 → (좌에서 우로) 7 < > <= >= 대소 비교 관계 → (좌에서 우로) 8 == != 동등 비교 → (좌에서 우로) 9 & 비트 또는 논리 비트 → (좌에서 우로) 10 ^ 비트 또는 논리 → (좌에서 우로) 11 | 비트 또는 논리 → (좌에서 우로) 12 && 논리 (단락 계산) 논리 → (좌에서 우로) 13 || 논리 (단락 계산) → (좌에서 우로) 14 ? : 조건 조건 ← (우에서 좌로) 15 = += -= *= /= %=
<<= >>= &= |= ^=대입 대입 ← (우에서 좌로) 16 , 콤마 콤마 → (좌에서 우로) #include <stdio.h> int main(void) { int a = 4, b = 6; double x = 3.3, y = 4.7; printf("%d ", a + b > y && x < y); # 산수 > 관계 > 논리 printf("%d ", a++ - --b * 2); # 단항 > 곱셈 > 뺄셈 printf("%f ", a > b ? x + 1 : y * 2); # 산술 > 관계 > 조건 printf("%f ", x += 3 && y + 2); # 산술 > 논리 > 대입 printf("%f\n", (x = x + 1, y = y + 1)); # 괄호 > 산술 > 대입 > 콤마 return 0; }
1 -6 9.400000 4.300000 5.700000
우선순위 요약
- 콤마 < 대입 < 조건(삼항) < 논리 < 관계 < 산술 < 단항 < 괄호와 대괄호
- 이항연산자 : 논리, 관계, 산술
- 괄호와 대괄호는 무엇보다도 가장 먼저 계산한다.
- 모든 단항연산자는 어느 이항연산자보다 먼저 계산한다.
- 산술연산자 *, /, %는 +, -보다 먼저 계산한다.
- 산술연산자는 이항연산자 중에서 가장 먼저 계산한다.
- 관계연산자는 논리연산자보다 먼저 계산한다.
- 조건 삼항연산자는 대입연산자보다 먼저 계산하나, 다른 대부분의 연산보다는 늦게 계산한다.
- 조건 > 대입 > 콤마 연산자 순으로 나중에 계산한다.
변수값 표현식 설명 해설 결과 x = 3
y = 3x >> 1 + 1 > 1 & y 산술 > 이동 > 관계 > 비트 ( ( x >> ( 1 + 1 )) > 1 ) & y 0 x - 3 | | y & 2 산술 > 비트 > 논리 ( x - 3 ) | | ( y & 2 ) 1 x & y && y >= 4 관계 > 비트 > 논리 ( x & y ) && ( y >= 4 ) 0 x && x | y++ 증가 > 비트 > 논리 x && ( x | ( y++ ) ) 1 #include <stdio.h> int main(void) { int m = 5, n = 10; # 우측에서 좌측으로 결합 printf("%d ", n += m /= 3); m = 5; n = 10; printf("%d\n", (n += (m /= 3))); printf("%d ", 10 * 3 / 2); # 좌측에서 우측으로 결합 printf("%d\n", 10 * (3 / 2)); # 우측에서 좌측으로 결합 # 우측에서 좌측으로 결합 (조건연산자는 결합성이 오른쪽에서 왼쪽으로) printf("%d ", 3 > 4 ? 3 - 4 : 3 > 4 ? 3 + 4 : 3 * 4); printf("%d ", 3 > 4 ? 3 - 4 : (3 > 4 ? 3 + 4 : 3 * 4)); return 0; }
11 11 15 10 12 12
결합성
- 결합법칙
- 대부분 좌에서 우로 수행
- 우에서 좌(d)로 수행
- 우선순위가 2위인 전위의 단항 연산자, 우선 순위 14위인 조건연산자 그리고 우선순위 15위인 대입연산자
- 산술연산식 10 * 3 / 2는 ((10 *3) / 2)
- 결과는 12
- 축약 대입연산자로 구성
- 연산식 n += m /= 3
- 우에서 좌(d)로 먼저 결합하여 식( n += (m /= 3))을 수행
다양한 연산식
- 수학이나 공학에서의 다양한 수식을 표현
- 연산의 우선순위를 고려하여 괄호의 사용이 필요
- 제곱근을 구하는 함수 sqrt()를 활용
- 두 피연산자의 크기를 비교하기 위한 연산자