3. Flutter 수업-dart 문법 예제 모음 part 2

dart 문법
홍윤's avatar
Sep 26, 2024
3. Flutter 수업-dart 문법 예제 모음 part 2
 

1. ex06.dart

//클래스 안에 없으니까 기본함수 //add를 더 추가하고 싶으면 자바에선 오버로딩이라고 한다. //하지만 dart는 오버로딩을 지원하지않는다. void add(int n1, int n2) { print(n1 + n2); } //Type생략 가능 -> Type추론(좋은 코드는 아니다.) void add1(n1, n2) { print(n1 + n2); } // return을 받는 것은 무조건 타입을 지정해줘야한다. var은 리턴 되지 않는다. // Object타입은 받을 수 있다. dynamic은 가능하다. int minus(n1, n2) { return n1 = n2; } //원래코드 int multi(n1,n2)였다 하지만 function을 쓰려고하면 //익명함수로 바꿔야한다. [int multi](n1,n2) []를 지운다. Function f = (n1, n2) { n1 = 3; // return n1 * n2; }; //람다식 표현식 //n1 = 3; 이렇게 만들지 못한다. //이유는 람다식은 한줄로 만들어야하기 때문에 Function f2 = (n1, n2) => n1 * n2; void main() { add(1, 2); //add1("일", 2); int result = minus(1, 2); print(result); print(f(1, 2)); }
notion image
💡

코드설명

1. 함수 오버로딩

  • 오버로딩:
    • 여러 개의 함수가 같은 이름을 가지지만, 매개변수의 타입이나 수가 다른 경우를 말합니다.
    • Java에서는 오버로딩을 지원하지만, Dart는 기본적으로 함수 오버로딩을 지원하지 않습니다.
    • 대신, 매개변수의 기본값이나 옵셔널 매개변수를 사용하여 유사한 기능을 구현할 수 있습니다.

2. 타입 추론과 명시적 타입 선언

  • 타입 추론:
    • add1 함수처럼 매개변수의 타입을 생략하면 Dart가 자동으로 타입을 추론합니다.
    • 이는 코드가 간결해지지만, 타입 안정성을 해칠 수 있습니다.
    • 따라서, 가능한 경우 명시적으로 타입을 선언하는 것이 좋습니다.
  • 명시적 타입 선언의 장점:
    • 코드의 가독성이 높아집니다.
    • 컴파일 시점에 타입 오류를 미리 잡을 수 있습니다.
    • 협업 시 다른 개발자들이 코드를 이해하기 쉽습니다.

3. Function 타입과 익명 함수

  • Function 타입:
    • Dart에서 함수 자체를 변수로 취급할 수 있습니다.
    • Function 타입을 사용하면 다양한 형태의 함수를 변수에 할당할 수 있습니다.
  • 익명 함수와 람다식:
    • 익명 함수는 이름이 없는 함수로, 일회성으로 사용하거나 다른 함수의 인자로 전달할 때 유용합니다.
    • 람다식은 익명 함수를 간결하게 표현한 형태로, 단일 표현식만 포함할 수 있습니다.

4. main 함수에서의 함수 호출

  • 함수 호출:
    • add, add1, minus, f, f2 등 다양한 함수를 호출하여 결과를 확인할 수 있습니다.
    • 주석 처리된 add1("일", 2);은 타입이 명시되지 않았기 때문에 실행 시 오류를 발생시킬 수 있습니다. 따라서 실제로는 주석을 유지하거나, 올바른 타입의 인수를 전달해야 합니다.

주의사항

  1. minus 함수의 구현 오류:
      • 현재 return n1 = n2;n2의 값을 n1에 할당하고 그 값을 반환합니다. 만약 의도한 것이 뺄셈이라면 return n1 - n2;로 수정해야 합니다.
      • 예시:
        • int minus(n1, n2) { return n1 - n2; }
  1. add1 함수의 타입 문제:
      • add1 함수는 타입이 명시되지 않아, 다양한 타입의 인수를 받을 수 있지만, 실제로는 타입이 일치하지 않을 경우 오류가 발생할 수 있습니다.
      • 따라서, 가능하면 타입을 명시적으로 지정하는 것이 좋습니다.
      • 예시:
        • void add1(int n1, int n2) { print(n1 + n2); }
  1. 람다식과 다중 문장:
      • 람다식은 한 줄로 표현해야 하므로, 다중 문장을 포함할 수 없습니다. 만약 여러 문장을 포함해야 한다면 익명 함수 블록을 사용해야 합니다.
      • 예시:
        • Function f3 = (n1, n2) { n1 = 3; return n1 * n2; };

          결론

          Dart에서 함수 선언, 타입 추론, 익명 함수, 람다식 등의 개념을 잘 보여주고 있습니다. 다음과 같은 점들을 유의하면서 코드를 작성하면 더욱 안전하고 효율적인 Dart 프로그램을 작성할 수 있습니다.
        • 타입을 명시적으로 선언하여 코드의 가독성과 안정성을 높이세요.
        • 함수 오버로딩을 지원하지 않는 Dart의 특성을 이해하고, 대신 옵셔널 매개변수매개변수의 기본값을 활용하세요.
        • 익명 함수람다식을 적절히 사용하여 코드의 간결함을 유지하세요.
        • 함수 구현 시 논리 오류가 없는지 꼼꼼히 확인하세요.

2. ex07.dart

//함수 행위를 결정하지 못할 때 ()안에 function 행위(리스너)를 넣는다. //print 뽑는게 아니니까 이때는 익명함수를 쓰는게 좋다. void whenComeMother(Function beh) { beh(); } void off() { print("컴퓨터끄기"); } //리턴 안 할 거면 whenComeMother() => print("컴퓨터끄기") 이런식은 쓰지 말자! void main() { whenComeMother(() { print("컴퓨터 끄기"); }); } //new 생성자 생략가능 // dog a = dog();
notion image
💡

코드설명

고차 함수 (Higher-Order Function)

  • 정의:
    • 다른 함수를 인자로 받거나, 함수를 반환하는 함수를 고차 함수라고 합니다.
  • 장점:
    • 코드의 재사용성을 높이고, 유연한 동작을 구현할 수 있습니다.
    • 콜백(callback) 패턴을 통해 비동기 작업이나 이벤트 핸들링에 유용하게 사용됩니다.
  • 예제:
    • whenComeMother는 인자로 받은 함수를 실행하는 고차 함수입니다. 이를 통해 다양한 동작을 유연하게 정의할 수 있습니다.

2. 익명 함수 (Anonymous Function)

  • 정의:
    • 이름이 없는 함수로, 주로 일회성으로 사용되거나 다른 함수의 인자로 전달될 때 사용됩니다.
  • 장점:
    • 코드의 간결성을 유지할 수 있습니다.
    • 특정 작업을 간단하게 정의할 수 있어, 코드의 가독성을 높입니다.
  • 예제:
    • main 함수에서 whenComeMother에 전달된 () { print("컴퓨터 끄기"); }는 익명 함수입니다.

3. 람다식 (Lambda Expression)

  • 정의:
    • 단일 표현식으로 함수를 간결하게 정의하는 방법입니다. Dart에서는 화살표 함수(=>)를 사용하여 람다식을 표현할 수 있습니다.
  • 주의사항:
    • 람다식은 단일 표현식만을 포함할 수 있으며, 여러 문장을 포함하려면 익명 함수를 사용해야 합니다.
  • 예제:
    • 주신 코드에서는 람다식을 사용하지 않고 익명 함수를 사용하여 whenComeMother를 호출하고 있습니다. 이는 함수 내에서 여러 작업을 수행할 필요가 있을 때 적합합니다.

4. 함수 타입 지정

  • Function 타입:
    • Dart에서 함수 자체를 변수로 취급할 수 있으며, Function 타입을 사용하여 함수를 인자로 받을 수 있습니다.
  • 타입 명시의 중요성:
    • 함수의 매개변수나 반환 타입을 명시적으로 지정하면, 코드의 안정성과 가독성이 향상됩니다.
    • 예를 들어, void whenComeMother(void Function() beh)와 같이 구체적으로 함수의 시그니처를 지정할 수 있습니다. 이는 잘못된 타입의 함수가 전달되는 것을 방지할 수 있습니다.
    • 요약

      Dart에서 고차 함수와 익명 함수를 활용하는 기본적인 예제입니다. 이를 통해 다른 함수의 동작을 유연하게 정의하고, 필요에 따라 다양한 행위를 전달할 수 있습니다. 다음과 같은 점들을 유의하면서 코드를 작성하면 더욱 안전하고 효율적인 Dart 프로그램을 작성할 수 있습니다.
    • 고차 함수를 활용하여 코드의 재사용성과 유연성을 높이세요.
    • 익명 함수람다식을 적절히 사용하여 코드의 간결함을 유지하세요.
    • 함수 타입을 명시적으로 지정하여 코드의 안정성과 가독성을 향상시키세요.
    • 타입 안전성을 유지하기 위해 함수의 매개변수와 반환 타입을 구체적으로 정의하세요.

3. ex08.dart

class Cat { String name; int age; String color; int thirsty; //이게 쓸 일이 많다. Cat(this.name, this.age, this.color, this.thirsty); } class Dog { String name; int age; String color; int thirsty; //이니셜라이즈 키워드 //한줄 요약시 Dog(String name, int age, String color, int thirsty) : this.name = name, this.age = age, this.color = color, this.thirsty = thirsty; } void main() { Dog("", 1, "", 1); }
💡
 

생성자(Constructor)의 두 가지 방식

  • 생성자 파라미터 이니셜라이저 (Cat 클래스):
    • Cat(this.name, this.age, this.color, this.thirsty);
    • 장점:
      • 코드가 간결합니다.
      • 클래스의 속성과 생성자 파라미터 간의 매핑이 명확합니다.
    • 단점:
      • 초기화 로직이 복잡할 경우 사용하기 어렵습니다.
  • 이니셜라이저 리스트 (Dog 클래스):
    • dart코드 복사Dog(String name, int age, String color, int thirsty) : this.name = name, this.age = age, this.color = color, this.thirsty = thirsty;
    • 장점:
      • 초기화 로직을 자유롭게 작성할 수 있습니다.
      • 상위 클래스의 생성자를 호출할 때 유용합니다.
    • 단점:
      • 코드가 다소 길어질 수 있습니다.

2. 생성자 오버로딩

  • 설명:
    • 오버로딩은 같은 이름의 함수를 여러 번 정의하여, 매개변수의 타입이나 수에 따라 다른 동작을 수행하게 하는 기법입니다.
    • Java에서는 생성자 오버로딩을 지원하지만, Dart는 기본적으로 생성자 오버로딩을 지원하지 않습니다.
  • 대안:
    • 명명된 생성자: 같은 클래스 내에서 여러 생성자를 정의할 수 있습니다.
      • class Dog { String name; int age; String color; int thirsty; Dog(this.name, this.age, this.color, this.thirsty); // 명명된 생성자 Dog.brownDog(String name, int age) : this(name, age, "갈색", 5); } void main() { Dog dog1 = Dog("멍멍이", 3, "갈색", 7); Dog dog2 = Dog.brownDog("브라운", 2); }
    • 옵셔널 매개변수: 생성자의 매개변수를 옵션으로 만들어 유연하게 초기화할 수 있습니다.
      • class Dog { String name; int age; String color; int thirsty; Dog(this.name, this.age, {this.color = "검은색", this.thirsty = 5}); } void main() { Dog dog1 = Dog("멍멍이", 3); Dog dog2 = Dog("브라운", 2, color: "갈색", thirsty: 7); }

        요약

        Dart에서 클래스생성자를 정의하는 두 가지 방식을 잘 보여주고 있습니다. Cat 클래스는 생성자 파라미터 이니셜라이저를 사용하여 간결하게 속성을 초기화하고, Dog 클래스는 이니셜라이저 리스트를 사용하여 보다 유연하게 속성을 초기화하고 있습니다.
        또한, Dart에서는 new 키워드를 생략할 수 있으며, 생성자 오버로딩을 직접 지원하지 않지만 명명된 생성자옵셔널 매개변수를 통해 유사한 기능을 구현할 수 있습니다.
      • Tip:
        • 생성자에서 타입을 명시적으로 지정하는 습관을 들이면 코드의 가독성과 안정성을 높일 수 있습니다.
        • 명명된 생성자옵셔널 매개변수를 활용하여 클래스의 인스턴스를 다양한 방식으로 초기화할 수 있습니다.

4. ex09.dart

class Dog { int age; String name; //오류가 사라지는 이유는 값이 무조건 들어가는 상황이기 때문에 오류가 나지 않는다. //Dog(this.age, this.name) //빌더 패턴이랑 비슷한 경우이다. Dog({required this.age, required this.name}); } //1. ?타입이 있고 //2. {} 선택적 매개변수를 받을 수 있다. class Cat { int? age; String? name; //값을 초기화 하고 싶은때는 //선택적 매개변수에게만 디폴트값을 설정 할 수 있다. Cat({this.age, this.name = "토토"}); void cry() { print("야옹"); } //값을 바로 보고싶으면 오버라이딩을 해주면 된다. //생성자 자동완성 키 찾아보기! String toString() { return "age: ${age}, name : ${name}"; } } //선택적 매개변수의 뜻은 넣어도 되고 안 넣어도 된다. //age:1 이런식으로 생략이 가능하다. 순서도 바꿔도 상관없다. //자바의 빌더패턴이 필요가 없다. 빌더패턴은 장점은 순서가 상관없다. 실수도 없다. //required를 쓰면 name,age를 반드시 받아야한다.!!!! //Cat의 경우는 하나만 써도 받을 수 있다. void main() { Dog d = Dog(name: "토토", age: 10); Cat c = Cat(age: 10); //케스케이드연산자 Cat c1 = Cat(age: 10)..cry(); print("-----------------------------------"); print(d); print("-----------------------------------"); print(c); print("-----------------------------------"); print(c1); }
notion image
💡

코드설명

1.명명된 매개변수(named parameters)와 필수 매개변수(required parameters)

  • 명명된 매개변수(named parameters):
    • 매개변수를 중괄호 {} 안에 정의하면, 객체를 생성할 때 매개변수의 이름을 명시적으로 지정하여 값을 전달할 수 있습니다.
    • 예: Dog(name: "토토", age: 10);
    • 장점:
      • 매개변수의 순서에 구애받지 않습니다.
      • 매개변수의 의미를 명확하게 전달할 수 있습니다.
  • 필수 매개변수(required parameters):
    • required 키워드를 사용하면, 해당 매개변수를 반드시 제공해야 합니다.
    • 이는 객체 생성 시 필수적인 속성을 빠뜨리지 않도록 도와줍니다.
    • 예: Dog({required this.age, required this.name});

2. 널 허용 타입(null safety)

  • Dart는 **널 안전성(null safety)**을 지원하여, 변수에 null 값을 허용할지 여부를 명확하게 지정할 수 있습니다.
  • 널 허용 타입(nullable types):
    • int?, String?와 같이 ?를 붙여서 선언하면, 해당 변수는 null 값을 가질 수 있습니다.
    • 예: int? age;, String? name;
  • 널 허용하지 않는 타입(non-nullable types):
    • int, String과 같이 ? 없이 선언하면, 해당 변수는 null 값을 가질 수 없습니다.
    • 예: int age;, String name;
  • 기본값 설정:
    • 선택적 매개변수에 기본값을 설정하면, 값을 제공하지 않아도 기본값이 자동으로 할당됩니다.
    • 예: Cat({this.age, this.name = "토토"});

3. 오버라이딩(overriding)

  • toString 메소드 오버라이딩:
    • toString 메소드를 오버라이딩하면, 객체를 문자열로 표현할 때 원하는 형식으로 출력할 수 있습니다.
    • 예:
      • @override String toString() { return "age: ${age}, name: ${name}"; }
    • 이는 디버깅 시 객체의 상태를 쉽게 확인할 수 있게 도와줍니다.

4. 케스케이드 연산자(cascade operator)

  • 정의:
    • 케스케이드 연산자(..)는 객체를 생성한 후, 해당 객체의 메소드를 연속적으로 호출할 때 사용됩니다.
  • 사용 예:
    • Cat c1 = Cat(age: 10)..cry();
    • c1 객체를 생성한 후, 바로 cry 메소드를 호출합니다.
  • 장점:
    • 객체를 생성하면서 여러 메소드를 연속적으로 호출할 수 있어 코드가 간결해집니다.
  • 주의사항:
    • 메소드 체이닝을 사용할 때, 객체가 올바르게 초기화되었는지 확인해야 합니다.

5. 빌더 패턴과 Dart의 대안

  • 빌더 패턴(builder pattern):
    • Java 등에서 객체의 복잡한 생성 과정을 단순화하고, 매개변수의 순서에 구애받지 않도록 도와주는 디자인 패턴입니다.
  • Dart의 대안:
    • Dart에서는 명명된 매개변수(named parameters)와 필수 매개변수(required parameters)를 사용하여 빌더 패턴과 유사한 기능을 구현할 수 있습니다.
    • 또한, 선택적 매개변수(optional parameters)와 기본값 설정을 통해 유연한 객체 생성을 지원합니다.
    • 요약

      Dart에서 클래스생성자를 정의하는 다양한 방법을 잘 보여주고 있습니다. **명명된 매개변수(named parameters)**와 **필수 매개변수(required parameters)**를 사용하여 객체 생성 시 매개변수의 순서에 구애받지 않고, 필요한 값을 명확하게 제공할 수 있습니다. 또한, **널 허용 타입(null safety)**과 기본값 설정을 통해 더욱 유연하고 안전한 코드를 작성할 수 있습니다.
    • 명명된 매개변수필수 매개변수를 적절히 사용하여 객체 생성을 명확하고 안전하게 관리하세요.
    • 널 허용 타입을 통해 필요한 경우에만 null을 허용하고, 그렇지 않은 경우에는 필수 값을 제공하도록 설계하세요.
    • toString 메소드 오버라이딩을 활용하여 객체의 상태를 쉽게 출력하고, 디버깅 시 유용하게 사용하세요.
    • 케스케이드 연산자를 사용하여 객체 생성과 메소드 호출을 간결하게 표현하세요.
    • 명명된 생성자빌더 패턴을 활용하여 복잡한 객체 생성 과정을 효율적으로 관리하세요.

5. ex10.dart

// null 처리방법 int? findById(int id) { return id == 1 ? 1 : null; } main() { //위험한코드 1 :특정한 값이 들어오면 터질 수 있기때문에 "!" 확실하지 않을때 쓰면 안된다. int r1 = findById(1)!; print(r1); print("-------------------------------------------------"); //위험한 코드 2: null 대체연산자(Orelse) int r2 = findById(5) ?? 0; print(r2); print("-------------------------------------------------"); // findById(5).toDouble(); 이건 되지 않는다. null일 수 있기 때문에 //findById(5)!.toDouble(); //이 함수는 null이면 실행하지 말구 null 아니면 실행이 되게한다. //이렇게 한 줄로 연산자를 쓸 수 있다. double? r3 = findById(5)?.toDouble() ?? 0; print(r3); //null 이면 0.0으로 받는다. //print(r3 ?? 0.0); }
notion image
💡

코드설명

  1. findById 함수
      • 역할: 주어진 id1이면 1을 반환하고, 그렇지 않으면 null을 반환합니다.
      • 반환 타입: int?int 또는 null을 반환할 수 있음을 의미합니다.
  1. main 함수 내 코드
      • r1 선언 및 출력
        • int r1 = findById(1)!; print(r1);
        • 설명: findById(1)1을 반환하므로 ! 연산자를 사용해 null이 아님을 확신하고 int 타입으로 변환합니다.
        • 주의: 만약 findByIdnull을 반환하면 런타임 오류가 발생합니다.
        • 출력: 1
      • r2 선언 및 출력
        • int r2 = findById(5) ?? 0; print(r2);
        • 설명: findById(5)null을 반환하므로, ?? 연산자를 사용해 null일 경우 기본값 0을 할당합니다.
        • 출력: 0
      • r3 선언 및 출력
        • double? r3 = findById(5)?.toDouble() ?? 0; print(r3);
        • 설명:
          • findById(5)null이면 ?. 연산자로 인해 toDouble()이 호출되지 않고 null이 됩니다.
          • ?? 연산자를 통해 null일 경우 기본값 0을 할당합니다.
        • 출력: 0

핵심 포인트

  • 널 단언 연산자(!):
    • 변수나 함수가 null이 아님을 확신할 때 사용합니다.
    • 확신할 수 없을 경우 사용하면 런타임 오류가 발생할 수 있습니다.
  • 널 병합 연산자(??):
    • 왼쪽 값이 null일 경우 오른쪽 값을 반환합니다.
    • 안전하게 기본값을 제공할 때 유용합니다.
  • 널 안전 호출 연산자(?.):
    • 객체가 null이 아닐 때만 메소드를 호출합니다.
    • null일 경우 null을 반환하여 오류를 방지합니다.
    • 요약

      Dart에서 null을 안전하게 처리하는 여러 방법을 활용할 수 있습니다. ! 연산자는 신중하게 사용하고, 가능한 ???. 연산자를 사용하여 안전성을 높이는 것이 좋습니다.
Share article

Uni