오늘은 C# 문법 3주차를 완강하고 공부한 내용을 정리하였다. 그리고 중간에 개발자 공부 특강도 수강하였는데, 특강이 굉장히 유익한 내용이 많았으며 공감도 많이 되었다.
특히 개발자로서 가져야 할 마음가짐이 가장 와닿았는데, 계속 코드를 베껴쓰고 있는 것 같은 나에게 가장 필요한 조언들이었다.
- 로직과 코드에 대한 의도를 생각하기
- 구현하는 기술, 스택에 목적과 근거 가지기
- 더 좋은 방법이 있는지 고민하기
아직 강의를 듣고도 숙제를 제출하려 하면 어떻게 구현해야 할지 막막했지만 모르는 부분은 적극적으로 질문하고, 부족한 부분은 협업으로 채워나가야겠다.
객체지향 프로그래밍
- 1. 특징
- 캡슐화 (Encapsulation)
- 관련된 데이터와 기능을 하나의 단위로 묶음
- 안정성과 유지보수성 향상
- 상속 (Inheritance)
- 기존의 클래스를 확장하여 새로운 클래스를 만듬
- 부모클래스(상위, 슈퍼 클래스)의 특성과 동작을 자식 클래스(하위, 서브 클래스)가 상속받아 재사용
- 코드 중복 감소, 코드의 구조화 및 유지보수 용이
- 다형성 (Polymorphism)
- 하나의 인터페이스나 기능을 다양한 방식으로 구현 및 사용
- 오버로딩, 오버라이딩을 통해 구현
- 유연하고 확장 가능한 코드 작성 및 가독성과 재사용성 향상
- 추상화 (Abstraction)
- 복잡한 시스템이나 개념을 단순화하여 필요 기능에 집중
- 실제 세계의 개념을 모델링, 필요한 부분에 대한 명세를 정의
- 핵심 개념에 집중함으로써 코드의 이해와 유지보수 용이
- 객체 (Object)
- 클래스로부터 생성된 실체, 데이터와 해당 데이터를 조작하는 메서드를 가짐
- 상태(데이터)와 행동(메서드)을 가지며, 실제 세계의 개체나 개념을 모델링
- 객체들 간의 상호작용을 통해 동작, 모듈화와 재사용성 향상
- 2. 클래스의 구성 요소
- 필드 (Fields) : 클래스에서 사용되는 변수. 객체의 상태를 나타내는 데이터를 저장
- 메서드 (Methods) : 클래스에서 수행되는 동작을 정의. 객체의 동작을 구현하기 위해 사용
- 생성자 (Constructors) : 객체를 초기화하는 역할. 객체가 생성될 때 자동으로 호출, 필드를 초기화
- 소멸자 (Destructors) : 객체가 소멸될 때 호출되는 메서드. 메모리나 리소스의 해체 등의 작업 수행
- 3. 클래스
- 클래스는 객체를 생성하기 위한 템플릿 또는 설계도의 역할
- 속성과 동작을 가짐. 속성을 필드로, 동작은 메서드로 표현
- 객체를 생성하기 위해서는 클래스를 사용하여 인스턴스를 만들어야 함
- ex) 붕어빵 틀로 비유하면, 클래스는 붕어빵을 만들기 위한 틀
- 4. 객체
- 객체는 클래스의 인스턴스. 클래스의 실체화된 형태
- 각 객체는 독립적인 상태를 가짐. 개체마다 고유한 데이터를 가질 수 있음
- ex) 붕어빵 틀로 비유하면, 객체는 붕어빵
- 5. 클래스 선언과 인스턴스
- 클래스는 데이터와 메서드를 하나로 묶은 사용자 정의 타입
<예시>
class Person
{
public string Name;
public int Age;
public void PrintInfo()
{
Console.WriteLine("Name: " + Name);
Console.WriteLine("Age: " + Age);
}
}
Person p = new Person();
p.Name = "Min";
p.Age = 26;
p.PrintInfo(); // 출력: Name: Min, Age: 26
- 6. 구조체 VS 클래스
- 구조체와 클래스는 모두 사용자 정의 형식을 만드는 데 사용
- 구조체는 값 형식, 스택에 할당, 상속불가, 작은 크기의 데이터 저장이나 단순 구조에 적합
- 클래스는 참조 형식, 힙에 할당, 단일 상속 및 다중 상속 가능, 복잡한 객체 표현 및 다양한 기능 제공
접근 제한자
- 클래스, 필드, 메서드 등의 접근 가능한 범위를 지정하는 키워드.
- 클래스의 캡슐화를 제어하는 역할
class Person
{
public string Name; // 외부에서 자유롭게 접근 가능
private int Age; // 같은 클래스 내부에서만 접근 가능
protected string Address; // 같은 클래스 내부와 상속받은 클래스에서만 접근 가능
}
필드와 메서드
- 클래스 : 필드 + 메서드
- 필드 : 클래스 내부에 선언되어 있는 변수, 클래스의 상태를 나타내는 데이터를 저장
- 메서드 : 클래스 내부에 선언되어 있는 함수, 클래스의 동작을 정의하고 실행
- 1. 필드 (Fields)
- 클래스나 구조체 내에서 객체의 상태(데이터)를 저장하는 변수
- 객체의 특징이나 속성을 표현하며, 클래스의 멤버 변수로 선언
- private 접근 제한자를 사용하여 외부 접근 제한, 필요한 경우에는 프로퍼티를 통해 간접적으로 접근
class Player
{
// 필드 선언
private string name;
private int level;
}
- 2. 메서드 (Methods)
- 클래스나 구조체에서 객체의 동작(기능)을 정의하는 함수
- 객체의 행동이나 동작을 구현, 클래스의 멤버 함수로 선언
- 입력값을 받아서 처리, 결과값 반환
- 객체의 상태를 변경 및 다른 메서드를 호출하여 작업을 수행
- public 접근 제한자를 사용하여 외부에서 호출
class Player
{
//메서드
public void Attack()
{
// 공격 동작 구현
}
}
- 메서드를 호출하기 위해 해당 메서드가 속해 있는 클래스의 인스턴스를 생성.
Player player = new Player(); // Player 클래스의 인스턴스 생성
player.Attack(); // Attack 메서드 호출
생성자와 소멸자
- 1. 생성자 (Constructor)
- 생성자란?
- 객체가 생성될 때 호출되는 특별한 메서드
- 클래스의 인스턴스(객체)를 초기화하고, 필요한 초기값을 설정
- 클래스와 동일한 이름을 가지며. 반환 타입 없음
- 객체 생성시 new 키워드와 함께 호출
- 생성자의 특징
- 객체를 초기화하는 과정에서 필요한 작업을 수행
- 여러 개 정의 가능, 생성자 오버로딩 가능
- 디폴트 생성자 존재
<예시>
class Person
{
private string name;
private int age;
// 매개변수가 없는 디폴트 생성자
public Person()
{
name = "Unknown";
age = 0;
}
// 매개변수를 받는 생성자
public Person(string newName, int newAge)
{
name = newName;
age = newAge;
}
public void PrintInfo()
{
Console.WriteLine($"Name: {name}, Age: {age}");
}
}
Person person1 = new Person(); // 디폴트 생성자 호출
Person person2 = new Person("Min", 26); // 매개변수를 받는 생성자 호출
- 2. 소멸자 (Destructor)
- 소멸자란?
- 객체가 소멸되는 시점에서 자동으로 호출되는 특별한 메서드
- 객체사용이 종료되고 메모리에서 해제 시, 자동으로 호툴되어 필요한 정리 작업 수행
- 클래스와 동일한 이름을 가지며, 이름 앞에 ~ 기호를 붙여 표현
- 반환 타입이 없고 매개변수를 가질 수 없음
- C# 에서는 가비지 컬렉터(Garbage Collector)에 의해 관리되는 메모리 해제를 담당. 명시적 소멸자 호출 권장X
- 소멸자의 역할
- 자원 해제: 파일 핸들, 네트워크 연결, 데이터베이스 연결 등의 외부 리소스를 사용한 경우, 소멸자를 통해 해제가능
- 메모리 해제: 객체가 사용한 메모리를 해제하고 관련된 자원 정리
- 로깅 및 디버깅: 객체가 소멸되는 시점에 로깅 작업을 수행 및 디버깅 정보 기록
class Person
{
private string name;
public Person(string newName)
{
name = newName;
Console.WriteLine("Person 객체 생성");
}
~Person()
{
Console.WriteLine("Person 객체 소멸");
}
}
프로퍼티(Property)
- 1. 개념 및 구문
- 프로퍼티란?
- 클래스 멤버로서, 객체의 필드 값을 읽거나 설정하는데 사용되는 접근자 메서드의 조합
- 객체의 필드에 간접적으로 값을 설정하거나 읽을 수 있음
- 필드에 대한 접근 제어와 데이터 유효성 검사 등을 수행 가능
- 프로퍼티는 필드와 마찬가지로 객체의 상태를 나타내는 데이터 역할, 외부에서 접근시 추가적인 로직 수행
- 구문
- get과 set 접근자를 사용하여 값을 읽고 설정하는 동작을 정의
- get 접근자는 프로퍼티의 값을 반환, set 접근자는 프로퍼티의 값을 설정
- 필요에 따라 get 또는 set 접근자 중 하나를 생략하여 읽기 전용 또는 쓰기 전용 프로퍼티를 정의
[접근 제한자] [데이터 타입] 프로퍼티명
{
get
{
// 필드를 반환하거나 다른 로직 수행
}
set
{
// 필드에 값을 설정하거나 다른 로직 수행
}
}
<예시>
Player 클래스의 이름과 레벨을 나타내는 name과 level 필드를 캡슐화한 프로퍼티의 예시.
class Person
{
private string name;
private int age;
public string Name
{
get { return name; }
set { name = value; }
}
public int Age
{
get { return age; }
set { age = value; }
}
}
Person person = new Person();
person.Name = "Min"; // Name 프로퍼티에 값 설정
person.Age = 26; // Age 프로퍼티에 값 설정
Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
※ 프로퍼티 접근 제한자 적용 & 유효성 검사 예시
class Person
{
private string name;
private int age;
public string Name
{
get { return name; }
private set { name = value; }
}
public int Age
{
get { return age; }
set
{
if (value >= 0)
age = value;
}
}
}
Person person = new Person();
person.Name = "Min"; // 컴파일 오류: Name 프로퍼티의 set 접근자는 private입니다.
person.Age = -26; // 유효성 검사에 의해 나이 값이 설정되지 않습니다.
Console.WriteLine($"Name: {person.Name}, Age: {person.Age}"); // Name과 Age 프로퍼티에 접근하여 값을 출력합니다.
- 2. 자동 프로퍼티 (Auto Property)
- 프로퍼티를 간단하게 정의하고 사용할 수 있는 편리한 기능
- 필드의 선언과 접근자 메서드의 구현을 컴파일러가 자동으로 처리하여 개발자가 간단한 구문으로 프로퍼티를 정의
[접근 제한자] [데이터 타입] 프로퍼티명 { get; set; }
<예시>
class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
Person person = new Person();
person.Name = "Min"; // 값을 설정
person.Age = 26; // 값을 설정
Console.WriteLine($"Name: {person.Name}, Age: {person.Age}"); // 값을 읽어 출력
상속과 다형성
- 1. 상속
- 개념
- 기존의 클래스(부모, 상위)를 확장하거나 재사용하여 새로운 클래스(자식, 하위)를 생성
- 자식 클래스는 부모 클래스의 멤버(필드, 메서드, 프로퍼티 등)를 상속받아 사용가능
- 상속을 통해 부모 클래스의 기능 확장 및 수정
- 장점
- 코드의 재사용성
- 계층 구조의 표현
- 유지보수의 향상
- 종류
- 단일 상속
- 다중 상속 (C#은 다중 상속 지원X)
- 인터페이스 상속
- 특징
- 부모 클래스의 멤버에 접근 : 부모 클래스의 기능을 재사용
- 메서드 재정의 : 다형성(Polymorphism) 구현
- 상속의 깊이 : 다수의 계층적인 상속구조를 가짐. 따라서 적절한 상속의 깊이를 유지하고 적절히 사용
- 접근 제한자와 상속
- 멤버의 접근 제한자는 중요한 역할. 부모 클래스의 멤버의 접근 제한자에 따라 자식 클래스도 범위 결정
- 상속된 멤버의 가시성을 조절하여 캡슐화와 정보 은닉 구현 가능
<예시>
// 부모 클래스
public class Animal
{
public string Name { get; set; }
public int Age { get; set; }
public void Eat()
{
Console.WriteLine("Animal is eating.");
}
public void Sleep()
{
Console.WriteLine("Animal is sleeping.");
}
}
// 자식 클래스
public class Dog : Animal
{
public void Bark()
{
Console.WriteLine("Dog is bark.");
}
}
public class Cat : Animal
{
public void Sleep()
{
Console.WriteLine("Cat is sleeping.");
}
public void Meow()
{
Console.WriteLine("Cat is meow.");
}
}
// 사용 예시
Dog dog = new Dog();
dog.Name = "Bobby";
dog.Age = 3;
dog.Eat(); // Animal is eating.
dog.Sleep(); // Animal is sleeping.
dog.Bark(); // Dog is barking
Cat cat = new Cat();
cat.Name = "Navi";
cat.Age = 10;
cat.Eat();
cat.Sleep();
cat.Meow();
- 2. 다형성
- 같은 타입이지만 다양한 동작을 수행할 수 있는 능력
1. 가상 (Virtual) 메서드
- 기본적으로 부모 클래스에서 정의, 자식 클래스에서 재정의
- virtual 키워드를 사용하여 선언, 필요에 따라 자식 클래스에서 재정의
<예시>
public class Unit
{
public virtual void Move()
{
Console.WriteLine("두발로 걷기");
}
public void Attack()
{
Console.WriteLine("Unit 공격");
}
}
public class Marine : Unit
{
}
public class Zergling : Unit
{
public override void Move()
{
Console.WriteLine("네발로 걷기");
}
}
// 사용 예시
// #1 참조형태와 실형태가 같을때
Marine marine = new Marine();
marine.Move();
marine.Attack();
Zergling zergling = new Zergling();
zergling.Move();
zergling.Attack();
// #2 참조형태와 실형태가 다를때
List<Unit> list = new List<Unit>();
list.Add(new Marine());
list.Add(new Zergling());
foreach (Unit unit in list)
{
unit.Move();
}
2. 추상 (Abstract) 클래스와 메서드
- 직접적으로 인스턴스를 생성할 수 없는 클래스
- 주로 상속을 위한 베이스 클래스
- abstract 키워드를 사용하여 선언, 추상 메서드 포함 가능
- 추상 메서드는 구현부가 없는 메서드로, 자식 클래스에서 반드시 구현되어야 함
<예시>
abstract class Shape
{
public abstract void Draw();
}
class Circle : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a circle");
}
}
class Square : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a square");
}
}
class Triangle : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a triangle");
}
}
List<Shape> list = new List<Shape>();
list.Add(new Circle());
list.Add(new Square());
list.Add(new Triangle());
foreach (Shape shape in list )
{
shape.Draw();
}
3. 오버라이딩과 오버로딩
- 오버라이딩 : 부모 클래스에서 이미 정의된 메서드를 자식 클래스에서 재정의하는 것
public class Shape
{
public virtual void Draw()
{
Console.WriteLine("Drawing a shape.");
}
}
public class Circle : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a circle.");
}
}
public class Rectangle : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a rectangle.");
}
}
Shape shape1 = new Circle();
Shape shape2 = new Rectangle();
shape1.Draw(); // Drawing a circle.
shape2.Draw(); // Drawing a rectangle.
- 오버로딩 : 동일한 메서드 이름을 갖고 있지만, 매개변수의 개수, 타입 또는 순서가 다른 여러 개의 메서드를 정의
public class Calculator
{
public int Add(int a, int b)
{
return a + b;
}
public int Add(int a, int b, int c)
{
return a + b + c;
}
}
Calculator calc = new Calculator();
int result1 = calc.Add(2, 3); // 5
int result2 = calc.Add(2, 3, 4); // 9
제너릭
- 제너릭은 클래스나 메서드를 일반화시켜 다양한 자료형에 대응할 수 있는 기능
- <T> 형태의 키워드를 이용하여 제너릭 선언
- 제너릭 클래스나 메서드에서 사용할 자료형은 선언 시점이 아닌 사용 시점에 결정
<예시>
// 제너릭 클래스 선언 예시
class Stack<T>
{
private T[] elements;
private int top;
public Stack()
{
elements = new T[100];
top = 0;
}
public void Push(T item)
{
elements[top++] = item;
}
public T Pop()
{
return elements[--top];
}
}
// 제너릭 클래스 사용 예시
Stack<int> intStack = new Stack<int>();
intStack.Push(1);
intStack.Push(2);
intStack.Push(3);
Console.WriteLine(intStack.Pop()); // 출력 결과: 3
// 제너릭을 두개 이상 사용하는 예시
class Pair<T1, T2>
{
public T1 First { get; set; }
public T2 Second { get; set; }
public Pair(T1 first, T2 second)
{
First = first;
Second = second;
}
public void Display()
{
Console.WriteLine($"First: {First}, Second: {Second}");
}
}
Pair<int, string> pair1 = new Pair<int, string>(1, "One");
pair1.Display();
Pair<double, bool> pair2 = new Pair<double, bool>(3.14, true);
pair2.Display();
<출력값>
First: 1, Second: One
First: 3.14, Second: True
out, ref 키워드
- out, ref 키워드는 메서드에서 매개변수를 전달할 때 사용
- out 키워드는 메서드에서 반환 값을 매개변수로 전달하는 경우
- ref 키워드는 메서드에서 매개변수를 수정하여 원래 값에 영향을 주는 경우
- our, ref 키워드를 사용하면 메서드에서 값을 반환하는 것이 아니라, 매개변수를 이용하여 값을 전달
<예시>
// out 키워드 사용 예시
void Divide(int a, int b, out int quotient, out int remainder)
{
quotient = a / b;
remainder = a % b;
}
int quotient, remainder;
Divide(7, 3, out quotient, out remainder);
Console.WriteLine($"{quotient}, {remainder}"); // 출력 결과: 2, 1
// ref 키워드 사용 예시
void Swap(ref int a, ref int b)
{
int temp = a;
a = b;
b = temp;
}
int x = 1, y = 2;
Swap(ref x, ref y);
Console.WriteLine($"{x}, {y}"); // 출력 결과: 2, 1
※ 주의사항
- 값의 변경 가능성 : ref 매개변수를 사용시 메서드 내에서 해당 변수의 값을 직접 변경 가능. 이는 예기치 않은 동작 초래 가능성
- 성능 이슈 : ref 매개변수는 값에 대한 복사 없이 메서드 내에서 직접 접근할 수 있으므로 성능상 이점이 있으나, 너무 많이 전달하면 코드의 가독성이 떨어지고 유지보수가 어려움.
- 변수 변경 여부 주의 : out 매개변수는 메서드 내에서 반드시 값을 할당해야 함. 따라서 out 매개변수를 전달할 때 해당 변수의 이전 값이 유지되지 않으므로 주의
'TIL (since 2023.08.07 ~ )' 카테고리의 다른 글
2023-08-21 TIL(개인과제 2일차) (0) | 2023.08.21 |
---|---|
2023-08-18 TIL (C# 기초문법 + 개인과제 1일차) (0) | 2023.08.18 |
2023-08-16 TIL (C# - 02) (2) | 2023.08.16 |
2023-08-14 TIL (C# - 01) (0) | 2023.08.14 |
2023-08-11 TIL (0) | 2023.08.11 |