3 분 소요

목요일 4번 문제

image-20231230154210231

위 코드는 “f2.txt”파일에 있는 단어들을 동적할당된 이차원배열에서 받아가지고 qsort()를 써서 값을 오름차순으로 재배열 하는 문제이다.

이때 compare()함수를 구현해주어야 하는데, 이게 생각보다 성가시다ㅜㅠ.

그래서 이 부분에 대해서만 집중적으로 정리해보려고 한다.

int compare(const void *a, const void *b)
{
    return strcmp(*(const char **)a, *(const char **)b);
}

코드리뷰: 여기서 ab는 비교될 두 요소를 가리키는 포인터입니다. 이 비교함수는 문자열 포인터들의 배열을 정렬하는데 사용됩니다.

1.const char **aconst char **b로 형변환하여 문자열 포인터를 가리키는 포인터로 캐스팅합니다. 이는 qsort 함수에서 제공되는 요소들이 const void *형태로 전달되기 떄문입니다.

2.strcmp(*(const char*)a, *(const **)b)를 통해 두 문자열을 비교합니다.

여기서 *(const char **)a*(const char**)b는 각각 ab가 가리키는 문자열 나타냅니다.

3.strcmp함수의 결과에 따라 비교 결과를 반환합니다. strcmp의 반환값이 양수이면 ab보다 크다는 것을 의미하고, ab보다 작다는 것을 의미합니다. 만약 두 문자열이 같으면 0을 반환합니다.

이렇게 구성된 compare함수는 qsort함수에 전달되어 배열의 정렬을 수행합니다. 함수가 제대로 동작하려면 배열의 각 요소가 문자열을 가리키는 포인터여야 합니다. 이러한 형태의 함수를 사용하는 것은 포인터를 활용하여 배열의 요소를 정렬하는 일반적인 방법 중 하나입니다.

금요일 5번문제

일단 학생정보를 파일에서 읽어와서 Student구조체의 배열을 생성하는 프로그램을 작성해보자.

typedef struct {
    int snum;         // 학번
    char name[20];    // 이름
    double score1;    // 점수1
    double score2;    // 점수2
    double quiz;      // 퀴즈 점수
} Student;

Student구조체는 학생의 정보를 나타냅니다. 학번(snum), 이름(name), 점수1(score1), 점수2(score2), 퀴즈점수(quiz)를 저장할 수 있습니다.

void readFromFile(Student** student, int* n, FILE *file_import)
{
    // 학생 구조체 배열 동적 할당
    *student = (Student*)malloc(*n * sizeof(Student));
    if (*student == NULL) {
        fprintf(stderr, "메모리를 할당할 수 없습니다.");
        exit(1);
    }

    // 파일에서 학생 정보 읽기
    for (int i = 0; i < *n; i++)
    {
        fscanf(file_import, "%s %lf %lf %lf",
            (*student)[i].name,
            &(*student)[i].score1,
            &(*student)[i].score2,
            &(*student)[i].quiz);
    }
}

readFromFile함수는 파일에서 학생정보를 읽어와서 Student 배열을 할당하고 채워넣는 역할을 합니다. fscanf함수를 사용하여 파일에서 이름과 점수들을 읽어옵니다.

이떄, 이 함수는 Student** student를 받아들이고 있는 걸 볼수가 있습니다. 이것은 Student구조체의 이중 포인터로, 동적으로 할당된 배열을 함수에 전달하고 해당 배열을 채우기 위해 사용됩니다.

만약 함수의 인자를 Student* student로 변경하면 함수 내에서 할당된 메모리의 주소를 수정하는 것이 아니라, 복사본에 작업을 수행하게 됩니다.

따라서 함수의 인자를 Student** student로 유지하여 주소를 전달받아 배열을 채우는 방식을 유지하는 것이 올바릅니다. 함수 호출 시에 &students와 같이 이중 포인터의 주소를 전달하게 되면, 함수 내에서 해당 주소에 접근하여 배열을 채울 수 있습니다.

int main()
{
    // 파일 열기
    FILE* file_import = fopen("f5.txt", "r");
    if (!file_import)
    {
        fprintf(stderr, "파일을 열 수 없습니다.");
        return 1;
    }

    int size;
    fscanf(file_import, "%d", &size);

    // 학생 구조체 배열 선언
    Student* students;

    // readFromFile 함수 호출하여 배열 채우기
    readFromFile(&students, &size);

    // 이제 'students' 배열은 학생 정보로 채워짐

    // 예시: 첫 번째 학생의 정보 출력
    printf("첫 번째 학생 정보:\n");
    printf("이름: %s\n", students[0].name);
    printf("점수 1: %.2lf\n", students[0].score1);
    printf("점수 2: %.2lf\n", students[0].score2);
    printf("퀴즈 점수: %.2lf\n", students[0].quiz);

    // 할당된 메모리 해제
    free(students);

    // 파일 닫기
    fclose(file_import);

    return 0;
}

main함수에서는 파일을 열고, readFromFile함수를 호출하여 학생정보를 읽어오고 출력합니다. 마지막으로 할당된 메모리를 해제하고 파일을 닫습니다.

main부에서 직접참조(&)를 한번써서 보내면, 함수 구현부에서 이중역참조를 써줘야 하고!

main부에서 직접참조(&)를 안써서 보내면, 함수 구현부에서 단일역참조를 써주면 된다.

(직접참조(&)를 써주는 경우는 포인터 배열을 써줘야하는 경우뿐이다.)

CI(1): 파일에서 데이터 읽기와 성적총합계산하기

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>

typedef struct {
    int snum;
    char name[20];
    double score1; // 실수형으로 변경하여 부동 소수점 형태의 점수 저장
    double score2;
    double quiz;
    double totalScore;
} Student;

void readFromFile(Student** student, int* n, FILE* file_import)
{
    // 학생 구조체 배열 동적 할당
    *student = (Student*)malloc(*n * sizeof(Student));
    if (*student == NULL) {
        fprintf(stderr, "메모리를 할당할 수 없습니다.");
        exit(1);
    }

    // 파일에서 학생 정보 읽기
    for (int i = 0; i < *n; i++)
    {
        fscanf(file_import, "%s %lf %lf %lf",
            (*student)[i].name,
            &(*student)[i].score1,
            &(*student)[i].score2,
            &(*student)[i].quiz);
    }
}
void calculateTotalScore(Student* students, int n)
{
    for (int i = 0; i < n; i++)
    {
        students[i].totalScore = students[i].score1 + students[i].score2 + students[i].quiz;
    }
}

int main()
{
    // 파일 열기
    FILE* file_import = fopen("f5.txt", "r");
    if (!file_import)
    {
        fprintf(stderr, "파일을 열 수 없습니다.");
        return 1;
    }

    int size;
    fscanf(file_import, "%d", &size);

    // 학생 구조체 배열 선언
    Student* students;

    // 파일에서 데이터 읽기
    readFromFile(&students, &size, file_import);

    //성적의 합계 계산
    calculateTotalScore(students, size);

    printf("첫 번째 학생 정보:\n");
    printf("이름: %s\n", students[0].name);
    printf("점수 1: %.2lf\n", students[0].score1);
    printf("점수 2: %.2lf\n", students[0].score2);
    printf("퀴즈 점수: %.2lf\n", students[0].quiz);
    printf("총합: %.2lf\n", students[0].totalScore);
    
    // 할당된 메모리 해제
    free(students);

    // 파일 닫기
    fclose(file_import);

    return 0;
}

참조)데이터출력하기

void printData(Student* students, int n)
{
    for (int i = 0; i < n; i++)
    {
        printf("%d %s %.1f %.1f %.1f %.1f\n", 
            students[i].snum, students[i].name, 
               students[i].score1, students[i].score2, 
               students[i].quiz, students[i].totalScore );
    }
}

코드리뷰: 데이터를 출력할때에는 다음과 같이 일반적으로 출력할 수 있다. 그러면 입력에 대한 코드와 비교를 해보면 입력(fscanf())에서는

fscanf(file, "%s %f %f %f", 
                (*students)[i].name, 
                &(*students)[i].score1, 
                &(*students)[i].score2,
               	&(*students)[i].quiz);
    }

다음과 같이 문자열에 대해서는 따로 직접참조(&)를 붙여주지 않는다. 왜냐하면 문자열을 저장하는 경우에는 그 자체로 주소값을 반환해버리기 때문이다. 그 외에 나머지 int형이나 float형에 대해서는 주소값 얻으려면 무조건 직접참조(&)를 붙여줘야 한다. 근데 이거랑 비교해보면, 출력할때를 봤을때 문자열을 출력한다고 해서 간접참조(*)를 일일이 붙여주냐? 또 그것도 아니라는 거다. 이런 면에서 C언에서도 살짝 비논리적인 부분이 없지 않아 있는 것 같다. 아니 많은 것 같다. (C언어는 억까가 많은 언어다ㄷㄷ)

댓글남기기