구조체 (Struct, Structure)
구조체는 여러 데이터를 하나의 단위로 묶어서 표현할 수 있는 사용자 정의 자료형이다.
공식 문서에서는
구조체는 값 형식으로, 여러 개의 관련된 변수를 하나의 단위로 캡슐화하는 자료형입니다.
라고 되어있다.
열거형 보다는 알아들을만 하다.
즉, 여러 개의 변수를 하나의 박스 안에 묶어 놓은 것이 구조체인데 이것을 잘 활용하면 나만의 커스텀 자료형을 정의해서 사용할 수 있다는 이야기다.
구조체를 선언하는 방식은 아래와 같다.
struct Car
{
public float maxSpeed; // 최고 속도
public string maker; // 제작사
private int carNumber; // 차량 번호 (비공개)
}
이렇게 Car라는 새로운 자료형을 만들어서 직접 변수 선언이 가능하다.
Car firstCar;
firstCar.maxSpeed = 480.5f;
firstCar.Maker = StarkIndustry;
Console.WriteLine(firstCar.maxSpeed); // 480.5f
Console.WriteLine(firstCar.Maker); // StarkIndustry
왜 이런게 필요한지 어떻게 활용하는지 와닿지 않을 수 있다.
예시 코드를 더 확인해보자.
using System;
namespace structure
{
internal class Program
{
static void Main(string[] args)
{
float maxSpeedCar1 = 220.4f; // 첫째 자동차 최고 속도
string makerCar1 = "Honda";
int carNumberCar1 = 1234;
float maxSpeedCar2 = 180.4f;
string makerCar2 = "Toyota";
int carNumberCar2 = 2345;
float maxSpeedCar3 = 150f;
string makerCar3 = "Kia";
int carNumberCar3 = 3456;
Console.WriteLine(maxSpeedCar1);
Console.WriteLine(makerCar1);
Console.WriteLine(carNumberCar1);
Console.WriteLine(maxSpeedCar2);
Console.WriteLine(makerCar2);
Console.WriteLine(carNumberCar2);
Console.WriteLine(maxSpeedCar3);
Console.WriteLine(makerCar3);
Console.WriteLine(carNumberCar3);
}
}
}
이런 코드가 있다고 가정해보자.
지금은 메이커가 3개 뿐이지만 저게 수십개가 되고, 관리해야할 변수가 늘어나면 정신없이 나열되어 있을 것이다.
작성자도 헷갈릴 거 같은데 협업 시 남이 코드를 보면 읽는데만 더 많은 시간을 사용하게 될 것이고 휴먼 에러 발생 확률도 높아진다.
그래서 이걸 구조체로 나누어 보면 어떻게 되느냐?
using System;
namespace structure
{
internal class Program
{
struct Car
{
public float maxSpeed;
public string maker;
private int carNumber;
}
static void Main(string[] args)
{
Car firstCar;
firstCar.maxSpeed = 12.14f;
firstCar.maker = "Tesla";
Car SecCar;
SecCar.maxSpeed = 222.14f;
SecCar.maker = "Tesla";
Console.WriteLine(firstCar.maxSpeed);
Console.WriteLine(firstCar.maker);
Console.WriteLine(SecCar.maxSpeed);
Console.WriteLine(SecCar.maker);
// .. . ..
}
}
}
코드가 줄어들긴 커녕 늘어났다고 할 수도 있지만 코드가 속성에 따라 분류되어 직관적으로 이해하기 더 좋아진 걸 볼 수 있다.
구조체의 특징
- 값 형식 ( Value Type ) - 할당시 값이 복사 됨 (클래스는 참조 형식)
- 기본 생성자 사용 불가 - 직접 초기화 하거나 사용자 정의 생성자를 만들어야 함
- 필드, 메서드, 속성, 이벤트 포함 가능
- 상속 불가 - 클래스와 달리 다른 구조체 / 클래스 를 상속받을 수는 없지만 인터페이스는 구현 가능
또한 구조체는 클래스와 비슷하게 생긴데다가 기능도 비슷해서 헷갈릴 수 있는데 몇가지 차이가 있다.
이걸 간단하게 표로 비교해서 보자.
| 구분 | 구조체 struct | 클래스 class |
| 형식 | 값 형식 Value Type | 참조 형식 Reference Type |
| 메모리 | 스택 Stack | 힙 Heap |
| 상속 | 불가 (인터페이스만 구현 가능) | 가능 |
| 생성자 | 기본 생성자 불가 | 기본 생성자 자동 제공 |
| 복사 | 값 자체를 복사 | 참조(주소)가 복사 |
구조체는 작고 독립적인 데이터 묶음을 만들 때 적합하고
클래스는 상속, 다형성, 참조 공유가 필요한 복잡한 상황에 적합하다.
심화 실습 문제를 풀어보면서 구조체에 대해 조금 더 익혀보자
문제
- Weapon 이라는 구조체 설계도를 하나 만든 후, 속성으로는 public 문자열 형 name을 가지게 하세요.
- Soilder이라는 구조체 설계도를 하나 더 만든 후, 속성으로 Weapon구조체를 담을 수 있는 배열 및 현재 손에 들고 있는 Weapon이 무엇인지 저장할 배열의 인덱스로 사용할 정수형 변수 하나를 작성합니다.
- 메인에 돌아와서 솔져 구조체를 하나 만들어줍니다. 솔져의 무기 목록 배열에 Weapon 구조체를 3개 담을 수 있게 초기화 시켜주시고, 솔져가 가진 무기의 배열에 본인이 희망하는 이름을 담은 무기 3가지를 모두 기입하여 줍니다. 처음엔 1번을 들고 있습니다(인간의 말로)
- 함수를 하나 만들되, 솔져형을 참조형 인자로 받는 void형 함수를 제작할 것입니다. 함수의 이름은 ChangeWeapon으로 하고 기능은 다음과 같습니다.
- 함수가 호출되면 배열 속의 Weapon 이름들을 전부 출력하고 다음줄에 어떤 무기로 바꿀것인지 콘솔에 적습니다.
- 유저로부터 1~3사이 정수를 입력받되 1~3이 아닐 경우, 제대로 1~3사이 정수를 입력하라고 콘솔에 입력 후 무한으로 재입력을 요구합니다.
- 제대로 1,2,3 중 하나를 입력 받았을 경우, 만약 본인의 손에 들고 있는 무기와 같은 무기를 입력하였어도 '현재 들고 있는 무기와 동일합니다' 를 출력 후, 재입력을 요구합니다.
- 본인 손에 들고 있는 무기와는 다른 무기를 정상적으로 골랐다면 콘솔 출력을 통해 해당 무기로 바뀌었음을 출력합니다.
- 이후 메인 문에 ChangeWeapon 함수를 한줄 한줄 3번 실행하되, 아래 각각의 상황에 맞게 호출하세요.
- 시작 시 본인이 들고 있는 무기와 일치하는 숫자를 유저가 입력한 상황.
- 1~3이 아닌 숫자를 입력한 상황.
- 정상적으로 무기 변경이 일어날 수 있는 상황.
- 이외 자유롭게 제작, 자유롭게 테스트
using System;
namespace structure
{
internal class Program
{
struct Weapon
{
public string name;
}
struct Soldier
{
public int currentWeapon;
public Weapon[] weapons;
}
static void Main(string[] args)
{
// 솔져 스티브 정의 및 데이터 설정
Soldier Steve;
Steve.weapons = new Weapon[3];
Steve.weapons[0].name = "Storm Breaker";
Steve.weapons[1].name = "Vibranium Shield";
Steve.weapons[2].name = "Bamboo Spear";
Steve.currentWeapon = 0;
// 무기 변경 함수 호출
ChangeWeapon(ref Steve);
ChangeWeapon(ref Steve);
ChangeWeapon(ref Steve);
// 무기 변경을 정상적으로 3번 완료하면 메인 종료
}
// 무기 변경 함수
static void ChangeWeapon(ref Soldier soldier)
{
// 배열 속 Weapon 이름들 전부 출력
for (int i = 0; i < soldier.weapons.Length; i++)
{
Console.WriteLine(soldier.weapons[i].name);
}
// 입력 무한 요청
int inputNum;
Console.WriteLine($"어떤 무기로 바꿀 것입니까? (1 ~ {soldier.weapons.Length} 입력)");
while (true)
{
if (int.TryParse(Console.ReadLine(), out inputNum))
{
// 솔져 웨폰 범위까지만 입력 가능 (1부터 시작)
if (inputNum > 0 && inputNum <= soldier.weapons.Length)
{
// 현재 무기와 같은 번호 입력시 예외
if (inputNum - 1 == soldier.currentWeapon)
{
Console.WriteLine("현재 들고 있는 무기와 동일합니다");
Console.WriteLine("다시 입력해주세요");
}
else
{
// 정상 입력시 무기 변경 완료 및 루프 종료
Console.WriteLine($"{soldier.weapons[soldier.currentWeapon].name} 에서 {soldier.weapons[inputNum - 1].name} 으로 무기를 변경하였습니다 ");
soldier.currentWeapon = inputNum - 1;
break;
}
}
else
{
Console.WriteLine($"1 ~ {soldier.weapons.Length} 사이 정수를 입력하세요");
}
}
}
}
}
}

잘 작동하는데 여기선 작동 보다는 구조체를 간단하게라도 사용해보는 게 목적인 문제로 보인다.
열거형과 마찬가지로 적절한 곳에서 사용할 수 있도록 의식해서 연습하도록 하자.
'C#' 카테고리의 다른 글
| C# - 콘솔프로젝트 : 숫자 야구 만들기 (0) | 2025.09.12 |
|---|---|
| C# - Window11 에서 Console.SetWindowSize 안 되는 문제 (0) | 2025.09.12 |
| C# - 열거형 (Enum, Enumeration) (0) | 2025.09.12 |
| C# - 재귀함수 (Recursive Function) (0) | 2025.09.11 |
| C# - Main (0) | 2025.09.11 |