본문 바로가기
DEV/dart & flutter

Dart 기본 문법 정리

by 올커 2023. 12. 27.

 

 

 

Dart 기본문법을 정리하려고 한다.

아래 내용을 보고 기억이 안날 때 마다 찾아보면(ctrl+f) 좋을 듯 하다.

 

<< 서문 작성 >

 

· Dart를 익힐 때에는 DartPad라는 아래 사이트가 매우 유용했다.

 

DartPad

 

dartpad.dev

 

· 그리고 Dart에서 특징은 각 코드의 끝에 세미콜론(;)을 꼭 붙여주어야 한다.

세미콜론을 붙이지 않으면 아래와 같은 에러를 만나게 된다.


🎆 기본 문법

1. 메인 함수 : void main()

- 다트는 프로그램 시작점 엔트리 함수로 main()을 사용하며, void는 어떤 값도 반환하지 않는다는 뜻이다.

- main 뒤의 소괄호에는 입력받을 매개변수를 지정할 수 있다.

void main(){

}

 


2. 변수 선언

- Dart에서 변수 선언은 기본적으로 아래와 같이 할 수 있다.

 

 (1) var : var의 특징은 변수의 타입을 자동적으로 추론하며, 한 번 선언된 변수는 타입이 고정된다. 따라서 다른 변수 타입의 값을 변수에 다시 저장할 경우 에러가 발생한다.

var 변수명 = 값;
main() {
	var no = 10;
    no = 20;
    no = 'hello';		// 오류
}

 

 var로 선언시 값을 지정하지않으면 dynamic으로 선언되어 여러 타입으로 대입이 가능하다.

main() {
    var no2;
        no2 = 10;
        no2 = 'hell0';
        no2 = true;
}

 

 

 (2) dynamic : dynamic은 변수의 타입을 고정시키지 않고, 다른 타입의 값을 새로 저장할 수 있다.

dynamic 변수명 = 값(num);
변수명 = 값(text);

// 에러 발생하지 않는다.

 

 (3) final, const : 변수의 값을 처음 선언 후 변경할 수 없도록 할 경우

void main() {
  final String name = '윤도현';
  name = '김종국';
  
  const String name2 = '김종국';
  name2 = '윤도현';
}

 위의 코드를 실행시 아래와 같이 변수값을 변경할 수 없기 때문에 에러가 발생한다.

 *참고.

   final → Runtime 상수 (코드가 실행한 후 값을 확정해야 할 때 사용)

   const → Buildtime 상수 (코드를 실행하지 않은 상태에서 값이 확정될 때 사용, 빌드타임에 이미 값이 지정)

 

 (4) 변수 타입 직접 지정

  - String : 문자열

String name = '문자열';

 

  - int : 정수

int isInt = 5;

 

  - double : 실수

double isDouble = 3.14;

 

  - bool : Boolean(true, false)

bool isTrue = true;

 


3. 컬렉션

 - 컬렉션 : 하나의 변수에 여러 값을 저장할 수 있는 타입(List, Map, Set)

 

 (1) List 타입 : 여러 값을 순서대로 한 수에 저장할 때

void main() {
  // 리스트에 넣을 타입은 <> 사이에 명시
  List<String> fruitList = ['사과', '배', '오렌지'];
  
  print(fruitList);
  print(fruitList[0]);
  
  print(fruitList.length);    // List의 길이
  
  fruitList[0] = '포도';    // 0번 인덱스값 변경
  print(fruitList);
}


// 실행 결과
// [사과, 배, 오렌지]
// 사과
// 3
// [포도, 배, 오렌지]

 

 · add() 함수 : List에 값을 추가

void main() {
  List<String> fruitList = ['사과', '배', '오렌지'];
  
  fruitList.add('포도');
  print(fruitList);
}

// 실행 결과
// [사과, 배, 오렌지, 포도]

 

 · removeAt() 함수 : List에 저장된 요소 중 () 안의 인덱스에 해당하는 요소 삭제

void main() {
  List<String> fruitList = ['사과', '배', '오렌지'];
  
  fruitList.removeAt(0);
  print(fruitList);
}

// 실행 결과
// [배, 오렌지]

 

 · where() 함수 : List에 저장된 순서대로 특정 조건에 해당하는 값을 필터링

void main() {
  List<String> fruitList = ['사과', '배', '오렌지'];
  
  final newList = fruitList.where(
    (name) => name == '사과' || name == '오렌지',    // '사과' 또는 '오렌지'만 유지
  );
  
  print(newList);
  print(newList.toList());    // toList() : Iterable을 List로 다시 변환할 경우
}

// 실행결과
// (사과, 오렌지)
// [사과, 오렌지]

 

 · map() 함수 : List에 저장된 순서대로 값을 변경

void main() {
  List<String> fruitList = ['사과', '배', '오렌지'];
  
  final newList = fruitList.map(
    (name) => '과일 $name',
  );
  
  print(newList);
  print(newList.toList());    // toList() : Iterable을 List로 다시 변환할 경우
}

// 실행 결과
// (과일 사과, 과일 배, 과일 오렌지)
// [과일 사과, 과일 배, 과일 오렌지]

 

 · reduce() 함수 : List에 저장된 순서대로 매개변수에 입력된 함수를 실행(리스트를 순회하며 값을 누적하는 특징)

   *주의 : 리스트를 구성하는 값의 타입과 반환되는 리스트를 구성할 값의 타입이 동일해야 함

void main() {
  List<String> fruitList = ['사과', '배', '오렌지'];
  
  final allfruits = fruitList.reduce((value, element) => value + ',' + element);
  
  print(allfruits);
}

// 실행 결과
// 사과,배,오렌지

 

 · fold() 함수 : reduce와 유사하나, 리스트를 구성하는 값의 타입과 반환되는 리스트를 구성하는 값의 타입이 동일해야 하는 제약이 없음. 첫 번째 매개변수에 초기값을 지정하고, 두 번째 매개변수에는 reduce()함수와 동일하게 작동하는 함수를 입력.

void main() {
  List<String> fruitList = ['사과', '배', '오렌지'];
  
  final allfruits = fruitList.fold<int>(0, (value, element) => value + element.length);
  
  print(allfruits);
}

// 실행 결과
// 6

 

 

 

 

 (2) Map 타입 : key, value 쌍을 저장.

void main() {
  Map<String, String> dictionary = {
    'John F. Kennedy' : '존 F. 케네디',
    'Barack Obama' : '버락 오바마',
    'Donald Trump' : '도널드 트럼프',
    'Joe Biden' : '조 바이든',
  };
  print(dictionary['Barack Obama']);
  print(dictionary.keys);
  print(dictionary.values);
}

// 실행 결과
// 버락 오바마
// (John F. Kennedy, Barack Obama, Donald Trump, Joe Biden)
// (존 F. 케네디, 버락 오바마, 도널드 트럼프, 조 바이든)

 

 (3) Set 타입 : 중복이 없는 유일한 값들의 집합

void main() {
  Set<String> fruit = {"사과", "사과", "배", "포도", "오렌지", "포도"};
  
  print(fruit);
  print(fruit.contains('사과'));    // 값이 있는지 확인
  print(fruit.toList());    // List로 변환
  
  List<String> fruit2 = ["사과", "사과", "배"];
  print(Set.from(fruit2));    // List를 Set으로 변환
}

// 실행 결과
// {사과, 배, 포도, 오렌지}
// true
// [사과, 배, 포도, 오렌지]
// {사과, 배}

 

 (4) enum : 한 변수의 값을 몇 가지 옵션으로 제한하고 싶을 때 void main()외부에서 정의하여 사용

enum Status {
  a,
  b,
  c,
}

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

// 실행 결과
// Status.a

 


4. 연산자

 (1) 수치연산자 : +, -, *, /(나눗셈, 몫), %(나눗셈, 나머지), ++, --, += ...

 (2) null 관련 연산자 : 아무 값도 없을 경우 null을 사용하며, 타입 뒤에 '?'를 추가하여 null 값을 저장

void main() {
  double? number1 = 1;   // 타입 뒤에 ?를 명시해서 null 값을 가질 수 있음
  // double number2 = null;   // 타입 뒤에 ?를 명시하지 않아 에러 발생
  
  double? nullnum;
  print(nullnum);
  
  nullnum ??= 3;    // ??을 사용하면 기존 값이 null일 때만 저장됨
  print(nullnum);
  
  nullnum ??= 4;
  print(nullnum);   // null이 아니므로 3이 유지된다.
}

// 실행 결과
// null
// 3
// 3

 

 (3) 값 비교 연산자 : >, <, >=, <=, ==, !=

 (4) 타입 비교 연산자 : is를 사용하여 변수 타입 비교

void main() {
  int number1 = 1;
  
  print(number1 is int);
  print(number1 is String);
  print(number1 is! int);
  print(number1 is! String);
}

// 실행 결과
// true
// false
// false
// true

 

 (5) 논리 연산자 : &&(and), ||(or)

 


5. 제어문

 (1) if 문 : if (조건1) { 코드1 } else if (조건2) {  코드2 } else { 코드3 }

void main() {
  String fruit = '사과';
  
  if (fruit == '배') {
    print('그 과일은 배 입니다');
  } else if (fruit == '포도') {
    print('그 과일은 포도 입니다');
  } else {
    print('그 과일은 사과 입니다');
  }
}

// 실행 결과
// 그 과일은 사과 입니다.

 

 (2) switch 문 : 입력된 상수값에 따라 알맞은 case 블록을 수행. case 끝네 break를 꼭 사용해야 하며, break를 통해 종료

enum Status {
  a,
  b,
  c,
}

void main() {
  Status status = Status.a;
  
  switch (status) {
    case Status.a:
      print('상태 : a');
      break;
    case Status.b:
      print('상태 : b');
      break;
    case Status.c:
      print('상태 : c');
      break;
    default:
      print('상태 : 상태 없음');
  }
  print(Status.values);
}

// 실행 결과
// 상태 : a
// [Status.a, Status.b, Status.c]

 

 (3) for 문 : 코드를 반복하여 실행할 때

void main() {
  for (int i = 0; i < 5; i++) {
    print(i);
  }
}

// 실행 결과
// 0
// 1
// 2
// 3
// 4

 

void main() {
  List<int> numList = [4, 3, 2, 1];
  
  for (int number in numList) {
    print(number);
  }
}

// 실행 결과
// 4
// 3
// 2
// 1

 

 (4) while문, do...while문 : 조건을 기반으로 코드를 반복하여 실행할 때(조건이 true이면 반복, false이면 중지)

void main() {
  int total = 0;
  
  while(total < 5){
    total += 1;
  }
  
  print(total);
}

// 실행 결과
// 5

 

void main() {
  int total = 0;
  
  do {
    total += 1;
  } while(total < 5);
  
  print(total);
}

// 실행 결과
// 5

 


6. 함수 및 람다

 (1) Positional parameter, Named parameter

 - 함수는 한번 지정하면 여러 곳에서 다시 사용할 수 있다.

 - 반환할 값이 없을 경우는 void 키워드를 사용한다.

int addNums(int a, int b) {
  return a + b;
}

void main() {
  print(addNums(3, 5));
}

// 실행 결과
// 8

 

 - 다트 함수에서는 Positional parameter(순서가 고정된 매개변수)와 Named parameter(이름이 있는 매개변수)가 있으며, Positional parameter는 입력된 순서대로 매개변수에 값이 지정되지만, Named parameter는 지정하고 싶은 매개변수의 이름을 키와 값 형태로 입력하며 입력 순서가 중요하지 않다. 이 때 중괄호 { }와 required 키워드를 사용한다.

int addNums({
  required int a,
  required int b,
}) {
  return a + b;
}

void main() {
  print(addNums(a: 2, b: 9));
}

// 실행 결과
// 11

 

 - 여기서 required 키워드는 매개변수가 null이 불가능한 타입일 경우 필수로 입력하거나 default를 지정해야 한다는 의미를 갖고 있다.

 - 기본값을 갖는 Positional parameter 지정 : [ ] 기호 사용

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

void main() {
  print(addNums(7));
}

// 실행 결과
// 9

 

 - Named parameter에 기본값 적용 : required 키워드를 생략하고 등호 다음에 원하는 기본값 입력

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

void main() {
  print(addNums(a: 7));
}

// 실행 결과
// 9

 

 - Positional parameter와 Named parameter를 섞어서 사용할 경우 Positional parameter가 Named parameter보다 반드시 먼저 위치해야 함

int addNums(
  int a, {
  required int b,
  int c = 4,
}) {
  return a + b + c;
}

void main() {
  print(addNums(1, b: 2, c: 8));
}

// 실행 결과
// 11

 

  (2) 익명 함수(Anonymous function), 람다 함수(Lambda function)

 - 함수 이름이 없이 일회성으로 사용되는 함수

 - 다트에서는 익명 함수와 람다 함수를 구분하지 않고 사용

 ※ 익명함수

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

 

 ※ 람다 함수

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

 

 - 익명 함수에서 { }를 빼고 => 기호를 추가한 것이 람다함수이다.

 - 매개변수는 없거나 하나 이상이어도 된다.

 - 익명 함수와 달리 람다 함수는 로직을 수행하는 스테이트먼트가 딱 하나이어야 한다.

 - 람다 함수는 이름을 정하고 미리 선언할 필요가 없기 대문에 글로벌 스코프로 다룰 필요가 없다.

 - 하나의 스테이트먼트만 다루기 때문에 간결하고 가독성이 높다. 

    → 콜백 함수나 리스트의 map(), reduce(), fold() 함수 등에서 일회성이 높은 로직을 작성할 때 주로 사용

void main() {
  List<int> numbers = [1, 2, 3, 4, 5];
  
  // 익명 함수
  final anonyNums = numbers.reduce((value, element) {
    return value + element;
  });
  
  print(anonyNums);
  
  // 람다 함수
  final lambdaNums = numbers.reduce((value, element) => value + element);
  
  print(lambdaNums);
}

// 실행 결과
// 15
// 15

 

 (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);

  // subtract() 함수도 Operation에 해당되는
  // 시그니처이므로 oper 변수에 저장 가능
  oper = subtract;
  oper(1, 2);
}


// 실행 결과
// 결괏값 : 3
// 결괏값 : -1

 

 - 다트에서 함수는 일급 객체(First-class citizen)이므로 함수를 값처럼 사용할 수 있다. 그래서 플러터에서는 typedef으로 선언한 함수를 다음과 같이 매개변수로 넣어 사용한다. (calculate() 함수의 3번째 매개변수로 add() 함수를 입력)

typedef Operation = void Function(int x, int y);

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

void calculate(int x, int y, Operation oper) {
  oper(x, y);
}

void main() {
  calculate(1, 2, add);
}

// 실행 결과
// 결괏값 : 3

 


7. try...catch

 - 특정 코드의 실행을 시도(try)해보고 문제가 있다면 에러를 잡는다(catch). 즉 try와 catch 사이의 괄호에 에러가 없을 때 실행할 로직을 작성하고, 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);
  }
}

// 실행 결과
// Exception: 이름이 잘못됐습니다!
반응형

댓글