방송에서 사용된 PPT를 이미지 형태로 제공합니다.
저작권이 있는 내용이니 재편집 및 무단배포를 금지해 주시기 바랍니다.
강의용이나 공부용으로 사용하시기를 부탁드립니다.
연산자의 우선순위는 꼭 외울 필요는 없지만 결합방향과 최우선연산자1 정도는 아는것이 필요하다.
() 괄호연산자(복합리터럴) 연산자는 최우선 연산자 이기때문에 연산시 가장 우선시 되니 연산자가 어려우면 많이 활용하는것이 좋다.
연산자 우선순위
우선순위 | 연산자 | 연산자설명 | 결합 법칙(방향) |
1 | x++ x-- ( ) [ ] . -> (자료형){값} |
증가 연산자(뒤, 후위) 감소 연산자(뒤, 후위) 함수 호출 배열 첨자 구조체/공용체 멤버 접근 포인터로 구조체/공용체 멤버 접근 복합 리터럴 |
← |
2 | ++x --x +x -x ! ~ (자료형) *x &x sizeof |
증가 연산자(앞, 전위) 감소 연산자(앞, 전위) 단항 덧셈(양의 부호) 단항 뺄셈(음의 부호) 논리 NOT 비트 NOT 자료형 캐스팅(자료형 변환) 포인터 x 역참조 x의 주소 자료형의 크기 |
← |
3 | * / % |
곱셈 나눗셈 나머지 |
→ |
4 | + - |
덧셈 뺄셈 |
→ |
5 | << >> |
비트를 왼쪽으로 시프트 비트를 오른쪽으로 시프트 |
→ |
6 | < <= > >= |
작음 작거나 같음 큼 크거나 같음 |
→ |
7 | == != |
같음 다름 |
→ |
8 | & | 비트 AND | → |
9 | ^ | 비트 XOR | → |
10 | | | 비트 OR | → |
11 | && | 논리 AND | → |
12 | || | 논리 OR | → |
13 | ? : | 삼항 연산자 | ← |
14 | = += -= *= /= %= <<= >>= &= ^= |= |
할당 덧셈 후 할당 뺄셈 후 할당 곱셈 후 할당 나눗셈 후 할당 나머지 연산 후 할당 비트를 왼쪽으로 시프트한 후 할당 비트를 오른쪽으로 시프트한 후 할당 비트 AND 연산 후 할당 비트 XOR 연산 후 할당 비트 OR 연산 후 할당 |
← |
15 | , | 쉼표(콤마) 연산자 | → |
산술연산자
기호 | 인자의 수 | 위치 | 의미 |
+ | 단일 | 전치 | 양의 부호 |
- | 단일 | 전치 | 음의 부호 |
+ | 이진 | 중치 | 덧셈 |
- | 이진 | 중치 | 뺄셈 |
* | 이진 | 중치 | 곱셈 |
/ | 이진 | 중치 | 나눗셈 |
% | 이진 | 중치 | 나머지 |
++ | 단일 | 전치,후치 | 1증가 |
-- | 단일 | 전치,후치 | 1감소 |
단일 연산자인 +와 -는 바로 부호를 의미한다. 여기서 +는 양의 부호를 -는 음의 부호를 의미하는데 +는 인자의 부호를 변경하지 않지만 -는 인자의 부호를 정반대로 바꾸는 역할을 한다. 따라서 +의 계산 결과는 뒤의 인자 그대로가 되나 -의 계산 결과는 뒤의 인자의 부호를 바꾼 것이 계산 결과가 된다.
이진 연산자인 +와 -는 덧셈과 뺄셈을 나타내는 연산자이며 *와 /는 각각 곱셈과 나눗셈 연산자이다. 그리고 % 연산자는 나머지 연산자인데 다음은 이의 몇 가지 예이다.
int i = 10, j =20;
float f = 1.2f, g = 2.4f;
i + j = 30;
f + g = 3.6;
i - j = -10;
g - f = 1.2;
j - f = 18.8
실수값 j 에서 실수값 f 를 빼면 그 결과는 실수가 된다. 즉 정수에서 실수의 값을 빼면 에러가 발생하지 않고 실수가 된다.
int i = 10, j = 4, k = -4;
float f = 12.4, g = 3.1;
float h1 = 10.0, h2 = 4.0;
i * j = 40
f * g = 38.44
h1 * h2 = 40.0
i * f = 124.0
i / j = 2
j / i = 0
i / k = -2
f / g = 4.0
h1 / h2 = 2.5
i / h2 = 2.5
정수와 실수 사이의 곱셈은 i * f 의 결과에서 나타났듯이 실수가 된다.
반면 / 의 경우에 정수끼리의 나눗셈은 정수가 되고 그 결과 몫만 계산된다는 것이다.
그래서 i / j 의 값이 2.5가 아닌 2가 되었다.
마찬가지로 i / k 는 -2.5가 아닌 -2가 되는데, 이를 보면 정수 나눗셈의 경우 결과가 양수이면 버림을, 음수이면 올림을 하는 꼴이 된다.
실수끼리의 나눗셈의 경우에는 제대로 계산이 되어서 h1 / h2 가 2.0이 아닌 2.5가 된다. 곱셈과 마찬가지로 실수와 정수의 나눗셈은 실수가 된다.
int i = 30, j = 7, k = 10, l = -30, m = -7;
float f = 30.0, g = 7.0;
i % j = 2
i % k = 0
f % g = 에러
i % m = 2
l % j = -2
l % m = -2
% 연산자는 몫을 구하고 난 나머지가 계산 결과가 되는데 나머지는 정수 나눗셈에서만 의미가 있기 때문에 % 연산자는 정수 데이터 유형들간에만 사용할 수 있다.
% 연산자의 왼쪽 인자가 음수이면 나머지도 음수이고 양수이면 나머지도 양수이다.
그리고 나머지 계산은 양수로 간주해서 계산한다. 그래서 l % j 와 l % m 의 경우에 l이 음수이기 때문에 나머지가 음수가 되었으며 i % m 의 경우에는 m이 음수이지만 i는 양수이기 때문에 나머지가 양수가 되었다. 반면에 실수에 대해 % 연산자를 적용하면 에러가 발생하게 된다.
a % b ⇔ a - (a / b) * b
위의 식은 나머지를 구하는 알고리즘 중의 하나인데, 여기서 a와 b는 정수이며 b는 0이 아니다.
마지막으로 남은 ++와 --는 단일 연산자이기 때문에 인자는 하나만 필요하다.
그런데 인자로 반드시 변수만 올 수 있다.
또 연산자가 인자의 앞에 올 수도 있고 뒤에 올 수도 있는데, 앞에 왔을 때와 뒤에 왔을 때의 의미가 다르다.
int i = 10;
float f = 1.2;
++i = 11( i의 값도11이 된다 )
i++ = 10( i의 값은 11이 된다 )
++f = 2.2( f의 값도 2.2가 된다 )
f++ = 1.2( f의 값은 2.2가 된다 )
++10 = 컴파일 에러
--i = 9( i의 값도9가 된다 )
i-- = 10( i의 값은 9가 된다 )
--f = 0.2( f의 값도 0.2가 된다 )
f-- = 1.2( f의 값은 0.2가 된다 )
--10 = 컴파일 에러
++i는 일단 i의 값을 1증가한 후에 이 증가된 값이 계산 결과가 되는데 반해 i++는 일단 현재의 i의 값을 계산 결과로 사용하고 그 뒤에 i의 값을 증가시키게 된다.
++는 정수 데이터 유형은 물론 실수 데이터 유형의 변수에도 사용할 수 있다. 그리고 ++10이 에러가 나는 것은 ++ 다음의 인자에 변수만 와야 하는데 상수인 10이 왔기 때문이다.
그리고 --는 ++와 사용하는 방법, 의미까지도 완전히 똑같은데 하나 더하는게 아니라 하나 빼는 것만 다르다.
void main() {
int i = 10, j = 10;
printf(" i = %d j = %d\n",i,j);
printf("++i = %d j++ = %d\n",++i,j++);
printf(" i = %d j = %d\n",i,j);
}
결과
i = 10 j = 10
++i = 11 j++ = 10
i = 11 j = 11
main() {
int n = 5, num = 3, i = 10;
printf("n = %d, n * n++ = %d\n",n,n*n++);
printf("num++ + num = %d\n",num++ + num);
printf("++i = %d, ++i = %d, ++i = %d\n",++i,++i,++i);
}
결과
n = 6, n * n++ = 30
num++ + num = 7
++i = 13, ++i = 12, ++i = 11
위 프로그램에서 원했던 결과는
n = 5, n * n++ = 25
num++ + num = 6
++i = 11, ++i = 12, ++i = 13
인데, 그렇지 않은 것은 C에서는 2개의 항을 필요로 하는 연산자(단, && || , ? 는 제외)에서 어느 항을 먼저 처리할 것이냐의 평가순서는 특별히 정해져 있지 않고 컴파일러가 상황에 따라 자기 편한대로 결정한다.
즉 n * n의 계산이 끝난 뒤에 n++가 되는 것이 아니라 * 연산자의 우측항을 먼저 평가해서 n++ 이 된 다음 n * n이 된다. 그리고 num++ + num의 경우도 전체수식을 다 계산한 후에 후위형을 처리하는 것이 아니라 수식을 참고해 나가면서 증감 연산자가 있으면 바로 증감이 일어나기 때문에 변수가 어떤 수식 또는 어떤 함수에 매개변수로 두 번 이상 쓰이면 그 변수에 ++나 --연산자는 절대로 사용하지 않는다.
산술 연산자들의 우선순위와 결합방향은 다음과 같다.
우선 순위 : ++, --, +(단일), -(단일) ⇒ *, /, % ⇒ +, -
결합 방향 : <-- --> -->
<-- 는 오른쪽에서 왼쪽을 의미하며 --> 는 왼쪽에서 오른쪽의 결합방향을 의미한다. 그리고 ⇒ 는 우선순위를 나타낸다.
main() {
int i = 10, j = 7, k = 3, l = 5, m = 10, n = 3;
printf("Result = %d\n",--i/k++ + m/j/n - -n*-l%j);
printf("i = %d, j = %d, k = %d, l = %d, m = %d, n = %d\n",i,j,k,l,m,n);
}
Result = 2
i = 9, j = 7, k = 4, l = 5, m = 10, n = 3
위 프로그램은 먼저 우선순위를 따져보고 다음 괄호친 것부터 계산해야 한다.
(--i)/(k++) + m/j/n - (-n)*(-l)%j
초기화 기준값 : i = 10, j = 7, k = 3, l = 5, m = 10, n = 3
우선 --i는 9가 되며 동시에 i의 값도 9가 되고 k++의 값은 k가 되므로 3이 되며 그후 k의 값이 하나 늘어나므로 k는 4가 된다.
그리고 -n과 -l은 당연히 -3과 -5가 된다.
9/3 + m/j/n - (-3)*(-5)%j
i = 9, j = 7, k = 4, l = 5, m = 10, n = 3
그 다음 계산해야 할 것은 우선순위가 높은 것들로 다음 괄호친 것들이다.
(9/3) + (m/j/n) - ((-3)*(-5)%j)
i = 9, j = 7, k = 4, l = 5, m = 10, n = 3
괄호 안의 연산자들은 모두 왼쪽에서 오른쪽으로 결합 하므로 처리 순서는 다음과 같다.
(9/3) + ((m/j)/n) - (((-3)*(-5))%j) i = 9, j = 7, k = 4, l = 5, m = 10, n = 3
이들을 차례대로 계산하면 다음과 같이 된다.
(3) + (1/n) - (15%j)
i = 9, j = 7, k = 4, l = 5, m = 10, n = 3
3 + 0 - 1 = 2
i = 9, j = 7, k = 4, l = 5, m = 10, n = 3
할당 연산자(대입 연산자,복합대입 연산자)
기호 | 인자의 수 | 위치 | 의미 |
= | 이진 | 중치 | 오른쪽 값을 할당(대입) |
*= | 이진 | 중치 | 곱한 값을 할당 |
/= | 이진 | 중치 | 나눈 값을 할당 |
%= | 이진 | 중치 | 나머지 값을 할당 |
+= | 이진 | 중치 | 곱셈 |
-= | 이진 | 중치 | 뺀 값을 할당 |
<<= | 이진 | 중치 | 왼쪽 쉬프트한 값을 할당 |
>>= | 이진 | 중치 | 오른쪽 쉬프트한 값을 할당 |
&= | 이진 | 중치 | AND한 값을 할당 |
^= | 이진 | 중치 | XOR한 값을 할당 |
|= | 이진 | 중치 | OR한 값을 할당 |
우선 '=' 부터 살펴보면 이는 x = y 형태로 사용하는 이진 연산자이며 ++와 유사하게 =의 왼쪽 인자(즉 x)에는 반드시 변수가 와야 한다. 그러나 오른쪽(즉 y)에는 아무 수식이 와도 상관 없다.
x = y 의 계산 결과는 x와는 전혀 상관없이 무조건 y의 값이 계산 결과가 된다. 즉 y의 값을 x의 변수에 할당하게 된다(그래서 이름이 할당 연산자이다).이것은 FORTRAN이나 BASIC에서 사용하는 할당문과 똑같은데 실제로 하는 일도 비슷하다. 그러나 C에서는 이것이 문장이 아니라
연산자, 즉 수식이란 것이다. 따라서 다른 연산자와 같이 섞어 쓸 수도 있으며 x = y = z와 같이 여러 개를 같이 사용할 수도 있다.
나머지 할당 연산자들은 모두 '=' 앞에 뭔가가 붙어 있는데 이를 'op='라고 하면(op는 =앞에 붙은 연산자를 의미한다) 이는 다음과 똑같은 의미를 갖는다.
x op= y ⇔ x = x op y
다음은 이의 몇 가지 사용예이다.
int i = 3, j = 5, k = 10;
i += j; (i = 8)
j *= k; (j = 50)
위와 같이 축약된 형태의 연산자를 사용할 때 주의할 점이 있는데, 위에서 x *= y는 x = x * y라고 했는데 그러면 x *= y + 1은 무엇이 되겠는가 하는 것이다.
x = x * y를 그대로 따른다면 x = x * y + 1이 되는데 그러면 x = (x * y) + 1이 된다. 과연 그런가? 이는 실제로 해보면 알 수 있는데 x *= y + 1은 x = x * (y + 1)과 같다. 따라서 x op= y는 x op= (y)로 생각하는 것이좋다. y의 전체 값에 대해 연산을 수행하기 때문이다.
또 x++ op= y와 같이 x에 대해 부수효과를 일으키는 ++나 --가 있는 경우이다. 할당 연산자의 왼쪽에는 반드시 변수가 와야 하기 때문에 x++는 올 수 없다(x++는 변수가 아니라 수식이다).
그러나 하나 예외가 있는데 x가 포인터 변수인 경우에는 가능하게 된다. 이때 x++ op= y가 역시 x++ = x++ op y가 된다면 x의 값은 2번 증가해야 할 것이다. 그러나 이 경우 x의 값은 하나밖에 증가하지 않는다. 즉 x++ op= y는 x++ = x op y가 되므로 주의해야 한다.
할당 연산자의 우선순위는 앞으로 나올 콤마 연산자 다음으로 가장 낮고 결합방향은 오른쪽에서 왼쪽이다. 따라서 x = y = z = 3은 x = (y = (z = 3))으로 처리되며 이의 계산 결과는 3이 되고 부수효과로 x, y, z는 모두 3을 값으로 갖게 된다.
다음은 할당 연산자에 관한 예이다.
int i = 3, j = 6, k = 9, l = 12;
float f = 1.2f, g = 0.1f;
① i += j -= k *= l /= 3 = -27 (i = -27, j = -30, k = 36, l = 4)
② f *= g + 2.1f = 2.64f (f = 2.64f)
③ k += j - i *= 3 (컴파일 에러)
④ k += j - (i *= 3) = 6 (k = 6)
①은 우선순위가 같기 때문에 결합방향은 오른쪽에서 왼쪽으로 (i += (j -= (k *= (l /= 3))))으로 처리된다. l /= 3은 l = l / 3이고 이는 l = 4이므로 계산 결과는 4가 된다(이때 l도 4가 된다).
따라서 (i += (j -= (k *= 4)))가 되며 k *= 4는 36이므로 i += (j -= 36)이 되고 j -= 36은 -30이므로 결국 i += -30이 되어 전체 계산 결과는 -27이 된다.
②는 f *= (g + 2.1f)가 되므로 f *= 2.2f가 되어 이는 2.64f가 된다.
③은 할당 연산자가 우선순위가 가장 낮기 때문에 k += ((j - i) *= 3)으로 처리 되는데 이는 k += (3 *= 3)으로 '*='의 왼쪽에는 반드시 변수가 와야 하는데 상수 3이 왔으므로 컴파일 에러가 발생한다.
④의 경우에는 괄호로 *= 를 먼저 처리했으므로 k += j - 9가 되어 이는 k += -3이 되고 이는 6이 된다.
관계 연산자
기호 | 인자의 수 | 위치 | 의미 |
< | 이진 | 중치 | 오른쪽 값을 할당(대입) |
<= | 이진 | 중치 | 곱한 값을 할당 |
> | 이진 | 중치 | 나눈 값을 할당 |
>= | 이진 | 중치 | 나머지 값을 할당 |
== | 이진 | 중치 | 곱셈 |
!= | 이진 | 중치 | 뺀 값을 할당 |
>= 에서 >와 =는 반드시 붙여써야 하며 > =와 같이 띄어 쓰게 되면 컴파일할 때 에러가 발생하게 된다.
이는 <=도 마찬 가지이다. 모든 연산자는 계산 결과가 있는데, 즉 3 < 4도 계산 결과가 있다는 것이다. 참인 경우는 무조건 계산 결과가 1이 되며 거짓인 경우는 무조건 계산 결과가 0이 된다. 3 < 4는 참이기 때문에 계산 결과가 1이 되며 7 < 2는 거짓이기 때문에 0이 된다.
int i = 1, j = 0, k = -1, l = 0;
i < j 결과 0
i <= (j + 1) 결과 1
k > l 결과 0
j >= l 결과 1
(i + k) >= (j + l) 결과 1
x == y는 x와 y의 값이 같으면 참이 되고 같지 않으면 거짓이 된다. 반면에 x != y는 x와 y의 값이 서로 같지 않으면 참이 되고 같으면 거짓이 되는데 x != y와 !(x == y)는 서로 동일한 수식 ! 는 다음에 나올 논리 연산자)이다.
int i = 1, j = 1, k = 0, l = -1;
i == j 결과 1
i != j 결과 0
(j + l) == k 결과 1
(i + j) != (j - l) 결과 0
('a' + 1) == i 결과 0
마지막의 ('a' + 1) == i 에서 ' '기호로 묶이면 char형을 나타내는데 여기에 산술 연산자가 쓰이면 해당 문자의 ASCII 코드 값과 연산을 한다.
이들의 우선순위는 산술 연산자보다는 낮다. 따라서 다음과 같은 순서로 결합된다.
int i = 1, j = 2, k = -5;
① -i - 5 * j >= k + 1
/* ((-i) - (5 * j)) >= (k + 1) */
② i >= j == k != 2 <= j - 5
/* ((i >= j) == k) != (2 <= (j - 5)) */
①은 변수에 해당값을 넣어 계산하면 ((-1) - (5 * 2)) >= (-5 + 1)은 -11 >= -4이 되는데 이것은 거짓이므로 0이 된다.
②는 (0 == -5) != 0이 되고 0 != 0이 되어 거짓이므로 0이 된다.
main() {
int i, j;
printf("Input two integers: ");
scanf("%d%d",&i,&j);
printf("\nYou typed: %d and %d\n",i,j);
printf("The result is %d\n",(i > j) - (i < j));
}
결과
Input two integers: 2 5 엔터
You typed 2 and 5
The result is -1
위 프로그램은 정수값 두개를 읽어들여 앞의 정수가 뒤의 정수보다 크면 1을, 같으면 0을, 그리고 작으면 -1을 출력시키는 프로그램인데 우선 두개의 정수값을 읽어야 하니까 두 개의 정수 변수가 필요하며 이를 각각 i, j라고 한다.
그러면 i > j면 1을, i == j면 0을, 그리고 i < j면 -1을 출력시켜야 한다.
그런데 이 세가지 중 반드시 어느 하나만 참이지 두개 이상이 동시에 참이 될 수 없기 때문에 다음과 같은 수식을 만들수 있다.
A(i > j) + B(i == j) + C(i < j)
우선 i > j가 참일 때 다른 것들은 다 거짓이므로(이는 0이다)
이경우 위의 수식은 A만 남게 되며 이때 이 값이 1이어야 하므로 A = 1이 된다.
반면에 i == j가 참인 경우에는 B만 남게 되는데 이때 이 값이 0이므로 B는 0이 되어야 한다.
마찬가지로 i < j인 경우에는 C만 남는데 이 값이 -1이어야 하므로 C는 -1이 된다.
따라서 위의 수식은 다음과 같이 된다.
1 * (i > j) + 0 * (i == j) + -1 * (i < j)
⇒ (i > j) - (i < j)
논리 연산자
기호 | 인자의 수 | 위치 | 의미 |
&& | 이진 | 중치 | AND(논리곱) |
|| | 이진 | 중치 | OR(논리합) |
! | 단일 | 중치 | NOT(부정) |
예를 들어 P와 Q가 참거짓을 나타내는 명제라고 할 때
P Q | P AND Q | P OR Q | NOT P |
참 참 | 참 | 참 | 거짓 |
참 거짓 | 거짓 | 참 | 거짓 |
거짓 참 | 거짓 | 참 | 참 |
거짓 거짓 | 거짓 | 거짓 | 참 |
C에서 &&, ||, !은 연산자이기 때문에 계산 결과가 반드시 있어야 한다. 그래서 C에서는 "0은 거짓이고 0이 아닌 것은 무조건 참이다" 라는 규칙을 정해 사용하고 있다. 즉 0이 아닌 것은 무조건 참이기 때문에 -1도 참이고 0.1도 참이고 'A'도 참이다. 이는 관계 연사자와 똑같이 하면 되는데 그 결과가 참이면 무조건 1이고 거짓이면 무조건 0으로 하는 것이다.
다음은 이의 몇가지 예이다.
int i = 1, j = 2, k = -3;
char c = 'A', d = '0', e = '\0';
float f = 1.2f, g = 0.0f;
i && j 결과 1
i && 0 결과 0
(i + j) && k = 1
c && d 결과 1 /* '0'은 코드값이 48이므로 참 */
c && e 결과 0 /* e의 값은 0이고 거짓 */
0 || 0.1 결과 1 /* 0.1은 0이 아니므로 참 */
f || g 결과 1
e || g 결과 0 /* g는 0(실수 0.0도 0이다)이므로 거짓 */
!i 결과 0
!e 결과 1
!d 결과 0
!f 결과 0
!g 결과 1
논리 연산자를 사용할 때 하나 주의할 것은 &&나 ||는 반드시 붙여 사용하여야 한다는 것이다.
& &나 | |와 같이 띄어쓰게 되면 컴파일 에러가 발생한다.
논리 연산자의 결합방향과 우선순위를 지금까지 나온 연산자들과 비교해서 나타내면
&&연산자가 || 보다 우선순위가 높다.
하지만 둘 다 단항, 산술 그리고 관계 연산자보다는 우선순위가 낮다.
결합방향은 왼쪽에서 오른쪽으로 된다. 반면에 !는 ++, --와 우선순위가 같으며 결합 방형은 오른쪽에서 왼쪽이다.
다음의 사용예를 살펴보자.
char c = 'B';
int i = 3, j = 3, k = 3;
double x = 0.0, y = 2.3;
i && j && k 결과 1 /* (i && j) && k 가 된다 */
!i - j 결과 -3 /* (!i) - j 가 되어 0 - 3이 된다 */
i * !x 결과 3 /* x가 0이라서 !x는 참, 즉 3 * 1이 된다 */
i < j || x > y 결과 0 /* (i < j) || (x > y) 가 된다 */
c - 1 == 'A' || c + 1 == 'Z' 결과 1 /* ((c - 1) == 'A' || ((c + 1) == 'Z') 가 된다 */
main() {
int i = 0, j = 3;
printf("%d\n",i != 0 && j / i == 1);
printf("%d\n",i == 0 || j / i == 1);
}
결과
0
1
위 프로그램에서 첫번째 printf문은 i != 0 && j / i == 1 의 값을 출력시키고 있는데 i의 값이 0 이므로 i != 0 은 거짓이다. &&는 어느 한쪽이 거짓이면 거짓이기 때문에 결과는 거짓이 되어 0이 된다. 그런데 문제는 j / i == 1 에 있다. 현재 i의 값이 0이기 때문에 j / i 는 3 / 0 이 되므로 0으로 나눗셈을 하게 되어 수행시 당연히 에러가 발생해야 된다. 그러나 이 경우에는 에러가 발생하
않고 0이 출력된다.
Microsoft C++(MSVC)와 TC(터보C)의 경우 &&에서 왼쪽의 값이 거짓(즉 0)일 때 오른쪽의 값을 계산하지 않는데 이것을 연결된 AND(Wired-AND) 처리 방법이라고 하며 그렇기 때문에 ++와 같이 부수효과를 일으키는 연산자는 &&의 오른쪽에 사용하지 않는 것이 좋다.
이와 같은 문제는 && 뿐만 아니라 ||에도 발생할 수 있는데, 두번째 printf문은 &&의 경우와 정반대라고 할 수 있다. i == 0 || j / i == 1 에서 현재 i의 값이 0이므로 i == 0 은 참이 된다.
|| 은 한쪽이 참이면 무조건 참이기 때문에 i == 0 || j / i == 1 은 참이 된다.
그러나 마찬가지로 j / i == 1 을 계산하면 i의 값이 0이기 때문에 수행시 에러가 발생하게 된다. 따라서 || 의 경우 왼쪽의 값이 참(즉 0이 아닌 값)일 때 오른쪽의 값을 계산할 것이야 아니냐하는 문제를 갖게 된다. 이 때 계산하지 않는 것을 연결된 OR(Wired-OR) 처리 방법이라고 한다.
MSC와 TC는 ||도 연결된 OR 형태로 처리하며 위의 프로그램의 경우 에러가 발생하지 않고 1이 출력되게 된다.
main() {
int i;
printf("Input score: ");
scanf("%d",&i);
printf("The grade is: %c\n",65 * (i >= 90) + 66 * (80 <= i && i <=89) +
67 * (70 <= i && i <= 79) + 68 * (60 <= i && i <= 69) + 70 * (i <= 59));
}
결과
Input score: 95 엔터
The grade is: A
위 프로그램은 점수를 읽어(이는 정수값으로 0 ~ 100점 사이라고 가정) 이의 값이 90 이상이면 A를, 80~89면 B를, 70~79면 C를, 60~69면 D를, 그 이하면 F를 출력시키는 것으로, 우선 정수값 하나를 읽어들이므로 이를 위한 변수로 i를 사용하고 있다.
입력한 i의 값이 95이기 때문에 printf문의 연산식 중에서 65 * (i >= 90)만 참이 되고 나머지는 모두 거짓이 되기 때문에 다음과 같이 되는데,
printf("The grade is: %c\n",65 * (1) + 66 * (0) + 67 * (0) + 68 * (0) + 70 * (0));
이를 모두 합한 결과를 %c로 출력하는데 A의 코드 값이 65이므로 원하는 결과를 얻을 수 있다.
형변환 규칙과 CAST연산자
C에서는 서로 다른 데이터 유형간의 연산을 자유롭게 허용하고 있는데 수식 안에 서로 다른 데이터 유형이 있을 경우 한 데이터 유형이 다른 데이터 유형으로 자동적으로 변환되는 형변환(type conversion)이 발생하게 된다. 이 형 변환은 임의로 발생하지 않고 컴파일러가 내부적으로 정한 규칙을 따르게 된다.
① char와 unsigned char는 일단 int로 변환한다.(이 때 char는 앞의 부호가 확장되고 unsigned char는 0이 확장된다.
② short int는 int로, unsigned short int는 unsigned int로 변환한다. 그러면 수식에는 char와 short int 유형은 없게 된다.
③ 수식이 x op y라고 할 때 x와 y중 더 큰 데이터 유형으로 x와 y를 변환한다.
이때 데이터 유형의 크기는 다음과 같다.
long double > double > float > unsigned long int > long int > unsigned int > int
예를 들어 char = c 일 때 c + 3.2는 먼저 c의 값이 int로 변환된다.
그러면 int + double이 되는데(3.2는 double이다) double > int 이므로 c의 값이 다시 double로 변환한다.
따라서 계산 결과는 double이 된다.
다음 수식의 계산 결과의 데이터 유형은?
int i;
double g;
i += g 결과 int
g는 double이고 i는 int이므로 역시 double이 되어야 할 것이다.
그러나 할당 연산자의 경우에는 오히려 왼쪽 변수의 데이터 유형을 따르게 된다.
따라서 double이 int로 변환되어 i 값에 들어가게 되고 이 값이 바로 계산 결과가 된다.
어떤 데이터 유형의 값을 강제적으로 다른 데이터 유형으로 변환하는 것이 필요할 때가 많은데,
C에서는 다음과 같은 형태로 형변환 연산자를 제공하고 있다.
(데이터 유형 이름) 수식
변환하고 싶은 데이터 유형의 이름에 괄호를 치면 된다.
형변환 연산자는 ++, --와 같이 우선순위가 제일 높으며 결합방향은 오른쪽에서 왼쪽이다.
따라서 (int)i + 1은 ((int)i + 1)로 처리되고
(float)(int)x는 ((float)((int)x))로 처리되는데 예를 들면 다음과 같다.
char c = 'A';
int i = 10;
float f = 3.2f;
(int)(c + i + f) /* 65 + 10 + 3 = 78(int) */
(int)f * i - 2 /* 3 * 10 - 2 = 28(int) */
(float)(int)f - (int)c - i /* 3.0 - 65 - 10 = -72.0(float) */
(long double)c - f * 10.0 /* 65.0 - 32.0 = 33.0(long double) */
※ 부호 확장(sign extension)과 0 확장(zero extension)
큰 데이터 유형의 값을 작은 데이터 유형의 값에 할당하거나 큰 데이터 유형의 값을 작은 데이터 유형으로 변환하면 오버플로우(overflow)가 발생하기 쉽다.
예를 들어 long int유형의 값 300000을 short int의 변수에 할당하게 되면 short int로는 300000을 표현할 수 없으므로
오버플로우가 발생하게 된다.
반면에 작은 데이터 유형의 값을 큰 데이터 유형에 할당하거나 변환하게 되면 빈 공간이 생길 수 있다.
이는 정수 데이터 유형간에서 발생할 수 있는데 예를 들어 char 유형의 값을 int 유형에 할당하게 되면 char는 1바이트이지만 int는 4바이트(32bit기준)이기 때문에 4바이트가 남게 된다. 이때 남는 3바이트를 무엇으로 채워넣느냐는 문제가 발생하게 된다.
char c = -1;
int i;
위의 프로그램에서 i = c와 같이 i에 c의 값을 할당한다고 한다면 i의 값은 당연히 -1이 되어야 할 것이다.
그런데 c = -1에서 c의 값은 2진수로 11111111이 된다. 이를 i에 그대로 할당하고 남는 것을 0으로 채워 넣는다면 0000000011111111이 되어 이는 -1이 아닌 +255가 되어 버린다.
int유형에서 -1은 1111111111111111이므로 오히려 1을 다 채워 넣어야 올바른 결과가 된다.
그렇다면 양수의 경우에는 0을, 음수의 경우에는 1을 채워넣는 것이 올바른 결과를 낳게 되는데
이를 바로 부호 확장(sign extension)이라고 한다.
unsigned char c = 129;
unsigned int i;
위에서 i = c라고 했을 때 만약 부호 확장을 한다면 c의 값이 10000001이므로 1이 확장되 i의 값은 1111111110000001이 되어 65409가 되어 버린다. 이는 올바른 결과가 이닐 것이다.
이 경우에는 오히려 무조건 0을 채워 넣는 것이 맞게 되는데 이를 바로 0 확장(zero extension)이라고 한다.
unsigned 유형의 데이터를 unsigned 유형의 데이터에 할당할 때에는 이와 같이 0 확장하도록 되어 있다.
unsigned 유형의 데이터를 signed 유형의 데이터에 할당할 때에는 unsigned 유형의 값이므로 0 확장하는 것이 올바르게 되며 signed 유형의 데이터를 unsigned 유형의 데이터에 할당할 때에는 부호 확장하는 것이 올바른 결과를 낳게 된다.
기타연산자
⑴ 조건 연산자(3항연산자,삼항연산자)
조건 연산자는 C에서만 볼 수 있는 연산자인데 특이하게도 3진 연산자이며 다음과 같은 형태로 사용한다.
수식1 ? 수식2 : 수식3
먼저 수식1을 계산한다. 그래서 그 계산 결과가 참이면 수식2를 계산하고 수식2를 계산한 결과가 이 조건 연산자의 계산 결과가 되며 수식1을 계산한 결과가 거짓이면 수식3을 계산하고 이를 계산한 결과가 바로 이 조건 연산자의 계산 결과가 된다.
주의할 것은 수식2가 계산되면 수식3은 계산되지 않으며 마찬가지로 수식3이 계산되면 수식2는 계산되지 않는다는 것이다.
x = (y < z) ? y : z;
조건 연산자의 우선순위는 할당 연산자 보다 높기 때문에(그 외의 다른 연산자보다는 우선 순위가 낮다) 괄호는 안 써도 되지만 괄호는 무엇을 조건으로 하는지 분명하게 해 주는 역할을 하기 때문에 써주는 것이 좋다. 그리고 결합방향은 오른쪽에서 왼쪽이다. 따라서 아래와 같이 복합으로 사용한다면
x ? y : z ? w : v
x ? y : (z ? w : v)로 처리된다.
char a = 'a', b = 'b';
int i = 1, j = 2, k;
double x = 7.07;
① i == j ? a - 1 : b + 1 결과 99 (int)
② j % 3 == 0 ? i + 4 : x 결과 7.07 (double)
③ j % 3 ? i + 4 : x 결과 5.0 (double)
④ k = i - 1 ? j += 3 : i * j 결과 2 (int)
① i와 j의 값은 서로 다르기 때문에 b + 1을 처리한다. 그래서 b의 ASCII 코드값인 98에 1을 더해 결과값은 99가 된다.
그리고 a와 b는 char형이므로 형변한 규칙에 의해 정수형이 된다.
② j를 3으로 나눈 나머지는 2가 되므로 0과는 같지 않기 때문에 x의 값인 7.07이 결과 값이 된다.
그리고 i + 4와 x중 x의 형이 더 높으므로 전체수식도 double형이 된다.
③ 나머지가 2이므로 i + 4가 결과값이 되어 5가 되지만 전체수식의 형이 double형이므로 5.0이 된다.
④ 조건 연산자가 =보다는 우선 순위가 높기 때문에 k = ((i - 1) ? (j += 3) : (i * j))로 처리 되며 i - 1은 거짓이기 때문에 i * j가 결과값이 된다.
main() {
int i;
printf("Input the number of books: ");
scanf("%d",&i);
printf("%d book%c\n",i,(i == 1) ? '\0' : 's');
}
결과
Input the number of books: 25 엔터
25 books
위의 두번째 printf문에서 조건 연산자를 사용하고 있는데 i가 1이면 '\0'을, 1이 아니면 's'를 계산 결과로 사용하고 있다. 이를 %c가 받으므로 결국 i가 1이면 널 문자가 출력되어 아무것도 안나오게 되며 1이 아니면 's'가 출력되게 된다.
다음은 3개의 정수값을 읽어 이의 최대값을 구하는 프로그램이다. 최대값은 일단 2개 중 큰 값을 구한 다음 이를 나머지 하나와 비교해 그 중 큰 값을 구하면 된다.
main() {
int x, y, z;
printf("Input three numbers: ");
scanf("%d%d%d",&x,&y,&z);
printf("The largest number is %d\n",(x = (x > y) ? x : y) > z ? x : z);
}
결과
Input three numbers: 22 87 -32 엔터
The largest number is 87
⑵ 비트 단위 연산자
기호 | 인자의 수 | 위치 | 의미 |
& | 이진 | 중치 | 비트 단위의 AND(논리곱) |
| | 이진 | 중치 | 비트 단위의 OR(논리합) |
^ | 이진 | 중치 | 비트단위의 XOR(베타적) |
~ | 단일 | 중치 | 1의보수(비트반전) |
>> | 이진 | 중치 | 비트 왼쪽 쉬프트 |
>> | 이진 | 중치 | 비트 오른쪽 쉬프트 |
위의 연산자 가운데 &와 |, ^ 는 두 개의 비트에 대해 동작하는 연산자로 &는 AND 연산을, | 는 OR 연산을, 그리고 ^는 XOR 연산을 수행한다.
이들 연산자의 계산 방법은 다음과 같다.
X Y | X & Y | X | Y | X ^ Y |
0 0 | 0 | 0 | 0 |
0 1 | 0 | 1 | 1 |
1 0 | 0 | 1 | 1 |
1 1 | 1 | 1 | 0 |
&와 |, ^를 C의 데이터 유형에 적용하게 되면 각 해당하는 비트끼리 계산을 수행하게 된다.
따라서 같은 크기의 데이터 유형에 대해서만 위의 연산자들을 적용하여야 하며, 각 비트 단위로 동작하기 때문에 부호는 의미
없다. 그래서 보통 unsigned 유형의 데이터에 대해서만 위의 연산자들을 사용하고 있다.
다음은 위의 3 연산자의 몇 가지 사용 예이다.
unsigned char c = 0x7A, d = 0xA3;
c & d 결과 0x22
c | d 결과 0xFB
c ^ d 결과 0xD9
2진수 비트로 표현하면
c = 0111 1010
d = 1010 0011
0111 1010
1010 0011
c & d = 0010 0010
0x22
0111 1010
1010 0011
c | d = 1111 1011
0xFB
0111 1010
1010 0011
c ^ d = 11011001
0xD9
데이터의 값이 10진수인 경우에는 이를 2진수로 변환해야만 비트 단위의 연산을 수행할 수 있고 또 음수값을 준 경우에는 2의 보수로 표현되기 때문에 주의해야 한다.
다음은 이의 한 예이다.
char c = 37, d = -5;
c & d = 33
c | d = -1
c ^ d = -34
2진수로 표현하면
c = 00100101
d = 11111011
0010 0101
1111 1011
c & d = 00100001
결과 33
0010 0101
1111 1011
c | d = 11111111
결과 -1
0010 0101
1111 1011
c ^ d = 11011110
결과 -34
그리고 단일 연산자인 ~는 각 비트를 1은 0으로, 0은 1로 뒤집는 역할을 한다. 각 비트를 뒤집는 것이 바로 1의 보수이기 때문에 ~는 1의 보수를 구하는 연산자가 된다.
다음은 ~연산자의 사용예이다.
char c = 0, d = -1, e = 56;
~c 결과 -1
~d 결과 0
~e 결과 -57
c는 0이므로 00000000이며 d는 11111111, e는 00111000이 된다.
그러면 ~c는 11111111, ~d는 00000000이 되며 ~e는 11000111이 된다.
이를 다시 10진수로 변환하면 위와 같은 결과를 얻을 수 있다.
참고로 χ와 ~χ의 값을 합하면 항상 -1이 된다는 것이다.
마지막으로 <<와 >>는 주어진 데이터를 왼쪽으로 쉬프트하거나 오른쪽으로 쉬프트한 결과를 계산 결과로 삼는다.
<<와 >>는
x << y
x >> y
와 같은 형태로 사용하는데 x << y는 x의 값을 y번 왼쪽으로 쉬프트한 값이 계산 결과가 되며
x >> y는 x의 값을 y번 오른쪽으 쉬프트한 것이 계산 결과가 된다.
이때 주의할 것은 x자체의 값은 쉬프트 되지 않는다는 것이다. 즉 이 연산자는 부수효과가 없기 때문에 x의 값은 바뀌지 않는다.
그리고 y는 반드시 양의 정수값이 와야 하며 y가 0일 때에는 그냥 x의 값이 되지만(0번 쉬프트 한것은 안한것과 같다)
y가 음수 값인 경우에는 에러가 발생하지 않고 그냥 0이 되어버리므로 주의하여야 한다.
다음은 0100 1011의 값을 왼쪽으로 1 쉬프트한 것이다.
위에 나타난 바와 같이 2번째 비트는 첫번째 비트로, 3번째 비트는 2번째 비트로,... 자리 이동을 하며 맨 마지막 비트(즉 8번째 비트)에는 무조건 0이 들어오게 된다. 그리고 첫번째 비트는 갈곳이 없기 때문에 사라지게 된다.
다음은 01001011의 값을 오른쪽으로 쉬프트한 것이다.
왼쪽 쉬프트와는 정반대로 오른쪽 쉬프트의 경우에는 맨 끝(즉 8번째)의 비트가 사라지게 되며 첫번째 자리에 새 데이터가 들어오게 되는데 이 경우에는 무조건 0이 들어오지 않는다.
일반적으로 데이터의 유형이 unsigned인 경우에는 무조건 0이 들어오게 된다(이 경우 이 쉬프트를 논리적 쉬프트라고 부른다). 반면에 쉬프트한 데이터의 유형이 signed인 경우에는 쉬프트하기 전의 첫번째 비트가 1인 경우에는 1이 들어오게 되며 0인 경우에는 0이 들어오게 된다.
즉 첫번째 비트와 같은 값이 들어온다(이를 산술 쉬프트라고 부른다).
다음은 쉬프트 연산자에 대한 몇 가지 예이다.
unsigned char c = 0x71, d = 0xB9, e = 47, f = -2;
c << 1 결과 0xE2
d << 2 결과 0xE4
e >> 3 결과 0x05
f >> 4 결과 0x0F
위의 계산 결과를 구하려면 각 변수의 2진수 형태를 먼저 구해야 한다.
c는 01110001이고 d는 10111001, e는 00101111, 그리고 f는 11111110이 된다.
c << 1은 1110 0010이 되며
d << 2는 두번 쉬프트하므로 11100100이 된다.
반면에 e >> 3과 f >> 4는 e와 f가 모두 unsingned 유형 이므로 논리 쉬프트를 수행하면
e >> 3은 00000101이 되고
f >> 4는 00001111이 된다.
만약 이들이 singed char였으면 e >> 3은 그대로 00000101이 되나 f >> 4는 11111111이 되어 달라지게 된다.
우순순위중 ~,!는 2순위로 우선순위가 높지만 <<,>> 는 5순위 & 는 8순위 ^,| 9순위로 각각 다르다.
주의해야한다.
main() {
unsigned int u;
printf("Input a number: ");
scanf("%d",&u);
printf("2\'s complement is: %x\n",~u+1);
}
결과
Input a number: 256 엔터
2's complement is: ff00
위 프로그램은 2의 보수를 16진수로 출력하는 프로그램인데, 비트 단위 연산자 가운데 2의 보수를 구하는 연산자는 없다.
그러나 2의 보수가 1의 보수에 1을 더한 것이기 때문에 이를 이용하면 쉽게 구할 수 있다. ~u가 1의 보수이므로 ~u+1은 2의 보수를 나타낸다.
※ 산술 쉬프트와 논리 쉬프트
논리 쉬프트의 경우에는 쉬프트를 비트들의 순수한 자리 이동으로 보며 오른쪽 쉬프트의 경우에는 첫번째 비트가 두번째 자리로 옮겨 가고 두번째 비트는 세번째 자리로 옮겨가고... 등 자리 이동으로 간주한다. 그리고 빈 자리에는 0이 들어 온다고 생각한다. 일반적으로 쉬프트하면 이 논리 쉬프트를 의미하는 경우가 많다. 그렇다면 산술 쉬프트는 이를 어떻게 보는가?
char c = 3; /* c = 00000011 */
c << 1 = 00000110 = 6
c << 2 = 00001100 = 12
c << 3 = 00011000 = 24
c << 4 = 00110000 = 48
위의 프로그램에서는 c의 값을 왼쪽으로 쉬프트 할 때마다 * 2한 것과 같은 결과를 낳고 있다.
즉 c << 1은 c * 2와 같으며 c << 2는 c * 2 * 2와 같고 c << 3은 c * 2 * 2 * 2와 같다.
이와 같이 왼쪽으로 쉬프트한 것이 곱한것과 관계가 있기 때문에 컴퓨터 가운데는 곱셈을 이 쉬프트를 이용하여 구현하는 경우가 대부분이다.
산술 쉬프트는 이와 같이 왼쪽으로 쉬프트하는 것을 곱셈으로 본다. 즉 * 2로 보는 것이다.
그러면 오른쪽 쉬프트는 어떻게 되겠는가?
char c = 48; /* c = 00110000 */
c >> 1 = 00011000 = 24
c >> 2 = 00001100 = 12
c >> 3 = 00000110 = 6
c >> 4 = 00000011 = 3
오른쪽으로 쉬프트를 할 때마다 이는 ÷ 2와 같은 효과를 낳게 된다(물론 이는 정수 나눗셈이다).
산술 쉬프트는 이와 같이 오른쪽으로 쉬프트하는 것을 나눗셈으로 간주한다. 그런데 문제는 음수의 경우에 발생한다.
위와 같이 한다면 -4를 오른쪽으로 쉬프트하면 -2가 되어야 한다.그런데 이를 논리 쉬프트와 같이 그냥 오른쪽으로 쉬프트하고
새로 들어올 자리에 0을 넣게 되면 다음과 같이 엉뚱한 결과가 나오게 된다.
-4 = 11111100
-4 >> 1 = 01111110 = 126
즉 -4를 오른쪽으로 그냥 쉬프트하면 +126이 되어 버리는데 이는 산술 쉬프트의 의미와 맞지 않는 것이다.
산술 쉬프트에서는 오른쪽으로 쉬프트하면 ÷2가 되므로 -2가 되어야 하기 때문이다.
그러면 어떻게 해야 하는가? 위의 경우 0이 아닌 1이 들어가게 되면 다음과 같이 올바른 결과가 된다.
-4 = 11111100
-4 >> 1 = 11111110 = -2
그래서 산술 쉬프트의 경우 오른쪽으로 쉬프트할 때에, 양수 값은 0이 들어오게 되고 음수값은 1이 들어오게 된다.
이것이 논리 쉬프트와 다른 점이다.
※ 비트 단위의 연산자의 응용
비트 단위의 연산자는 특히 특정 비트의 값을 0으로 세팅하거나 1로 세팅하고자 할 때, 특정 비트의 값만 짤라내고자 할 때에 사용한다.
이는 한글을 처리하거나 그래픽 처리, 하드웨어 제어 등에서 매우 자주 사용되는 연산들이다.
우선 특정 비트만 0으로 만들고자 할 때(이를 선택적 리셋(reset)이라 한다)
예를 들어 unsigned char유형의 데이터를 첫번째 비트와 4, 5번째 비트를 0으로 세팅하고자 할 때 보통 마스크(mask)라고 부르는 것을 먼저 만들어야 한다.
선택적 리셋을 위한 마스크는 다음과 같이 만들면 된다. 즉 0으로 세팅하고자 하는 비트는 0으로, 나머지 비트는 1로 만들면 되는데, 1, 4, 5번째 비트를 0으로 세팅하고자 하므로 마스크는 01100111이 된다.
이 마스크를 처리할 데이터와 '&' 연산을 수행하면 다음과 같이 된다.
데이터: 11010101
마스크: 01100111
& ----------
01000101
이와는 정반대로 특정 비트를 1로 세팅하고자 할 때(이를 선택적 셋(set)이라 한다), 이 경우에도 먼저 마스크를 만드는데 예를 들어 1, 7, 8비트를 1로 세팅하고자 할 때의 마스크는 10000011이 된다. 이 마스크를 이번에는 데이터와 '|' 연산을 수행하면 된다.
데이터: 11010101
마스크: 10000011
| ----------
11010111
main() {
unsigned char c;
printf("Input a character: ");
scanf("%c",&c);
printf("%1d",(c & 0x80) >> 7);
printf("%1d",(c & 0x40) >> 6);
printf("%1d",(c & 0x20) >> 5);
printf("%1d",(c & 0x10) >> 4);
printf("%1d",(c & 0x08) >> 3);
printf("%1d",(c & 0x04) >> 2);
printf("%1d",(c & 0x02) >> 1);
printf("%1d\n",(c & 0x01));
}
결과
Input a character: A 엔터
01000001
코드 값을 10진수나 8진수, 또는 16진수로 출력시킨다면 문제가 간단하지만 2진수의 경우에는 해당 포맷이 없기 때문에 출력할 수 있는 방법이 없다.
위의 프로그램은 unsigned char유형의 문자를 입력받아 이의 코드값을 2진수 형태로 출력시키는 것으로 이는 앞의 선택적 리셋을 사용하면 된다.
맨 처음 비트를 출력하고자 하면 첫번째 비트를 제외한 나머지 비트를 모두 0으로 세팅한 다음에 오른쪽으로 7번 쉬프트하면 된다.
그러면 0000000X 형태가 되는데 이 X의 값이 바로 첫번째 비트의 값이 된다.
따라서 이 값을 출력시키면 첫번째 비트의 값을 출력하게 된다.
마찬가지로 2번째 비트의 값도 역시 2번째 비트의 값을 제외한 나머지를 0으로 세팅한 후 이를 오른쪽으로 6번 쉬프트하여 출력시키면 된다.
이렇게 출력시키다 맨 마지막 8번째 비트는 마지막 8번째 비트를 제외한 나머지 비트들을 0으로 리셋시킨 후 쉬프트할 필요없이 그대로 출력하면 된다.
⑶ 콤마 연산자
C에서는 ','가 두가지로 사용된다. 인자나 변수를 구분하기 위한 기호로도 사용되며 연산자로도 사용되는데 이때에는 다음과 같이 2진 연산자 형태로 사용한다.
수식1, 수식2
일단 수식1을 계산한다. 그리고 수식2를 계산한다. 그런데 계산 결과는 무조건 수식2가 된다. 왜냐하면 쉼표 연산자의 최종적인 형과 값은 맨 마지막에 계산된 수식의 결과에 따르기 때문이다.
예를 들어 (10 * 3 - 25, 7)의 경우 10 * 3 - 25를 먼저 계산한다. 그러면 5가 되는데 이는 계산만하고 그냥 버린다.
그리고 두번째 수식을 보면 7이므로 이의 계산 결과는 7이 된다.
main() {
int a, b, c, d, e;
int f = 5;
d = (a = 5, b = 3, c = 4); /* 괄호는 ','가 연산자임을 나타내기 위해 사용 */
e = (f = 2, f + 3);
printf("a = %d, b = %d, c = %d, d = %d, e = %d, f = %d\n",a,b,c,d,e,f);
}
결과
a = 5, b = 3, c = 4, d = 4, e = 5, f = 2
d변수의 값을 보면 c변수가 가지고 있던 4를 가지고 있음을 확인할 수 있다.
즉 맨 마지막에 계산된 수식의 값이 쉼표 연산자의 최종 값이 된다.
그리고 e의 값은 5가 되는데 f의 값이 2로 바뀐 다음 f + 3의 결과가 e에 대입되기 때문이다.
※ 콤마 연산자와 콤마
C에서 콤마는 두가지 용도로 사용된다. 첫째는 인자나 변수를 구분하는 역할을 하는 기호로, 둘째는 바로 콤마 연산자로 사용된다. 구분하는 기호로는 변수 선언할 때와 함수 호출할 때 인자를 구분하기 위해서만 사용되기 때문에 이를 콤마 연산자와 혼동할 염려는 거의 없다. 그런데 함수를 호출할 때 콤마를 사용하게 되면, 그 주위에 괄호가 있고 없고에 따라 그 의미가 완전히 달라
지게 된다.
printf("i = %d, j = %d\n",i,j);
위의 printf문 내의 콤마는 인자들을 구분하는 의미로 사용된 것이다. 따라서 i와 j의 값이 제대로 출력되어 나오게 된다.
그런데 이를 다음과 같이 괄호로 둘러 싸게 되면 그 의미가 완전히 달라지게 된다.
printf("i = %d, j = %d\n",(i,j));
즉 i,j의 콤마는 콤마 연산자로 해석되어 위의 printf문은 아래와 같은 의미를 갖게 된다.
printf("i = %d, j = %d\n",j);
(4)sizeof 연산자
C에서만 볼 수 있는 연산자로 다음과 같은 형태로 사용한다.
sizeof 수식
sizeof (데이터 유형)
이 연산자는 기호를 사용하지 않고 특이하게 이름을 사용한다. 그리고 인자로 수식이나 데이터 유형의 이름이 올 수 있는데, 인자가 수식일 때에는 괄호를 붙이지 않아도 되지만 어떤 데이터 유형의 이름일 때에는 반드시 괄호를 붙여야 한다.
예를 들어 sizeof ++i에서 ++i는 수식이므로 괄호가 필요 없지만 sizeof(int)에서는 int가 데이터 유형의 이름이므로 괄호가 필요하다.
이 sizeof 연산자는 크기를 계산하는 연산자인데 수식의 경우에는 그 수식의 계산 결과의 데이터 유형의 크기를 계산하며 데이터 유형의 경우에는 그대로 데이터 유형의 크기를 계산한다.
계산은 바이트 단위로 계산한다. sizeof(int)는 4가 되며 sizeof(double)은 8이 된다.
그리고 sizeof("test")는 5가 되는데 스트링은 그 스트링의 길이 + 1이 되기 때문이다.( 문자열은 끝에 널문자를 가지고 있다)
결합방향은 오른쪽에서 왼쪽이고 우선순위는 ++, --와 같다.
따라서 sizeof sizeof(double)은 sizeof(sizeof(double))로 처리되는데 이의 값은 4가 된다.
왜냐하면 sizeof(double)은 8이 되지만 8이란 값 자체는 int 유형이기 때문이다.
다음은 sizeof 연산자에 관한 몇 가지 예이다.
int i, j = 3;
char c;
long l;
sizeof i + j = 5 /* sizeof(i) + j로 처리 */
sizeof (i + j) = 2
sizeof i + sizeof j = 4
sizeof sizeof c = 2 /* sizeof(1) 이 되므로 int 유형이 된다 */
main() {
printf("The size of data types is computed\n\n");
printf(" char = %2d byte\n",sizeof(char));
printf(" short int = %2d bytes\n",sizeof(short int));
printf(" int = %2d bytes\n",sizeof(int));
printf(" long int = %2d bytes\n",sizeof(long int));
printf(" float = %2d bytes\n",sizeof(float));
printf(" double = %2d bytes\n",sizeof(double));
printf("long double = %2d bytes\n",sizeof(long double));
}
결과
The size of data types is computed
char = 1 byte
short int = 2 bytes
int = 4 bytes
long int = 4 bytes
float = 4 bytes
double = 8 bytes
long double = 8 bytes
코딩덕후 강의 PPT 연산자 이미지파일
코딩덕후 C언어 기초강의 유투브 까페
https://cafe.naver.com/coducks
코딩덕후 : 네이버 카페
코딩을 조금 더 쉽게 재미있게 배우기 위한 까페입니다. C언어/Win32Api/C++/파이썬등을 유투브로 배우세요
cafe.naver.com
유투브 강의
'C' 카테고리의 다른 글
C언어 기초강의 5강 main()함수의이해 (0) | 2022.08.12 |
---|---|
C언어 기초강의 4강 C언어의 역사 (0) | 2022.08.12 |
C언어 기초강의 - 3강 보수에 대하여 ( 코딩덕후) (0) | 2022.08.11 |
C언어 기초강의 1강 컴퓨터와 메모리 (0) | 2022.08.11 |
Visual Studio Community 설치 (0) | 2022.08.02 |
댓글