반응형

열거(Enums)

열거(Enum)는 상수(변경 불가능한 변수)의 그룹을 정의할 때 사용하는 특별한 “클래스”다. 게임 개발에서 로직을 구현할 때도 자주 사용된다. 예를 들어 난이도 설정, 게임 상태 등을 구현할 때 사용된다. 예시1은 난이도 설정에 대한 간단한 로직이다.

예시1:

class Program
{
  enum Level
  {
    Low,
    Medium,
    High
  }
  static void Main(string[] args)
  {
    Level myVar = Level.Medium;
    Console.WriteLine(myVar);
  }
}

// 출력 : Medium
// 출처 : w3schools

열거형에는 값도 설정할 수 있다

문자열의 인덱스 값과 마찬가지로 열거형도 첫 번째 항목의 값은 0으로 시작한다. 한 가지 신경써야 하는 점은 정수값을 얻기 위해서 명시적 변환이 필요하다는 점이다.

예시2:

enum Months
{
  January,    // 0
  February,   // 1
  March,      // 2
  April,      // 3
  May,        // 4
  June,       // 5
  July        // 6
}

static void Main(string[] args)
{
  int myNum = (int) Months.April; // int로 명시적 변환
  Console.WriteLine(myNum);
}

// 출력 : 3
// 출처 : w3schools

또한 열거형 값을 임의로 할당할 수 도 있다.

예시3:

enum Months
{
  January,    // 0
  February,   // 1
  March=6,    // 6
  April,      // 7
  May,        // 8
  June,       // 9
  July        // 10
}

static void Main(string[] args)
{
  int myNum = (int) Months.April;
  Console.WriteLine(myNum);
}

// 출력 : 7
// 출처 : w3schools

예시3에서 March의 값을 6으로 변경해주었다. 그 뒤의 값들도 순차적으로 변화하는 것을 확인할 수 있다. 변경된 값만 바뀌는 것이 아니라 그 뒤의 값들도 오름차순으로 값이 바뀐다는 점을 유의하자.

열거형과 Switch

열거형은 스위치 문과 함께 자주 사용된다고 한다.

enum Level 
{
  Low,
  Medium,
  High
}

static void Main(string[] args) 
{
  Level myVar = Level.Medium;
  switch(myVar) 
  {
    case Level.Low:
      Console.WriteLine("Low level");
      break;
    case Level.Medium:
       Console.WriteLine("Medium level");
      break;
    case Level.High:
      Console.WriteLine("High level");
      break;
  }
}

// 출력 : Medium level

요약정리

  1. 열거형은 변하지 않는 값(상수)들의 그룹을 정의할 때 사용하는 특수 클래스다.
  2. 열거형의 항목들은 0부터 시작하는 값을 가지고 있으며, 별도로 설정할 수 도 있다.
반응형
반응형

인터페이스(Interfaces)는 무엇인가?

인터페이스는 C#에서 추상화를 구현하는 또 다른 방법이다. 어떤 클래스가 어떤 메서드들을 가져야 하는지를 정의하는 일종의 규칙이라고 할 수 있다.

 

추상 클래스, 추상 메서드에 대해서는 아래 포스트에 자세히 정리해 두었으니 참고하자.

 

2024.04.23 - [C# 기초] - [C#기초] 추상(abstract) 클래스와 메서드

 

[C#기초] 추상(abstract) 클래스와 메서드

추상화(Abstract)란 무엇인가? 추상화는 특정 정보는 숨기고, 필수 정보만 사용자에게 표시하는 프로세스이다. 추상화는 클래스나 인터페이스를 사용하여 구현할 수 있다. 클래스 또는 메서드를

bigchoiiistudio.tistory.com

인터페이스의 특징은 선언부에 메서드 구현을 포함하지 않는다는 것이다. 단지 메서드의 이름, 매개변수, 반환값의 형식 등을 정의한다.

예시1:

// interface
interface IAnimal
{
  void animalSound(); // interface method (does not have a body)
  void run(); // interface method (does not have a body)
}

// 코드 출처 : w3schools
  • 인터페이스의 이름은 시작 부분에 문자 ‘I’로 시작하는 것이 일반적이다. 클래스와 인터페이스를 구분하기 위함이다.

인터페이스 사용 방법

인터페이스를 구현하는 방법은 클래스가 클래스를 상속할 때와 같이 ‘:’ 기호를 사용한다.

예시2:

// 인터페이스
interface IAnimal
{
  void animalSound(); // 인터페이스 메서드
}

// Pig "implements" the IAnimal interface
class Pig : IAnimal
{
  public void animalSound()
  {
    // The body of animalSound() is provided here
    Console.WriteLine("The pig says: wee wee");
  }
}

class Program
{
  static void Main(string[] args)
  {
    Pig myPig = new Pig();  // Create a Pig object
    myPig.animalSound();
  }
}

// 코드 출처 : w3schools

인터페이스의 특징

인터페이스에 정의해 놓은 메서드는 구현부에서 반드시 재정의 해야 한다. 예시2에서는 IAnimal 인터페이스에 animalSound 메서드가 정의되었다.

 

이를 Pig 클래스에서 구현할 때 animalSound 메서드를 재정의해주었다. 추상화는 추상 클래스 내에 정의된 메서드 외에도 이를 상속받은 클래스에서 새로운 메서드나 변수 등을 선언할 수 있었지만, 인터페이스는 불가능하다.

 

인터페이스는 기존에 정의된 속성과 메서드 외에는 추가할 수 없다.

예시3:

// 추상화(Abstraction) 예제

// 추상 클래스인 Animal
abstract class Animal
{
    // 추상 메서드 MakeSound 정의
    public abstract void MakeSound();
}

// Animal을 상속받은 Dog 클래스 (살)
class Dog : Animal
{
    // MakeSound 메서드 구현
    public override void MakeSound()
    {
        Console.WriteLine("멍멍!");
    }

    // Dog 클래스의 특정한 기능과 속성
    public void WagTail()
    {
        Console.WriteLine("꼬리를 흔들며 행복해요!");
    }
}

// Animal을 상속받은 Cat 클래스 (장기)
class Cat : Animal
{
    // MakeSound 메서드 구현
    public override void MakeSound()
    {
        Console.WriteLine("야옹!");
    }

    // Cat 클래스의 특정한 기능과 속성
    public void Purr()
    {
        Console.WriteLine("산소온거니까 편안해요.");
    }
}

// 인터페이스(Interface) 예제
interface IShape
{
    void Draw();
}

class Rectangle : IShape
{
    public void Draw()
    {
        Console.WriteLine("사각형을 그립니다.");
    }
}

class Circle : IShape
{
    public void Draw()
    {
        Console.WriteLine("원을 그립니다.");
    }
}

class Program
{
    static void Main(string[] args)
    {
        // 추상화 예제
        Animal dog = new Dog();
        dog.MakeSound(); // "멍멍!" 출력
        dog.WagTail();

        Animal cat = new Cat();
        cat.MakeSound(); // "야옹~" 출력
        cat.Purr();

        // 인터페이스 예제
        IShape rectangle = new Rectangle();
        rectangle.Draw(); // "사각형을 그립니다." 출력

        IShape circle = new Circle();
        circle.Draw(); // "원을 그립니다." 출력
    }
}

또한 인터페이스는 여러 개를 한 번에 구현할 수 있다. 예시4를 살펴보자. IFirstInterface와 ISecondInterface를 선언했고, DemoClass는 이 두 인터페이스를 모두 구현했다. 이를 다중 인터페이스라고 한다. 구현은 ,기호로 구분하여한다.

 

예시4:

interface IFirstInterface 
{
  void myMethod(); // interface method
}

interface ISecondInterface 
{
  void myOtherMethod(); // interface method
}

// Implement multiple interfaces
class DemoClass : IFirstInterface, ISecondInterface 
{
  public void myMethod() 
  {
    Console.WriteLine("Some text..");
  }
  public void myOtherMethod() 
  {
    Console.WriteLine("Some other text...");
  }
}

class Program 
{
  static void Main(string[] args)
  {
    DemoClass myObj = new DemoClass();
    myObj.myMethod();
    myObj.myOtherMethod();
  }
}

다중 인터페이스를 사용하는 이유는 C#이라는 언어가 클래스의 다중 상속을 지원하지 않기 때문이다.

요약정리

  1. 인터페이스는 추상화를 구현하는 또 다른 방법이며, 어떤 클래스가 어떤 메서드를 가져야 하는지 정의하는 일종의 규칙이다.
  2. 추상 클래스와 마찬가지로 직접적으로 객체 생성을 할 수 없다.
  3. 인터페이스의 선언부의 메서드에는 구현을 포함하지 않는다.
  4. 인터페이스 구현 시 해당 메서드를 반드시 재정의해야 한다.
  5. 인터페이스에는 속성과 메서드가 포함될 수 있으나 필드/변수는 포함 될 수 없다.
  6. 인터페이스 멤버는 기본적으로 abstractpublic이다.
  7. 인터페이스는 생성자를 포함할 수 없다.
반응형
반응형

추상화(Abstract)란 무엇인가?

추상화는 특정 정보는 숨기고, 필수 정보만 사용자에게 표시하는 프로세스이다. 추상화는 클래스나 인터페이스를 사용하여 구현할 수 있다. 클래스 또는 메서드를 선언할 때 abstract 키워드를 사용하면 된다.

 

추상화를 거쳐 추상 클래스 또는 추상 메서드가 되면 직접적인 접근이 제한되고, 상속에 의해서만 접근할 수 있다.

추상 클래스와 메서드

  • 추상 클래스 : 객체를 생성하는 데 사용할 수 없다. 접근을 위해서는 상속되어야 한다.
  • 추상 메서드 : 추상 메서드가 포함된 추상 클래스에서만 사용가능하며, 추상 클래스를 상속받은 자식 클래스(파생 클래스)에서 접근할 수 있다.

추상 클래스는 왜 사용되는가?

추상 클래스를 뼈에 비유해보자. 뼈는 동물의 기본 구조를 정의한다. 이와 마찬 가지로 추상 클래스는 하위 클래스들이 공유할 수 있는 기본적인 특성을 정의하는데 사용된다. 예시1을 살펴보자.

예시1:

using System;

// 추상 클래스인 Animal
abstract class Animal
{
    // 추상 메서드 MakeSound 정의
    public abstract void MakeSound();
}

// Animal을 상속받은 Dog 클래스 (살)
class Dog : Animal
{
    // MakeSound 메서드 구현
    public override void MakeSound()
    {
        Console.WriteLine("멍멍!");
    }

    // Dog 클래스의 특정한 기능과 속성
    public void WagTail()
    {
        Console.WriteLine("꼬리를 흔들며 행복해요!");
    }
}

// Animal을 상속받은 Cat 클래스 (장기)
class Cat : Animal
{
    // MakeSound 메서드 구현
    public override void MakeSound()
    {
        Console.WriteLine("야옹!");
    }

    // Cat 클래스의 특정한 기능과 속성
    public void Purr()
    {
        Console.WriteLine("편안해요.");
    }
}

class Program
{
    static void Main(string[] args)
    {
        // Dog 클래스의 인스턴스 생성
        Dog myDog = new Dog();
        Console.WriteLine("Dog이 소리를 냅니다:");
        myDog.MakeSound(); // Dog의 MakeSound 메서드 호출
        myDog.WagTail(); // Dog의 WagTail 메서드 호출

        Console.WriteLine();

        // Cat 클래스의 인스턴스 생성
        Cat myCat = new Cat();
        Console.WriteLine("Cat이 소리를 냅니다:");
        myCat.MakeSound(); // Cat의 MakeSound 메서드 호출
        myCat.Purr(); // Cat의 Purr 메서드 호출
    }
}

예시1에서는 Animal이 ‘뼈 역할’을 하고, 이를 상속받아 구체적인 동물 클래스들이 살이나 장기 역할을 한다고 보면 된다. 추상 클래스인 Animal에는 공통적인 동작인 MakeSound 메서드가 정의되어 있다.

 

다시 한번 정리하자면 추상 클래스는 하위 클래스들에게 상속할 ‘공통된 특성’을 정의한다. 이를 상속받은 Dog, Cat 클래스 등은 각각 저마다의 특성과 행동을 정의한다. 이런 식으로 호랑이, 염소 등 동물의 공통된 특징(예시1에서는 울음소리)을 상속받고, 각 클래스에 맞게 확장하고, 구체화하는 것이 가능해진다.

다형성과의 추상화

예시1에서 Cat 클래스와 Dog 클래스 내에 override 키워드가 사용된 것을 알 수 있다. 이전 포스트에서 다형성에 대해 이야기했는데, 다형성 역시 상속을 기반으로 한다. 다형성에 대한 자세한 설명은 아래 포스트를 참고하자.

2024.04.23 - [C# 기초] - [C#기초] 상속(Inheritance)과 다형성(Polymorphism)

 

추상화와 다형성은 객체지향 프로그래밍에서 매우 밀접한 관련이 있는 개념이라고 한다. 코드의 재사용성 측면에서 둘 다 좋은 효율을 보여주는 거 같다.

 

추상화가 공통된 특성을 정의하는 것이라면, 다형성은 이를 바탕으로 하위 클래스들이 동일한 이름의 메서드를 사용할 수 있게 해 준다. 예시1에서는 MakeSound라는 메서드를 오버라이딩하여 모든 클래스에서 같은 이름으로 사용하고 있다.

반응형
반응형

상속(Inheritance)이란?

상속은 한 클래스의 필드(Field)와 메서드(Method)를 다른 클래스로 전달하는 개념이다. 보통 상속하는 클래스를 '부모 클래스', 상속 받는 클래스를 '자식 클래스'라고 한다.

Tenor

 

실생활에 있을 법한 예를 들어 보자. 엄마가 중학생 아들에게 학교 생활을 하며 사용하라고 체크카드를 줬다. 이 체크카드는 엄마 명의의 계좌에 연결되어 있다. 아들은 엄마 명의의 계좌에 있는 돈을 마음대로 사용할 수 있다. 이를 코드로 한 번 구현해 보자.

예시1:

class MotherAccount // base class 부모 클래스  
{
    public string accountName = "신한";
    public void Payment() // 
    {
        Console.WriteLine("0000원이 결제 되었습니다.");
    }
}

class Son : MotherAccount // Son 클래스가 MotherAccount 클래스를 상속  
{
    public string name = "영수";
}

class Program
{
    static void Main(string[] args)
    {
        // mySon 객체 생성  
        Son mySon = new Son();

        // Payment() 메서드 호출
        mySon.Payment();

        Console.WriteLine(mySon.name + "가 " + mySon.accountName + "체크카드를 사용했어요.");
    }
}

예시1의 결과

MotherAccount라는 클래스를 상속한 Son 클래스가 MotherAccount의 멤버에 접근할 수 있게 되었다. 상속을 하는 방법은 클래스를 선언할 때 다음과 같이 : 기호를 사용하면 된다. class Son : MotherAccount

sealed 키워드

다른 클래스가 특정 클래스에 상속되는 것을 원하지 않을 때는 sealed키워드를 선언부 앞에 추가하면 된다.
예시2:

sealed class Vehicle
{

}

class Car : Vehicle{

}

상속하려고 하는 경우 다음과 같은 에러 메시지가 발생할 것이다.

다형성(Polymorphism)이란 무엇일까?

다형성은 "다양한 형태"를 의미하는 말이다. 객체 지향 프로그래밍에서 중요한 개념인데, 같은 이름의 메서드가 다른 클래스에 의해 다르게 구현될 수 있는 능력을 의미한다. 아래 예시를 살펴보자.

예시3:

class Animal  // 부모클래스
{
  public virtual void animalSound() 
  {
    Console.WriteLine("The animal makes a sound");
  }
}

class Pig : Animal  // 자식클래스 Pig
{
  public override void animalSound() 
  {
    Console.WriteLine("The pig says: wee wee");
  }
}

class Dog : Animal  // 자식 클래스 Dog
{
  public override void animalSound() 
  {
    Console.WriteLine("The dog says: bow wow");
  }
}

class Program 
{
  static void Main(string[] args) 
  {
    Animal myAnimal = new Animal();  // Animal 객체 생성
    Animal myPig = new Pig();  // Pig 객체 생성
    Animal myDog = new Dog();  // Dog 객체 생성

    myAnimal.animalSound();
    myPig.animalSound();
    myDog.animalSound();
  }
}

상속을 통해 PigDog 클래스는 Animal 클래스의 특성을 상속받는다. 이처럼 새로운 동물 클래스를 추가할 때 기존 코드의 수정 없이 새로운 클래스를 만들어 확장할 수 있다.

 

앞서 다형성은 같은 이름의 메서드가 다른 클래스에 의해 다르게 구현될 수 있음을 의미한다고 했다. 예시3에서 살펴보면 PigDog 클래스는 Animal 클래스 내의 animalSound()라는 메서드와 동일한 이름을 가진 메서드를 클래스 내에 선언하고 있다.

 

100마리의 동물 클래스를 추가한다고 했을 때 다형성을 사용하지 않는다고 하면 어떻게 될까? animalSound1, animalSound2… 이런 형태로 메서드 이름을 각자 다르게 가져가야 할 것이다.

virtual과 override 키워드

다형성은 virtual 키워드와 override 키워드를 사용해 구현할 수 있다.

예시4:

class Animal  // 부모클래스
{
  public virtual void animalSound() 
  {
    Console.WriteLine("The animal makes a sound");
  }
}

부모 클래스에서 virtual 키워드가 붙은 메서드는 하위 클래스에서 재정의 할 수 있게 된다.

class Dog : Animal  // 자식 클래스 Dog
{
  public override void animalSound() 
  {
    Console.WriteLine("The dog says: bow wow");
  }
}

Dog 클래스 내의 animalSound()메서드는 Animal 클래스 내의 메서드와 이름이 같다. 하지만 메서드 내의 코드는 다르게 작성된 것을 확인할 수 있다. 이런 형태로 부모 클래스의 메서드를 재정의(Override) 할 수 있다.

반응형
반응형

프로퍼티 및 캡슐화(Property and Encapsulation)

캡슐화(Encapsulation)

객체 지향 프로그래밍의 주요 개념 중 하나이다. 데이터와 그 데이터를 조작하는 메서드를 하나로 묶어 “민감한” 데이터가 사용자에게 숨겨지도록 하는 것이다.

이전 포스트에서 접근 제한자의 사용 방법에 대해 다뤘다. private라는 접근 제한자를 사용함으로써 외부 클래스에서 접근을 제한했는데, 이와 같은 일련의 과정을 캡슐화(Encapsulation)라고 할 수 있을 것이다.

캡슐화는 다음과 같은 과정을 수행해야 한다.

  • 필드/변수를 private로 선언
  • private 필드에 액세스하고, 업데이트하기 위해 get, set 메소드를 사용

프로퍼티(Property)

앞서 설명한 거처럼 private는 동일한 클래스 내에서만 접근할 수 있다. 유니티로 개발을 하다 보면 때때로 외부 클래스에서 private 멤버에 접근해야 하는 경우가 있다. 이 것은 프로퍼티 통해 가능하다.

프로퍼티는 get, set 두 가지 메서드를 사용해 public으로 선언했을 때처럼 private 필드의 값을 읽고, 쓰고, 계산하는 멤버다. 멤버는 클래스 내에 있는 변수, 메서드 등을 멤버라고 한다.

예시 1:

class Person
{
  private string name; // 필드

  public string Name   // property
  {
    get { return name; }   // get 메소드
    set { name = value; }  // set 메소드
  }
}

Name 프로퍼티는 name 필드와 연결된다. 프로퍼티의 첫 글자는 대문자이고, 필드와 프로퍼티의 이름은 같게 하는 것이 권장된다.

get 메서드는 name 필드의 값을 반환한다. set 메소드는 name 필드에 값을 할당한다. 예시 1에서 value 키워드는 name 필드에 할당하는 값을 의미한다. 예시 2에서 조금 더 자세히 살펴보도록 하자.

예시 2:

class Person
{
  private string name; // 필드
  public string Name   // 프로퍼티
  {
    get { return name; }
    set { name = value; }
  }
}

class Program
{
  static void Main(string[] args)
  {
    Person myObj = new Person(); // 객체 생성
    myObj.Name = "Liam";
    Console.WriteLine(myObj.Name);
  }
}

//출력 : Liam

Program이라는 외부 클래스에서 Person 클래스에 대한 myObj라는 객체를 생성했다. 그리고 myObj객체에 Name 프로퍼티를 사용하여 name이라는 필드에 Liam이라는 값을 할당했다.

프로퍼티를 사용했기 때문에 private로 선언되었지만 외부 클래스에서 접근하여 필드의 값까지 수정할 수 있게 된 것이다.

자동 프로퍼티(Automatic Property)

C#에서는 단축/자동 프로퍼티를 사용하는 방법도 제공한다. 속성에 대한 필드를 정의할 필요가 없는 경우에는 예시 3에서 보는 것처럼 단축해서 코드를 작성할 수 있다.

예시 3:

class Person
{
  public string Name  // 프로퍼티
  { get; set; }
}

class Program
{
  static void Main(string[] args)
  {
    Person myObj = new Person();
    myObj.Name = "Liam";
    Console.WriteLine(myObj.Name);
  }
}

//출력 Liam

예시 2예시 3은 기능적으로는 동일하다고 불 수 있다. 그러나 내부적으로는 다르게 구현되어 있다. 예시 2에서는 name필드에 Liam이라는 값을 할당하지만, 예시 3에서는 백킹 필드(Backing Filed)라는 비공개 변수에 값이 할당된다.

백킹 필드는 프로퍼티에 의해 읽거나 쓰이는 실제 데이터를 저장하는 데 사용되는 비공개 변수다. 프로퍼티의 내부에서만 사용되며, 클래스 외부에서는 백킹 필드로 직접적으로 접근할 수는 없다.

그렇다면 여러 객체를 생성하는 경우에는 어떻게 될까? 당연하게도 객체마다 프로퍼티를 사용했을 때 각각의 백킹 필드가 할당된다.

부가적으로 설명하자면 객체를 생성하면 컴퓨터 메모리에 객체의 데이터와 메서드를 저장하기 위한 메모리 블록이 할당된다. 이 메모리 블록 내에서 객체와 관련된 모든 멤버에 대한 메모리가 할당되는 것이고, 고유 주소를 갖게 된다. 백킹 필드 객체가 할당받은 메모리 블록 내에 데이터를 저장하는 것이다.

총정리

캡슐화와 프로퍼티는 객체 지향 프로그래밍에서 코드의 가독성, 유지 보수성, 재사용성, 보안 등을 향상하는데 중요한 역할을 한다.

캡슐화로 외부에서 민감한 데이터에 접근하는 것을 막음과 동시에, 개발자가 프로퍼티를 통해 접근할 수 있게 한다.

반응형
반응형

접근 제한자(Access Modifier)란 무엇인가?

우리나라에서는 접근 제한자 또는 접근 한정자라고 부르는 거 같다. 정리해 보자면 접근 제한자는 클래스, 필드, 메소드 및 속성에 대한 접근 수준을 설정하는 데 사용된다. 아래 예시를 살펴보자.

예시1:

public string color;

예시1에서 public을 접근 제한자라고 한다. 가장 앞

C#의 접근 제한자

접근 제한자 설명
public 외부 클래스를 포함한 모든 클래스에서 접근을 허용함.
private 동일한 클래스 내에서만 접근을 허용함.
protected 동일한 클래스 내에서 또는 해당 클래스에서 상속된 클래스에서 접근을 허용함.
internal 자체 어셈블리 내에서만 접근을 허용함. 다른 어셈블리에서는 없음.

 

private protectedprotected internal처럼 조합하여 사용하는 경우도 있다. protectedinternal에 대해서는 나도 사용해 본 적이 없다. 추후에 다루게 된다면 다시 한번 정리해 보도록 하겠다.

Private Modifier(비공개 제한자)

private를 사용하여 필드를 선언하면 동일한 클래스 내에서만 접근할 수 있다.

예시2:

class Car
{
        private string model = "Mustang";

      static void Main(string[] args)
    {
            Car myObj = new Car();
        Console.WriteLine(myObj.model);
    }
}

// 출력 : Mustang

예시3:

class Car
{
  private string model = "Mustang";
}

class Program
{
  static void Main(string[] args)
  {
    Car myObj = new Car();
    Console.WriteLine(myObj.model);
  }
}

model 변수는 Car 클래스 내부에서 private로 선언되었다. 그런데 Program 클래스에서 접근을 하려고 하는 경우 다음과 같은 에러가 발생한다.

private를 사용했기 때문에 외부 클래스에서 접근할 수 없다.

접근 제한자를 사용하여 클래스와 멤버의 접근을 제어함으로써 코드의 안정성을 높일 수 있다.

반응형
반응형

클래스 생성자 🛠️

via GIPHY

 

생성자는 객체를 초기화하는 데 사용되는 특수 메서드이다. 생성자의 장점은 클래스의 객체가 생성될 때 호출된다는 것이다. 필드의 초기 값을 설정하는 데 사용할 수 있다.

예시1:

// Car 클래스 생성
class Car
{
  public string model;  // 필드 생성(클래스 내의 변수 == 필드)

  // Car 클래스에 대한 클래스 생성자 생성
  public Car()
  {
    model = "Mustang"; // model의 초기값 설
  }

  static void Main(string[] args)
  {
    Car Ford = new Car();  // Car 클래스의 객체를 생성(생성자를 호출)
    Console.WriteLine(Ford.model); 
  }
}

// 출 "Mustang"

1. 생성자를 생성할 때 알아두면 좋은 점

  1. 생성자 이름은 클래스 이름과 일치해야한다.
  2. 클래스 생성자는 반환 유형(예: void 또는 int)을 가질 수 없다.
  3. 객체가 생성될 때 생성자가 호출된다.
  4. C#에서는 클래스 생성자를 직접 만들지 않으면 자동으로 생성함. 그러나 초기값 설정을 할 수 없음.

2. 생성자 매개변수(Constructor Parameters)

생성자는 필드를 초기화하는 데 사용되는 매개변수를 사용할 수도 있다.

예시2:

class Car
{
  public string model;

  // 매개변수와 함께 Car클래스에 대한 생성자 생성
  public Car(string modelName)
  {
    model = modelName;
  }

  static void Main(string[] args)
  {
    Car Ford = new Car("Mustang");
    Console.WriteLine(Ford.model);
  }
}

// 출력 "Mustang"

예시2 를 보면 매개변수를 추가해서 Ford라는 객체를 생성하면서 초기값을 지정한다. 예시3 을 조금 더 살펴보자.

예시3:

class Car
{
  public string model;
  public string color;
  public int year;

  // 여러가지 매개변수를 추가
  public Car(string modelName, string modelColor, int modelYear)
  {
    model = modelName;
    color = modelColor;
    year = modelYear;
  }

  static void Main(string[] args)
  {
    Car Ford = new Car("Mustang", "Red", 1969);
    Console.WriteLine(Ford.color + " " + Ford.year + " " + Ford.model);
  }
}

// 출력: Red 1969 Mustang

Ford뿐만 아니라 Hyundai, KIA라는 객체를 생성한다고 했을 때, 매개변수의 값만 바꿔주면 각 객체마다 설정을 해줄 수 있다. 예시4와 예시5를 살펴보자. 생성자는 코드를 간결하게 바꿔주는데 도움을 준다.

예시4:

//생성자를 사용하지 않았을 때

class Car
{
    public string model;
    public string color;
    public int year;

  static void Main(string[] args)
  {
    Car Ford = new Car();
    Ford.model = "Mustang";
    Ford.color = "red";
    Ford.year = 1969;

    Car Hyundai = new Car();
    Hyundai.model = "Avante";
    Hyundai.color = "white";
    Hyundai.year = 2005;

    Car Kia = new Car();
    Kia.model = "Ray";
    Kia.color = "blue";
    Kia.year = 2017;

    Console.WriteLine(Ford.model);
    Console.WriteLine(Hyundai.model);
    Console.WriteLine(Kia.model);
  }
}

예시5:

//생성자를 사용했을 때

class Car
{
    public string model;
    public string color;
    public int year;

    public Car(string modelName, string modelColor, int modelYear)
  {
    model = modelName;
    color = modelColor;
    year = modelYear;
  }

  static void Main(string[] args)
  {
    Car Ford = new Car("Mustang", "Red", 1969);
    Car Hyundai = new Car("Avante", "White", 2005);
    Car Kia = new Car("Ray", "blue", 2017);

    Console.WriteLine(Ford.model);
    Console.WriteLine(Hyundai.model);
    Console.WriteLine(Kia.model);
  }
}

나같은 초보자가 얼핏보기엔 코드의 길이면에서는 큰 차이가 없어보인다고 생각할지도 모르겠다. 하지만 생성자의 사용은 다음과 같은 장점을 가진다.

  1. 코드의 간결성 :

객체를 인스턴스화할 때 필요한 모든 초기화 작업을 한 곳에서 처리할 수 있다. 예시4에서는 3개의 객체를 생성하는데 15줄이 사용됐다. 객체를 30개 이상 생성한다면 코드의 양은 150줄이 될 것이다. 생성자를 사용한다면 30줄이다.

 

   2. 유지보수성 :

 

예를 들어 Car 클래스의 model이라는 필드의 이름을 수정할 일이 생겼다고 가정해보자. 예시4경우라면 코드를 하나하나 수정해야한다. 하지만 예시5의 경우에는 생성자 내부의 코드만 수정해주면 된다.

 

생활코딩 강의를 들었을 때 개발을 하면서 ‘극단적인 가정’을 해보면 효율성을 개선하는데 도움이 된다는 내용이 있었다. 1,2개 였을 때는 잘 안 보이던 비효율이, 5천개라면? 1만개라면? 이라는 가정에서는 명백하게 드러나는 경우가 있다.

반응형
반응형

객체지향이란 무엇일까?

tenor

객체 지향 프로그래밍(Object-Oriented Programming)은 마치 레고 블록을 조립하듯이, 프로그래밍을 하는 방식이다. 레고 블록처럼, 객체지향 프로그래밍에서는 **클래스(Class)**라는 틀을 만들고, 그 틀을 사용해서 **객체(Object)**라는 작은 부품들을 만든다. 이렇게 만들어진 객체들은 각자의 정보(데이터)와 할 수 있는 일(메소드)를 가지고 있다.

  • 클래스는 레시피: 클래스는 마치 요리 레시피와 같다. 레시피대로 음식을 만들면, 똑같은 맛의 음식을 여러 번 만들 수 있는 거처럼, 클래스도 재사용이 가능하다.
  • 객체는 요리된 음식: 객체는 레시피대로 만들어진 음식이다. 각각의 객체는 독립적으로 존재하면서, 각자의 특성을 가지고 있다.
  • 코드 재사용: 객체지향 프로그래밍은 레고 블록을 여러 번 재사용할 수 있듯이, 코드를 재사용할 수 있게 해줘서, 더 적은 코드로 더 많은 일을 할 수 있다.
  • 유지보수 용이: 레고 블록을 쉽게 분리하고 다시 조립할 수 있는거처럼, 객체지향 프로그래밍 역시 코드를 쉽게 변경하고 관리하기가 좋다.

이렇게 객체지향 프로그래밍은 복잡한 프로그램도 깔끔하고 체계적으로 만들 수 있도록 돕는다. 마치 레고 블록으로 큰 성을 만들 듯이, 작은 부품들을 조합해서 멋진 프로그램을 만들 수 있다.

절차적 프로그래밍과의 차이는?

앞서 비유한 바와 같이 요리와 레고에 빗대어 객체 지향 프로그래밍(OOP)과 절차적 프로그래밍의 차이를 정리해본다.

  • 객체 지향 프로그래밍 (OOP):
    • 장난감 상자: 상자 안에 여러 가지 장난감이 있다고 하자. 각각의 장난감은 이름과 색깔, 기능을 가지고 있다. 예를 들어, '레고 블록’은 쌓을 수 있고, '자동차 장난감’은 굴러간다. 이 장난감들은 각자의 역할을 가지고 있어서, 우리가 상상하는 세계를 만들 수 있다.
    • 역할 분담: 장난감들을 활용해 여러 가지 놀이를 할 수 있다. 레고 블록으로 집을 지으면, 자동차 장난감은 그 집 앞을 지나가고, 인형은 그 집에서 사는 거처럼 말이다.
  • 절차적 프로그래밍:
    • 요리 레시피: 요리할 때 레시피를 따라 하나씩 재료를 준비하고, 조리 순서대로 음식을 만든다. 모든 단계가 순서대로 진행된다.
    • 단계별 작업: 먼저 쌀을 씻고, 물을 붓고, 밥을 짓는 것처럼, 모든 작업이 차례대로 이루어진다. 각 단계는 전체 요리 과정의 일부분이다.

 

이렇게 객체지향 프로그래밍은 여러 가지 재료를 만들고, 그 재료를 조합해 프로그램을 만들어 가는 방식이다. 장난감 상자 안의 장난감들이 각자의 역할을 가지고 놀이의 재료가 되는 거처럼말이다.

 

여기서 내가 만든 놀이를 프로그램이라고 할 수 있을 것이고, 장난감 상자를 클래스(Class), 장난감들을 객체(Object)라고 볼 수 있다. 클래스와 객체에 대해서는 추후에 다시 한 번 정리하도록 하겠다.

 

반면, 절차적 프로그래밍은 요리 레시피를 따라 순서대로 작업을 수행하는 것과 비슷하다. 이름에서 직관적으로 알 수 있듯이 단계적으로 진행되는 것이 절차적 프로그래밍의 특징이다.

 

C# == 객체지향 프로그래밍 언어

C#은 Microsoft가 개발한 객체 지향 프로그래밍 언어이다. 유니티로 게임 개발을 할 때 스크립팅 언어로 사용된다. 이 외에도 Windows 플랫폼에서 개발된 소프트웨어를 위해 사용됩니다. 국내보다는 해외에서 더 많이 사용된다고 한다.

 

유니티는 왜 c#을 채택했나?

게임 개발에 자주 사용되는 C++와 비교할 때, 한 가지 주요한 이유는 배우기 쉽고 직관적인 구조를 가진다는 점이다. 유니티의 슬로건은 '게임 개발의 민주화'로, 초보자부터 전문가까지 모두에게 적합한 선택지를 제공한다.

ps. 유니티 엔진은 C++로 개발되었다.

 

이 외에도 여러가지 이유가 있지만 성능면에서 효율적인 면에서 C#이 좋은 평가를 받고 있기 때문이라는 의견도 있다.


 

반응형
반응형

클래스와 객체

C#의 모든 것은 해당 속성 및 메서드와 함께 클래스 및 객체와 연결된다.

자동차를 예를 들어보자. 자동차는 클래스와 같다. 무게, 색상, 브랜드 등과 같은 것을 속성, 구동 및 브레이크와 같은 것들을 메서드라고 비유할 수 있다. 폭스바겐 자동차를 만들고 싶다면 자동차 클래스의 속성과 메서드를 사용하여 만들 수 있다. 이때 만들어진 폭스바겐 자동차가 객체다.

다음은 필수는 아니지만 알아두면 도움 될만한 내용이다.

  1. C# 파일과 클래스 이름을 동일하게 가져가는 경우가 많다. 코드를 체계적으로 정리하기 위함이다.
  2. 클래스 이름을 지정할 때 첫 글자는 대문자로 한다.
  3. 변수가 클래스 내에서 직접 선언되면 이를 ‘필드(field)’ 또는 ‘속성(attribute)’라고 한다.

객체 만들기

예시:

class Car // 자동차 클래스
{
  string color = "red"; // 필드

  static void Main(string[] args)
  {
    Car myObj = new Car(); // myObj라는 객체 생성
    Console.WriteLine(myObj.color);
  }
}

클래스(myObj.color) 내의 변수/필드에 접근하려면 점 구문(.)을 사용한다.

다중 객체

예시:

class Car
{
  string color = "red";
  static void Main(string[] args)
  {
    Car myObj1 = new Car();
    Car myObj2 = new Car();
    Console.WriteLine(myObj1.color);
    Console.WriteLine(myObj2.color);
  }
}

한 클래스에 여러 객체를 만드는 것도 가능하다.

여러 클래스 사용1

클래스의 객체를 생성하고 다른 클래스에서 접근할 수도 있다. 이는 더 나은 클래스 구성을 위해 자주 사용된다고 한다.

Car.cs

class Car
{
  public string color = "red"; // 접근제한자 public 사용
}

Program.cs

class Program
{
  static void Main(string[] args)
  {
    Car myObj = new Car();
    Console.WriteLine(myObj.color);
  }
}

Car.cs스크립트를 살펴보면 string앞에 public이라는 키워드가 사용되었다. 이 키워드는 다른 클래스에서의 접근을 허락한다. 이를 접근 제한자 또는 접근 한정자라고 부르는데, public이 외에도 private, protected 등이 있다.

클래스 멤버

앞서 클래스 내에서 선언된 변수를 필드 또는 속성이라고 부른다고 하였다. 클래스 멤버는 클래스 내에 있는 필드 또는 메서드들을 뜻하는 말이다.

예시:

class MyClass
{
  // 클래스 멤버
  string color = "red";        // 필드
  int maxSpeed = 200;          // 필드
  public void fullThrottle()   // 메소드
  {
    Console.WriteLine("The car is going as fast as it can!");
  }
}

필드를 비워 두고 객체를 생성할 때 수정할 수도 있다. 이는 한 클래스에서 여러 객체를 사용할 때 특히 유용하다.

예시:

class Car // Car클래스
{
    // 클래스 멤버
  string model; // 비어 있는 상태로 선언된 필드
  string color;
  int year;

  static void Main(string[] args)
  {
    Car Ford = new Car(); // Car클래스에서 Ford 객체 생성
    Ford.model = "Mustang"; 
    Ford.color = "red";
    Ford.year = 1969;

    Car Opel = new Car(); // Car클래스에서 Opel 객체 생성
    Opel.model = "Astra";
    Opel.color = "white";
    Opel.year = 2005;

    Console.WriteLine(Ford.model);
    Console.WriteLine(Opel.model);
  }
}

프로그램에서 사용되는 데이터는 실행될 때 컴퓨터 메모리(RAM)에 저장 공간을 할당받는다. 클래스에 객체가 만들어질 때도 각각의 객체마다 별도로 저장 공간을 할당 받는다.

즉, Ford.model = “Mustang”;이라는 코드가 실행되면 Ford라는 객체의 modelMustang이라는 값이 저장되는 것이지 Car 클래스의 model 필드에 Mustang이라는 값이 저장되지 않는다.

ps. 단 클래스를 정의하는 것만으로는 저장공간이 할당되지 않는다고 한다.

객체 메서드

메서드는 일반적으로 클래스에 속하며, 객체의 동작 방식을 정의한다. 필드와 마찬가지로 점 구문(.)을 사용하여 메서드에 접근 할 수 있다. 그리고 메서드를 호출할 때는 이름 뒤에 두 개의 괄호()와 세미콜론을 사용한다.

예시:

class Car
{
  string color;                 // field
  int maxSpeed;                 // field
  public void fullThrottle()    // method, public 접근 제한자를 사용했다는 점에 유의하자.
  {
    Console.WriteLine("The car is going as fast as it can!");
  }

  static void Main(string[] args)
  {
    Car myObj = new Car();
    myObj.fullThrottle();  // Call the method
  }
}

앞의 예시에서 fullThrottle 메소드 앞에 public 키워드가 붙었다. 그 이유는 public(공개) 메소드는 객체를 생성한 후에만 호출이 가능하고, 객체의 상태를 변경하거나 기능을 실행할 수 있다.

ps.static(정적) 메서드의 경우에는 클래스의 객체를 만들지 않고도 접근할 수 있지만, 클래스의 필드에 직접 접근하여 값을 수정할 수 없다.

반응형
반응형

반복문(Loop)

While문

루프는 지정된 조건에 도달하는 한 코드 블록을 실행할 수 있다.

지정된 조건이 True인 동안 코드블록을 반복 실행한다.

 

주의 할 점은 조건 안의 변수값을 증가시켜야 한다.

그렇지 않으면 무한 루프에 빠진다.

 

예시:

int i = 0;
while (i < 5)
{
  Console.WriteLine(i);
  i++;
}

Do/While문

do/while 루프는 while 루프의 변형이다.

이 루프는 조건이 true인지 확인하기 전에 코드 블록을 한 번 실행한 다음 조건이 true인 동안 루프를 반복한다.

조건이 거짓이더라도 코드가 한 번은 실행된다.

 

예시:

int i = 0;
do
{
  Console.WriteLine(i);
  i++;
}
while (i < 5);

For문

코드 블록을 반복하려는 횟수를 정확히 알고 있으면 while 루프 대신 for 루프를 사용한다.

예시:

for (statement 1; statement 2; statement 3)
{
// code block to be executed}

Statement 1 코드 블록이 실행되기 전에 (한 번) 실행됩니다.

Statement 2 코드 블록 실행 조건을 정의합니다.

Statement 3 코드 블록이 실행된 후에 (매번) 실행됩니다.

  • 중첩루프

루프를 다른 루프 안에 배치하는 것도 가능하다.

이것을 중첩 루프라고 한다.

"내부 루프"는 "외부 루프"가 반복될 때마다 한 번씩 실행된다.

 

예시:

// Outer loop
for (int i = 1; i <= 2; ++i)
{
  Console.WriteLine("Outer: " + i);  // Executes 2 times

  // Inner loop
  for (int j = 1; j <= 3; j++)
  {
    Console.WriteLine(" Inner: " + j); // Executes 6 times (2 * 3)
  }
}

// Outer: 1
//  Inner: 1
//  Inner: 2
//  Inner: 3
// Outer: 2
//  Inner: 1
//  Inner: 2
//  Inner: 3

Foreach문

배열의 요소를 반복하는 데만 사용되는 foreach 루프도 있다.

예시:

foreach (type variableName in arrayName)
{
// code block to be executed}
}

// 구체적인 예시

string[] cars = {"Volvo", "BMW", "Ford", "Mazda"};
foreach (string i in cars) 
{
  Console.WriteLine(i);
}

// Volvo
// BMW
// Ford
// Mazda

Break / Continue

  1. Break : switch문에서 사용되기도 하고, 반복문에서 벗어날 때 사용하기도 한다.
  2. Continue : 지정된 조건이 발생할 경우 반복을 건너뛰고, 다음 루프로 넘어간다.

Break문 예시:

int i = 0;
while (i < 10)
{
  Console.WriteLine(i);
  i++;
  if (i == 4)
  {
    break;
  }
}

// 0
// 1
// 2
// 3

Continue문 예시:

int i = 0;
while (i < 10)
{
  if (i == 4)
  {
    i++;
    continue;
  }
  Console.WriteLine(i);
  i++;
}

// 0
// 1
// 2
// 3
// 5
// 6
// 7
// 8
// 9

배열 (Array)

배열은 각 값에 대해 별도의 변수를 선언하는 대신 단일 변수타입에 여러 값을 저장하는 데 사용

// 빈 배열 선언

string[] cars;

// 값이 삽입된 배열 선언

string[] cars = {"Volvo", "BMW", "Ford", "Mazda"}; // 문자열
int[] myNum = {10, 20, 30, 40}; // 정수

인덱스 번호를 참조하여 배열 요소에 액세스 할 수 있다.

string[] cars = {"Volvo", "BMW", "Ford", "Mazda"};
Console.WriteLine(cars[0]);

// Volvo

배열 요소 바꾸기:

string[] cars = {"Volvo", "BMW", "Ford", "Mazda"};
cars[0] = "Opel";
Console.WriteLine(cars[0]);

// Opel

배열 길이 구하기:

string[] cars = {"Volvo", "BMW", "Ford", "Mazda"};
Console.WriteLine(cars.Length);

// 4

배열을 선언하는 여러가지 방법:

// 배열의 크기만 생성
string[] cars = new string[4];

// 배열의 크기와 함께 값을 할당
string[] cars = new string[4] {"Volvo", "BMW", "Ford", "Mazda"};

// 배열의 크기는 지정하지 않고, 값만 할당
string[] cars = new string[] {"Volvo", "BMW", "Ford", "Mazda"};

// 배열의 크기는 지정하지 않고, 값만 할당
string[] cars = {"Volvo", "BMW", "Ford", "Mazda"};

배열을 선언하고 나중에 초기화하는 경우 new 키워드를 사용해야 한다.

배열을 초기화한다는 것은 배열에 처음으로 값을 할당하는 과정을 의미한다.

new 키워드를 사용하는 경우 기존의 배열은 사라지고, 새로운 배열이 할당된다.

배열의 정렬

배열은 알파벳순이나 오름차순으로 정렬하는 Sort()와 같은 다양한 배열 메서드를 사용하여 정렬할 수 있다.

  1. Sort()
// Sort a string
string[] cars = {"Volvo", "BMW", "Ford", "Mazda"};
Array.Sort(cars);
foreach (string i in cars)
{
  Console.WriteLine(i);
}

// Sort an int
int[] myNumbers = {5, 1, 8, 9};
Array.Sort(myNumbers);
foreach (int i in myNumbers)
{
  Console.WriteLine(i);
}
  1. System.Linq

Min, Max 및 Sum과 같은 다른 유용한 배열 메서드는 System.Linq 네임스페이스에 있다.

using System;
using System.Linq;

namespace MyApplication
{
  class Program
  {
    static void Main(string[] args)
    {
      int[] myNumbers = {5, 1, 8, 9};
      Console.WriteLine(myNumbers.Max());  // returns the largest value
      Console.WriteLine(myNumbers.Min());  // returns the smallest value
      Console.WriteLine(myNumbers.Sum());  // returns the sum of elements
    }
  }
}

다차원 배열

  1. 2차원 배열

2D 배열을 만들려면 자체 중괄호 세트 내에 각 배열을 추가하고 대괄호 안에 쉼표(,)를 삽입한다.

예시:

int[,] numbers = { {1, 4, 2}, {3, 6, 8} };

단일 쉼표 [,]는 배열이 2차원임을 지정한다.

3차원 배열에는 int[,,]라는 두 개의 쉼표가 있다.

배열을 행과 열이 있는 테이블로 생각하면 쉽다.

출처 : w3school

  1. 2차원 배열에 엑세스하는 방법

예시:

int[,] numbers = { {1, 4, 2}, {3, 6, 8} };
Console.WriteLine(numbers[0, 2]);  // Outputs 2

배열 인덱스는 0부터 시작한다. [0]이 첫 번째 요소이다.

 

배열의 요소 변경:

int[,] numbers = { {1, 4, 2}, {3, 6, 8} };
numbers[0, 0] = 5;  // Change value to 5
Console.WriteLine(numbers[0, 0]); // Outputs 5 instead of 1

2차원 배열의 루프:

// Using foreach

int[,] numbers = { {1, 4, 2}, {3, 6, 8} };

foreach (int i in numbers)
{
  Console.WriteLine(i);
}

// Using for
int[,] numbers = { {1, 4, 2}, {3, 6, 8} };

for (int i = 0; i < numbers.GetLength(0); i++) 
{ 
  for (int j = 0; j < numbers.GetLength(1); j++) 
  { 
    Console.WriteLine(numbers[i, j]); 
  } 
}  

메소드

메소드는 호출될 때만 실행되는 코드 블록이다.

매개변수라고 하는 데이터를 메서드에 전달할 수 있다.

메소드는 특정 작업을 수행하는 데 사용되며 함수라고도 한다.

 

메소드를 사용하는 이유는 코드의 가독성을 높이고,

반복되는 코드를 재사용하기 위함이다.

 

C#에서는 메서드 이름을 대문자로 시작하는 것이 권장된다.

예시:

class Program // 클래스 이름
{
  static void MyMethod() 
  {
    // code to be executed
  }
}
반응형

+ Recent posts