지금까지 다트를 하면서 아래와 같이 main
이라는 블록에 각종 코드를 작성하였다.
void main() {
print('Hello World!');
var limit = 10;
for (var i = 0; i < limit; i++) {
print('i: ${i}');
}
}
여기서 main
은 다트를 시작하는 진입점이며, 함수이다. 그렇다면 함수란 무엇일까?
수학에서 함수(函數, 영어: function) 또는 사상(寫像, 영어: map, mapping)은 어떤 집합의 각 원소를 다른 집합의 유일한 원소에 대응시키는 이항 관계
다. 즉, 한 변수의 값에 따라 정해지는 다른 변수의 값을 먼저 주어지는 값에 상대하여 일컫는 말
이다.
그만 알아보자...
쉽게 설명하기 위해 아래 그림을 보자. 그림에서 라고 하는 함수는 를 입력으로 받아서 그 값을 제곱()한 값을 반환한다. 그렇다. 함수란 특정 입력을 받아서 함수내에서 어떠한 연산을 진행하고 결과를 반환해 주는 것이다.
우리가 지금까지 써온 main()
은 물론, print()
역시 어떤 문자열을 입력으로 받아서 표준출력(모니터)에 출력하는 함수이다. 함수에서 크게 알아야 하는 것은 함수를 구성하는 입력값
, 함수내용
, 출력값
이렇게 3가지라고 할 수 있다.
다트의 함수는 아래과 같이 사용하며, 각각을 정리하면 다음과 같다.
functionName
: 함수의 이름을 나타내며, 외부에서 해당 이름으로 함수에 접근할 수 있다. 수학에서 에서 에 해당한다.
paramType param
: 해당 함수에서 사용하기 위해 입력으로 받는 값을 의미하며, 기존에 변수를 선언하듯 변수의 타입고 변수명을 지정하면 된다. 여기서 선언된 변수는 일반적으로 함수 내에서만 접근이 가능하며, 함수가 종료될 때 함수 내에 있는 변수의 생명주기가 종료된다.
returnType
: 해당 함수가 종료될 때 함수를 호출한 쪽에게 어떤 타입의 데이터가 반환되는지 알려주는 용도이며, 실제로 해당 타입으로 반환을 해야 한다.
returnType functionName(param1Type param1, [param2Type param2, ...]) {
function contents;
return someData;
}
예시
// int형 파라미터 a, b를 입력으로 받고 int형 값을 반환하는 addFunc라는 함수
int addFunc(int a, int b) {
var result = a + b;
return result;
}
void main() {
int num1 = 10;
int num2 = 20;
int result = addFunc(num1, num2); // 함수를 호출하는 쪽에서는 함수명과 파라미터에 들어갈 값을 넣어주고, int형을 반환하는 함수이기 때문에 함수의 결과를 result라는 int형 변수에 저장한다.
print('result: ${result}');
}
결과
result: 30
위 예시를 보면 addFunc
함수는 int
형을 반환한다는 것은 알겠다. 그런데 main
함수 앞에 있는 void
는 무엇일까?
예상했겠지만 함수의 결과값이 없다는 의미로 void
를 사용한 것이다.
다트에서 함수의 결과, 즉 함수가 return
하는 값이 존재하면 해당 값의 타입을 작성하고 return할 값이 없으면 void를 작성하여 명시적으로 해당 함수의 결과가 없음을 알려준다. 따라서 main 함수는 별도로 반환하는 값이 없다는 것을 알 수 있다. main에서 리턴이 존재하는 언어도 있으니 주의하도록 하자.
예시
// int형 파라미터 a, b를 입력으로 받고 int형 값을 반환하는 addFunc라는 함수
int addFunc(int a, int b) {
var result = a + b;
return result;
}
// String형 파라미터 name를 입력으로 받고 String형 값을 반환하는 makeNewName 함수
String makeNewName(String name) {
var result = name + ' 최고';
return result;
}
// String형 파라미터 name를 입력으로 받고 값을 반환하지 않는 printName 함수
void printName(String name) {
print(name);
}
함수를 정의할 때 항상 모든 입력값이 필요한건 아니다. 경우에 따라 필요하지 않은 값을 처리하기 위해 다트는 Optional Parameters
기능을 지원한다.
[]
를 사용하여 파라미터를 감싸면 해당 파라미터는 필수가 아닌 선택형 파라미터로 지정되며, 이를 Optional Positional Parameter
라고 한다. 또한 선택형 파라미터를 전달하지 않으면 해당 변수는 null
값을 가지게 된다.{}
를 사용하여 파라미터를 감싸면 해당 파라미터는 필수가 아닌 선택형 파라미터로 지정되며, 이를 Optional Named Parameter
라고 한다. {}
내에 위치한 파라미터는 {pram1: defaultValue}
형식으로 기본값을 지정할 수도 있다. 또한 {}
로 감싸진 파라미터는 함수를 호출할 때 반드시 해당 파라미터명을 지정하여 호출해야 한다.// b 변수를 int?형으로 지정한 이유는 b의 값이 없으면 null이 될 수도 있기 때문임. 다트 최신 버전은 null check를 잘 해야 함
void optionalPositional(int a, [int? b]) {
print('a: ${a}');
print('b: ${b}');
}
void optionalNamed(int a, {int? b}) {
print('a: ${a}');
print('b: ${b}');
}
// int?형으로 지정하지 않아도 에러가 나지 않는 이유는 b의 값이 없으면 자동으로 10이 되기 때문임.
void optionalNamedWithDefaultValue(int a, {int b: 10}) {
print('a: ${a}');
print('b: ${b}');
}
void main() {
optionalPositional(10);
print('-'*20);
optionalNamed(10, b:20);
print('-'*20);
optionalNamedWithDefaultValue(10);
}
결과
a: 10
b: null
--------------------
a: 10
b: 20
--------------------
a: 10
b: 10
다트는 함수의 재귀, 즉 자기 자신을 호출하는 것을 허용한다. 재귀함수는 함수가 끝나지 않았을 때 자기 자신을 자기 호출할 수 있는데, 피보나치 수열이나 팩토리얼을 구하는 등의 경우에 사용하면 유용하게 사용할 수 있다.
일반적으로 함수 내에 함수를 빠져나갈 수 있는 분기를 두고 가장 깊은 곳까지 들어갔다가 순차적으로 값을 반환하게 된다.
int factorial(int num) {
// 재귀함수 종료 조건으로, num이 1이하일 때만 1을 반환한다.
if (num <= 1) {
return 1;
} else {
return num * factorial(num - 1); // 재귀함수를 호출한다.
}
}
void main() {
const num = 5;
final fact = factorial(num);
print('Factorial of 5 is: ${fact}');
}
다트는 람다함수도 지원하며 아래와 같다. 일반 함수와 다른 점은 expression
부분에 표현식 즉, (반환타입에 맞는)특정 값을 반환하는 식이 들어가야 하며 긴 구문을 사용할 수 없다. 람다함수는 =>
를 사용하여 화살표 함수라고도 불린다. 람다 함수를 1회성으로 사용하는 경우, 함수의 이름을 생략한 익명함수를 사용할 수도 있다.(일반 함수도 함수명을 생략하는 익명함수를 만들 수 있다.)
[return_type]functionName(parameters)=>expression;
예시
String myFunc(String name) => '안녕하세요. ${name}입니다.';
void main() {
String name = '남슈크림빵';
print(myFunc(name));
((name) => print('안녕히가세요. ${name}입니다.'))(name);
}
결과
안녕하세요. 남슈크림빵입니다.
안녕히가세요. 남슈크림빵입니다.