C# 중급 문법 (5): 구조체(Struct) - 가볍고 빠른 값 형식!
안녕하세요! C# 객체 지향 프로그래밍의 중요한 개념들인 클래스, 상속, 다형성, 추상 클래스, 인터페이스에 대해 알아보았습니다. 이 모든 것은 '참조 형식'인 클래스를 중심으로 설명되었죠. 이번 포스팅에서는 클래스와 유사해 보이지만 중요한 차이점을 가진 **구조체(Struct)**에 대해 다루어 보겠습니다. 구조체는 C#에서 '값 형식'의 핵심이며, 특정 상황에서 클래스보다 더 효율적인 대안이 될 수 있습니다.
1. 구조체(Struct)란 무엇일까요?
**구조체(Struct)**는 C#에서 클래스와 함께 사용자가 직접 정의할 수 있는 복합 데이터 형식입니다. 클래스처럼 필드, 속성, 메서드, 생성자 등을 가질 수 있습니다. 하지만, 클래스와 결정적으로 다른 점은 구조체가 '값 형식(Value Type)'이라는 것입니다.
- 값 형식 (Value Type):
- 데이터 자체가 변수에 직접 저장됩니다.
- 변수에 값을 할당하거나 메서드에 인수로 전달할 때, 데이터의 복사본이 생성됩니다.
- 스택(Stack) 메모리에 할당되는 경우가 많아 클래스(힙 메모리)보다 빠를 수 있습니다.
- 참조 형식 (Reference Type - 클래스):
- 변수에 실제 데이터가 아닌, 데이터가 저장된 메모리 주소(참조)가 저장됩니다.
- 할당이나 전달 시에는 주소만 복사되므로, 여러 변수가 동일한 데이터를 가리킬 수 있습니다.
- 힙(Heap) 메모리에 할당되며, 가비지 컬렉터의 관리 대상이 됩니다.
간단히 말해, 구조체는 클래스보다 더 가볍고 데이터의 복사본으로 작동하는 데이터 묶음이라고 생각할 수 있습니다.
2. 구조체의 주요 특징 및 제약 사항 🚫
구조체는 값 형식이기 때문에 클래스와 비교했을 때 몇 가지 중요한 특징과 제약 사항을 가집니다.
- 상속 불가: 구조체는 다른 구조체나 클래스를 상속할 수 없습니다. 또한, 구조체는 다른 클래스나 구조체의 부모가 될 수도 없습니다. (단, 인터페이스는 구현할 수 있습니다.)
- 기본 생성자: C# 10부터는 구조체에도 매개변수 없는 기본 생성자를 명시적으로 정의할 수 있습니다. 이전 버전에서는 기본 생성자를 정의할 수 없었고, 모든 필드는 자동으로 기본값으로 초기화되었습니다.
- 필드 초기화: C# 10부터는 구조체 필드를 선언과 동시에 초기화할 수 있습니다.
- new 키워드 사용: 구조체도 new 키워드를 사용하여 인스턴스화할 수 있습니다. 이 경우 모든 필드가 기본값으로 초기화됩니다. new를 사용하지 않고도 선언만으로 사용할 수 있지만, 사용 전에 모든 필드를 수동으로 초기화해야 합니다.
- 값에 의한 전달: 구조체를 메서드에 전달하거나 다른 변수에 할당할 때, 항상 값의 복사본이 전달됩니다. 따라서 원본 데이터는 변경되지 않습니다.
3. 구조체 구현 및 사용 예시
간단한 2D 좌표를 나타내는 Point 구조체를 만들어 보겠습니다.
// Point 구조체 정의
public struct Point
{
// 속성 (필드를 직접 노출하거나, 캡슐화를 위해 private 필드 + public 속성 사용 가능)
public int X { get; set; }
public int Y { get; set; }
// 생성자: C# 10 이전에는 필드를 선언과 동시에 초기화할 수 없었으나, 현재는 가능
public Point(int x, int y)
{
X = x; // 모든 필드를 생성자에서 초기화해야 합니다.
Y = y;
Console.WriteLine($"Point ({X}, {Y}) 생성됨.");
}
// 메서드
public void DisplayCoordinates()
{
Console.WriteLine($"좌표: ({X}, {Y})");
}
// 메서드: Point 객체의 값을 변경하는 메서드 (새로운 복사본을 반환하는 것이 일반적)
public Point Move(int deltaX, int deltaY)
{
// 구조체는 값을 복사하므로, 일반적으로 변경된 새 구조체를 반환합니다.
// this.X += deltaX; this.Y += deltaY; 와 같이 내부에서 직접 변경하는 것도 가능하지만
// 값 형식의 불변성(immutable)을 유지하는 것이 권장됩니다.
return new Point(X + deltaX, Y + deltaY);
}
}
구조체 사용 예시:
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine("--- 구조체 생성 및 사용 ---");
// 1. new 키워드를 사용한 구조체 생성
Point p1 = new Point(10, 20);
p1.DisplayCoordinates(); // 출력: 좌표: (10, 20)
// 2. new 키워드 없이 구조체 변수 선언 (모든 필드 수동 초기화 필요, C# 11부터는 기본값으로 초기화됨)
Point p2;
p2.X = 5;
p2.Y = 15;
p2.DisplayCoordinates(); // 출력: 좌표: (5, 15)
Console.WriteLine("\n--- 값 형식으로서의 동작 확인 (복사) ---");
Point p3 = p1; // p1의 값(10, 20)이 p3로 '복사'됨
Console.WriteLine($"p1: ({p1.X}, {p1.Y})"); // 출력: p1: (10, 20)
Console.WriteLine($"p3: ({p3.X}, {p3.Y})"); // 출력: p3: (10, 20)
// p3의 값 변경 (p1에는 영향 없음)
p3.X = 100;
Console.WriteLine($"p3.X 변경 후:");
Console.WriteLine($"p1: ({p1.X}, {p1.Y})"); // 출력: p1: (10, 20) (변화 없음)
Console.WriteLine($"p3: ({p3.X}, {p3.Y})"); // 출력: p3: (100, 20)
Console.WriteLine("\n--- 메서드에 구조체 전달 ---");
ChangePoint(p1); // p1의 복사본이 전달됨
Console.WriteLine($"ChangePoint 호출 후 p1: ({p1.X}, {p1.Y})"); // 출력: p1: (10, 20) (변화 없음)
Console.WriteLine("\n--- 구조체 내부 메서드를 통한 변경 (새로운 구조체 반환) ---");
Point p4 = new Point(1, 1);
p4.DisplayCoordinates(); // 출력: 좌표: (1, 1)
Point movedP4 = p4.Move(5, 5); // Move 메서드는 새로운 Point 객체를 반환
Console.WriteLine($"원래 p4: ({p4.X}, {p4.Y})"); // 출력: 원래 p4: (1, 1) (변화 없음)
Console.WriteLine($"이동된 movedP4: ({movedP4.X}, {movedP4.Y})"); // 출력: 이동된 movedP4: (6, 6)
}
// 메서드에 구조체 인자를 전달 (값 복사)
public static void ChangePoint(Point point)
{
point.X = 999;
point.Y = 888;
Console.WriteLine($"ChangePoint 메서드 내: ({point.X}, {point.Y})"); // 출력: ChangePoint 메서드 내: (999, 888)
}
}
위 예시에서 가장 중요한 점은 p3 = p1과 ChangePoint(p1) 호출 시, 값 자체가 복사된다는 것입니다. 클래스였다면 참조가 복사되어 원본이 변경되었겠지만, 구조체는 독립적인 복사본이 생성되어 원본에 영향을 주지 않습니다.
4. 구조체는 언제 사용하는 것이 좋을까요? 💡
클래스 대신 구조체를 사용하는 것이 더 유리한 경우는 다음과 같습니다:
- 작은 데이터 묶음: Point, Size, Color, DateTime 등 작고 간단한 데이터를 표현할 때 적합합니다.
- 불변(Immutable) 객체: 생성된 후 내부 값이 변경되지 않는 객체를 표현할 때 유리합니다. 값 형식의 특성상 복사본을 만들어 변경하므로 불변성을 유지하기 좋습니다.
- 성능 최적화:
- 힙 할당 및 가비지 컬렉션 오버헤드를 줄여야 할 때 (구조체는 주로 스택에 할당되거나, 배열 내에 인라인으로 저장됨).
- 매우 많은 수의 작은 객체를 생성해야 할 때.
- 객체의 수명이 짧고 자주 생성/소멸될 때.
그러나:
- 구조체 크기가 너무 커지면 값 복사에 드는 비용이 오히려 성능 저하를 일으킬 수 있습니다. 일반적으로 16바이트 미만의 크기일 때 이점을 가집니다.
- 다형성이나 상속이 필요한 경우에는 구조체를 사용할 수 없습니다. 이 때는 클래스를 사용해야 합니다.
마무리하며: 값 형식의 이해, C# 프로그래밍의 깊이! 🎓
구조체는 C#에서 값 형식의 대표 주자로서, 클래스와는 다른 메모리 관리 및 동작 방식을 가집니다. 이 차이점을 이해하는 것은 C#의 성능 특성을 파악하고, 특정 상황에 맞는 최적의 데이터 형식을 선택하는 데 매우 중요합니다.
작고 불변하며 성능이 중요한 데이터를 다룰 때는 구조체를 고려하고, 상속, 다형성, 더 복잡한 객체 상태 관리가 필요할 때는 클래스를 선택하는 것이 일반적인 좋은 접근 방식입니다.
구조체에 대해 더 궁금한 점이나 특정 사용 시나리오에 대한 질문이 있으신가요? 언제든지 댓글로 질문해주세요! 다음 포스팅에서는 C#의 또 다른 중요한 개념인 인터덱스에 대해 알아보겠습니다.
'C# > C# 문법' 카테고리의 다른 글
| C# 중급 문법 (7) : 델리게이트와 이벤트 (1) | 2025.08.31 |
|---|---|
| C# 중급 문법 (6) : Static에 대해서 (1) | 2025.08.20 |
| C# 중급 문법 (4) : 추상 클래스와 인터페이스 (4) | 2025.08.13 |
| C# 중급 문법 (3) : 상속과 다형성 (4) | 2025.08.09 |
| C# 중급 문법 (2) : 생성자와 캡슐화 (2) | 2025.08.05 |