코딩일지(2024-01-14)
목요일 4번 문제
위 코드는 “f2.txt”파일에 있는 단어들을 동적할당된 이차원배열에서 받아가지고 qsort()를 써서 값을 오름차순으로 재배열 하는 문제이다.
이때 compare()함수를 구현해주어야 하는데, 이게 생각보다 성가시다ㅜㅠ.
그래서 이 부분에 대해서만 집중적으로 정리해보려고 한다.
int compare(const void *a, const void *b)
{
return strcmp(*(const char **)a, *(const char **)b);
}
코드리뷰: 여기서 a
와 b
는 비교될 두 요소를 가리키는 포인터입니다. 이 비교함수는 문자열 포인터들의 배열을 정렬하는데 사용됩니다.
1.const char **a
와 const char **b
로 형변환하여 문자열 포인터를 가리키는 포인터로 캐스팅합니다. 이는 qsort
함수에서 제공되는 요소들이 const void *
형태로 전달되기 떄문입니다.
2.strcmp(*(const char*)a, *(const **)b)
를 통해 두 문자열을 비교합니다.
여기서 *(const char **)a
와 *(const char**)b
는 각각 a
와 b
가 가리키는 문자열 나타냅니다.
3.strcmp
함수의 결과에 따라 비교 결과를 반환합니다. strcmp
의 반환값이 양수이면 a
가 b
보다 크다는 것을 의미하고, a
가 b
보다 작다는 것을 의미합니다. 만약 두 문자열이 같으면 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언어는 억까가 많은 언어다ㄷㄷ)
댓글남기기