다트) Dart 2 - 함수와 제어문, 클래스 정리
1. 함수와 제어문
1) 함수 선언과 호출하기
(1) 함수 선언 위치
- 톱 / 클래스 / 함수 내부에서 함수 선언 가능
void some1() { void some2() {} } class Myclass { void some3() {} }
- 오버로딩 미지원
- 오버로딩 : 동명 함수를 타입, 개수를 다양하게 여러개 만드는 방법
class My Class { void some() {} void some(int a) {} // 오류(함수 이름 중복) }
- 오버로딩 : 동명 함수를 타입, 개수를 다양하게 여러개 만드는 방법
(2) 매개변수 타입
- 타입 지정
void some(int? a) {} main() { some(20); some(null); some('hello'); // 오류 }
- 타입 미지정(var 선언, 형식 생략) → dynamic 타입
void some(var a) {} // var 선언 // void some(a) {} // 생략 main() { some(); // 오류 some(15); some('world'); }
(3) 함수의 반환 타입
- 반환 타입 지정(없을 시 void, return 미지정 시 null)
void some1() {} // 없음 int some2() {return 20;} // int 20 dynamic some3() {return 20;} // dynamic 20 some4() {} // dynamic null
(4) 화살표 함수
void some() {
print('hello world');
}
void some() => print('hello world');
2) 명명된 매개변수
- 일반 함수 호출 시, 매개변수의 개수/타입/순서에 맞게 데이터 전달
void some(int a, String b, bool c) {} main() { some(); // 오류 some('hello', true, 10); // 오류 some(10, 'hello', true); }
(1) 명명된 매개변수
- 옵셔널(Optional) : 함수의 매개변수를 선택적으로 지정 하는 기능
- 이름:값 형태
void some({String? data}) => print('data: $data'); main() { some(data: 'world'); }
(2) 명명된 매개변수 선언 규칙
- 중괄호 {}로 묶어, 마지막에 한번만 선언
- 기본값 설정 가능
void some1({String? data1, bool? data2}, int data3) { } // 오류 void some2(int data1, {String? data2, bool? data3}, {int? data4}) { } // 오류 void some3(int data1, {String? data2, bool? data3}) { } // 성공
(3) 명명된 매개변수 호출 규칙
- 데이터 전달 순서 상관 없이 전달 시 이름 명시
- 모든 매개변수 전달 필요X(기본값 활용)
void some(int data1, {String? data2, bool? data3}) {} main() { some(); // 오류 some(10); // 성공 some(10, 'hello', true); // 오류 some(data3:true, 10, data2:'hello'); // 성공 }
(4) 기본 인자 설정 : 기본값
String myFun({String data = 'hello'}){
return data;
}
main() {
print('myFun() result : ${myFun()}'); // myFun() result : hello
print('myFun(world) result : ${myFun(data:"world")}'); // myFun() result : hello
}
(5) 필수 매개변수 선언 - required
some({required int arg}) {print(arg);}
main() {
some(); // 오류
some(arg:10); // 성공
}
3) 옵셔널 위치 매개변수(optional positional parameter)
- 대괄호 []로 묶어 마지막에 한번만 선언
- 기본값 설정 가능
void some(int arg1, [String arg2 = 'hello', bool arg3 = false]) { } main() { some(); // 오류 some(10); // 성공 some(10, arg2:'world', arg3: true); // 오류 some(10, 'world', true); // 성공 some(10, 'world'); // 성공 }
4) 함수 타입 인수
- 함수타입(function type) : 함수를 대입할 수 있는 객체
void some() { } Function data = some;
int plus(int no) => no + 10; int multiply(int no) => no * 10; Function testFun(Function argFun) { print('argFun : ${argFun(20)}'); return multiply; } main(List<String> args) { var result = testFun(plus); // argFun : 30 print('result : ${result(20)}'); // result : 200 }
some(int fun(int a)) {print(fun(30));} main() { some((int a) {return a + 20;}); // 50 }
(1) 익명 함수
- 정의 : 이름이 생략된 함수
- 익명 함수(annonymous functions) == 람다 함수(lambda function)
Function fun = (arg) {return 10;} //함수 이름이 없다(fun이라는 객체에 담은 것)
var list = ['apples', 'bananas', 'oranges']; main() { list.forEach((item) { print('${list.indexOf(item)}: $item'); // 0: apples,... }); }
5) 게터와 세터 함수
- 게터(getter) : get예약어(데이터 가져오기)
- 세터(setter) : set예약어(데이터 변경하기)
String _name = 'Hello'; String get name { return _name.toUpperCase(); } // 데이터 가져오기 set name(value) { _name = value; } // 데이터 변경하기(매개변수 필요) main() { name = "World"; // get, set이 선언되어있기에 가능 print('name : $name'); }
6) 기타 연산자 알아보기
(1) 나누기 연산자 - /, ~/
main() {
int a = 8;
print('a / 5 = ${a / 5}'); // 1.6
print('a ~/ 5 = ${a ~/ 5}'); // 1
}
(2) 타입 확인과 변환 - is, as
class User {
void some() {
print("Hello");
}
}
main() {
Object obj = User();
// obj.some(); // 오류
if (obj is User) { // 타입 확인, 자동 형 변환
obj.some()
}
Object obj1 = User();
(obj1 as User).some(); // 명시적 형 변환
}
- 다트의 최상위 클래스 : Object(>User)
- 객체를 변수 대입 시, 하위→상위(형 변환)은 자동
- 상위→하위(형 변환)은 is(자동 형 변환), as(명시적 형 변환)을 실시해야함
(3) 반복해서 접근하기 - .. / ?..
class User{
String? name;
int? age;
some() {
print('name: $name, age: $age');
}
}
- 객체 생성과 멤버 접근
var user = User(); user.name = 'kkang'; user.age = 10; user.some(); // name: kkang, age: 10
- 캐스케이드 연산자 사용 예(마지막에만 ;)
- 클래스가 일반 객체일 시 .. / Nullable객체일 시 ?..
User() ..name = 'kkang' ..age = 30 ..some();
- 클래스가 일반 객체일 시 .. / Nullable객체일 시 ?..
7) 실행 흐름 제어하기
(1) for 반복문에서 in 연산자
- 작성법 : 초기화; 조건; 증감
main() { var list = [10, 20, 30]; for (var i = 0; i < list.length; i++) { print(list[i]); // 10 20 30 } }
- in 연산자로 간소화
main() { var list = [10, 20, 30]; for (var x in list) { print(x); } }
(2) switch~case 선택문
- 실행 흐름을 분기할 때 사용 / 그다음 실행흐름 결정
- break : switch문 나가기
- continue : 특정 위치(라벨)로 이동하기
- return : swith문 종료하기(반환하기)
- throw : switch문 종료하기(던지기)
some(arg) { swith(arg) { case 'A': print('A'); // 오류[break, continue, return, throw을 지정X] case 'B': print('B'); } }
(3) 예외 던지기와 예외 처리
- ✨예외 처리문 : 런 타임 오류를 제어
- 예외를 던지는 throw 문
- 예외 던지기
some() { throw Exception('my exception'); }
- 문자열 던지기
some() { throw 'my exception'; }
- 사용자 정의 객체 던지기
some() { throw User(); }
- 예외 던지기
- try~on~finally 예외 처리
some() { throw FormatException('my exception'); } main(List<String> args) { try { print('step1....'); // step1.... some(); print('step2....'); } on FormatException { print('step3....'); // step3.... } on Exception { print('step4....'); } finally { print('step5....'); // step5.... } print('step6....'); // step6.... }
- 예외 객체 가져오기
some() { throw FormatException('my exception'); } main(List<String> args) { try { print('step1....'); // step1.... some(); print('step2....'); } on FormatException catch(e) { print('step3....$e'); // step3....FormatException: my exception } on Exception catch(e) { print('step4....$e'); } finally { print('step5....'); // step5.... } print('step6....'); // step6.... }
- try~catch 예외 처리(예외 종류 미구분)
try { some(); } catch(e) { print('catch....$e') }
- 예외 객체 가져오기
2. 클래스(객체, 생성자, 상속, 추상)
1) 클래스와 객체
(1) 클래스 선언과 생성
- 클래스 선언
class User { String name = 'kkang'; int age = 10; void sayHello() { print('Hello $name, age : $age'); } }
- 객체 생성
User user1 = new User(); // new는 생략가능
(2) 객체 맴버와 클래스 멤버
- 멤버 : 클래스 내부에서 선언된 변수나 함수
- 객체 멤버 : 단순하게 선언한 멤버(객체 생성 후 접근 가능)
- 클래스 멤버 : static예약어로 선언한 멤버(클래스 명칭으로 접근 가능)
class MyClass { String data1 = 'hello'; // 객체 멤버 static String data2 = 'hello'; // 클래스 멤버 myFun1() { print('myFun1 call....'); } static myFun2() { print('myFun2 call....'); } }
- 객체 멤버 이용
MyClass.data1 = 'world'; // 오류 MyClass obj = MyClass(); obj.data1 = 'world'; // 성공
- 클래스 멤버 이용
MyClass.data2 = 'world'; // 성공 MyClass obj = MyClass(); obj.data2 = 'world'; // 오류
2) 생성자와 멤버 초기화
(1) 생성자 선언
- 생성자(constructor) : 객체를 생성할 때 호출되는 함수
- 모든 클래스가 소유(만들지 않더라도 자동으로 같은 이름의 기본 생성자를 만듬)
class User { User() { } }
- 모든 클래스가 소유(만들지 않더라도 자동으로 같은 이름의 기본 생성자를 만듬)
(2) 멤버 초기화하기
- 멤버 초기화 생성자
class User { late String name; late int age; User(String name, int age) { this.name = name; this.age = age; } }
- 멤버 초기화 생성자 단순화
class User { late String name; late int age; User(this.name, this.age); sayHello() { print('name : $name, age : $age'); } }
(3) 초기화 목록
- 생성자 선언 시 초기화 목록(initializer list)을 사용 가능
- 리스트의 데이터로 초기화
class User { late int data1; late int data2; MyClass(List<int> args) : this.data1 = args[0], this.data2 = args[1] { } }
- 클래스 멤버 함수의 반환 값으로 초기화
class MyClass { late int data1; late int data2; MyClass(int arg1, int arg2) : this.data1 = calFun(arg1), this.data2 = calFun(arg2) { } static int calFun(int arg) { return arg * 10; } printData() { print('$data1, $data2'); } }
3) 명명된 생성자
- 정의 : 다른 이름으로 여러 생성자를 정의하는 기법(자바의 오버로딩)
- 명명된 생성자 선언
class MyClass { MyClass() { } MyClass.first() { } MyClass.second() { } }
- 명명된 생성자로 객체 생성
var obj1 = MyClass(); var obj2 = MyClass.first(); var obj3 = MyClass.second();
(1) this로 다른 생성자 호출하기
- 예시 : MyClass.first호출 시, MyClass도 호출하고 싶을 때 사용
- this() 잘못된 호출 예
class MyClass { MyClass(int data1, int data2) { print('MyClass() call....'); } // 1. this()는 {} 안쪽에 쓸 수 없다 MyClass.first(int arg) { this(arg, 0); // 오류 } // 2. this() 사용 시 {}을 작성할 수 없다. MyClass.first(int arg) : this(arg, 0) { } // 오류 // 3. this() 사용 시 해당 호출문 외에 다른 구문 사용할 수 없다. MyClass.first(int arg) : this(arg, 0), this.data1=arg1; // 오류 }
- this() 올바른 호출 예
class MyClass { MyClass(int data1, data2) { print('MyClass() call....'); } MyClass.first(int arg) : this(arg, 0); // 성공 }
- ✨ 명명된 생성자 중첩 호출
class MyClass { late int data1; late int data2; MyClass(this.data1, this.data2); MyClass.first(int arg) : this(arg, 0); // 기본 생성자(MyClass) 호출 MyClass.second() : this.first(0); // 명명된 생성자(MyClass.first) 호출 }
4) 팩토리 생성자
- 용도 : 상황에 맞는 객체를 준비해 반환할 때(캐시 알고리즘이나 상속 관계에 따른 다형성을 구현할 때 유용)
- 팩토리 생성자(factory constructor)는 factory 예약어로 선언
- 팩토리 생성자는 반드시 객체를 반환해주어야 함.✨
- 구현 예시
class MyClass { MyClass._instance(); factory MyClass() { return MyClass._instance(); } } main() { var obj = MyClass(); }
- 캐시(cache) 알고리즘 구현 예시
class Image { late String url; static Map<String, Image> _cache = <String, Image>{}; Image._instance(this.url); factory Image(String url) { if (_cache[url] == null) { var obj = Image._instance(url); _cache[url] = obj; } return _cache[url]!; } } main() { var image1 = Image('a.jpg'); var image2 = Image('a.jpg'); print('image1 == image2 : ${image1 == image2}'); }
5) 상수 생성자
(1) const로 생성자 선언
- 상수 생성자 선언
class MyClass { const MyClass(); }
- 상수 생성자 잘못 선언한 예
class MyClass { int data1; const MyClass(); // 오류 }
- 상수 생성자의 객체 생성
class MyClass { final int data1; const MyClass(this.data1); } main() { var obj1 = MyClass(10); var obj2 = MyClass(20); print('obj1.data : ${obj1.data1}, obj2.data : ${obj2.data1}'); }
(2) const로 객체 생성
- 상수 객체 생성 오류
class MyClass { } main() { var obj1 = const MyClass(); // 오류 }
- 상수 객체 생성
class MyClass { final int dat1; const MyClass(this.data1); } main () { var obj1 = const MyClass(10); }
- 일반 객체 선언(같은 값으로 초기화했지만 서로 다른 객체이므로)
var obj1 = MyClass(10); var obj2 = MyClass(10); print('obj1 == obj2 : ${obj1 == obj2}'); // false
- 같은 값으로 상수 객체 선언(obj1 객체가 있으므로 obj2에 대입함)
var obj1 = const MyClass(10); var obj2 = const MyClass(10); print('obj1 == obj2 : ${obj1 == obj2}'); // true
- 다른 값으로 상수 객체 선언
var obj1 = const MyClass(10); var obj2 = const MyClass(20); print('obj1 == obj2 : ${obj1 == obj2}'); // false
- 같은 값으로 상수 객체와 일반 객체 선언
var obj1 = const MyClass(10); var obj2 = MyClass(10); print('obj1 == obj2 : ${obj1 == obj2}'); // false
6) 상속(inheritance)
(1) 상속과 오버라이딩
- 상속(inheritance) : 클래스를 재활용하는 객체지향 프로그래밍의 핵심
- 기존 클래스 : 부모 클래스
- 상속받은 새 클래스 : 자식 클래스
- 예약어 : extends
- 오바리이딩(overriding) : 상속 받은 멤버 재정의
- 함수에서 널 불허 지역 변수 초기화
class SuperClass { int myData = 10; void myFun() { print('Super..myFun()...'); } } class SubClass extends SuperClass { } main(List<String> args) { var obj = SubClass(); obj.myFun(); // 'Super..myFun()...' print('obj.data : ${obj.myData}'); // obj.data : 10 }
- 오버라이딩
class SuperClass { int myData = 10; void myFun() { print('Super..myFun()...'); } } class SubClass extends SuperClass { int myData = 20; void myFun() { print('Sub... myFun()...'); } } main(List<String> args) { var obj = SubClass(); obj.myFun(); // 'Sub..myFun()...' print('obj.data : ${obj.myData}'); // obj.data : 20 }
- 부모 클래스의 멤버에 접근하기
class SuperClass { int myData = 10; void myFun() { print('Super..myFun()...'); } } class SubClass extends SuperClass { int myData = 20; void myFun() { super.myFun(); // Super..myFun()... print('Sub... myFun()..myData : $myData, super.myData : ${super.myData}'); // Sub... myFun()..myData : 20, super.myData : 10 } } main(List<String> args) { var obj = SubClass(); obj.myFun(); }
(2) 부모 생성자 호출하기
- 자식 클래스의 생성자 호출(부모 생성자는 자동 호출됨)
class SuperClass { SuperClass() {} } class SubClass extends SuperClass{ SubClass() {} } main() { var obj = SubClass(); }
- 부모 생성자 호출
class SuperClass { SuperClass() {} } class SubClass extends SuperClass { SubClass() : super() {} }
- 부모 생성자가 매개변수나 명명된 생성자를 가진다면 생략하면 안됨
class SuperClass { SuperClass(int arg) {} SuperClass.first() {} } class SubClass extends SuperClass { SubClass() : super(10) {} // 성공(생략가능) SubClass.name() : super.first() {} // 성공(생략불가) }
(3) 부모 클래스 초기화
- 부모 클래스의 멤버 변수 초기화1
class SuperClass { String name; int age; SuperClass(this.name, this.age) {} } class SubClass extends SuperClass { SubClass(String name, int age) : super(name, age) {} // 부모 클래스 멤버 초기화 } main() { var obj = SubClass('kkang', 10); print('${obj.name}, ${obj.age}'); // kkang, 10 }
- 부모 클래스의 멤버 변수 초기화2
class SuperClass { String name; int age; SuperClass(this.name, this.age) {} } class SubClass extends SuperClass { SubClass(super.name, super.age) {} // 부모 클래스 멤버 초기화 } main() { var obj = SubClass('kkang', 10); print('${obj.name}, ${obj.age}'); // kkang, 10 }
7) 추상 클래스와 인터페이스
(1) 추상 클래스
- 추상 클래스 : 추상 함수만 제공하여 상속받는 클래스에서 반드시 재정의해서 사용하도록 강제하는 방법(abstract를 통해 선언)
abstract class User{ void some(); // 성공 } class Customer extends User { @override void some() {} // 오버라이드 하는 곳 }
(2) 인터페이스 알아보기
- 인터페이스(interface) : 부모의 특징을 도구로 사용해 새로운 특징을 만들어 사용하는 방법
- 인터페이스는 모든 멤버 재정의 해야 함!
class User { int no; String name; User(this.no, this.name); String greet(String who) => 'Hello, $who. I am $name. no is $no'; } class MyClass implements User { int no = 10; String name = 'kim'; @override String greet(String who) { return 'hello $who'; } }
- 인터페이스 타입 객체 선언
main(){ User user = MyClass(); print('${user.greet('lee')}'); }
- 한 클래스에 여러 인터페이스 지정
class MyClass implements User, MyInterface {}
8) 멤버를 공유하는 믹스인
(1) 믹스인 선언
- mixin라는 예약어로 선언
- 클래스가 아니므로
생성자
를 선언할 수 없다. 그러므로객체
를 생성할 수 없다. - 선언 예시
mixin MyMixin{ int mixinData = 10; void mixInFun() { print('MyMixin... myFun()...'); } }
(2) 믹스인 사용하기
- 클래스는 다중 상속 불가 → 믹스인 멤버 이용
class MySuper { int superData = 20; void superFun() { print('MySuper... superFun()'); } } class MyClass extends MySuper with MyMixin { void sayHello() { print('class data : $superData, mixin data : $mixinData'); mixInFun(); superFun(); } } main() { var obj = MyClass(); obj.sayHello(); }
- 믹스인 타입 객체 선언(MyMixin)
main() { var obj = MyClass(); // MyMixin obj2 = MyClass(); if (obj is MyMixin) { print('obj is MyMixin'); // obj is MyMixin } else { print('obj is not MyMixin'); } }
(3) 믹스인 사용 제약
- 믹스인 타입 제한 : on 예약어로 해당 클래스 지정(상속받는 자식 클래스도 가능)
mixin MyMixin on MySuper {} class MySuper {} class MyClass extends MySuper with MyMixin {} // 성공 class MySomeClass with MyMixin {} // 오류
댓글남기기