관리 메뉴

공부 기록장 💻

C언어 Express 15장 파일 입출력 Programming 심화 문제 추가 본문

# Language & Tools/C

C언어 Express 15장 파일 입출력 Programming 심화 문제 추가

dream_for 2021. 1. 26. 01:15

 

 

1. 두 파일을 생성하여 입력받는 문자들을 각각 파일에 출력한 뒤, 두 파일이 동일한지 비교하는 프로그램

(#1, #7 참고)

 

중요하다고 생각하는 부분

- 파일 포인터 생성

- 파일 입출력 관련 라이브러리 함수 : fopen_s() / fprintf() / fclose() / fgetc() / fputc() / feof()

- getchar()함수의 반환값 EOF

 

// 두 파일을 생성하여 각 파일에 문자들을 입력받아 저장하고, 두 파일의 내용이 동일한지 판별하는 프로그램

#include <stdio.h>

int main(void) {

	// 두 개의 파일 포인터, 파일명 저장할 문자열 생성, 입력 받아 파일에 저장할 문자/비교할 두 개의 문자 생성

	FILE* fp1 = NULL, * fp2 = NULL;
	char fname1[50], fname2[50];
	char ch, ch1, ch2;

	//파일을 생성하여 입력받는 문자를 파일에 출력

	printf("첫번째 파일 이름 : ");
	gets(fname1, 50);
	fopen_s(&fp1, fname1, "wt");
	printf("첫번째 파일 내용 입력(종료는 Ctrl-Z) :\n");
	while ((ch = getchar()) != EOF)
		fputc(ch, fp1);

	printf("두번째 파일 이름 : ");
	gets(fname2, 50);
	fopen_s(&fp2, fname2, "wt");
	printf("두번째 파일 내용 입력(종료는 Ctrl-Z) :\n");
	while ((ch = getchar()) != EOF)
		fputc(ch, fp2);

	fclose(fp1);
	fclose(fp2);

	//생성된 파일을 다시 열어 내용이 동일한지 비교

	fopen_s(&fp1, fname1, "rt");
	fopen_s(&fp2, fname2, "rt");
	if (fp1 == NULL || fp2 == NULL) {
		fprintf(stderr, "파일 열기 실패\n");
		exit(1);
	}

	int n = 0;
	while (!feof(fp1)) {
		n++;
		ch1 = fgetc(fp1);
		ch2 = fgetc(fp2);
		printf("%d : %c %c\n", n, ch1, ch2);
		if (ch1 != ch2) {
			printf("%d번째에서 %c, %c 다름\n", n, ch1, ch2);
			break;
		}
	}

	fclose(fp1);
	fclose(fp2);

	return 0;
}

 

<stdio.h> 헤더 파일에 EOF는 -1으로 정의되어 있으며, 문자 아스키코드 값은 음수를 포함하고 있지 않기 때문에

Ctrl-Z 키와 함께 줄바꿈 문자를 입력받으면 getchar() 함수에서는 자동으로 -1 값을 반환하게 된다.

이를 이용해, 반복문을 통해 받는 입력값을 두 파일에 각각 출력하도록 한다.

 

이후, 두 파일을 비교하기 위해 반복문의 조건값은 fp1 파일이 끝났는지의 여부로 정한다.

반복문 내에서 fp1과 fp2로부터 입력받는 두 문자를 비교하여 종료하게 만드는 break문 조건을 추가했기 때문에,

fp2파일이 먼저 끝나는 경우여도 올바르게 실행된다.

 

아래 결과보기

 

 

 

2. 텍스트 파일을 생성하여 문장들을 입력하여 저장하고, 이 파일의 모든 문자들을 대문자로 변경하여 새로운 파일에 저장하고, 각 줄의 앞에 폭이 6이고 오른쪽 정렬된 줄 번호를 붙여 화면에 출력하는 프로그램 (#2, #10 참고)

 

#include <stdio.h>
#include <ctype.h>

int main(void) {

	FILE* fp1 = NULL, * fp2 = NULL;
	char fname1[100], fname2[100], buffer[100];
	char ch;
	int n = 0;

	// 첫번째 파일을 생성하여 내용을 입력하여 파일에 출력한다.

	printf("첫번째 파일 이름 : ");
	gets(fname1);
	fopen_s(&fp1, fname1, "wt");
	printf("파일에 쓸 문장 입력(종료는 Ctrl-Z) : \n");
	while ((ch = getchar()) != EOF)
		fputc(ch, fp1);
	fclose(fp1);

	// 두번째 파일을 생성하여 모든 문자를 대문자로 변환하여 출력한다.

	printf("\n두번째 파일 이름 : ");
	gets(fname2);
	fopen_s(&fp2, fname2, "wt");
	fopen_s(&fp1, fname1, "rt");

	while ((ch = fgetc(fp1))!=EOF) {
		if (islower(ch)) ch = toupper(ch); // 소문자인 경우 대문자로 변환
		fputc(ch, fp2);
	}
	fclose(fp1);
	fclose(fp2);

	// 생성된 두번째 파일로부터 문장을 받아 줄번호와 함께 화면에 출력한다.

	fopen_s(&fp2, fname2, "rt");
	while (fgets(buffer, 100, fp2))
		printf("%6d: %s", ++n, buffer); // 한 줄의 문장을 받아 출력할 때 줄 번호도 함께 출력한다.
	fclose(fp2);

	return 0;
}

 

첫번째 파일은 키보드로부터 문자를 입력받아 출력하는 반면, 두번째 파일은 다른 파일로부터 문자를 입력받아 출력하는 방식이다.

소문자인지 확인하는 islower() 함수와 소문자를 대문자로 변환하는 toupper() 함수를 사용하기 위해

<ctype.h> 헤더파일을 추가한다.

키보드로부터 입력받는 문자들을 첫번째 파일에 출력하고,

다시 첫번째 파일로부터 문자들을 읽어서 소문자인 경우엔 대문자로 변환하여, 문자들을 두번째 파일에 출력한다.

두번째 파일을 다시 열어 fgets() 함수를 이용해 한 문장씩 읽어 buffer에 저장하고

이 문자열을 오른쪽 정렬하는 줄바꿈 문자와 함께 화면에 출력한다.

 

아래 결과보기

더보기

 

첫번째 파일의 문자들이 모두 대문자로 변환되어 두번째 파일에 출력되는 것을 확인할 수 있다.

 

 

 

 

3. 이진 이미지 파일 복사하는 프로그램 (#3번)

 

중요하다고 생각하는 부분

- fread(), fwrite() 함수의 사용(인수와 반환값의 의미 잘 이해하기)

 

#include <stdio.h>

int main(void) {

	FILE* fp1 = NULL, * fp2 = NULL;
	char orig[100], copy[100], buffer[1024];
	int r_count = 0, w_count = 0; // fread(), fwrite() 함수의 반환값을 저장할 변수

	printf("이미지 파일 이름: ");
	gets(orig);
	printf("복사할 이미지 파일 이름: ");
	gets(copy);

	fopen_s(&fp1, orig, "rb");
	fopen_s(&fp2, copy, "wb");
	if (fp1 == NULL || fp2 == NULL) {
		fprintf(stderr, "파일 열기 오류\n");
		exit(1);
	}

	// buffer 메모리에 1바이트 char형 크기만큼 buffer의 크기인 1024 개수만큼 이진 데이터를 읽는다.
	// 파일의 끝이거나 오류가 나는 경우에는 0을 반환한다.

	while ((r_count = fread(buffer, sizeof(char), sizeof(buffer), fp1)) > 0)
	{
		w_count = fwrite(buffer, sizeof(char), r_count, fp2);
		printf("%d %d\n", r_count, w_count); 

		if (w_count < 0) // 이진 데이터를 출력하는데 오류가 난 경우
		{
			fprintf(stderr, "파일 쓰기 오류\n");
			exit(1);
		}
		if (w_count < r_count) // 출력에 성공한 데이터 개수가 읽은 데이터 개수보다 적은 경우
		{
			printf("복사 오류\n");
			exit(1);
		}
	}
	
	printf("%s 파일로 이미지가 성공적으로 복사됨\n");
	fclose(fp1);
	fclose(fp2);

	return 0;
}

 

이미지는 이진 형태의 파일이므로, fread()와 fwrite() 함수를 사용하여 orig파일을 copy 파일로 복사해주도록 한다.

1024 byte 담을 수 있는 char형 메모리 블록 buffer를 사용하였다.

orig 이름의 이미지 파일로부터 1 바이트의 데이터 항목을 총 1024개수만큼 읽어 메모리에 저장하고,

이를 생성한 copy 이름의 이미지 파일에 출력하도록 한다.

orig 파일을 전부 읽게 될 때 까지 반복문이 실행된다.

이 때, fwrite() 함수의 반환값이 오류가 난 경우 또는 출력에 성공한 데이터 개수가 읽은 데이터 개수보다 작아 복사에 오류가 난 경우엔 프로그램 실행을 종료하도록 한다.

 

이 과정을 정확히 확인해보기 위해 코드를 추가하여 읽고 쓴 항목의 개수를 출력해 보았다.

 

아래 결과보기

더보기

 

마지막을 제외하고는 메모리 블록의 크기만큼 총 1024 바이트의 데이터가 각각 입력되고 출력되었다.

 

 

 

 

4. 학생 3명의 이름과 교과목 성적들을 구조체 데이터로 입력받아 파일에 저장하고, 각 학생의 평균과 각 과목의 평균을 구하여 새로운 파일에 쓴 뒤 화면에 출력하는 프로그램 (#5번)

 

#include <stdio.h>
#define SIZE 3

// 이름, 국어/수학/영어 점수, 평균을 멤버 변수로 갖는 구조체를 새로운 자료형 SCORES로 정의
typedef struct {
	char name[10];
	int kor;
	int math;
	int eng;
	double avg;
}SCORES;

int main(void) {

	FILE* fp1 = NULL, * fp2 = NULL;
	SCORES list[SIZE]; // 3개의 요소를 갖는 SCORES형 배열 선언
	char name[10];
	double avg;

	// 학생들의 점수를 저장할 파일, 평균을 구해 저장할 파일을 연다.

	fopen_s(&fp1, "scores.txt", "wt");
	fopen_s(&fp2, "avg.txt", "wt");

	// 한 명씩 각각의 정보를 입력 받아 평균을 구하고, 파일에 필요한 데이터를 각각 출력한다.
	for (int i = 0;i < SIZE;i++) {
		printf("< 학생 %d 정보 입력 >\n", i+1);
		printf("이름: ");
		gets(list[i].name);
		printf("국어 점수: ");
		scanf_s("%d", &list[i].kor);
		printf("수학 점수: ");
		scanf_s("%d", &list[i].math);
		printf("영어 점수: ");
		scanf_s("%d", &list[i].eng);
		getchar();

		list[i].avg = (list[i].kor + list[i].math + list[i].eng) / SIZE;
		fprintf(fp1, "%s %d %d %d %f\n", list[i].name, list[i].kor, list[i].math, list[i].eng, list[i].avg);
		fprintf(fp2, "%s %.3f\n", list[i].name, list[i].avg);
	}
	
	fclose(fp1);
	fclose(fp2);

	// 파일에 입력되어 있는 평균 정보를 읽어 화면에 출력한다.

	fopen_s(&fp2, "avg.txt", "rt");
	printf("\n< 학생들의 평균 >\n");
	for (int i = 0;i < SIZE;i++) {
		fscanf_s(fp2, "%s %lf\n", name, 10, &avg);
		printf("%s %.3f\n", name, avg);
	}

	fclose(fp2);

	return 0;
}

 

 

 

아래 결과보기

 

 

 

 

5. 소규모의 데이터 프로그램 - 도서 관리 프로그램: 도서 추가, 출력, 검색 종료 (#11번)

 

중요하다고 생각하는 부분

- 파일을 함수의 인자로 전달

- fseek() 함수 이용해 파일 포인터 위치 지정

- strcmp() 문자열 비교 함수 이용하여 동일한 문자열 찾기

 

#include <stdio.h>
#include <string.h>
#define SIZE 3

// 이름, 국어/수학/영어 점수, 평균을 멤버 변수로 갖는 구조체를 새로운 자료형 SCORES로 정의
typedef struct {
	char title[20];
	char author[20];
	char publisher[20];
}BOOK;

void add_record(FILE* fp); // 파일에 데이터 추가하는 함수
void print_record(FILE* fp); // 파일의 내용을 출력하는 함수
void search_record(FILE* fp); // 파일로부터 데이터를 검색하는 함수
void menu(); // 메뉴 출력 함수
BOOK get_data(); // 한 개의 구조체 입력받는 함수

int main(void) {

	FILE* fp = NULL;
	int choice; // 메뉴를 입력받는 변수

	fopen_s(&fp, "book_library.dat", "w+t");
	menu();

	while (1) {
		printf("\n메뉴를 입력하시오: ");
		scanf_s("%d", &choice);
		if (choice == 4)
			break;
		getchar();

		switch (choice) // choice 숫자에 따라 각각 다른 함수를 호출
		{
		case 1:add_record(fp);break;
		case 2:print_record(fp);break;
		case 3:search_record(fp);break;
		default:break;
		}
	}
	fclose(fp);

	return 0;
}


void menu() {
	printf("-------------------------\n");
	printf("1. 추가\n2. 출력\n3. 검색\n4. 종료\n");
	printf("-------------------------\n\n");
}
BOOK get_data() {
	BOOK b;

	printf("도서 이름: ");
	gets(b.title);
	printf("저자 이름: ");
	gets(b.author);
	printf("출판사: ");
	gets(b.publisher);

	return b;
}
void add_record(FILE* fp) {
	BOOK data = get_data();
	
	fseek(fp, 0, SEEK_END); // 파일의 끝에서 새로운 데이터를 출력하도록 한다.
	fwrite(&data, sizeof(data), 1, fp);
}
void print_record(FILE* fp) {
	BOOK data;
	int n = 0;
	fseek(fp, 0, SEEK_SET); // 파일의 처음에서부터 데이터를 읽어 화면에 출력하도록 한다.

	while (1) {
		fread(&data, sizeof(data), 1, fp);
		if (feof(fp))break;// data 변수의 크기만큼 한 개씩 읽는다. 만약 파일의 끝인 경우엔, 반복문을 빠져나감
		printf("<도서 %d 정보>\n", ++n);
		printf("도서명: %s\n저자: %s\n출판사: %s\n\n", data.title, data.author, data.publisher);
	}
}
void search_record(FILE* fp) {
	BOOK data;
	char title[20];
	int n = 0;

	printf("검색할 도서명을 입력하시오: ");
	gets(title);

	fseek(fp, 0, SEEK_SET);
	while (1) {
		++n;
		fread(&data, sizeof(data), 1, fp);
		if (feof(fp))break;
		if (strcmp(title, data.title) == 0) // 입력받은 도서명이 탐색되는 경우 파일에 저장돼 있는 해당 도서의 정보를 출력
		{
			printf("%s가 %d번째에서 발견됨\n", title, n);
			printf("저자: %s\n출판사: %s\n\n", data.author, data.publisher);
			return 0;
		}
	}
	printf("도서가 발견되지 않음\n\n");
}

 

1. main()  - 반복문 이용하여 메뉴 번호를 입력받아 해당 함수를 실행하도록 함

2. get_data()  - 새로운 구조체의 데이터를 키보드로부터 입력받음

3. add_record() - get_data() 함수로부터 받은 데이터를 파일 끝에 출력하여 저장하도록 함

4. print_record() - 파일의 시작 지점으로부터 구조체의 데이터들을 읽어 화면에 출력

5. search_record() - 검색하고자 하는 문자열을 입력받아 파일에 저장되어 있는 문자열과 비교하여, 동일한 문자열을 찾은 경우 해당 구조체의 데이터를 화면에 출력

 

아래 실행창 

더보기

 

 

 

 

 

6. 파일에서 특정한 단어를 찾아 해당 단어가 위치한 줄 번호와 해당 문장을 출력하는 프로그램 (#12번)

 

중요하다고 생각하는 부분

- fgets() 함수 반환값

- strstr() 문자열 처리 함수 사용

 

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

int main(void) {
	FILE* fp;
	char fname[20], word[20], buffer[100]; 
	int n = 0; // 줄 번호를 저장할 변수

	printf("파일 이름: ");
	gets(fname); 
	printf("탐색할 단어: ");
	gets(word);
	
	fopen_s(&fp, fname, "rt");
	if (fp == NULL) {
		fprintf(stderr, "파일 열기 오류\n");
		exit(1);
	}

	while (fgets(buffer, 100, fp)) // fgets()함수의 값이 NULL이 아닌 동안 반복문 실행(파일의 끝이 다다르기 전까지)
	{
		++n; // 반복문이 실행될 때마다 변수값 증가
		if (strstr(buffer, word)) // 파일로부터 읽은 한 줄의 buffer 내에 word 문자가 포함된 경우
			printf("%d : %s", n, buffer);
	}

	fclose(fp);
	return 0;
}

 

파일로부터 한 줄의 문장을 읽어 문자열을 저장하는 fgets() 함수를 사용한다.

각 반복문이 실행될 때마다 읽어들인 buffer에 입력받은 문자가 포함되어 있는지 확인하여

탐색이 되는 경우, 해당 문장을 줄 번호와 함께 출력한다.

 

아래 결과보기

더보기

노래 snowman의 가사를 발췌하여 "snowman_lyrics.txt" 파일에 미리 출력하여 저장해 놓았다.

해당 텍스트 파일에서 'snowman' 단어를 찾기로 한다.

한 줄의 문장에서 snowman 단어가 탐색된 경우, 줄번호와 함께 해당 문장이 출력된 것을 확인할 수 있다.

 

word는 문자열로 선언된 변수이기 때문에, 한 줄의 문장도 탐색 가능하다.

 

 

 

7. 특정한 단어를 찾아서 다른 단어로 변경한 뒤, 새로운 파일에 출력하는 프로그램 (#13번)

 

중요하다고 생각하는 부분

- fgets() 함수의 반환값 :  한 줄의 문장(줄바꿈 문자 포함) 반환

- 문자열 처리 라이브러리 함수 사용 : strtok(), strstr(), strcat(), strcmp()의 반환값

- 서식화된 출력 fprintf() 함수 사용 (줄바꿈 문자 포함 필요)

 

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

int main(void) {
	
	FILE* fp1 = NULL, * fp2 = NULL;
	char fname1[20], fname2[20], word[20], new[20], buffer1[200];
	char* tok = NULL, *next = NULL;
	char seps[] = " .,[]\n";
	int n = 0;

	printf("원본 파일 이름 : ");
	gets(fname1);
	printf("새로운 파일 이름 : ");
	gets(fname2);
	printf("탐색할 단어 : ");
	gets(word);
	printf("수정할 단어 : ");
	gets(new);

	fopen_s(&fp1, fname1, "rt");
	fopen_s(&fp2, fname2, "wt");
	if (fp1 == NULL || fp2 == NULL) {
		fprintf(stderr, "파일 열기 오로\n");
		exit(1);
	}

	while (fgets(buffer1, 200, fp1)) {
		++n;
		printf("%d번째 buffer = %s", n, buffer1);

		if (strstr(buffer1, word)) // 문자열 buffer1가 단어 word가 포함한 경우
		{
			char buffer2[200] = ""; // 수정된 단어를 포함한 문장
			printf("단어 %s 발견\n\n", word, n);
			tok = strtok_s(buffer1, seps, &next); // buffer1으로부터 토큰 생성 시작
			
			while (tok != NULL) {
				if (strcmp(word, tok) == 0) // word와 tok 문자열이 동일한 경우 
					strcat_s(buffer2, 200, new); // 토큰 대신 입력받은 new 문자열을 이어붙인다 
				else
					strcat_s(buffer2, 200, tok);
				strcat_s(buffer2, 200, " "); // 토큰 뒤에는 공백을 추가한다.

				tok = strtok_s(NULL, seps, &next); // 다음 토큰 생성
			}
			fprintf(fp2, "%s\n",buffer2);
		}
		else // word가 포함되지 않은 문장은 그대로 파일에 출력(buffer1는 줄바꿈 문자까지 포함한 상태)
			fputs(buffer1, fp2);
	}

	fclose(fp1);
	fclose(fp2);

	return 0;
}

 

문자열을 처리하는 라이브러리 함수 사용에 대한 이해가 매우 중요한 문제이다.

가장 먼저, 기존 텍스트 파일로부터 fgets() 함수를 이용해서 buffer1에 한 줄의 문자열을 저장한다.

(이 때, buffer1은 마지막에 줄바꿈 문자까지 포함하고 있다는 사실을 기억하고 있어야한다.)

그리고 buffer1에서 바꾸고자 하는 단어 word가 포함되어 있는지 확인한다.

포함되어 있지 않은 경우에는, 그대로 buffer1을 새로운 파일에 출력하도록 한다.

 

단어가 포함되어 있는 경우엔, strtok() 함수를 이용해 토큰을 분리하여 각 단어들을 null로 초기화한 buffer2에 이어붙인다.

토큰을 분리할 때 마다, 그 단어가 word와 같은지 strcmp() 함수를 이용하여 확인한 뒤

같지 않은 경우엔 그대로 word를 이어붙이고,

같은 경우에는 입력받은 new를 이어붙인다.

 

마지막 토큰을 처리한 이후에는, 줄바꿈 문자와 함께 buffer2를 새로운 파일에 출력하도록 한다.

(위에 언급한 바와 같이 buffer2는 buffer1과 다르게 줄바꿈 문자가 포함되어있지 않으므로, fputs()를 사용하게 되면 그다음 문장과 구분을 하기 어렵다. 따라서 서식화된 출력을 위해 fprintf() 함수를 사용하도록 하자.)

 

 

아래 결과보기

더보기

위 문제의 예시를 사용했다.

snowman_lyrics.txt 파일의 문자열을 읽어

snowman 단어를 대문자 SNOWMAN으로 변환하여

새로운 snowman_lyrics.txt 에 저장해보았다.

 

 

문자열을 제대로 읽었는지 확인하기 위해 각 줄의 문자열을 출력하도록 하였고,

snowman단어가 포함된 문장은 또 따로 출력하도록 하였다.

 

 

기존의 원본 파일과 새로 생성된 파일을 비교하면, 프로그램이 제대로 실행되었는지 확인할 수 있다.

snowman 이 대문자로 변경되어 있음을 확인 가능하다.

 

728x90
반응형
Comments