본문 바로가기

프로그래밍 언어 기초/Dart

[Dart] Dart 기본 문법2 [제어문, 반복문, 함수, 익명 함수, 람다 함수, 예외 처리(try-catch)]

개요

오늘도 이어서 Flutter의 프로그래밍 언어인 Dart의 문법에 대해서 알아보겠습니다.

오늘의 포스팅에서 소개할 문법은 아래와 같습니다.

 

  • 연산자
  • 제어문과 반복문
  • 함수
  • 익명 함수와 람다 함수
  • 예외 처리(try-catch)

1. 연산자(Operator)

1.1 기본 연산자

dart에서 기본적인 사칙연산 및 나머지 연산은 아래와 같이 사용할 수 있습니다.

void main(){
    double number = 16;

    // dart의 사칙연산 + 모듈러 연산
    print(number+2); // 18.0
    print(number-5); // 11.0
    print(number*1); // 16.0
    print(number/4); // 4.0
    print(number%3); // 1.0

    // 단항 연산
    number++;
    print(number); // 17. 0
    number--;
    print(number); // 16.0
    ++number;
    print(number); // 17.0
    --number;
    print(number); // 16.0

    number += 2; // 18.0
    number -= 3; // 15.0
    number *= 4; // 60.0
    number /= 5; // 12.0
    number %= 2; // 0.0
    
}

 

 

1.2. null 연산자

dart에서는 null-safety를 위해 아래와 같은 연산자를 사용할 수 있습니다.

void main(){
    // null 연산자
    // dart의 기본 자료형에는 ? 를 붙여 null 값을 허용할 수 있습니다.
    // var? num1 = 1; // Error: Expected an identifier, but got '?'.
    int? num1 = 1;
    print(num1); // 1
    num1 = null;
    print(num1); // null

    dynamic? hello; // dynamic도 null 가능!, 값을 초기화하지 않았기에 null이 입력 됨
    print(hello); // null

    hello ??= 3; // null 일 경우 값을 할당하는 연산자
    print(hello); // 3
    hello ??= 4; // null이 아닌 값이 할당되었으므로 반영되지 않음
    print(hello); // 3

    // 비교 연산자
    int myNumber1 = 10;
    int myNumber2 = 20;

    bool result1 = myNumber1 > myNumber2; // false
    bool result2 = myNumber1 < myNumber2; // true
    bool result3 = myNumber1 >= myNumber2; // false
    bool result4 = myNumber1 <= myNumber2; // true
    bool result5 = myNumber1 == myNumber2; // false
    bool result6 = myNumber1 != myNumber2; // true
}

 

 

1.3. 타입 비교 연산자

dart에서는 주어진 데이터의 자료형을 판단하기 위해 아래와 같이 '타입 비교 연산자(is)'를 사용할 수 있습니다.

void main(){
	// 타입 비교 연산자
    var unkownNumber = 1.0;

    bool isInt = unkownNumber is int; // false
    bool isString = unkownNumber is String; // false
    bool isBool = unkownNumber is bool; // false
    bool isDouble = unkownNumber is double; // true
    bool isDyNamic = unkownNumber is dynamic; // true
    // bool isVar = unkownNumber is var; // Error: Expected an identifier, but got ';'. Try inserting an identifier before ';'

    print(isInt);
    print(isString);
    print(isBool);
    print(isDouble);
    print(isDyNamic);

    // 타입 비교 연산에서 dynamic은 모든 자료형에 대해 true를 리턴합니다.
    int intValue = 1;
    String strValue = "hello";
    bool boolValue = false;
    double doubleValue = 1.0;

    bool isDyNamic1 = intValue is dynamic; // true
    bool isDyNamic2 = strValue is dynamic; // true
    bool isDyNamic3 = boolValue is dynamic; // true
    bool isDyNamic4 = doubleValue is dynamic; // true

    // 불리언(bool) 값의 반전
    bool isNotInt = unkownNumber is! int; // true

    // 논리 연산자
    bool logic1 = 12 > 34 && 56 > 78; // false;
    bool logic2 = 12 > 34 && 56 < 78; // false;
    bool logic3 = 12 < 34 && 56 < 78; // true;

    bool logic4 = 12 > 34 || 56 > 78; // false;
    bool logic5 = 12 > 34 || 56 < 78; // true;
    bool logic6 = 12 < 34 || 56 < 78; // true;
}

 


2. 제어문과 반복문

2.1. 제어문(if)

dart에서는 특정한 조건을 기준으로 프로그램의 흐름을 제어하기 위해 아래와 같이 if문을 사용할 수 있습니다.

void main(){

    // if문(if statement)
    int refValue = 123;

    if(refValue == 0){
        print('refValue is 0');
    }else if(refValue >= 100){
        print('refValue is bigger than 100');
    }else {
        print('unKnown value... $refValue');
    }
    // -> refValue is bigger than 100

}

 

2.2. swtich문

dart에서는 if문과 더불어 어떠한 조건에 따라 프로그램의 흐름을 여러 개로 분기할 때, switch 문을 사용할 수 있습니다.

switch문은 if문보다 조건에 대한 설정이 다소 제한적일 수 있지만,

조건식이 명료하다면 if문 보다 상대적으로 직관적으로 코드를 작성할 수 있으며 프로그램의 흐름을 조절할 수 있습니다. 

void main(){
    // switch
    var servedDrink = Drink.Coffee;

    switch(servedDrink){
        case Drink.Coffee:{
            print('served drink is Coffee');
            break;
        }
        case Drink.Juice:{
            print('served drink is Juice');
            break;
        }
        case Drink.Water:{
            print('served drink is Water');
            break;
        }
        case Drink.Beer:{
            print('served drink is Beer');
            break;
        }
        default:{
            print('served drink is unknown... $servedDrink');
            break;
        }
    }
    // served drink is Coffee
}

 

2.3. 반복문 - for

dart에서는 여러 번 같은 동작을 수행하는 작업에 대해 for문을 사용할 수 있습니다.

dart에서도 for문은 선언부, 조건부, 증감식으로 구성되며 조건부에 명시된 조건을 만족할 때 까지 작업을 반복합니다.

void main(){

    // for
    // 선언부, 조건부, 증감식으로 구성!
    for(int i=0; i < 3 ; i++){
        print('current index is $i');
    }

    /**
        current index is 0
        current index is 1
        current index is 2
     */

}

 

또한, dart의 for문은 컬렉션 자료형과 in을 함께 사용하면 컬렉션 변수에 담긴 모든 데이터를 순회할 수 있습니다.

이때 사용할 수 있는 문법이 for-in 이고, 아래와 같이 사용할 수 있습니다.

 

void main(){
	// for - in
    List<int> oddNumbers = [1,3,5,7];
    for(int oddNum in oddNumbers){
        print('odd num is $oddNum');
    }
    /*
        odd num is 1
        odd num is 3
        odd num is 5
        odd num is 7
    */
}

 

 

💡 여기서 잠깐, dart에는 배열(Array)이 없나요?

dart에서는 연속된 값을 저장할 수 있는 자료형은 컬렉션 자료형만이 존재합니다.

기존의 전통적인 프로그래밍 언어에서는 컬렉션으로 분류 되는 자료형 List를 지원하는데요.

이와 별도로 Java와 같은 객체 지향 언어에서는 배열(Array)이라는 자료형도 제공합니다.

하지만, dart에서는 배열(Array)은 없고, 연속된 값을 저장하는 자료형에는 List를 사용합니다.

 

2.4. 반복문 while

dart에서는 for문과 더불어 반복적인 작업을 위해 아래와 같이 while문을 사용할 수 있습니다.

void main() {
    // while
    int start = 0;
    while(start < 10){
        start+=2;
        print(start); // 2 4 6 8 10...
    }

    // while와 reduce의 활용!
    int start2 = 0;
    List<int> numbers = [];
    while(start2 < 10){
        start2 += 2;
        numbers.add(start2);
    }
    int sum = numbers.reduce((v, e)=> v + e);
    print(sum); // 30;
}

 

 

또한, while문의 또 다른 형태인 do-while문도 아래와 같이 사용할 수 있습니다. 

void main(){
    // do while
    int start3 = 0;
    do{
        start3 += 1;
    }while(start3 < 5);
    print(start3); // 5
}

 


 

3. dart의 함수(Function)

dart에서 함수는 리턴타입, 함수명 , 매개변수의 구조로 구성됩니다.

만약 함수에서 리턴하려는 값이 없다면 void 를 사용하면 됩니다.

dart에서는 기본적으로 함수에서 선언한 매개 변수의 순서에 따라 값이 할당 됩니다.

 

예를 들어 함수 f1에 매개변수를 int a, int b라고 선언했다면 a가 b보다 먼저 선언되었으므로,

입력된 순서에 따라 각각 a, b에 할당됩니다.
 

그리고 이렇게 선언한 매개 변수를 고정된 매개변수(positional parameter)라고 부릅니다.

 

반면에, 경우에 따라서는 선언된 순서와 상관 없이 값을 할당해야할 수도 있는데,

이 경우에는 이름이 있는 매개변수 (named parameter)라는 문법을 통해 할당할 수 있습니다.

단, 이름이 있는 매개 변수의 경우에는 기본 값을 정하지 않았을 때에는 반드시 값을 할당해주어야 합니다.

혹은 `required`라는 예약어를 붙여서 함수 실행 시 입력을 강제해야 합니다.

 

 

3.1. 고정된 매개변수(positional parameter) 함수

// 포지셔널 파라미터 함수를 선언하는 법
int addPositionNumber1(var a, var b){
    return a+b;
}

int addPositionNumber2(int a, int b){
    return a+b;
}

// 기본 값이 있는 포지셔널 파라미터
int addPositionNumber3(int a, [int b = 0, int c = 0]){
    return a+b+c;
}


void main(){

    // 포지셔널 파라미터를 선언한 후, 타입을 설정하지 않으면 리턴 타입에 기준한 예외가 발생할 수 있음.
    var calcResult1 = addPositionNumber1(1,2); // 3
    // var calcResult2 = addPositionNumber1(1,"2"); // type 'String' is not a subtype of type 'num' of 'other'
    // var calcResult3 = addPositionNumber1(1, 3.0); // type 'double' is not a subtype of type 'int'
    // var calcResult4 = addNumber(1.0, 3.0); // type 'double' is not a subtype of type 'int'

    // 포시져널 파라미터에 타입을 선언했을 때, 유효한 자료형이 아니라면 예외를 발생
    var calcResult5 = addPositionNumber2(1,2); // 3
    // var calcResult6 = addPositionNumber2(1,1.0); // Error: The argument type 'double' can't be assigned to the parameter type 'int'.

    // 포지셔널 파라미터에서는 변수의 순서대로 입력 값이 할당됨, 단 기본 값이 설정되어 있다면 생략 가능
    var calcResult7 = addPositionNumber3(1); // 1, 기본 값이 설정되어 있으므로 생략 가능
    var calcResult8 = addPositionNumber3(1,2,3); // 6, 다른 값으로 치환해서 사용도 가능
}

 

 

3.2. 이름이 있는 매개변수 (named parameter) 함수

// 네임드 파라미터 함수
int addNamedNumber1({int a = 0, int b = 0}){
    return a+b;
}

int addNamedNumber2({
    required int a,
    required int b
}){
    return a+b;
}

int addNamedNumber3({
    required int a,
    int b = 0
}){
    return a+b;
}

void main(){
    // 네임드 파라미터에는 값을 할당할 때 명시적으로 지칭해주어야 함, 아니면 타입 캐스팅 예외를 발생함.
    // 네임드에서도 기본 값을 설정했다면 생략할 수 있음.
    var calcResult9 = addNamedNumber1(a:1); // 1
    var calcResult10 = addNamedNumber1(a:1, b:2); // 3

    // 네임드 파라미터에서 변수 앞에 `required` 를 붙이면, null을 허용하지 않거나 기본값이 필요함을 의미합니다.
    // 따라서, 이러한 조건이 만족 되지 않으면 예외가 발생함.
    // var calcResult11 = addNamedNumber2(a:1); // Error: Required named parameter 'b' must be provided.
    var calcResult12 = addNamedNumber2(a:1, b:2); // 3


    // 네임드 파라미터에서는 `requried`와 기본 값 파라미터를 함께 사용 가능함.
    var calcResult13 = addNamedNumber3(a:1); // 1
    var calcResult14 = addNamedNumber3(a:1, b:2); // 3
}

 

 

3.3. 고정된 매개변수(positional parameter)와 이름이 있는 매개변수(named parameter)의 혼용

// 혼합된 경우
// 단, 혼합하여 사용할 경우에는 반드시 포지셔닝 파라미터가 앞에 위치해야 한다.
int addMixNumber(int a, {
    required int b,
    int c = 3
}){
    return a + b + c;
}

void main(){
    // 포지셔닝 파라미터와 네임드 파라미터는 혼용이 가능함.
    // 단, 이 경우에는 포지셔닝 파라미터가 항상 앞에 위치해야 함.
    var calcResult15 = addMixNumber(1, b:2); // 6
    var calcResult16 = addMixNumber(1, b:2, c:3); // 6
    print(calcResult15);
    print(calcResult16);
}

 


4. 익명 함수와 람다 함수

dart에서도 익명 함수와 람다 함수를 지원합니다.

익명함수와 람다함수는 프로그래밍 언어에서 구분되는 개념이지만, dart에서는 구분되지 않습니다.
따라서, 개념적으로 중괄호({})로 구분되는 함수의 몸체(바디, body)를 갖는 함수를 익명 함수,

그렇지 않고 하나의 구문(statement)만을 갖는 함수를 람다 함수라고 구분할 수 있습니다.

 

void main(){
    // 익명 함수
    var func = (int a){
        print(a); // 변수 a를 받아 출력한다.
        return a+1;
    };
    func(2); // 2
    var result = func(2);
    print(result); // 3

    // 람다 함수
    var lamb = (int a) => a+1;
    print(lamb(1)); // 2

    var lamb2 = (int a) => print(a); // 무조건 statement는 1개만!
    print(lamb(2));
}

 

 

4.1. typedef

dart에서는 함수의 타입도 미리 선언이 가능합니다.

함수의 타입 지정에는 Function을 사용하며 리턴 타입 + Fucntion + 매개변수의 조합을 사용합니다.

typedef는 함수의 유형을 정할 수 있으므로, dart의 기본 함수부터 익명함수 람다함수까지

리턴타입과 파라미터가 일치한다면 하나의 타입으로 지정하여 변수에 담아 사용할 수 있습니다.

// type def
typedef MyFunction1 = void Function(int x, int y);
typedef MyFunction2 = int Function(int x, int y);
typedef MyFunction3 = int Function(int a, int b, int c, MyFunction2 f2);

void addFunc1(int a, int b){
    print(a+b);
}

int addFunc2(int a, int b){
    return a+b;
}

int addFunc3(int a, int b, int c, MyFunction2 fx1){
    return a + fx1(b, c);
}


void main(){
    MyFunction1 fx1 = (int a, int b) {
        print(a+b);
    };
    MyFunction1 fx2 = addFunc1;
    fx1(1,2); // 3
    fx2(1,2); // 3

    MyFunction2 fx3 = (int a, int b) => a+b;
    MyFunction2 fx4 = addFunc2;

    print(fx3(1,2)); // 3
    print(fx4(1,2)); // 3

    // 심화
    MyFunction3 fx5 = addFunc3;
    var result1 = fx5(1, 2, 3, addFunc2);
    print(result1); // 6
    
}

 


5. 예외 처리(try-catch)

dart에서도 작성한 프로그램 코드가 에기치 못한 오류를 발생시켰을 때의 예외 처리를 아래와 같이 할 수 있습니다.

void main(){

    // 예외가 발생하지 않을 때,
    try {
        String name = "hello";
        print(name);
    }catch(e){
        print(e);
    }finally{
        print("finally...");
    }

    // 강제 예외 발생
    try {
        String name2 = "hello";
        throw Exception("Manual Exception!!");
        print(name2);

    }catch(e){
        print(e); // Exception: Manual Exception!!
    }finally{
        print("finally...2");
    }

}