# 변수와 상수

#### 변수의 개념

변수는 데이터를 저장하는 메모리 공간에 대한 참조이다. Dart에서는 변수의 타입을 명시하거나 타입 추론을 통해 컴파일러가 자동으로 타입을 결정할 수 있다. 변수를 선언하는 기본적인 방식은 아래와 같다:

```dart
int a = 10; // 정수형 변수 a 선언 및 초기화
double b = 20.5; // 실수형 변수 b 선언 및 초기화
String name = 'Dart'; // 문자열 변수 name 선언 및 초기화
```

변수는 값을 저장하고 변경할 수 있으며, 프로그램 실행 중 데이터에 접근하고 수정하는 데 중요한 역할을 한다.

#### 상수의 개념

상수는 한 번 초기화된 이후 값을 변경할 수 없는 변수이다. Dart에서 상수를 선언할 때는 `const`나 `final` 키워드를 사용한다. `const`는 컴파일 타임 상수이며, `final`은 런타임 상수이다. 즉, `const`는 컴파일 시에 값이 결정되어야 하지만, `final`은 실행 중 한 번만 값을 설정할 수 있다.

```dart
const pi = 3.14159; // 컴파일 타임 상수
final date = DateTime.now(); // 런타임 상수
```

#### 타입 추론

Dart는 타입을 명시하지 않아도 변수의 값을 보고 타입을 추론한다. 이를 통해 더 간결한 코드 작성을 도울 수 있다. 타입 추론을 사용한 변수 선언은 아래와 같다:

```dart
var age = 25; // int 타입으로 추론됨
var height = 180.5; // double 타입으로 추론됨
var name = 'John'; // String 타입으로 추론됨
```

하지만, 추론된 타입은 이후에 변경할 수 없기 때문에 변수의 데이터 타입에 대한 유연성은 제한된다.

#### 변수와 상수의 차이점

변수와 상수의 가장 큰 차이점은 값을 변경할 수 있느냐의 여부이다. 변수는 초기화된 이후에도 값을 변경할 수 있지만, 상수는 한 번 초기화되면 값을 변경할 수 없다. 이 차이점을 바탕으로 다음과 같은 상황에서 각각을 사용할 수 있다:

* **변수**: 프로그램 실행 중 값이 변할 가능성이 있는 데이터
* **상수**: 프로그램 실행 동안 절대 변하지 않는 데이터

이러한 개념을 바탕으로, 프로그램의 구조와 안정성을 고려하여 적절한 곳에 변수를 사용할지, 상수를 사용할지 결정해야 한다.

#### 수학적 표현

변수와 상수를 수학적으로 표현하면, 변수는 함수나 수식에서 바뀔 수 있는 값으로, 상수는 일정한 값을 가지는 항으로 정의된다. 예를 들어:

$$
y = mx + b
$$

이 방정식에서 $m$과 $b$는 상수로, 변하지 않는 값이지만, $x$와 $y$는 변수로, 다양한 입력에 따라 값이 바뀝니다.

#### 변수의 메모리 할당과 스코프

변수는 메모리 공간에 값을 저장하고, 해당 메모리 공간에 접근하여 값을 읽거나 수정할 수 있다. Dart에서 변수의 메모리 할당은 변수의 **스코프**(scope)에 따라 달라진다. 스코프란 변수가 유효한 코드 영역을 의미하며, Dart에서는 전역 스코프와 지역 스코프를 구분할 수 있다.

**전역 변수**

전역 변수는 프로그램 전체에서 접근할 수 있는 변수로, 함수나 클래스 외부에서 선언된 변수이다. 전역 변수는 프로그램이 종료될 때까지 메모리에 할당되어 있으며, 어디에서든지 접근 가능한다.

```dart
int globalVar = 100;

void main() {
  print(globalVar); // 전역 변수 사용 가능
}
```

**지역 변수**

지역 변수는 특정 블록 내에서만 유효한 변수이다. 함수 내부에서 선언된 변수는 해당 함수 내에서만 접근 가능하며, 함수가 종료되면 지역 변수는 메모리에서 해제된다.

```dart
void main() {
  int localVar = 10; // 지역 변수
  print(localVar);   // 함수 내에서만 사용 가능
}
```

지역 변수는 함수가 실행될 때 메모리에 할당되고, 함수가 종료되면 해제된다. 이는 메모리 사용을 최적화하는 중요한 메커니즘이다.

**변수의 Shadowing**

변수 **Shadowing**이란 지역 변수의 이름이 상위 스코프의 변수 이름과 동일할 때, 상위 스코프의 변수가 가려지는 현상을 말한다. Dart에서는 이와 같은 상황이 발생하면 가장 가까운 스코프의 변수가 우선한다.

```dart
int value = 100; // 전역 변수

void main() {
  int value = 200; // 지역 변수, 전역 변수를 가림
  print(value);    // 200 출력
}
```

이 코드에서는 전역 변수 `value`가 있지만, `main()` 함수 내에서 동일한 이름의 지역 변수가 선언되었으므로 전역 변수는 가려진다.

#### 변수의 데이터 타입

Dart는 **정적 타입 언어**이므로, 변수의 데이터 타입이 고정되어 있으며, 선언된 타입 이외의 값을 저장할 수 없다. Dart에서 지원하는 기본 데이터 타입은 다음과 같다:

* **정수형(int)**: 정수 값을 저장

$$
\mathbf{a} = 5
$$

* **실수형(double)**: 소수점을 포함한 실수 값을 저장

$$
\mathbf{b} = 3.14
$$

* **문자열(String)**: 문자열을 저장

$$
\text{str} = \text{"Dart is great"}
$$

* **불리언(bool)**: 논리적 참(True) 또는 거짓(False)을 저장

$$
\mathbf{c} = \text{true}
$$

각각의 데이터 타입은 메모리에서 다른 크기를 차지하며, 올바르게 사용해야 메모리 효율성을 높일 수 있다.

#### 변수의 타입 변환

Dart에서는 암시적(implicit) 타입 변환이 허용되지 않으며, 명시적(explicit)으로 타입을 변환해야 한다. 예를 들어, 정수형 변수를 실수형 변수로 변환하는 방법은 다음과 같다:

```dart
int intValue = 10;
double doubleValue = intValue.toDouble(); // 정수를 실수로 변환
```

이와 같이, 데이터 타입에 맞지 않는 연산을 피하기 위해 명시적인 타입 변환을 해야 한다. 특히, 정수형에서 실수형으로의 변환은 자주 사용하는 예이다.

#### 메모리 효율성을 고려한 변수 사용

변수를 사용할 때 메모리 할당을 신중히 고려하는 것이 중요하다. Dart에서는 `final`과 `const` 키워드를 사용하여 메모리 효율성을 높일 수 있다. 이 키워드를 사용하면 값이 한 번 설정되면 이후에는 변경되지 않으므로, 메모리에서 불필요한 재할당을 방지할 수 있다.

```dart
final int finalValue = 10;
const double pi = 3.14159;
```

`final`과 `const`의 차이점은 `const`는 컴파일 타임 상수이며, `final`은 런타임 상수이다. 이를 적절히 활용하면 코드의 성능을 최적화할 수 있다.

#### 변수 초기화와 값 변경

변수를 선언하고 즉시 값을 할당하는 것을 **초기화**라고 한다. 초기화되지 않은 변수를 사용하려고 할 경우, Dart에서는 오류를 발생시킨다. 따라서 변수는 선언과 동시에 값을 할당하거나, 이후에 반드시 값을 할당해야 한다.

```dart
int x; // 초기화되지 않은 변수
x = 5; // 이후에 값을 할당하여 초기화
```

변수는 선언된 이후, 프로그램 실행 중 자유롭게 값을 변경할 수 있다. Dart는 **동적 타입 언어**가 아닌 **정적 타입 언어**이기 때문에, 변수에 한 번 할당된 데이터 타입은 변경할 수 없다. 예를 들어, `int` 타입 변수에 문자열 값을 할당하려고 하면 오류가 발생한다.

```dart
int num = 10;
num = "String"; // 오류 발생: 'int' 타입 변수에 문자열 할당 불가
```

#### 상수와 변수의 초기화 타이밍

상수와 변수의 초기화 타이밍은 프로그램의 동작에 중요한 영향을 미친다. 상수는 프로그램 실행 중 값이 변경되지 않기 때문에 메모리 관리 측면에서 유리한다. Dart에서는 두 가지 상수 선언 방식인 `const`와 `final`을 지원한다.

* **`const`**: 컴파일 시점에 반드시 초기화되어야 하는 상수이다. 컴파일 타임에 값이 결정되며, 이후 절대 변경되지 않는다.
* **`final`**: 런타임에 값이 결정될 수 있으며, 프로그램 실행 중 한 번만 초기화된다. 한 번 값이 할당되면 변경할 수 없다.

예를 들어, 컴파일 타임에 값이 결정되는 상수는 `const`로, 런타임에 값을 할당해야 하는 경우 `final`을 사용할 수 있다.

```dart
const double pi = 3.14159; // 컴파일 타임 상수
final DateTime now = DateTime.now(); // 런타임 상수
```

#### 변수의 유효 범위 (Scope)

변수의 **유효 범위**란 변수가 참조될 수 있는 코드의 범위를 의미한다. Dart에서는 **블록 스코프** 규칙을 따르며, 변수는 선언된 블록 내에서만 유효한다. Dart에서 변수의 스코프는 크게 두 가지로 나눌 수 있다.

1. **전역 스코프**: 프로그램의 어느 곳에서나 접근 가능한 변수이다. 함수나 클래스 외부에서 선언된 변수는 전역 변수로 취급된다.
2. **지역 스코프**: 특정 함수나 블록 내에서만 접근 가능한 변수이다. 블록 내에서 선언된 변수는 해당 블록을 벗어나면 접근할 수 없다.

**예제: 변수 스코프**

```dart
int globalVar = 100; // 전역 변수

void main() {
  int localVar = 50; // 지역 변수
  if (true) {
    int blockVar = 25; // 블록 변수
    print(globalVar); // 전역 변수에 접근 가능
    print(localVar);  // 지역 변수에 접근 가능
    print(blockVar);  // 블록 변수에 접근 가능
  }
  print(blockVar); // 오류 발생: 블록 변수를 블록 외부에서 접근 불가
}
```

위 예제에서는 `globalVar`는 프로그램의 모든 영역에서 접근할 수 있지만, `localVar`는 `main()` 함수 내부에서만 유효하며, `blockVar`는 `if` 블록 내에서만 유효한다. 이는 변수의 스코프가 메모리 관리와 성능에 영향을 미치는 중요한 요소임을 보여준다.

#### 메모리 관리와 변수의 생명 주기

변수의 **생명 주기**는 변수가 메모리에 할당되고 해제되는 시간을 의미한다. Dart는 **가비지 컬렉션**(garbage collection) 메커니즘을 사용하여 더 이상 참조되지 않는 객체를 자동으로 해제한다. 이는 프로그래머가 수동으로 메모리를 관리하지 않아도 된다는 장점이 있지만, 불필요한 변수 선언과 메모리 낭비를 줄이기 위해 적절한 스코프 사용이 중요하다.

* 전역 변수는 프로그램이 실행되는 동안 계속해서 메모리에 존재하므로, 불필요하게 전역 변수를 남용하면 메모리 사용량이 증가할 수 있다.
* 지역 변수는 함수가 호출될 때 메모리에 할당되고, 함수가 종료되면 자동으로 해제된다.

따라서, 가능한 한 변수는 필요한 범위 내에서만 선언하고 사용해야 메모리 사용을 최적화할 수 있다.

#### 변수와 함수의 상호작용

변수는 함수에서 중요한 역할을 하며, 함수는 변수의 값을 입력받아 처리하고 결과를 반환한다. 함수 내에서 변수를 선언하여 연산을 수행하거나, 외부에서 선언된 변수를 함수로 전달하여 작업할 수 있다. Dart에서 함수와 변수의 상호작용은 다음과 같다:

```dart
int addNumbers(int a, int b) {
  return a + b;
}

void main() {
  int result = addNumbers(3, 5);
  print(result); // 8 출력
}
```

이와 같은 함수 내 변수 사용은 코드의 재사용성을 높이고, 유지보수를 쉽게 만든다.
