외로운 Nova의 작업실

c# 언어 공부 - 3(클래스,static,복사,this, 접근한정자) 본문

Programming/C#

c# 언어 공부 - 3(클래스,static,복사,this, 접근한정자)

Nova_ 2022. 5. 2. 01:49

안녕하세요. 오늘은 클래스에대해서 알아보겠습니다.

클래스란 자동차에 비유하면 자동차 설계도와 같습니다.

객체는 이 클래스(자동차 설계도)를 가지고 만든 자동차와 같습니다.

또한 객체는 인스턴스라고도 불립니다.

 

<클래스>

세상의 모든 물체를 객체로 바라보는 것이 객체지향 프로그래밍의 핵심입니다.

모든 객체에는 속성과 기능으로 이루어져있습니다.

예를들어 강아지로봇을 객체로 생각해본다면, 클래스(강아지로봇 설계도)에는 강아지 털의 색, 강아지 크기등이 속성으로 존재해야하고, 밥먹기, 짖기 등이 기능으로 존재해야한다고 생각 할 수 있습니다.

그럼, 강아지 로봇을 코드로 구현시켜보겠습니다.

   class DogRobot
    {
        public string DogColor; // 강아지 색 속성
        public int DogSize; //강아지크기 속성

        public void bark() //짖는 기능
        {
            Console.WriteLine("왈왈");
        }

        public void eat() //밥먹는 기능
        {
            Console.WriteLine("냠냠 배부르다");
        }
    }

이런식으로 강아지의 로봇의 클래스 구현이 가능합니다.

또한 클래스의 속성을 필드라고하고, 클래스의 기능을 메소드라고 말합니다.

 

<객체의 생성자와 종료자>

이렇게 강아지 로봇의 클래스(설계도)를 만들었다면 설계도대로 실체화된 객체를 만들어 봅시다.

먼저 코드부터 보시죠.

using System;

namespace StudyCSharp
{
    class MainApp
    {

        static void Main(string[] args)
        { 
            DogRobot kong = new DogRobot(); //클래스대로의 객체 생성

            kong.DogColor = "white"; //흰색
            kong.DogSize = 50; //50센치

            kong.bark(); //짖기 기능
            kong.eat(); //밥먹기 기능
        }

    }

    class DogRobot
    {
        public string DogColor; // 강아지 색 속성
        public int DogSize; //강아지크기 속성

        public void bark() //짖는 기능
        {
            Console.WriteLine("왈왈");
        }

        public void eat() //밥먹는 기능
        {
            Console.WriteLine("냠냠 배부르다");
        }
    }
}

메인 함수안에 있는 DogRobot kong = new DogRobot()은 실제 클래스대로 객체를 만들어 줍니다.

좌변의 DogRobot kong은 DogRobot 클래스를 담을 변수(주소를 담음) kong을 선언하는것입니다.

우변은 DogRobot()이라는 클래스의 생성자가 메모리 영역에 실제 클래스만큼의 메모리를 만들고 new 연산자가 그 메모리의 시작점을 반환시켜줍니다.

DogRobot() 생성자는 컴파일러가 자동적으로 클래스의 이름과 똑같이 자동으로 만들어 줍니다.

하지만 우리가 생성자를 직접 만들어서 매개변수를 할당하여 한번에 객체속성을 초기화 할 수 있습니다.

코드부터 보시죠.

using System;

namespace StudyCSharp
{
    class MainApp
    {

        static void Main(string[] args)
        { 
            DogRobot kong = new DogRobot("white", 50); //새롭게 정의한 생성자

            kong.bark(); //짖기 기능
            kong.eat(); //밥먹기 기능
        }

    }

    class DogRobot
    {
        public string DogColor; // 강아지 색 속성
        public int DogSize; //강아지크기 속성

        public DogRobot() //원래 생성자
        {
            DogColor = "";
            DogSize = 0;
        }

        public DogRobot(string Color, int Size) //우리가 정의하는 생성자
        {
            DogColor = Color;
            DogSize = Size;
        }
        public void bark() //짖는 기능
        {
            Console.WriteLine("왈왈");
        }

        public void eat() //밥먹는 기능
        {
            Console.WriteLine("냠냠 배부르다");
        }
    }
}

이런식으로 생성자를 재정의하여 매개변수로 한번에 속성을 초기화 할 수 있습니다.

단, 주의할점은 생성자를 재정의하면 컴파일러가 자동으로 해주던 매개변수없는 생성자는 우리가 직접 정의하지않으면 안만들어준다는 사실입니다.

그러면 실제 객체생성에서 DogRobot kong = new DongRobot()을 사용할 수 없게된다는 뜻입니다.

그러니 매개변수 없는 생성자를 사용하려면 꼭 다시 재 정의 해주셔야합니다.

 

이렇게 객체를 직접 생성 할 수 있지만, 직접 없앨 순 없습니다.

객체의 종료는 CLR(Common Language Runtime)의 가비지 컬렉터가 자동적으로 해주기 때문입니다.

다만 객체의 종료자는 ~객체이름()과 같습니다.

하지만 객체의 종료자는 클래스안에서 '정의'만 할 수 있지 실제로 '사용'은 안됩니다.

또한 종료자를 사용안하는 이유도 있습니다.

가비지 컬렉터가 우리보다 더 똑똑하게 객체를 수거해가기 때문입니다.

그래도 종료자를 직접 정의만 해보겠습니다.

using System;

namespace StudyCSharp
{
    class MainApp
    {

        static void Main(string[] args)
        { 
            DogRobot kong = new DogRobot("white", 50);

            kong.bark(); //짖기 기능
            kong.eat(); //밥먹기 기능
        }

    }

    class DogRobot
    {
        public string DogColor; // 강아지 색 속성
        public int DogSize; //강아지크기 속성

        public DogRobot() //원래 생성자
        {
            DogColor = "";
            DogSize = 0;
        }

        public DogRobot(string Color, int Size) //우리가 정의하는 생성자
        {
            DogColor = Color;
            DogSize = Size;
        }
        
        ~DogRobot() //종료자 구현
        {
            Console.WriteLine("안녕"); 
        }
        public void bark() //짖는 기능
        {
            Console.WriteLine("왈왈");
        }

        public void eat() //밥먹는 기능
        {
            Console.WriteLine("냠냠 배부르다");
        }
    }
}

종료자는 구현하지않고 가비지 컬렉터에게 맡기는 편이 좋습니다.

 

 

<정적(static)필드와 메소드>

정적필드와 정적 메소드는 클래스엔 있지만 객체(인스턴스)에겐 없는 것이라고 한번에 설명 할 수 있습니다.

예를 들어서 컴퓨터 설계도엔 세상의 컴퓨터의 갯수가 담겨져있는 변수와 컴퓨터를 만드는 공장을 찾는 법에대해서 적혀있지만 이는 실제 컴퓨터에겐 필요없는 변수와 메소드입니다.

그래서 클래스엔 있지만 객체(인스턴스)에겐 주지 않기위해 static을 만든 것입니다.

사용법을 위해 코드를 봅시다.

using System;

namespace StudyCSharp
{
    class MainApp
    {

        static void Main(string[] args)
        { 
            Computer Mac = new Computer();

            Mac.cpu = "i5";
            Mac.BootComputer();

            Computer.WorldCount++;
            Computer.FindFactory();


        }

    }

   class Computer
    {
        public static int WorldCount = 0; //세상의 컴퓨터의 갯수 - 실제 객체에겐 필요 없음

        public string cpu; //cpu 종류 - 실제 컴퓨터에게 필요
        
        public static void FindFactory() //컴퓨터를 만들 공장을 찾는법 - 실제 객체에겐 필요 없음
        {
            Console.WriteLine("01099999999로 전화해봐라");
        }

        public void BootComputer() //컴퓨터 시작 - 실제 컴퓨터에게 필요
        {
            Console.Write("컴퓨터를 시작합니다");
        }
    }
    
}

필드와 메소드 선언하기전 앞에 static을 붙여주면 객체에겐 생성이 안됩니다.

또한 static필드와 메소드는 클래스를 직접 호출해서 사용할 수 있습니다.

 

<객체의 복사>

객체를 복사할때는 얕은 복사와 깊은 복사가 있습니다.

이는 값에의한 매개변수전달과 참조에의한 매개변수전달과 비슷합니다.

복사할때 그 주소를 복사하는지, 아니면 값을 복사하는지가 주된 관건입니다.

먼저 코드부터 봅시다.

using System;

namespace StudyCSharp
{
    class MainApp
    {

        static void Main(string[] args)
        { 
            Computer Mac = new Computer();

            Mac.cpu = "i5";
            Mac.ShowCpu();
            
            Computer Window = new Computer();
            Window = Mac;
            Window.cpu = "i7";
            
            Window.ShowCpu(); //만약 참조에의한거면 Mac과 Window 둘다 i7으로 변환
            Mac.ShowCpu(); //만약 값에 의한거면 mac은 i7, window로 i5로 출력

            


        }

    }

   class Computer
    {
        public static int WorldCount = 0; //세상의 컴퓨터의 갯수 - 실제 객체에겐 필요 없음

        public string cpu; //cpu 종류 - 실제 컴퓨터에게 필요
        
        public static void FindFactory() //컴퓨터를 만들 공장을 찾는법 - 실제 객체에겐 필요 없음
        {
            Console.WriteLine("01099999999로 전화해봐라");
        }

        public void BootComputer() //컴퓨터 시작 - 실제 컴퓨터에게 필요
        {
            Console.Write("컴퓨터를 시작합니다");
        }

        public void ShowCpu()
        {
            Console.WriteLine($"{cpu}");
        }
    }
    
}

위의 결과는 아래와 같습니다.

참조에 의한 복사

결론은 객체 그 자체로 복사를 하면 참조에의한 복사라는 것입니다.

그결과 window의 값을 변경햇지만 mac까지 변경된것이지요.

그럼 값을 복사하려면 어떻게해야할까요?

따로 만들어진게 없어서 우리가 직접 구현해야합니다.

코드를 보시죠.

using System;

namespace StudyCSharp
{
    class MainApp
    {

        static void Main(string[] args)
        { 
            Computer Mac = new Computer();

            Mac.cpu = "i5";
            Mac.ShowCpu();

            Computer Window = Mac.copy();
            Window.cpu = "i7";
            
            Window.ShowCpu(); //만약 참조에의한거면 Mac과 Window 둘다 i7으로 변환
            Mac.ShowCpu(); //만약 값에 의한거면 mac은 i7, window로 i5로 출력

            


        }

    }

   class Computer
    {
        public static int WorldCount = 0; //세상의 컴퓨터의 갯수 - 실제 객체에겐 필요 없음

        public string cpu; //cpu 종류 - 실제 컴퓨터에게 필요
        
        public static void FindFactory() //컴퓨터를 만들 공장을 찾는법 - 실제 객체에겐 필요 없음
        {
            Console.WriteLine("01099999999로 전화해봐라");
        }

        public void BootComputer() //컴퓨터 시작 - 실제 컴퓨터에게 필요
        {
            Console.Write("컴퓨터를 시작합니다");
        }

        public void ShowCpu()
        {
            Console.WriteLine($"{cpu}");
        }

        public Computer copy()//객체를 따로 하나 만들어서 복사한후 리턴합니다.
        {
            Computer com = new Computer();
            com.cpu = this.cpu;

            return com;
        }
    }
    
}

위의 결과는 아래와 같습니다.

값에 의한 복사

값에 의한 복사로 구현이 완료된걸 볼 수 있습니다.

 

<this 나>

this 키워드는 변수 명이 똑같을때 구분하는 역할을 해줍니다.

먼저 코드부터 보시죠.

using System;

namespace StudyCSharp
{
    class MainApp
    {

        static void Main(string[] args)
        {
            Computer Mac = new Computer("i10");


        }

    }

   class Computer
    {
        public string cpu;

        public Computer()
        {
            cpu = "";
        }

        public Computer(string cpu)//매개변수 cpu와
        {
            this.cpu = cpu;//해당 클래스의 필드 cpu의 이름이 같지만 this로 구분해주고있다.
        }
    }
    
}

코드안의 주석처럼 cpu라는 변수는 총 두개 입니다. 매개변수로서의 cpu와 클래스의 필드로서의 cpu입니다.

이 두개를 구분해주는 것은 this라는 키워드입니다.

이 this는 클래스 안에서 사용이되는데, 그 클래스를 지칭하는 키워드입니다.

this를 사용하면 변수명이 같아도 잘 구분해주는 좋은 키워드입니다.

 

this의 활용방법은 하나 더 있습니다.

바로 중복 할 수 있는 오버로딩입니다.

먼저 그냥 오버로딩 코드입니다.

using System;

namespace StudyCSharp
{
    class MainApp
    {

        static void Main(string[] args)
        {


        }

    }

   class Computer
    {
        public string cpu;
        public int a, b, c;

        public Computer()
        {
            a = 1;
        }

        public Computer(int b)
        {
            a = 1;
            this.b = b;
        }
        public Computer(int b, int c)
        {
            a = 1;
            this.b = b;
            this.c = c;
        }
    }
    
}

위 코드를 아래와 같이 '간결하게'쓸 수 있습니다.

using System;

namespace StudyCSharp
{
    class MainApp
    {

        static void Main(string[] args)
        {
            Computer Window = new Computer(2, 3);

            Console.WriteLine($"{Window.a}, {Window.b}, {Window.c}");

        }

    }

    class Computer
    {
        public string cpu;
        public int a, b, c;

        public Computer()
        {
            a = 1;
        }

        public Computer(int b) : this()
        {
            this.b = b;
        }
        public Computer(int b, int c) : this(b)
        {
            this.c = c;
        }
    }

}

아래는 결과 화면입니다.

this를 활용한 오버로딩

 

<접근 한정자>

선풍기를 만들때 버튼 2개와 다이얼 2개면 충분합니다.

시작 버튼, 종료버튼, 세기 다이얼, 타이머 다이얼 이런식으로 말이죠.

하지만 실제 선풍기 안에는 수많은 기능들이 있습니다.

예를들어 시작 버튼이 눌리면 전기를 모터로 보내는 기능, 모터에 전기가 통하면 모터가 돌아가는 기능들이 있습니다.

이렇게 수많은 기능들이 있지만 사용자가(객체가)쓸 수 있는건 딱 4가지 기능밖에 없습니다.

이를 구현하는데 필요한 것이 접근 한정자입니다.

아래는 접근한정자의 종류입니다.

public 클래스 내부/외부 모든 곳에서 접근 할 수 있습니다.
protected 클래스 외부에서는 접근할 수 없지만 파생 클래스에서는 접근이 가능합니다.
private 클래스내부에서만 접근이 가능합니다.
internal 같은 어셈블리 코드에 있는 코드에서만 public으로 접근할 수 있습니다.
protected internal 같은 어셈블리에 있는 코드에서만 protected로 접근 할 수 있습니다.
private protected 같은 어셈블리에 있는 클래스에서 상속받은 클래스 내부에서만 접근이 가능합니다.

접근 한정자와 static의 다른점은 static은 클래스는 가지지만 객체는 가지지 못하는 것이고, 접근한정자는 클래스와 객체 둘다 가지지만 객체가 사용하지 못한다는 점입니다.

코드를 봅시다.

using System;

namespace StudyCSharp
{
    class MainApp
    {

        static void Main(string[] args)
        {
            
            Computer Mac = new Computer();
			
            //Mac.cpu = "i7" 불가
            Mac.ChangeCpu("i7");
        }

    }

    class Computer
    {
        private string cpu; //직접 손못댐
        
        public void ChangeCpu(string cpu) //메소드를 통해서 cpu접근 가능
        {
            this.cpu = cpu;
        }

    }

}

위와 같은 코드로 사용이됩니다.

주석에 있는 것처럼 cpu는 private이므로 직접 Mac.cpu로 접근이 불가합니다.

 

다음시간에는 상속부터 배워보도록 하겠습니다.

Comments