Dart 기본문법을 정리하려고 한다.
아래 내용을 보고 기억이 안날 때 마다 찾아보면(ctrl+f) 좋을 듯 하다.
<< 서문 작성 >
· Dart를 익힐 때에는 DartPad라는 아래 사이트가 매우 유용했다.
· 그리고 Dart에서 특징은 각 코드의 끝에 세미콜론(;)을 꼭 붙여주어야 한다.
세미콜론을 붙이지 않으면 아래와 같은 에러를 만나게 된다.
🎆 기본 문법
1. 메인 함수 : void main()
- 다트는 프로그램 시작점 엔트리 함수로 main()을 사용하며, void는 어떤 값도 반환하지 않는다는 뜻이다.
- main 뒤의 소괄호에는 입력받을 매개변수를 지정할 수 있다.
void main(){
}
2. 변수 선언
- Dart에서 변수 선언은 기본적으로 아래와 같이 할 수 있다.
(1) var : var의 특징은 변수의 타입을 자동적으로 추론하며, 한 번 선언된 변수는 타입이 고정된다. 따라서 다른 변수 타입의 값을 변수에 다시 저장할 경우 에러가 발생한다.
var 변수명 = 값;
main() {
var no = 10;
no = 20;
no = 'hello'; // 오류
}
var로 선언시 값을 지정하지않으면 dynamic으로 선언되어 여러 타입으로 대입이 가능하다.
main() {
var no2;
no2 = 10;
no2 = 'hell0';
no2 = true;
}
(2) dynamic : dynamic은 변수의 타입을 고정시키지 않고, 다른 타입의 값을 새로 저장할 수 있다.
dynamic 변수명 = 값(num);
변수명 = 값(text);
// 에러 발생하지 않는다.
(3) final, const : 변수의 값을 처음 선언 후 변경할 수 없도록 할 경우
void main() {
final String name = '윤도현';
name = '김종국';
const String name2 = '김종국';
name2 = '윤도현';
}
위의 코드를 실행시 아래와 같이 변수값을 변경할 수 없기 때문에 에러가 발생한다.
*참고.
final → Runtime 상수 (코드가 실행한 후 값을 확정해야 할 때 사용)
const → Buildtime 상수 (코드를 실행하지 않은 상태에서 값이 확정될 때 사용, 빌드타임에 이미 값이 지정)
(4) 변수 타입 직접 지정
- String : 문자열
String name = '문자열';
- int : 정수
int isInt = 5;
- double : 실수
double isDouble = 3.14;
- bool : Boolean(true, false)
bool isTrue = true;
3. 컬렉션
- 컬렉션 : 하나의 변수에 여러 값을 저장할 수 있는 타입(List, Map, Set)
(1) List 타입 : 여러 값을 순서대로 한 수에 저장할 때
void main() {
// 리스트에 넣을 타입은 <> 사이에 명시
List<String> fruitList = ['사과', '배', '오렌지'];
print(fruitList);
print(fruitList[0]);
print(fruitList.length); // List의 길이
fruitList[0] = '포도'; // 0번 인덱스값 변경
print(fruitList);
}
// 실행 결과
// [사과, 배, 오렌지]
// 사과
// 3
// [포도, 배, 오렌지]
· add() 함수 : List에 값을 추가
void main() {
List<String> fruitList = ['사과', '배', '오렌지'];
fruitList.add('포도');
print(fruitList);
}
// 실행 결과
// [사과, 배, 오렌지, 포도]
· removeAt() 함수 : List에 저장된 요소 중 () 안의 인덱스에 해당하는 요소 삭제
void main() {
List<String> fruitList = ['사과', '배', '오렌지'];
fruitList.removeAt(0);
print(fruitList);
}
// 실행 결과
// [배, 오렌지]
· where() 함수 : List에 저장된 순서대로 특정 조건에 해당하는 값을 필터링
void main() {
List<String> fruitList = ['사과', '배', '오렌지'];
final newList = fruitList.where(
(name) => name == '사과' || name == '오렌지', // '사과' 또는 '오렌지'만 유지
);
print(newList);
print(newList.toList()); // toList() : Iterable을 List로 다시 변환할 경우
}
// 실행결과
// (사과, 오렌지)
// [사과, 오렌지]
· map() 함수 : List에 저장된 순서대로 값을 변경
void main() {
List<String> fruitList = ['사과', '배', '오렌지'];
final newList = fruitList.map(
(name) => '과일 $name',
);
print(newList);
print(newList.toList()); // toList() : Iterable을 List로 다시 변환할 경우
}
// 실행 결과
// (과일 사과, 과일 배, 과일 오렌지)
// [과일 사과, 과일 배, 과일 오렌지]
· reduce() 함수 : List에 저장된 순서대로 매개변수에 입력된 함수를 실행(리스트를 순회하며 값을 누적하는 특징)
*주의 : 리스트를 구성하는 값의 타입과 반환되는 리스트를 구성할 값의 타입이 동일해야 함
void main() {
List<String> fruitList = ['사과', '배', '오렌지'];
final allfruits = fruitList.reduce((value, element) => value + ',' + element);
print(allfruits);
}
// 실행 결과
// 사과,배,오렌지
· fold() 함수 : reduce와 유사하나, 리스트를 구성하는 값의 타입과 반환되는 리스트를 구성하는 값의 타입이 동일해야 하는 제약이 없음. 첫 번째 매개변수에 초기값을 지정하고, 두 번째 매개변수에는 reduce()함수와 동일하게 작동하는 함수를 입력.
void main() {
List<String> fruitList = ['사과', '배', '오렌지'];
final allfruits = fruitList.fold<int>(0, (value, element) => value + element.length);
print(allfruits);
}
// 실행 결과
// 6
(2) Map 타입 : key, value 쌍을 저장.
void main() {
Map<String, String> dictionary = {
'John F. Kennedy' : '존 F. 케네디',
'Barack Obama' : '버락 오바마',
'Donald Trump' : '도널드 트럼프',
'Joe Biden' : '조 바이든',
};
print(dictionary['Barack Obama']);
print(dictionary.keys);
print(dictionary.values);
}
// 실행 결과
// 버락 오바마
// (John F. Kennedy, Barack Obama, Donald Trump, Joe Biden)
// (존 F. 케네디, 버락 오바마, 도널드 트럼프, 조 바이든)
(3) Set 타입 : 중복이 없는 유일한 값들의 집합
void main() {
Set<String> fruit = {"사과", "사과", "배", "포도", "오렌지", "포도"};
print(fruit);
print(fruit.contains('사과')); // 값이 있는지 확인
print(fruit.toList()); // List로 변환
List<String> fruit2 = ["사과", "사과", "배"];
print(Set.from(fruit2)); // List를 Set으로 변환
}
// 실행 결과
// {사과, 배, 포도, 오렌지}
// true
// [사과, 배, 포도, 오렌지]
// {사과, 배}
(4) enum : 한 변수의 값을 몇 가지 옵션으로 제한하고 싶을 때 void main()외부에서 정의하여 사용
enum Status {
a,
b,
c,
}
void main() {
Status status = Status.a;
print(status);
}
// 실행 결과
// Status.a
4. 연산자
(1) 수치연산자 : +, -, *, /(나눗셈, 몫), %(나눗셈, 나머지), ++, --, += ...
(2) null 관련 연산자 : 아무 값도 없을 경우 null을 사용하며, 타입 뒤에 '?'를 추가하여 null 값을 저장
void main() {
double? number1 = 1; // 타입 뒤에 ?를 명시해서 null 값을 가질 수 있음
// double number2 = null; // 타입 뒤에 ?를 명시하지 않아 에러 발생
double? nullnum;
print(nullnum);
nullnum ??= 3; // ??을 사용하면 기존 값이 null일 때만 저장됨
print(nullnum);
nullnum ??= 4;
print(nullnum); // null이 아니므로 3이 유지된다.
}
// 실행 결과
// null
// 3
// 3
(3) 값 비교 연산자 : >, <, >=, <=, ==, !=
(4) 타입 비교 연산자 : is를 사용하여 변수 타입 비교
void main() {
int number1 = 1;
print(number1 is int);
print(number1 is String);
print(number1 is! int);
print(number1 is! String);
}
// 실행 결과
// true
// false
// false
// true
(5) 논리 연산자 : &&(and), ||(or)
5. 제어문
(1) if 문 : if (조건1) { 코드1 } else if (조건2) { 코드2 } else { 코드3 }
void main() {
String fruit = '사과';
if (fruit == '배') {
print('그 과일은 배 입니다');
} else if (fruit == '포도') {
print('그 과일은 포도 입니다');
} else {
print('그 과일은 사과 입니다');
}
}
// 실행 결과
// 그 과일은 사과 입니다.
(2) switch 문 : 입력된 상수값에 따라 알맞은 case 블록을 수행. case 끝네 break를 꼭 사용해야 하며, break를 통해 종료
enum Status {
a,
b,
c,
}
void main() {
Status status = Status.a;
switch (status) {
case Status.a:
print('상태 : a');
break;
case Status.b:
print('상태 : b');
break;
case Status.c:
print('상태 : c');
break;
default:
print('상태 : 상태 없음');
}
print(Status.values);
}
// 실행 결과
// 상태 : a
// [Status.a, Status.b, Status.c]
(3) for 문 : 코드를 반복하여 실행할 때
void main() {
for (int i = 0; i < 5; i++) {
print(i);
}
}
// 실행 결과
// 0
// 1
// 2
// 3
// 4
void main() {
List<int> numList = [4, 3, 2, 1];
for (int number in numList) {
print(number);
}
}
// 실행 결과
// 4
// 3
// 2
// 1
(4) while문, do...while문 : 조건을 기반으로 코드를 반복하여 실행할 때(조건이 true이면 반복, false이면 중지)
void main() {
int total = 0;
while(total < 5){
total += 1;
}
print(total);
}
// 실행 결과
// 5
void main() {
int total = 0;
do {
total += 1;
} while(total < 5);
print(total);
}
// 실행 결과
// 5
6. 함수 및 람다
(1) Positional parameter, Named parameter
- 함수는 한번 지정하면 여러 곳에서 다시 사용할 수 있다.
- 반환할 값이 없을 경우는 void 키워드를 사용한다.
int addNums(int a, int b) {
return a + b;
}
void main() {
print(addNums(3, 5));
}
// 실행 결과
// 8
- 다트 함수에서는 Positional parameter(순서가 고정된 매개변수)와 Named parameter(이름이 있는 매개변수)가 있으며, Positional parameter는 입력된 순서대로 매개변수에 값이 지정되지만, Named parameter는 지정하고 싶은 매개변수의 이름을 키와 값 형태로 입력하며 입력 순서가 중요하지 않다. 이 때 중괄호 { }와 required 키워드를 사용한다.
int addNums({
required int a,
required int b,
}) {
return a + b;
}
void main() {
print(addNums(a: 2, b: 9));
}
// 실행 결과
// 11
- 여기서 required 키워드는 매개변수가 null이 불가능한 타입일 경우 필수로 입력하거나 default를 지정해야 한다는 의미를 갖고 있다.
- 기본값을 갖는 Positional parameter 지정 : [ ] 기호 사용
int addNums(int a, [int b = 2]) {
return a + b;
}
void main() {
print(addNums(7));
}
// 실행 결과
// 9
- Named parameter에 기본값 적용 : required 키워드를 생략하고 등호 다음에 원하는 기본값 입력
int addNums({
required int a,
int b = 2,
}) {
return a + b;
}
void main() {
print(addNums(a: 7));
}
// 실행 결과
// 9
- Positional parameter와 Named parameter를 섞어서 사용할 경우 Positional parameter가 Named parameter보다 반드시 먼저 위치해야 함
int addNums(
int a, {
required int b,
int c = 4,
}) {
return a + b + c;
}
void main() {
print(addNums(1, b: 2, c: 8));
}
// 실행 결과
// 11
(2) 익명 함수(Anonymous function), 람다 함수(Lambda function)
- 함수 이름이 없이 일회성으로 사용되는 함수
- 다트에서는 익명 함수와 람다 함수를 구분하지 않고 사용
※ 익명함수
(매개변수) {
함수 바디
}
※ 람다 함수
(매개변수) => 단 하나의 스테이트먼트
- 익명 함수에서 { }를 빼고 => 기호를 추가한 것이 람다함수이다.
- 매개변수는 없거나 하나 이상이어도 된다.
- 익명 함수와 달리 람다 함수는 로직을 수행하는 스테이트먼트가 딱 하나이어야 한다.
- 람다 함수는 이름을 정하고 미리 선언할 필요가 없기 대문에 글로벌 스코프로 다룰 필요가 없다.
- 하나의 스테이트먼트만 다루기 때문에 간결하고 가독성이 높다.
→ 콜백 함수나 리스트의 map(), reduce(), fold() 함수 등에서 일회성이 높은 로직을 작성할 때 주로 사용
void main() {
List<int> numbers = [1, 2, 3, 4, 5];
// 익명 함수
final anonyNums = numbers.reduce((value, element) {
return value + element;
});
print(anonyNums);
// 람다 함수
final lambdaNums = numbers.reduce((value, element) => value + element);
print(lambdaNums);
}
// 실행 결과
// 15
// 15
(3) typedef : 함수의 시그니처(특징 - 반환값 타입, 매개변수 개수와 타입 등)를 정의.
typedef Operation = void Function(int x, int y);
void add(int x, int y) {
print('결괏값 : ${x + y}');
}
void subtract(int x, int y) {
print('결괏값 : ${x - y}');
}
void main() {
// typedef는 일반적인 변수의 type처럼 사용 가능
Operation oper = add;
oper(1, 2);
// subtract() 함수도 Operation에 해당되는
// 시그니처이므로 oper 변수에 저장 가능
oper = subtract;
oper(1, 2);
}
// 실행 결과
// 결괏값 : 3
// 결괏값 : -1
- 다트에서 함수는 일급 객체(First-class citizen)이므로 함수를 값처럼 사용할 수 있다. 그래서 플러터에서는 typedef으로 선언한 함수를 다음과 같이 매개변수로 넣어 사용한다. (calculate() 함수의 3번째 매개변수로 add() 함수를 입력)
typedef Operation = void Function(int x, int y);
void add(int x, int y) {
print('결괏값 : ${x + y}');
}
void calculate(int x, int y, Operation oper) {
oper(x, y);
}
void main() {
calculate(1, 2, add);
}
// 실행 결과
// 결괏값 : 3
7. try...catch
- 특정 코드의 실행을 시도(try)해보고 문제가 있다면 에러를 잡는다(catch). 즉 try와 catch 사이의 괄호에 에러가 없을 때 실행할 로직을 작성하고, catch가 감싸는 괄호에 에러가 났을 때 실행할 로직을 작성한다. 만약 try 로직에서 에러가 나면 이후 로직은 실행되지 않고 바로 catch 로직으로 넘어간다.
void main() {
try{
// 에러가 없을 때 실행할 로직
final String name = '플러터';
print(name); // ➊ 에러가 없으니 출력됨
}catch(e){ // catch는 첫 번째 매개변수에 에러 정보를 전달해줍니다.
// 에러가 있을 때 실행할 로직
print(e);
}
}
// 실행 결과
// 플러터
- 다트 언어에서는 throw 키워드를 사용하여 에러를 발생시킬 수 있다.
void main() {
try{
final String name = '코드팩토리';
// ➊ throw 키워드로 고의적으로 에러를 발생시킵니다.
throw Exception('이름이 잘못됐습니다!');
print(name);
}catch(e){
// ➋ try에서 에러가 발생했으니 catch 로직이 실행됩니다.
print(e);
}
}
// 실행 결과
// Exception: 이름이 잘못됐습니다!
'DEV > dart & flutter' 카테고리의 다른 글
다트 비동기 프로그래밍 Future, async/await, Stream (1) | 2024.01.01 |
---|---|
Dart 객체지향 프로그래밍, 클래스 (1) | 2023.12.31 |
댓글