변수의 개념
변수는 데이터를 저장하는 메모리 공간에 대한 참조이다. Dart에서는 변수의 타입을 명시하거나 타입 추론을 통해 컴파일러가 자동으로 타입을 결정할 수 있다. 변수를 선언하는 기본적인 방식은 아래와 같다:
int a = 10; // 정수형 변수 a 선언 및 초기화
double b = 20.5; // 실수형 변수 b 선언 및 초기화
String name = 'Dart'; // 문자열 변수 name 선언 및 초기화
변수는 값을 저장하고 변경할 수 있으며, 프로그램 실행 중 데이터에 접근하고 수정하는 데 중요한 역할을 한다.
상수의 개념
상수는 한 번 초기화된 이후 값을 변경할 수 없는 변수이다. Dart에서 상수를 선언할 때는 const
나 final
키워드를 사용한다. const
는 컴파일 타임 상수이며, final
은 런타임 상수이다. 즉, const
는 컴파일 시에 값이 결정되어야 하지만, final
은 실행 중 한 번만 값을 설정할 수 있다.
const pi = 3.14159; // 컴파일 타임 상수
final date = DateTime.now(); // 런타임 상수
타입 추론
Dart는 타입을 명시하지 않아도 변수의 값을 보고 타입을 추론한다. 이를 통해 더 간결한 코드 작성을 도울 수 있다. 타입 추론을 사용한 변수 선언은 아래와 같다:
var age = 25; // int 타입으로 추론됨
var height = 180.5; // double 타입으로 추론됨
var name = 'John'; // String 타입으로 추론됨
하지만, 추론된 타입은 이후에 변경할 수 없기 때문에 변수의 데이터 타입에 대한 유연성은 제한된다.
변수와 상수의 차이점
변수와 상수의 가장 큰 차이점은 값을 변경할 수 있느냐의 여부이다. 변수는 초기화된 이후에도 값을 변경할 수 있지만, 상수는 한 번 초기화되면 값을 변경할 수 없다. 이 차이점을 바탕으로 다음과 같은 상황에서 각각을 사용할 수 있다:
- 변수: 프로그램 실행 중 값이 변할 가능성이 있는 데이터
- 상수: 프로그램 실행 동안 절대 변하지 않는 데이터
이러한 개념을 바탕으로, 프로그램의 구조와 안정성을 고려하여 적절한 곳에 변수를 사용할지, 상수를 사용할지 결정해야 한다.
수학적 표현
변수와 상수를 수학적으로 표현하면, 변수는 함수나 수식에서 바뀔 수 있는 값으로, 상수는 일정한 값을 가지는 항으로 정의된다. 예를 들어:
이 방정식에서 m과 b는 상수로, 변하지 않는 값이지만, x와 y는 변수로, 다양한 입력에 따라 값이 바뀝니다.
변수의 메모리 할당과 스코프
변수는 메모리 공간에 값을 저장하고, 해당 메모리 공간에 접근하여 값을 읽거나 수정할 수 있다. Dart에서 변수의 메모리 할당은 변수의 스코프(scope)에 따라 달라진다. 스코프란 변수가 유효한 코드 영역을 의미하며, Dart에서는 전역 스코프와 지역 스코프를 구분할 수 있다.
전역 변수
전역 변수는 프로그램 전체에서 접근할 수 있는 변수로, 함수나 클래스 외부에서 선언된 변수이다. 전역 변수는 프로그램이 종료될 때까지 메모리에 할당되어 있으며, 어디에서든지 접근 가능한다.
int globalVar = 100;
void main() {
print(globalVar); // 전역 변수 사용 가능
}
지역 변수
지역 변수는 특정 블록 내에서만 유효한 변수이다. 함수 내부에서 선언된 변수는 해당 함수 내에서만 접근 가능하며, 함수가 종료되면 지역 변수는 메모리에서 해제된다.
void main() {
int localVar = 10; // 지역 변수
print(localVar); // 함수 내에서만 사용 가능
}
지역 변수는 함수가 실행될 때 메모리에 할당되고, 함수가 종료되면 해제된다. 이는 메모리 사용을 최적화하는 중요한 메커니즘이다.
변수의 Shadowing
변수 Shadowing이란 지역 변수의 이름이 상위 스코프의 변수 이름과 동일할 때, 상위 스코프의 변수가 가려지는 현상을 말한다. Dart에서는 이와 같은 상황이 발생하면 가장 가까운 스코프의 변수가 우선한다.
int value = 100; // 전역 변수
void main() {
int value = 200; // 지역 변수, 전역 변수를 가림
print(value); // 200 출력
}
이 코드에서는 전역 변수 value
가 있지만, main()
함수 내에서 동일한 이름의 지역 변수가 선언되었으므로 전역 변수는 가려진다.
변수의 데이터 타입
Dart는 정적 타입 언어이므로, 변수의 데이터 타입이 고정되어 있으며, 선언된 타입 이외의 값을 저장할 수 없다. Dart에서 지원하는 기본 데이터 타입은 다음과 같다:
- 정수형(int): 정수 값을 저장
- 실수형(double): 소수점을 포함한 실수 값을 저장
- 문자열(String): 문자열을 저장
- 불리언(bool): 논리적 참(True) 또는 거짓(False)을 저장
각각의 데이터 타입은 메모리에서 다른 크기를 차지하며, 올바르게 사용해야 메모리 효율성을 높일 수 있다.
변수의 타입 변환
Dart에서는 암시적(implicit) 타입 변환이 허용되지 않으며, 명시적(explicit)으로 타입을 변환해야 한다. 예를 들어, 정수형 변수를 실수형 변수로 변환하는 방법은 다음과 같다:
int intValue = 10;
double doubleValue = intValue.toDouble(); // 정수를 실수로 변환
이와 같이, 데이터 타입에 맞지 않는 연산을 피하기 위해 명시적인 타입 변환을 해야 한다. 특히, 정수형에서 실수형으로의 변환은 자주 사용하는 예이다.
메모리 효율성을 고려한 변수 사용
변수를 사용할 때 메모리 할당을 신중히 고려하는 것이 중요하다. Dart에서는 final
과 const
키워드를 사용하여 메모리 효율성을 높일 수 있다. 이 키워드를 사용하면 값이 한 번 설정되면 이후에는 변경되지 않으므로, 메모리에서 불필요한 재할당을 방지할 수 있다.
final int finalValue = 10;
const double pi = 3.14159;
final
과 const
의 차이점은 const
는 컴파일 타임 상수이며, final
은 런타임 상수이다. 이를 적절히 활용하면 코드의 성능을 최적화할 수 있다.
변수 초기화와 값 변경
변수를 선언하고 즉시 값을 할당하는 것을 초기화라고 한다. 초기화되지 않은 변수를 사용하려고 할 경우, Dart에서는 오류를 발생시킨다. 따라서 변수는 선언과 동시에 값을 할당하거나, 이후에 반드시 값을 할당해야 한다.
int x; // 초기화되지 않은 변수
x = 5; // 이후에 값을 할당하여 초기화
변수는 선언된 이후, 프로그램 실행 중 자유롭게 값을 변경할 수 있다. Dart는 동적 타입 언어가 아닌 정적 타입 언어이기 때문에, 변수에 한 번 할당된 데이터 타입은 변경할 수 없다. 예를 들어, int
타입 변수에 문자열 값을 할당하려고 하면 오류가 발생한다.
int num = 10;
num = "String"; // 오류 발생: 'int' 타입 변수에 문자열 할당 불가
상수와 변수의 초기화 타이밍
상수와 변수의 초기화 타이밍은 프로그램의 동작에 중요한 영향을 미친다. 상수는 프로그램 실행 중 값이 변경되지 않기 때문에 메모리 관리 측면에서 유리한다. Dart에서는 두 가지 상수 선언 방식인 const
와 final
을 지원한다.
-
const
: 컴파일 시점에 반드시 초기화되어야 하는 상수이다. 컴파일 타임에 값이 결정되며, 이후 절대 변경되지 않는다. -
final
: 런타임에 값이 결정될 수 있으며, 프로그램 실행 중 한 번만 초기화된다. 한 번 값이 할당되면 변경할 수 없다.
예를 들어, 컴파일 타임에 값이 결정되는 상수는 const
로, 런타임에 값을 할당해야 하는 경우 final
을 사용할 수 있다.
const double pi = 3.14159; // 컴파일 타임 상수
final DateTime now = DateTime.now(); // 런타임 상수
변수의 유효 범위 (Scope)
변수의 유효 범위란 변수가 참조될 수 있는 코드의 범위를 의미한다. Dart에서는 블록 스코프 규칙을 따르며, 변수는 선언된 블록 내에서만 유효한다. 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에서 함수와 변수의 상호작용은 다음과 같다:
int addNumbers(int a, int b) {
return a + b;
}
void main() {
int result = addNumbers(3, 5);
print(result); // 8 출력
}
이와 같은 함수 내 변수 사용은 코드의 재사용성을 높이고, 유지보수를 쉽게 만든다.