요약
1. C 프로그램은 입력을 바이트들의 스트림으로 인식한다.
- 파일, 키보드와 같은 입력장치, 다른 프로그램의 출력은 스트림의 소스가 될 수 있음
2. C에서 특정 파일에 접근하기 위해선,
1) 파일 포인터(FILE *형)을 생성
2) 그 포인터에 하나의 특정 파일 이름을 연결
3. 파일의 끝
- C의 입력 함수들은 파일의 끝을 나타내는 EOF 문자를 읽으면, 파일의 끝에 도달했다고 인식한다.
1. 텍스트 인식 vs 바이너리 인식
ANSI C는 파일을 읽을 때, 두 가지 인식을 제공한다; 텍스트 인식(text)과 바이너리(binary) 인식
2. 입출력 수준
두 가지 입출력 수준: 저수준 입출력 (Low-level I/O)와 표준 고수준 입출력 (Standard high-level I/O)
1) 저수준 입출력: 운영체제가 제공하는 기본적인 입출력 서비스
2) 표준 고수준 입출력: C라이브러리 함수들의 표준 패키지와 stdio.h 헤더파일 정의들을 사용함
- 모든 운영체제가 동일한 저수준 입출력을 사용한다고 보장하지 않기 때문에, ANSI C는 표준 입출력 패키지만 제공함
- 즉, ANSI C가 표준 입출력 모델의 이식성을 보장
3) 표준 파일: 표준입력 (standard input), 표준 출력 (standard output), 표준 에러 출력 (standard error output)
- C 프로그램은 사용자를 위해, 위 3개의 파일들을 자동으로 연다.
표준 파일 | 설명 |
표준 입력 | - 디폴트로서, 일반적으로 시스템이 사용하는 키보드와 같은 입력 장치 - 프로그램에 입력을 제공 - C 함수: getchar(), gets(), scanf() |
표준 출력 | - 디폴트로서, 일반적으로 시스템이 사용하는 디스플레이 스크린과 같은 출력 장치 - 프로그램의 정상적인 출력을 나타냄 - C 함수: putchar(), puts(), printf() |
표준 에러 | - 디폴트로서, 일반적으로 시스템이 사용하는 디스플레이 스크린과 같은 출력 장치 - 에러 메시지를 출력할 논리적으로 구분되는 어떤 장소를 제공함 |
* 리다이렉션은 다른 파일들을 표준 입출/출력으로 인식하게 함
3. 표준 입출력
표준 입출력 패키지는 저수준 입출력에 비해 다음 세 가지 장점을 가진다.
(1) 이식성
(2) 다양한 입출력을 처리하는 전문화된 함수들 제공
(3) 입력과 출력에 버퍼*(buffer)를 사용
1) 버퍼란?
- 정보가 한번에 한 바이트씩 전달되지 않고, 큰 덩어리로 버퍼라는 공간에 전송이 됨.
- 이러한 버퍼링은 데이터의 전송 속도를 크게 증가 시킴
파일 포인터
1) 파일포인터란?
- stdio.h에 정의되어있는 유도 데이터
- 파일 포인터는 실제 파일을 가리키지 않은다. 대신, 그 파일의 입출력에 사용되는 버퍼 정보를 포함하여 그 파일에 관련된 정보가 들어있는 데이터들을 가리킴
2) stdin, stdout, stderr
- stdio.h 헤더 파일은 다음 세 개의 파일 포인터를 표준 파일에 연결한다.
- stdin, stdout, stderr는 모두 FILE을 가리키는 포인터형이다.
표준파일 | 파일 포인터 | 일반적으로 |
표준입력 | stdin | 키보드 |
표준 출력 | stdout | 스크린 |
표준 에러 | stderr | 스크린 |
함수 1: fopen()
함수
1) Desciption:
(a) 해당 함수를 이용하여 파일을 연다.
(b) Reference: https://www.cplusplus.com/reference/cstdio/fopen/
2) Header file: stdio.h
3) Syntax: fopen(const char *filename, const char *mode)
(a) const char *filename
: 열려고 하는 파일의 이름 (또는 파일 이름을 가지고 있는 문자열의 주소)
(b) const char *mode
: 파일을 열 때, 사용되는 모드 문자열들
4) Return:
(a) 해당 함수가 정상적으로 파일을 열게되면, 파일 포인터를 리턴한다. 정상적으로 열지 못한 경우, NULL 포인터를 리턴함.
(b) 파일 포인터: 해당 파일을 나타내는데 사용되는 포인터
* fopen의 모드
모드 | 문자열 읽기 |
"r" | - 읽기를 위해 파일을 텍스트 모드로 연다 - "r+": 갱신을 하기 위해 텍스트 모드로 연다 |
"w" | - 쓰기를 위해 파일을 텍스트 모드로 연다. - 파일이 이미 존재하면 파일의 길이를 0으로 만든다. - 파일이 없으면 새 파일을 만든다. - "w+": 파일을 갱신하기 위해 텍스트 모드로 연다 |
"a" | - 쓰기를 위해 파일을 텍스트 모드로 연다. - 파일이 이미 존재하면 그 파일의 끝에 덧붙임 - 파일이 없으면 새 파일을 만듬 |
"rb" | - 읽기를 위해 바이너리 모드로 연다. |
"wb" | - 읽기를 위해 바이너리 모드로 연다. - 파일이 존재하면 파일의 길이를 0으로 만든다. |
"ab" | - 파일을 읽기 위해 바이너리 모드로 연다. - 파일이 이미 존재하면 그 파일의 끝에 덧붙인다. |
함수 2: fclose()
- fclose(fp)
- 필요할 때 버퍼를 비우면서 fp가 가리키는 파일을 닫음
- 리턴값: fclose()가 파일을 성공적으로 닫으면 0을 리턴하지만 그렇지 않을 경우, EOF를 리턴함
- 파일이 정상적으로 닫혔는지 확인하는 코드
if (fclose(fp) != 0) {
printf("There is an error when a file closes");
}
Note: 파일 끝 (EOF: End of File)
1) EOF라는 특별한 문자를 통해, 프로그램은 파일의 끝에 도달했음을 확인할 수 있음.
2) EOF를 확인하는 코드
- 파일 스트림으로부터 문자를 하나씩 읽는 과정에서, 파일의 끝에 도달했는지를 확인하는 코드
int chl
FILE *fp;
fp = fopen("example.txt", "r");
while((ch = getc(fp)) != EOF){
putchar(ch);
}
파일 입/출력 함수 1: getc() 및 putc() 함수
1) getc()와 putc()는 getchar()와 putchar()와 비슷하게 동작함.
- 차이점: getc()와 putc()의 경우, 어느 파일을 사용할 것인지 알려줘야 함.
- getchar()와 putchar()는 getc()와 putc()로부터 각각 표준입력과 표준출력을 사용하여 정의한다.
2) getc(*FILE file_in_pointer);
- file_in_pointer가 가리키는 파일로부터 하나의 문자를 얻음
3) putc(char ch, *FILE file_out_pointer):
- 문자 ch를 file_out_pointer이 가리키는 파일에 출력한다.
※ NOTE
- stdout: 표준 출력에 사용되는 파일 포인터로 정의
- 아래의 두 표현은 동일하다.
char ch = 'a';
putc(ch, stdout);
putchar(ch);
파일 입출력 함수: fprintf(), fscanf(), fgets(), fputs()
- 아래의 파일 입출력 함수들은 문자와 문자열을 다루는 텍스트 지향적인 함수들이다.
- 만일 수치 데이터를 저장한다면, fread() 및 fwrite()와 같이 바이너리 데이터를 입출력할 수 있는 함수를 사용해야 한다.
- 아래 함수들은 순차 접근 (Sequential Access)* 함수들이다.
* 순차접근: 파일 내용을 순차적으로 처리함
fgets(char *string, int num, FILE *fptr) | 1. parameters a. 첫번째 인자: - 입력을 저장할 주소 (파일로부터 읽은 문자열을 저장할 배열) - char형 배열의 이름 b. 두번째 인자: 입력 문자열의 최대 크기를 나타내는 정수 c. 세번째 인자: 읽을 파일을 가리키는 파일 포인터 2. 설명 - 상한(upper limit)보다 하나 적은 수의 문자들을 읽을 때까지 또는 파일을 끝을 만날 때까지 문자들을 읽되, 첫 개행 문자까지 읽는다. 그 후 읽은 것이 문자열이 되도록 종결 널문자(\0)을 추가한다. 3. gets()와 차이점: a. gets()은 개행문자를 버린 후, 널문자를 추가하지만, fgets() 함수는 만약 개행문자를 읽는다면, 개행문자 뒤에 널문자를 추가한다. b. fgets()는 기억 공간 오버플로를 방지할 수 있기 때문에, gets()보다 중요한 프로그램에서 더 사용하기 좋음 c. fgets()는 개행을 읽어 문자열 안에 넣고 puts()는 출력에 개행을 넣기 때문에, fgets()는 fputs()과 함께 사용해야 한다. 4. 리턴값 - EOF를 만났을 경우, NULL 값을 리턴함 - 그렇지 않은 경우, 함수에 전달된 주소를 리턴함 |
예시) fgets()를 사용하여, 표준 입력(키보드)으로부터 문자들을 읽은 후, 표준 출력으로 다시 출력
#incldue <stdio.h>
#define MAXLINE 20
int main(void){
char line[MAXLINE];
while (fgets(line, MAXLINE, stdin) != NULL && line[0] != '\n') fputs(line, stdout);
}
바이너리 파일 입출력: fread(), fwrite() 함수
파일이 저장될 때, 만일 fprintf() 함수와 같은 텍스트 지향적인 함수를 사용하게 되면, 수치데이터가 문자열로 변환 후 저장이 되는 것이기에 값이 변하거나 원래 수치데이터의 정밀도를 정확하게 회복할 수 없다. 따라서 수치 데이터를 저장할 때에는 바이너리 데이터 그 자체를 저장하여 데이터 손실이 없도록 해야 한다.
함수 | 설명 |
size_t fwrite() | • 바이너리 데이터를 파일에 저장한다. • size_t fwrite(const void * restrict ptr, size_t size, size_t nmemb, FILE * restrict fp); - ptr: 저장할 데이터 덩어리의 주소 - size: 저장할 그 데이터의 덩어리 크기 (바이트 단위) - nmemb: 저장할 데이터 덩어리들의 수 - fp: 그 데이터가 저장될 파일 • 리턴값: 성공적으로 저장한 항목의 수를 리턴 (nmemb). 단, 에러가 발생하면, nmemb보다 작을 수 있음 • example char buffer[256]; fwrite(buffer, sizeof(char), 256, fp) |
size_t fread() | • 바이너리 데이터를 파일로부터 읽음 • size_t fread(void * restrict ptr, size_t size, size_t nmemb, FILE * restrict fp); - 전달인자는 fwrite()와 동일 • 리턴값: - 성공적으로 읽은 항목의 수를 리턴 (nmemb). - 단 에러가 발생하면, nmemb보다 작을 수 있음 |
파일의 임의 접근: fseek(), ftell(), fgetpos(), fsetpos()
함수 | 설명 |
fseek() | • fseek(FILE * stream, long int offset, mode); a. steam: 처리할 파일을 가리키는 FILE 포인터 b. offset: - 시작 위치로부터 얼마나 멀리 가야하는지를 알려줌 - 해당 값은 long으로 정의되어야 함 - 양수: 앞으로 이동/ 0: 현재 위치/ 음수: 뒤로 이동 c. 모드: 시작 위치를 나타냄 (자세한 내용은 아래 참조) • 리턴값: 모든 것이 성공적이면 0을 리턴, 그렇지 않으면 -1를 리턴 |
ftell() | • 파일의 현재 위치를 리턴함 - 파일의 시작으로부터 첫 바이트를 0으로 하는 바이트 수를 리턴함으로써 파일 위치를 지정 |
fgetpos() | • fseek() 및 ftell()의 문제점: 파일 크기를 long형으로 나타낼 수 있는 값으로 제한함 • fgetpos(FILE * restrict stream, fpos_t * restrict pos); - 호출되었을 때, fpos_t값을 pos가 가리키는 위치에 넣는다. 해당 위치는 파일 안의 어떤 위치를 나타냄 - 성공하면 0을 실패하면 0이 아닌 값을 리턴함 - fpos_t: 해당 변수나 데이터 객체는 파일 안에서 어떤 위치를 지정할 수 있다. |
fsetpos() | • int fsetpos(FILE *stream, const fpost_t *pos); - pos가 가리키는 위치에 있는 fpos_t 값을 사용하여, 파일 포인터를 그 값이 나타내는 위치로 설정한다. - fpos_t 값은 바로 전의 fgetpos() 호출에서 얻은 값이어야 함 • 리턴값: - 성공하면 0, 실패하면 0이 아닌 값을 리턴 |
fseek() 및 ftell()의 모드
mode | 시작 위치 |
SEEK_SET | 파일의 시작 |
SEEK_CUR | 현재 위치 |
SEEK_END | 파일의 끝 |
예시)
FILE *fp;
fp = fopen("example.txt", "r");
fseek(fp, 0L, SEEK_SET); // 파일의 시작으로 간다.
fseek(fp, 10L, SEEK_SET); // 파일의 시작위치에서 10바이트 앞으로 이동
fseek(fp, 2L, SEEK_CUR); // 파일의 현재 위치에서부터 2바이트 앞으로 이동
fseek(fp, 0L, SEEK_END); // 파일의 마지막위치로 이동
fseek(fp, -10L, SEEK_END); // 파일의 마지막 위치에서 10바이트 뒤로 이동
파일의 에러: feof() 및 ferror() 함수
표준 입력 함수가 EOF를 반환할 때의 두 가지 가능성:
1) 파일의 끝에 도달
2) 예기치 못한 에러로 인한 EOF 반환
따라서, 두 가능성을 구별할 수 있는 함수들이 필요하다.
함수 | 설명 |
int feof(FILE *fp); | - 마지막 입력 호출에서 a. 파일의 끝을 만나면, 0이 아닌 값을 리턴함 b. 파일의 끝이 아닐 경우, 0을 리턴 |
int ferror(FILE *fp) | - 읽기 에러나 쓰기 에러가 a. 발생하는 경우: 0이 아닌 값을 반환 b. 발생하지 않은 경우: 0을 반환 |
Appendix 1: 바이너리 입출력을 사용하는 임의 접근
#include <stdio.h>
#include <stdlib.h>
#define ARSIZE 1000
int main(){
double number[ARSIZE];
double value;
const char *file = "number.dat";
long pos;
FILE *iofile;
// double 형으로 이루어진 한 집합을 만듦
for (int i =0; i < ARSIZE; i++)
number[i] = 100.0 + i + 1.0/(i + 1);
// 파일을 열기 위해 시도함
if ((iofile = fopen(file, "wb")) == NULL){
// stderr로 메시지를 출력함
fprintf(stderr, "출력을 위한 %s 파일을 열 수 없습니다.\n", file);
exit(1);
}
// 배열을 바이너리 포맷으로 파일에 저장
fwrite(number, sizeof(double), ARSIZE, iofile);
fclose(iofile);
// 저장한 파일을 다시 읽어서 확인하는 절차
if ((iofile = fopen(file, "rb")) == NULL){
fprintf(stderr, "임의 접근을 위한 %s 파일을 열 수 없습니다.\n", file);
exit(1);
}
// 파일로부터 선택된 항목을 읽음
printf("0-%d 범위 내에서 하나의 인덱스를 입력하시오.\n", ARSIZE - 1);
int i;
scanf("%d", &i);
while(i > 0 && i < ARSIZE){
pos = (long) i * sizeof(double); // 오프셋을 계산함
fseek(iofile, pos, SEEK_SET); // 오프셋 위치로 이동함
fread(&value, sizeof(double), 1, iofile); // 해당 오프셋에 위치한 값을 읽음
printf("그 위치에 있는 값은 %f입니다.\n", value);
printf("다음 인덱스를 입력하시오:");
scanf("%d", &i);
}
fclose(iofile);
}
'프로그래밍 언어 > C, C++의 기본적인 설명' 카테고리의 다른 글
[C 언어] 명령행 전달인자 (argc, argv) (0) | 2022.03.25 |
---|---|
[표준 ANSI C 라이브러리] complex.h (0) | 2022.03.24 |
[표준 ANSI C 라이브러리] stdio.h (0) | 2022.03.04 |
[C/C++] 기본적인 입출력함수 (0) | 2022.02.27 |
[C/C++] 디버그, 신텍스 에러, 세만틱 에러 (0) | 2022.02.19 |
댓글