01. 자료형 - Dart

Shawn Kang·2022년 7월 26일
0

Dart

목록 보기
2/4
post-thumbnail

개요

Dart는 아래와 같은 자료형을 지원한다:

  • 숫자 (int, double)
  • 문자열 (String)
  • 부울린 자료형 (bool)
  • 리스트 (List)
  • 집합 (Sets)
  • 해시 맵 (Map)
  • 룬 (Runes)
  • 심볼 (Symbol)
  • null (Null)

그 외에도 다른 몇 가지의 비교적 한정적인 역할을 하는 자료형들이 있다:

  • Object: Null을 제외한 모든 Dart 클래스들의 상위 클래스
  • Enum: 모든 열거형들의 상위 클래스
  • Future, Stream: 비동기 처리에 활용됨
  • Iterable: for-in 반복문 또는 동기적인 Generator 함수에 사용됨 (Generator에 대해서는 나중에 알아보도록 하자)
  • Never: Never가 포함된 Expression이 무슨 값을 갖는지 평가할 수 없음을 나타냄, 보통은 항상 예외를 던지는 함수에 사용된다
  • dynamic: 이 자료형을 사용한 변수에 대해 컴파일 전에 자료형 확인을 하지 않겠다는 것을 나타냄, ObjectObject?를 대신 사용 가능
  • void: 값이 절대 사용될 일이 없음을 나타냄

추가적으로 Object, Object?, Never 그리고 Null은 Dart의 전체 클래스 계층 구조에서 특별한 의미를 갖고 있다.

간단히 언급하자면, Null Safety 적용 전에는 클래스 계층 구조가 상단부터 Object - 각종 자료형 등 Dart 클래스 - Null이었는데, 적용 이후에는 Null이 별도로 분리되면서 Object의 최하단에는 Never가, Object?의 최하단에는 Null이 위치하게 되었다는 이야기다. 다시 말해 non-nullable 객체에서 Null을 대체하게 된 것이 Never라는 말.

해당 내용에 대해서는 Understanding null safety | Darttop-and-bottom 부분을 참고하도록 하자.


숫자

Dart의 숫자 자료형은 아래와 같이 두 가지 형태를 갖는다:

int (정수)

정수는 네이티브 플랫폼에서는 -2^63에서 2^63 - 1의 범위를 갖고, 웹 플랫폼에서는 -2^53에서 2^53 - 1의 범위를 갖는다.

이렇게 된 이유는 네이티브에 플랫폼에서는 64 비트 부호 있는 2의 보수법으로 정수를 표현하지만, 웹으로 빌드 시에는 64 비트 부동 소수점 형식으로 변환되기 때문.

일단 Dart에서는 숫자 구현 방식의 차이점으로 인한 문제는 많이 발생하지 않으며, 구현 방식이 사용자에게 숨겨지도록 설계되었기 때문에 intdouble 자료형을 여러분이 알던 그대로 평범하게 대하라고 조언해주고 있다.

어쨌든 이 내용에 대해 더 궁금한 점이 있는 분께서는 Numbers in Dart | Dart를 참고하기 바란다.

double (실수)

64 비트 부동 소수점으로 구현되었다.

여담

두 자료형에서 지원하는 연산자들

intdouble 클래스는 둘 다 num 클래스의 하위 클래스이다.num 클래스는 사칙연산과 같은 기본적인 연사자를 제공하며 abs(), ceil() 그리고 floor()과 같은 다른 함수들도 제공한다. >>와 같은 비트 연산자는 int 클래스에 정의되어 있으며, 다른 추가적인 숫자를 다루는 함수들을 살펴보고 싶다면 dart:math 라이브러리를 참고하기 바란다.

int의 비트 연산자

int 자료형은 <<, >>, >>>, ~(보수), &(AND), |(OR), ^(XOR) 연산자를 지원한다.

int의 16 진법 표기

int 자료형은 16 진법 표기를 지원한다. int a = 0xABCD1234와 같은 식으로 선언 가능.

String과 숫자 간 변환

intdouble 자료형은 클래스 수준 함수로 parse(args)를 지원한다. argsString을 입력하면 그에 맞는 숫자로 변환해주는 식이다. 아래 코드르 참고하기 바란다:

int intValue = int.parse("1004");
double doubleValue = double.parse("3.141592");

반대로 intdoubleString으로 바꾸는 것도 가능. 숫자 뒤에 toString() 함수를 붙여주기만 하면 된다. 다만 double의 경우 String으로 변환하는 함수가 4개 존재하고, 상황에 따라 적절하게 사용하면 될 듯 하다.

String intStr = 1004.toString();
String doubleStr = 3.141592.toString();
  • double.toString(): 기본적인 변환
  • double.toStringAsExponential([int? fractionDigits]): 소수점 아래 fractionDigit 자리까지 3.14e+02와 같은 지수 표현식으로 변환
  • double.toStringAsFixed(int fractionDigits): 소수점 아래 fractionDigits 자리까지 변환
  • double.toStringAsPrecision(int precision): Bit precision에 따른 변환

문자열

String

Dart의 문자열은 UTF-16 코드 하나하나의 나열로 구성되어 있다. 선언에는 '(작은 따옴표)와 "(큰 따옴표)를 둘 다 활용할 수 있다.

String big = "큰 따옴표";
String small = '작은 따옴표';

여담

문자열 안에 변수 표기하기

double pi = 3.14;
String str = "원주율은 $pi입니다.";

${expression}을 통해 문자열 안에 변수를 표기할 수 있다. 만약 expression이 식별자라면 중괄호를 생략해도 된다.

다양한 기능 지원

문자열 간 + 연산자를 통해 두 문자열을 접합할 수 있다.

작은 따옴표 또는 큰 따옴표를 3개 연달아 사용해서 여러 줄의 문자열을 나타낼 수 있다.

String str = '''
이것은
굉장히
길고
긴
문자열입니다.
''';

작은 따옴표와 큰 따옴표의 병행 사용

이게 무슨 말이냐 하면, 아래 코드와 같은 상황을 의미한다:

String badStr = "누군가 말했다. "도망쳐!""; // 오류 발생함

이상의 코드는 오류를 일으킨다. 따라서 문자열 안에 작은 따옴표 또는 큰 따옴표를 넣을 생각이라면, 1) 문자열 안에 넣을 따옴표문자열을 감쌀 따옴표를 분리해서 사용하기 바란다. 예를 들어 큰 따옴표를 문자열에 넣을 생각이라면, 문자열 자체는 작은 따옴표로 감싸야 한다. 아래 코드처럼 말이다:

String goodStr = '누군가 말했다. "도망쳐!"';

부울 자료형

bool

bool 자료형에 할당될 수 있는 값은 컴파일 상수에 해당하는 truefalse 말고는 없다.

여담

if 문에서 bool 자료형의 활용

보통 Python에서 if 문을 사용할 때, 이렇게 쓰기도 한다:

boolValue = 1
if boolValue:
	# TODO

하지만 Dart에서는 bool 자료형이 아닐 경우 if 문 안에 직접 집어넣을 수 없다. 대안으로 값을 비교하거나 bool 값을 반환하는 함수를 사용하도록 하자.

// 잘못된 예시
int intValue = 1;
if (intValue) {
	// TODO
} 

// 더 좋은 예시
// 1. bool 자료형을 반환하는 함수 사용
String strValue = "";
if (strValue.isEmpty()) {
	// TODO
}

// 2. 값 비교
int intValue = 1;
if (intValue == 1) {
	// TODO
}

리스트

List

List는 모두가 아는 배열에 해당하는 자료형이다. 아래와 같은 방법으로 선언할 수 있다:

// 한 줄로 선언
var list1 = [1, 2, 3];

// 여러 줄로 선언
var list2 = [
	'Samsung',
    'LG',
    'Apple'
];

여담

List 자료형과 Collection에 대해

Dart에서는 여러 값을 동시에 다룰 수 있는 유용한 도구들을 제공한다. 거기에는 List, Set 등 기본적인 자료형도 존재하지만, Collection 라이브러리를 통해 큐, 연결 리스트 등 풍부한 도구를 추가적으로 제공하고 있다.

Collection 라이브러리에 대한 내용은 링크를 참고하기 바란다.

인덱싱 및 길이

List는 C 언어에서 보던, 0부터 시작하는 인덱싱을 지원한다.
또한 List.length를 통해 List의 길이를 확인할 수 있다.

Spread 연산자 (...)

첫 글에서도 언급한 바 있지만, List는 Spread 연산자(...)와 Null-aware spread 연산자(...?)를 지원한다. 이 연산자는 Collection에 여러 값을 동시에 삽입할 수 있게 해 준다. 아래 코드를 참고하자:

var list1 = [1, 2, 3];
var list2 = [0, ...list1];
print(list2.length); // 리스트 길이는 4로 출력됨

Spread 연산자(...)를 적용할 리스트가 null 값을 가질 가능성이 있는 경우에는 Null-aware spread 연산자(...?)를 사용하도록 하자.

Collection if와 Collection for

이 역시 첫 글에서도 잠깐 언급한 내용이다. Collection if는와 for은 리스트를 선언할 때 반복문과 조건문을 활용할 수 있게 해 준다.

  • Collection if: 조건식을 만족할 경우 리스트에 값을 추가
  • Collection for: 반복문 연산을 통해 생기는 값을 리스트에 추가

아래 코드를 참고하자:

// Collection if
var list1 = [1, 2, 3, if (condition) 4]; // condition 만족 시 list1 = [1, 2, 3, 4]

// Collection for
var list2 = [1, 2, 3];
var list3 = ["0번", for (var i in list2) "$i번"]; // ["0번", "1번", "2번", "3번"]

집합

Set

집합을 나타내는 자료형이다. {}(중괄호)를 통해 선언할 수 있다. 아래 코드처럼:

var set1 = {1, 2, 3};

다만 위 코드에서 중요한 점은, Dart는 set1Set<int>으로 추측한다는 사실이다. 따라서 만약 사용자가 set1에 정수가 아닌 다른 자료형을 넣으려고 시도할 경우, Dart는 오류를 반환하게 된다. 따라서 필요한 경우 자료형을 확실히 명시하여 set을 선언하는 것도 고려해볼 수 있겠다.

var set2 = <String>{};
Set<String> set3 = {};

여담

집합에 원소 추가하기

var set = {1, 2, 3};
set.add(4);

Set.add(args) 또는 Set.addAll(args) 함수를 사용하자. 전자의 경우는 단일 원소, 후자의 경우는 여러 원소를 집합에 추가할 때 사용한다.

집합의 길이

var set = {1, 2, 3};
print(set.length);

Set.length를 통해 집합 길이를 확인할 수 있다.


해시 맵

Map

일반적으로 우리가 알고 있는 해시 맵은 Python의 Dictionary와 비슷한, 여러 개의 키와 값을 각각 연결한 것들이다. 이 키와 값은 어떤 자료형이든 다 넣을 수 있다.

한편, 각각의 키는 고유해야 하지만 값은 여러 키에 대해 중복하여 사용할 수 있다.

Map의 경우는 아래와 같이 선언할 수 있지만, List와 비슷하게 var로 선언할 경우 Dart가 스스로 자료형을 추측하게 된다. 따라서 웬만해서는 자료형을 명시하여 선언해줄 수 있도록 하자:

// Dart는 map1을 Map<String, String>으로 추측함
var map1 = {
	"철수": "반장",
  	"영희": "부반장",
  	"이즈리얼": "총무"
};
  
// 자료형을 명시하여 Map 선언하기
var map2 = <int, String>{};
Map<int, String> map3 = {
	1: "감자",
  	2: "당근",
  	3: "칡"
};

여담

해시 맵에 키, 값 쌍 추가하기

Python에서 Dictionary에 추가하던 그 방식을 그대로 사용하면 된다:

var map = <int, String>{};
map[0] = "수박";

해시 맵의 길이

Map.length를 통해 집합 길이를 확인할 수 있다.

Map<int, String> map = {
	1: "감자",
  	2: "당근",
  	3: "칡"
};
print(map.length);

해시 맵에서 Spread 연산자 사용하기

해시 맵도 Collection의 일종이라 Spread 연산자를 사용할 수 있다. 아래 코드를 참고하자:

var map1 = <int, String>{1: "이즈리얼", 2: "베인", 3: "자야"};
var map2 = <int, String>{...map1, 4: "진", 5: "제리", 6: "시비르"};
print(map2); // {1: 이즈리얼, 2: 베인, 3: 자야, 4: 진, 5: 제리, 6: 시비르}

리스트를 해시 맵으로 바꾸기

리스트를 해시 맵으로 바꿀 수도 있다. 먼저 리스트 하나에 번호를 매기고 싶을 경우:

var list1 = ["이즈리얼", "카직스", "신드라"];
var map3 = list1.asMap();
print(map3); // {0: 이즈리얼, 1: 카직스, 2: 신드라}

다음으로는 두 개의 리스트를 하나의 맵으로 합치고 싶을 경우:

var list1 = ["이즈리얼", "카직스", "신드라"];
var list2 = ["원거리 딜러", "정글러", "미드 라이너"];
var map4 = Map.fromIterables(list1, list2);
print(map4); // {이즈리얼: 원거리 딜러, 카직스: 정글러, 신드라: 미드 라이너}

그 외에 Map.fromEntries(), Map.fromIterable()도 존재하므로, 필요한 경우 Map 클래스 문서를 참고하기 바란다.


Runes

룬이란 문자열을 구성하는 각 문자의 유니코드 값을 나타내는 자료형이다.

룬에 대한 정확한 이해를 위해서는 유니코드에 대해 조금 알 필요가 있다. 유니코드 문자들에는 1 문자1 코드 포인트(Code point)가 할당되어 있다. 또한 인코딩 바이트 수마다 다르게 표현된다.

  • UTF-8: 1~4 바이트 가변 길이 인코딩
  • UTF-16: 2~4 바이트 가변 길이 인코딩
  • UTF-32: 4 바이트 인코딩

따라서 동일한 코드 포인트를 갖고 동일한 인코딩 방식을 사용하더라도, 인코딩 바이트 폼에 따라 표현되는 형식이 다를 수 있다. 예를 들어 알파벳 M의 경우, 코드 포인트가 U+004D이지만 UTF-8에서는 4D, UTF-16에서는 004D이다.

어쨌든 Dart의 문자열은 UTF-16 코드 유닛들의 나열로 이루어져 있기 때문에, 코드 포인트를 문자열로 표현하기 위해서는 특별한 문법이 필요하다. 이를 표현하는 가장 보편적인 방식은 \uXXXX의 4자리 16진수 값으로 작성하는 것. 예를 들어 '♥'의 코드 포인트는 \u2665이다.

4자리보다 길거나 짧은 코드 포인트를 다루기 위해서는, 값을 {}(중괄호) 안에 넣어서 활용해보자. \u{1f606} 처럼.


심볼

Symbol

심볼이란 Dart 프로그램 내에서 선언된 식별자나 연산자를 표현하는 데 쓰이는 자료형이다. 아마 웬만해서는 사용자가 이걸 쓸 일이 없을 테니 여기서는 넘어가고, 필요한 분들은 Symbols | Language tour | Dart를 참고하길 바란다.


유연한 자료형

vardynamic

vardynamic 자료형은 어떤 자료형도 할당받을 수 있는, 상당히 유동적인 자료형이다. 다만 이 두 자료형에는 약간의 차이가 있다.

먼저 공통점으로는 두 자료형 모두 변수에 할당된 값을 바꿀 수 있다. 다만 dynamic의 경우는 추가로, 원래 자료형과 다른 자료형의 값으로도 재할당이 가능하다. var은 불가능.

var 자료형은 선언 및 초기화 시 할당되는 값에 의해 자료형이 결정되며, 일단 한 번 정해진 자료형은 추후 변경할 수 없다. 초기화할 때 int 값을 할당했다면, 그 변수는 그 순간부터 int 자료형으로 취급받는 것.

아래 코드를 참고해보자:

var a = 10; // 이 순간부터 변수 a는 int 값만 받을 수 있음
print(a.runtimeType.toString()) // int 출력됨
a = 30; // 문제 없이 재할당 됨
  
a = "Hello, world!" // 여기서 오류 발생

이 코드를 돌려보면 기존 10이었던 값을 30으로 바꿀 수는 있지만, 문자열로는 바꿀 수 없다는 걸 알 수 있다.

반면 dynamic 자료형은 자료형에 관계없이 어떤 값으로든 재할당이 가능하다. 초기화할 때 int로 선언했어도 재할당은 String이든 Listbool이든 어떤 자료형이든 상관이 없다.

dynamic a = 10;
print(a.runtimeType.toString()) // int 출력됨
a = 30; // 문제 없이 재할당 됨
  
a = "Hello, world!"; // 문제 없이 재할당 됨
print(a.runtimeType.toString()) // String 출력됨
profile
i meant to be

0개의 댓글