윤성우의 열혈 C 프로그래밍 - 도전! 프로그래밍 4

Yumin Jung·2023년 10월 28일
0

도전 1

구조체 배열을 이용하여 책 3권을 저장할 수 있는 프로그램이다.

/*도전 프로그래밍 4-1 간단한 도서관리용 프로그램*/
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

typedef struct {
	char writer[20];
	char title[20];
	int page;
}BOOK;

int main(void) {

	BOOK arr[3];
	int i,j;
	printf("저자, 제목, 페이지 수 순으로 총 3권을 입력하세요.\n\n");
	for (i = 0; i < 3; i++) {
		printf("\n%d번째 책 입력\n", i+1);
		scanf("%s %s %d", arr[i].writer, arr[i].title, &(arr[i].page));
	}

	printf("\n\n-----------------도서 정보--------------------\n");
	for (j = 0; j < 3; j++) {
		printf("\n%d번째 책 정보\n", j+1);
		printf("저자: %s\n제목: %s\n페이지 수: %d\n\n", arr[j].writer, arr[j].title, arr[j].page);
	}
	return 0;
}

도전 2

도전 2는 도전1에서 구현한 프로그램의 다른 버전인데, 구조체 배열을 선언하는 것이 아닌 구조체 포인터 배열을 선언하고 구조체 변수를 동적으로 할당하는 형태로 만드는 문제였다.

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

typedef struct {
	char writer[20];
	char title[20];
	int page;
}BOOK;

int main(void) {
	int i, j;
	BOOK* ptr[3];

	printf("저자, 제목, 페이지 수 순으로 총 3권을 입력하세요.\n\n");
	for (i = 0; i < 3; i++) {
		ptr[i] = (BOOK*)malloc(sizeof(BOOK));//포인터 배열의 한 블럭 단위로 동적 할당해줘야 한다 ??
		printf("\n%d번째 책 입력\n", i + 1);
		scanf("%s %s %d", ptr[i]->writer, ptr[i]->title, &ptr[i]->page);
	}
	printf("\n\n-----------------도서 정보--------------------\n");
	for (j = 0; j < 3; j++) {
		printf("\n%d번째 책 정보\n", j + 1);
		printf("저자: %s\n제목: %s\n페이지 수: %d\n\n", ptr[j]->writer, ptr[j]->title, ptr[j]->page);
	}
    
    for(i=0; i<3; i++)
	free(list[i]);
    }
	return 0;
}

포인터 배열의 각 요소에 대해 개별적으로 malloc 함수를 사용하여 메모리를 할당했는데 이것에 대한 이유를 chatGPT에 물어보았다.

"포인터 배열을 사용할 때, 각 요소는 메모리를 가리켜야 하며, 이 메모리는 각각 다른 데이터를 저장하는 데 사용됩니다. 포인터 배열의 각 요소에 메모리를 할당하지 않고 단일 메모리 블럭으로 할당하면 모든 요소가 같은 메모리를 가리키게 되므로 데이터를 올바르게 저장하고 접근할 수 없습니다."

사실 나는 처음에 포인터 배열을 선언과 동시에 한번에 단일메모리블럭으로 할당하였다. 이해해보니 어렵지 않았지만, 포인터에 대해 아직 부족하다는 것을 느꼈다.

도전2에 대한 해설을 보았다.

#include <stdio.h>
#include <stdlib.h>
void ClearLineFromReadBuffer();

#define LEN 30

typedef struct _book {
	char author[LEN];
	char title[LEN];
	int page;
} book;

int main(void)
{
	int i;
	book* list[3];

	printf("도서 정보 입력\n");
	for(i=0; i<3; i++)	//데이터 입력
	{
		book * pBook=(book*)malloc(sizeof(book)*1);

		printf("저자 : ");
		fgets(pBook->author, LEN, stdin);

		printf("제목 : ");
		fgets(pBook->title, LEN, stdin);

		printf("페이지 수 : ");
		scanf("%d", &(pBook->page));

		ClearLineFromReadBuffer();

		list[i]=pBook;
	}

	printf("\n도서 정보 출력\n");
	for(i=0; i<3; i++)	// 데이터 츨력
	{
		printf("book %d \n", i+1);
		printf("저자 : %s", list[i]->author);
		printf("제목 : %s", list[i]->title);
		printf("페이지 수 : %d \n", list[i]->page);
	}

	for(i=0; i<3; i++)	// 할당된 메모리 해제.
	{
		free(list[i]);
	}
	
	return 0;
}


void ClearLineFromReadBuffer(void)
{
	while(getchar() != '\n');
}

입출력 함수를 잘 사용하였다. 나도 더 적극적으로 사용하도록 노력해야겠다.

도전 2가 도전 1보다 나은 코드

도전 1에서 구현한 방법보다 도전 2에서 구현한 방법이 지니는 장점이 무엇일까?

나의 생각 : 동적할당을 했기 때문에, 외부 함수에 만약 책에 대한 정보를 담은 배열을 선언했을 때, 소멸되거나 중복되지 않을 것이다. 또한, 쉽게 realloc 함수를 이용해 책의 개수를 늘릴수 있을것이다.(솔직히 잘 모르겠음 아직까진)

도전 3

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

typedef struct {
	double real;
	double imagine;
}Complex;

void ForComplexNumCal(Complex, Complex);

int main(void) {
	printf("두 개의 복소수 정보를 입력 받아 두 복소수의 덧셈과 곱셈의 결과를 출력하는 프로그램 입니다\n\n");
	Complex a;
	Complex b;
	printf("복소수 입력1[실수 허수]: ");
	scanf("%lf %lf", &a.real, &a.imagine);
	printf("복소수 입력2[실수 허수]: ");
	scanf("%lf %lf", &b.real, &b.imagine);
	ForComplexNumCal(a, b);
    return 0;
}
void ForComplexNumCal(Complex a, Complex b) {

	printf("합의 결과] 실수 : %f 허수 : %f\n", (a.real + b.real), (a.imagine + b.imagine));
	printf("곱의 결과] 실수 : %f 허수 : %f\n", (a.real * b.real) - (a.imagine * b.imagine),(b.real * a.imagine) + (a.real * b.imagine));
}

도전 4

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

int main(void) {
	char word[20];
	int startA=0, startP=0;

	FILE* fp = fopen("D:\\text.txt", "rt");
	int ret;

	while (1) {
		ret=fscanf(fp, "%s", word);
		if (ret == EOF)
			break;
		if (word[0] == 'a'|| word[0] == 'A')
			startA++;
		else if (word[0] == 'p'||word[0] == 'P')
			startP++;
		else
			continue;
	}
	printf("a로 시작하는 단어의 수: %d\np로 시작하는 단어의 수: %d", startA, startP);
	return 0;
}

fscanf 함수는 scanf 함수와 마찬가지로 공백을 기준으로 문자열을 구분하기 때문에 그 특성을 이용하여 word 배열의 첫번째 인덱스를 if문을 이용해 a, p가 처음으로 들어간 문자열을 골라냈다.

추가

fclose함수를 통해 스트림을 닫아주는 것을 깜빡했다. 습관화 들이자.

도전 5

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

int main(void) {
	FILE* d1 = fopen("D:\\d1.txt", "rt");
	FILE* d2 = fopen("D:\\d2.txt", "rt");
	char comp1[300];
	char comp2[300];
	int cmp;

	while (1) {
		fgets(comp1, sizeof(comp1), d1);
		fgets(comp2, sizeof(comp2), d2);

		if (feof(d1) && feof(d2) != 0)
			break;
	}
	cmp=(strcmp(comp1, comp2));
	if (cmp == 0)
		puts("같습니다");
	else
		puts("다릅니다");

	fclose(d1);
	fclose(d2);

	return 0;
}

feof함수로 d1와 d2 스트림이 두 개다 완료했는지 확인을 한 후 strcmp함수로 비교를 하였다.

도전 6

/*전화번호 관리 프로그램*/
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

typedef struct {
	char name[10];
	char pnum[20];
}PERINF;

void insert(PERINF* a, int*used,int* max_size) {
	if (*used >= *max_size) {
		puts("더 이상 데이터 삽입이 불가합니다.");
	}
	puts("[INSERT]"); printf("\n");
	printf("이름을 입력하세요 : ");
	scanf("%s",a[*used].name);
	printf("전화번호를 입력하세요 : ");
	scanf("%s",a[*used].pnum);
	puts("데이터가 삽입되었습니다."); printf("\n");
	++(*used);
}

void delete(PERINF* a, int*used) {
	char sear[10];
	int count = 0;
	puts("[DELETE]"); printf("\n");
	for (int i = 0; i <*used; i++) {
		printf("%d번\n\n%s\n%s\n\n",i+1, a[i].name, a[i].pnum);
	}
	printf("삭제할 사람의 이름을 입력하세요 : ");
	scanf("%s", sear);
	for (int j = 0; j < *used; j++) {
		if (strcmp(sear, a[j].name) == 0) {
			++count;
			for (int k = j; k < *used; k++)
				a[j] = a[j + 1];//벨로그에 설명
			puts("삭제하였습니다.");
			--(*used);
			break;
		}
	}
		if(count<=0)
			puts("찾을 수 없습니다."); printf("\n");
}

void search(PERINF* a, int * used) {
	char sear[10];
	int count=0;
	puts("[SEARCH]"); printf("\n");
	printf("검색할 사람의 이름을 입력하세요 : ");
	scanf("%s", sear);
	for (int j = 0; j <= *used; j++) {
		if (strcmp(sear, a[j].name) == 0) {
			printf("\n%s\n%s\n\n", a[j].name, a[j].pnum);
			++count;
			break;
		}
	}
	if (count <= 0)
		puts("검색 결과가 없습니다."); printf("\n");
}

void printAll(PERINF* a, int * used) {
	puts("[Print All]"); printf("\n");
	if (*used>0) {
		for (int i = 0; i < *used; i++)
			printf("%d번\n\n%s\n%s\n\n", i + 1, a[i].name, a[i].pnum);
	}
	else
		puts("데이터가 없습니다"); printf("\n");
	}

int main(void) {
	int sel, max_size = 30 , used = 0;
	PERINF* mem = (PERINF*)malloc(sizeof(PERINF)*max_size);

	puts("--------------전화번호 관리 프로그램입니다.----------------");
	puts("사용하실 기능을 선택하세요.");
	while (1) {

		puts("***** MENU *****");
		puts("1. Insert");
		puts("2. Delete");
		puts("3. Search");
		puts("4. Print All");
		puts("5. Exit");
		printf("Choose the item : ");
		scanf("%d", &sel);

		if (sel == 1)
			insert(mem, &used, &max_size);

		else if (sel == 2)
			delete(mem, &used);

		else if (sel == 3)
			search(mem, &used);

		else if (sel == 4)
			printAll(mem, &used);

		else if (sel == 5)
			break;

		else
			puts("다시 입력하세요 ");
		}

	free(mem);
	return 0;
	}

도전 2와 도전 6

각자 다른 동적할당 방법에 대한 chatGPT의 설명

질문

전화번호 관리 프로그램은 대용량 배열을 한 번에 동적할당하는 방식을 사용했고, 도서 정보 입력 프로그램은 각 요소를 반복적으로 동적할당하는 방식을 사용했어. 왜 각자 다른 방식을 사용한거지?

ChatGPT

"전화번호 관리 프로그램"과 "도서 정보 입력 프로그램"은 각각 다른 요구 사항과 목적을 가지고 있기 때문에 다른 동적 할당 방식을 사용했을 가능성이 높습니다. 여기에는 몇 가지 이유가 있을 수 있습니다:

데이터 크기 예측에 따라서: "전화번호 관리 프로그램"은 일반적으로 사용자가 제한된 수의 연락처를 관리하는 데 사용됩니다. 데이터 크기가 예측 가능하고, 최대 연락처 수가 상대적으로 작을 것으로 예상되므로 대용량 배열을 한 번에 동적으로 할당하는 방식을 선택할 수 있습니다.

데이터 변경 빈도에 따라서: "전화번호 관리 프로그램"의 경우, 사용자가 연락처를 추가하거나 삭제하는 빈도가 낮을 수 있습니다. 따라서 데이터 크기가 고정되고 변화가 적을 것으로 예상되므로 한 번에 할당된 배열을 사용할 수 있습니다.

메모리 효율성: "도서 정보 입력 프로그램"은 동적 크기 조정이 필요한 경우가 많을 수 있습니다. 각 도서 정보가 서로 다른 크기를 가질 수 있으며, 새로운 도서가 추가되거나 삭제될 수 있으므로 각 요소를 반복적으로 동적으로 할당하는 방식을 통해 메모리를 효율적으로 활용할 수 있습니다.

용도 및 데이터 구조에 따라서: 각 프로그램은 다른 데이터 구조를 다루며, 이에 따라 동적 할당 방식이 달라질 수 있습니다. "전화번호 관리 프로그램"은 간단한 구조로 고정된 필드 크기를 가지고 있으므로 대용량 배열을 사용할 수 있습니다. "도서 정보 입력 프로그램"은 구조가 복잡하고 각 항목의 크기가 다를 수 있으므로 각 요소를 반복적으로 할당하는 방식이 더 적합할 수 있습니다.

요약하면, 프로그램의 목적, 데이터 크기 예측, 데이터 변경 빈도, 메모리 효율성 및 데이터 구조 등 다양한 요인이 각각의 동적 할당 방식을 결정하는 데 영향을 미칠 수 있습니다.

도전 6,7 최종문제

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

typedef struct {
    char name[10];
    char pnum[20];
} PERINF;

void insert(PERINF* a, int* used, int* max_size) {
    if (*used >= *max_size) {
        puts("더 이상 데이터 삽입이 불가합니다.");
        return;
    }
    puts("[INSERT]");
    printf("\n");
    printf("이름을 입력하세요 : ");
    scanf("%s", a[*used].name);
    printf("전화번호를 입력하세요 : ");
    scanf("%s", a[*used].pnum);
    puts("데이터가 삽입되었습니다.");
    (*used)++;
}

void delete(PERINF* a, int* used) {
    char sear[10];
    int count = 0;
    puts("[DELETE]");
    printf("\n");
    for (int i = 0; i < *used; i++) {
        printf("%d번\n\n%s\n%s\n\n", i + 1, a[i].name, a[i].pnum);
    }
    printf("삭제할 사람의 이름을 입력하세요 : ");
    scanf("%s", sear);
    for (int j = 0; j < *used; j++) {
        if (strcmp(sear, a[j].name) == 0) {
            count++;
            for (int k = j; k < *used - 1; k++) {
                strcpy(a[k].name, a[k + 1].name);
                strcpy(a[k].pnum, a[k + 1].pnum);
            }
            (*used)--;
            puts("삭제하였습니다.");
            break;
        }
    }
    if (count <= 0)
        puts("찾을 수 없습니다.");
    printf("\n");
}

void search(PERINF* a, int used) {
    char sear[10];
    int count = 0;
    puts("[SEARCH]");
    printf("\n");
    printf("검색할 사람의 이름을 입력하세요 : ");
    scanf("%s", sear);
    for (int j = 0; j < used; j++) {
        if (strcmp(sear, a[j].name) == 0) {
            printf("\n%s\n%s\n\n", a[j].name, a[j].pnum);
            count++;
            break;
        }
    }
    if (count <= 0)
        puts("검색 결과가 없습니다.");
    printf("\n");
}

void printAll(PERINF* a, int used) {
    puts("[Print All]");
    printf("\n");
    if (used > 0) {
        for (int i = 0; i < used; i++)
            printf("%d번\n\n%s\n%s\n\n", i + 1, a[i].name, a[i].pnum);
    }
    else
        puts("데이터가 없습니다");
    printf("\n");
}

void saveDataToFile(PERINF* a, int used) {
    FILE* fp = fopen("D:\\phonebook.txt", "wt");
    if (fp != NULL) {
        for (int i = 0; i < used; i++)
            fprintf(fp, "%s %s\n", a[i].name, a[i].pnum);
        fclose(fp);
    }
    else {
        puts("데이터 저장에 실패하였습니다.");
    }
}

void loadDataFromFile(PERINF* a, int* used, int* max_size) {
    FILE* fp = fopen("D:\\phonebook.txt", "rt");
    int count = 0;

    if (fp) {
        while (count < *max_size && fscanf(fp, "%s %s", a[count].name, a[count].pnum) != EOF) {
            count++;
        }
        /*if (feof(fp) != 0)
            puts("파일 열기 완료");*/

        *used = count;
        fclose(fp);
    }
    else {
        puts("파일을 열 수 없습니다.");
    }
}

int main(void) {
    int sel, max_size = 30, used = 0;
    PERINF* mem = (PERINF*)malloc(sizeof(PERINF) * max_size);

    loadDataFromFile(mem, &used, &max_size);
    puts("--------------전화번호 관리 프로그램입니다.----------------");
    puts("사용하실 기능을 선택하세요.");

    while (1) {
        puts("***** MENU *****");
        puts("1. Insert");
        puts("2. Delete");
        puts("3. Search");
        puts("4. Print All");
        puts("5. Exit");
        printf("Choose the item : ");
        scanf("%d", &sel);

        if (sel == 1)
            insert(mem, &used, &max_size);
        else if (sel == 2)
            delete(mem, &used);
        else if (sel == 3)
            search(mem, used);
        else if (sel == 4)
            printAll(mem, used);
        else if (sel == 5) {
            saveDataToFile(mem, used);
            break;
        }
        else
            puts("다시 입력하세요 ");
    }

    free(mem);
    return 0;
}

해설지

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

#define NAME_LEN	20
#define TEL_LEN		20
#define MAX_PERSON_NUM		100

typedef struct _Person
{
	char name[NAME_LEN];
	char phone[TEL_LEN];
} Person;

void ShowMenu(void);	//메뉴 출력
void InsertTelInfo(Person *, int *);	//데이터 입력 입력
void RemoveTelInfo(Person *, int *);	//이름으로 데이터 삭제 
void SearchTelInfo(Person *, int);	//데이터 검색
void PrintAll(Person *, int);	//전체 데이터 출력 
void LoadData(Person *, int *);	//파일로부터 데이터 읽음.
void StoreData(Person *, int);	//파일에 데이터 저장.

int main(void)
{
	int choice;
	Person perArr[MAX_PERSON_NUM];	// Data 저장을 위한 배열
	int perNum=0;	// 저장된 인원 수

	LoadData(perArr, &perNum);

	while(1)
	{
		ShowMenu();
		printf("Choose the item : "); 
		scanf("%d", &choice);

		switch(choice)
		{
			case 1 : 
				InsertTelInfo(perArr, &perNum); 
				break;
			case 2 : 
				RemoveTelInfo(perArr, &perNum); 
				break;
			case 3 : 
				SearchTelInfo(perArr, perNum); 
				break;
			case 4 : 
				PrintAll(perArr, perNum); 
				break;
			case 5 : 
				StoreData(perArr, perNum); 
				return 0;
			default : 
				printf("Illegal selection.. \n"); 
				break;
		}
	}
	return 0;
}

void ShowMenu(void)
{
	printf("\n---------Menu--------\n");
	printf("             1. Insert \n");
	printf("             2. Delete \n");
	printf("             3. Search \n");
	printf("             4. Print All\n");
	printf("             5. Exit\n");
}

void InsertTelInfo(Person * parr, int * pnum)
{
	char name[NAME_LEN], phone[TEL_LEN];

	printf("[ INSERT ] \n");
	printf("Input Name : "); scanf("%s", name);
	printf("Input Tel Number : "); scanf("%s", phone);

	strcpy(parr[*pnum].name, name);
	strcpy(parr[*pnum].phone, phone);
	(*pnum)++;

	printf("--------> Data Inserted......\n");
}

void RemoveTelInfo(Person * parr, int * pnum)
{
	int i, j;
	char name[NAME_LEN];

	printf("[ Remove ] \n");
	printf("Input Name for Removing : "); scanf("%s", name);

	for(i=0; i < *pnum; i++)
	{
		if(strcmp(parr[i].name, name)==0)
		{
			for(j=i+1; j < *pnum; j++)
			{
				strcpy(parr[j-1].name, parr[j].name);
				strcpy(parr[j-1].phone, parr[j].phone);
			}
			(*pnum)--;
			return;
		}
	}

	printf("-------> Data Not Found......\n");
}

void SearchTelInfo(Person * parr, int num)
{
	int i;
	char name[NAME_LEN];

	printf("[ Search ] \n");
	printf("Input Name for Searching : "); scanf("%s", name);

	for(i=0; i<num; i++)
	{
		if(strcmp(parr[i].name, name)==0)
		{
			printf("---> Name: %s   /   Tel : %s  \n",
					parr[i].name, parr[i].phone);
			return;
		}
	}

	printf("--------> Data Not Found......\n");
}

void PrintAll(Person * parr, int num)
{
	int i;
	printf("[ Print All Data ] \n");

	for(i=0; i<num; i++)
		printf("Name : %s    /   Tel : %s \n", parr[i].name, parr[i].phone);
}

void LoadData(Person * parr, int * pnum)		//data Load
{
	FILE * file = fopen("data.dat", "rt");
	if(file==NULL)
	{	
		return;	
	}

	while(1)
	{
		fscanf(file, "%s %s", parr[*pnum].name, parr[*pnum].phone);
		if(feof(file)!=0)
			break;
		(*pnum)++;
	}
}

void StoreData(Person * parr, int num)		//data store
{
	int i;

	FILE * file = fopen("data.dat", "wt");
	if(file==NULL)
	{
		printf("file open error!\n");
		return;
	}

	for(i=0; i<num; i++)
		fprintf(file, "%s %s ", parr[i].name, parr[i].phone);
}

비교

내 코드와 해설지 코드의 차이는 일단 데이터 저장 방식이 달랐다. 정적이냐 동적이냐가 다른데, 나는 이렇게 생각했다.

메모리 효율성

동적 할당을 사용하면 필요한 메모리만 할당하므로 메모리를 더 효율적으로 관리할 수 있다. 해설지 코드에서는 최대 개수를 제한하고 정적 배열을 사용하므로, 메모리 사용량이 항상 최대 크기를 차지한다.

데이터 크기 유연성

동적 할당을 사용하면 데이터 크기가 변경될 때 더 쉽게 대응할 수 있다. 해설지 코드에서는 최대 개수를 정의하고 배열을 할당하므로, 만약 데이터 크기가 예상보다 많이 증가하면 문제가 발생할 수 있다.

정적 할당은 간단하고 안전하지만 메모리 사용량과 데이터크기에 제한이 있을 수 있다는 생각을 하였다.

profile
문과를 정말로 존중해

0개의 댓글