연산자는 값을 가지고 무언가를 하는 기호입니다. +로 더하고, >로 비교하고, &&로 조건을 조합합니다. 대부분의 프로그래밍 언어에 있는 연산자들이지만, 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로 여러 경우를 처리하는 법을 알아봅니다.