[TIL] Flutter 9기 사전 캠프 Day 2: 함수 & 조건문, 반복문 & 클래스 & 제너릭

현서·2025년 11월 5일

[TIL] Flutter 9기

목록 보기
2/102

📝 기본 문법

어제 강의에서 다 못 들었던 부분 정리

변수

final 한 번만 할당 가능 (= const)
import 다른 Dart 파일이나 패키지를 불러올 때 반드시 최상단에 위치

자료형설명예제
int정수int age = 20;
double실수double height = 172.5;
String문자열String name = "홍길동";
bool참/거짓bool isStudent = true;

키워드설명예제
var한 번만 타입 지정, 이후 재할당 가능var score = 100;
final한 번만 할당 가능, 이후 변경 불가final name = "현서";
import다른 Dart 파일이나 패키지 불러오기, 최상단 위치import 'dart:math';

📝 함수

함수란?

  • 하나의 기능을 수행하는 코드 묶음
  • 필요할 때 호출하면 그 블록이 실행

함수 정의 방법

반환타입 함수명(매개변수) { 실행할 코드들 }

// void : 결과값 없이 실행만 한다는 뜻
// sayHello(): 함수 이름 (블록의 이름)

void sayHello() {
  print('안녕하세요!');
}

void main() {
  printName("홍길동");
}

void printName(String name) {
	print("이름은 $name입니다");
}

const printName = (name) => {
  console.log(`이름은 ${name}입니다`);
};

printName("홍길동");

props 느낌같음

함수 파라미터

1️⃣ 위치 기반 파라미터

순서대로 값을 전달해야하는 방식

void greet(String name, int age) {
  print('이름: $name, 나이: $age');
}

void main() {
  greet('현서', 24); // 순서 중요!
}

2️⃣ 이름 있는 파라미터

  • 파라미터의 이름을 명시적으로 지정해서 전달하는 방식
  • required 키워드로 꼭 필요한 값 지정 가능
void greet({required String name, int age = 0}) {
  print('이름: $name, 나이: $age');
}

void main() {
  greet(name: '현서', age: 24);
}
💡 순서 바꿔도 ok, 값을 넣어주면 기본으로 그 값을 넣는다는 뜻

3️⃣ 옵셔널 위치 파라미터

  • 전달하지 않아도 되는 위치 파라미터
 void greet(String name, [int age = 0]) {
  print('이름: $name, 나이: $age');
}

void main() {
  greet('현서');       // 나이 생략 가능
  greet('현서', 24);    // 나이도 전달 가능
}
💡 [] 대괄호 안 값은 생략 가능

📝 조건문, 반복문

조건문이란?

프로그램이 어떤 조건을 만족할 때만 실행되는 코드 블록 만일 ~~ 라면

키워드설명
if조건이 true일 때 실행
else if다른 조건 추가할 때
else위 조건에 모두 해당하지 않을 때
 void main() {
  int age = 20;

  if (age >= 18) {
    print('성인입니다.');
  } else {
    print('미성년자입니다.');
  }
}
// 성인입니다. 출력

<void main() {
  int score = 85;

  if (score >= 90) {
    print('A');
  } else if (score >= 80) {
    print('B');
  } else {
    print('C 이하');
  }
} //B

자바 스크립트랑 비슷한듯

const grade = (score) => {
  if (score >= 90) {
    console.log("A");
  } else if (score >= 80) {
    console.log("B");
  } else {
    console.log("C 이하");
  }
};

grade(85) //B

반복문이란?

똑같은 코드를 여러 번 반복할 때 사용

for문

반복 횟수가 명확할 때 사용
for (변수 선언초기값; 조건; 증감식) { ... }

 void main() {
  for (int i = 1; i <= 5; i++) {
    print('안녕하세요 $i');
  }
}

// 출력
안녕하세요 1
안녕하세요 2
안녕하세요 3
안녕하세요 4
안녕하세요 5

지겹게 공부했던 for문 다시 보니 반갑..^^

while문

반복 횟수가 조건에 따라 달라질 때 사용
while (조건) { ... }

 void main() {
  int i = 1;
  
	// true 일동안 실행
  while (i <= 5) {
    print('while 반복: $i');
    i++; //증감 따로 작성
  }
}

예시

 void main() {
    for (int i = 1; i <= 9; i++) {
      print("2 * $i = ${2 * i}");
    }
}

i 변수 하나만 쓸 때는 $i 이렇게 써도 가능 수식 쓸 때는 묶기 ${}

📝 클래스

클래스란?

  • 비슷한 특징을 가진 속성과 행동들을 묶어놓은 설계도
  • 그릇들을 담아두는 박스
  • 클래스는 박스를 만드는 생산라인, 객체는 생산라인에서 만들어진 박스

Class(키워드) 클래스명 { 속성, 메서드 정의 }
→ 대문자로 시작

<class Person {
	// 속성
  String name;
  int age;

	// 생성자 : 클래스를 생성(객체로 만드는 일)할 때 초기화(속성에 값을 할당)하는 특별한 함수
  Person(this.name, this.age);
	
	/// 메서드(클래스 안에 있는 함수)
  void printIntro() {
    print("내이름은 $name! $age살 이지!");
  }
}

void main(){
	// 객체화
  Person joy = Person("조이", 29);
}


마린을 개발한다고 가정

  • 마린이 가지고 있는 속성 : 체력, 공격력
  • 마린이 가지고 있는 특징 : 공격, 이동

이걸 토대로 만든 설계도

// hp와 attack은 마린이 가지고 있는 속성(변수)
// move()와 shoot()는 마린이 할 수 있는 행동(메서드)
class Marine {
  int hp = 40;
  int attack = 6;

  void move(String direction) {
    print("마린이 $direction 방향으로 이동합니다.");
  }

  void shoot() {
    print("탕탕! 마린이 공격합니다. 공격력: $attack");
  }
}

게임을 시작하면 마린에 대한 정보(설계도)만 존재

  • 설계도는 “마린은 HP 40, 공격력 6, 이동하고 공격할 수 있다”는 정보를 담고 있음.
  • 하지만 이 정보만으로는 게임에서 실제로 움직이거나 싸우는 마린이 존재 x
  • 이 상태는 '객체화 되지 않은 상태', 즉 아직 '사용할 수 없는 상태'
Marine marine1 = Marine();

실제 마린을 만들어 내는 행위 ⇒ 객체화

생성자란?

클래스를 생성(객체로 만드는 일)할 때 초기화(속성에 값을 할당)하는 특별한 함수
함수의 파라미터와 마찬가지로 네임드 파라미터 가능

기본 생성자

클래스 이름과 동일한 이름을 가진 가장 기본적인 생성자

  • 클래스 이름과 똑같이 Person() 형태로 사용
  • 하나의 클래스에 기본 생성자는 하나만 존재 가능
  • 주로 객체의 기본 속성을 초기화할 때 사용
class Person {
  String name;
  int age;

  // 기본 생성자
  Person(this.name, this.age);
}

class Person2 {
  String name;
  int age;
  bool? isMale;

// 네임드 파라미터를 사용하는 기본생성자
  Person2({required this.name, required this.age, this.isMale});
}

void main(){
	Person p1 = Person("지드래건", 30);
	// Person p2 = Person(30, "지드래건"); // 불가능
	
	Person2 pp = Person2(name: "지드래건", age: 30);
	Person2 pp2 = Person2(age: 30, name: "지드래건"); // 가능
	
	// nullable isMale
	Person2(name: "지드래건", age: 30); // 가능
	Person2(name: "지드래건", age: 30, isMale: true);
  // Person2(name: "지드래건", isMale: true); // 불가능
}

class Person은 위치 기반 파라미터
위치가 바뀌면 불가능

class Person2은 네임드 파라미터
위치 바뀌어도 가능

isMale은 nullable(bool?)이라서 생략 가능

네임드 생성자

이름이 지정되어 있는 생성자
이를 통해 코드의 가독성을 높이고 특정 목적에 맞는 객체 생성을 용이하게 함
기본 생성자 외에 이름을 붙여서 여러 개 만들 수 있는 생성자

class Marine {
  int hp = 40;
  int attack = 6;

  void move(String direction) {
    print("마린이 $direction 방향으로 이동합니다.");
  }

  void shoot() {
    print("탕탕! 마린이 공격합니다. 공격력: $attack");
  }
  Marine();
  Marine.hero() : hp = 100, attack = 6;
}

void main(List<String> arguments) {
  Marine hero = Marine.hero();
  Marine normal = Marine();
}

Marine();
Marine.hero() : hp = 100, attack = 6;

  1. Marine();
    기본 생성자
    아무 설정 안 함 → 위에서 선언한 기본값(hp = 40, attack = 6) 그대로 사용.

  2. Marine.hero();
    네임드 생성자 : 뒤에서 멤버 변수 값을 새로 초기화함.
    → hp = 100, attack = 6으로 설정.
    즉, “영웅 마린”을 따로 만드는 특별한 생성자.

void main(List<String> arguments) {
  Marine hero = Marine.hero();
  Marine normal = Marine();
}

Marine.hero() → hp=100짜리 영웅 마린 생성.
Marine() → hp=40짜리 일반 마린 생성.

상속

상속이란?

부모 클래스의 특징을 물려받는 것 extends

🚗
일반 자동차(Car) 설계도를 기반으로, 전기차(ElectricCar)를 만들고 싶을 때
기존 Car 클래스의 특징을 그대로 물려받고 (바퀴 4개, 운전)
전기차만의 특징을 추가 (배터리 값, 충전)

class Car {
  int wheels = 4;

  void drive() {
    print('브르릉! 자동차가 달립니다.');
  }
}

class ElectricCar extends Car {
  int batteryLevel = 100;

  void charge() {
    print('충전 중... 배터리 레벨: $batteryLevel%');
  }
}

부모 클래스의 메서드를 재정의 할 수 있음

class ElectricCar extends Car {
  int batteryLevel = 100;
  
  // 재정의 했음을 알려주는 Annotation(어노테이션)
  // 코드를 위한 메타 데이터
  // @override -> 실행에는 영향을 주지 않음, 협업시 헷갈리지 않게
  
  void drive() {
    print('위이잉');
  }

  void charge() {
    print('충전 중... 배터리 레벨: $batteryLevel%');
  }
}
void main() {
	Car c = Car();
	c.drive();    
    Car c2 = ElectricCar();
    if(c2 is ElectricCar){
      (c2 as ElectricCar).charge();
   }
}

Car c2 = ElectricCar();
부모 타입 변수에 자식 객체를 넣은 것(업캐스팅)

if (c2 is ElectricCar)
c2.charge를 사용하면 오류
is 연산자는 타입 확인 연산자
→ c2가 실제로 ElectricCar의 인스턴스인지 확인.
true면 아래 코드 실행

(c2 as ElectricCar).charge();
as는 형변환 연산자
→ c2를 ElectricCar 타입으로 변환.
변환 후 ElectricCar만 가진 메서드(charge())를 호출할 수 있게 됨

추상클래스

추상클래스란?

클래스 앞에 abstract 키워드를 사용하고 미완성 메소드(추상 메소드)를 포함할수 있으며 직접 객체를 생성할 수 없고 반드시 상속을 통해 완성해야 하는 클래스

abstract class Hero {
	// 구현체(중괄호)가 없는 메소드 : 추상 메소드
	// 반드시 상속받은 클래스에서 상속받아 구현해주어야함
	void skill(); 
}

구현을 강제하기 위해 사용

abstract class Car {
  int wheels = 4;

  void drive() {
    print('브르릉! 자동차가 달립니다.');
  }
  
  // 자동차의 깜빡이
  // 반드시 있어야 하지만 자동차 별로 다름
  void turnSignal(); // 추상 메서드
}

Car는 “자동차”라는 공통 설계도 역할
drive()는 모든 자동차가 공통으로 가지는 행동이니까 구현되어 있음
turnSignal()은 모든 자동차에 있어야 하지만 브랜드마다 다르게 작동하므로 “구체적 구현 없이” 선언만 되어 있음 → 추상 메서드
즉 Car는 “자동차는 깜빡이가 반드시 있어야 한다”라는 규칙을 강제하는 클래스

class AudiCar extends Car {
  
  void turnSignal() {
    print(">>>");
    print("<<<");
  }
}

class TeslaCar extends Car {
  
  void turnSignal() {
    print("<<<");
    print("]]]");
  }
}

class BusanCar extends Car {
  
  void turnSignal() {
    print("");
  }
}

실행 예시1

void main() {
  AudiCar a = AudiCar();
  TeslaCar t = TeslaCar();
  BusanCar b = BusanCar();

  a.drive();        // 브르릉! 자동차가 달립니다.
  a.turnSignal();   // >>> <<<
  
  t.drive();        // 브르릉! 자동차가 달립니다.
  t.turnSignal();   // <<< ]]]

  b.turnSignal();   // (출력 없음)
}

실행 예시2

void main() {
  Car car1 = AudiCar();
  Car car2 = TeslaCar();
  Car car3 = BusanCar();

  car1.turnSignal();   // >>> <<<
  
  car2.turnSignal();   // <<< ]]]

  car3.turnSignal();   // (출력 없음)
}

static 키워드

static 키워드란?

static은 클래스에 속하는 공유변수 또는 공유함수를 정의할 때 사용하는 키워드

class Student {
  // 일반(static이 아닌) 멤버 변수: 인스턴스마다 다른 값을 가집니다.
  String name; 

  // static 멤버 변수: 클래스 전체가 공유하는 값입니다.
  static String schoolName = "ABC 고등학교";

  // static 메서드: 인스턴스 없이 바로 호출할 수 있습니다.
  static void displaySchoolName() {
    print("우리 학교는 $schoolName 입니다.");
  }

  Student(this.name);
}
  • name
    일반 멤버 변수
    인스턴스(객체)마다 값이 다름
    student1.name, student2.name 처럼 접근해야 함
  • static String schoolName
    클래스 멤버 변수 (정적 변수)
    모든 인스턴스가 하나의 값을 공유함
    Student.schoolName으로 접근 (객체 필요 없음)
  • static void displaySchoolName()
    클래스 메서드
    인스턴스 생성 없이 호출 가능 (Student.displaySchoolName())
    내부에서 this 사용 불가능 (객체 정보가 없으니까)

💡 실행 과정

void main() {
  // static 멤버에 접근하는 방법
  // 객체를 만들 필요 없이, 클래스 이름으로 바로 접근합니다.
  print("학교 이름: ${Student.schoolName}"); 
  Student.displaySchoolName();

  // 일반 멤버에 접근하는 방법 (객체를 생성해야만 접근 가능)
  Student student1 = Student("김철수");
  Student student2 = Student("박영희");

  print("${student1.name} 학생의 학교 이름은 ${Student.schoolName}입니다.");
  print("${student2.name} 학생의 학교 이름은 ${Student.schoolName}입니다.");

student1.name → "김철수"
student2.name → "박영희"
하지만 두 객체 모두 Student.schoolName을 공유함

  // 만약 학교 이름이 바뀌면?
  Student.schoolName = "새로운 학교"; // static 변수는 한 번 바꾸면 모두에게 적용됩니다.

  print("${student1.name} 학생의 새로운 학교 이름은 ${Student.schoolName}입니다.");
  print("${student2.name} 학생의 새로운 학교 이름은 ${Student.schoolName}입니다.");
}

📝 제너릭

제너릭이란?

무엇이든 담을 수 있는 바구니
사과 바구니, 바나나 바구니, 책 바구니 따로 구입 X
다용도 바구니 구입** : 사과, 바나나, 책 모두 보관 가능

제너릭을 쓰지 않는 코드

class IntBox {
	int content;
	Box(this.content);
}

class DoubleBox {
	double content;
	Box(this.content);
}

class StringBox {
	String content;
	Box(this.content);
}

제너릭을 쓴 코드

// T는 타입 자리 표시자
// 사용할 때 어떤 타입을 쓸지 나중에 지정합니다.
class Box<T> {
  T content;
  Box(this.content);
}

void main(){
	var stringBox = Box<String>("Apple"); // content는 String
	var intBox = Box<int>(123); // content는 int
}

class Box → T는 타입 변수
Box → T가 String으로 바뀜
Box → T가 int로 바뀜
같은 클래스 로직으로 여러 타입을 처리할 수 있음

꼭 T 여야 하나요?

T 대신에 어떠한 이름을 써도 무관
관습적으로 많이 써와서 사용

이름의미사용 예
TType (가장 일반적인 타입 하나)Box<T>
EElement (List, Set 등에 자주 씀)List<E>
KKey (Map의 key 타입)Map<K, V>
VValue (Map의 value 타입)Map<K, V>
S, U, etc.기타 타입 여러 개 필요할 때Pair<S, T>

💡 함수에도 사용 가능

T echo<T>(T value) {
return value;
}

void main() {
print(echo<int>(42));        // 42
print(echo<String>("Hi"));   // Hi
}

왜 좋은가?

  • 코드를 반복해서 쓰지 않아도 됨, 재사용성이 늘어남
    (예: 사과용 박스, 바나나용 박스 각각 만들 필요 없음)
  • 에러를 미리 잡을 수 있음
    숫자 상자에 실수로 문자를 넣으려고 하면 컴퓨터가 미리 알려줌
  • 확장성과 유연함이 커짐
    다양한 상황에 한 번 만든 코드로 대응할 수 있음

공부 소감

원래 알고 있던 클래스 개념과는 새로운 개념이라 좀 더 이해하고 공부하면 좋을듯
앞으로 flutter에서 배운 부분들이 어떻게 사용될지 감이 안 잡히지만 열심히 해보는 걸로..!

0개의 댓글