[Dart] 다트 문법

찌니월드·2024년 2월 12일
0

📚 책 스터디

목록 보기
1/11
post-thumbnail

변수 선언

var

  • 변수에 값이 들어가면 자동으로 타입을 추론하는 타입 추론 기능을 제공
  • 명시적으로 타입을 선언하지 않아도 됨
void main() {
  var name = '찌니월드';
  print(name); // 찌니월드
  
  // 변수값 변경 가능
  name = '코코몽';
  print(name); // 코코몽
  
  // 변수명 중복은 불가능
  // 그래서 다음 코드에서 주석을 제거하면 코드에서 에러 발생
  // var name = '코코찌니';
}

dynamic

  • var는 타입을 한 번 유추하면 추론된 타입이 고정됨
    ➡️ 고정된 변수 타입과 다른 변수 타입의 값을 같은 변수에 다시 저장하려 하면 에러 발생
  • dynamic 키워드는 변수의 타입이 고정되지 않아서 다른 타입의 값을 저장 가능
void main() {
  dynamic name = '찌니월드';
  print(name); // 찌니월드
  name = 1;
  print(name); // 1
}

final/const

  • final과 const로 선언한 변수는 선언 후 값을 변경할 수 없음
void main() {
  final String name = '찌니월드';
  name = '코코몽'; // 에러 발생. final로 선언한 변수는 선언 후 값을 변경할 수 없음
  
  const String name2 = '코코몽';
  name2 = '찌니월드'; // 에러 발생. const로 선언한 변수는 선언 후 값을 변경할 수 없음
}

🧐 그럼 final과 const의 차이점은 무엇일까?

final은 런타임, const는 빌드 타임 상수이다.
구체적인 차이점을 알아보기 위해서 DateTime.now() 함수를 이용해 알아보자.

void main() {
 final DateTime now = DateTime.now();
 print(now);
}

반면에 const를 사용하면 에러가 난다. const로 지정한 변수는 빌드 타임에 값을 알 수 있어야 하는데 DateTime.now() 함수는 런타임에 반환되는 값을 알 수 있기 때문이다.

void main() {
// 에러
 const DateTime now = DateTime.now();
 print(now);
}

📢 코드를 실행하지 않은 상태에서 값이 확정되면 const를, 실행될 때 확정되면 final을 사용해주자.

변수 타입

문자열, 정수, 실수, 불리언 타입

void main() {
  // String - 문자열
  String name = '찌니월드';

  // int - 정수
  int isInt = 10;

  // double - 실수
  double isDouble = 2.5;

  // bool - 불리언 (true/false)
  bool isTrue = true;

  print(name); // 찌니월드
  print(isInt); // 10
  print(isDouble); // 2.5
  print(isTrue); // true
}

List 타입

  • 하나의 변수에 여러 값을 순서대로 저장할 때 사용
  • 리스트명[인덱스] 형식으로 특정 원소에 접근 가능
    • 인덱스는 0부터 시작
    • 마지막 인덱스에 접근하고 싶다면 리스트 길이 - 1의 값을 사용하면 됨
void main() {
  // 리스트에 넣을 타입을 <> 사이에 명시할 수 있다.
  List<String> fruitList = ['사과', '바나나', '체리', '딸기'];
  
  print(fruitList);
  print(fruitList[0]); // 첫 원소 지정
  print(fruitList[3]); // 마지막 원소 지정
  
  print(fruitList.length); // 길이 반환
  
  fruitList[3] = '포도';
  print(fruitList); // [사과, 바나나, 체리, 포도]
}
  • 리스트의 길이는 length를 가져와 확인할 수 있음

add() 함수

  • List에 값을 추가할 때 사용
void main() {
  List<String> fruitList = ['사과', '바나나', '체리', '딸기'];
  
  fruitList.add('망고'); // 리스트의 끝에 추가
  print(fruitList); // [사과, 바나나, 체리, 딸기, 망고]
}

where() 함수

  • List에 있는 값들을 순서대로 순회하면서 특정 조건에 맞는 값만 필터링하는 데 사용
  • 각 값별로 true를 반환하면 값을 유지하고, false를 반환하면 값을 버림
  • 순회가 끝나면 유지된 값들을 기반으로 이터러블 반환
void main() {
  List<String> fruitList = ['사과', '바나나', '체리', '딸기'];

  final newList = fruitList.where(
    (name) => name == '체리' || name == '딸기', // '체리' 또는 '딸기'만 유지
  );

  print(newList);
  print(newList.toList()); // Iterable을 List로 다시 변환할 때 .toList() 사용
}

map() 함수

  • List에 있는 값들을 순서대로 순회하면서 값을 변경 가능
  • 순회가 끝나면 이터러블 반환
void main() {
  List<String> fruitList = ['사과', '바나나', '체리', '딸기'];

  final newfruitList = fruitList.map(
    (name) => '과일 $name', // 리스트의 모든 값 앞에 '과일' 추가
  );

  print(newfruitList);
  print(newfruitList.toList()); // Iterable을 List로 다시 변환하고 싶을 때 .toList() 사용
}

reduce() 함수

  • List에 있는 값들을 순회할 때마다 값을 쌓아가는 것이 특징
void main() {
  List<String> fruitList = ['사과', '바나나', '체리', '딸기'];

  final allFruits = fruitList.reduce((value, element) => value + ',' + element);

  print(allFruits); // 사과,바나나,체리,딸기
}
🧐 그럼 reduce() 함수는 어떻게 값을 쌓아갈까?

순회가 처음 시작될 때 첫 번째 매개변수는 리스트의 첫 번째 값 즉, '사과'를 받게 되고, 두 번째 매개변수는 '바나나'를 받게 된다.

첫 번째 순회 이후로는 첫 번째 매개변수에 기존 순회에서 반환한 값이 첫 번째 매개변수에 입력되고 리스트에서의 다음 값이(체리) 두 번째 매개변수에 입력된다.

fold() 함수

  • reduce() 함수와 실행되는 논리는 똑같음
  • reduce() 함수는 리스트를 구성하는 값들의 타입과 반환되는 리스트를 구성할 값들의 타입이 완전히 같아야 함
  • fold() 함수는 어떠한 타입이든 반환 가능
void main() {
  List<String> fruitList = ['사과', '바나나', '체리', '딸기'];

  final allFruits = fruitList.fold<int>(0, (value, element) => value + element.length);

  print(allFruits); // 9
}
  • 첫 번째 순회 때 리스트의 첫 번째 값이 아닌 fold() 함수의 첫 번째 매개변수에 입력된 값이 초깃값으로 사용 됨

Map 타입

  • 맵은 키를 이용해서 원하는 값을 빠르게 찾을 수 있음
  • Map<키 타입, 값 타입> 맵 이름
void main() {
  Map<String, String> dictionary = {
    'Harry Potter': '해리 포터', // 키 : 값
    'Ron Weasley': '론 위즐리',
    'Hermione Granger': '헤르미온느 그레인저',
  };

  print(dictionary['Harry Potter']); // 해리 포터
  print(dictionary['Hermione Granger']); // 헤르미온느 그레인저
}
  • 모든 Map 타입은 키와 값을 모두 반환받을 수 있음
void main() {
  Map<String, String> dictionary = {
    'Harry Potter': '해리 포터', // 키 : 값
    'Ron Weasley': '론 위즐리',
    'Hermione Granger': '헤르미온느 그레인저',
  };

  print(dictionary.keys); // (Harry Potter, Ron Weasley, Hermione Granger)
  print(dictionary.values); // (해리 포터, 론 위즐리, 헤르미온느 그레인저)
}

Set 타입

  • 중복 없는 값들의 집합
  • Set<타입> 세트이름
void main() {
  Set<String> fruit = {'포도', '사과', '딸기', '딸기'}; // 딸기 중복
  
  print(fruit); // {포도, 사과, 딸기}
  print(fruit.contains('포도')); // 값이 있는지 확인하기
  print(fruit.toList()); // 리스트로 변환하기
  
  List<String> fruit2 = ['포도', '사과', '사과'];
  print(Set.from(fruit2)); // List 타입을 Set 타입으로 변환
}

enum

  • 한 변수의 값을 몇 가지 옵션으로 제한하는 기능
  • 선택지가 제한적일 때 사용
enum Status {
  approved,
  pending,
  rejected,
}

void main() {
  Status status = Status.approved;
  print(status); // Status.approved
}

연산자

null 관련 연산자

다트 언어에서는 변수 타입이 null값을 가지는지 여부를 직접 지정해줘야 한다.

  • 타입 키워드를 그대로 사용하면 기본적으로 null값 저장 ❌
  • 타입 뒤에 '?'를 추가해줘야 null값 저장 ⭕️
void main() {
  // 타입 뒤에 ?를 명시해서 null값을 가질 수 있다.
  double? number1 = 1;
  
  // 타입 뒤에 ?를 명시하지 않아 에러가 난다.
  double number2 = null;
}

null을 가질 수 있는 변수에 새로운 값을 추가할 때 ??를 사용하면 기존에 null인 때만 값이 저장되도록 할 수 있다.

void main() {
  double? number; // 자동으로 null값 지정
  print(number);
  
  number ??= 3; // ??를 사용하면 기존 값이 null일 때만 저장된다.
  print(number);
  
  number ??= 4; // null이 아니므로 3이 유지된다.
  print(number);
}

타입 비교 연산자

  • is 키워드를 사용하면 변수의 타입 비교 가능
void main() {
  int number1 = 1;
  
  print(number1 is int); // true
  print(number1 is String); // false
  print(number1 is! int); // false. !는 반대를 의미한다.(int 타입이 아닌 경우 true)
  print(number1 is! String); // true
}

제어문

if문

  • 괄호 안에 작성한 조건이 true이면 해당 조건의 코드 블록이 실행
void main() {
  int number = 2;
  
  if (number % 3 == 0) {
    print('3의 배수입니다.');
  } else if (number % 3 == 1) {
    print('나머지가 1입니다.');
  } else {
    print('맞는 조건이 없습니다.');
  }
}

switch문

  • break 키워드를 사용하면 switch문 밖으로 나갈 수 있음
enum Status {
  approved,
  pending,
  rejected,
}

void main() {
  Status status = Status.approved;

  switch (status) {
    case Status.approved:
      print('승인 상태입니다.');
      break;
    case Status.pending:
      print('대기 상태입니다.');
      break;
    case Status.rejected:
      print('거절 상태입니다.');
      break;
    default:
      print('알 수 없는 상태입니다.');
  }
}

for문

  • 횟수 기반으로 함수를 반복적으로 실행
void main() {
  // 값 선언; 조건 설정; loop마다 실행할 기능
  for (int i = 0; i < 3; i++) {
    print(i);
  }
}
  • for...in 패턴의 for문
void main() {
  List<int> numberList = [3, 6, 9];
  
  for (int number in numberList) {
    print(number);
  }
}

while문과 do...while문

while문은 조건을 기반으로 반복문을 실행한다. 조건이 true이면 계속 실행하고 false이면 멈추게 된다.

while문

  • 조건을 먼저 확인한 후 true가 반환되면 반복문을 실행
void main() {
  int total = 0;
  
  while(total < 10) { // total 값이 10보다 작으면 계속 실행
    total += 1;
  }
  
  print(total);
}

do...while문

  • 반복문을 실행한 후 조건을 확인
void main() {
  int total = 0;

  do {
    total += 1;
  } while (total < 10);

  print(total);
}

함수와 람다

  • 함수를 사용하면 한 번만 작성하고 여러 곳에서 재활용 가능
  • 반환할 값이 없을 때는 void 키워드를 사용
  • 다트 함수에서 매개변수를 지정하는 방법
    • 순서가 고정된 매개변수(포지셔널 파라미터)
    • 이름이 있는 매개변수(네임드 파라미터)

포지셔널 파라미터

int addTwoNumbers(int a, [int b = 2]) {
  return a + b;
}

void main() {
  print(addTwoNumbers(1)); // 3
}

네임드 파라미터

int addTwoNumbers({
  required int a,
  int b = 2,
}) {
  return a + b;
}

void main() {
  print(addTwoNumbers(a: 1)); // 3
}

익명 함수와 람다 함수

  • 함수 이름이 없고 일회성으로 사용
  • 다트 언어에서는 익명 함수와 람다 함수를 구분 ❌

익명 함수

  (매개변수) {
    함수 바디
  }
}

람다 함수

  (매개변수) => 단 하나의 스테이트먼트

typedef와 함수

  • 함수 선언부를 정의하는 키워드
  • 함수가 무슨 동작을 하는지에 대한 정의는 없음
typedef Operation = void Function(int x, int y);

void add(int x, int y) {
  print('결과값: ${x + y}');
}

void subtract(int x, int y) {
  print('결과값: ${x - y}');
}

void main() {
  // typedef는 일반적인 변수의 type처럼 사용 가능
  Operation oper = add;
  oper(1, 2); // 결과값: 3
  
  // subtract() 함수도 Operation에 해당되는
  // 시그니처이므로 oper 변수에 저장 가능
  oper = subtract;
  oper(1, 2); // 결과값: -1
}

try...catch

특정 코드의 실행을 시도(try)해보고 문제가 있다면 에러를 잡으라(catch)는 뜻이다.

void main() {
  try{
    
    // 에러가 없을 때 실행할 로직
    final String name = '찌니월드';
    
    print(name); // 찌니월드
  }catch(e){ // catch는 첫 번째 매개변수에 에러 정보를 전달해준다.
    
    // 에러가 있을 때 실행할 로직
    print(e);
  }
}

throw 키워드를 사용해 에러를 발생시킬 수도 있다.

void main() {
  try{
    final String name = '움파룸파';
    
    // throw 키워드로 고의적으로 에러를 발생시켜보자
    throw Exception('이름이 잘못됐습니다!');
    
    print(name);
  }catch(e){
    
    // try에서 에러가 발생했으니 catch 로직이 실행된다.
    print(e);
  }
}

throw 키워드를 사용해서 에러를 발생시키니 try에 있는 로직 실행이 중지되고 catch 로직이 실행됐다. 그래서 name 변수 값은 출력되지 않고 발생한 에러 메시지가 출력되었다.

참고

이 글은 골든래빗 《코드팩토리의 플러터 프로그래밍》의 스터디 내용 입니다.

profile
Front-End Developer

0개의 댓글