iBetter Books
수정

연산자는 값을 가지고 무언가를 하는 기호입니다. +로 더하고, >로 비교하고, &&로 조건을 조합합니다. 대부분의 프로그래밍 언어에 있는 연산자들이지만, Dart에는 특별히 유용한 것들이 몇 가지 더 있습니다.

이번 챕터에서는 Dart의 연산자를 종류별로 완전히 정리합니다. 처음 보는 것도 있겠지만, 하나하나 보다 보면 어렵지 않습니다.

산술 연산자

숫자를 계산하는 가장 기본적인 연산자들입니다.

void main() {
  int a = 17;
  int b = 5;

  print(a + b);   // 22 — 덧셈
  print(a - b);   // 12 — 뺄셈
  print(a * b);   // 85 — 곱셈
  print(a / b);   // 3.4 — 나눗셈 (결과는 항상 double)
  print(a ~/ b);  // 3   — 정수 나눗셈 (몫)
  print(a % b);   // 2   — 나머지
  print(-a);      // -17 — 단항 부정

  double x = 2.7;
  print(x.ceil());   // 3  — 올림
  print(x.floor());  // 2  — 내림
  print(x.round());  // 3  — 반올림
  print(x.abs());    // 2.7 — 절댓값
}

증가/감소 연산자도 있습니다.

void main() {
  int count = 5;

  count++;   // count = count + 1
  print(count);  // 6

  count--;   // count = count - 1
  print(count);  // 5

  // 전위 vs 후위
  int a = 10;
  print(a++);  // 10 — 출력 후 증가
  print(a);    // 11

  int b = 10;
  print(++b);  // 11 — 증가 후 출력
  print(b);    // 11
}

비교 연산자

두 값을 비교하여 bool 값을 반환합니다.

void main() {
  int x = 10;
  int y = 20;

  print(x == y);   // false — 같음
  print(x != y);   // true  — 다름
  print(x < y);    // true  — 작음
  print(x > y);    // false — 큼
  print(x <= 10);  // true  — 이하
  print(x >= 10);  // true  — 이상

  // 문자열 비교
  String a = 'hello';
  String b = 'hello';
  print(a == b);   // true  — 내용이 같으면 true

  // 주의: 객체 동일성 비교는 identical() 사용
  // Dart에서 == 는 값 비교이므로 보통 == 를 씁니다
}

논리 연산자

bool 값들을 조합합니다.

void main() {
  bool isAdult = true;
  bool hasTicket = false;
  bool isVIP = true;

  // && — 둘 다 true여야 true
  print(isAdult && hasTicket);   // false

  // || — 하나라도 true면 true
  print(isAdult || hasTicket);   // true

  // ! — 반전
  print(!hasTicket);             // true

  // 복합 조건
  bool canEnter = isAdult && (hasTicket || isVIP);
  print(canEnter);  // true (어른이면서 티켓 또는 VIP)
}

할당 연산자

값을 변수에 넣는 연산자입니다.

void main() {
  int score = 0;

  score = 80;    // 기본 할당
  print(score);  // 80

  score += 10;   // score = score + 10
  print(score);  // 90

  score -= 5;    // score = score - 5
  print(score);  // 85

  score *= 2;    // score = score * 2
  print(score);  // 170

  score ~/= 3;   // score = score ~/ 3 (정수 나눗셈)
  print(score);  // 56

  score %= 10;   // score = score % 10
  print(score);  // 6
}

??= — null 조건 할당

이 연산자는 Dart에서 특히 유용합니다. 변수가 null일 때만 값을 할당합니다.

void main() {
  String? name;

  name ??= '기본 이름';   // name이 null이므로 할당됨
  print(name);             // 기본 이름

  name ??= '다른 이름';   // name이 이미 값 있으므로 무시됨
  print(name);             // 기본 이름 (변경 안 됨)
}

캐스케이드 연산자 (..)

캐스케이드 연산자는 Dart만의 독특한 문법입니다. 같은 객체에 여러 메서드를 연속으로 호출할 때 씁니다.

보통 이렇게 씁니다.

void main() {
  var buffer = StringBuffer();
  buffer.write('Hello');
  buffer.write(', ');
  buffer.write('Dart!');
  print(buffer.toString());  // Hello, Dart!
}

캐스케이드 연산자를 쓰면 더 간결하게 쓸 수 있습니다.

void main() {
  var buffer = StringBuffer()
    ..write('Hello')
    ..write(', ')
    ..write('Dart!');
  print(buffer.toString());  // Hello, Dart!
}

..는 메서드를 호출하되 반환값을 무시하고, 항상 원래 객체를 반환합니다. 그래서 계속 .으로 이어 부를 수 있습니다.

Flutter에서는 이 패턴이 매우 자주 나옵니다.

// Flutter에서 자주 보는 패턴 (지금은 느낌만 파악)
// var paint = Paint()
//   ..color = Colors.blue
//   ..strokeWidth = 2.0
//   ..style = PaintingStyle.stroke;

null-aware 버전인 ?..도 있습니다. 객체가 null이면 전체 캐스케이드를 건너뜁니다.

void main() {
  StringBuffer? buffer;

  buffer?..write('Hello')..write(' World');  // null이므로 아무것도 안 함
  print(buffer);  // null

  buffer = StringBuffer();
  buffer?..write('Hello')..write(' World');  // 실행됨
  print(buffer);  // Hello World
}

타입 테스트 연산자

변수의 타입을 확인하거나 변환할 때 씁니다.

is — 타입 확인

void main() {
  Object value = 'Hello';

  print(value is String);   // true
  print(value is int);      // false
  print(value is! String);  // false — is의 반대

  // is로 확인하면 그 블록 안에서 타입이 자동으로 좁혀짐
  if (value is String) {
    // 여기서 value는 String으로 취급됨 (스마트 캐스트)
    print(value.toUpperCase());  // HELLO
  }
}

as — 타입 변환 (캐스팅)

void main() {
  Object value = 'Hello, Dart!';

  // as로 강제 타입 변환
  String text = value as String;
  print(text.length);  // 12

  // 잘못된 타입으로 as 하면 런타임 오류
  // int number = value as int;  // 오류! String을 int로 변환 불가
}

as는 타입이 확실할 때만 씁니다. 확실하지 않으면 is로 먼저 확인하는 것이 안전합니다.

void printInfo(Object value) {
  if (value is String) {
    print('문자열: ${value.toUpperCase()}');
  } else if (value is int) {
    print('정수: ${value * 2}');
  } else if (value is double) {
    print('실수: ${value.toStringAsFixed(2)}');
  } else {
    print('알 수 없는 타입: ${value.runtimeType}');
  }
}

void main() {
  printInfo('hello');  // 문자열: HELLO
  printInfo(42);       // 정수: 84
  printInfo(3.14);     // 실수: 3.14
  printInfo(true);     // 알 수 없는 타입: bool
}

조건 연산자

간단한 if-else를 한 줄로 쓸 때 씁니다.

void main() {
  int score = 75;

  // 삼항 연산자
  String grade = score >= 60 ? '합격' : '불합격';
  print(grade);  // 합격

  // 중첩도 가능하지만 가독성이 떨어짐
  String level = score >= 90 ? 'A' : score >= 80 ? 'B' : score >= 70 ? 'C' : 'D';
  print(level);  // C
}

연산자 우선순위

여러 연산자가 섞이면 어떤 것이 먼저 실행될까요. 수학과 비슷하게 *+보다 먼저입니다.

void main() {
  print(2 + 3 * 4);   // 14 (곱셈 먼저)
  print((2 + 3) * 4); // 20 (괄호 먼저)

  // 헷갈리면 괄호를 쓰는 것이 좋습니다
  bool result = true || false && false;  // &&가 ||보다 먼저
  print(result);  // true (false && false = false, true || false = true)
}

복잡한 조건에서는 괄호를 써서 의도를 명확히 하는 것이 좋습니다.

연산자 총정리 예제

void main() {
  // 기본 계산
  double principal = 1000000;  // 원금
  double rate = 0.05;          // 이율 5%
  int years = 3;

  double interest = principal * rate * years;
  double total = principal + interest;

  print('원금: ${principal.toStringAsFixed(0)}원');
  print('이자: ${interest.toStringAsFixed(0)}원');
  print('합계: ${total.toStringAsFixed(0)}원');

  // 조건 판단
  bool isHighRate = rate >= 0.05;
  bool isLongTerm = years >= 5;
  String productType = (isHighRate && isLongTerm) ? '우수 상품' : '일반 상품';
  print('상품 유형: $productType');

  // null 처리
  String? discountCode = null;
  double finalPrice = total;

  discountCode ??= 'DEFAULT';
  print('할인 코드: $discountCode');

  // 타입 테스트
  Object amount = total;
  if (amount is double) {
    print('최종 금액: ${amount.toStringAsFixed(0)}원');
  }

  // 캐스케이드로 보고서 작성
  var report = StringBuffer()
    ..writeln('=== 이자 계산 보고서 ===')
    ..writeln('원금: $principal')
    ..writeln('이율: ${rate * 100}%')
    ..writeln('기간: $years년')
    ..writeln('이자: $interest')
    ..writeln('합계: $total');

  print(report.toString());
}

PART 02가 끝났습니다. 변수와 상수, 기본 타입, 타입 추론, null과 Null Safety, 그리고 연산자까지 Dart의 기초 중의 기초를 모두 다루었습니다. 이 내용들이 앞으로 모든 코드의 바탕이 됩니다.

다음 PART 03에서는 흐름 제어를 배웁니다. if-else로 갈림길을 만들고, for와 while로 반복을 표현하며, switch로 여러 경우를 처리하는 법을 알아봅니다.