1. 클래스 개요
객체 지향 프로그래밍(OOP, Object-Oriented Programming)은 객체(Object)를 사용하여 프로그램을 처리됩니다.
객체란 변수(Variable)와 메서드(Method)로 구성되어 있는 프로그램에서 기능을 실행시키는 단위입니다.
클래스(Class)는 객체를 만들기 위해 변수와 메서드로 정의한 사용자 정의 데이터형(User Define Data Type)입니다.
그리고, 클래스에 의해 생성 된 객체가 인스턴스(Instance)입니다.
클래스는 하나의 논리 단위에 속하는 데이터 및 동작 집합을 캡슐화하는 데이터 구조입니다.
데이터 및 동작은 클래스의 멤버로 메서드, 속성, 이벤트 등으로 구성되어 있습니다.
1.1 클래스 선언
클래스는 접근제한자 class키워드 클래스 이름으로 선언합니다.
클래스 선언 |
접근-제한자 class 고유-식별자 public class 클래스-이름 { // 필드, 속성, 메서드, 이벤트 } |
1.2 접근 제한자(Access Modifier)
접근 제한자는 클래스 내에 멤버(필드, 메서드, 속성, 이벤트 등)의 접근을 제한하는 역할을 합니다.
- public : 클래스의 외부에서도 접근 가능.
- protected : 상속받은 자식 클래스에게만 접근 가능.
- private : 클래스의 내부에서만 사용.
아무 선언을 하지 않으면, 기본 접근 제한자는 private입니다.
1.3 멤버(Member)
클래스는 데이터와 동작을 나타내는 멤버가 있습니다.
멤버는 생성자, 소멸자, 상수, 필드(Field), 메서드(Method), 속성(Property, 프로퍼티), 이벤트(Event), 인덱서, 연산자, 클래스 등으로 구성되어 있습니다.
멤버 |
내용 |
예제 |
생성자 |
클래스를 초기화할 때 실행됩니다. |
Example(...) |
소멸자 |
클래스를 종료할 때 실행됩니다. |
~Example(...) |
상수 |
상수 |
const float pi = 3.14f; |
필드 |
변수, public 메서드 |
int field; |
메서드 |
메서드 |
void Method(){...}; |
속성 |
클래스 밖에서는 변수처럼, 클래스 안에서는 메서드처럼 사용됩니다. |
int Property {get; set;} |
이벤트 |
속성처럼 사용할 수 있는 메서드입니다. |
event EvnetHandler Event; |
인덱서 |
속성처럼 사용할 수 있는 배열입니다. |
int this[int i]{...} |
연산자 |
오버로드 된 연산자입니다 |
static Example operator +(Exmaple x, Example y){...} |
클래스 |
클래스 내의 클래스입니다. |
class Example2{...} |
1.4 객체 만들기(인스턴스화, Instantiation)
객체는 참조 형식입니다.
클래스에서 new 키워드를 사용하여 선언하면 됩니다.
사용 방법 : 객체 만들기 |
접근-제한자 클래스-이름 객체-이름 = new 클래스-이름(); |
using UnityEngine;
public class Class1 // 클래스 선언
{
public int m_IntVariable;
private string m_StringVariable;
public void DebugLog(string message)
{
m_StringVariable = message;
Debug.Log(message);
}
}
public class ClassExample : MonoBehaviour
{
private Class1 m_Class1 = new Class1(); // 객체 인스턴스화
void Start()
{
m_Class1.DebugLog("Example"); // 출력 : Example
}
}
1.5 생성자(Constructor)
생성자는 초기화를 위한 메서드로 인스턴스를 생성할 때 반드시 호출됩니다.
생성자는 클래스 이름과 같은 이름을 사용하며, 인수는 어떤 것을 사용할 수 있으며, 반환값은 없습니다.
기본 생성자는 인수가 없는 것이며, 기본생성자는 아무 일도 일어나지 않습니다.
또한, 기본생성자는 생략이 가능합니다.
예제 : 기본생성자 |
public class Class1 { public Class1() { } // 기본생성자 } |
만약, 기본 생성자가 아닌 인수를 가지는 생성자를 사용하게 되면, 기본생성자 생성이 억제됩니다.
예제 : 기본 생성자 외의 일반 생성자 |
public class Class1 { public Class1(int i) { } // 일반 생성자 } |
1.6 정적(Static)
정적 멤버란 인스턴스 없이 클래스에서 직접 호출할 수 있는 멤버를 의미합니다.
정적 멤버 선언을 할 때 사용되는 키워드가 static 입니다.
정적 멤버는 변수와 메서드에 다 사용할 수 있습니다.
예제 : static |
public class Class1 { public static int staticInt; } Class1.staticInt = 1; |
정적 멤버 중 static이 없는 멤버를 인스턴스 멤버라고 합니다.
정적 멤버는 인스턴스 멤버로는 접근할 수 없습니다.
예제 : static 접근 |
public class Class1 { public static int staticInt; public int instanceInt; public static void StaticMethod() { staticInt++; instanceInt++; // 에러 } } Class1.staticInt = 1; |
하지만, 다른 멤버들은 호출할 때, 메모리에 올라가게 되지만, 정적 멤버는 프로그램이 시작할 때부터 종료될 때까지 메모리에 올라가 있기 때문에 메모리 낭비가 생길 수 있습니다.
1.7 속성(Property, 프로퍼티)
속성이란 클래스 밖에서는 변수로, 클래스 안에서는 메서드처럼 사용되는 멤버입니다.
객체 지향 프로그램에서 캡슐화(Encapsulation)란 객체의 속성(Data Fileds)과 행위(메서드, Methods)를 하나로 묶고, 실제 구현 내용 일부를 외부에 감추어 은닉하는 것을 의미합니다.
속성은 캡슐화에 유용하게 사용됩니다.
하지만, 속성은 필드 하나에 메서드가 2개가 필요합니다.
다음은 속성의 기본 형태입니다.
예제 : 속성 기본 형태 |
private int property; public int Property { get { return this.property} set { this.property = value} } |
get과 set 둘 다 아무 처리를 하지 않는다면 간단하게 작성할 수 있습니다.
이것을 자동 프로퍼티라고 합니다.
예제 : 자동 프로퍼티 |
public int Property{ get ; set; } |
또한 속성의 get이나 set에 접근 제한자를 사용하여 외부에서 접근을 제한할 수 있습니다.
아래 예제는 외부에서 읽을 수 있지만, 수정을 할 수 없는 속성입니다.
예제 : 읽기 전용 속성 |
public int Property{ get ; private set; } |
유니티에서는 속성은 public으로 선언하여도 Inspector(인스펙트) 창에 나타나지 않습니다.
이때 [SerializedField]을 사용하면, Inspector 창에도 나타나게 됩니다.
예제 : Inspector 창에 나타나게 하는 속성 |
[SerializedField] private int property; public int Property { get { return this.property} set { this.property = value} } |
1.8 인덱서(Indexer)
인덱서는 배열로 속성과 같은 효과를 낼 수 있습니다.
인덱서는 클래스 객체의 데이터를 배열 형태로 인덱스를 사용하여 접근할 수 있습니다.
클래스가 배열이 아니지만, 마치 배열처럼 [](대괄호)를 사용하여 클래스 내의 특정 필드에 접근합니다.
속성 이름 대신 this[]를 사용하는 것과 첨자를 사용한다는 점을 빼고는 속성과 같습니다.
예제 : 인덱서 |
priavte int[] x; public int this[int I] { get { return x[i]; } set { x[i] = value; } } |
using UnityEngine;
public class IndexerClass
{
int[] data = new int[10];
public int this[int index]
{
get
{
if (index >= 0)
return data[index];
else
return -1;
}
set
{
if (index >= 0)
data[index] = value;
}
}
}
public class IndexerExample : MonoBehaviour
{
void Start()
{
IndexerClass indexerClass = new IndexerClass();
indexerClass[1] = 1234; // 인덱서 set 사용
Debug.Log(indexerClass[1]); // 출력 : 1234
int i = indexerClass[1]; // 인덱서 get 사용
Debug.Log(i); // 출력 : 1234
}
}
1.9 메서드(Method)
1.9.1 메서드
메서드란 클래스 내에서 코드 블록을 별도의 이름을 붙여서 다른 위치에서 호출하도록 만든 것입니다.
메서드는 일반적으로 한 가지 이상의 기능을 하기 위한 코드들의 집합으로 메서드 정의와 호출로 나눌 수 있습니다.
먼저 메서드 정의는 아래와 같습니다.
메서드 정의 |
접근-제한자 데이터-형식 메서드-이름(매개변수-1,...) { ... return 변환-값; } |
그리고, 이 메서드의 호출은 아래와 같습니다.
메서드 호출 |
데이터-형식 메서드-이름(매개변수-1,...); |
이 메서드는 매개변수와 변환 값을 지정하여 값을 주고받습니다.
매개변수란 메서드를 호출 할 때, 메서드 안에서 사용하는 변수를 괄호 안에 선언하는 것입니다.
이 매개변수의 개수는 제한이 없지만, 값은 메서드 이름의 매개변수의 개수와 같아야 합니다.
매개변수 개수가 틀리면, 다른 메서드로 인식하기 때문입니다.
변환 값이란 이 메서드가 실행되면, 얻어지는 값을 의미합니다.
일반적으로 메서드를 호출할 때와 메서드를 정의할 때의 데이터 형식은 같아야 합니다.
그리고, 변환형 데이터 형식에는 void라는 데이터 형식이 있습니다.
void는 변환 값이 없이 메서드를 실행만 합니다.
그리고 return이라는 키워드는 변환 값을 의미합니다.
이때, 변환 값은 변환형과 같은 데이터 형식이어야 합니다.
그리고 변환형이 void일 경우, return이 없어도 되고, return 만 적어두고 값을 없애도 됩니다.
using UnityEngine;
public class MethodeExample : MonoBehaviour
{
void Start()
{
int parameter1 = 1, parameter2 = 2;
int intMethod = IntMethod(parameter1, parameter2);
Debug.Log(intMethod); // 출력 : 3
float parameter3 = 1.2f;
VoidMethod(parameter3.ToString()); // 출력 : 1.2
}
int IntMethod(int p1, int p2)
{
return (p1 + p2);
}
void VoidMethod(string message)
{
Debug.Log(message);
return; // 생략 가능
}
}
1.9.2 매개변수
1.9.2.1 값 전달(Pass by Value)
C#은 메서드에 매개변수를 전달할 때 값을 복사해서 전달하는 값 전달(Pass by Value)입니다.
매서드 내에 전달 된 매개변수는 메서드가 끝나고 함수가 리턴되면, 매개변수 값은 원래 값으로 유지됩니다.
using UnityEngine;
public class PassByValueExample : MonoBehaviour
{
void Start()
{
int c = Add(1, 2);
Debug.Log(c); // 출력 : 3
}
int Add(int a, int b)
{
return a + b;
}
}
1.9.2.2 참조 전달(Pass by Reference)
메서드에 매개변수를 전달할 때, 참조로 전달할 수 있는데 이때 사용하는 키워드가 ref와 out입니다.
참조 전달되는 변수는 메서드 내에서 변경된 값은 리턴 후에도 유효합니다.
그런 이유로 ref와 out은 리턴 값처럼 사용할 수 있습니다.
ref |
out |
|
공통점 |
참조 전달 |
참조 전달 |
차이점 |
변수가 꼭 초기화하고 사용해야 함. |
변수 초기화를 꼭 할 필요는 없음. |
using UnityEngine;
public class PassByReferenceExample : MonoBehaviour
{
void Start()
{
int a = 1, b = 2;
int c = 0; // 초기화
bool b1 = RefMethod(a, b, ref c);
int d = 3, e = 4;
int f; // 초기화하지 않음
bool b2 = OutMethod(d, e, out f);
}
bool RefMethod(int a, int b, ref int c)
{
c = a + b;
return true;
}
bool OutMethod(int d, int e, out int f)
{
f = d + e;
return true;
}
}
1.9.3 네임드 매개변수(Named Parameter)
매 세드에 매개변수를 전달할 때 일반적으로 순서대로 매개변수가 넘겨집니다.
하지만, C# 4.0부터는 위치에 상관없이 매개변수 이름을 지정하면 매개변수를 전달할 수 있습니다.
사용방법은 매개변수 이름을 매개변수 앞에 선언해 주고, (:)클론 을 적어 둔 뒤 변숫값을 적어두면 됩니다.
using UnityEngine;
public class NamedParameterExample : MonoBehaviour
{
void Start()
{
NamedParameter(name: "Coderzero", height: 174.5f, age: 47);
}
void NamedParameter(string name, int age, float height)
{
Debug.LogFormat($"Name : {name} Age : {age} Height : {height}");
// 출력 : Name : Coderzero Age : 47 Height : 174.5
}
}
1.9.2.3 매개변수 기본값 (Optional Parameter)
C# 4.0 부터 매개변수에 기본값을 할당할 수 있습니다.
기본값을 할당받은 매개변수는 생략이 가능합니다.
이 매개변수를 Optional Parameter라고 하는데, 매개변수들 중 맨 마지막에 위치하여야 합니다.
그리고, Optional Parameter가 여러 개일 경우 Optional이 아닌 매개변수 뒤에 위치하여야 합니다.
사용방법은 메서드를 선언할 때 매개변수에 기본값을 선언해 주면 됩니다.
using UnityEngine;
public class OptionalParameterExample : MonoBehaviour
{
void Start()
{
OptionalParameter(1, 2.3f); // 출력 : 1
OptionalParameter(1, 2.3f, false); // 출력 : 2.3
}
void OptionalParameter(int i, float f, bool isInt = true)
{
if (isInt)
Debug.Log(i);
else
Debug.Log(f);
}
}
1.9.2.4 Params
메서드는 매개변수의 개수가 고정되어 있습니다.
하지만, 매개변수에 앞에 params 키워드를 사용하여 가변적인 배열을 매개변수로 만들 수 있습니다.
params는 한 메서드 안에 하나만 존재해야 하며, 맨 마지막에 위치해야 합니다.
사용방법 매개변수를 선언할 때 앞에 params 키워드를 적어 주고, 배열 형태로 선언하면 됩니다.
using UnityEngine;
public class ParamsExample : MonoBehaviour
{
void Start()
{
float f1 = Add(1, 2);
Debug.Log(f1); // 출력 : 3
float f2 = Add(3, 4, 5);
Debug.Log(f2); // 출력 : 12
}
float Add(params float[] fs)
{
float result = 0;
for (int i = 0; i < fs.Length; i++)
{
result += fs[i];
}
return result;
}
}
1.9.3 확장 메서드(Extension Method)
일반적으로 메서드는 클래스 안에 선언되어 사용됩니다.
하지만, 확장 메서드는 이미 만들어진 클래스에 메서드를 추가하여 사용할 수 있습니다.
선언 형식은 정적(static) 클래스를 먼저 정의하고 정적(static) 메서드를 선언하면 됩니다.
확장 메서드의 첫 번째 매개변수는 이미 만들어진 클래스 이름으로 형식을 지정하며, 매개변수 앞에 this 한정자가 넣습니다.
2번째 매개변수부터는 실제로 사용하는 매개변수를 넣으면 됩니다.
사용방법 : 확장 메서드 선언 |
public static class 클래스-이름 { public static 데이터-형식 메서드-이름(this 이미-만들어진-클래스-이름 식별자, 매개변수...) { ... // return [변환 값]; // 데이터 형식이 void가 아니면 변환 값이 생김 } } |
확장 메서드는 정적 메서드로 정의되지만, 인스턴스 메서드 구문을 사용하여 호출됩니다.
using UnityEngine;
public static class ExtensionMethod
{
public static string NewToLower(this string str) // 확장 메서드, string 클래스에 ToLowerMethod 메서드 추가
{
return string.Format("ToLower : {0}", str.ToLower());
}
}
public class ExtensionMethodExample : MonoBehaviour
{
void Start()
{
string s = "AbCde";
Debug.Log(s.NewToLower()); // 출력 : ToLower : abcde,
}
}
1.9.3 튜플(Tuple)
C# 7.0 이상부터 지원하는 기능입니다.
일반적으로 메서드는 하나의 값만을 변환받을 수 있습니다.
하지만 튜플을 사용하게 되면, 복수의 값을 리턴 받을 수 있습니다.
사용방법은 메서드 선언할 때 데이터 형식을 하나 넣는 대신, 괄호()를 사용하여 콤마를 사용하여 여러 가지 데이터 형식을 순차적으로 넣어 주고, 메서드 안에서 변환 값을 괄호()와 콤마를 사용하여 순차적으로 넣어주면 됩니다.
using System.Collections.Generic;
using UnityEngine;
public class TupleExample : MonoBehaviour
{
void Start()
{
List<int> list = new List<int> { 1, 2, 3, 4, 5 };
var r = Average(list);
Debug.LogFormat("Count : {0}, Average : {1}", r.count, r.average); // 출력 : Count : 5, Average : 3
}
(int count, float average) Average(List<int> data)
{
int cnt = data.Count;
int sum = 0;
for (int i = 0; i < cnt; i++)
{
sum += data[i];
}
float avrg = sum / cnt;
return (cnt, avrg);
}
}
2. 상속(Inherutabce)
하나의 클래스를 사용하다가, 그 클래스의 멤버를 그대로 사용하면서, 다른 기능을 사용하고 싶은 경우가 있습니다.
이때 사용하는 기법이 상속입니다.
상속은 이미 만들어진 클랙스의 기능을 바탕으로 새로운 클래스를 만들어서 새 멤버를 추가할 수 있습니다.
우리는 새로운 C# 스크립트를 만들 때마다, MonoBehaviour 상속을 받은 클래스가 생성됩니다.
using UnityEngine;
public class UnityInherutabceExample : MonoBehaviour
{
void Start()
{
}
}
여기서 클래스에서 상속을 시키는 클래스를 부모 클래스(Base Class)라고 하고, 파생시키는 클래스를 자식 클래스(Derived Class)라고 합니다.
자식 클래스를 만들 때 클래스 이름 뒤에 :(클론)을 붙이고 그 뒤에 부모 클래스 이름을 적으면 됩니다.
using UnityEngine;
public class BaseClass
{
public string m_StringVariable;
public int m_IntVariable { get; set; }
}
public class DerivedClass1 : BaseClass
{
public void Method()
{
Debug.Log(m_StringVariable);
}
}
public class DerivedClass2 : BaseClass
{
public void Method()
{
Debug.Log(m_IntVariable);
}
}
public class InheritanceExample : MonoBehaviour
{
DerivedClass1 m_BaseClass1 = new DerivedClass1();
DerivedClass2 m_BaseClass2 = new DerivedClass2();
void Start()
{
m_BaseClass1.m_StringVariable = "DerivedClass1";
m_BaseClass1.Method(); // 출력 : DerivedClass1
m_BaseClass2.m_IntVariable = 3;
m_BaseClass2.Method(); // 출력 : 3
}
}
3. 추상(Abstract)
3.1 추상 클래스
추상 클래스란 그 클래스 자체는 객체(인스턴스)를 생성하지 못하고, 자식 클래스로만 객체(인스턴스)를 생성할 수 있는 클래스입니다.
추상 클래스 생성 방법은 클래스 앞에 abstract 키워드를 붙이면 됩니다.
예제 : 추상 클래스 |
abstract public class BaseClass { } public class DerivedClass1 : BaseClass { } // BaseClass bc1 = new BaseClass(); // 에러 DerivedClass1 dc1 = new DerivedClass1(); // 정상 |
3.2 추상 메서드
추상 메서드는 abstract로 정의된 추상 클래스에서 정의된 메서드입니다.
추상 메서드는 메서드 앞에 abstract 키워드를 붙이면 됩니다.
추상 메서드는 메서드 이름의 정의가 있지만, 구현 없이 프로토타입(Prototype)만 가지고 있습니다.
자식 클래스에서는 반드시 구현을 해야 하는데, 이 것을 오브라이드(Override)라고 합니다.
그래서 자식 클래스에서 메서드 앞에 override 키워드를 붙어야 합니다.
예제 : 추상 메서드 |
abstract public class BaseClass { abstract public void Method(); } public class DerivedClass1 : BaseClass { override public void Method() { Debug.Log("Abstract Method"); } } |
4. 인터페이스(Interface)
일반적으로 클래스는 하나의 클래스가 하나의 클래스에 상속이 가능합니다. 다시 얘기하면 다중 상속이 불가능합니다.
하지만 인터페이스는 다중 상속이 가능합니다.
인터페이스는 추상 메서드만을 포함한 클래스라 할 수 있습니다.
인터페이스도 추상 메서드이기 때문에, 인터페이스에서는 메서드 이름 정의가 있지만, 구현 없습니다.
그리고, 자식 클래스에서는 구현합니다.
인터페이스는 클래스이름 앞에 class 대신 interface 키워드를 붙입니다.
예제 : 인터페이스 |
interface IBase1 { void Method11(); // void Mehtod2() {...} // 틀림. } interface IBase2 { void Method21(); } public class Class1 : IBase1, IBase2 { void Method11() { Debug.Log("Interface("Interface Method11"); } // 선언부가 있어야 합니다. void Method21() { Debug.Log("Interface("Interface Method21"); } // 선언부가 있어야 합니다. } |
인터페이스는 객체(인스턴스)를 생성할 수 없습니다.
인터페이스 상속을 받은 클래스를 객체(인스턴스)를 만들 수 있습니다.
예제 : 객체 생성 |
// IBase1 ib1 = new IBase1(); // 틀림. Class1 c1 = new Class1(); // 맞음. |
5. Partial 클래스
하나의 클래스에는 하나의 기능을 넣는 것을 기본으로 합니다.
하지만, 실제로 클래스를 만들다 보면 클래스가 너무 커지게 되면, 가독성을 위해서나 기능별로 분리하여 사용해야 하는 경우가 발생합니다.
이때 사용하는 것이 Partial 클래스입니다.
Partial 클래스와 Partial 클래스 멤버들은 다른 클래스를 사용하기 위해서는 접근 제한자를 public으로 사용해야 합니다.
Partial 클래스는 class앞에 partial 키워드 한정자를 넣으면 됩니다.
선언 |
public partial class 클래스-이름 {} public partial class 클래스-이름 {} |
using UnityEngine;
public partial class PartialClass
{
public void DebugLog1()
{
Debug.Log("DebugLog1");
}
}
public partial class PartialClass
{
public void DebugLog2()
{
Debug.Log("DebugLog2");
}
}
public class PartialClassExample : MonoBehaviour
{
private PartialClass m_PartialClass;
void Start()
{
m_PartialClass = new PartialClass();
m_PartialClass.DebugLog1(); // 출력 : DebugLog1
m_PartialClass.DebugLog2(); // 출력 : DebugLog2
}
}
partial 한정자는 class, struct 또는 interface 키워드 바로 앞에만 올 수 있습니다.
partial 키워드는 클래스, 구조체 또는 인터페이스의 다른 부분을 네임스페이스에서 정의할 수 있음을 나타냅니다.
모든 부분은 partial 키워드를 사용해야 합니다.
'프로그램 > 유니티 C# 강좌' 카테고리의 다른 글
[유니티 C# 강좌] 15. 제네릭 (Generics) (0) | 2020.01.29 |
---|---|
[유니티 C# 강좌] 14. 구조체(Struct) (0) | 2020.01.29 |
[유니티 C# 강좌] 12. 네임스페이스(Namespaces), using 문 (0) | 2019.11.29 |
[유니티 C# 강좌] 11. 예외 처리(Exception handling) (0) | 2019.11.25 |
[유니티 C# 강좌] 10. 점프문(Jump Statement) (0) | 2019.11.24 |