4장 연산자 - 기본
2 + 5 라고 한다면 2와 5는 피연산자 + 는 연산자(덧셈연산자)를 의미한다. 전체 항의 개수는 3개이고 연산자를 제외한 2개의 피연산자 항의 개수만 생각하면 2항이 된다. 그러므로 +(덧셈 연산자)를 2항 연산자라고도 부른다. 같은 맥락으로 뺄셈,곱셈,나눗셈 등의 산술 연산자들은 모두 2항 연산자로 분류한다. C 언어의 연산자는 피연산자의 개수가 1개인 단항 연산자, 2개인 2항 연산자, 3개인 3항 연산자까지 있다.

연산자 우선순위?
연산이 여러개가 있을때 어떤 연산을 먼저 해야하는지 순서를 말한다.
3+2*4 를 연산한다면 먼저 곱셈(*)를 먼저하고 덧셈(+)를한다. 이유는 연산자 우선순위에 따라 곱셈, 나눗셈 연산이 덧셈 뺄셈 보다 우선순위가 높기 때문이다.
(3+2)*4 를 연산한다면 먼저 괄호안의 3+2를 먼저 수행한다.
우선순위 | 연산자 | 설명 | 결합 방향 |
---|---|---|---|
1 | () [ ] -> . |
괄호, 배열 접근, 구조체 포인터, 멤버 접근 | 왼쪽에서 오른쪽 |
++ -- |
후위 증감 연산자 | ||
2 | ++ -- + - ! ~ * & sizeof (type) |
전위 증감, 부호, 논리 부정, 비트 반전, 포인터 | 오른쪽에서 왼쪽 |
주소 참조, 크기 계산, 형 변환 | |||
3 | * / % |
곱셈, 나눗셈, 나머지 | 왼쪽에서 오른쪽 |
4 | + - |
덧셈, 뺄셈 | 왼쪽에서 오른쪽 |
5 | << >> |
비트 이동 연산자 | 왼쪽에서 오른쪽 |
6 | < <= > >= |
관계 연산자 | 왼쪽에서 오른쪽 |
7 | == != |
동등성 연산자 | 왼쪽에서 오른쪽 |
8 | & |
비트 AND | 왼쪽에서 오른쪽 |
9 | ^ |
비트 XOR | 왼쪽에서 오른쪽 |
10 | ` | ` | 비트 OR |
11 | && |
논리 AND | 왼쪽에서 오른쪽 |
12 | ` | ` | |
13 | ?: |
조건부 연산자 (삼항 연산자) | 오른쪽에서 왼쪽 |
14 | = += -= *= /= %= &= ` |
= ^=` `<<=` `>>=` |
대입 및 복합 대입 연산자 |
15 | , |
콤마 연산자 | 왼쪽에서 오른쪽 |
결합성은 연산자 우선순위가 같을 때 어느 쪽 먼저 연산할 것인지 나타내는것이다.
또한 덧셈 연산은 교환법칙이 성립한다.
산술 연산자
수학에 대한 상식을 옮긴 문법으로 +, -, *, / 으로 표시하며 각각 덧셈, 뺄셈, 곱셈, 나눗셈을 의미한다.
덧셈, 뺄셈 연산자
#include <stdio.h>
int main(void){
int Result = 0;
Result = 2 + 4 - 5;
printf("Result : %d\n", Result);
return 1;
}
실행결과
❯ ./welcome
Result : 1
덧셈과 뺄셈은 우선순위가 같기에 결합성을 고려해야 한다. 두 연산자 모두 결합성이 왼쪽에서 오른쪽 방향이므로 2 + 4 연산 먼저 수행한다.
2 + 4를 연산한 결과는 6인데, 덧셈의 임시결과이다. 이어서 6에서 5를 뺄셈을 수행한다. 결과는 1이 된다. 중요한것은 임시결과 6은 다른 연산에 참여하여 또 다른 임시결과를 얻는 과정에서 유실 된다.
임시결과 6이 필요하다면 유실 전에 저장 해야한다. 아래와 같은 방법으로 임시결과를 저장할수있다.
#include <stdio.h>
int main(void){
int Result = 0;
Result = 2 + 4;
printf("Result : %d\n", Result - 5);
return 1;
}
실행결과
❯ ./welcome
Result : 1
이형자료 간의 연산 및 형승격
#include <stdio.h>
int main(void){
char ch = 'A';
printf("%c\n", ch);
printf("%c\n", ch + 1);// char + int 는 int 형식.
printf("%c\n", 'A' + 2);
printf("%d\n", 2.0 + 2); // double + int 는 double 형식.
printf("%f\n", 2.0 + 2);
return 1;
}
실행결과
❯ ./welcome
A
B
C
0
4.000000
이형자료 간의 연산이 문법적으로 전혀 하자가 없는 경우, 임시결과의 자료형은 연산에 참여한 피연산자 중 정보 표현 범위가 더 넓은 자료형이 된다. 예를 들어 char + int 는 int 형식이 되고, double + int 는 double 형식이 된다.
이처럼 연산의 결과가 피연산자의 자료형보다 표현범위가 넓은 형식으로 변경되는 현상을 형승격(type promotion) 이라고 한다.
곱셈,나눗셈 연산자
곱셈 연산자는 기호는 '*(에스터리스크, ,asterisk)'로 표시하고, 나눗셈 연산자는 '/(슬래쉬, slash)'로 표시한다.
#include <stdio.h>
int main(void){
int x = 10;
printf("%d\n", x * 10);
printf("%d\n", x * 10.0); // int * double 은 %d로 출력 불가능하다.
printf("%d\n", x / 10); // int / int 는 int 형식이다. 소수점 이하는 버려짐.
printf("%d\n", 5 / 2); // int / int 는 int 형식으로 소수점 이하는 버려짐.
printf("%f\n", 5 / 2); // int / int 는 int 형식인데,정수2를 %f로 출력하는것과같음.
printf("%f\n", 5.0 / 2); // double / int 는 double 형식이다.
return 1;
}
실행결과
❯ ./welcome
100
0
1
2
0.000000
2.500000
나눗셈 연산은 정수를 정수로 나눈 결과는 정수이고, 절대 0으로 나눌수 없다.
정수 5를 정수 2로 나누면 연산결과 2.5 이지만 역시 정수이므로 소수점 이하 정보가 전부 버림 처리 된다. 따라서 심각한 연산 오류가 발생할수 있다. 주의 해야한다.
#include <stdio.h>
int main(void){
int nInput = 0;
scanf("%d", &nInput);
printf("%d\n", 10 / nInput);
return 1;
}
실행결과
0입력.
프로그램 죽음.
이 오류의 핵심은 0으로 나눈것이다. 컴퓨터는 10을 0으로 나눈다면 10에서 0을 뺄것이다. 그러면 몫은 1이 된고 나머지는 그대로 10 이다. 이 뺄셈연산을 아무리 해도 끝나지 않기때문에 프로그램이 죽는것이다.
나머지 연산자
나머지 연산자는 나눗셈과 원리는 같다. 하지만 몫을 활용하는 것이 아니라 나머지를 활용한다. 몫이 필요할때는 나눗셈을 하고 나머지가 필요할 때는 나머지 연산을 통해 나머지를 구한다. 나머지 연산자 기호는 '%' 이다.
#include <stdio.h>
int main(void){
int x = 0;
printf("숫자 입력 : ");
scanf("%d", &x);
printf("몫 : %d\n", x / 3);
printf("나머지 : %d\n", x % 3);
return 1;
}
실행결과
❯ ./welcome
숫자 입력 : 13
몫 : 4
나머지 : 1
위 예제 코드 방식으로 몫을 구하고 싶으면 나눗셈, 나머지를 구하고싶으면 나머지 연산을 하면 된다.
또 알아야할 사실은 나머지 연산자의 피연산자로 실수가 사용될 수 없다는 점이다.
프로그래머 입장에서 나머지 연산자는 연산자 그 이상의 의미가 있다. 어떤수를 3으로 나눈다면 나올수 있는 결과는 0,1,2 뿐이다. 나머지 연산은 제수만큼 경우의 수를 가진다. 나머지 연산을 사용하면 일정 범위로 경우의 수를 만들수 있기 때문에 중요하다.
대입 연산자
대입 연산자는 '=' 기호로 표시되는 연산자로 오른쪽 피연산자의 값을 왼쪽 피연산자로 정보를 복사하는 연산을 수행한다.
단순 대입 연산자
단순 대입 연산자는 복사와 덮어쓰기 기능이 결합한 기본적인 대입 연산자 이다.
#include <stdio.h>
int main(void){
int x = 0, nInput = 0;
scanf("%d", &nInput);
x = nInput; // 사용자로부터 입력받은 값을 x 에 단순 대입.
printf("%d\n", x);
return 1;
}
실행결과
❯ ./welcome
20
20
x = nInput 연산을 수행하고 왼쪽 피연산자의 정보를 덮어쓴다. 기존 x 값은 유실 된다.

그림을 보면 대입 연산자의 왼쪽 피연산자를 l-value 라고 하고 오른쪽 피연산자를 r-value라고 한다. l-value 의 l 은 left 도 의미하지만 locator 라는 의미도 있다. 즉, 정보를 담을 수 있는 변수를 말한다. 정보를 담을수 있는 변수가 아니라면 l-value가 될 수 없다.
#include <stdio.h>
int main(void){
char szBuffer[32] = {0};
3 = 4; // 상수에 대입 연산을 수행할수 없다.
szBuffer = 'A'; // 배열의 이름은 주소상수 이다. 변수가 아니다.
return 1;
}
실행결과
컴파일 에러.
3도 상수이고 4도 상수이다. 상수에 상수를 대입한다는것은 말이 안된다. 변수의 본질은 메모리이고 상수의 본질은 정보이다. 변수에 상수를 담는 것이 일반적이라는 상식을 따르면 된다.
szBuffer='A' 를 보면 문제가 없는것 같기도 하다. 그런데 배열 이름은 메모리의 주소(상수)라는 것이다. 이름이 있어 변수라고 생각하기 쉽지만 사실은 메모리의 주소 이다. 따라서 szBuffer 는 l-value 가 될수 없다.
두 변숫값 교환
#include <stdio.h>
int main(void){
int x = 10, y = 20, nTmp = 0;
printf("교환 전 %d, %d\n", x,y);
nTmp = x;
x = y;
y = nTmp;
printf("교환 후 %d, %d\n", x,y);
return 1;
}
실행결과
❯ ./welcome
교환 전 10, 20
교환 후 20, 10

그림처럼 세번의 단순 대입이 필요하다.
복합 대입 연산자?
복합 대입 연산자는 기존의 단순 대입 연산자에 산술 연산자나 비트 연산자를 조합하여 새로운 하나의 연산자를 만든 것 이다. 실제로 연산을 두 번 한다.
#include <stdio.h>
int main(void){
int nResult = 0;
nResult += 3; //nResult = nResult + 3;
printf("%d\n", nResult);
nResult *= 3; //nResult = nResult * 3;
printf("%d\n", nResult);
nResult /= 3; //nResult = nResult / 3;
printf("%d\n", nResult);
nResult -= 3; //nResult = nResult - 3;
printf("%d\n", nResult);
nResult %= 3; //nResult = nResult % 3;
printf("%d\n", nResult);
return 1;
}
실행결과
❯ ./welcome
3
9
3
0
0
+= 연산자는 누적을 구현하는 데 매우 중요한 역할을 한다.
#include <stdio.h>
int main(void){
int nInput = 0, nTotal = 0;
scanf("%d", &nInput);
nTotal += nInput;
scanf("%d", &nInput);
nTotal += nInput;
scanf("%d", &nInput);
nTotal += nInput;
printf("Total : %d\n", nTotal);
return 1;
}
실행결과
❯ ./welcome
1
2
3
Total : 6
위 코드에서 누적의 대상이 되는 변수는 누적 연산에 앞서 반드시 0으로 초기화를 해주어야한다. 그렇지 않으면 그 변수에 어떤 값이 들어있는지 알수가 없다. 알수 없는 값이란 쓰레기 값이라고 한다.
형변환 연산자
형변환(type cast) 연산자는 피연산자의 자료형을 새로운 자료형으로 변경하는 단항 연산자 이다. 형변환 연산자는 강제로 형변환을 시킨다. 그렇기 때문에 적절한 변환이면 상관없지만 그렇지 않으면 정보의 손실이 발생하고 심각한 논리적 오류가 발생할 수 있다.
#include <stdio.h>
int main(void){
int x = 5;
printf("%f\n", (double)5 / 2); // double / int > double
printf("%f\n", (double)x / 2); // double / int > double
printf("%f\n", x / (double)2); // int / double > double
printf("%f\n", (double)(x / 2)); // double
return 1;
}
실행결과
❯ ./welcome
2.500000
2.500000
2.500000
2.000000
실수형을 정수형으로 형변환하면 소수점 이하 정보는 모두 버려진다.
단항 증감 연산자
단한 증감 연산자란 각각 '++', '--' 로 표시 되는 연산자 이다. 피연산자에 저장된 정보를 1씩 증가(감소)시킨다.라는 의미이다.
#include <stdio.h>
int main(void){
int x = 10;
x += 1;
printf("%d\n", x);
++x;
printf("%d\n", x);
return 1;
}
실행결과
❯ ./welcome
11
12
단항 증감 연산자는 복합 대입 연산자인 +=,-= 과 비슷 하다. 그러나 연산자의 우선순위는 전혀 다르다.
++x는 x 에 1을 누적하는 연산이다. 그런데 이때는 누적보다는 계수(counting) 라는 표현이 적합하다.
단항 증감 연산이 조금 어려운 이유는 전위식 표기(++x) 일 경우엔 연산자 우선순위가 높지만 후위식 표기(x++)인 경우 우선순위가 최하위가 된다.
비트 연산자
비트 연산자는 자료형을 근거로 해석된 정보가 아닌 일정 길이의 메모리에 담긴 2진수 정보를 그대로 비트 단위로 계산하는 연산자이다. 만일 8비트 정보라면 여덟 개의 비트 각각에 대해 여덟 번 연산하는 것이다.
비트 연산자는 AND(&), OR(|), XOR(^), NOT(~), Shift left(<<), Shift right(>>) 가 있다.
비트 연산자의 사용
#include <stdio.h>
int main(void){
int nData = 0x11223344;
printf("%08X\n", nData & 0x00FFFF00);
printf("%08X\n", nData | 0x2211FFFF);
printf("%08X\n", nData ^ 0x2211FFFF);
printf("%08X\n", ~nData);
printf("%08X\n", nData >> 8);
printf("%08X\n", nData << 16);
return 1;
}
실행결과
❯ ./welcome
00223300
3333FFFF
3333CCBB
EEDDCCBB
00112233
33440000
AND 연산은 두 수 모두 1(참)이어야 결과가 1이다.
OR 연산은 한쪽만 1이여도 1(참)이다.
XOR 연산은 두 값이 서로 같을때 0이다.
NOT 연산은 0과 1을 뒤집는 연산이다.
마스크 연산
특정 비트열에 비트 AND 연산함으로써 일정 구간이 모두 0이 되도록 의도한 연산을 마스크(mask) 연산 이라고 한다. 마스크란 불필요한 정보를 가려서 필요한 정보만 골라냄을 의미한다.
#include <stdio.h>
int main(void){
int nData = 0x11223344;
printf("%08X\n", nData & 0xFF000000);
printf("%08X\n", nData & 0x00FF0000);
printf("%08X\n", nData & 0x0000FF00);
printf("%08X\n", nData & 0x000000FF);
return 1;
}
실행결과
❯ ./welcome
11000000
00220000
00003300
00000044