특징 | 클래스 | 구조체 |
---|---|---|
키워드 | class | struct |
형식 | 참조 형식 ( 힙에 할당 ) | 값 형식 ( 스택에 할당 ) |
복사 | 얕은 복사 | 깊은 복사 |
인스턴스 생성 | new 연산자와 생성자 필요 | 선언만으로 생성 |
생성자 | 매개변수 없는 생성자 선언 가능 | 매개변수 없는 생성자 선언 불가능 |
상속 | 가능 | 값 형식 이므로 상속 불가능 |
이 중에서도 가장 중요한 부분이
"클래스"는 "참조 형식" "구조체"는 "값 형식"이라는 점이다.
구조체는 "값 형식"이기 때문에
할당 연산자 = 를 사용하면 모든 필드가 그대로 복사된다.
(클래스는 얕은 복사가 진행된다)
얕은 복사와 깊은 복사란
class Test
{}
Test t1 = new Test();
Test t2 = t1;
이럴경우 t1의 객체는 힙에 할당이 되고 t1이라는 변수명으로 힙에 할당된 메모리 주소를 가르키는 것임.
그런데
Test t2 = t1;
을 해주게되면 t1이 힙에 가르키고있는 힙메모리의 주소를 t2도 같은 곳을 가르키게된다.
namespace TestCSharp
{
class Test
{
public int shared = 10;
}
public class Program
{
static void Main(string[] args)
{
Test t1 = new Test();
Console.WriteLine(t1.shared);
Test t2 = t1;
t2.shared = 30;
Console.WriteLine(t1.shared);
Console.WriteLine(t2.shared);
}
}
}
궁금하니까 확인 해보면
10, 30, 30 나옴. 같은 힙메모리 주소를 가르키고 있기 때문에
주소도 똑같다.
readnly를 사용하여 구조체를 선언을 하면 컴파일러는 해당 구조체의 모든 필드가
readonly로 선언되도록 강제한다.
안하면 컴파일 에러!
namespace TestCSharp
{
readonly struct RGBColor
{
public readonly byte R;
public readonly byte G;
public readonly byte B;
public RGBColor(byte r, byte g, byte b)
{
R = r;
G = g;
B = b;
}
}
public class Program
{
static void Main(string[] args)
{
RGBColor color = new RGBColor(255, 255, 255);
// color.R = 255; readonly라서 에러가 난다.
}
}
}
readonly라서 생성자를 통해서만 필드 변수의 값 초기화가 가능하다.
여러 필드를 담을 수 있는 구조체이다.
구조체와 달리 형식 이름이 없다.
즉석에서 사용할 복합 데이터 형식을 선언할 경우 적합하다.
또한 튜플은 구조체 이므로 값 형식이다.
var tuple = (123, 456);
var : 컴파일러가 튜플의 모양을 보고 직접 형식을 결정함.
튜플은 System.ValueTuple 구조체를 기반으로 만들어 지기 때문에
123 => Itme1 이라는 필드에 담고, 456 => Item2라는 필드에 담는다.
var tuple = (Name : "박상현", Age : 17);
cw($"{tuple.Name} {tuple.Age}")
사용할려면 당연히
tuple.Item1, tuple.Item2 // 이렇게 사용해야함.
var tuple (Name : "박상현", Age : 17);
var (name, age) = tuple;
cw($"{name} {age}");
var tuple (Name : "박상현", Age : 17);
var (name, _) = tuple;
cw($"{name}"); // '_' 는 특정 필드 무시
현재 튜플이 분해가 가능한 이유는 Deconstructor (분해자)를 구현하고 있기 때문이다.
분해자를 구현하고 있는 객체를 분해한 결과를 switch문이나 switch 식의 분기 조건에 활용가능.
이것을 "위치 패턴 매칭"이라고 한다.
private static double GetDiscountRate(object client)
{
return client switch
{
("학생", int n) when n < 18 => 0.2,
("학생", _) => 0.1,
("일반", int n) when n < 18 => 0.1,
("일반", _) => 0.05,
_ => 0,
};
}
이렇게 위치 패턴 매칭을 사용할 수 있다.
❗지금은 이거 위에는 switch 식이다❗
그래 니가 알고있는 일반적인
int a = 10;
switch(a)
{
case 10:
case 20:
case 30:
}
이런게 switch문임.
var a1 = (job : "학생", age : 17);
var a2 = (job : "학생", age : 23);
var a3 = (job : "일반", age : 15);
var a4 = (job : "일반", age : 21);
Console.WriteLine($"a1 : {GetDiscountRate(a1)}");
Console.WriteLine($"a2 : {GetDiscountRate(a2)}");
Console.WriteLine($"a3 : {GetDiscountRate(a3)}");
Console.WriteLine($"a4 : {GetDiscountRate(a4)}");
ㅇㅋ? => 굿.
구조체는 깊은 복사가 일어나고 값 형식이고 메소드를 가질 수 있다.