iBetter Books
수정

프로그래밍을 처음 배울 때 가장 먼저 마주치는 개념이 있습니다. 바로 "값을 어딘가에 저장한다"는 아이디어입니다. 숫자 42를 계산에 쓰고 싶다면, 그 숫자를 어딘가에 보관해두어야 합니다. 그 보관 장소가 바로 변수(variable)입니다.

Dart에서 변수를 선언하는 방법은 여러 가지입니다. 그리고 변수 중에는 한번 값을 담으면 바꿀 수 없는 특별한 것도 있습니다. 이것이 상수(constant)입니다. 이번 챕터에서는 그릇을 고르는 법, 즉 변수와 상수를 선언하고 사용하는 법을 배웁니다.

var로 변수 선언하기

Dart에서 가장 간단하게 변수를 만드는 방법은 var 키워드를 사용하는 것입니다.

void main() {
  var name = 'Alice';
  var age = 25;
  var height = 165.5;

  print(name);    // Alice
  print(age);     // 25
  print(height);  // 165.5
}

var를 쓰면 Dart가 오른쪽 값을 보고 타입을 스스로 결정합니다. 'Alice'는 문자열이니 nameString 타입, 25는 정수니 ageint 타입이 됩니다. 이것을 타입 추론이라고 하며, 자세한 내용은 Ch 03에서 다룹니다.

변수는 값을 바꿀 수 있습니다.

void main() {
  var score = 80;
  print(score);  // 80

  score = 95;
  print(score);  // 95
}

단, 한번 타입이 정해진 변수에 다른 타입의 값을 넣으면 오류가 납니다.

void main() {
  var score = 80;
  score = '완벽';  // 오류! int 변수에 String을 넣을 수 없음
}

명시적 타입 선언

var 대신 타입을 직접 써서 변수를 선언할 수도 있습니다.

void main() {
  String name = 'Bob';
  int age = 30;
  double weight = 72.5;
  bool isStudent = true;

  print('$name, $age세, 몸무게 ${weight}kg');
}

var와 명시적 타입 선언, 어느 것이 더 좋을까요. 정답은 없지만, 일반적으로 지역 변수에는 var를 많이 씁니다. 타입이 명확할 때는 var가 더 읽기 편하고, 타입을 강조하고 싶을 때는 명시적으로 씁니다.

void main() {
  // 이 둘은 같은 의미입니다
  var message = 'Hello';
  String message2 = 'Hello';
}

final — 런타임 상수

프로그램을 만들다 보면 "이 값은 한번 정하면 절대 바꾸지 않겠다"고 약속하고 싶을 때가 있습니다. 예를 들어 사용자 이름을 한번 받으면 그 세션에서는 변경하지 않겠다거나, 파일 경로를 고정하고 싶을 때입니다.

이럴 때 final을 씁니다.

void main() {
  final String userName = 'Charlie';
  final startTime = DateTime.now();  // 실행 시점에 결정됨

  print(userName);
  print(startTime);

  // userName = 'Dave';  // 오류! final 변수는 재할당 불가
}

final의 핵심은 런타임(프로그램 실행 중)에 값이 결정된다는 점입니다. DateTime.now()처럼 프로그램이 실제로 실행될 때 비로소 알 수 있는 값도 final에 담을 수 있습니다.

const — 컴파일 타임 상수

constfinal보다 한 단계 더 엄격합니다. 컴파일 시점, 즉 코드를 기계어로 바꾸기 전에 이미 값이 결정되어 있어야 합니다.

void main() {
  const pi = 3.14159265;
  const appName = 'My Dart App';
  const maxRetry = 3;

  print('$appName의 최대 재시도 횟수: $maxRetry');
  print('원주율: $pi');
}

const를 쓰면 Dart는 그 값이 프로그램 전체에서 딱 하나만 존재하도록 최적화합니다. 메모리를 아끼고 성능을 높이는 방법입니다.

반면 아래 코드는 오류입니다.

void main() {
  // 오류! DateTime.now()는 실행 시점에 결정되므로 const 불가
  const startTime = DateTime.now();
}

final vs const 정리

구분 재할당 값 결정 시점 사용 예
var 가능 언제든지 일반 변수
final 불가 런타임 사용자 입력, 현재 시각
const 불가 컴파일 타임 수학 상수, 고정 문자열
void main() {
  // var: 값 변경 가능
  var count = 0;
  count = 1;  // OK

  // final: 한번만 할당, 런타임 결정 가능
  final createdAt = DateTime.now();  // OK

  // const: 컴파일 타임에 이미 알아야 함
  const version = '1.0.0';  // OK
  // const now = DateTime.now();  // 오류!
}

const 생성자 맛보기

Dart에서 const는 변수뿐 아니라 객체를 만들 때도 씁니다. Flutter를 공부하다 보면 이런 코드를 자주 보게 됩니다.

// Flutter의 예시 (지금은 느낌만 파악해도 충분합니다)
// const Text('Hello')
// const EdgeInsets.all(16.0)

클래스 앞에 const를 붙이면 그 객체는 컴파일 타임에 생성되고 재사용됩니다. 같은 값의 const 객체는 메모리에 딱 하나만 존재합니다. 지금은 이 개념을 가볍게 봐두고, PART 05 클래스 편에서 자세히 다룹니다.

변수 초기화와 스코프

변수를 선언했지만 아직 값을 넣지 않은 상태를 미초기화라고 합니다. Dart에서는 미초기화 변수를 사용하면 컴파일 오류가 납니다.

void main() {
  int count;
  // print(count);  // 오류! 초기화되지 않은 변수 사용

  count = 10;
  print(count);  // 10
}

값을 나중에 넣을 것이 확실하다면, 일단 선언만 해두고 나중에 초기화할 수 있습니다. 단, 사용하기 전에 반드시 초기화해야 합니다.

스코프(scope)는 변수가 살아있는 범위입니다. 중괄호 {} 안에서 선언된 변수는 그 중괄호 밖에서는 보이지 않습니다.

void main() {
  int outer = 10;

  {
    int inner = 20;
    print(outer);  // 10 — outer는 보임
    print(inner);  // 20 — inner는 보임
  }

  print(outer);  // 10 — outer는 여전히 보임
  // print(inner);  // 오류! inner는 이 범위에서 보이지 않음
}

이 규칙을 이해하면 나중에 함수와 반복문을 배울 때 훨씬 수월합니다.

전체 예제로 복습하기

지금까지 배운 내용을 하나의 예제로 정리해봅니다.

void main() {
  // var: 타입 추론
  var language = 'Dart';
  var version = 3.11;

  // 명시적 타입
  String author = 'Google';
  int year = 2011;

  // final: 런타임 상수
  final launchTime = DateTime.now();

  // const: 컴파일 타임 상수
  const maxVersion = 999;

  print('$language $version (출시: $year년, 개발: $author)');
  print('실행 시각: $launchTime');
  print('최대 버전: $maxVersion');

  // 값 변경
  language = 'Flutter';  // var는 변경 가능
  version = 3.12;

  // author = 'Meta';     // String 타입이지만 var가 아니어도 변경 가능
  // launchTime = ...;    // 오류! final은 재할당 불가
  // maxVersion = 1000;   // 오류! const는 재할당 불가

  print('변경 후: $language $version');
}

다음 챕터에서는 Dart가 기본으로 제공하는 타입들, 즉 숫자와 문자열과 불리언을 더 깊이 살펴봅니다. 각 타입에서 쓸 수 있는 유용한 메서드와 변환 방법도 함께 배웁니다.