Dart 문법 - 기본/변수/타입

악어·2023년 5월 3일
0

Dart 문법

목록 보기
1/7
post-thumbnail

문법 공부 시작

지루한 문법공부를 시작한다..
다른 언어에서 사용하던 것들이
다트에서 어떻게 달라지는지 위주로 공부하면
될거같다. (소요기간 3~4일 예상)

문서를 보고 하는게 정석이겠지만
내 공부 스타일상 처음부터 완벽하게
언어를 배우고 가지 않으므로
적당한 무료 유튜브 강의를 1.75배속으로 봤다.

(참고 영상)
https://www.youtube.com/watch?v=3Ck42C2ZCb8&t=374s

영상 > 깃허브 플러터 프로젝트 클론 >
코드분석 > 간단한 프젝만들기

순서대로 주요 기능들을 익히고
이를 기반으로 카페자리 서비스를
리빌딩 해봐야겠다.



문법

1. 기본

  • 문장 끝마다 세미콜론
  • 들여쓰기가 많은 플러터 특성상 들여쓰기는 2칸만
  • 선행타입
  • 주석은 //


2. 변수 / 주요타입 정리

// 정수
int num1 = 10;


// 실수
double num2 = 1.25;


// 정수 & 실수 공용
num num3 = 8;
num num4 = 1.4;


// Boolean
bool isTrue = true;
bool isTrue = false;


// 문자열
String name1 = "무언가 글자";
String name2 = '작은 따옴표 가능';
String multiline = """
  세개 사용하면
  여러줄 가능한 듯?
""";


// optional
String? name3 = "문자일수도, null일수도 있음";
name3 = null;


// var
var a = "String 타입";  // 자동으로 String 설정
var b = 10;             // 자동으로 int 설정


// dynamic 
dynamic c = 10;
c = "뭔 10이야 문자열이지";  // 동적 변수 할당 가능


// final / const (상수)
final String name4 = "카페자리 흥해라";
cont int somthingCount = 80;


// 시간
DateTime now = DateTime.now();


// 리스트
List<String> names = ["철수", "영희", "뭐시기"];


// dictionary (키:값)
Map<String, int> heights = {
  "철수": 180,
  "영희": 160,
  "뭐시기": 190
};


// 집합
Set<int> ages = {
  21, 22, 28, 31, 22, 25
};
// > 결과: ages = {21, 22, 28, 31, 25}


// enum
enum result {
  success,
  fail,
  progress,
}

기본적인 타입이나 변수 할당에 대해 간략하게 적어봤다.
dart에서는 이런것들은 쓴다 정도로 보고
각각의 활용법을 상세하게 다뤄본다.



3. 숫자


  • 기본 연산
int num1 = 28;
int num2 = 4;

print(num1 + num2);  // 32
print(num1 - num2);  // 24
print(num1 * num2);  // 112
print(num1 / num2);  // 7
print(num1 % num2);  // 0

double이나 num 타입에서도 비슷하므로 생략한다.

int와 double을 합친 num 자료형이 있다는게 새로운데,
이게 새로운 타입인지 아니면 그냥 표기상에 int와 double을
자유롭게 받을수 있는 동적 타입인지 알아보기 위해
몇가지 실험을 해봤다.


// double과 int를 연산하면 num 타입이 나올까?

double num1 = 28.5;
int num2 = 4;

var num3 = num1 + num2;
var num4 = num1 - num2;
var num5 = num1 * num2;
var num6 = num1 / num2;
var num7 = num1 % num2;

print(num3.runtimeType);    // double
print(num4.runtimeType);	// double
print(num5.runtimeType);	// int
print(num3.runtimeType);	// double
print(num6.runtimeType);	// double
print(num7.runtimeType);	// double

그냥 결과값이 정수로 떨어지면 int,
그렇지 않으면 double이 나온다.


// num끼리의 연산, num과 다른 타입과의 연산은??

num num1 = 10;
num num2 = 4.5;
int num3 = 20;
double num4 = 32.3;

// num - num 연산
var num5 = num1 + num2;
var num6 = num1 - num2;
var num7 = num1 * num2;
var num8 = num1 / num2;
var num9 = num1 % num2;

// num(int값) - int 연산
var num10 = num1 + num3;
var num11 = num1 - num3;
var num12 = num1 * num3;
var num13 = num1 / num3;
var num14 = num1 % num3;

// num(double값) - int 연산
var num15 = num2 + num2;
var num16 = num2 - num2;
var num17 = num2 * num2;
var num18 = num2 / num2;
var num19 = num2 % num2;

// num(int값) - double 연산
var num20 = num1 + num3;
var num21 = num1 - num3;
var num22 = num1 * num3;
var num23 = num1 / num3;
var num24 = num1 % num3;

// num(double값) - double 연산
var num25 = num2 + num4;
var num26 = num2 - num4;
var num27 = num2 * num4;
var num28 = num2 / num4;
var num29 = num2 % num4;

print type 찍는건 난잡하니 생략하고,
결국 다 int아니면 double로 타입이 나왔다.

num은 새로운 타입이 아닌 숫자만 사용 가능한
dynamic의 일종이다 라는게 결론이다.



4. 문자열

문자열 활용법은 코틀린과 거의 똑같다.

// 큰 따옴표 작은 따옴표 자유롭게 사용 가능
String name1 = "철수";
String name2 = "영희";


// 여러줄은 따옴표 세개로
String multiline1 = """
첫줄
둘째줄
""";
String multiline2 = '''
첫줄
둘째줄
''';


// 변수 기입은 ${}
int age = 28;
print("제 나이는 ${age}살입니다.");


// 슬라이싱 - substring()
String stringToSlice = "012345678";
print(stringToSlice.substring(5));     // 5678
print(stringToSlice.substring(3, 6));  // 345


// 문자열 연산
String org = "012";
String stringToAdd = "345";
print(org + stringToAdd);     // 012345
org += "678";
print(org);     // 012678

적어도 내가 주로 쓰는 범위의 문자열 메소드는
코틀린과 거의 똑같아서 편할 것 같다.

substring에서 파라미터를 하나만 넣었을때
해당 인덱스에서 끝까지 출력해주는 부분이
예상과 달라서(나는 처음부터 해당 인덱스까지 잘라줄줄..)
그부분만 조심하면 될 것 같다.



5. var, dynamic

var를 통해 타입 명시를 생략할 수 있고,
dynamic을 통해 동적 타입을 둘 수 있다.

코틀린을 쓸때는 후행타입이다보니,
코드가 너무 난잡해질까봐 변수에 일일이
타입을 표기해주지는 않았다.

하지만 다트에서는 var 자리에
타입을 써줄 수 있으므로,
굳이 헷갈리게 var를 사용하지는 않을 듯 하다.

// kotlin
val num = 10
var msg = "첫번째"
msg += "두번째"


// dart
int num = 10;
String msg = "첫번째";
msg += "두번째";

다음은 dynamic인데, 깐깐한 프로그래밍 언어를
선호하는 내 성격상 거의 사용하지 않을 듯 하다.
굳이 사용한다면 double과 int를 모두 받을 수 있는
num정도만 쓰지 않을까..??

아직 내 프로그래밍 경험상 string -> int나
int -> boolean 등 드라마틱한 타입 변환이
필요한 경우는 많이 없었다. 고로 안쓸듯.



6. final, const

기본적으로 둘의 용도는 비슷하다.
상수값을 정의하는데 사용한다.

final String baseUrl1 = "cafejari.software";
final baseUrl2 = "cafejari.shop";

const String baseUrl3 = "cafejaritest.software";
const baseUrl4 = "cafejaritest.shop";

위 처럼 타입을 생략할수도, 표기할 수도 있다.
둘의 차이점은 생성되는 시점이다.
final은 실행시점에, const는 빌드 시점에 생성된다.

for(int i = 0; i < 5; i++) {
  final num = i;
  print(num);
} // 0, 1, 2, 3, 4

for(int i = 0; i < 5; i++) {
  const num = i;
  print(num);
} // 에러

위처럼 실행시점에야 알 수 있는 값을
상수로 받아올때는 fianl을 사용하면 된다.
(const는 저렇게 활용하면 에러남)

변하지 않는 환경변수값을 정할 때 주로
const를 활용하면 될 것같다.

내 경험상 굳이 final을 사용할 일이 있을까 싶다.



7. nullable

다트 언어에서도 이제 null활용이 자유롭다.
(안될때가 있던다는 거..ㅠ)
활용은 코틀린과 비슷해보였으나
표현방법은 조금 다른듯 했다.


String? canBeNull = "Hi";
print(canBeNull);  // Hi
canBeNull = null;
print(canBeNull);  // null

기본 표현은 이처럼 간단하다.
그럼 강제 진행이나 null일 경우에 대한
대처도 알아보자.


String? canBeNull = "Hi";
print(canBeNull!);  // Hi

canBeNull = null;
print(canBeNull ?? "default");  // default

이처럼 ! 를 통해 타입을 강제할 수 있고,
?? 를 통해 null일 경우에 대한 대처가 가능하다.


여기서 드는 의문점은 두가지 정도였다.

  1. nullable타입을 얼마나 엄격하게 잡아주는가?
  2. unwrapping 관습이 있는가?

// 1. 얼마나 엄격하게 잡아줄까?
int? num1 = 10;
int num2 = 12;
print(num1 + num2); // 12

int? num1 = null;
int num2 = 12;
print(num1 + num2); // 에러

num1과 num2가 int와 int?로 타입이 다름에도
그냥 연산해버린다.
생각보다 느슨한 설계인가 싶어서
좀 더 자세하게 실험해보았다.


int addNumbers(int? num1, int num2) {
  return num1 + num2;
} // 빨간줄

위는 함수 표현인데, 다행히 타입이 다른
두 값에 대해 연산이 불가하도록 빨간줄이 뜬다.

몇 가지 실험을 더 진행해본 결과,
맨처음 사례처럼 빌드단계에서
명백히 null이 아닐 경우에만
실행됨을 확인할 수 있었다.

생각보다 똑똑한 놈이다!


2. unwrapping 방법은?

int addNumbers(int? num1, int num2) {
  
  return num1 ?? 0 + num2;
}

int addNumbers(int? num1, int num2) {
  
  if(num1 == null) {
    return  0;
  }
  
  return num1 + num2;
}

위 방법처럼 null일 경우
일일이 default값을 지정해주는 방법이 있고,
아래 방법처럼 if를 통해 체크해주는 방법이 있다.

아래 방법을 통해 체크한 경우,
해당 부분 이후에는 unwrapping처리되어
빨간줄이 사라진다.



8. DateTime

코틀린과 똑같이 datetime을 활용할 수 있다.

DateTime now = DateTime.now();
print(now);  // 2023-05-03 11:43:50.505

시간 표현이 일반적이라 쉽게 활용 가능해보인다.
자세한 활용은 어차피 필요할때 다시 검색해볼테니
여기서 자세히 다루진 않는다.



9. 배열

다트에서의 리스트 활용은 굉장히 무난한다.

List<String> names = ["아이언맨", "토르", "스파이더맨"];
List<int> ages = [32, 38, 22];
List<dynamic> anything = ["아이언맨", 40, true];

위 두줄처럼 엄격하게 타입을 제한할수도,
아래처럼 타입을 동적으로 두어 파이썬처럼
활용할 수도 있다.
리스트의 메소드를 간단히만 알아보면,


List<String> names = ["아이언맨", "토르", "스파이더맨"];
  
print(names[1]);        // 토르
print(names.length);    // 3
print(names.isEmpty);   // false 
print(names.reversed);  // (스파이더맨, 토르, 아이언맨)
print(names.reversed.runtimeType);  // ReversedListIterable<String>
  
names.add("헐크"); 
print(names);    // [아이언맨, 토르, 스파이더맨, 헐크]
  
names.remove("아이언맨");
print(names);    // [토르, 스파이더맨, 헐크]
  
names.removeLast(); 
print(names);    // [토르, 스파이더맨]

더 많은 메소드가 있겠지만, 필요할때 찾아 쓰는걸로 하자.
그보다, 재밌는 특징 세 가지가 있었다.

첫번째는 reversed 메소드를 썼을때 튜플 같은 형식이
출력되어, 직접 타입을 찍어보니 ReversedListIterable이라는
타입을 확인할 수 있었다. 개발할 때 .toList()를 통해
리스트로 만들어 줘야겠다.

두번째는 pop()이 없었다는 것인데,
pop의 역할을 removeLast가 대신해준다.
알고있어야겠다.

세번째는 코틀린과는 달리
mutableList와 List를 구분하지 않는다는 것이었다.
솔직히 둘을 구분해 사용할 만큼 수준높은
개발실력을 갖추지는 못했기에 불편한 부분이었는데,
다트에서는 걱정할 필요가 없을 것 같다.



10. Map, Set

파이썬의 dictionary와 같은 타입이
다트에서는 Map이라는 이름으로 존재한다.

Map<String, int> people = {"민수": 28, "수진": 24};
Map<String, dynamic> people = {"민지": "여성", "동수": 25};

List를 쓸 때와 똑같이 활용 가능하며,
역시 동적타입을 통한 선언이 가능하다.
주요 메소드만 보자.

Map<String, int> people = {"민수": 28, "수진": 24};
  
print(people.entries);   // (MapEntry(민수: 28), MapEntry(수진: 24))
print(people.entries.runtimeType);  // EfficientLengthMappedIterable<String, MapEntry<String, int>>
print(people.keys);      // (민수, 수진)
print(people.keys.runtimeType);     // LinkedHashMapKeyIterable<String>
print(people.values);    // (28, 24)
print(people.values.runtimeType);   // EfficientLengthMappedIterable<String, int>
print(people.isEmpty);   // false
print(people.length);    // 2

people["인호"] = 31;
print(people);    // {민수: 28, 수진: 24, 인호: 31}

people.remove("민수");
print(people);    // {수진: 24, 인호: 31}

여기서도 마찬가지로 대부분 무난하고,
entries, keys, values를 통해 값들을 뽑아낼 때
타입만 조심하면 될 것 같다.
(어차피 보통 forEach 때릴거지만)



파이썬의 set과 같은 타입으로
다트에도 Set 타입이 있다.
거의 활용해본적이 없는데..
중복값 제거해야 할 때에는 쓸 수 있어보인다.

어차피 각종 메소드는 쓸 때 또 검색할테니
대충 선언 방법만 알고 넘어가자

Set<int> ages = {20, 30, 21, 22, 45, 20, 28};
print(ages);   // {20, 30, 21, 22, 45, 28}


11. enum

swift에서 enum을 사용했을 때 개발 경험이 좋았다.
코틀린에서도 활용해보려고 sealed를 사용했었는데,
결국 그냥 제공해주는게 제일 편한 법이다.

다트에서도 이를 제공해주니 자주 활용해봐야지.

enum Result {
  success,
  inProgress,
  fail
}

void main() {
  
  Result a = Result.inProgress;
  
  switch(a) {
    case Result.success:
      // TODO: Handle this case.
      break;
    case Result.inProgress:
      // TODO: Handle this case.
      break;
    case Result.fail:
      // TODO: Handle this case.
      break;
  }
}

switch문에 대해서는 operator편에서 다뤄보도록 한다.

위와 같이 어떤 분류가 필요할 때,
타입을 지정해 사용할 수 있다는 것은 큰 장점이다.
0 1 2 3이나 "case1" "case2" 등
값을 통해 분류하면 버그가 발생할 가능성도 높고
가독성도 떨어지기 때문이다.

enum은 정말 많이 쓸 듯 싶다ㅎㅎ



12. Typedef

코틀린과 스위프트에서 유용하게 활용했던
Typealias에 대응하는 개념이다.

MVVM 패턴에서는 자주 사용하는 Model들을
List로 묶어 쓸때가 많은데,
이때 해당 모델 타입명을 복수형으로
바꿔주는 식으로 자주 활용한다.

class Cafe {}

typedef Cafes = List<Cafe>;

void main() {
  
  Cafes cafes = [Cafe(), Cafe(), Cafe()];
  
}

자주 사용하는 List<~>나 Map<~> 형태의 타입을
쉽게 쓸 수 있다는 점에서 가독성이 좋아질 수 있다.

하지만 너무 남용하면 나만 알아볼 수 있게되거나
나조차 헷갈릴 수 있다.

따라서 협업을 시작할 때,
typedef를 활용할 특정한 case를
coding convention에 지정해놓고
규칙에 맞게 쓰는게 좋을 것이다.



마무리

여기서는 큰틀에서 타입, 변수에 대해 훑어보았다.
어차피 코딩할 때 또 찾아볼거 아니까
여기서는 큰 줄기만 확인했다.

다음은 if, loop, switch등 자주 활용되는
operator들에대해 코틀린과 비교하며 알아봐야겠다.

profile
냅다 회사부터 세워버린 개발자

0개의 댓글