입출력 함수와 연산자 - 2

연산자의 기능과 사용방법, 우선순위.

  • 학습 목표

    1. 연산자.
    2. 연산자의 우선순위 이해.
  • 주요 용어

    • 연산자 (operator) : 임의의 자료에 대해 각종 연산을 수행하도록 하는 기호.
    • 이항 연산자 : 2개의 자료를 재상으로 산술적인 처리를 수행하는 연산자.
    • 단항 연산자 : 1개의 자료만을 대상으로 산술적인 처리를 수행하는 연산자.
    • 관계 연산자 : 피 연산자에 대한 대.소 관계를 비교하는 연산자.
    • 논리 연산자 : 피 연산자에 대한 논리연산(AND, OR, NOT)을 수행하는 연산자.
    • 대입 연산자 : '='를 사용하여, 연산자의 오른쪽을 왼쪽에 대입하는 데 사용.
    • 조건 연산자 : 주어진 조건의 만족여부에 따라 지정된 수식을 수행하는 연산자.
    • 비트 연산자 : 수치를 2진수로 변환하여 bit 단위의 연산을 수행하는 연산자.
    • 기타 연산자 : sizeof(), cast(형변환), &(주소 연산자), *(내용 연산자) 등.
    • 연산자 우선순위 : 모든 연산자에는 연산자 우선순위가 정해져 있음.

연산자

임의의 자료에 대해 각종 연산을 수행하도록 하는 기호.

  • 연산자의 종류
구분 연산자
산술 연산자 + - * / % ++ --
관계 연산자 > < >= <= == !=
논리 연산자 & || !
대입 연산자 += -= *= /= %= <<= >>= !=
조건 연산자 ?:
비트 연산자 & | ^ ~ << >>
기타 연산자 sizeof() cast & .
  #include <stdio.h>

  int main()
  {
    int x, y;
    x = 10, y = 3;

    printf("x + y = %d\n", x + y);
    printf("x / y = %d\n", x / y);
    printf("x %% y = %d\n", x % y);
    printf("y %% x = %d\n", y % x);
    return 0;
  }

산술 연산자

피 연산자에 대해 사칙연산을 포함한 각종 산술연산을 수행하는 연산자.

구분 연산자 기능
이항 연산자 + - * / 가 감 승 제를 계산 i + 5
이항 연산자 % 나눗셈의 나머지 계산 i % 5
단항 연산자 - 부호의 반전 -5
단항 연산자 ++ 1 증가 i++, ++i
단항 연산자 -- 1 감소 i--, --i
  #include <stdio.h>

  int main()
  {
    int x = 5, a, b;
    a = ++x * x--;
    b = x * 10;

    printf("a = %d b = %d x = %d\n", a, b, x);
    return 0;
  }

관계 연산자

피 연산자에 대한 대,소 관계를 비교하는 연산자.

연산자 기능
== 같은가의 여부 비교 a == b : a와 b는 같다.
!= 다른가의 여부 비교 a != b : a와 b는 같지 않다.
> >= < <= 대,소 관계 비교 a >= b : a는 b보다 크거나 같다.
#include <stdio.h>

int main()
{

  int a = 4, b, c, d;

  b = a > 2;
  printf("b = %d\n", b);

  c = a < 2;
  printf("c = %d\n", c);

  d = a == 4;
  printf("d = %d\n", d);
  return 0;
}

논리 연산자

피 연산자에 대해 논리 연산을 수행하는 연산자.

연산자 기능
&& 논리곱(AND) : 양쪽 모두 참일 때만 참 a && b
|| 논리합(OR) : 양쪽 중 하나라도 참이면 참 a || b
! 논리 부정(NOT) : 오른쪽이 참이면 거짓, 거짓이면 참 !a
#include <stdio.h>

int main()
{

  int a = 4, b = 7, c, d, e;

  c = a > 2 & b <= 7;
  printf("c = %d\n", c);

  d = a < 2 || b <= 7;
  printf("d = %d\n", d);

  e = !a;
  printf("e = %d\n", e);

  return 0;
}

대입 연산자

연산자의 오른쪽을 왼쪽에 대입하는 연산자.

연산자 기능
= a = 5 a에 5를 대입
+= a += 5 a += 5 더한 후 결과를 대입
-= a -= 5 a -= 5 뺀 후 결과를 대입
*= a *= 5 a *= 5 곱한 후 결과를 대입
/= a /= 5 a /= 5 나눈 후 결과를 대입
%= a %=5 a %=5 나눈 후 그 나머지를 대입
&= a &= 5 a &= 5 bit 단위 AND 연산을 한 후 결과를 대입
|= a |= 5 a |= 5 bit 단위 OR 연산을 한 후 결과를 대입
^= a ^= 5 a ^= 5 bit단위 XOR 연산을 한 후 결과를 대입
<<= a <<= 5 a <<= 5 a의 값을 5 bit 좌로 이동 후 결과를 대입
>>= a >>= 5 a >>= 5 a의 값을 5비트 우로 이동 후 결과를 대입
#include <stdio.h>

int main()
{
  int a = 10, b = 3, c = 1;

  a *= (b - 1);
  b /= 2 + 3;
  c += 2;

  printf("a = %d, b = %d, c = %d\n", a, b, c);
  return 0;
}

조건 연산자

주어진 조건의 만족 여부에 따라 지정된 수식을 수행하는 연산자.

  • 형식 : (조건) ? 수식1 : 수식2;
  • 기능 : 조건이 성립하면 수식1, 성립하지 않으면 수식2를 실행
  • 사용 예 : x = (5 > 2) ? 1 : 0;
#include <stdio.h>

int main()
{
  int a = 10, b;

  b = (a > 15) ? (a + 1) : (a - 1);

  printf("b = %d\n", b);
  return 0;
}

비트 연산자

수치에 대해 bit 단위의 연산을 수행하는 연산자.

연산자 기능
& (bit AND) a & b 두 bit가 모두 1일 때만 1
| (bit OR) a | b 두 bit중 하나라도 1이면 1
^ (bit XOR) a ^ b 두 bit가 서로 다를 때만 1
~ (bit NOT) ~a 1은 0, 0은 1로 치환
<< (bit 좌로 이동) a << 2 2 bit 왼쪽으로 이동
>> (bit 우로 이동) a >> 2 2 bit 오른쪽으로 이동

비트 연산의 예

x -> 1 0 1 1 0 0 1 1
y -> 0 1 0 0 1 0 0 1
x & y -> 0 0 0 0 0 0 0 1
x | y -> 1 1 1 1 0 0 1 1
x ^ y -> 1 1 1 1 1 0 1 0
~x -> 0 1 0 0 1 1 0 0
x << 2 -> 1 1 0 0 1 1 0 0
x >> 2 -> 0 0 1 0 1 1 0 0

기타 연산자

연산자 기능
sizeof() 지정한 자료형, 수식, 변수가 차지하는 기억공간의 크기(byte)를 구함
cast(형변환) 지정한 자료형을 다른 자료형으로 강제정으로 바꿈
& 주소 연산자로 피 연산자의 주소를 나타냄
* 내용 연산자로 피 연산자의 내용을 가져옴

sizeof()

  • 형식 : sizeof(자료)
  • 기능 : 지정한 자료(자료형, 상수, 변수, 수식)에 대한 기억 장소의 크기를 구함
  • 사용 예 : sizeof(int)
#include <stdio.h>

int main()
{
  float a = 3.14;

  printf("sizeof data type int : %d byte\n", sizeof(int));
  printf("sizeof a, type of float  : %d byte\n", sizeof(a));
  // printf("b = %d", b);

  return 0;
}

cast()

  • 형식 : (형명칭) 자료;
  • 기능 : 이미 지정된 자료의 자료형을 강제적으로 다른 자료형으로 변환한다.
  • 사용 예 : (float) i / j
#include <stdio.h>

int main()
{
  int a = 3, b = 4;

  double c;

  c = (double)a / b;

  printf("result : %f\n", c);
  return 0;
}

연산자 우선 순위

연산자 명 연산자 결합방향 우선순위
괄호, 구조체, 공용체 연산자 () [] -> 좌 -> 우 높음
단항 연산자 ! ~ ++ -- & * sizeof() cast 우 -> 좌
승, 제 (이항 연산자) * / % 좌 -> 우
가, 감 (이항 연산자) + - 좌 -> 우
비트 이동 (이항 연산자) << >> 좌 -> 우
대 소 비교 (이항 연산자) < <= > >= 좌 -> 우
등가 판정 (이항 연산자) == != 좌 -> 우
bit AND (이항 연산자) & 좌 -> 우
bit XOR(이항 연산자) ^ 좌 -> 우
bit OR(이항 연산자) | 좌 -> 우
논리 AND (이항 연산자) && 좌 -> 우
논리 OR(이항 연산자) || 좌 -> 우
조건 연산자 ?: 우 -> 좌
대입 연산자 = += *= ... &= 우 -> 좌
#include <stdio.h>

int main()
{

  int a, b, c;

  a = 10;
  b = 20;
  c = 30;

  printf("a + b * c = %d\n", a + b * c);
  printf("a = b += 2 * c => a = %d\n", a = b += 2 * c);
  printf("a = ( b > c ) ? b : c => a = %d\n", a = (b > c) ? b : c);
  printf("a / b * c = %d\n", a / b * c);
  printf("a *= b = c + 5 => a = %d\n", a *= b = c + 5);

  return 0;
}
728x90
반응형

'Language > C' 카테고리의 다른 글

[C] 함수와 기억 클래스 - 1  (0) 2020.12.20
[C] 선택 제어문과 반복 제어문  (0) 2020.12.20
[C] 입출력 함수와 연산자 - 1  (0) 2020.12.15
[C] 자료형과 선행 처리기  (0) 2020.12.14
[C] C 언어의 개요  (0) 2020.12.13

유지보수나 기능 개선프로젝트를 진행하면서 분기 처리 부분에서 가독성이 떨어지는 코드를 많이 보게 되었다.
종종 이런 코드는 가독성이 떨어지는건 물론이고 코드 작성자 본인도 가독성 문제로 오류를 만들어 내는 로직도 있었다.

 


연산자 우선 순위

좋은 분기분을 만들기 전에 언어별 연산자 우선순위를 알아야 한다.
결합 방법에 대해서는 언어별, 버전별 기준으로 정확하지 않으니 참고만 하면 된다.
언어별 연산자 우선순위는 사칙 연산에서 곱셈/나눗셈이 덧셈/뺄셈보다 먼저 이뤄지는 것처럼 이해하면 된다.

 


java 연산자 우선순위

우선순위 연산 기호 결합 방향
1 [], . >>>>
2 x++, x-- <<<<
3 ++x, --x,_x, -x, ~, !, (type) <<<<
4 *, /, % >>>>
5 +, - >>>>
6 <<, >>, >>> >>>>
7 <, >, <=, >=, instanceof >>>>
8 ==, != >>>>
9 & >>>>
10 ^ >>>>
11   >>>>
12 && >>>>
13    
14 ? a : b <<<<
15 =, +=, -=, *=, /=, %=, &=, ^=, !=, <<=, >>=, >>>= <<<<

 

javascript 연산자 우선순위

자바스크립트 연산자 우선순위(비트 연산자 제외)
순위 기능 연산자
1 괄호 ()          
2 증감/논리 연산자 not ++ -- !      
3 산술 연산자 곱셈 * / %      
4 산술 연산자 덧셈 + -        
5 비교 연산자 대소 < <= > >=    
6 비교 연산자 같음 == === != !==    
7 논리 연산자 and &&          
8 논리 연산자 or ||          
9 대입 연산자 = += -= *= /= %=

 

python 연산자 우선순위

우선순위 연산자 설명
1 (값...), [값...], {키: 값...}, {값...} 튜플, 리스트, 딕셔너리, 세트 생성
2 x[인덱스], x[인덱스:인덱스], x(인수...), x.속성 리스트(튜플) 첨자, 슬라이싱, 함수 호출, 속성 참조
3 await x await 표현식
4 ** 거듭제곱
5 +x, -x, ~x 단항 덧셈(양의 부호), 단항 뺄셈(음의 부호), 비트 NOT
6 *, @, /, //, % 곱셈, 행렬 곱셈, 나눗셈, 버림 나눗셈, 나머지
7 +, - 덧셈, 뺄셈
8 <<, >> 비트 시프트
9 & 비트 AND
10 ^ 비트 XOR
11    
12 in, not in, is, is not, <, <=, >, >=, !=, == 포함 연산자, 객체 비교 연산자, 비교 연산자
13 not x 논리 NOT
14 and 논리 AND
15 or 논리 OR
16 if else 조건부 표현식
17 lambda 람다 표현식

 


단축 평가 (Short-circuit Evaluation)

단축 평가는 and(&&) 연산자와 or(||) 연산자의 원리를 이해하면 쉽다.

    if( condition1 && condition2 && condition3 && condition3 )

위와 같이 && 연산자로 여러 boolean 값의 상황이 있을 때, && 연산자는 하나의 조건이라도 false면 결과가 false 이기 때문에,
condition1이 false라면 굳이 뒤에 있는 2, 3, 4를 확인하지 않는다.

    if( condition1 || condition2 || condition3 || condition3 )

마찬 가지로 || 연산자로 여러 boolean 값의 상황이 있을 때는 condition1이 true면 그다음 조건은 검사하지 않는다.
이런 단축 평가의 개념으로 boolean 값이 간단하게 나오는 상황을 앞 쪽에 적용하면 빠르게 처리할 수 있다.

 


가독성 높은 분기 처리 방법

중첩 if

일반적으로 개발할 시 긍정적(유효한) 상황을 염두하고 개발을 하는데,
지속적으로 유효성을 체크하는 로직이 들어갈 경우 if 문이 계층구조처럼 구성이 만들어지기도 함.

    Object getUserInfo() {
      Session session = getSession();
      if( session != null ) {
        User user = session.getAttribute("loginUser");
        if( user != null ) {
          String id = user.getId();
          if( id != null ) {
            // 처리할 로직
          }
        }
      }
    }

 

중첩 if - 개선

역으로 부정적(유효하지 않는) 상황을 염두하고 분기 처리를 하면, 가독성과 구조적으로 좋은 코드가 나옴.
실제 처리할 로직이 시작하기 전에 유효하지 않는 상황으로 분기되면 해당 메소드나 함수를 return이나 예외 처리로 벗어나는 것을 보호절 숙어 라고 함.

    Object getUserInfo() {
      Session session = getSession();
      if( session == null ) return;

      User user = session.getAttribute("loginUser");
      if( user == null ) return;

      String id = user.getId();
      if( id == null ) return;

      // 처리할 로직
    }

 

 


loop 내에서의 분기 처리

    String line;
    while( line = reader.readline() ) {
      if( line.startsWith('#') || line.isEmpty() )
        continue;

      //  처리할 로직
    }

 

 


가독성을 위한 if

좌변에 유동적인 값이나 표현을 넣고, 우변에 상수와 같은 고정 값을 넣어야 가독성이 좋아짐.

    if( 10 <= length )

    //  아래가 더 보기 편함.
    if( length >= 10 )

 

 


if/else 블록의 순서

  • 가능하다면 if 조건 안에는 긍정 조건을 넣어야 가독성이 좋음.
  • 단, if/else 처리 로직 중 간단한 로직을 먼저 if 절에 넣는 것이 가독성이 좋음.
  • 단, 보호절 숙어가 우선순위가 높음. 부정의 조건을 넣어서 계층 구조를 만들지 않을 수 있다면 이게 더 낫다는 의미.
  •   if( hasAuth ) {
        // 간단하고 긍정적인 내용
      } else {
        // 상대적으로 복잡하고 긴 내용
      }

 


삼항 연산자

  • 간단한 구문일 경우 : 삼항 연산자.
  • 복잡한 구문일 경우 : if/else.

삼항 연산자는 코드 한 줄로 가독성을 챙길 수 있으며, 한 줄로 처리하기 복잡할 경우에는 if/else를 사용하는 게 낫다.

  • 삼항 연산자가 이득인 경우
  • let timeType = hour < 12 ? 'am' : 'pm';
  • if/else가 이득인 경우
  •   let timeKor, timeEng;
      if( hour < 12 ) { 
          timeKor = '오전';
        timeEng = 'am';
      } else { 
          timeKor = '오후';
        timeEng = 'pm';
      }

 


복잡한 분기 추출

재사용되거나, 분기 절 내의 복잡한 내용을 메소드로 표현하게 되면 더 나은 가독성을 얻을 수 있다.

  • 기존
  •     if( user.id == post.registerId ) {
            // 사용자가 게시물의 작성자 이다.
        } else {
            // 사용자가 게시물의 작성자가 아니다.
        }
  • 개선
  •     boolean isEditable = isOwner( user, post );
        if( isEditable ) {
            // 사용자가 게시물의 작성자 이다.
        } else {
            // 사용자가 게시물의 작성자가 아니다.
        }
        boolean isOwner( User user, Post post ) { return user.id == post.registerId; }

 


드모르간 법칙

괄호 중첩이 적을수록 가독성이 좋다.

  • 기존
  • if( !(hasfile && !isPrivate) ) return false;
  • 개선
  • if( !hasFile || isPrivate ) return false;

 


복잡한 논리 가독성 향상

한 라인으로 논리를 표현하지 않고, 가독성을 위해 적절한 여러 라인으로 분리하여 보호절과 유사하게 한다.

  • 기존
  •   public class Range {
        private int bgn;
        private int end;
        // this의 bgn이나 end가 other의 bgn이나 end에 속하는지 확인
        private boolean isOverlapsWith( Range other ) {
          return ( this.bgn >= other.bgn && this.bgn < other.end )
              || ( this.end > other.bgn && this.end <= other.end )
              || ( this.bgn <= other.bgn && this.end >= other.end );
        } 
      }
  • 개선
  •   public class Range {
        private int bgn;
        private int end;
        // this의 bgn이나 end가 other의 bgn이나 end에 속하는지 확인
        private boolean isOverlapsWith( Range other ) {
          // this가 시작하기 전에 끝난다.
          if( other.end <= this.bgn ) return false;
          // this가 끝난 후에 시작한다.
          if( other.bgn >= this.end ) return false;
          // 겹친다.
          return true;
        }
      }

 


switch 문의 사용

if-else 문은 각 조건문을 iterate 하며 로직을 결정한다. N개의 if-else 구문이 있다면 N번의 조건 여부를 판단한다.
switch 문은 입력받은 케이스로 로직이 바로 넘어가게 된다.
일반적으로 4개 이상의 조건일 때 if-else 보다 switch 문을 사용하는 것이 성능에 좋다고 한다.

시간 복잡도

다음은 if-else와 switch의 시간 복잡도이다.

  • if-else : O(N)
  • switch : O(logN)
  int num = 5;
  int ret;

  if (num == 0)      ret = num;
  else if (num == 1) ret = num;
  else if (num == 3) ret = num;
  else if (num == 5) ret = num;
  else if (num == 7) ret = num;
  else               ret = num;

  System.out.println(ret);
  int num = 5;
  int ret;

  switch (num) {
    case 0: ret = num; break;
    case 1: ret = num; break;
    case 3: ret = num; break;
    case 5: ret = num; break;
    case 7: ret = num; break;
    default: ret = num; break;
  }
  System.out.println(ret);

 

 


참고자료 :

728x90
반응형

+ Recent posts