구조체

구조체는 타입이 다른 변수들의 집합이다. 다양한 타입의 변수들을 멤버로 포함할 수 있는데 멤버의 개수에 제한이 없기 때문에 때로는 덩치가 굉장히 커질 수도 있다. 그러나 아무리 커지더라도 구조체는 스택에 생성되는 값 타입이며 구조체 변수끼리 대입도 가능하다. 구조체가 값 타입이기 때문에 구조체 형식으로 정의되어 있는 Int32, Double 등이 모두 값 타입인 것이다. 선언하는 기본 형식은 다음과 같다.

struct 이름
{
  
멤버 선언문;
}

선언문 끝에 세미콜론이 없다는 것만 제외하고는 C++의 선언문과 동일하다. 앞의 열거형에서 잠시 봤다시피 C#은 타입 정의문 끝에는 세미콜론을 붙이지 않는다. 이는 클래스도 마찬가지이다. 구조체 이름은 사용자가 정의한 타입이므로 이 이름으로부터 구조체 변수를 선언할 수 있다. 필요하다면 메서드도 포함할 수 있으며 심지어 선언될 때 자동으로 호출되는 생성자까지도 정의할 수 있다. 구조체의 멤버를 참조할 때는 . 연산자를 사용한다. 모든 면에서 C++의 구조체와 비슷하지만 다른 점도 많다.

첫 째로 구조체 멤버의 디폴트 액세스 지정은 private이므로 그냥 선언하면 외부에서 이 멤버를 참조할 수 없다. 외부로 공개할 멤버들은 반드시 public 엑세스 지정을 해야 한다. C++의 구조체는 C의 구조체와 역호환성을 유지하기 위해 어쩔 수 없이 모든 멤버를 공개해야 했지만 C#은 이런 부담이 없으므로 적극적으로 정보를 은폐할 수 있다.

두 번째로 구조체는 봉인되어 있으므로 기반 클래스로 사용할 수 없다. 즉, 구조체로부터 다른 구조체나 클래스를 파생시킬 수는 없다. 상속을 할 수 없으므로 멤버의 액세스 지정자에 protected를 사용할 수 없다. 구조체는 어디까지나 단순한 값의 집합일 뿐이며 상속 계층을 구성해야 한다면 클래스를 사용해야 한다.

책에 대한 정보를 하나의 변수로 표현하려면 구조체가 적당하다. 책 하나를 표현하기 위해서는 제목, 저자, 출판사, 가격, 출판 년도, 페이지 수 등의 여러 가지 정보가 필요하며 이 정보들은 모두 타입이 다르다. 이처럼 타입이 다른 변수들은 하나의 구조체로 묶을 수 있다. 다음 예제는 책을 표현하는 Book 구조체를 정의하는데 편의상 필드는 제목과 가격만 포함했다.




이 예제에서 보다시피 구조체 선언이 Main보다 뒤쪽에 와도 상관없다. C++은 명칭을 사용하기 전에 반드시 먼저 선언해야 하나 C#은 그렇지 않으므로 선언 순서에 신경쓸 필요가 없고 전방 선언이라는 것도 할 필요가 없다. 어디엔가 선언되어 있기만 하면 컴파일러가 타입 정보를 제대로 찾아 내므로 굉장히 편리하다. 클래스 선언문도 마찬가지이다.

Book 구조체에는 문자열형의 제목(Name)과 정수형의 가격(Pirce)이 필드로 포함되어 있다. 둘 다 외부에서 읽을 수 있도록 public으로 선언했다. Main에서는 Book 타입의 구조체 변수 b를 선언하고 b의 Name과 Price 필드에 제목과 가격을 대입하여 초기화했다. 구조체의 멤버를 액세스할 때는 C에서와 마찬가지로 . 연산자를 사용한다. 구조체가 제대로 초기화되었는지 확인하기 위해 WriteLine 메서드로 구조체 내용을 출력해 보았다.

구조체는 스택에 생성되는 값 타입이므로 선언한 후 곧바로 사용할 수 있다. 하지만 참조 타입인 클래스와 사용 방법을 일관되게 통일하기 위해 new 연산자를 쓰는 방법도 지원한다. 위 예제의 선언문을 다음과 같이 수정해도 결과는 동일하다.

Book b = new Book();

그러나 이는 어디까지나 형식을 일치시키기 위한 예외 적용일 뿐이지 new로 할당한다고 하더라도 구조체는 여전히 스택에 생성되는 값 타입이다. C#이 이 문법을 지원하는 이유는 통일성과 생성자 호출을 위해서이다. 구조체를 선언함과 동시에 초기화하려면 생성자를 정의해야 한다. Book 구조체에 생성자와 메서드를 추가해 보자.



제목과 가격을 인수로 받아 자신의 멤버를 초기화하는 생성자를 정의했다. 생성자는 구조체의 타입명과 동일한 이름을 가져야 하며 결과를 리턴할 수는 없다. 생성자가 정의되어 있으므로 Main에서는 new 연산자로 Book 생성자를 호출하여 선언과 동시에 초기화할 수 있다. C++처럼 다음 형식으로 구조체 변수를 선언 및 초기화하는 것은 허용되지 않는다.

Book b("노점상으로 떼돈벌기", 10000);             // 에러

반드시 new 연산자로 생성자를 호출해야 한다. 구조체가 생성자를 정의하지 않으면 컴파일러가 디폴트 생성자를 알아서 만드는데 디폴트 생성자는 모든 필드를 기본값으로 초기화한다. 만약 생성자가 하나라도 정의되어 있으면 이때는 디폴트 생성자를 만들지 않는다. 필요할 경우 여러 버전의 생성자를 오버로딩할 수는 있지만 인수를 취하지 않는 디폴트 생성자를 사용자가 직접 정의할 수는 없도록 되어 있다.

구조체는 클래스에 비해서는 확실히 기능상 열등한 존재이다. 참조 타입인 클래스는 힙에 생성되므로 스택을 덜 차지하며 상속도 가능하고 다형성도 지원한다. 그러나 구조체는 간단하다는 면에서 여전히 쓸모가 있는데 예를 들어 좌표를 표현하는 타입을 정의하고 싶다고 하자. 아마 다음과 같은 구조체를 정의할 것이다.

struct Point
{
     public int x, y;
}

이 구조체에는 좌표의 수평, 수직값을 기억하는 x, y 두 개의 필드밖에 없다. 이런 단순한 타입을 만들기 위해 생성자를 정의하고 변수가 필요할 때마다 new를 호출하는 것은 무척 번거로우며 고작 8바이트밖에 안되는 메모리를 힙에 할당하여 가비지 컬렉터를 괴롭힐 필요가 없다. 좌표에 대한 특별한 동작이 필요하지도 않으므로 꼭 필요한 메서드도 사실 없는 셈이다. 이런 단순한 타입을 정의할 때는 이형 변수 집합인 구조체가 딱 어울린다.

크기를 표현하는 Size나 사각영역을 표현하는 Rect 등도 구조체로 만드는 것이 적합하다. 그러나 구조체는 값 타입이기 때문에 너무 커서는 안된다. 스택을 지나치게 많이 차지하며 구조체끼리의 대입은 멤버 전체를 복사하는 느린 연산이기 때문에 함수의 인수로 사용하면 호출 속도가 느려진다. 이에 비해 참조 타입인 클래스는 복사해도 참조만 복사되므로 속도가 대단히 빠르다. C#에서 구조체와 클래스는 용도가 분명히 다르다 
Posted by 코딩하는 야구쟁이
,