Dandy Now!
  • [Flutter] "모두가 할 수 있는 플러터 UI 입문"과 함께한 Dart 기초 문법 요약정리
    2022년 01월 31일 15시 06분 23초에 업로드 된 글입니다.
    작성자: DandyNow
    728x90
    반응형

    "최주호, 정호준, & 정동진. (2021). 모두가 할 수 있는 플러터 UI 입문. 앤써북"으로 플러터 공부를 하고 있다. 최근 Dart 기초 문법을 끝냈으며, 그간 공부했던 언어들(파이썬, 자바스크립트)과 특이점이 있는 부분을 복습하고 기억하기 위해 정리하였다.

     


     

    // var와 dynamic으로 타입 추론이 가능하나 차이점이 있다.
    void main() {
      // int n1 = 1;
      // double d1 = 10.1;
      // bool b1 = true;
      // String s1 = "홍길동";
      var n1 = 1;
      dynamic d1 = 10.1;
      var b1 = true;
      var s1 = "홍길동";
    
      // runtimeType를 이용해 타입을 확인할 수 있다.
      // var로 한 번 초기화된 데이터 타입은 다른 타입으로 변경 불가능.
      print("var 정수 : ${n1.runtimeType}"); // var 정수 : int
      print("dynamic 실수 : ${d1.runtimeType}"); // dynamic 실수 : double
    
      // dynamic은 var와 달리 다른 타입으로 변경 가능. 
      d1 = 10;
      print("dynamic 정수 : ${d1.runtimeType}"); // dynamic 실수 : int
    
      print("var 부울 : ${b1.runtimeType}"); // var 부울 : bool
      print("var 문자열 : ${s1.runtimeType}"); // var 문자열 : String
    }

    var와 dynamic으로 타입 추론이 가능하다. 차이점은 var로 한 번 초기화된 데이터 타입은 다른 타입으로 변경할 수 없다.  반면 dynamic은 다른 타입으로 변경이 가능하다.

     

    // 산술 연산자
    void main() {
      print("3 ~/ 2 = ${3 ~/ 2}"); // 몫, 파이썬의 //와 다르게 생겼다.
    }

    산술 연산자에서 몫을 구할 때 파이썬의 경우 // 를 사용한다. Dart에서는 ~/ 이다.

     

    // 조건문
    void main() {
      // 삼항 연산자
      print(point >= 60 ? "합격" : "불합격"); // 자바스크립트에서의 사용법과 같아 보인다.
    
      // null 대체 연산자
      dynamic username = null;
      print(username); // null
      print(username ?? "홍길동"); // 홍길동(?? 앞의 값이 null이면 ?? 뒤의 값을 출력하고, 아니면 ?? 앞의 값을 출력)
    }

    if 조건문의 경우 특이점이 없다. 삼항 연산자는 자바스크립트의 경우와 같다. 조건이 맞으면 : 의 왼쪽 값을 출력하고 조건이 맞지 않으면 : 의 오른쪽 값을 출력한다. null 대체 연산자는 생소했다. ?? 앞의 값이 null 인 경우 ?? 뒤의 값을 출력하고, 아니면 ?? 앞의 값을 출력한다.

     

    // 익명함수
    // 함수를 매개변수로 전달받을 때는 Function 키워드 사용
    void magicBox(Function f) {
      f();
    }
    
    // 익명 함수를 인수로 전달할 수 있다.
    void main() {
      magicBox(() {
        print("더하기");
      });
    }

    함수를 매개변수로 전달받을 때는 Function 키워드를 사용한다. 익명 함수를 인수로 전달할 수 있다.

     

    // 변수에 익명 함수를 대입할 수 있다. 이때 Function 타입 사용
    Function add = (int n1, int n2) {
      print(n1 + n2);
    };
    
    void main() {
      add(1, 3);
    }

    변수에 익명 함수를 대입할 수 있다. 이때 Function 타입을 사용한다. 자바스크립트의 함수 표현식과 유사해 보인다.

     

    void main() {
      // 람다식
      Function addOne = (n) => n + 1;
      print(addOne(2)); // 3
    
      // 익명함수
      Function addTwo = (n) {
        return n + 1;
      };
      print(addTwo(2)); // 3
    }

    위 코드의 결과 값은 동일하다. 각각 람다식과 익명 함수로 작성되었다.

     

    class Dog {
      String name = "Toto";
      int age = 13;
      String color = "white";
      int thirsty = 100;
    
      // 객체는 다른 객체와 협력할 수 있다.
      void drinkWater(Water w) {
        w.drink();
        thirsty = thirsty - 50;
      }
    }
    
    class Water {
      double liter = 2.0;
    
      void drink() {
        liter = liter - 0.5;
      }
    }
    
    void main() {
      Dog d1 = Dog();
      Water w1 = Water();
    
      d1.drinkWater(w1);
      print("남은 물의 양은 ${w1.liter}");
      d1.drinkWater(w1);
      print("갈증 지수는 ${d1.thirsty}");
    }

    객체는 다른 객체와 협력할 수 있다.

     

    class Dog {
      String name = "Toto";
      int age = 13;
      String color = "white";
      int thirsty = 100;
    
      Dog(this.name, this.age, this.color, this.thirsty) {} // 생성자
    }
    
    void main() {
      Dog d1 = Dog("Toto", 13, "white", 100);
      Dog d2 = Dog("Mango", 2, "black", 50);
    
      print("d1의 이름은 ${d1.name}");
      print("d2의 이름은 ${d2.name}");
    }

    생성자는 클래스를 객체로 만들 때 초기화를 위한 함수이다.

     

    // 선택적 매개변수
    class Person {
      String name;
      int money;
    
      Person({this.name = '', this.money = 0}); // 기본 값을 정의할 수 있다. 매개변수를 {}로 감싸야 한다.
    }
    
    void main() {
      Person p1 = Person(name: "홍길동");
      Person p2 = Person(name: "임꺽정", money: 10000); // 인수의 순서나 누락 상관없다.
    
      print("${p1.name}의 재산은 ${p1.money}"); // 홍길동의 재산은 0
      print("${p2.name}의 재산은 ${p2.money}"); // 임꺽정의 재산은 10000
    }

    선택적 매개변수를 이용해 기본 값을 정의할 수 있다. 생성자의 매개변수를 {}로 감싸야한다.

     

    class Chef {
      String name;
      Chef(this.name);
      void cook() {
        print("요리를 시작합니다.");
      }
    }
    
    void main() {
      // Chef c1 = Chef("홍길동");
      // print("요리사 이름 ${c1.name}"); //요리사 이름 홍길동
    
      Chef c1 = Chef("홍길동")..cook(); // ..는 cascade 연산자이다. 코드 한 줄로 객체를 변수로 넘겨주면서 객체가 가진 함수를 호출할 수 있는 표기법이다.
      print("요리사 이름 ${c1.name}"); // 요리를 시작합니다. // 요리사 이름 홍길동
    }

    ..는 cascade 연산자이다. 코드 한 줄로 객체를 변수로 넘겨주면서 객체가 가진 함수를 호출할 수 있는 표기법이다.

     

    class Burger {
      Burger() {
        print("버거");
      }
    }
    
    class CheeseBurger extends Burger { // 부모 생성자 Burger을 상속
      CheeseBurger() {
        print("치즈버거");
      }
    }
    
    void main() {
      CheeseBurger cb = CheeseBurger(); //버거이기도 하고 치츠버거이기도 하다. 다형성 성립
    }

    부모 생성자 Burger를 상속하여 객체 cb는 버거이기도 하고 치츠버거이기도 하다. 다형성 성립.

     

    class Burger {
      String name = '';
      Burger() {}
    }
    
    class CheeseBurger extends Burger {
      CheeseBurger(String name) {
        // this.name = name;
        super.name = name; // 보모의 객체 참조
      }
    }
    
    void main() {
      CheeseBurger cb = CheeseBurger("치즈햄버거");
      print(cb.name);
    }

    super 키워드는 자식이 부모의 객체를 참조할 수 있는 키워드이다.

     

    class Burger {
      final String name; // final은 상수, 단 한번만 초기화할 수 있고, 반드시 초기화 되어야 한다.
      Burger(this.name) {}
    }
    
    class CheeseBurger extends Burger {
      CheeseBurger(String name) : super(name) {} // 이니셜라이져(:) 키워드는 생성자의 내부 스택이 실행되기 전에 다른 무언가를 호출하고 싶을 때 사용한다. 이 코드에서 이니셜라이즈를 사용하지 않으면 final 변수가 초기화 되지 않아 오류가 발생한다.
    }
    
    void main() {
      CheeseBurger cb = CheeseBurger("치즈햄버거");
      print(cb.name);
    }

    final은 상수이다. 단 한 번만 초기화할 수 있고, 반드시 초기화되어야 한다. 이니셜 라이져(:) 키워드는 생성자의 내부 스택이 실행되기 전에 다른 무언가를 호출하고 싶을 때 사용한다. 위 코드에서는 이니셜 라이즈를 사용하지 않으면 final 변수가 초기화되지 않아 오류가 발생한다.

     

    class Engine {
      int power = 5000;
    }
    
    class Wheel {
      String wheelName = "4륜 구동 바퀴";
    }
    
    class BMW with Engine, Wheel {
    } // Mixin은 여러 클래스 계층에서 클래스의 코드를 재사용하는 방법. 다중 상속 문제 해결, 컴퍼지션 사용 없이 다른 클래스 코드 재사용 가능.
    
    void main() {
      BMW b = new BMW();
      print(b.power);
      print(b.wheelName);
    
      BMW c = BMW(); // new를 붙이지 않아도 객체가 만들어 진다.
      print(c.power);
      print(c.wheelName);
    }

    Mixin은 여러 클래스 계층에서 클래스의 코드를 재사용하는 방법이다. 다중 상속 문제 해결하고, 컴퍼지션 사용 없이 다른 클래스 코드를 재사용할 수 있다.

     

    // 추상 클래스
    abstract class Animal {
      void sound(); // 구체화되지 않은 추상 메서드
    }
    
    class Dog implements Animal {
      void sound() {
        print("멍멍 배고파");
      }
    }
    
    class Cat implements Animal {
      void sound() {
        print("야옹 배고파");
      }
    }
    
    class Fish implements Animal {
      void sound() {
        print("뻐끔뻐끔 배고파");
      }
    }
    
    void start(Animal a) {
      a.sound();
    }
    
    void main() {
      start(Dog());
      start(Cat());
      start(Fish());
    }

    추상 클래스는 구체화되지 않은 추상 메서드를 가지고 있다. 자식 클래스는 구체화된 메서드를 오버라이드 한다.

     

    // 컬렉션
    import 'dart:math'; // Random 클래스용 라이브러리
    
    void main() {
      List<int> nums = [1, 2, 3, 4]; // 리스트
      print(nums[0]);
      
      Map<String, dynamic> user = {"id": 1, "username": "cos"}; // 맵
      print(user["id"]);
      
      // Set을 이용한 로또 번호 생성하기
      Set<int> lotto = {}; // Set, 집합 표현, 중복 허용 안함, 순서 없음.
      Random r = Random();
      lotto.add(r.nextInt(45) + 1);
      lotto.add(r.nextInt(45) + 1);
      lotto.add(r.nextInt(45) + 1);
      lotto.add(r.nextInt(45) + 1);
      lotto.add(r.nextInt(45) + 1);
      lotto.add(r.nextInt(45) + 1);
      print(lotto);
      List<int> lottoList = lotto.toList(); // List 타입으로 변경
      lottoList.sort();
      print(lottoList);
    }

    리스트는 파이썬과 동일하다. 맵은 파이썬의 딕셔너리를 닮았다. Set은 집합을 표현하며 중복을 허용하지 않고, 순서가 없다. Random 클래스의 사용법도 잘 봐두자!

     

    // map 함수
    void main() {
      var chobab = ["새우초밥", "광어초밥", "연어초밥"];
      var chobabChange = chobab.map((e) => "간장_" + e);
      print(chobabChange); // (간장_새우초밥, 간장_광어초밥, 간장_연어초밥)
    }

    컬렉션의 Map와 혼동하지 말자! 파이썬의 map과 유사한 기능이다. iterable object에 사용한다.

     

    // 필터링. 컬렉션에 담긴 데이터를 삭제할 때 많이 사용
    void main() {
      var chobab = ["새우초밥", "광어초밥", "연어초밥"];
      var chobabChange = chobab.where((element) => element != "광어초밥");
      print(chobabChange); // (새우초밥, 연어초밥)
    }

    where는 필터링 기능이다. 컬렉션에 담긴 데이터를 삭제할 때 많이 사용한다.

     

    void main() {
      // 복사1
      var list = [1, 2, 3];
      var list1 = list; // 참조에 의한 호출
      var newList = [...list]; // 복사, 값에 의한 호출
      newList[0] = 500;
      print(list); // [1, 2, 3]
      print(list1); // [1, 2, 3]
      print(newList); // [500, 2, 3]
    
      // 복사2
      var list2 = [
        {"id": 1},
        {"id": 2}
      ];
      var newList2 = list2.map((i) => {...i}).toList(); // 리스트 내에 있는 맵을 복사할 경우 {} 안에서 스프레드 연산자 사용.
      print(newList2); // [{id: 1}, {id: 2}]
      print(list2.hashCode); // 400195542
      print(newList2.hashCode); // 389943684, 주소가 list2와 다르다. 복사된 값에 의한 호출이라는 의미.
      newList2[0]["id"] = 500;
      print(list2); // [{id: 1}, {id: 2}]
      print(newList2); // [{id: 500}, {id: 2}]
      print(list2.hashCode); // 533114396
      print(newList2.hashCode); // 1051251238
      
      // 추가
      var list3 = [1, 2, 3];
      var newList3 = [...list, 4]; // 컬렉션 데이터 추가
      print(list3); // [1, 2, 3]
      print(newList3); // [1, 2, 3, 4]
      
      // 수정
      var users = [
        {"id": 1, "username": "cos", "password": 1234},
        {"id": 2, "username": "ssar", "password": 5678},
      ];
      var newUsers = users.map((user) =>
          // user["id"] == 2 ? {"id": 2, "username": "love", "password": 5678} : user);
          user["id"] == 2 ? {...user, "username": "love"} : user); // 위와 동일한 코드
      print(users); // [{id: 1, username: cos, password: 1234}, {id: 2, username: ssar, password: 5678}]
      print(newUsers); // ({id: 1, username: cos, password: 1234}, {id: 2, username: love, password: 5678})
    }

    스프레드 연산자 ... 는 값을 복사, 추가, 수정한다. 리스트 내에 있는 맵을 복사할 경우 {} 안에서 스프레드 연산자를 사용한다. 삼항 연산에 사용된 스프레드 연산자도 눈여겨보자!

     

    // final, const는 둘다 상수. final은 runtime때 초기화, const는 compile때 초기화.
    class Animal {
      final String name;
      const Animal(this.name);
    }
    
    void main() {
      Animal a1 = const Animal("사자");
      Animal a2 = const Animal("사자");
      Animal a3 = const Animal("기린");
      print(a1.hashCode); // 970471807
      print(a2.hashCode); // 970471807 // 생성자 인수가 동일하기 때문에 객체를 재사용
      print(a3.hashCode); // 197636435 // 생성자 인수가 다르기 때문에 새로운 객체 생성
    }

    final, const는 둘 다 상수를 선언하는 키워드이다. final은 runtime때 초기화, const는 compile때 초기화된다. 생성자 인수가 동일할 때는 객체를 재사용하고, 다를 때는 새로운 객체를 생성한다. compile때 초기화되면 런타임 때 속도가 빠르다. 동일한 클래스를 객체로 여러 번 만들어야 하는 경우에 생성자 인수의 값이 동일하면 같은 객체이기 때문에 메모리에 만들어진 객체를 재사용한다. const를 잘 활용하면 flutter에서 그림을 효율적으로 그릴 수 있다.

     

    class Person {
      // String name;
      // int age;
      // Person({required this.name, required this.age}) // required 붙이면 선택적 파라미터이지만 값을 무조건 받아야 한다.
      
      String? name; // 자료형 뒤에 ?를 붙이면 null을 받을 수 있는 타입이 된다.
      int? age;
    
      Person({this.name, this.age});
    }
    
    void main() {
      Person p = Person(age: 30);
      String name2 = p.name ?? "이름없음"; // null 대체연산자 값 받기, 디폴트값 "이름없음"
      int age2 = p.age ?? 0;
    
      print(p.name); // null
      print(p.age); // 30
      print(name2); // 이름없음
      print(age2); // 30
    }

    Dart 2.12 버전부터 Null Safety가 적용된다. 자료형 뒤에 ? 를 붙이면 Null Safety가 적용되어 null을 받을 수 있는 타입이 된다. required를 생성자 파라미터의 변수명 앞에 붙이면 선택적 파라미터이지만 값을 무조건 받아야 한다.

    728x90
    반응형
    댓글