클래스와 객체
I. OOP의 특징
객체 지향 프로그래밍 (OOP)
객체 지향 프로그래밍 (Object-Oriented Programming) : 컴퓨터 프로그램을 여러 명령어의 목록으로 보지 않고 여러 개의 독립된 객체들의 집합으로 파악하는 프로그래밍 기법. I. 객체, 클래스, 인스턴스
temp-franc.tistory.com
!! 정리
OOP
: 컴퓨터 프로그램을 여러 개의 독립된 객체들의 집합으로 파악하는 프로그래밍 기법.
: 객체들이 서로 유기적으로 상호작용하여 기능을 구현한다.
1. 캡슐화
: 각각의 필요한 기능을 구현하고, 기능한 구현을 또 그룹화할 수 있음.
: 그룹화를 통해 외부의 접근을 제어하고 데이터를 보호한다.
2. 상속
: 기존의 클래스를 확장해 새로운 클래스를 만들 수 있음.
: 새로운 클래스에서도 해당 클래스의 속성과 기능을 재사용하거나 확장할 수 있음.
3. 다형성
: 하나의 메서드가 다양한 객체에서 다르게 동작할 수 있음.
: 메서드 오버라이딩, 메서드 오버로딩
4. 추상화
: 프로그램으로 표현하기 위해, 실제의 개념을 단순화하고 필요한 개념을 강조할 수 있음.
: 핵심 개념에만 집중을 하고, 세부 개념은 사후로 밀어둠.
II. 클래스의 구성요소
: 클래스 내부에 정의되는 데이터와 메서드
클래스 (Class)
: 객체를 생성하기 위한 템플릿 혹은 설계도 역할.
1. 필드 (Fields)
: 클래스 내부에 정의된 데이터. (상태)
: 클래스에서 사용되는 변수.
: 객체의 상태를 나나태는 데이터를 저장하기 위해 사용됨.
public class Person
{
public string Name {get; set;} // 필드 : Name
public int Age {get; set;} // 필드 : Age
}
2. 메서드 (Methods)
: 클래스에서 수행되는 동작이 정의. (동작)
: 클래스 내부에서 작업을 수행하거나 외부에서 호출되어 작업을 처리
: 메서드는 입력값을 받아서 처리하고, 결과값을 반환(이 경우, void 대신 반환하는 데이터 타입 기입) 할 수도 있다.
: 클래스에서 정의한 필드값은 매개변수로 추가하지 않아도 사용할 수 있다.
public class Person
{
public string Name { get; set; } // 필드 : Name
public int Age { get; set; } // 필드 : Age
public void CallAge()
{ // 필드에서 선언한 값은 동일 클래스 메서드의 매개변수로 따로 추가하지 않아도 됨.
Console.WriteLine($"이름은 {Name}, 나이는 {Age.ToString()} .");
}
}
!! 메서드 실행
Person francJi = new Person(); // 인스턴스 생성
francJi.Name = "FrancJi"; // 필드값 Name 입력
francJi.Age = 28; // 필드값 Age 입력
francJi.CallAge();
>
!! 클래스를 활용하지 않는다면..
CallAge 메서드와 동일한 기능을 갖는 함수를 Main에 만듦.
메서드의 매개변수로써 클래스의 필드가 아닌 string과 int를 요구하도로 수정한 뒤 실행해보면...
static void Main(string[] args)
{
void CallDirect(string Name, int Age)
{
Console.WriteLine($"이름은 {Name}, 나이는 {Age.ToString()} .");
}
string francJiName = "FrancJi"; // Main 함수에 직접 변수 선언
int francJiAge = 28;
CallDirect(francJiName, francJiAge);
}
>
만약 5개 이상의 메서드를 메인에서 구현해야하고, 4명 이상의 사람에 대해 메서드를 적용시켜본다고 가정하자.
1. Main함수에서만 구현하는 경우
static void Main(string[] args)
{
// 사람 1 이름에 대한 변수
// 사람 1 나이에 대한 변수
// 사람 2 이름에 대한 변수
// 사람 2 나이에 대한 변수
// 사람 3 이름에 대한 변수
// 사람 3 나이에 대한 변수
// 사람 4 이름에 대한 변수
// 사람 4 나이에 대한 변수
// 사람 5 이름에 대한 변수
// 사람 5 나이에 대한 변수
// 인사하는 기능을 구현한 메서드 (매개변수 : 이름)
// 나이를 말하는 기능을 구현한 메서드 (매개변수 : 이름, 나이)
// 두 명의 나이를 비교하는 기능을 구현한 메서드 (매개변수 : 이름1, 이름2, 나이1, 나이2)
// 내년의 나이를 알려주고, 단위가 변하면 놀려주는 메서드 (매개변수 : 이름, 나이)
// MZ한가를 알려주는 메서드 (매개변수 : 이름, 나이)
// 인사하는 기능을 구현한 메서드(이름 1)
// 나이를 말하는 기능을 구현한 메서드(이름 1, 나이 1)
// 두명의 나이를 비교하는 기능을 구현한 메서드(이름 1, 나이 1, 이름 2, 나이 2)
// 내년의 나이를 알려주고, 단위가 변하면 놀려주는 메서드 (이름 1, 나이 1)
// MZ한가를 알려주는 메서드 (이름 1, 나이 1)
//...
}
2. 클래스로 활용하여 구현하는 경우
public class Person
{
// 필드값 : 이름
// 필드값 : 나이
// 인사하는 기능을 구현한 메서드 ()
// 나이를 말하는 기능을 구현한 메서드 ()
// 두 명의 나이를 비교하는 기능을 구현한 메서드 (매개변수 : 비교하는 객체)
// 내년의 나이를 알려주고, 단위가 변하면 놀려주는 메서드 ()
// MZ한가를 알려주는 메서드 ()
}
static void Main(string[] args)
{
// 사람 1 인스턴스화.
// 사람 2 인스턴스화.
// 사람 3 인스턴스화.
// 사람 4 인스턴스화.
// 사람 5 인스턴스화.
// 사람 1.인사하는 기능을 구현한 메서드
// 사람 1.나이를 말하는 기능을 구현한 메서드
// 사람 1.두 명의 나이를 비교하는 기능을 구현한 메서드(사람 2)
// 사람 1.내년의 나이를 알려주고, 단위가 변하면 놀려주는 메서드 ()
// 사람 1.MZ한가를 알려주는 메서드 ()
// ...
}
다루는 객체와, 해당 객체를 인스턴스화하는 클래스의 메서드와 필드가 많으면 많아질수록, 클래스를 활용하는 것이 코드가 간결해지는 것을 확인할 수 있다.
==>
코드의 가독성이 좋아진다.
또한, 이름 1을 다른 이름으로 변경한다고 생각해보자.
: 위의 Main 함수에서 메서드를 제작하고 활용한 경우에는. 이름 1이 매개변수로 사용된 메서드의 매개변수 부분을 일일히 바꿔줘야한다.
: 아래의 Class를 활용하는 경우에는, 해당 사람 1 객체의 필드만 바꾸면 해결된다.
==>
코드의 유지보수가 쉬워진다.
3. 생성자 (Constructor)
: 객체가 생성될 때 호출되는 메서드
: 클래스의 인스턴스를 초기화하고, 필요한 초기값을 설정하는 역할을 수행
: 클래스와 동일한 이름을 가지며, 반환 타입이 없다
public class Animal
{
public string Name { get; set; } // 필드값 입력
public int Age { get; set; }
public Animal(string name, int age) // 생성자
{
Name = name;
Age = age;
Console.WriteLine("Animal 생성자 호출");
}
// ... 생략 ...
}
static void Main(string[] args)
{
Animal mungs = new Dog("Mungs", 5);
// ㄴ->mungs 라는 인스턴스를 초기화할건데, new Dog() 안의 매개변수를 Dog의 생성자로 전달해라. 라는 의미
Console.WriteLine($"이름 : {mungs.Name} 나이 : {mungs.Age}");
}
생성자는 생략할 수 있다.
: 다만, 생성자를 생략한 클래스에 필드값이 있는 경우, 생성한 객체를 통해 직접 필드에 값을 할당해주어야 한다.
public class Person
{
public string Name { get; set; } // 필드 : Name
public int Age { get; set; } // 필드 : Age
public void CallAge()
{
Console.WriteLine($"이름은 {Name}, 나이는 {Age.ToString()} .");
}
// .. 생략
}
static void Main(string[] args)
{
Person francJi = new Person(); // 인스턴스 francJi를 Person 클래스의 생성자를 통해 초기화
francJi.Name = "FrancJi"; // Person 클래스에서 생성자가 생략되었으므로, 직접 필드에 값을 할당
francJi.Age = 28; // Person 클래스에서 생성자가 생략되었으므로, 직접 필드에 값을 할당
}
만약 인스턴스를 초기화하고, 해당 인스턴스의 필드에 값을 할당하지 않으면 어떤 일이 벌어질까?
static void Main(string[] args)
{
Person francJi = new Person(); // 인스턴스 생성
//francJi.Name = "FrancJi"; // 주석으로 필드값 입력 막아둠.
//francJi.Age = 28;
francJi.CallAge();
}
>
: 각기 타입의 디폴트 값으로 실행된다. (string은 "", int는 0)
: 객체를 초기화할 때, 전달받은 매개변수가 없으므로, francJi 객체의 Name 필드와 Age 필드에 각기 default값을 할당함.
인스턴스화
: 클래스의 생성자를 호출하고, 호출한 생성자를 통해서 객체(인스턴스)를 초기화하는 것.
"class 이름" "인스턴스(객체) 이름" = new "class 이름"(필요할 경우 매개변수 입력)
Animal mungs = new Dog("Mungs", 5);
생성자는 객체를 초기화하는 과정에서 필요한 과정을 수행할 수 있다.
< 필요한 과정 >
1. 객체 초기화
: 객체가 생성될 때, 객체의 초기 상태 설정.
2. 매개변수 전달
: 매개변수를 전달받아, 생성자를 호출할 때, 필요한 데이터를 초기화되는 객체에 전달
3. 상속 관계에서 초기화 작업을 수행.
: 자식 클래스에서 부모 클래스의 생성자를 호출하여, 부모 클래스의 생성자로 초기화 작업을 수행.
// 부모 클래스
public class Animal
{
public string Name { get; set; }
public int Age { get; set; }
public Animal(string name, int age)
{
Name = name;
Age = age;
Console.WriteLine("Animal 생성자 호출");
}
// ... 생략 ...
}
// 자식 클래스
public class Dog : Animal
{
public string DogBreed { get; set; }
public Dog (string name, int age, string dogBreed) : base(name, age)
{
DogBreed = dogBreed;
Console.WriteLine("Dog 생성자 호출");
}
//... 생략 ...
}
static void Main(string[] args)
{
Dog mungs = new Dog("Mungs", 5, "웰시코기");
Console.WriteLine($"이름 : {mungs.Name} 나이 : {mungs.Age} 견종 : {mungs.DogBreed}");
}
>
3 - !! . Animal 클래스와 Dog 클래스의 "생성자 호출"을 출력한 부분에 중단점을 지정하여, 흐름을 알아보았다.
1. Main 함수 부분 인스턴스화 시작
2. Dog 클래스의 새로운 객체를 초기화할 때, 매개변수를 통해 하기로 약속하였으므로,
입력받은 매개변수를 Dog 클래스를 참고해 새로운 객체에 전달하는 과정.
3. 전달하는 과정 중에, name과 age의 경우는 Animal의 필드에 값을 입력하기로 약속하였으므로, name과 age를 입력하기 위해, Animal 클래스를 참고한다.
: 매개변수로 입력받은 age와, name을 Animal 클래스를 활용해 새로운 객체의 Name 필드와 Age 필드에 저장.
>
4. 다시 Dog 클래스로 돌아와 DogBreed 필드에 입력받은 dogbreed를 저장.
>
5. 이후, 다시 Main 함수로 들어와, 생성한 객체를 이용하여 코드 실행.
4. 소멸자 (Destructor)
: 객체의 사용이 종료되고 메모리에서 해제될 떄 자동으로 호출되는 메서드
: 클래스와 동일한 이름을 가지며, 이름 앞에 ~ 기호를 붙여 표현한다.
: 자원 해제, 메모리 해제, 로깅 및 디버깅 등의 작업을 수행할 때 사용한다.
class Person
{
//... 생략 ...
~Person()
{
// 소멸될 때 작업
}
}
static void Main(string[] args)
{
using (Person person = new Person())
{
// 인스턴스 person 활용
}
// 블록을 벗어나면 ~Person()에 의해 소멸
}
5. 프로퍼티 (Property)
: 객체의 필드 값을 읽거나 설정하는데 사용되는 접근자(Accessor) 메서드의 조합.
: 객체의 필드에 직접 접근하지 않고, 간접적으로 값을 설정하거나 읽을 수 있도록 함.
: 필드에 대한 접근 제어와 데이터 유효성 검사 등을 수행할 수 있음.
: get 과 set 접근자를 사용하여 값을 읽고 설정하는 동작을 정의함.
접근자 (Accessor)
: 클래스의 필드값을 읽거나 설정하기 위한 메서드
1. get
: 프로퍼티의 값을 읽을 때 사용하는 접근자
: 값을 반환하는 역할 수행
2. set
: 프로퍼티의 값을 설정(쓰기) 할 때 사용하는 접근자
: 값을 받아와 내부 필드에 할당하는 역할을 수행
public class GameCharacter
{
private int level; // 필드
public int Level // 프로퍼티
{
get { return level;}
set { level = value; }
}
public GameCharacter(int levelParameter)
{
Level = levelParameter; // 입력받은 매개변수로 프로퍼티 Level 초기화
}
}
//
static void Main(string[] args)
{
gunner = new GameCharacter(5);
int characterLevel = gunner.Level;
Console.WriteLine($"Character Level: {characterLevel}");
}
!! 코드 흐름
1. 인스턴스 gunner을 초기화하기 위해, GameCharacter에 있는 생성자 호출
2. GameCharacter에 있는 생성자가 인스턴스 gunner의 프로퍼티 Level의 set 접근자를 호출하여,
set 의 value에 입력받은 매개변수 전달.
3. 프로퍼티 Level의 set 접근자는 전달받은 value를 자기자신과 gunner의 필드 level에 할당.
4. 초기화 종료 및 메인함수 실행. gunner의 프로퍼티 Level을 호출
5. 프로퍼티 Level의 get 접근자가 private으로 선언한 level 값을 반환함.
>
접근 제한자
: 객체 지향 프로그래밍에서 클래스의 멤버(필드, 메서드, 속성 등)에 대한 접근 권한을 지정하는 키워드
1. public : 모든 외부의 접근을 허가함.
2. private : 같은 클래스 내에서만 접근을 허가함.
3. protected : 같은 클래스 내부와 파생 클래스에서 접근을 허가함.
4. internal : 같은 **어셈블리 내에서 접근을 허가함.
**어셈블리 (assembly)
: NET 런타임 환경에서 실행할 수 있는 코드와 리소스, 메타데이터를 포장하고 배포하는 단위
접근자와 접근제한자를 활용하여 외부의 접근을 제어할 수 있다.
: 위의 예시처럼, private으로 설정한 필드값은 set 접근자를 통해서만 변경이 가능하다.
자동 프로퍼티
: 필드의 선언가 접근자 메서드의 구현을 컴파일러가 자동으로 처리하여 개발자가 간단한 구문으로 프로퍼티를 정의.
class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
III. 클래스와 구조체
1. 값 형식과 참조 형식
값 형식
: 데이터의 실제 값을 저장.
: 기본 데이터 형식 및 구조체(struct)가 해당
: 메모리의 스택(stack) 영역에 저장된다.
참조 형식
: 메모리 주소(데이터에 대한 참조) 값을 저장함.
: 클래스(class), 인터페이스(interface), 델리게이트(delegete) 등이 해당
: 메모리의 힙(Heap) 영역에 저장된다.