모각코

[2025 하계 모각코] 5회차 결과 - 구조체

pengine 2025. 7. 29. 21:55

활동 목표: 구조체 학습

 

활동 결과:

 

1. 구조체

 

1.1. 구조체의 개념

구조체란 여러 자료형을 가진 변수를 하나로 묶어 사용하는 것을 말한다.

Python을 배우고 C를 공부하고 있는 입장에서 처음 봤을 때 Class와 유사하다고 생각하였다.

결과적으로는 데이터 묶음이라는 형식 측면에서 유사하고 Python의 객체 지향 형식을 지원하지 않는다는 차이가 있다.

한 마디로 정의하자면, 구조체는 여러 자료형의 묶음을 하나의 단위로 여려 변수에서 접근할 수 있도록 해 주는 것이다.

. 연산자를 통해 구조체 내의 변수에 접근할 수 있다.

구조체를 선언하는 방법은 아래와 같다.

#include <stdio.h>
#include <string.h>

struct Student{  // 구조체 선언
    char name[50];
    int id;
    float gpa;
};

int main(){
    struct Student stu1;       // 구조체 변수 선언
    strcpy(stu1.name, "Lee");  //값 할당
    stu1.id = 20250001;
    stu1.gpa = 4.4;

    struct Student stu2 = {    // 구조체 변수 선언과 동시에 멤버 초기화
        "Kim",
        20250002,
        3.8
    };
    
    struct Student stu3 = {    // 구조체 변수 선언과 동시에 멤버 지정 초기화
        .name = "Choi",
        .gpa = 4.1,
        .id = 20250003
    };
    printf("%f\n", stu1.gpa);
    printf("%s\n", stu2.name);
    printf("%d\n", stu3.id);
    return 0;
}

>> 4.400000
Kim
20250003

 

위 코드에서 구조체, 구조체 변수, 멤버라는 용어가 나오는데

Student 구조체에서 stu1, stu2, stu3이라는 구조체 변수가 존재하고 그 아래에 있는 name, id, gpa가 멤버라고 보면 된다.

 

1.2. typedef

typedef란 기존 자료형에 새로운 별칭을 붙여주는 것이다. 

#include <stdio.h>

typedef int jeong_su;
typedef char mun_za;


int main(){
    jeong_su num1 = 1;
    mun_za name[20] = "일";
    printf("%s=%d", name, num1);
    return 0;
}

>> 일=1

 

 

typedef는 주로 구조체에 많이 사용된다. 구조체에서 구조체 변수를 선언할 때 매번 struct 구조체 구조체변수 형식을 써야 하는 것을 더욱 편하고 가독성있게 만들어 준다.

 

#include <stdio.h>
#include <string.h>

typedef struct Student{
    char name[50];
    int id;
    float gpa;
} Student;

int main(){
    Student stu1;
    strcpy(stu1.name, "Lee");
    stu1.id = 20250001;
    stu1.gpa = 4.4;

    Student stu2 = {
        "Kim",
        20250002,
        3.8
    };
    
    Student stu3 = {
        .name = "Choi",
        .gpa = 4.1,
        .id = 20250003
    };
    printf("%f\n", stu1.gpa);
    printf("%s\n", stu2.name);
    printf("%d\n", stu3.id);
    return 0;
}

 

기존 자료형에 새로운 별칭을 붙여준다고 했는데 그러면 구조체의 (struct 구조체이름)이 하나의 자료형인가?

맞다. (struct 구조체이름)을 통해 하나의 자료형을 생성해내는 것이다.

 

 

1.3. 구조체 배열

구조체 배열이란 여러 구조체 변수를 모아 둔 배열이다. 구조체 배열은 코드를 더욱 깔끔하고 가독성 있으며 조직적으로 짤 수 있도록 도와준다.

#include <stdio.h>
#include <string.h>

typedef struct Student{
    char name[50];
    int id;
    float gpa;
} Student;

int main(){
    Student students[3] = {
        {"Lee", 20250001, 4.4},
        {"Kim", 20250002, 3.8},
        {"Choi", 20250003, 4.1}
    };

    printf("%f\n", students[0].gpa);
    printf("%s\n", students[1].name);
    printf("%d\n", students[2].id);
    return 0;
}

>> 4.400000
Kim
20250003

 

1.4. 구조체 포인터와 동적 메모리 할당

구조체 포인터란 구조체 변수의 메모리 주소를 가리키는 포인터이다. 

구조체 포인터에선 -> 연산자를 쓸 수 있는데, ptr->id = (*ptr).id 와 같이 역참조 후 . 연산자를 사용한 것과 동일하다.

#include <stdio.h>

typedef struct Student{
    char name[50];
    int id;
    float gpa;
} Student;

int main(){
    Student stu1;
    Student *ptr = &stu1;
    ptr->id = 20250001;
    printf("%d", stu1.id);
    return 0;
}

>> 20250001

 

구조체 배열과 구조체 포인터를 이용해 동적 할당하는 코드를 짜 보았다. (여러 수정을 거치며..)

배우지 않은 내용이 있는데 간단히 설명하자면 scanf로 입력을 받아 저장할 때, 버퍼를 거치는데 버퍼에 개행 문자 '\n'가 존재한 채로 입력을 받으면 개행 문자 '\n'를 인식하고 입력을 중지한다. 따라서 입력을 받은 뒤 버퍼를 비워줘야 하는데, getchar()는 입력 버퍼에 있는 문자를 반환해주는 함수이다. 따라서 입력 버퍼가 '\n' 일 때까지 받아서 종료해준다.

 

또한 %49s라고 쓴 부분은 name의 길이가 49자(49 + \0)가 넘어가면 버퍼 오버플로우가 발생해 보안상 매우 위험한 코드가 될 수 있으므로 입력을 49자로 제한해 버퍼 오버플로우를 방지했다.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

typedef struct Student{
    char name[50];
    int id;
    float gpa;
} Student;

void clear_buffer(){  // 입력 버퍼 비우기 함수
    int c;
    while ((c = getchar()) != '\n' && c != EOF);
}

int main(){
    int num;
    printf("enter number of student ");
    scanf("%d", &num);
    clear_buffer();
    Student *students;
    students = (Student*)malloc(num * sizeof(Student));

    for (int i = 0; i < num; i++){
        printf("student%d name: ", i + 1);
        scanf("%49s", students[i].name);  // 버퍼 오버플로우 방지
        clear_buffer();
        students[i].id = 20250001 + i;
        printf("student%d gpa: ", i + 1);
        scanf("%f", &students[i].gpa);
        clear_buffer();
    };
    
    for (int i = 0; i < num; i++){
        printf("%s\n", students[i].name);
        printf("%d\n", students[i].id);
        printf("%f\n", students[i].gpa);
    };

    free(students);
    students = NULL;

    return 0;
}

>> enter number of student 3
student1 name: Lee
student1 gpa: 4.4
student2 name: Kim
student2 gpa: 3.8
student3 name: Choi
student3 gpa: 4.1
Lee
20250001
4.400000
Kim
20250002
3.800000
Choi
20250003
4.100000

 

1.5. 구조체와 함수

구조체는 함수와 같이 사용해 활용할 수 있다.

구조체를 반환, 구조체의 포인터를 반환, 구조체를 함수의 인자로 받아 활용할 수 있다.

구조체의 크기가 커지면 부담이 크기 때문에 3회차 때 배운 것과 같이 포인터로 전달하고 반환한다.

또한 const를 이용해 안정성을 높일 수 있다.

 

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

typedef struct Student{
    char name[50];
    int id;
    float gpa;
} Student;

Student newStudent(const char *name, int id, float gpa){  // 구조체를 반환
    Student news;
    strcpy(news.name, name);
    news.id = id;
    news.gpa = gpa;
    return news;
};

Student *newDynamicStudent(const char *name, int id, float gpa){  // 구조체의 포인터를 반환
    Student *newds = (Student*)malloc(sizeof(Student));
    if (newds == NULL){
        return NULL;
    };
    strcpy(newds->name, name);
    newds->id = id;
    newds->gpa = gpa;
    return newds;
};
float get_gpa(Student *student){  // 함수의 인자로 받아 활용
    return student->gpa;
}

int main(){
    Student student1 = newStudent("Lee", 20250001, 4.4);
    Student *student2 = newDynamicStudent("Kim", 20250002, 3.8);
    if (student2 == NULL){
        return 0;
    };
    printf("%d\n", student1.id);
    printf("%s\n", student2->name);
    printf("%f\n", get_gpa(student2));
    free(student2);
    return 0;
}
    
>> 20250001
Kim
3.800000