객체 지향

Woogie_·2025년 2월 24일

Unity

목록 보기
19/20

디버깅 기초

  • 한 줄씩 검사하면서 내 코드가 어떻게 실행되고 있는지 확인할 수 있는 과정
  • F9로 코드 바로 실행하고 싶은 곳에 브레이크 포인트를 걸 수 있다.
  • F5를 통해 디버깅 모드를 실행할 수 있다.
  • F10을 통해 한 줄씩 코드를 실행할 수 있다. (대신 함수에 들어가진 않는다.)
  • F11을 통해 함수 내로 들어가 코드를 한 줄씩 수행할 수 있다.
  • 조사식을 통해서 원하는 값이 실시간으로 변경할 수 있는 것을 확인할 수 있다.
  • 브레이크 포인트를 우클릭하여 조건을 걸 수 있다. (특정 조건에서만 브레이크 포인트가 걸린다.)

객체지향의 시작

  • 절차(procedure) 지향 : 함수 기반 / 프로그램이 커질 수록 유지보수 측면에서 어려움을 겪을 수 있다. (함수 호출 순서가 중요하다)

  • 객체(OOP Object Oriented Programming) 지향 : 모든 것을 객체 우선으로 생각한다. (모든 코드들을 객체라고 한다.)

  • 객체를 설계하기 위해선 어떠한 방식으로든 객체를 묘사해야 한다.

  • 객체는 속성과 기능으로 묘사할 수 있다.

  • 예) 기사라는 직업 (속성 : hp, attack, pos...), (기능 : Move, Attack, Die ... / 할 수 있는 모든 것)

  • 객체를 묘사하고 싶을 때 쓰는 문법은 class

  • class는 객체의 틀 (붕어빵 틀과 같이 재료만 넣으면 객체를 만들어 내는 틀)

  • public으로 선언해줘야 클래스를 불러올 때 외부에서도 사용할 수 있게 된다. (public이 없을 경우 클래스 내에서만 사용할 수 있다.)

  • new 문법을 통해서 객체를 생성할 수 있다.

using System;

namespace Csharp
{
    class Knight
    {
        public int hp;
        public int attack;

        public void Move()
        {
            Console.WriteLine("Knight Move");
        }

        public void Attack() 
        {
            Console.WriteLine("Knight Attack");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Knight knight = new Knight();

            knight.hp = 100;
            knight.attack = 10;

            knight.Move();
            knight.Attack();
        }
    }
}

복사(값)와 참조

  • 얕은 복사 / 깊은 복사
  • class와 struct의 차이 : class는 참조를 해서 작업을 하게 되고 struct 같은 경우 복사를 하여 작업을 하게 된다.
using System;

namespace Csharp
{
    class Knight
    {
        public int hp;
        public int attack;

        public void Move()
        {
            Console.WriteLine("Knight Move");
        }

        public void Attack() 
        {
            Console.WriteLine("Knight Attack");
        }
    }

    struct Mage
    {
        public int hp;
        public int attack;
    }
    class Program
    {
        static void KillMage(Mage mage)
        {
            mage.hp = 0;
        }

        static void KillKnight(Knight knight)
        {
            knight.hp = 0;
        }
        static void Main(string[] args)
        {

            Mage mage;
            mage.hp = 100;
            mage.attack = 50;
            KillMage(mage);

            Knight knight = new Knight();
            knight.hp = 100;
            knight.attack = 10;
            KillKnight(knight);
        }
    }
}
  • struct 같은 경우 new 생략 가능
  • mage는 structer를 활용했기 때문에 원본 값의 변화가 없지만 knight는 클래스를 활용해 원본 값의 변화가 있다.
  • 클래스의 경우 참조를 하기 때문에 knight1, knight2 로 그냥 만들 경우 같은 것을 참조하기 떄문에 결국 같은 것을 가리키게 된다.
  • new 를 활용해서 새로 생성을 해줘야 한다.
  • 매번 new를 대입하는게 힘들다면 deep copy를 활용해서 여러 객체를 생성 할 수 있다.
using System;

namespace Csharp
{
    class Knight
    {
        public int hp;
        public int attack;

        public Knight Clone()
        {
            Knight knight = new Knight();
            knight.hp = hp;
            knight.attack = attack;
            return knight;
        }
        public void Move()
        {
            Console.WriteLine("Knight Move");
        }

        public void Attack() 
        {
            Console.WriteLine("Knight Attack");
        }
    }

    struct Mage
    {
        public int hp;
        public int attack;
    }
    class Program
    {
        static void KillMage(Mage mage)
        {
            mage.hp = 0;
        }

        static void KillKnight(Knight knight)
        {
            knight.hp = 0;
        }
        static void Main(string[] args)
        {

            Mage mage;
            mage.hp = 100;
            mage.attack = 50;
            KillMage(mage);

            Knight knight = new Knight();
            knight.hp = 100;
            knight.attack = 10;

            Knight knight2 = knight.Clone();
        }
    }
}

스택과 힙

  • 스택 메모리 힙 메모리

코드(code) 영역
메모리의 코드(code) 영역은 실행할 프로그램의 코드가 저장되는 영역으로 텍스트(code) 영역이라고도 부릅니다.

CPU는 코드 영역에 저장된 명령어를 하나씩 가져가서 처리하게 됩니다.

데이터(data) 영역
메모리의 데이터(data) 영역은 프로그램의 전역 변수와 정적(static) 변수가 저장되는 영역입니다.

데이터 영역은 프로그램의 시작과 함께 할당되며, 프로그램이 종료되면 소멸합니다.

스택(stack) 영역
메모리의 스택(stack) 영역은 함수의 호출과 관계되는 지역 변수와 매개변수가 저장되는 영역입니다.

스택 영역은 함수의 호출과 함께 할당되며, 함수의 호출이 완료되면 소멸합니다.

이렇게 스택 영역에 저장되는 함수의 호출 정보를 스택 프레임(stack frame)이라고 합니다.

스택 영역은 푸시(push) 동작으로 데이터를 저장하고, 팝(pop) 동작으로 데이터를 인출합니다.

이러한 스택은 후입선출(LIFO, Last-In First-Out) 방식에 따라 동작하므로, 가장 늦게 저장된 데이터가 가장 먼저 인출됩니다.

스택 영역은 메모리의 높은 주소에서 낮은 주소의 방향으로 할당됩니다.

힙(heap) 영역
메모리의 힙(heap) 영역은 사용자가 직접 관리할 수 있는 ‘그리고 해야만 하는’ 메모리 영역입니다.

힙 영역은 사용자에 의해 메모리 공간이 동적으로 할당되고 해제됩니다.

힙 영역은 메모리의 낮은 주소에서 높은 주소의 방향으로 할당됩니다.

스택과 힙의 장단점
스택
매우 빠른 액세스

변수를 명시 적으로 할당 해제 할 필요가 없습니다.

공간은 CPU에 의해 효율적으로 관리되고 메모리는 단편화되지 않습니다.

지역 변수 만

스택 크기 제한 (OS에 따라 다름)

변수의 크기를 조정할 수 없습니다.

힙
변수는 전역 적으로 액세스 할 수 있습니다.

메모리 크기 제한 없음

(상대적으로) 느린 액세스

효율적인 공간 사용을 보장하지 못하면 메모리 블록이 할당 된 후 시간이 지남에 따라 메모리가 조각화되어 해제 될 수 있습니다.

메모리를 관리해야합니다 (변수를 할당하고 해제하는 책임이 있습니다)

변수는 C언어 realloc() or 자바 new

생성자

  • 클래스를 선언함과 동시에 어떤 속성을 부여해주는 역할 / 클래스와 이름이 같아야 한다. / 생성자 함수는 반환 타입을 아무것도 입력하면 안된다. (void도 입력하면 안된다.)
using System;

namespace Csharp
{
    class Knight
    {
        public int hp;
        public int attack;

        public Knight()
        {
            hp = 100;
            attack = 100;
            Console.WriteLine("생성자 호출!");
        }

        public Knight(int hp)
        {
            this.hp = hp;
        }
        public Knight Clone()
        {
            Knight knight = new Knight();
            knight.hp = hp;
            knight.attack = attack;
            return knight;
        }
        public void Move()
        {
            Console.WriteLine("Knight Move");
        }

        public void Attack() 
        {
            Console.WriteLine("Knight Attack");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Knight knight = new Knight();
        }
    }
}
  • 생성자도 딱 하나만 쓸 수 있는 게 아니라 다양하게 쓸 수 있다.
  • this 키워드를 사용해서 생성자의 매개변수로 세팅해달라는 것을 명시할 수 있다.
     public Knight(int hp) : this()
     {
         this.hp = hp;
     }
  • this() 를 통해서 default 생성자를 호출 후에 추가적인 생성자를 불러올 수 있다.

static

class Progrma
{
    static void Main(string[], args)
    {

    }
}
  • class 안에 각 인스턴스마다 달라지는 필드 존재
  • 필드를 추가할 때 static으로 선언을 하게 될 경우 클래스에 종속이 되게 된다.
  • 오로지 한 개만 존재한다.
  • 클래스를 불러와서 생성자를 사용해서 여러 값을 만들더라도 고유하게 한 가지 값을 모두 같게 공유한다.
  • 클래스 내의 함수를 불러올 때 static의 경우 바로 불러올 수 있지만 인스턴스에 종속적인 함수들은 따로 만든 객체로 사용해줘야 한다.
  • 예) Console.WriteLine(); 의 경우 static 이기 때문에 바로 사용 가능

상속성

  • 객체 지향의 3대 속성 (상속성, 은닉성, 다향성)
  • 여러 클래스를 생성할 때 각 클래스마다 필드를 작성하고 데이터를 관리하는 것은 비효율적일 수 있다.
  • 상속을 하기 위해 부모와 자식 계층 관계를 정의해야한다.
  • 부모 혹은 기반 클래스 로부터 자식 혹은 파생 클래스로 상속해준다.
using System;

namespace Csharp
{
    class Player
    {
        static public int counter = 1;
        public int id;
        public int hp;
        public int attack;

        public Player()
        {
            Console.WriteLine("Player 생성자 호출!")
        }
    }
    class Mage : Player
    {

    }
    class Archer : Player
    {
    
    }
    class Knight : Player 
    {
        Knight()
        {
            Console.WriteLine("Knight 생성자 호출!");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {

        }
    }
}
  • 생성자의 경우에도 상위 클래스의 생성자부터 호출이 되고 자식 클래스의 생성자가 호출이 된다.

  • base 키워드를 통해서 상속 받은 클래스를 가리킬 수 있다. (this는 자신의 클래스의 변수에 접근 / base는 부모 클래스의 변수에 접근)

using System;

namespace Csharp
{
    class Player
    {
        static public int counter = 1;
        public int id;
        public int hp;
        public int attack;

        public void Move()
        {
            Console.WriteLine("Player Move!");
        }
    
        public void Attack()
        {
            Console.WriteLine("Player Attack!");
        }
    }
    class Mage : Player
    {

    }
    class Archer : Player
    {
    
    }
    class Knight : Player 
    {

    }
    class Program
    {
        static void Main(string[] args)
        {
            Knight knight = new Knight();
            knight.Attack();
        }
    }
}
  • 부모 클래스의 속성과 함수 모두 자식 클래스가 이어 받아서 사용할 수 있다.
  • 플레이어의 특징을 부모 클래스로부터 상속 시키고 Knight 클래스 만의 특정 기능을 추가할 수 있게 된다.

은닉성

  • 보안 level
  • 예를 들어 자동차에서 엔진 혹은 연료 장치들을 외부(사용자) 에게 노출시키지 않고 내부에 은닉하는 느낌
  • 어떤 클래스를 만들 때 public으로 선언하는 것이 아닌 클래스에서만 사용할 수 있게 하는 경우 (외부에선 절대 사용해서 안되는 경우)
  • 주로 사용하는 접근 한정자는 public / protected / private
  • public 가장 개방적인 형태
  • private => 나만 사용하겠다
  • 주로 변수를 private으로 선언으로 하고 변수를 세팅하는 함수를 public으로 공개하여 세팅하는 함수를 통해 유지 보수 측면에서 변수를 어느 순간에 다뤘는지 쉽게 파악할 수 있다.
  • protected => 보호는 되지만 상속받은 클래스는 접근할 수 있다.
using System;
using System.Xml.Serialization;

namespace CSharp
{
    class Knight
    {
        private int hp;
        
        public void SetHp(int hp)
        {
            this.hp = hp;
        }

        protected int mp;
    }

    class SuperKnight : Knight
    {
        void Test()
        {
            mp = 10;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {

        }
    }
}

클래스 형식 변환

  • 부모 클래스를 통해서 함수에 접근할 수 있지만 자식 클래스에만 있는 변수에 접근할 수 없기 때문에 형식 변환이 필요하다.
  • 단 클래스를 강제로 형변환이 됐을 경우 crash가 날 수 있다.
  • 실제로 실행해보기 전까지 문제가 뭔지 모를 수 있다.
  • 무조건 캐스팅 하지 않고 체크한 후에 함수에 진입하게 한다.
  • as 키워드를 사용해서 형식을 체크할 수도 있다.
using System;
using System.Xml.Serialization;

namespace CSharp
{
    class Player
    {
        protected int hp;
        protected int attack;
    }
    class Knight : Player
    {

    }

    class Mage : Player
    {
        public int mp;
    }
    class Program
    {
        static void EnterGame(Player player)
        {
            // bool isMage = (player is Mage);
            Mage mage = (player as Mage);

            if (mage != null)
            {
                mage.mp = 10;
                Console.WriteLine("나는 마법사다!");
            }
        }
        static void Main(string[] args)
        {
            Knight knight = new Knight();
            Mage mage = new Mage();

            EnterGame(knight);
            EnterGame(mage);
        }
    }
}
  • 참고) null 은 참조하고 있는 값이 없음!

다형성

  • 부모 클래스에 있는 함수 이름고 같은 걸 자식 클래스에서 사용하고 싶을 때 new 키워드를 통해 새로 생성할 수 있다.
using System;
using System.Xml.Serialization;

namespace CSharp
{
    class Player
    {
        protected int hp;
        protected int attack;

        public void Move()
        {
            Console.WriteLine("Player 이동");
        }
    }
    class Knight : Player
    {
        public new void Move()
        {
            Console.WriteLine("Knight 이동");
        }
    }

    class Mage : Player
    {
        public int mp;
        public new void Move()
        {
            Console.WriteLine("Mage 이동");
        }
    }
    class Program
    {
        static void EnterGame(Player player)
        {
            // bool isMage = (player is Mage);
            Mage mage = (player as Mage);

            if (mage != null)
            {
                mage.mp = 10;
                Console.WriteLine("나는 마법사다!");
                mage.Move();
            }

            Knight knight = (player as Knight);
            
            if (knight != null)
            {
                Console.WriteLine("나는 기사다!");
                knight.Move();
            }
        }
        static void Main(string[] args)
        {
            Knight knight = new Knight();
            Mage mage = new Mage();

            EnterGame(knight);
            EnterGame(mage);
        }
    }
}
  • 클래스 형식 마다 함수를 사용할 수 있지만 굉장히 비효율적이다.
  • 다형성 (poly morphism)을 활용
  • virtual / override 활용
  • 오버로딩 : 함수 이름의 재사용 / 오버라이딩 : 다형성 이용
using System;
using System.Xml.Serialization;

namespace CSharp
{
    class Player
    {
        protected int hp;
        protected int attack;

        public virtual void Move()
        {
            Console.WriteLine("Player 이동");
        }
    }
    class Knight : Player
    {
        public override void Move()
        {
            Console.WriteLine("Knight 이동");
        }
    }

    class Mage : Player
    {
        public int mp;
        public override void Move()
        {
            Console.WriteLine("Mage 이동");
        }
    }
    class Program
    {
        static void EnterGame(Player player)
        {
            player.Move();
        }
        static void Main(string[] args)
        {
            Knight knight = new Knight();
            Mage mage = new Mage();

            EnterGame(knight);
            EnterGame(mage);
        }
    }
}
  • 클래스의 형태에 맞는 함수를 호출하게 된다.
  • 오버라이딩 : 클래스 타입에 따라 다양한 함수를 호출하겠다.
  • 다중 상속할 때도 오버라이딩 할 수 있다.
  • C#에만 있는 문법 중 sealed 를 사용하여 그 클래스 이후에 상속 받는 함수들은 가상 함수를 사용할 수 없다는 것을 선언

문자열 둘러보기

using System;

namespace CSarp
{
    class Program
    {
        static void Main(string[] args)
        {
            string name = "Harry Potter";

            // 1. 찾기
            bool found = name.Contains("Harry");
            Console.WriteLine(found); // True

            int index = name.IndexOf('r');
            Console.WriteLine(index); // 가장 먼저 찾는 index / 없으면 -1

            // 2.변형
            // 2-1 추가
            name = name + "Junior";
            Console.WriteLine(name);

            // 2-2 대소문자 변경
            string lowerCaseName = name.ToLower();
            string upperCaseName = name.ToUpper();
            Console.WriteLine(lowerCaseName + " " + upperCaseName);

            string newName = name.Replace('r', 'l');
            Console.WriteLine(newName); // Hally PottelJuniol

            // 3. 분할
            string[] names = name.Split(new char[] { ' ' });
            string substringName = name.Substring(5);
            Console.WriteLine(substringName); // PotterJunior
        }
    }
}
profile
상상을 구현하는 개발자

0개의 댓글