[C#] 구조체, ref, out


[C#] 구조체, ref, out

구조체 (Struct)

구조체는 클래스처럼 사용자 정의 형식을 두기 위해 사용한다.

특징

  1. new로 생성해도 되고 안해도 된다.
  2. 기본 생성자는 명시적으로 정의할 수 없다.
  3. 매개변수를 갖는 생성자를 정의해도 디폴트 생성자가 생성된다.(클래스는 생성x)
  4. 매개변수를 받는 생성자의 경우 반드시 해당 코드 내에서 구조체 모든 필드 값을 할당해야 한다.
struct Vector //Vector 구조체 선언
{
    public int X;
    public int Y;
    
    //생성자
    public Vector(int x, int y)
    {
        // 모든 필드 초기화 필수
        this.X = x;
        this.Y = y;
    }
    public override string ToString()
    {
        return "X: " + X + "Y : " + Y;
    }
}

class Program
{
    static void Main(string[] args)
    {
        Vector v1 = new Vector(); //new사용
        Vector v2; // new 없이도 가능
        v2.X = 0;
        Vector v3 = new Vector(5, 10);
        Console.WriteLine(v3);
    }
}

구조체와 클래스는 아주 유사하지만 값 형식이냐 아니면 참조 형식이냐를 차이점으로 두고 있다.

struct Vector //구조체 선언
{
    public int X;
    public int Y;
    public Vector(int x, int y) //생성자
    {
        // 모든 필드 초기화 필수
        this.X = x;
        this.Y = y;
    }
    public override string ToString()
    {
        return "X: " + X + " Y : " + Y;
    }
}

class Point //클래스 선언
{
    public int X;
    public int Y;
    public Point(int x, int y) //생성자
    {
        this.X = x;
        this.Y = y;
    }
    public override string ToString()
    {
        return "X: " + X + " Y : " + Y;
    }
}

class Program
{
    static void Main(string[] args)
    {
        Vector v1;
        v1.X = 5;
        v1.Y = 10;
        Vector v2 = v1; // 값 형식 대입
        v2.X = 7;
        v2.Y = 15;
 
        Console.WriteLine("v1 : " + v1);
        Console.WriteLine("v2 : " + v2);
 
        Point pt1 = new Point(6,12);
        Point pt2 = pt1;
 
        pt2.X = 7;
        pt2.Y = 22;
 
        Console.WriteLine("pt1 : " + pt1);
        Console.WriteLine("pt2 : " + pt2);
    }
}

실행시켰을 때 결과는 다음과 같다.

v1 : X: 5 Y : 10 v2 : X: 7 Y : 15 pt1 : X: 7 Y : 22 pt2 : X: 7 Y : 22

즉, 구조체는 스택영역에 값이 직접 들어가서 깊은 복사가 되지만 클래스는 참조하는 주소가 복사되면서 스택 영역의 변수가 같은 힙 영역을 가리킨다.

클래스와 구조체를 선택하는 기준

  1. 일반적으로 모든 사용자 정의타입은 클래스로 구현
  2. 깊은/얕은 복사 차이가 민감한 타입은 선택적으로 구조체로 구현
  3. 참조 형식은 garbage collector에 의해 관리받게 된다. 참조 형식을 쓸 때 garbage collector에 부담이 되는데, 이런 부하를 피해야 할 때 구조체를 선택한다.

ref, out 예약어

ref와 out은 참조에 의한 호출을 지원하기 위한 예약어이다.

값 형식이든 참조 형식이든 둘 다 엄연히 스택에 있는 값이 복사되는 상황이다.(Call by value)

즉, 값 형식은 스택 영역 메모리에 값이 직접 들어있어서 그것이 복사된 것이고,

참조 형식은 스택 영역 메모리에 힙영역의 주소가 들어있어서 그 주소가 복사되면서 같은 객체를 가리킨 것이다.

하지만 ref 예약어를 사용하면 그 스택 영역 메모리의 주소를 사용하면서 Call by reference를 사용하게 된다.

  • ref를 구조체에서 사용하면 클래스처럼 “얕은 복사”로 전달한 것과 같은 효과가 난다.
static void Main(string[] args)
{
    Vector v1;
    v1.X = 5;
    v1.Y = 10;
 
    Change(ref v1);
    Console.WriteLine(v1);
 
}

static void Change(ref Vector vt) //call by reference
{
    vt.X = 7;
    vt.Y = 11;
}

결과 : x:7 y:11

out과 ref의 차이점

out 예약어도 Call by reference를 가능하게 하는 키워드이다.

  1. out으로 지정된 인자에 넘길 변수는 초기화되지 않아도 된다. 초기화해도 out인자를 받는 메서드에서 그 값을 사용할 수 없다.
  2. out으로 지정된 인자를 받는 메서드는 반드시 변수에 값을 넣어서 반환해야 한다.

즉, out은 ref의 기능 가운데 몇가지를 강제적으로 제한하면서 특별한 용도를 가지는 것이다.

bool Divide(int n1, int n2, out int result)
{
    if(n2 == 0)
    {
        result = 0;
        return false;
    }
    result = n1/n2;
    return true;
}
 
int quotient;
if(Divide(15,3,out quotient) == true)
{
    Console.WriteLine(quotient); // 출력 : 5
}

위와 같이 짜는 이유는 단순히 결과만 리턴하면 분모가 0일 경우 0을 리턴하지만 이게 분모에 0을 줘서 0을 리턴 받았는지 실제로 몫이 0이어서 0을 리턴했는지 알 수가 없기 때문이다.

결과값을 반드시 넣어서 리턴해야하는 약속을 지켰다.

만약 5번째줄의 result = 0; 에서 할당을 안했다면 리턴할 때 에러가 발생할 것이다.