해당 포스팅은 다양한 자료를 통해 학습한 내용을 바탕으로 정리한 글입니다
함수
void main () { }
일반적으로 Dart에서 함수는 위와 같은 형태를 가지고 있다.
javascript에서 function main () { } 과 비슷한 형태 구조를 가지고 있는데
함수 앞에 있는 void는 해당 함수가 아무것도 반환하지 않는다는 것을 의미한다.
만약 해당 함수가 어떤 반환값을 가지고 있다면 해당 데이터의 타입에 맞게 작성해주어야 한다.
int num (int a, int b) {
return a+b;
}
함수의 파라미터 자리에는 위와 같은 방식으로 입력할 수 있는데,
이 때도 각 파라미터에는 해당 파라미터의 타입을 입력해주어야 한다.
positional parameter
String introduce (String name, int age, String country) {
return "안녕하세요. 저는 $name입니다. $age세 이고, $country에 거주합니다.";
}
void main () {
print(introduce("우믑", 20, "한국")); // 안녕하세요. 저는 우믑입니다. 20세 이고, 한국에 거주합니다.
print(introduce("한국", 20, "우믑")); // 안녕하세요. 저는 한국입니다. 20세 이고, 우믑에 거주합니다.
print(introduce(20, "우믑", "한국")); // error
}
위의 introduce 함수는 3개의 데이터가 필요한 함수다.
해당 형식의 파라미터는 각 파라미터 자리에 일치하는 타입의 데이터가 들어와야 한다.
name에는 String 타입의 데이터가, age에는 int 타입에 데이터가 필요한데,
각각의 자리에 타입이 일치하지 않는 데이터가 들어오게 되면 에러를 반환하게 된다.
각 파라미터의 타입에 맞게 데이터를 입력해줘야 하는 부분도 주의해야 하지만,
데이터 입력 순서도 정확하게 일치를 해야 내가 원하는 결과값을 얻을 수 있다.
이러한 파라미터를 positional parameter라고 한다.
String introduce (String name, [int age = 30, String country = "일본"]) {
return "안녕하세요. 저는 $name입니다. $age세 이고, $country에 거주합니다.";
}
void main () {
print(introduce("우믑")); // 안녕하세요. 저는 우믑입니다. 30세 이고, 일본에 거주합니다.
}
positional parameter에서 만약 특정 파라미터의 값을 부여할 수도 있고 아닐수도 있다면
위의 예시처럼 파라미터에 대괄호 ([])를 씌워서 해당 파라미터는 옵셔널하다고 표시할 수 있다.
이렇게 되면 함수 호출시에 아규먼트를 넣지 않더라도 에러 없이 값이 나오게 된다.
단, 대괄호로 감싼 파라미터에는 기본값을 표기해주어야 한다.
Named parameter
String introduce ({
String name = "우믑",
int age = 20,
String country = "한국",
}) {
return "안녕하세요. 저는 $name입니다. $age세 이고, $country에 거주합니다.";
}
void main () {
print(introduce(name: "믑우", age: 30, country: "일본")); // 안녕하세요. 저는 믑우입니다. 30세 이고, 일본에 거주합니다.
print(introduce(country: "미국", age: 25, name: "우믑")); // 안녕하세요. 저는 우믑입니다. 25세 이고, 미국에 거주합니다.
}
앞서 확인한 positional parameter에서는 파라미터의 위치를 정확하게 일치해야 원하는 결과값을 얻을 수 있었는데,
이렇게 되면 함수 호출시에 아규먼트의 순서를 확인해야 한다는 불편함이 있고,
자칫 다른 값을 넣게 되면 에러나 원하지 않는 결과값을 얻게 된다는 문제점이 있다.
그러한 부분들은 named parameter를 통해 해결할 수 있다.
위의 예시처럼 파라미터에 중괄호 ({})로 감싸주고 기본값을 지정하는 것이 named parameter의 사용 방식인데
이렇게 처리하게 되면 함수 호출시 아규먼트 자리에 이름과 데이터를 넣는 방식으로 지정할 수 있다
이렇게 사용하면 아규먼트의 순서는 상관이 없고, 아규먼트의 이름과 데이터만 잘 매칭해주면
positional parameter의 문제를 해결할 수 있다.
String introduce ({
required String name,
int age = 20,
String country = "한국",
}) {
return "안녕하세요. 저는 $name입니다. $age세 이고, $country에 거주합니다.";
}
void main () {
print(introduce(name: "믑우")); // 안녕하세요. 저는 믑우입니다. 20세 이고, 한국에 거주합니다.
print(introduce(name: "우믑")); // 안녕하세요. 저는 우믑입니다. 20세 이고, 한국에 거주합니다.
}
named parameter 역시 옵셔널하게 파라미터를 지정할 수 있는데,
기본값이 있는 경우에는 함수 호출시 아규먼트를 전달하지 않아도 되는데,
함수 호출시 반드시 필요한 아규먼트가 있을 경우에는 해당 파라미터 앞에 required를 붙여줘야 한다.
Class
class User {
String name = "우믑";
int age = 20;
void introduce () {
print("안녕! 난 $name이야. 나이는 $age살이야");
}
}
void main () {
var user = User();
user.introduce(); // 안녕! 난 우믑이야. 나이는 20살이야
}
Dart에서 기본적인 Class는 위와 같은 형태를 가지고 있다.
Class의 프로퍼티는 각각 타입이 지정된 형태로 있어야 하고, 함수 형태도 존재할 수 있다.
구현한 Class는 위의 예시처럼 특정한 변수에 인스턴스를 생성해 할당할 수 있고,
생성한 인스턴스에서 프로퍼티를 호출해 사용할 수 있다.
사실 Class를 만들어 두는 것은 구현한 Class의 구조를 이용해서
다양한 인스턴스를 만들어 재사용하는 것이 목적인데, 현재 예시로 든 Class는 재사용이 어렵게 되어있다.
Constructors
class User {
User(this.name, this.age);
String name;
int age;
void introduce () {
print("안녕! 난 $name이야. 나이는 $age살이야");
}
}
void main () {
var user1 = User("우믑",20);
var user2 = User("믑우",30);
user1.introduce(); // 안녕! 난 우믑이야. 나이는 20살이야
user2.introduce(); // 안녕! 난 믑우이야. 나이는 30살이야
}
Class를 재사용하기 위해서는 Constructor가 필요하다
Constructor는 마치 함수를 호출하는 것 처럼 사용한다.
위의 예시에서 보이는 this는 해당 Constructor가 속한 Class를 가리킨다.
위와 같이 설정해주면 같은 구조지만 다른 데이터를 입력해주면서 다른 결과를 가져올 수 있게 된다.
class User {
String name,gender;
int age;
User({
required this.name,
required this.age,
required this.gender,
});
void introduce() {
print("안녕! 난 $name이야. 나이는 $age살이야, 그리고 난 $gender야");
}
}
void main() {
var user1 = User(name: "우믑", age: 20, gender: "남자");
user1.introduce(); // 안녕! 난 우믑이야. 나이는 20살이야, 그리고 난 남자야
}
Class의 데이터가 많아지면 인스턴스 생성에도 어려움이 있기에
Class 역시 named parameter를 사용할 수 있다.
위의 예시를 보면 gender라는 프로퍼티가 추가 되어있는데,
이렇게 여러개의 프로퍼티가 있는 경우 함수에서 적용했던 것과 마찬가지로 named parameter로 관리할 수 있다.
Named Constructors
위에서 살펴본 기본적인 Constructor는 인스턴스를 생성할 때, Class에 있는 프로퍼티에 데이터를 넣는 방식으로 생성했다.
만약 특정 프로퍼티를 초기화 시킨 상태의 인스턴스를 생성하려고 한다면 Named Constructor를 활용하면 된다.
class User {
String name, gender, country;
int age;
User({
required this.name,
required this.age,
required this.gender,
required this.country,
});
User.setKoreaMan({required String name, required int age})
: this.name = name,
this.age = age,
this.gender = "남자",
this.country = "한국";
}
void main() {
var user1 = User.setKoreaMan(name: "우믑", age: 20);
}
위의 예시를 살펴보면 한국 남자를 기본으로 하는 named constructor를 구현했다.
Class에 내가 만들고자 하는 이름 (ex. setKoreaMan)을 설정하고 입력받을 데이터 형태를 구현해 놓는다.
여기서 일반적인 constructor와 데이터를 받는 형태가 다르니 주의해서 사용해야 한다.
그 후 콜론 ( : )을 사용해 입력받은 값을 프로퍼티에 할당한다.
위와 같은 방식으로 Named constructor를 사용할 수 있다.
상속
이번에는 사람이라는 공통 클래스를 가지고 다양한 클래스를 만들때 유용한 개념을 알아보자.
class Human {
String name, gender;
int age;
Player(this.name, this.age, this.gender);
}
여기 Human이라는 클래스에는 이름, 성별, 나이 프로퍼티가 존재한다.
이 클래스를 기본으로 해 다양한 클래스를 만들 때 상속(inheritance)라는 개념이 적용된다.
class SportMan extends Human {
String type;
SportMan(this.type, String name, int age) : super(name, age, "남자");
}
남자 운동 선수 클래스를 구현하는데 이름, 나이, 성별, 종목의 프로퍼티가 필요하다고 가정했을 때
일일히 다 선언하는 것이 아닌 Human의 프로퍼티를 상속받아 구현할 수 있다.
클래스 이름 뒤에 extends 라는 명령어를 작성하고 그 뒤에 다른 클래스를 불러오면
뒤에 있는 클래스의 프로퍼티를 모두 상속 받을 수 있다.
현재 SportMan의 클래스는 Human 클래스의 프로퍼티 (이름, 나이, 성별)를 상속 받았지만
실제 클래스 안에 존재하는 것이 아니기 때문에 constructor에 this로 데이터를 변경할 수 없다.
실제로는 부모 클래스 (Human)에 있는 프로퍼티기 때문에
이를 변경해서 사용하기 위해서는 super 라는 개념이 필요하다.
위의 예시에서는 SportMan이라는 클래스에 gender가 남자로 고정되어야 하기에 위와 같이
constructor를 설정했다.
class Player {
String name, gender;
int age;
Player({required this.name, required this.age, required this.gender});
}
class SportMan extends Player {
String type;
SportMan({required this.type, required String name, required int age})
: super(name: name, age: age, gender: "남자");
}
물론 이 부분도 named parameter 방식으로 데이터를 전달하고 받을 수 있다.
@override
class Human {
String name, gender;
int age;
Player({required this.name, required this.age, required this.gender});
int remainRetireAge() {
return 60 - this.age;
}
}
이번에는 부모 클래스에서 생성된 메서드를 상속 받은 클래스에서 사용하는 방법을 알아보자.
현재 Human 클래스에는 은퇴 나이까지 얼마나 남았는지 반환해주는 메서드가 있다.
class SportMan extends Human {
String type;
SportMan({required this.type, required String name, required int age})
: super(name: name, age: age, gender: "남자");
@override
int remainRetireAge() {
return 40 - this.age;
}
}
스포츠계에 있는 사람들은 일반 사람들보다 은퇴가 빠르기 때문에
Human에서 상속 받은 메서드를 그대로 사용할 수 없는데,
@override라는 개념을 사용하면 상속 받은 메서드를 덮어쓰는 방식으로 사용할 수 있게 된다.
void main() {
var user1 = Human(name: "우믑", age:20, gender:"남자");
var user2 = SportMan(type: "축구", name: "손흥민", age: 33);
print(user1.remainRetireAge()); // 40
print(user2.remainRetireAge()); // 7
}
해당 메서드를 각각 호출해보니 결과값이 다르게 나오는 것을 확인할 수 있었다.
포스팅 작성에 도움이 된 글
- 노마드코더 : Dart 시작하기 강의 [class]
- 인프런 : 코드팩토리 입문 Dart 언어 - Dart #2
'Stack > Dart & Flutter' 카테고리의 다른 글
[TIL] Dart 기본 문법 _ 변수, 타입, 연산자 (0) | 2025.05.09 |
---|