개발 일지/C언어

구조체란?

DDD Developer 2024. 8. 23. 21:28
728x90
반응형

 

구조체

 

구조체(struct)는 C 언어에서 여러 개의 변수를 묶어서 하나의 새로운 데이터 타입을 만드는 방법. 마치 레고 블록처럼 서로 다른 모양과 색깔의 블록들을 조립하여 새로운 형태를 만드는 것과 비슷하다. 각 블록은 다른 종류의 데이터(숫자, 문자, 다른 구조체 등)를 나타낼 수 있습니다.

 

왜 구조체를 써야 할까? 

 

데이터 정리 

  • 관련 데이터 묶기: 예를 들어, 학생 정보를 관리한다고 생각해 보자. 이름, 나이, 학번, 주소 등 여러 정보가 필요하다는걸 알 수 있다, 이 정보들을 각각 따로따로 변수로 관리하면 코드가 복잡해지고 실수하기 쉽다. 구조체를 사용하면 이 정보들을 하나로 묶어서 "학생"이라는 새로운 데이터 타입을 만들 수 있다. 마치 서랍장에 물건들을 종류별로 정리하는 것처럼, 관련 데이터를 구조체로 묶어서 코드를 깔끔하게 관리할 수 있다.
  • 재사용성: 한 번 정의한 구조체는 여러 곳에서 재사용할 수 있다. 예를 들어, "학생" 구조체를 정의했다면 여러 학생의 정보를 관리할 때 매번 같은 변수들을 선언할 필요 없이 "학생" 구조체를 사용하면 된다. 마치 붕어빵 틀처럼, 한 번 만든 틀로 여러 개의 붕어빵을 만들 수 있는 것과 같다.

함수 호출 시 편리함 

  • 여러 값 전달: 함수에 여러 개의 값을 전달해야 할 때, 각각의 값을 따로따로 전달하는 것은 번거롭고 에러 발생 가능성도 높습니다. 구조체를 사용하면 여러 개의 값을 하나로 묶어서 함수에 전달할 수 있습니다. 마치 택배 상자에 여러 물건을 담아서 한 번에 보내는 것처럼, 구조체를 사용하면 여러 값을 하나로 묶어서 함수에 편리하게 전달할 수 있습니다.
  • 반환 값: 함수에서 여러 개의 값을 반환해야 할 때도 구조체가 유용합니다. 함수는 하나의 값만 반환할 수 있지만, 구조체를 사용하면 여러 개의 값을 하나로 묶어서 반환할 수 있다. 마치 선물 세트처럼, 여러 가지 선물을 하나의 상자에 담아서 주는 것과 같다.

메모리 관리 효율성 

  • 메모리 할당: 구조체를 사용하면 메모리 할당 및 해제를 효율적으로 관리할 수 있다. 구조체 변수를 선언하면 구조체 내부의 모든 멤버 변수들을 위한 메모리가 한 번에 할당된다. 마치 아파트처럼, 여러 개의 방이 하나의 건물 안에 있는 것과 같다.
  • 데이터 접근: 구조체 멤버 변수에 접근하는 것은 간단하고 효율적입니다. 구조체 변수 이름과 멤버 변수 이름을 점(.)으로 연결하여 접근할 수 있습니다. 마치 건물 이름과 호수를 사용하여 특정 아파트에 접근하는 것과 같다.


구조체의 정의


구조체를 정의하려면 struct 키워드를 사용. 예를들어 "사람"이라는 구조체를 정의해 본다 생각해보자.

struct Person {
    char name[50];   // 이름 (문자열)
    int age;         // 나이 (정수)
    float height;    // 키 (실수)
    float weight;    // 몸무게 (실수)
};

 

위 예제에서 Person이라는 구조체를 정의했다. 이 구조체는 이름, 나이, 키, 몸무게를 포함하고 있습니다.

각각의 멤버는 서로 다른 데이터 타입을 가질 수 있다.

 

 

구조체 변수 선언 및 사용

 

구조체를 정의한 후, 실제로 사용하려면 구조체 변수를 선언해야 한다. 구조체 변수 선언은 일반 변수 선언과 비슷하다

struct Person person1;

 

.이 코드는 Person 구조체 타입의 person1이라는 변수를 선언한다. person1을 사용하여 구조체 멤버에 접근할 수 있다.

 

 

구조체 멤버에 접근


구조체 멤버에 접근할 때는 점(.) 연산자를 사용한다.

예를 들어 person1의 이름을 설정하고, 나이를 설정하는 방법은 다음과 같다.

 

#include <stdio.h>
#include <string.h> // 문자열 복사를 위해 포함

struct Person {
    char name[50];
    int age;
    float height;
    float weight;
};

int main() {
    struct Person person1;

    // 구조체 멤버에 값 할당
    strcpy(person1.name, "John Doe"); // 문자열 복사
    person1.age = 30;
    person1.height = 175.5;
    person1.weight = 70.2;

    // 구조체 멤버 출력
    printf("Name: %s\n", person1.name);
    printf("Age: %d\n", person1.age);
    printf("Height: %.1f cm\n", person1.height);
    printf("Weight: %.1f kg\n", person1.weight);

    return 0;
}

 

이 코드에서는 strcpy 함수를 사용하여 person1.name에 "John Doe"라는 문자열을 복사를했다.

나이, 키, 몸무게는 각각의 타입에 맞는 값을 직접 할당했다.

 

strcpy 함수는 다른 글에 잘 적어놨으니 읽어보시길

 

 

구조체 배열

struct Person people[100]; // 최대 100명의 사람 정보를 저장할 수 있는 배열

 

이렇게 하면 people 배열을 통해 최대 100명의 사람 정보를 저장할 수 있고, 각각의 정보에 접근할 때는 배열 인덱스를 사용하면 된다

 

 

구조체 포인터


구조체는 포인터를 통해서도 접근할 수 있다. 구조체 포인터를 사용하면 메모리 효율적인 코드를 작성할 수 있다.

 

struct Person *ptr;
ptr = &person1; // person1의 주소를 포인터 ptr에 저장

// 포인터를 통해 구조체 멤버에 접근할 때는 -> 연산자를 사용
printf("Name: %s\n", ptr->name);
printf("Age: %d\n", ptr->age);

 

ptr->name은 (*ptr).name과 동일한 표현

 

 

typedef란?

 

typedef는 C 언어에서 데이터 타입에 새로운 이름을 붙여주는 키워드. 즉, typedef를 사용하면 긴 데이터 타입 이름을 짧고 기억하기 쉽게 바꿀 수 있다.

 

 

왜 구조체에 typedef를 사용하는가?

 

typedef를 구조체와 함께 사용하는 이유는 구조체를 더 쉽게 사용하고, 코드 가독성을 높이며, 유지보수를 더 편리하게 하기 위함

 

구조체에 typedef를 사용하는 방법

 

기본적으로 구조체를 사용하려면, 구조체를 정의하고 변수로 선언할 때마다 struct 키워드를 사용해야 한다.

하지만 typedef를 사용하면 이 과정을 더 간단하게 만들 수 있다.

typedef struct {
    char name[50];
    int age;
    float height;
} Person;

Person person1;  // struct 키워드 없이도 구조체 변수를 선언할 수 있음

 

위 코드에서는 typedef를 사용해 struct 키워드를 생략할 수 있다. 이제 Person을 기본 데이터 타입처럼 사용할 수 있다.

 

typedef를 사용하는 것을 실생활의 비유로 설명하면

 

구조체는 서랍장이다. 서랍장에는 여러 칸(필드)이 있어서 각각 다른 물건(데이터)을 보관할 수 있다.

typedef는 서랍장의 라벨이다. 라벨을 붙여서 서랍장을 쉽게 식별할 수 있고, 서랍장을 사용할 때마다 라벨을 보면 어떤 물건을 보관하고 있는지 쉽게 알 수 있다.

 

 

간단하게 잘 작성한 코드는 아니지만 내꺼 코드 예시를 들면서 분석해보겠다.

 

// 플레이어 구조체 정의
typedef struct {
    char name[50];
    ClassType class;
    int level;
    double physicalAttack;
    double magicalAttack;
    int strength;
    int agility;
    int luck;
    int magicPower;
    double defense;
    double resistance;
    double speed;
    double critical;
    int HP;
    int MP;
} Player;
// 클래스별 기본 능력치 구조체 정의
typedef struct {
    int baseHP;
    int baseMP;
    double basePhysicalAttack;
    double baseMagicalAttack;
    double baseDefense;
    double baseResistance;
    double baseSpeed;
    double baseCritical;
} ClassAttributes;
// 직업별 기본 능력치 배열 정의
const ClassAttributes classAttributes[] = {
    { 100, 20, 4.0, 3.0, 4.0, 4.0, 5.0, 2.0 }, // BEGINNER
    { 150, 30, 10.0, 5.0, 8.0, 4.0, 6.0, 2.0 }, // WARRIOR
    { 100, 100, 5.0, 15.0, 4.0, 10.0, 5.0, 10.0 }, // MAGE
    { 120, 40, 7.0, 7.0, 6.0, 6.0, 8.0, 6.0 }, // ARCHER
    { 130, 50, 8.0, 6.0, 5.0, 5.0, 7.0, 8.0 },  // ROGUE
    { 200, 60, 15.0, 8.0, 10.0, 5.0, 7.0, 7.0 }  // HERO
};

 


 

 

 

그리고 내가 어떻게 구조체를 썼는지 설명하겠다.

// 플레이어 속성 계산 함수
void calculateAttributes(Player *player)
{
    // 클래스에 따른 기본 능력치 가져오기
    ClassAttributes attributes = classAttributes[player->class];
    
    // 레벨이 1일 때를 기준으로 능력치 계산
    int levelAdjustment = player->level - 1;

    player->HP = attributes.baseHP + (levelAdjustment * 10);
    player->MP = attributes.baseMP + (levelAdjustment * 5);
    player->physicalAttack = attributes.basePhysicalAttack + (levelAdjustment * 0.5);
    player->magicalAttack = attributes.baseMagicalAttack + (levelAdjustment * 0.5);
    player->defense = attributes.baseDefense + (levelAdjustment * 0.3);
    player->resistance = attributes.baseResistance + (levelAdjustment * 0.3);
    player->speed = attributes.baseSpeed + (levelAdjustment * 0.2);
    player->critical = attributes.baseCritical + (levelAdjustment * 0.1);
}


calculateAttributes() 함수는 내가 플레이어의 레벨과 직업에 따라 능력치가 바뀌게 설정해 놓은 함수다.

 


Player 구조체 포인터 (Player *player)


역할: 함수의 입력으로 플레이어 정보를 담고 있는 Player 구조체의 메모리 주소를 전달받는다.
활용: 포인터를 통해 함수에서 원본 Player 구조체의 변수 (레벨, 직업, HP, MP, 공격력, 방어력 등)에 직접 접근하고 값을 변경할 수 있다.

 

 

ClassAttributes 구조체

 

역할: 각 직업별 기본 능력치를 저장.
활용: player->class(플레이어 직업)  값을 이용하여 classAttributes 배열에서 해당 직업의 기본 능력치(ClassAttributes 구조체)를 가져온다.
-> player->class  =  (*player).class 와 같다

직업별 기본 능력치 가져오기

player->class  값을 이용하여 classAttributes 배열에서 해당 직업의 기본 능력치(ClassAttributes 구조체)를 attributes 변수에 저장 할 수있다.

 


레벨에 따른 능력치 계산

player->level (플레이어의 레벨) 값을 이용하여 레벨 상승에 따른 추가 능력치를 계산.

 


플레이어 능력치 업데이트

계산된 능력치를 player 포인터를 통해 원본 Player 구조체의 각 멤버 변수에 직접 업데이트한다.
(예: player->HP = attributes.baseHP + (levelAdjustment * 10);)

// 플레이어 속성 계산 함수
void calculateAttributes(Player *player)
{
    // 클래스에 따른 기본 능력치 가져오기
    ClassAttributes attributes = classAttributes[player->class];
    
    // 레벨이 1일 때를 기준으로 능력치 계산
    int levelAdjustment = player->level - 1;

    player->HP = attributes.baseHP + (levelAdjustment * 10);
    player->MP = attributes.baseMP + (levelAdjustment * 5);
    player->physicalAttack = attributes.basePhysicalAttack + (levelAdjustment * 0.5);
    player->magicalAttack = attributes.baseMagicalAttack + (levelAdjustment * 0.5);
    player->defense = attributes.baseDefense + (levelAdjustment * 0.3);
    player->resistance = attributes.baseResistance + (levelAdjustment * 0.3);
    player->speed = attributes.baseSpeed + (levelAdjustment * 0.2);
    player->critical = attributes.baseCritical + (levelAdjustment * 0.1);
}

 

어렵게 설명한것 같아서 코드를 좀 더 쉽고 깊게 설명해 보겠다.

 

 

작동 원리

1. 직업별 기본 능력치 가져오기: ClassAttributes attributes = classAttributes[player->class];

 

 

  • playerPlayer 구조체를 가리키는 포인터. 즉, player는 실제 Player 구조체가 저장된 메모리 주소를 가지고 있다.
  • '->' 연산자를 사용하여 player가 가리키는 Player 구조체의 class 멤버에 접근.
  • player->class는 플레이어의 직업 유형을 나타내는 ClassType 열거형 값. (예: BEGINNER는 0, WARRIOR는 1)

 

classAttributes[player->class]:

 

 

  • classAttributesClassAttributes 구조체 배열. 각 배열 요소는 특정 직업의 기본 능력치 정보를 담고 있다.
  • player->class 값을 인덱스로 사용하여 classAttributes 배열에서 해당 직업에 맞는 ClassAttributes 구조체를 가져온다. (예 player->classWARRIOR (즉, 1)이라면, classAttributes[1]에 저장된 전사의 기본 능력치 정보를 가져온다.)

ClassAttributes attributes =  

 

가져온 ClassAttributes 구조체를 attributes라는 새로운 변수에 복사하여 저장한다.

이제 attributes 변수를 통해 해당 직업의 기본 HP, MP, 공격력, 방어력 등의 정보에 접근할 수 있다.

(예: attributes.baseHP, attributes.baseMP)

 

 

레벨에 따른 능력치 계산: int levelAdjustment = player->level - 1;

  • player->level: player 포인터를 통해 Player 구조체의 level 멤버에 접근하여 플레이어의 현재 레벨 값을 가져온다.
  • levelAdjustment = player->level - 1: 플레이어의 레벨이 1일 때를 기준으로 능력치를 계산하기 위해 levelAdjustment 변수에 레벨에서 1을 뺀 값을 저장했다

 

플레이어 능력치 업데이트: player->HP = attributes.baseHP + (levelAdjustment * 10);

 

  • attributes.baseHP: 직업별 기본 능력치(attributes)에서 해당 직업의 기본 HP 값을 가져온다.
  • levelAdjustment * 10: 레벨 상승에 따른 추가 HP를 계산. (레벨 1당 HP 10씩 증가)
  • = 연산자를 사용하여 계산된 최종 HP 값을 player 포인터를 통해 원본 Player 구조체의 HP 멤버 변수에 저장.
  • 이와 같은 방식으로 MP, physicalAttack, magicalAttack, defense, resistance, speed, critical 값도 계산하고 업데이트.

 

 

정리:

  • calculateAttributes 함수는 Player 구조체 포인터를 통해 플레이어의 레벨과 직업 정보에 접근.
  • classAttributes 배열과 ClassAttributes 구조체를 활용하여 직업별 기본 능력치 정보를 가져온다.
  • 레벨에 따른 추가 능력치를 계산하고, 기본 능력치와 합산하여 최종 능력치를 계산.
  • 계산된 최종 능력치를 Player 구조체 포인터를 통해 원본 Player 구조체에 저장

이렇게 내꺼 코드를 예시로 들어서 구조체를 어떻게 사용하는지 왜 사용하는지 알아봤다.


 

 

 

 

728x90
반응형