본문 바로가기

TIL (since 2023.08.07 ~ )

2023-08-17 TIL (C# - 03)

오늘은 C# 문법 3주차를 완강하고 공부한 내용을 정리하였다. 그리고 중간에 개발자 공부 특강도 수강하였는데, 특강이 굉장히 유익한 내용이 많았으며 공감도 많이 되었다.

 

특히 개발자로서 가져야 할 마음가짐이 가장 와닿았는데, 계속 코드를 베껴쓰고 있는 것 같은 나에게 가장 필요한 조언들이었다.

 

  1. 로직과 코드에 대한 의도를 생각하기
  2. 구현하는 기술, 스택에 목적과 근거 가지기
  3. 더 좋은 방법이 있는지 고민하기

 

아직 강의를 듣고도 숙제를 제출하려 하면 어떻게 구현해야 할지 막막했지만 모르는 부분은 적극적으로 질문하고, 부족한 부분은 협업으로 채워나가야겠다. 

 

 


객체지향 프로그래밍

- 1. 특징

  1. 캡슐화 (Encapsulation)
    • 관련된 데이터와 기능을 하나의 단위로 묶음
    • 안정성과 유지보수성 향상
  2. 상속 (Inheritance)
    • 기존의 클래스를 확장하여 새로운 클래스를 만듬
    • 부모클래스(상위, 슈퍼 클래스)의 특성과 동작을 자식 클래스(하위, 서브 클래스)가 상속받아 재사용
    • 코드 중복 감소, 코드의 구조화 및 유지보수 용이
  3. 다형성 (Polymorphism)
    • 하나의 인터페이스나 기능을 다양한 방식으로 구현 및 사용
    • 오버로딩, 오버라이딩을 통해 구현
    • 유연하고 확장 가능한 코드 작성 및 가독성과 재사용성 향상 
  4. 추상화 (Abstraction)
    • 복잡한 시스템이나 개념을 단순화하여 필요 기능에 집중
    • 실제 세계의 개념을 모델링, 필요한 부분에 대한 명세를 정의
    • 핵심 개념에 집중함으로써 코드의 이해와 유지보수 용이
  5. 객체 (Object)
    • 클래스로부터 생성된 실체, 데이터와 해당 데이터를 조작하는 메서드를 가짐
    • 상태(데이터)와 행동(메서드)을 가지며, 실제 세계의 개체나 개념을 모델링
    • 객체들 간의 상호작용을 통해 동작, 모듈화와 재사용성 향상

 

- 2. 클래스의 구성 요소

  1. 필드 (Fields) : 클래스에서 사용되는 변수. 객체의 상태를 나타내는 데이터를 저장
  2. 메서드 (Methods) : 클래스에서 수행되는 동작을 정의. 객체의 동작을 구현하기 위해 사용
  3. 생성자 (Constructors) : 객체를 초기화하는 역할. 객체가 생성될 때 자동으로 호출, 필드를 초기화
  4. 소멸자 (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)

  1. 생성자란?
    • 객체가 생성될 때 호출되는 특별한 메서드
    • 클래스의 인스턴스(객체)를 초기화하고, 필요한 초기값을 설정
    • 클래스와 동일한 이름을 가지며. 반환 타입 없음
    • 객체 생성시 new 키워드와 함께 호출
  2. 생성자의 특징
    • 객체를 초기화하는 과정에서 필요한 작업을 수행
    • 여러 개 정의 가능, 생성자 오버로딩 가능
    • 디폴트 생성자 존재

<예시>

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)

  1. 소멸자란?
    • 객체가 소멸되는 시점에서 자동으로 호출되는 특별한 메서드
    • 객체사용이 종료되고 메모리에서 해제 시, 자동으로 호툴되어 필요한 정리 작업 수행
    • 클래스와 동일한 이름을 가지며, 이름 앞에 ~ 기호를 붙여 표현
    • 반환 타입이 없고 매개변수를 가질 수 없음
    • C# 에서는 가비지 컬렉터(Garbage Collector)에 의해 관리되는 메모리 해제를 담당. 명시적 소멸자 호출 권장X
  2. 소멸자의 역할
    • 자원 해제: 파일 핸들, 네트워크 연결, 데이터베이스 연결 등의 외부 리소스를 사용한 경우, 소멸자를 통해 해제가능
    • 메모리 해제: 객체가 사용한 메모리를 해제하고 관련된 자원 정리
    • 로깅 및 디버깅: 객체가 소멸되는 시점에 로깅 작업을 수행 및 디버깅 정보 기록

 

class Person
{
	private string name;
    
    public Person(string newName)
    {
    	name = newName;
        Console.WriteLine("Person 객체 생성");
    }
    
    ~Person()
    {
    	Console.WriteLine("Person 객체 소멸");
    }
}

 

 

 

프로퍼티(Property)

- 1. 개념 및 구문

  1. 프로퍼티란?
    • 클래스 멤버로서, 객체의 필드 값을 읽거나 설정하는데 사용되는 접근자 메서드의 조합
    • 객체의 필드에 간접적으로 값을 설정하거나 읽을 수 있음
    • 필드에 대한 접근 제어와 데이터 유효성 검사 등을 수행 가능
    • 프로퍼티는 필드와 마찬가지로 객체의 상태를 나타내는 데이터 역할, 외부에서 접근시 추가적인 로직 수행
  2. 구문
    • 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. 상속

  1. 개념
    • 기존의 클래스(부모, 상위)를 확장하거나 재사용하여 새로운 클래스(자식, 하위)를 생성
    • 자식 클래스는 부모 클래스의 멤버(필드, 메서드, 프로퍼티 등)를 상속받아 사용가능
    • 상속을 통해 부모 클래스의 기능 확장 및 수정
  2. 장점
    • 코드의 재사용성
    • 계층 구조의 표현
    • 유지보수의 향상
  3. 종류
    • 단일 상속
    • 다중 상속 (C#은 다중 상속 지원X)
    • 인터페이스 상속
  4. 특징
    • 부모 클래스의 멤버에 접근 : 부모 클래스의 기능을 재사용
    • 메서드 재정의 : 다형성(Polymorphism) 구현
    • 상속의 깊이 : 다수의 계층적인 상속구조를 가짐. 따라서 적절한 상속의 깊이를 유지하고 적절히 사용
  5. 접근 제한자와 상속
    • 멤버의 접근 제한자는 중요한 역할. 부모 클래스의 멤버의 접근 제한자에 따라 자식 클래스도 범위 결정
    • 상속된 멤버의 가시성을 조절하여 캡슐화와 정보 은닉 구현 가능

<예시>

// 부모 클래스
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

 

※ 주의사항

  1. 값의 변경 가능성 : ref 매개변수를 사용시 메서드 내에서 해당 변수의 값을 직접 변경 가능. 이는 예기치 않은 동작 초래 가능성
  2. 성능 이슈 : ref 매개변수는 값에 대한 복사 없이 메서드 내에서 직접 접근할 수 있으므로 성능상 이점이 있으나, 너무 많이 전달하면 코드의 가독성이 떨어지고 유지보수가 어려움.
  3. 변수 변경 여부 주의 : 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