pc관련/C언어2019. 4. 11. 21:33

* 디스크 파일의 사용
대부분의 프로그램에서는 데이터나 도는 사용 환경을 저장하는 등 여러 가지 목적으로 디스크 파이를 사용한다. 오늘은 다음과 같은 내용을 다룰 것이다.

·디스크 파일에 관련된 스트림
·C에서 사용되는 두 가지 형태의 디스크 파일
·파일에 데이터를 저장하는 방법
·파일에서 데이터를 읽어들이는 방법
·파일을 닫는 방법
·디스크 파일의 관리
·임시 파일의 사용

1. 스트림과 디스크 파일
: C는 디스크 파일을 포함하여 모든 입력과 출력을 스트림으로 수행한다. 앞에서는 키보드,화면, 그리고 DOS 시스템에서 프린터와 같이 특정 장치와 연결되어 있는 C의 미리 정의된 스트림을 사용하는 방법을 다루었다. 디스크 파일 스트림은 기본적으로 동일한 방법으로 사용된다. 이것은 스트림을 통한 입출력의 한 가지 장점이다. 한 가지 스트림을 사용하는 방법은 다른 스트림에서 변화가 없거나 약간만 다르게 해서 사용할 수 있다. 디스크 파일 스트림에서 중요한 차이점이 있다면, 프로그램이 특정 디스크 파일과 관련된 스트림을 반드시 생성해야 한다는 것이다.

2. 디스크 파일의 종류
: 14번째 강의에서 C의 스트림에 텍스트(text)와 이진(binary)의 두 가지 종류가 있다는 것을 설명했다. 이런 두 가지 형태의 스트림 중에서 어떤 것도 파일과 관련될 수 있는데, 파일을 적절한 모드로 사용하기 위해서는 각각의 특성을 이해할 필요가 있다.

텍스트 스트림(text stream)은 텍스트 모드 파일과 관련되어 있다. 텍스트 모드 파일은 일련의 문장들로 구성된다. 각각의 문장은 문자로 구성되고 문장의 마지막을 나타내는 하나 이상의 문자를 포함한다. 문장의 최대 길이는 255자이다. '문장'은 C의 문자열과 다르다는 것을 기억할 필요가 있다. 문장에는 널 문자(\0)가 포함되지 않는다. 텍스트 모드의 스트림을 사용할 때 C의 문장 진행 문자(\n)와 운영체제가 디스크 파일에서 문장의 마지막을 표시하기 위해서 사용하는 문자 사이에는 변환이 수행된다. DOS 환경에서는 개행 문자(CR : carriage return)와 다음 줄 문자(LF : line feed)로 변환된다. 데이터가 텍스트 모드의 파일에 저장될 때 각각의 \n은 CR-LF로 변환된다. 데이터가 디스크 파일에서 읽어들여질 때 DR-LF는 다시 \n으로 변환된다. UNIX 환경에서는 어떤 변환도 수행되지 않으므로 문장 진행 문자가 변경되지 않고 그대로 남는다.

이진 스트림(binary stream)은 이진 모드 파일과 관련되어 있다. 모든 데이터는 있는 그대로 저장되거나 읽어들여지고, 문장 내에서 구분이나 문장의 마지막을 표시하는 문자는 사용되지 않는다. NULL과 문장의 마지막을 표시하는 문자는 특별한 의미를 가지지 않으며 다른 어떤 데이터와 똑같이 취급된다.

어떤 파일 입출력 함수는 한 가지 파일 모드에서만 사용될 수 있고, 다른 어떤 함수는 두 가지 모드에서 사용될 수 있다. 이 장에서는 어떤 함수를 어떤 모드에서 사용해야 하는지 알려줄 것이다.

3. 파일 이름
: 모든 디스크 파일은 이름을 가지고 있고 디스크 파일을 다룰 때에는 반드시 파일 이름을 사용해야 한다. 파일 이름은 다른 텍스트 데이터와 마찬가지로 문자열에 저장된다. 파일 이름을 위해 적용되거나 제한되는 규칙은 운영체제마다 다른다. DOS와 윈도우 3.x에서 파일 이름은 1 ~ 8자까지의 이름, 선택적으로 사용되는 마침표(.), 3자까지의 확장자로 구성된다. 반면에, 윈도우 95(98)와 NT, 대부분의 UNIX 시스템에서는 256자까지의 파일 이름을 사용할 수 있다.

운영체제마다 다른 것으로는 파일명에 허용되는 문자도 포함된다. 예를 들어, 윈도우 95에서는 다음 문자들이 허용되지 않는다.

/ \ : * ? " < > |

여러분은 사용중인 운영체제에 따라 파일명 규칙을 지키고 주의해야 한다. 또한, C 프로그램에서의 파일명은 경로 정보를 가질 수 있다. 경로(path)는 파일이 위치된 드라이브와 디렉토리(또는 폴더)를 가리킨다. 만약 경로 없이 파일명을 지정하면 프로그램은 파일이 운영체제가 현재 기본적으로 사용중인 디렉토리에 있다고 가정할 것이다. 파일명의 일부분으로 경로 정보를 항상 지정하는 것은 좋은 프로그래밍 습관이다. PC에서 백슬래시 문자(\)는 경로의 디렉토리 이름을 구분하는 데 사용된다. 예를 들어, DOS와 윈도우에서 다음은

c:\data\list.txt

드라이브 C의 디렉토리 \DATA에 저장되어 있는 LIST.TXT라는 이름의 파일을 뜻한다. 백슬래시 문자가 문자열에서 사용될 때에는 C에 대해서 특수한 의미를 가진다는 것을 알고 있을 것이다. 그래서 백슬래시 문자 자체를 표현하려면 하나의 백슬래시를 추가해야 한다. 앞에 나타난 파일 이름은 C 프로그램에서 다음과 같이 표현될 것이다.

char *filename = "c:\\daa\\list.txt;

키보드에서 파일 이름을 입력한다면 하나의 백슬래시만을 사용할 수 있다. 모든 시스템에서 백슬래시가 디렉토리 구분자로 사용되는 것은 아니다. 예를 들어, UNIX에서 는 일반적인 슬래시(/)를 사용한다.

4. 파일 열기
: 디스크 파일에 관련된 스트림을 생성하는 과정을 파일 열기(opening)라고 한다. 파일을 열게 되면 파일에서 프로그램으로 데이터를 읽어들이는 읽기(reading) 동작이나 프로그램에서 파일로 데이터를 저장하는 쓰기(writing) 동작, 또는 두 가지 모두가 가능하게 된다. 파일의 사용을 마치게 되면 닫아야 한다. 파일을 닫는 것에 대해서는 이 장에서 나중에 설명할 것이다. 파일을 열기 위해서는 라이브러리 함수 fopen()을 사용한다. fopen()의 원형은 STDIO.H에 나타나 있으며 다음과 같다.

FILE *fopen(const char *filename, const char *mode);

이 원형은 fopen()이 STDIO.H에 선언된 구조체인 FILE형에 대한 포인터를 돌려준다는 사실을 알려준다. FILE 구조체의 멤버는 프로그램에서 여러 가지 파일 관리를 수행하기 위해 사용되지만, 여기에서는 자세한 내용을 알 필요가 없다. 그러나 열기 원하는 각각의 파일에 대해서는 FILE형에 대한 포인터를 선언해야 한다. fopen()을 호출하면 함수는 FILE 구조체형 변수를 생성하고, 생성된 구조체에 대한 포인터를 돌려준다. 파일을 사용하는 이후의 모든 동작에서는 이 포인터가 사용된다. 만약 fopen() 함수에서 문제가 발생하면 NULL을 돌려준다. 예를 들어, 이런 실패는 하드웨어 에러나 또는 초기화되지 않은 디스크에서 파일을 열려고 할 때 발생할 수 있다.

인수 filename은 열리는 파일의 이름이다. 앞에서도 언급했듯이 filename에는 경로 정보가 포함될 수 있고, 포함되는 것이 좋다. 인수 filename은 큰 따옴표 내에 포함된 일반적인 문자열이나 도는 문자열 변수에 대한 포인터가 될 수 있다. 인수 mode는 열릴 파일의 사용 모드를 지정하는 것이다. mode는 이진(binary)모드나 텍스트(text) 모드, 그리고 읽기(reading)나 쓰기(writing), 또는 두 가지 모두 중에서 어떤 상태로 열려야 하는지 제어한다. mode에 사용할 수 있는 값은 <표 16.1>에 나타나 있다.

<표 16.1> fopen() 함수의 mode의 값

모드 의미
r 읽기 상태로 파일을 연다. 지정된 이름의 파일이 존재하지 않으면 fopen()은  NULL을 돌려준다.
w 쓰기 상태로 파일을 연다. 지정된 이름의 파일이 존재하지 않으면 생성된다. 지정된 이름의 파일이 존재하면 경고 없이 삭제되고 새롭고 비어 있는 파일 이 생성된다.
a 데이터 추가 상태로 파일을 연다. 지정된 이름의 파일이 존재하지 않으면  생성된다. 파일이 이미 존재한다면 새로운 데이터는 파일의 마지막에  추가된다.
r++ 읽기와 쓰기 상태로 파일을 연다. 지정된 이름의 파일이 존재하지 않으면  생성된다. 파일이 이미 존재한다면 새로운 데이터는 이전의 데이터를  덮어쓰며 파일의 시작 부분에 위치된다.
w+ 읽기와 쓰기 상태로 파일을 연다. 지정된 이름의 파일이 존재하지 않으면 생성된다. 파일이 이미 존재한다면 덮어써진다.
a+ 읽기와 데이터 추가 상태로 파일을 연다. 지정된 이름의 파일이 존재하지  않으면 생성된다. 파일이 이미 존재한다면 새로운 데이터는 파일의 마지막에  추가된다.

기본적으로 설정되어 있는 파일의 모드는 텍스트(text)이다. 파일을 이진 모드로 열기 위해서는 인수 mode에 b를 추가해야 한다. 인수 mode에 a를 포함시키면 텍스트 모드의 파일을 데이터 추가가 가능한 상태로 열어주고, ab를 포함시키면 이진 모드의 파일을 데이터 추가가 가능한 상태로 열어줄 것이다. 만약 에러가 발생하면 fopen()은 NULL을 돌려준다는 사실을 기억하자. 다음과 같은 경우에는 NULL의 복귀값을 얻게 될 것이다.

·유효하지 않은 파일 이름을 사용한 경우

·준비되지 않은 디스크에서 파일을 열려고 할 때. 예를 들어, 드라이브가 닫히지 않았거나 디스크가 초기화되어 있지 않을 때

·존재하지 않는 디렉토리나 디스크 드라이브의 파일을 열려고 할 때

·존재하지 않는 파일을 'r' 모드로 열려고 할 때

fopen()을 사용할 때에는 에러가 발생했는지 확인할 필요가 있다. 구체적으로 어떤 에러가 발생했는지 정확하게 알 수 있는 방법은 없지만, 적절한 메시지를 출력하고 다시 파일을 열도록 해주거나 프로그램을 종료할 수 있다. 대부분의 C 컴파일러는 에러의 상태에 대한 정보를 알 수 있게 해주는 ANSI 비 호환 확장 기능을 포함하고 있다.

<리스트 16.1> 다양한 모드로 디스크 파일을 여는 fopen()의 사용

/* fopen() 함수의 사용 예 */

#include

 

main()

{

FILE *fp;

char ch, filename[40], mode[4];

 

while(1)

{

 

/* 파일명과 모드 입력 */

 

printf("\nEnter a filename: ");

gets(filename);

printf("\nEnter a mode (max 3 characters): ");

gets(mode);

 

/* 파일 열기를 시도함 */

 

if((fp = fopen(filename, mode)) != NULL)

{

printf("\nSuccessful opening %s in mode %s.\n,

filename, mode);

fclose(fp);

puts("Enter x to exit, any other to continue.");

if((ch = getc(stdin)) == 'x')

break;

else

continue;

}

else

{

fprintf(stderr, "\nError opening file %s in mode %s.\n",

filename, mode);

puts("Enter x to exit, any other to try again.");

if((ch = getc(stdin) == 'x')

break;

else

continue;

}

}

}

 

-> 입력 / 출력

Enter a filename : junk.txt

Enter a mode (max 3 characters): w

Successful opening junk.txt in mode w.
Enter x to exit, any other to continue.
j

Enter a filename: morejunk.txt

Enter a mode (max 3 characters): r

Error opening morejunk.txt in mode r.
Enter x to exit, any other to try again.
x

5. 파일에 데이터 기록하고 읽어들이기
: 디스크 파일을 사용하는 프로그램에서는 파일에 데이터를 기록하거나 파일에서 데이터를 읽어들이고 또는 두 가지를 모두 수행할 수 있다. 디스크 파일에 데이터를 저장하기 위해서 는 세가지 방법을 사용할 수 있다.

·형식화된 데이터를 파일에 저장하기 위해서 형식화된 출력을 사용할 수 있다. 형식화된 출력은 텍스트 모드의 파일에서만 사용해야 한다. 형식화된 출력의 기본적인 용도는 스프레드시트나 데이터베이스 등의 다른 프로그램에서 사용되는 텍스트와 숫자 데이터를 가지는 파일을 생성하는 것이다. 또한, 드물기는 하지만 C 프로그램에서 사용할 파일을 생성하기 위해서 형식화된 출력을 사용한다.

·한 문자나 문장을 파일에 저장하기 위해서 문자 출력을 수행할 수 있다. 기술적으로 이진 모드의 파일에 문자 출력을 수행하는 것은 불가능하지만 특수한 방법으로는 가능할 수도 있다. 문자 출력은 텍스트 파일에만 제한하여 사용해야 한다. 문자 출력은 문서 작성기와 같은 프로그램뿐 아니라 C 에서도 사용할 수 있는 형식으로, 숫자가 아닌 텍스트 데이터를 저장하기 위해서 주로 사용된다.

·메모리의 일부분에 저장된 내용을 디스크 파일에 저장하기 위해서 직접 출력을 사용할 수 있다. 이 방법은 이진 파일에서만 사용된다. 직접 출력은 나중에 C 프로그램에서 사용하기 위한 데이터를 저장하는 가장 좋은 방법이다. 파일에서 데이터를 읽어들이는 경우에도, 앞에서 설명한 것과 마찬가지로 형식화된 입력, 문자 입력, 또는 직접 입력의 세 가지 방법을 사용할 수 있다. 파일을 읽어들이는 경우에 사용하는 방법은 대개 파일의 특성에 따라 다르다. 일반적으로, 여러분은 파일을 저장했을 때와 같은 방법으로 데이터를 읽어들이게 되지만 반드시 지켜야 하는 규칙은 아니다. 그러나 파일을 저장했을 때와 다른 모드로 파일을 읽어들이려면 C와 파일 형식에 대한 충분한 지식이 필요하다.

앞에서 파일 입력과 출력의 세 가지 형태를 잘 이해했다면 각각의 형태가 어떤 경우에 가장 적합한지 알 수 있을 것이다. 그러나 이런 구분은 결코 사용을 제한하기 위한 것이 아니다. C 언어의 한 가지 장점은 융통성이므로, 숙련된 프로그래머는 파일 출력의 형태를 대부분 필요성에 맞추러 사용할 수 있다. 초보적인 프로그래머는 경우에는 다음에서 설명할 내용을 참고로 해서 프로그래밍에 적용할 수 있을 것이다.

5.1 형식화된 파일 입력과 출력
: 형식화된 파일 입/출력은 특정 방법으로 형식화된 텍스트와 숫자 데이터를 다룬다. 이것을 14번째 강의에서 설명한 printf()와 scanf() 함수를 통해서 키보드 입력과 화면 출력을 형식화하는 것에 비유할 수 있다. 우선, 형식화된 출력에 대해서 설명한 후에 입력을 다룰 것이다.

▶ 형식화된 파일 출력
: 형식화된 파일 출력은 라이브러리 함수 fprintf()를 통해서 수행된다. fprintf()의 원형은 헤더 파일 STDIO.H에 정의되어 있으며, 다음과 같다.

int fprintf(FILE *fp, char *fmt, ...);

첫 번째 인수는 FILE형에 대한 포인터이다. 데이터를 특정 디스크 파일에 기록하기 위해서는 fopen()을 사용하여 파일을 열었을 때 구해지는 포인터를 전달해야 한다. 두 번째 인수는 형식화 문자열이다. 형식화 문자열에 대한 내용은 14번째 강의에서 printf()를 설명할 때 다루었다. fprintf()에서 사용되는 형식화 문자열은 printf()에서 사용되는 것과 똑같은 규칙을 따른다. 상세한 내용은 14번깨 강의를 참조하기 바람.....^^;; 마지막 인수는 말줄임표(...)이다. 이것은 무슨 뜻일까? 함수 원형에서 사용되는 말줄임표(...)는 변칙적인 개수의 인수를 뜻한다. 즉, fprintf()는 파일 포인터와 형식화 문자열을 인수로 가지며, 추가로 필요한 만큼 많은 인수를 받아들일 수 있다. 이것은 printf()와 비슷하다. 추가로 사용되는 인수는 지정된 스트림으로 출력되는 변수의 이름이다. fprintf()는 인수 목록에서 지정된 스트림으로 출력 내용을 전달한다는 것을 제외하면 printf() 와 비슷하게 동작한다는 것을 기억하기 바란다. 사실, fprintf()에서 stdout을 스트림 인수로 지정한다면 fprintf()는 printf()와 동일하다. <리스트 16.2>에 있는 프로그램은 fprintf()를 사용하고 있다.

<리스트 16.2> fprintf()가 파일과 stdout으로 동일한 내용의 형식화된 출력을 수행한다는 것을 보여주는 예

/* fprintf() 함수의 사용 예 */

#include

#include

void clear_kb(void);

 

main()

{

FILE *fp;

float data[5];

int count;

char filename[20];

 

puts("Enter 5 floating point numerical values.");

 

for(count = 0; count < 5; count++)

scanf("%f", &data[count]);

 

/* 파일명을 구하고 파일을 연다. */

/* 우선 stdin에서 나머지 문자를 지운다. */

 

clear_kb();

 

puts("Enter a name for the file.");

gets(filename);

 

if((fp = fopen(filename, "w")) == NULL)

{

fprintf(stderr, "Error opening file %s.", filename);

exit(1);

}

 

/* 숫자 데이터를 파일과 stdout으로 기록한다. */

 

for(count = 0; count < 5; count++)

{

fprintf(fp, "\ndata[%d] = %f", count, data[count]);

fprintf(stdout, "\ndata[%d] = %f", count, data[count]);

}

fclose(fp);

printf("\n");

return(0);

}

 

void clear_kb(void)

/* stdin에서 나머지 문자를 지운다. */

{

char junk[80];

gets(junk);

}

 

▶ 형식화된 파일 입력
: 형식화된 파일 입력에서는 입력 동작이 stdin 대신에 지정된 스트림을 통해서 수행된다는 것을 제외하면 scanf()와 비슷하게 동작하는 라이브러리 함수 fscanf()를 사용한다. scanf()는 14번째 강의에서 설명했다. fcsanf()의 원형은 다음과 같다.

int fscanf(FILE *fp, const char *fmt, ...);

인수 fp는 fopen()이 돌려주는 FILE형에 대한 포인터이고, fmt는 fscanf()가 입력을 받아들이 는 방법을 지정하는 형식화 문자열에 대한 포인터이다. 형식화 문자열의 구성 요소는 scanf()에서 사용되는 것과 동일하다. 마지막으로, 말줄임표(...)는 fscanf()가 입력된 값을 할당하는 변수의 주소인 하나 이상의 추가적인 인수를 뜻한다. fscanf()를 사용하기 전에 14번째 강의에서 설명한 내용을 한 번 더 읽어볼 필요가 있을 것이다. 함수 fscanf()는 stdin 대신에 지정된 스트림에서 문자를 읽어들인다는 것을 제외하면 scanf()와 똑같은 방법으로 사용된다. fscanf()를 사용해보기 위해서는 함수가 읽어들일 수 있도록 형식화된 약간의 숫자나 문자열 이 저장된 텍스트 파일이 필요하다. 에디터를 사용하여 공백(빈칸이나 문장 진행 문자)으로 구분되는 5개의 부동 소수형 숫자를 가지는 INPUT.TXT라는 이름의 파일을 생성하자. 예를 들어, 다음과 같은 파일을 생성할 수 있다.

123.45 87.001
100.02
0.00456 1.0005

이제 <리스트 16.3>에 있는 프로그램을 컴파일하고 실행하자.]

<리스트 16.3> 디스크파일에서 형식화된 데이터를 읽어들이기 위해 fscanf()를 사용하는 예제

/* fscanf()로 형식화된 파일 데이터 읽어들이기 */

#include

#include

 

main()

{

float f1, f2, f3, f4, f5;

FILE *fp;

 

if((fp = fopen("INPUT.TXT", "r")) == NULL)

{

fprintf(stderr, "Error opening file.");

exit(1);

}

 

fscanf(fp, "%f %f %f %f %f", &f1, &f2, &f3, &f4, &f5);

printf("The values are %f, %f, %f, %f, and %f.", f1, f2, f3, f4, f5);

 

fclose(fp);

return(0);

}

 

5.2 문자 입력과 출력
: 디스크 파일에서 수행되는 문자 입출력(character I/O)은 한 문자뿐 아니라 문자들로 구성되는 문장을 대상으로 한다. 문장은 문장 진행(newline) 문자로 종료되는 일련의 문자들이라는 것을 기억하자. 문자 입출력은 텍스트 모드파일에서 수행해야 한다. 문자 입출력 함수에 대한 설명과 예제 프로그램을 살펴보도록 하자.

▶ 문자 입력
: 한 문자를 읽어들이기 위한 getc(), fgetc()와 문장을 읽어들이기 위한 fgets()의 세 가지 문자 입력 함수가 있다.

▶ getc()와 fgetc() 함수
: 함수 getc()와 fgetc()는 동일하므로 원하는 함수를 사용하면 된다. 두 함수는 지정된 스트림에서 한 문자를 읽어들인다. getc()의 원형은 STDIO.H에 정의되어 있다.

int getc(FILE *fp);

인수 fp는 파일이 열릴 때 fopen()이 돌려주는 포인터이다. 함수는 입력된 문자를 돌려주거나 또는 에러가 발생하면 EOF를 돌려준다.

여러분은 키보드에서 문자를 입력하기 위해 앞의 프로그램에서 getc()를 사용했다. 이 함수는 C의 스트림의 융툥성을 확인할 수 있는 또다른 예이다. 키보드나 파일 입력을 위해 이 함수를 사용할 수 있기 때문이다. 만약 getc()와 fgetc()가 한 문자를 돌려준다면 원형에 왜 int형의 복귀값을 가지는 것일까? 파일을 읽을 때 파일의 마지막을 표시하는 문자를 읽을 필요가 있는데, 시스템에 따라서 이 문자가 char형이 아닌 int형이 될 수 있으므로 int형의 복귀값을 가지는 것이다. <리스트 16.10>에서 getc()를 사용하는 예를 볼 것이다.

▶ fgets() 함수
: 파일에서 문장을 읽어들이기 위해서는 fgets() 라이브러리 함수를 사용하자. 원형은 다음과 같다.

char *fgets(char *str, int n, FILE *fp);

인수 str은 입력 내용이 저장되는 버퍼에 대한 포인터이고, n은 입력되는 문자의 최대 개수이며, fp는 파일이 열릴 때 fopen()이 돌려주는 FILE형에 대한 포인터이다.

fgets() 함수를 호출하면 fp에서 문자를 읽어들이고 str이 지적하는 메모리 영역에 읽어들인 문자를 저장한다. 함수는 문장 진행 문자가 나타나거나 또는 n-1자를 읽어들일 때까지 계속해서 문자를 읽어들인다. n의 값을 str에 할당된 메모리의 바이트 수와 같은 값으로 설정하면 입력 내용이 할당된 영역을 벗어나서 메모리를 겹쳐쓰지 않도록 방지할 수 있다. n-1은 fgets()가 문자열의 마지막에 추가하는 널 종료 문자(\0)을 위한 공간은 제외한 값이다. 입력이 성공적으로 끝나면 fgets()는 str을 돌려준다. 또한, 다음과 같이 두 가지 경우에 NULL값을 돌려주고 에러가 발생한다.

·str에 어떤 문자를 할당하기 전에 읽기 에러가 발생하거나 EOF 문자가 나타나면 함수는 NULL을 돌려주고, str이 지적하는 메모리의 내용은 변경되지 않는다.

·str에 하나 이상의 문자를 할당한 후에 읽기 에러가 발생하거나 EOF 문자가 나타나면 함수는 NULL을 돌려주고, str이 지적하는 메모리에는 쓸모없는 데이터가 저장된다. 여기서 fgets() 함수가 반드시 한 줄의 문장을 읽어들이지는 않는다는 것을 알 수 있을 것이다. 즉, 함수는 문장 진행 문자가 나타날 때까지 계속해서 문자를 읽어들이지 않는다. 만약 문장 진행 문자가 나타나기 전에 n-1자를 읽어들였다면 fgets()는 동작을 중단한다. 프로그램은 다시 입력 동작을 수행할 때 마지막으로 입력 동작을 중단한 곳에서부터 시작한다. fgets()가 문장 진행문자에서 중단할 때까지 전체 문자열을 읽어들이기 위해서는 입력 버퍼의 크기는 물론이고 fgets()에 전달되는 n의 값을 충분한 크기로 설정하자.

▶ 문자 출력
: 문자 출력 함수에는 putc()와 fputs()가 있다.

▶ putc() 함수
: 라이브러리 함수 putc()는 지정된 스트림에 한 문자를 출력한다. 함수의 원형은 STDIO.H에 정의되어 있으며, 다음과 같다.

int putc(int ch, FILE *fp);

인수 ch는 출력되는 문자이다. 다른 문자 함수에서와 마찬가지로 이 함수에서도 int형이 사용되고 있지만 실제로는 하위 바이트만 사용된다. 인수 fp는 파일에 대한 포인터이다. 즉, 파일을 열 때 fopen()이 돌려주는 포인터이다. 함수 putc()의 동작이 성공적이었다면 출력된 문자를 돌려주고 에러가 발생한 경우에는 EOF를 돌려준다. 기호 상수 EOf는 STDIO.H에 정의되어 있으며 -1의 값을 가진다. 실제로 '어떤' 문자도 -1이라는 값을 가지고 있지 않으므로 텍스트 모드의 파일에서는 에러를 표현하는 문자로 EOF를 사용할 수 있다.

▶ fputs() 함수
: 지정된 스트림에 문장을 출력하기 위해서는 라이브러리 함수 fputs()를 사용하지. 이 함수는 14번째 강의에서 다루어진 puts()와 같은 방법으로 사용된다. 유일한 차이는 fputs()를 사용할 경우 출력 스트림을 지정할 수 있다는 것이다. 또한, fputs()는 문자열의 마지막에 문장 진행 문자를 추가하지 않는다. 원한다면 문장 진행 문자를 직접 포함시키도록 하자. STDIO.H에 정의되어 있는 함수의 원형은 다음과 같다.

char fputs(char *str, FILE *fp);

인수 str은 스트림으로 출력되고 널 문자로 종료되는 문자열에 대한 포인터이고, fp는 파일을 열 때 fopen()이 돌려주는 FILE형에 대한 포인터이다. str이 지적하는 문자열은 마지막의 \0을 제거한 상태로 파일에 기록된다. 함수 fputs()의 동작이 성공적이라면 음수가 아닌 값을 돌려주고, 에러가 발생하면 EOF를 돌려준다.

5.3 직접 파일 입력과 출력
: 현재 사용 중인 C 프로그램이나 또는 다른 어떤 C 프로그램에서 나중에 사용하기 위한 데이터를 저장할 때에는 직접 파일 입출력을 가장 많이 사용한다. 직접 입출력은 이진 모드의 파일에서만 사용된다. 직접 출력을 수행할 때에는 데이터가 블록 단위로 메모리에서 디스크 파일로 저장된다. 직접 입력의 경우에는 이와 반대로 블록 단위의 데이터를 디스크 파일에서 메모리로 읽어들인다. 예를 들어, 직접 출력 함수를 한 번 호출하여 double형의 배열 전체를 디스크에 저장할 수 있고, 직접 입력 함수를 한 번 호출하여 다시 디스크에서 메모리로 전체 배열을 읽어들일 수 있다. 직접 입출력 함수는 fread()와 fwrite()이다.

▶ fwrite() 함수
: 라이브러리 함수 fwrite()는 메모리의 데이터를 블록 단위로 이진 모드의 파일에 기록한다. STDIO.H에 정의되어 있는 함수의 원형은 다음과 같다.

int fwrite(void *buf, int size, int count, FILE *fp);

인수 buf는 파일에 기록할 데이터가 저장되어 있는 메모리 영역에 대한 포인터이다. 포인터의 형은 void이므로 어떤 데이터형에 대한 포인터가 될 수 있다. 인수 size는 개별적인 데이터 항목의 크기를 바이트 단위로 지정하는 것이고, count는 기록할 항목의 수를 지정한다. 예를 들어, 100개의 요소를 가지는 정수형 배열을 저장하기 원한다면 각각의 int형은 2바이트를 차지하므로 size는 2가 될 것이고, 배열은 100개의 요소를 가지므로 count는 100이 될 것이다. size 인수를 계산하기 위해서 sizeof() 연산자를 사용할 수 있다. 인수 fp는 물론 파일을 열 때 fopen()이 돌려주는 FILE형에 대한 포인터이다. gwrite() 함수의 동작이 성공적이면 기록한 항목의 개수를 돌려준다. 만약 함수가 돌려주는 값이 count보다 작다면 어던 에러가 발생했다는 것을 알 수 있다. 에러를 확인하기 위해서는 대개 다음과 같이 fwrite()를 사용한다.

if((fwrite(buf, size, count, fp) != count)
fprintf(stderr, "Error writing to file.");

fwrite()를 사용하는 몇 가지 예를 살펴보자. 하나의 double형 변수 X를 파일에 기록하기 위해서 다음과 같이 한다.

fwrite(&x, sizeof(double), 1, fp);

50개의 address형 구조체를 가지는 배열 data[]를 파일에 기록하기 위해서는 두 가지 방법을 사용할 수 있다.

fwrite(data, sizeof(address), 50, fp);
fwrite(data, sizeof(data), 1, fp);

첫 번째 방법에서는 address형의 크기를 가지는 50개의 요소가 배열 포함되어 있는 것으로 계산하여 배열을 저장한다. 두 번째 방법에서는 배열을 하나의 '요소'로 취급한다. 두 가지 방법의 실행 결과는 동일하다. 다음 단원에서는 fread()를 설명하고 나서 fread()와 fwrite()를 사용하는 프로그램을 분석할 것이다.

▶ fread() 함수
: fread() 라이브러리 함수는 이진 모드의 파일에서 블록 단위의 데이터를 메모리로 읽어들인 다. STDIO.H에 정의되어 있는 함수의 원형은 다음과 같다.

int fread(void *buf, int size, int count, FILE *fp);

인수 buf는 파일에서 읽어들인 데이터를 저장할 메모리 영역에 대한 포인터이다. fwrite()에서와 마찬가지로 포인터형은 void이다. 인수 size는 읽어들일 개별적인 데이터 항목의 크기를 바이트 단위로 지정하는 것이고, count는 읽어들일 항목의 개수를 지정한다. 이런 인수들이 fwrite()에서 사용되는 인수들과 같다는 것에 주목하지 바란다. 여기에서도 size 인수를 계산하기 위해서 sizeof() 연산자를 자주 사용한다. 인수 fp는 항상 그렇듯이 파일을 열 때 fopen()이 돌려주는 FILE형에 대한 포인터이다. fread() 함수는 읽어들인 항목의 개수를 돌려준다. 그러나 파일의 마지막에 도달하거나 또는 에러가 발생한다면 이 값은 count보다 작을 수 있다. <리스트 16.4>에 있는 프로그램은 fwrite()와 fread()의 사용 예를 보여준다.

<리스트 16.4> 직접 파일 입출력을 위한 fwrite()와 fread()의 사용

/* fwrite()와 fread()를 사용한 직접 파일 입출력 */

#include

#include

 

#define SIZE 20

 

main(0

{

int count, array1[SIZE], array2[SIZE];

FILE *fp;

 

/* array1[] 초기화 */

 

for(count = 0; count < SIZE; count++)

array1[SIZE] = 2 * count;

 

/* 이진 모드 파일 열기 */

 

if((fp = fopen("direct.txt", "wb")) == NULL)

{

fprintf(stderr, "Error opening file.");

exit(1);

}

/* array1[]을 파일에 저장 */

 

if(fwrite(array1, sizeof(int), SIZE, fp) != SIZE)

{

fprintf(stderr, "Error writing to file.");

exit(1);

}

 

fclose(fp);

 

/* 같은 파일을 이진 모드 읽기 상태로 연다. */

 

if((fp = fopen("direct.txt", "rb")) == NULL)

{

fprintf(stderr, "Error epening file.");

exit(1);

}

 

/* 데이터를 array2[]로 읽어들인다. */

 

if(fread(array2, sizeof(int), SIZE, fp) != SIZE)

{

fprintf(stderr, "Error reading file.");

exit(1);

}

 

fclose(fp);

 

/* 두 배열이 같다는 것을 보여주기 위해 출력 */

 

for(count = 0; count < SIZE; count++)

printf("%d\t%d\n", array1[count], array2[count]);

return(0);

}

 

6. 파일 버퍼링 : 파일 닫기와 플러시
: 파일의 사용을 마칠 때에는 fclose() 함수로 파일을 닫아야 한다. 이 장에 나타난 프로그램 에서는 이미 fclose()를 사용했었다. 함수의 원형은 다음과 같다.

int fclose(FILE *fp);

인수 fp는 스트림에 관련된 FILE형 포인터이다. fclose()의 동작이 성공적으로 수행되면 함수는 0을 돌려주고 에러가 발생하면 -1을 돌려준다. 파일을 닫을 때에는 파일 버퍼가 플러시(flush)된다. 즉, 파일에 기록된다. 또한, fcloseall() 함수를 사용하여 표준으로 정의되어 있는 stdin, stdout, stdprn, stderr, stdaux를 제외하고 열려 있는 모드 스트림을 닫을 수도 있다. 이 함수의 원형은 다음과 같다.

int fcloseall(void);

이 함수는 모든 스트림의 버퍼를 플러시하고 나서 닫힌 스트림의 개수를 돌려준다.

main()의 마지막에 도달하거나 또는 exit() 함수를 실행하여 프로그램을 마칠 때에는 모든 스트림이 자동으로 플러시되고 닫힌다. 그러나 프로그래머가 스트림을 직접 닫는 것이 좋다. 특히, 디스크 파일과 관련된 스트림에서는 파일의 사용이 끝나는 즉시 닫아야 한다. 이것은 스트림 버퍼와 관련된 문제이다.

디스크 파일에 관련된 스트림을 생성할 때에는 자동으로 버퍼가 생성되고 스트림과 연결된다 버퍼(buffer)는 파일에 기록되거나 파일에서 읽어들여지는 데이터를 임시로 저장하기 위해서 사용되는 메모리 영역이다. 버퍼는 디스크 드라이브가 블록 단위를 기본으로 하는 장치이므 로 필요하다. 이런 장치들은 데이터를 블록 단위로 읽어들이거나 기록할 때 효과적이므로 블록 단위 장치라고 한다. 이상적인 블록의 크기는 사용중인 하드웨어에 따라 다르다. 대개 수백에서 수천 바이트 사이의 크기이다. 그러나 정확한 블록의 크기에 대해서는 신경 쓸 필요가 없다.

파일 스트림과 관련된 버퍼는 문자를 기본으로 하는 스트림과 블록을 기본으로 하는 디스크 장치 간의 중간 매체 역할을 한다. 프로그램에서 데이터를 스트림으로 기록할 때 데이터는 버퍼가 가득찰 대가지 저장되었다가 블록 단위로 디스크에 기록된다. 이것은 디스크 파일에서 데이터를 읽어들이는 경우에도 적용된다. 버퍼의 생성과 사용은 모두 운영체제에 의해서 자동으로 처리된다. 프로그래머가 신경 쓸 일은 없다. C는 버퍼를 관리하기 위한 몇 가지 함수를 제공하지만 자세한 내용은 생략한다. 그래서 실제로는 이런 버퍼의 동작이 이론과 다르게 이루어진다. 프로그램이 실행되는 동안 디스크에 '저장할' 데이터가 디스크에 기록되는 것이 아니라 버퍼 내에 존재한다는 뜻이다. 만약 전원이 차단되거나 다른 어떤 문제가 발생하여 프로그램이 '중단'되면 버퍼 내의 데이터는 사라질 것이고, 디스크 파일에 저장된 것을 정확히 파악할 수 없게 된다.

파일을 닫지 않고 스트림에 관련된 버퍼를 플러시(flush)하기 위해서는 fflush()나 flushall() 라이브러리 함수를 사용한다. 파일을 계속해서 사용하는 동안 버퍼를 디스크에 기록하기 원한다면 fflush()를 사용하자. 열려 있는 모든 스트림의 버퍼를 플러시 하기 위해서는 flushall()을 사용하자. 두 함수의 원형은 다음과 같다.

int fflush(FILE *fp);
int flushall(void);

인수 fp는 파일을 열 때 fopen()이 돌려주는 FILE 포인터이다. 만약 쓰기 가능한 상태로 파일을 열었다면 fflush()는 버퍼를 디스크에 저장한다. 그러나 읽기 가능한 상태로 파일을 열었다면 버퍼는 제거된다. 함수 fflush()의 동작이 성공적으로 수행되면 0을 돌려주고, 에러가 발생하면 EOF를 돌려준다. 함수 flushall()은 열린 스트림의 개수를 돌려준다.

7. 파일의 순차적인 사용과 무작위 사용
: 열려 있는 모든 파일은 관련된 파일 위치 표시(file position indicator)를 가지고 있다. 위치 표시는 파일에서 읽기와 쓰기 동작이 수행되는 위치를 가리킨다. 위치는 항상 파일의 시작을 기준으로 해서 바이트 단위로 표현된다. 새로운 파일을 열 때 위치 표시는 항상 파일의 시작 부분인 위치 0을 가리킨다. 새로운 파일의 길이는 0이므로 다른 곳을 지적할 수 없다. 이미 존재하는 파일을 열 때 파일이 추가 가능한 상태로 열리면 위치 표시는 파일의 마지막을 지적하고, 파일이 다른 어떤 모드로 열리면 파일의 시작 부분을 지적한다.

여기서의 범위를 벗어나는 내용이기는 하지만, 이 장의 앞 부분에서 설명한 파일 입출력 함수는 위치 표시를 사용한다. 쓰기와 읽기 동작을 현재의 위치 표시에서 수행하고 나서 위치 표시를 변경하는 것이다. 예를 들어, 읽기 가능한 상태로 파일을 열고 10바이트를 읽어 들이면 위치 0부터 9가지에 해당하는 10바이트를 파일의 처음부터 읽어들이는 것이다. 읽기 동작을 수행하고 나면 위치 표시는 위치 10을 지적하게 되고 다음 읽기 동작은 위치 10부터 시작된다. 그래서 파일의 모든 데이터를 순차적으로(sequentially) 읽어들이거나 또는 파일에 데이터를 순차적으로 기록하는 경우에는 스트림 입출력 함수가 자동으로 다루어 주므로 위치 표시에 대해서 신경 쓸 필요가 없다. 그러나 파일을 다른 방법으로 사용할 필요가 있을 때에는 위치 표시의 값을 결정하거나 변경하게 해주는 C 라이브러리 함수를 사용해야 한다. 위치 표시의 값을 제어하면 파일을 무작위(random) 상태로 사용할 수 있다. 여기서 '무작위'라는 것은 앞 부분의 모든 데이터를 읽어들이거나 또는 앞 부분에 기록하지 않고 파일에서 임의의 위치에 있는 데이터를 읽어들이거나 데이터를 기록할 수 있다는 것을 뜻한다.

7.1 ftell()과 rewind() 함수
: 위치 표시가 파일의 시작 부분을 지적하도록 설정하기 위해서 라이브러리 함수 rewind()를 사용하자. STDIO.H에 정의되어 있는 함수의 원형은 다음과 같다.

void rewind(FILE *fp);

인수 fp는 스트림과 관련된 FILE 포인터이다. rewind()를 호출한 후에 파일의 위치 표시는 파일의 시작 부분인 바이트 0을 지적하게 된다. 파일에서 약간의 데이터를 일어들인 후에 파일을 닫고나서 다시 열지 않고도 파일의 시작부터 읽어들이기 원한다면 rewind()를 사용함. 파일의 위치 표시 값을 설정하기 위해서는 ftell()을 사용하자. STDIO.H에 정의되어 있는 이 함수의 원형은 다음과 같다.

long ftell(FILE *fp);

인수 fp는 파일을 열 때 fopen()이 돌려주는 FILE 포인터이다. 함수 ftell()은 파일의 시작부터 현재 파일의 위치까지를 바이트 단위로 나타내는 long형 값을 돌려준다. 첫 번째 바이트는 위치 0이다. 만약 에러가 발생하면 ftell()은 -1의 long형인 -1L을 돌려준다. rewind()와 ftell()의 동작을 이해하기 위해서 <리스트 16.5>에 있는 프로그램을 살펴보자.

<리스트 16.5> ftell()과 rewind()의 사용

/* ftell()과 rewind()의 사용 예 */

#include

#include

 

#define VUFLEN 6

 

char msg[] = "abcdefghijklmnopqrstuvwxyz";

 

main()

{

FILE *fp;

char buf[BUFLEN];

 

if((fp = fopen("TEXT.TXT", "w")) == NULL)

{

fprintf(stderr, "Error opening file.");

exit(1);

}

 

if(fputs(msg, fp) == EOF)

{

fprintf(stderr, "Error writing to file.");

exit(1);

}

 

fclose(fp);

 

/* 읽기 상태로 파일을 연다 */

 

if((fp = fopen("TEXT.TXT", "r")) == NULL)

{

fprintf(stderr, "Error opening file.");

exit(1);

}

printf("\nImmediately after opening, position = %ld", ftell(fp));

 

/* 5 문자를 읽어들인다. */

 

fgets(buf, BUFLEN, fp);

printf("\nAfter reading in %s, position = %ld", buf, ftell(fp));

 

/* 다음 5문자를 읽어들인다. */

 

fgets(buf, BUFLEN, fp);

printf("\n\nThe next 5 characters are %s, and position now = %ld",

buf, ftell(fp));

 

/* 스트림을 시작 부분으로 설정한다. */

 

rewind(fp);

 

printf("\n\nAfter rewinding, the position is back at %ld",

ftell(fp));

 

/* 5 문자를 읽어들인다. */

 

fgets(buf, BUFLEN, fp);

printf("\nand reading starts at the beginning again: %s", buf);

fclose(fp);

return(0);

}

 

7.2 fseek() 함수
: 스트림의 위치 표시(position indicator)를 더욱 정확하게 제어하기 위해서는 라이브러리 함수 fseek()를 사용할 수 있다. fseek()를 사용하면 위치 표시가 파일 내의 임의의 위치를 지적하도록 설정할 수 있다. STDIO.H에 정의되어 있는 함수 원형은 다음과 같다.

int fseek(FILE *fp, long offset, int origin);

인수 fp는 파일과 관련된 FILE 포인터이다. 위치 표시가 이동되는 거리는 offset에 바이트 단위로 지정된다. 인수 origin은 이동이 시작되는 위치를 지정한다. origin에 사용할 수 있는 기호 상수는 IO.H에 정의되어 있는데, <표 16.2>에 나타나 있듯이 세 가지 값이 있다.

<표 16.2> fseek()에서 사용되는 origin의 값

상수이름 의미
SEEK_SET 0 위치 표시를 파일의 시작부터 offset 바이트 뒤로 이동
SEEK_CUR 1 위치 표시를 현재 위치에서 offset 바이트 뒤로 이동
SEEK_END 2 위치 표시를 파일의 마지막부터 offset 바이트 앞으로 이동

함수 fseek()는 위치 표시를 성공적으로 이동시키면 0을 돌려주고, 에러가 발생하면 0이 아닌 값을 돌려준다. <리스트 16.6>에 있는 프로글매은 파일을 무작위 상태로 사용하기 위해서 fseek()를 사용하고 있다.

<리스트 16.6> fseek()를 사용하여 무작위 상태로 파일을 사용하는 예

/* fseek()를 사용한 무작위 사용 */

#include

#include

#include

 

#define MAX 50

 

main()

{

FILE *fp;

int data, count, array[MAX];

long offset;

 

/* 배열의 초기화 */

 

for(count = 0; count < MAX; count++)

array[count] = count * 10;

 

/* 쓰기 상태로 이진 파일 열기 */

 

if((fp = fopen("RANDOM.DAT", "wb")) == NULL)

{

fprintf(stderr, "\nError opening file.");

exit(1);

}

 

/* 배열을 파일에 기록하고 나서 닫는다. */

 

if((fwrite(array, sizeof(int), MAX, fp)) != MAX)

{

fprintf(stderr, "\nError writing data to file.");

exit(1);

}

 

fclose(fp);

 

/* 읽기 상태로 파일 열기 */

 

if((fp = fopen("RANDOM.DAT", "rb")) == NULL)

{

fprintf(stderr, "\nError opening file.");

exit(1);

}

 

/* 읽어들일 요소를 요구한다. */

/* 요소를 입력하면 출력해주며, -1을 입력하면 마친다. */

 

while(1)

{

printf("\nError element to read, 0-%d, -1 to quit: ", MAX - 1);

scanf("%ld", &offset);

 

if(offset < 0)

break;

else if (offset > MAX-1)

continue;

 

/* 위치 표시를 지정된 요소로 이동시킨다. */

 

if((fseek(fp, (offset*sizeof(int)), SEEK_SET)) != 0)

{

fprintf(stderr, "\nError using fseek().");

exit(1);

}

 

/* 하나의 정수를 읽어들인다. */

 

fread(&data, sizeof(int), 1, fp);

 

printf("\nElement %ld has value %d.", offset, data);

}

 

fclose(fp);

return(0);

}

 

=> 14번째 줄부터 35번째 줄까지는 <리스트 16.5>와 비슷하다. 16번째 줄과 17번째 줄은 50개의 int형 값을 가지는 data라는 배열을 초기화한다. 각 요소에 저장되는 값은 색인을 10배한 것이다. 그리고 나서 배열은 RANDOM.DAT라는 이진 파일에 저장된다. 21번째 줄에서 'wb'를 사용하여 파일을 열었으므로 이진 모드라는 것을 알 수 있다. 39번째 줄에서는 무한 루프인 while문을 실행하기 전에 파일을 읽기 가능한 이진 모드로 다시 열고 있다. while문에서는 값을 읽어들이기 원하는 배열 요소의 번호를 입력하도록 요구한다. 53번째 줄부터 56번째 줄까지는 입력된 요소가 파일 내에 포함되는 것인지 확인해본다는 것에 주의하자. 그렇다면 파일의 마지막을 벗어난 요소를 읽어들인다는 뜻일까? 실제로 그렇다. 배열의 마지막을 지난 곳에 값이 저장될 수 있는 것과 마찬가지로, C는 파일의 마지막을 지난 곳에서 값을 읽어들이게 해준다. 만약 파일의 마지막을 지난 곳이나 시작 이전에서 값을 읽어들인다면 결과는 예상할 수 없을 것이다. 이 프로그램의 53번째 줄부터 56번째 줄까지에 나타나 있는 것처럼 수행하는 동작의 결과를 항상 확인해 보는 것이 좋다. 읽어들이기 원하는 요소의 번호를 확인하고 나면 60번째 줄에서는 fseek()를 사용하여 적절한 위치로 이동한다. SEEK_SET이 사용되므로 이동은 파일의 시작을 기준으로 수행된다. 파일 내에서 이동되는 거리는 offset의 값이 아니라 offset의 값에 요소의 크기를 곱한 만큼이라는 것을 기억하자. 그리고 나서 68번째 줄에서는 값을 읽고, 70번째 줄에서는 값을 출력한다.

8. 파일의 마지막을 찾는 방법
: 파일의 길이를 정확하게 알고 있는 경우에는 파일의 마지막을 찾을 필요가 없을 것이다. 예를 들어, 100개의 요소를 가지는 정수형 배열을 저장하기 위해서 fwrite()를 사용한다면 전체적인 파일은 200바이트의 길이(2바이트 정수라고 가정할 때)가 된다는 것을 알 수 있다. 그러나 파일의 정확한 길이를 모르는 상태에서 파일의 처음부터 마지막까지를 읽어들이기 원하는 경우에는 어떻게 할 것인가? 파일의 마지막을 찾는 두 가지 방법이 있다. 텍스트 모드의 파일에서 문자 단위로 값을 읽어들일 때에는 EOF 문자를 찾을 수 있다. 기호 상수 EOF는 STDIO.H에서 -1로 정의되어 있고 '실제' 문자에서 사용되지 않는 값이다. 그래서 문자 입력 함수가 텍스트 모드의 스트림에서 EOF를 읽어들일 때 파일의 마지막에 도달했다는 것을 알 수 있다. 예를 들어, 다음과 같은 문장을 작성할 수 있을 것이다.

while((c = fgetc(fp)) != EOF)

이진 모드의 스트림에서는 -1의 값을 가지는 데이터가 사용될 수도 있으므로 EOF가 파일의 마지막을 뜻하지는 않는다. -1의 값을 파일의 마지막을 뜻하지 않을 것이다. 대신에, 이진 모드에서는 라이브러리 함수 feof()를 사용할 수 있다. 이 함수는 이진 모드와 텍스트 모드의 파일에서 사용할 수 있다.

int feof(FILE *fp);

인수 fp는 파일을 열 때 fopen()이 돌려주는 FILE 포인터이다. 함수 feof()는 파일 fp의 마지막에 도달하지 않았다면 0을 돌려주고, 파일의 마짐가에 도달하면 0이 아닌 값을 돌려준다. feof() 함수를 사용했을 때 파일의 마지막에 도달했다는 것을 확인하면 rewind()를 수행하거나, fseek()를 사용하거나, 또는 파일을 닫고 다시 열 때가지 더 이상의 일기 동작이 허용되지 않는다. <리스트 16.7>에 있는 프로그램은 feof()의 사용 예를 보여준다. 프로그램이 파일의 이름을 입력하도록 요구할 때 텍스트 파일의 이름을 입력하자. 예를 들어, C 소스 파일이나 STDIO.H와 같은 헤더 파일의 이름을 입력할 수 있다. 파일이 현재 디렉토리에 있다는 것을 확인하거나 또는 파일명의 일부로 경로를 입력하기 바란다. 프로그램은 feof()가 파일의 마지막을 발견할 때까지 파일을 한 번에 한 줄씩 읽어들이고 stdout으로 출력한다.

<리스트 16.7> 파일의 마지막을 찾기 위한 feof()의 사용 예

/* 파일의 마지막 찾기 */

#include

#include

 

#define BUFSIZE 100

 

main()

{

char buf[BUFSIZE];

char filename[60];

FILE *fp;

 

puts("Error name of text file to display: ");

gets(filename);

 

/* 읽기 상태로 파일 열기 */

if((fp = fopen(filename, "r")) == NULL)

{

fprintf(stderr, "Error opening file.");

exit(1);

}

 

/* 파일의 마지막에 도달하지 않았다면 한 줄을 읽고 출력한다. */

 

while(!feof(fp))

{

fgets(buf, BUFSIZE, fp);

printf("%s", buf);

}

 

fclose(fp);

return(0);

}

 

-> 입력 / 출력

Enter name of text file to display:
hello.c
#include
main()
{
printf("Hello, world.");
return(0);
}

9. 파일 처리 함수
: 파일 처리(file management)는 디스크에 존재하는 파일을 다루는 동작을 뜻한다. 즉, 파일에서 데이터를 읽어들이거나 파이에 저장하는 것이 아니라 파일 자체를 삭제하거나 파일의 이름을 변경하고 복사하는 것을 말한다. C 표준 라이브러리에서는 파일을 삭제하고, 이름을 변경하기 위한 함수가 제공되며, 자신만의 파일 복사 함수를 작성할 수 있다.

9.1 파일 삭제하기
: 파일을 삭제하기 위해서는 라이브러리 함수 remove()를 사용한다. 함수 원형은 다음과 같이 STDIO.H에 정의되어 있다.

int remove(const char *filename);

변수 *filename은 삭제되는 파일의 이름에 대한 포인터이다. 파일의 이름에 대해서는 이 장의 앞 부분을 참고하기 바란다. 지정된 파일이 존재한다면 DOS 프롬프트에서 DEL 명령이나 UNIX에서 rm 명령을 사용한 것과 마찬가지로 삭제되고, remove() 함수는 0을 돌려준다. 파일이 존재하지 않거나 또는 읽기 전용 상태로 되어 있거나, 사용자가 적절한 이용 권한을 가지지 않거나, 다른 어떤 에러가 발생하면 remove()는 -1을 돌려준다. <리스트 16.8>에 있는 간단한 프로그램은 remove()의 사용 예를 보여준다. 주의하자. 파일을 '삭제'하면 완전히 사라지는 것이다.

<리스트 16.8> 디스크 파일을 삭제하기 위한 remove()함수의 사용

/* remove() 함수의 사용 예 */

#include

 

main()

{

char filename[80];

 

printf("Enter the filename to delete: ");

gets(filename);

 

if(remove(filename) == 0)

printf("The file %s has been deleted.", filename);

else

fprintf(stderr, "Error deleting the file %s.", filename);

return(0);

}

 

9.2 파일의 이름 변경하기
: rename() 함수는 이미 존재하는 디스크 파일의 이름을 변경한다. 함수 원형은 다음과 같이 STDIO.H에 정의되어 있다.

int rename(const char *oldname, const char *newname);

oldname과 newname이 지적하는 파일의 이름은 이 장의 앞부분에서 설명한 규칙을 따른다. 한 가지 제한이 있다면 두 이름은 동일한 디스크 드라이브를 지정해야 한다는 것이다. 서로 다른 디스크 드라이브에 존재하는 파일의 이름을 '변경'할 수는 없다. 함수 rename()의 동작이 성공적이라면 0을 돌려주고, 에러가 발생하면 -1을 돌려준다. 에러는 다음과 같은 경우에 발생할 수 있다.

·oldname이라는 파일이 존재하지 않는다.
·newname이라는 이름의 파일이 이미 존재한다.
·서로 다른 디스크에서 이름을 변경하려고 한다.

<리스트 16.9>에 있는 프로그램은 rename()의 사용 예를 보여준다.

<리스트 16.9> 디스크 파일의 이름을 변경하기 위한 rename()의 사용

/* 파일명을 변경하기 위한 rename()의 사용 예 */

#include

 

main()

{

char oldname[80], newname[80];

 

printf("Enter current filename: ");

gets(oldname);

printf("Enter new name for file: ");

gets(newname);

 

if(rename(oldname, newname) == 0)

printf("%s has been renamed %s.", oldname, newname);

else

fprintf(stderr, "An error has occurred renaming %s.", oldname);

return(0);

}

 

9.3 파일 복사하기
: 프로그램에서는 가끔 파일을 복사할 필요가 있다. 파일을 복사한다는 것은 다른 이름이나 또는 동일한 이름이지만 다른 드라이브나 디렉토리에 동일한 내용의 복사본을 만드는 것을 뜻한다. DOS에서는 COPY 명령을 수행하여 파일을 복사할 수 있고, 다른 운영체제에서는 같은 기능의 명령이 있다. C 프로그램에서는 어떻게 파일을 복사할 수 있을까? C 에서는 파일을 복사는 라이브러리 함수를 제공하지 않으므로 직접 자신만의 함수를 작성할 필요가 있다. 이것은 어렵게 생각될 수 있지만, C의 입출력용 스트림을 사용한다면 아주 간단하게 수행할 수 있다. 다음은 함수에서 수행할 필요가 있는 단계들이다.

① 원본 파일을 읽기 가능한 상태의 이진 모드로 연다. 이진 모드를 사용하는 이유는 텍스트 파일뿐 아니라 모든 종류의 파일을 복사할 수 있도록 하기 위해서이다.

② 목적 파일을 쓰기 가능한 상태의 이진 모드로 연다.

③ 원본 파일에서 문자를 읽어들인다. 파일이 처음 열릴 때 위치 표시는 파일의 사직 부분을 지적하고 있으므로 파일 포인터를 다시 위치시킬 필요는 없다는 사실을 기억하자.

④ 함수 feof()가 원본 파일의 마지막에 도달했다는 것을 알려주면 복사가 완료된 것이므로 두 개의 파일을 닫고 함수를 호출한 프로그램으로 돌아갈 수 있다.

⑤ 파일의 마지막에 도달하지 않았다면 읽어들인 문자를 목적 파일에 기록하고 나서 단계 3으로 돌아간다.

<리스트 16.10>에 있는 프로그램은 앞에서 설명한 순서대로 원본과 목적 파일의 이름을 전달받아서 복사 동작을 수행하는 함수 copy_file()을 사용하고 있다. 어떤 파일을 열 때 에러가 발생하면 이 함수는 복사를 수행하지 않고 함수를 호출했던 프로그램에 -1을 돌려줌. 복사 과정이 완료되면 프로그램은 두 개의 파일을 닫고 0을 돌려준다.

<리스트 16.10> 파일을 복사하는 함수

/* 파일 복사 */

#include

 

int file_copy(char *oldname, char *newname);

 

main()

{

char source[80], destination[80];

 

/* 원본과 목적 파일의 이름을 구한다. */

 

printf("\nEnter source file: ");

gets(source);

printf("\nEnter destination file: ");

gets(destination);

 

if(file_copy(source, destination) == 0)

puts("Copy operation successful");

else

fprintf(stderr, "Error during copy operation");

return(0);

}

int file_copy(char *oldname, char *newname)

{

FILE *fold, *fnew;

int c;

 

/* 원본 파일을 읽기 상태의 이진 모드로 연다. */

 

if((fold = fopen(oldname, "rb")) == NULL)

return -1;

 

/* 목적 파일을 쓰기 상태의 이진 모드로 연다. */

 

if((fnew = fopen(newname, "wb")) == NULL)

{

fclose(fold);

return -1;

}

 

/* 원본에서 한 번에 한 바이트를 읽는다. */

/* 만약 파일의 마지막에 도달하지 않았다면 */

/* 목적 파일에 바이트를 기록한다. */

 

while(1)

{

c = fgetc(foid);

 

if(!feof(foid))

fputc(c, fnew);

else

break;

}

 

fclose(fnew);

fclose(fold);

 

return(0);

}

 

10. 임시 파일 사용하기
: 어떤 프로그램은 실행되는 동안 하나이상의 임시 파일을 사용한다. 임시 파일(temporary file)은 프로그램에 의해서 생성되고 프로그램이 실행되는 동안 다른 목적으로 사용되다가 프로그램이 종료되기 전에 삭제되는 파일이다. 임시 파일을 생성할 때에는 나중에 삭제할 것이므로 파일의 이름에 대해서 신경 쓰지 않는다. 그러나 이미 사용중이 아닌 파일의 이름을 사용해야 한다. C 표준 라이브러리에서는 어떤 존재하는 파일과 충돌하지 않는 파일의 이름을 생성하는 함수 tmpnam()이 제공된다. STDIO.H에 정의되어 있는 함수의 원형은 다음과 같다.

char *tmpnam(char *s);

인수 s는 파일 이름을 저장하기에 충분한 버퍼에 대한 포인터가 되어야 한다. 또한, 파일의 임시 이름을 tmpnam의 내장 버퍼에 저장하는 경우에는 널(NULL) 포인터를 전달할 수 있고, 함수는 버퍼에 대한 포인터를 돌려준다. <리스트 16.11>에 있는 프로그램은 파일의 임시 이름을 생성하기 위해서 tmpnam()을 사용하는 두 가지 방법을 보여준다.

<리스트 16.11> 파일의 임시 이름을 생성하기 위한 tmpnam()의 사용

/* 임시 파일명의 사용 예 */

#include

 

main()

{

char buffer[10], *c;

 

/* 정의된 버퍼에 임시 이름을 저장한다. */

 

tmpnam(buffer);

 

/* 다른 이름을 구한다. */

/* 이번에는 함수의 내장 버퍼에 저장한다. */

 

c = tmpnam(NULL);

 

/* 이름을 출력한다. */

 

printf("Temporary name 1: %s", buffer);

printf("\nTemporary name 2: %s", c);

}

 

 

'pc관련 > C언어' 카테고리의 다른 글

CreateWindow() - 10개의 인수  (0) 2019.04.14
API 윈도우 창띄우기  (0) 2019.04.14
C언어_링크리스트  (0) 2019.04.07
C언어_포인터,고급기능  (0) 2019.04.07
고급 프로그램제어문  (0) 2019.04.03
Posted by 둥이파파^^
pc관련/C언어2019. 4. 7. 22:57

5. 링크 리스트
: 링크드 리스트(linked list)는 C에서 쉽게 구현할 수 있는 유용한 데이터 저장방법이다. 왜 포인터에 대한 주제를 다루면서 링크드 리스트를 언급하는 것일까? 잠시 후에 배울 것처럼 포인터는 링크드 리스트의 핵심이다.

단순 링크드 리스트, 이중 링크드 리스트, 이진 트리를 포함하여 많은 종류의 링크드 리스트가 있다. 각각의 형태는 데이터를 저장할 필요가 있는 특별한 경우에 적절히 사용된다. 이런 링크드 리스트에서 공통적인 사항은 데이터 항목 간의 결합이 데이터 항목 자체 내에 포함되어 있는 정보에 의해 포인터의 형식으로 정의된다는 것이다. 이런 사실은 데이터 항목 간의 결합이 배열의 배치와 저장을 기반으로 하는 배열과 링크드 리스트를 분명히 구분하는 기준이다. 이 단원에서는 가장 간단한 형태의 링크드 리스트이고 간단히 링크드 리스트라고도 하는 단순 링크드 리스트에 대해서 설명한다.

5.1 링크드 리스트의 기초
: 링크드 리스트의 각 데이터 항목은 구조체에 포함되어 있다. 구조체는 데이터를 저장하는 데 필요한 요소들을 가지고 있다. 어떤 데이터 요소를 가지는지는 프로그램에 따라 달라진다. 링크드 리스트에는 한 가지 요소가 추가되는데 바로 포인터이다. 이 포인터는 링크드 리스트에서 연결 방법을 제공한다. 다음은 간단한 예제이다.

struct person{
char name[20];
struct person *next;
};

이 코드는 person이라는 구조체를 정의한다. 데이터만을 이야기한다면 person은 단지 20개 의 요소로 구성되는 문자형 배열을 가진다. 여러분은 일반적으로 이런 간단한 데이터에 대해 링크드 리스트를 사용하지 않겠지만, 여기서는 예로 들어 설명하는 것이므로 실용성에 대해서는 생각할 필요가 없다. person 구조체는 person형의 포인터를 가진다. 다시 말해서 같은 형의 다른 구조체에 대한 포인터이다. 이것은 person형의 각 구조체가 데이터의 모음을 가질 뿐 아니라 다른 person 구조체를 지적할 수 있음을 뜻한다. <그림 15.7>은 이런 구조체가 링크드 리스트에서 서로 연결되는 방법을 보여준다.

<그림 15.7> 링크드 리스트에서의 연결 상태

<그림 15.7>에서 각 person구조체가 다음 person 구조체를 지적한다는 것에 주목하기 바란다 마지막 person구조체는 어떤 것도 지적하지 않는다. 링크드 리스트에서 마지막 요소는 NULL의 값으로 할당되는 포인터 요소에 의해 정의된다.

* 참고
: 링크드 리스트에서 하나의 요소가 되는 각 구조체를 링크드 리스트의 링크(link), 노드(node) , 요소(element)라고 한다.

여러분은 링크드 리스트에서 마지막 링크가 어떻게 구분되는지 배웠다. 첫번째 링크는 어떻게 구분될까? 첫 번째 링크는 헤드 포인터(head pointer)라는 특별한 포인터(구조체가 아닌 포인터)에 의해 구분된다. 헤드 포인터는 항상 링크드 리스트에서 첫번째 링크를 지적한다. 첫 번째 링크는 두 변째 링크에 대한 포인터를 가지고, 두 번째 링크는 세 번째에 대한 포인터를 가지고, 이런 연결 상태는 포인터가 NULL인 마지막 링크를 만날 때까지 계속된다. 만약 전체 링크드 리스트가 비어 있다면, 즉 어떤 링크도 없다면 헤드 포인터가 NULL로 설정된다. <그림 15.8>은 링크드 리스트가 시작되기 전과 링크드 리스트에 첫 번째 링크가 추가된 후의 헤드 포인터를 보여준다.

<그림 15.8> 링크드 리스트의 헤드 포인터

5.2 링크드 리스트 다루기
: 링크드 리스트를 사용할 때에는 각 링크를 추가, 삭제, 변경할 수 있다. 링크를 변경하는 것은 크게 어렵지 않다. 그러나 링크를 추가하고 삭제하는 것은 약간 복잡하다. 앞에서도 설명했듯이 링크드 리스트에서 링크들은 포인터로 연결된다. 링크를 추가하고 삭제하는 것은 대부분 이런 포인터를 다루는 일이다. 각 링크는 링크드 리스트의 시작, 중간, 마지막에 추가될 수 있다. 어디에 추가하느냐에 따라 포인터를 변경하는 방법이 달라진다. 이 장에서는 간단한 링크드 리스트 예제와 함께 더욱 복잡한 프로그램을 살펴볼 것이다. 그러나 복잡한 프로그램을 설명하기 전에 링크드 리스트를 사용할 때 필요한 동작을 하나씩 살펴보도록 하자. 이 단원에서는 앞에서 사용했던 person 구조체를 계속해서 사용할 것이다.

▶ 준비 작업
: 링크드 리스트를 사용하려면 링크드 리스트에서 사용할 데이터 구조체를 정의할 필요가 있고 헤드 포인터를 선언할 필요가 있다. 링크드 리스트는 비어있는 상태로 시작하므로 헤드 포인터는 NULL로 초기화되어야 한다. 또한, 링크를 추가하는 데 사용할 링크드 리스트의 구조체형에 대한 포인터를 추가로 선언해야 한다. 잠시 후에 설명할 것처럼 하나 이상의 포인터가 필요하다. 다음은 예제이다.

struct person{
char name[20];
struct person *next;
};
struct person *new;
struct person *head;
head = NULL;

▶ 링크드 리스트의 시작 부분에 링크 추가하기
: 만약 헤드 포인터가 NULL이면 링크드 리스트는 비어 있는 것이고 새로운 링크는 유일한 멤버가 될 것이다. 헤드 포인터가 NULL이 아니라면 링크드 리스트는 이미 하나 이상의 링크를 가지고 있는 것이다. 어떤 경우든지 링크드 리스트의 시작 부분에 새로운 링크를 추가하는 방법은 똑같다.

① malloc()을 사용하여 메모리 공간을 할당하며 구조체형 변수를 생성한다.

② 새로운 링크의 next 포인터를 헤드 포인터의 현재 값으로 설정한다. 이 값은 링크드 리스트가 비어 있다면 NULL이 될 것이고, 그렇지 않다면 현재 첫 번째 링크의 주소가 될 것이다.

③ 헤드 포인터가 새로운 요소를 지적하게 한다.

다음은 이런 작업을 수행하는 코드이다.

new = (person*)malloc(sizeof(struct person));
new -> next = head;
head = new;

malloc()은 새로운 링크를 위한 메모리를 할당하는 데 사용된다는 것에 주목하기 바란다. 각 새로운 링크를 추가할 때 단지 해당 링크에 필요한 메모리만이 할당된다. calloc() 함수를 사용할 수도 있을 것이다. 두 함수의 차이점에 주의할 필요가 있다. 주요 차이점은 calloc()이 새로운 링크를 초기화한다는 것이다. malloc()은 새로운 링크를 초기화하지 않는다.

▶ 링크드 리스트의 마지막에 링크 추가하기
: 링크드 리스트의 마지막에 추가하기 위해서는 헤드 포인터에서 시작하고 마지막 링크를 찾을 때까지 링크드 리스트를 통해 차례대로 진행할 필요가 있다. 마지막 링크를 발견하면 다음과 같은 단계를 따른다.

① malloc()을 사용하여 메모리 공간을 할당하며 구조체형 변수를 생성한다.

② 마지막 링크의 next 포인터가 새로운 링크를 지적하게 설정한다. 새로운 링크의 주소는 malloc()에 의해 복귀된다.

③ 새로운 링크가 링크드 리스트에서 마지막 항목이라는 것을 표시하기 위해 새로운 링크의 next 포인터를 NULL로 설정한다.

다음은 코드이다.

person *current;

current = head;
while(current -> next != NULL)
current = current -> next;
new = (person*)malloc(sizeof(struct person));
current -> next = new;
new -> next = NULL;

▶ 링크드 리스트의 중간에 링크 추가하기
: 힝크드 리스트를 사용하다 보면 대개는 링크드 리스트의 중간에 어디든지 링크를 추가하게 될 것이다. 정확히 새로운 링크가 위치되는 곳은 링크드 리스트를 어떻게 사용하느냐에 따라 다르다. 예를 들어, 하나 이상의 데이터 요소에 따라 링크드 리스트를 정렬한다면 링크를 추가하고 나서 정렬해야 하므로 새로운 링크의 위치가 달라진다. 다음과 같이 진행하기 바란다.

① 리스트에서 새로운 링크가 그 다음에 위치될 기존의 링크를 찾는다. 이것을 표식 요소(marker element)라고 하자.

② malloc()을 사용하여 메모리 공간을 할당하며 구조체형 변수를 생성한다.

③ 표식 요소의 next 포인터가 새로운 링크를 지적하게 한다. 새로운 링크의 주소는 malloc()에 의해 반환된다.

④ 새로운 링크의 next 포인터를 표식 요소가 지적하던 기존의 링크를 지적하게 설정한다. 다음은 예제 코드이다.

person *marker;
/* 링크드 리스트에서 원하는 위치를 지적하도록 표식을 설정하는 코드 */

new = (LINK)malloc(sizeof(PERSON));
new -> next = marker -> next;
marker -> next = new;

▶ 링크드 리스트에서 링크 삭제하기
: 링크드 리스트에서 링크를 삭제하는 것은 포인터를 다루면 될 정도로 간단한 문제이다. 정확한 과정은 링크드 리스트에서 링크의 위치에 따라 다르다.

·첫 링크를 삭제하기 위해서 헤드 포인터가 링크드 리스트에서 두 번째 링크를 지적하도록 설정한다.

·마지막 링크를 삭제하기 위해서 마지막 바로 앞의 링크의 next 포인터를 NULL로 설정한다

·다른 어떤 링크를 삭제하기 위해서 삭제되는 바로 앞 링크의 next 포인터를 삭제되는 바로 다음 링크를 지적하도록 설정한다.

다음은 링크드 리스트에서 첫 링크를 삭제하는 코드이다.

head = head -> next;

다음은 링크드 리스트에서 마지막 링크를 삭제하는 코드이다.

person *current1, *current2;
current1 = head;
current2 = current1 -> next;
while (current2 -> next != NULL)
{
current1 = current2;
current2 = current1 -> next;
}
current1 -> next = NULL;
if(head == current1)
head = null;

마지막으로, 다음 코드는 링크드 리스트에서 특정 링크를 삭제한다.

person *current1, *current2;
/* current1이 삭제되는 바로 앞 링크를 지적하도록 설정하는 코드 */
current2 = current1 -> next;
current1 -> next = current2 -> next;

어느 부분에서 링크를 삭제하든지 삭제된 링크는 여전히 메모리에 남아 있지만 링크드 리스트에서 지적하는 포인터가 없으므로 제거된다. 실제 프로그래밍에서는 "삭제된" 링크가 차지하던 메모리를 해제시켜 확보하기 원할 것이다. free() 함수를 이용하면 되는데, 자세한 내용은 나중에 "메모리 다루기"에서 설명할 것이다.

5.3 간단한 링크드 리스트 예제
: <리스트 15.12>는 링크드 리스트를 사용하는 기본적인 방법을 보여준다. 이 프로그램은 사용자 입력을 받아들이지 앟고 대부분의 기본적인 링크드 리스트 작업에 요구되는 코드를 보여주는 것 외에 특별한 동작을 수행하지 않으며 예제로만 사용되는 것이다.

이 프로그램은 다음과 같은 작업을 수행한다.

① 링크드 리스트를 위한 구조체와 포인터를 정의한다.

② 링크드 리스트에 첫 링크를 추가한다.

③ 링크드 리스트의 마지막에 링크를 추가한다.

④ 링크드 리스트의 중간에 링크를 추가한다.

⑤ 링크드 리스트의 내용을 화면에 출력한다.

<리스트 15.12> 링크드 리스트의 기초

/* 링크드 리스트의 기본적인 사용 방법을

보여주는 예제 */

 

#include

#include

#include

 

/* 링크드 리스트인 data 구조체 */

struct data {

char name[20];

struct data *next;

};

 

/* 구조체에 대한 typedef형을 정의하고 포인터를 사용해서 지적한다. */

 

typedef struct data PERSON;

typedef PERSON *LINK;

 

main()

{

/* head, new, current 요소 포인터 */

LINK head = NULL;

LINK new = NULL;

LINK current = NULL;

 

/* 첫 번째 요소를 추가한다. */

/* 이 예제에서는 링크드 리스트가 항상 비어 있지만

비어 있는 것으로 가정하지 않는다. */

 

new = (LINK)malloc(sizeof(PERSON));

new -> next = head;

head = new;

strcpy(new -> name, "Abigail");

 

/* 링크드 리스트의 마지막에 요소를 추가한다. */

/* 링크드 리스트에 최소한 하나의 요소가 있다고 가정한다. */

 

current = head;

while(current -> next != NULL)

{

current = current -> next;

}

 

new = (LINK)malloc(sizeof(PERSON));

current -> next = new;

new -> next = NULL;

strcpy(new -> name, "Catherine");

 

/* 링크드 리스트의 두 번째 위치에 새로운 요소 추가 */

new = (LINK)malloc(sizeof(PERSON));

new -> next = head -> next;

head -> next = new;

strcpy(new -> name, "Beatrice");

 

/* 모든 데이터 항목을 차례대로 출력 */

current = head;

while(current != NULL)

{

printf("\n%s", current -> name);

current = current -> next;

}

 

printf("\n");

return(0);

}

 

=> 아마도 최소한 코드의 일부분을 이해할 수 있을 것이다. 9번째 줄부터 12번째 줄까지는 링크드 리스트를 위한 데이터 구조를 선언한다. 16번째 줄과 17번째 줄은 데이터 구조체와 데이터 구조체에 대한 포인터를 위한 typedef문을 정의한다. 이런 typedef가 반드시 필요하지는 않지만 struct data 대신에 PERSON을 사용하고, struct data* 대신에 LINK를 사용하게 해주므로 코드를 단순화할 수 있을 것이다.

22번째 줄부터 24번째 줄까지는 헤드 포인터를 선언하고 링크드 리스트를 다룰 때 사용할 다른 한 쌍의 포인터를 선언한다. 모든 포인터는 NULL로 초기화된다.

30번째 줄부터 33번째 줄까지는 링크드 리스트의 시작 부분에 새로운 링크를 추가한다. 30번째 줄은 새로운 데이터 구조체를 할당한다. malloc()의 결과가 성공적이라고 가정한다는 것에 주목하기 바란다. 실제 프로그래밍에서는 이렇게 가정해서는 안된다.

31번째 줄은 이 새로운 구조체의 next 포인터가 헤드 포인터를 지적하도록 설정한다. 왜 단순히 이 포인터에 NULL을 설정하지 않을까? 링크드 리스트가 현재 비어 있기 때문이다. 이 코드는 링크드 리스트에 이미 다른 링크가 있더라도 그대로 사용할 수 있을 것이다. 새로운 첫 링크는 여러분이 원하는 대로 이전의 첫 번째 링크를 지적하게 될 것이다.

32번째 줄은 헤드 포인터가 새로운 링크를 지적하게 하고, 33번째 줄은 링크에 임의의 데이터를 저장한다.

링크드 리스트의 마지막에 새로운 링크를 추가하는 것은 약간 더 복잡하다. 예제에서는 링크드 리스트에 하나의 링크만 존재하지만 실제로 프로그램을 작성할 때에는 이런 사실을 알 수 없으므로 전체 링크드 리스트에서 마지막 링크를 찾을 때까지 반복해야 한다. next 포인터가 NULL을 지적하는 링크가 마지막 링크이다. 이런 과정은 38번째 줄부터 42번째 줄까지 수행된다. 일단 마지막 링크를 찾았다면 새로운 데이터 구조를 할당하고, 이전의 마지막 링크가 새로운 구조체를 지적하게 하고, 새로운 링크의 next 포인터를 NULL로 설정하면 된다. 이것은 44번째 줄부터 47번째 줄까지의 동작이다.

다음 작업은 링크드 리스트의 중간에 링크를 추가하는 일이다. 이 예제에서는 두 번째 위치에 링크를 추가하면 된다. 50번째 줄에서 새로운 데이터 구조체를 할당하고 나서 새로운 링크의 next 포인터가 두 번째로 사용되는 링크를 지적하도록 설정한다. 그리고 51번째 줄에서 두 번째 링크를 세 번째 링크로 만들며, 52번째 줄에서 첫 링크의 next 포인터가 새로운 링크를 지적하게 한다.

마지막으로, 프로그램은 링크드 리스트의 모든 내용을 출력한다. 헤드 포인터가 지적하는 링크에서부터 NULL 포인터로 표시되는 마지막 링크를 찾을 때까지 전체 링크드 리스트를 통해서 차례대로 진행하면 된다. 56번째 줄부터 61번째 줄까지 이런 작업을 수행하고 있다.

5.4 링크드 리스트 구현하기
: 이제 링크드 리스트에 링크를 추가하는 방법을 보았으므로 실제로 사용하는 방법을 살펴볼 필요가 있다. <리스트 15.13>은 5개의 항목을 가지는 링크드 리스트를 사용하는 다소 긴 프로그램이다. 입력된 문자들은 링크드 리스트를 사용해서 메모리에 저장된다. 이런 문자들은 단지 이름, 주소, 다른 어떤 데이터를 표현할 정도로 간단하다. 예제를 가능한 한 쉽게 만들기 위해 한 문자씩 저장하도록 하겠다.

이 링크드 리스트 프로그램을 복잡하게 만드는 원인은 문자를 입력하고 나서 링크를 정렬하기 때문이다. 물론 이 기능이 프로그램을 상당히 가치있게 만드는 특징이기도 하다. 각 링크들은 값에 다라 시작, 중간, 마지막에 추가되고 링크드 리스트 전체는 항상 정렬된다. 만약 간단히 링크를 마지막에 추가하는 프로그램을 작성했다면 전체적인 구조는 훨씬 더 간단할 것이다. 그러나 프로그램은 그리 유용하지 않을 것이다.

<리스트 15.13> 문자들의 링크드 리스트 구현하기

/*====================================================*

* 프로그램 : list1513.c *

* 도서명 : C 언어 21일 완성 *

* 목적 : 링크드 리스트 구현하기 *

*====================================================*/

#include

#include

 

#define NULL

#define NULL 0

#endif

 

/* 링크드 리스트로 사용되는 구조체 */

struct list

{

int ch; /* char형을 저장할 int형 선언 */

struct list *next_rec;

};

 

/* 구조체와 포인터에 대한 typedef형 */

typedef struct list LIST;

typedef LIST *LISTPTR;

 

/* 함수 원형 */

LISTPTR add_to_list(int, LISTPTR);

void show_list(LISTPTR);

void free_memory_list(LISTPTR);

 

int main(void)

{

LISTPTR first = NULL; /* 헤드 포인터 */

int i = 0;

int ch;

char traash[256]; /* stdin 버퍼를 정리한 */

 

while(i++ < 5) /* 주어진 5항목을 근거로 링크드 리스트를 구성 */

{

ch = 0;

printf("\nEnter character %d, ", i);

 

do

{

printf("\nMust be a to z: ");

ch = getc(stdin); /* 버퍼에서 다음 문자를 구함 */

gets(trash); /* 버퍼에서 trash를 제거 */

} while((ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z'));

 

first = add_to_list(ch, first);

}

 

show_list(first); /* 전체 링크드 리스트를 출력 */

free_memory_list(first); /* 모든 메모리를 해제 */

return(0);

}

 

/*====================================================*

* 함수 : add_to_list()

* 목적 : 링크드 리스트에 새로운 링크를 추가

* 입력값 : int ch = 저장할 문자

LISTPTR first = 원래 헤드 포인터의 주소

* 복귀값 : 헤드 포인터의 주소(first)

*====================================================*/

 

LISTPTR add_to_list(int ch, LISTPTR first)

{

LISTPTR new_rec = NULL; /* 새로운 링크의 주소를 저장 */

LISTPTR tmp_rec = NULL; /* 임시 포인터를 저장 */

LISTPTR prev_rec = NULL;

 

/* 메모리 할당 */

new_rec = (LISTPTR)malloc(sizeof(LIST));

if(!new_rec) /* 메모리 할당 불가 */

{

printf("\nunable to allocate memory!\n");

exit(1);

}

 

/* 새로운 링크의 데이터 설정 */

new_rec -> ch = ch;

new_rec -> next_rec = NULL;

 

if(first == NULL) /* 링크드 리스트에 첫 링크 추가 */

{

first = new_rec;

new_rec -> next_rec = NULL; /* 불필요하지만 안전을 보장함 */

}

else /* 첫 링크가 아니면 */

{

/* 첫 링크 앞인지 확인 */

if(new_rec -> ch < first -> ch)

{

new_rec -> next_rec = first;

first = new_rec;

}

else /* 중간이나 긑에 추가됨 */

{

tmp_rec = first -> next_rec;

prev_rec = first;

 

/* 링크가 추가되는 곳을 확인 */

 

if(tmp_rec == NULL)

{

/* 끝에 두 번째 링크를 추가 */

prev_rec -> next_rec = new_rec;

}

else

{

/* 중간에 추가하는지 확인 */

while(tmp_rec -> next_rec != NULL)

{

if(new_rec -> ch < tmp_rec -> ch)

{

new_rec -> next_rec = tmp_rec;

if(new_rec -> next_rec != prev_rec -> next_rec)

{

printf("ERROR");

gets(stdin);

exit(0);

}

prev_rec -> next_rec = new_rec;

break; /* 링크가 추가되고 while을 마침 */

}

else

{

tmp_rec = tmp_rec -> next_rec;

prev_rec = prev_rec -> next_rec;

}

}

 

/* 끝에 추가하는지 확인 */

if(tmp_rec -> next_rec == NULL)

{

if(new_rec -> ch < tmp_rec -> ch) /* 끝에서 두 번째 위치 */

{

new_rec -> next_rec = tmp_rec;

prev_rec -> next_rec = new_rec;

}

else /* 끝 위치 */

{

tmp_rec -> next_rec = new_rec;

new_rec -> next_rec = NULL; /* 중복 작업 */

}

}

}

}

}

return(first);

}

 

/*====================================================*

* 함수 : show_list

* 목적 : 링크드 리스트의 정보 출력

*====================================================*

 

void show_list(LISTPTR first)

{

LISTPTR cur_ptr;

int counter = 1;

 

printf("\n\nRec addr Position Data Next Rec addr\n");

printf("======== ======== ==== ==============\n");

 

cur_ptr = first;

while(cur_ptr != NULL)

{

printf(" %x ", cur_ptr);

printf(" %2i %c", counter++, cur_ptr -> ch);

printf(" %x \n", cur_ptr -> next_rec);

cur_ptr = cur_ptr -> next_rec;

}

}

 

/*====================================================*

* 함수 : free_memory_list

* 목적 : 링크드 리스트를 위해 정리된 모든 메모리 해제

*====================================================*

 

void free_memory_list(LISTPTR first)

{

LISTPTR cur_ptr, next_rec;

cur_ptr = first; /* 처음에서 시작 */

 

while(cur_ptr != NULL) /* 링크드 리스트의 끝까지 진행 */

{

next_rec = cur_ptr -> next_rec; /* 다음 링크의 주소 구하기 */

free(cur_ptr); /* 현재 링크 해제 */

cur_ptr = next_rec; /* 현재 링크 정리 */

}

}

 

-> 입력 / 출력

Enter character 1,
Must be a to z: q

Enter character 2,
Must be a to z: b

Enter character 3,
Must be a to z: z

Enter character 4,
Must be a to z: c

Enter character 5,
Must be a to z: a

Rec addr Position Data Next Rec addr
======== ======== ==== ==============
C3A 1 a C22
C22 2 b C32
C32 3 c C1A
C1A 4 q C2A
C2A 5 z 0

=> 분석
: 이 프로그램은 링크드 리스트에 링크를 추가하는 것을 보여준다. 예제를 이해하기는 어려울 것이다. 그러나 자세히 분석해 보면 앞에서 설명하는 링크를 추가하는 세 가지 방법을 모두 다루고 있음을 알 수 있다. 이 프로그램은 링크드 리스트의 시작, 중간, 마지막에 새로운 링크를 추가하는데 사용할 수 있다. 또한, 시작 부분에 추가되는 첫 링크와 중간에 추가되는 두 번째 링크를 새로 추가하는 경우에 대해서 중점적으로 다루고 있다. <리스트 15.13>의 시작 부분에 있는 내용들은 익숙하므로 쉽게 이해할 수 있을 것이다. 9번째 줄부터 14번째 줄까지는 NULL값이 이미 정의되어 있는지 확인해본다. 만약 그렇지 않다면 10번째 줄은 NULL값을 0으로 정의한다. 14번째 줄부터 22번째 줄까지는 링크드 리스트를 위한 구조체를 정의하고, 구조체와 포인터를 쉽게 사용할 수 있도록 하기 위해서 typedef를 사용하고 있다. main()함수는 이해하기 쉽다. first라는 헤드 포인터가 31번째 줄에서 선언된다. 이 포인터가 NULL로 초기화된다는 것에 주목하지 바란다. 여러분은 결코 포인터를 초기화하지 않은 상태로 내 버려두어서는 안된다는 것을 기억하기 바란다. 36번째 줄부터 49번째 줄까지는 사용자로부터 5문자를 읽어들이는 while 순환문이 있다. 다섯 번을 반복하는 이 외부 while 순환문 내에서 do...while은 입력된 각 문자가 영문자인지 확인하는 역할을 한다. 내부 순환문 대신에 isalpha()함수를 사용할 수도 있을 것이다. 데이터를 읽어들이고 나면 add_to_list()가 호출된다. 링크드 리스트의 시작에 대한 포인터와 링크드 리스트에 추가되는 데이터가 함수로 전달된다. main() 함수는 링크드 리스트의 데이터를 출력하기 위해 show_list()를 호출하고, 링크드 리스트에서 링크를 저장하기 위해 할당된 모든 메모리를 해제하는 free_memory_list()를 호출하면 끝난다. 이런 함수들은 비슷한 방법으로 동작한다. 각각은 헤드 포인터인 first를 사용하여 링크드 리스트의 링크드 리스트의 처음에서 시작한다. while 순환문은 특정 링크에 서 next_ptr 값을 사용하여 다음 링크로 진행한다. next_ptr이 NULL과 같을 때 링크드 리스트의 마지막에 도달한 것이므로 함수는 종료된다. 이 리스트에서 가장 중요하고 가장 복잡한 함수는 56번째 줄부터 149번째 줄까지의 add_to_list()이다. 66번째 줄부터 68번째 줄까지는 세 개의 서로 다른 링크를 지적하는 데 사용할 세 포인터를 선언한다. new_rec 포인터는 추가되는 새로운 링크를 지적할 것이다. tmp_rec 포인터는 링크드 리스트에서 기준이 되는 현재 링크를 지적할 것이다. 링크드 리스트에 하나 이상의 링크가 있다면 prev_rec 포인터는 기준이 되는 현재 링크 앞의 링크를 지적하는 데 사용된다.

71번째 줄은 추가되는 새로운 링크를 위한 메모리를 할당한다. new_rec 포인터는 malloc()에 의해 반환되는 값으로 설정된다. 메모리가 할당될 수 없다면 74번째 줄과 75번째 줄은 에러 메시지를 출력하고 프로그램을 마친다. 만약 메모리가 성공적으로 할당되었다면 프로그램은 계속된다.

79번째 줄은 구조체에 이 함수로 전달된 데이터를 저장한다. 이렇게 하기 위해서 단순히 함수로 전달된 문자 ch를 새로운 링크의 문자 필드인 new_rec -> ch로 설정하면 된다. 더 복잡한 프로그램에서는 여러 필드를 설정해야 할 것이다. 80번째 줄은 새로운 링크가 임의의 위치를 지적하지 않도록 next_rec을 NULL로 설정한다.

82번째 줄은 링크드 리스트에 어던 링크가 있는지 확인하면서 '링크 추가' 과정을 시작한다. 만약 헤드 포인터 first가 NULL로 지적되면, 즉 추가되는 링크가 링크드 리스트의 첫 번째 링크이면 헤드 포인터는 단순히 새로운 포인터로 설정되고 작업은 끝난다. 만약 새로운 링크가 처음이 아니면 함수는 87번째 줄의 else 내에서 계속 진행한다.

90번째 줄은 새로운 링크가 링크드 리스트의 시작 부분으로 이동되어야 하는지 확인해본다. 앞에서 배웠듯이 이것은 링크를 추가하는 세 가지 경우의 하나이다. 만약 링크를 처음에 위치시킨다면 92번째 줄은 새로운 링크의 next_rec 포인터가 이전의 '처음' 링크를 지적하게 한다. 그리고 나서 93번째 줄은 포인터 first가 새로운 링크를 지적하게 한다. 이렇게 해서 링크드 리스트의 시작 부분에 새로운 링크가 추가된다. 만약 새로운 링크가 비어 있는 링크드 리스트에 추가되는 첫 번째 링크가 아니고 기존 링크드 리스트의 첫 번째 위치에 추가되는 것도 아니라면 일크드 리스트의 중간이나 마지막에 위치하는 것임을 알 수 있다. 97번째 줄과 98번째 줄은 앞에서 선언한 tmp_rec와 prev_rec 포인터를 설정한다. 포인터 tmp_rec는 링크드 리스트에서 두 번째 링크의 주소로 설정되고, prev_rec는 링크드 리스트에서 첫 번째 링크로 설정된다. 링크드 리스트에 단지 하나의 링크만 있다면 tmp_rec가 NULL과 같아진다는 것에 주의하기 바란다. tmp_rec가 NULL로 설정되는 첫 링크의 next_ptr로 설정되기 때문이다.

102번째 줄은 이런 경우를 확인하고 있다. 만약 tmp_rec가 NULL이면 새로운 링크가 링크드 리스트에 추가되는 두 번째 링크라는 것을 알 수 있다. 새로운 링크가 첫 링크 앞에 위치되지 않는다는 것을 이미 알고 있으므로 마지막 링크가 되는 것이다. 이렇게 하기 위해서 단순히 prev_rec -> next_ptr을 새로운 링크로 설정하면 되고 작업은 끝난다. 만약 tmp_rec 포인터가 NULL이 아니라면 이미 링크드 리스트에 두 개 이상의 링크가 있다는 것을 알 수 있다. 110번째 줄부터 129번째 줄까지에 있는 while 순환문은 새로운 링크가 위치되어야 하는 곳을 결정하기 위해 링크의 나머지 부분을 차례대로 확인한다. 112번째 줄은 새로운 링크의 데이터 값이 현재 지적되는 링크보다 작은지 확인해본다. 만약 데이터 값이 작다면 여기에 링크를 추가하면 된다. 그러나 새로운 링크의 데이터가 현재 링크의 데이터보다 크다면 링크드 리스트의 다음 링크를 살펴볼 필요가 있다. 126번째 줄과 127번째 줄은 포인터 tmp_rec와 next_rec를 다음 링크로 설정한다. 만약 문자가 현재 링크의 문자보다 '작다면' 링크드 리스트의 중간에 링크를 추가하기 위해 앞에서 설명했던 방법을 따르면 된다. 이것은 114번째 줄부터 122번째 줄까지 나타나 있다. 114번째 줄에서 새로운 링크의 next 포인터를 현재 링크의 주소 tmp_rec와 같게 설정한다. 121번째 줄은 이전 링크의 next 포인터가 새로운 링크를 지적하도록 설정한다. 그리고 나서 작업이 끝난다. 코드는 while 순환문을 마치기 위해 break문을 사용한다.

앞에서 설명한 내용은 링크드 리스트의 중간에 추가되는 새로운 링크를 다루는 것이다. 만약 링크드 리스트의 마지막에 도달했다면 110번째 줄부터 129번째 줄까지의 while 순환문 은 링크를 추가하지 않고 끝날 것이다. 132번째 줄부터 144번째 줄까지는 링크를 마지막에 추가하는 작업을 수행한다.

만약 링크드 리스트의 마지막 링크에 도달하면 tmp_rec -> next_rec는 NULL과 같아질 것이다. 132번째 줄은 이것을 확인한다. 134번째 줄은 링크가 마지막 링크의 앞이나 뒤에 위치되어야 하는지 확인한다. 만약 새로운 링크가 마지막 링크 다음으로 가야 한다면 링크의 next_rec를 132번째 줄에서 새로운 링크로 설정하고 새로운 링크의 next 포인터를 142번째 줄에서 NULL로 설정한다.

▶ <리스트 15.13> 수정하기
: 링크드 리스트는 상당히 어려운 주제이다. 그러나 <리스트 15.13>에서 볼 수 있듯이 일정한 순서대로 데이터를 저장하는 가장 좋은 방법이기도 하다. 링크드 리스트에서 어디든지 새로운 데이터 항목을 추가하는 것이 쉬우므로 링크드 리스트로 데이터 항목의 목록을 정렬해서 사용하는 것이 배열을 사용하는 것보다 훨씬 더 간단하다. 앞의 예제는 이름, 전화번호, 다른 어떤 데이터를 정렬하도록 쉽게 수정할 수 있다. 또한, 앞의 프로그램에서는 오름차순(A에서 Z)으로 정렬했지만 내림차순(Z에서 A)으로 정렬하도록 수정하는 것도 어렵지 않다.

▶ 링크드 리스트에서 삭제하기
: 링크드 리스트에 정보를 추가하는 기능은 기본적이지만 가끔 정보를 제거하기 원할 때가 있을 것이다. 링크나 요소를 삭제하는 것은 추가하는 것과 비슷하다. 여러분은 링크드 리스트의 시작, 중간, 마지막에서 링크를 삭제할 수 있다. 각각의 경우에 적절한 포인터를 조절하면 된다. 또한, 삭제된 링크가 차지하고 있던 메모리를 해제시킬 필요가 있다.

'pc관련 > C언어' 카테고리의 다른 글

API 윈도우 창띄우기  (0) 2019.04.14
C언어_디스크파일의 사용  (0) 2019.04.11
C언어_포인터,고급기능  (0) 2019.04.07
고급 프로그램제어문  (0) 2019.04.03
C언어_변수에 범위  (0) 2019.03.28
Posted by 둥이파파^^
pc관련/C언어2019. 4. 7. 22:49

"포인터에 대해서"에서는 C 프로그래밍 언어의 가장 중요한 주제인 포인터에 대한 기본적인 내용을 다루었다. 이 장에서는 더욱 유용한 프로그램을 작성할 수 있도록 포인터에 대한 좀더 많은 내용을 설명할 것이다. 오늘은 다음과 같은 내용을 배울 것이다.

·포인터에 대한 포인터를 선언하는 방법
·다차원 배열과 포인터를 함께 사용하는 방법
·포인터의 배열을 선언하는 방법
·함수에 대한 포인터를 선언하는 방법
·데이터 저장을 위한 링크드 리스트를 생성하기 위해 포인터를 사용하는 방법

1. 포인터에 대한 포인터
: 포인터는 다른 어떤 변수의 주소값을 가지는 숫자 변수이다. 포인터는 간접 연산자(*)를 사용하여 선언할 수 있다. 예를 들어, 다음 문장은

int *ptr;

int형 변수를 지적하는 ptr이라는 이름의 포인터를 선언한다. 이런 포인터를 선언하고 나면 대응하는 형태의 어떤 변수를 지적하도록 하기 위해서 주소 연산자(&)를 사용한다. x가 int형 변수로 선언되어 있다고 가정한다면, 다음 문장은

ptr = &x;

x의 주소를 ptr에 할당하여 ptr이 x를 지적하도록 한다. 또한, 간접 연산자를 사용하면 포인터가 지적하는 변수의 값을 참조할 수 있다. 다음 문장은 모두 x에 12의 값을 저장한다.

x = 12;
*ptr = 12;

포인터 자체는 숫자 변수이므로 컴퓨터 내의 메모리에서 특정 주소에 저장된다. 그래서 포인터에 대한 포인터, 즉 포인터 변수의 값이 다른 포인터의 주소인 변수를 생성할 수도 있다. 다음은 예이다.

int x = 12; /* x는 int형 변수이다. */
int *ptr = &x; /* ptr은 x에 대한 포인터이다. */
int **ptr_to_ptr = &ptr; /* ptr_to_ptr은 int형 변수에 대한 포인터의 포인터이다. */

포인터의 포인터를 선언할 때에는 이중 간접 연산자(**)를 사용한다는 것을 기억하자. 또한, 포인터의 포인터가 지적하는 변수를 참조할 때에도 이중 간접 연산자를 사용한다. 그래서 다음의 문장은

**ptr_to_ptr = 12;

변수 x에 12의 값을 할당하고, 다음 문장은

printf("%d", **ptr_to_prt);

x의 값을 화면 상에 출력한다. 여기에서 실수로 하나의 간접 연산자를 사용하면 에러가 발생한다. 다음 문장은

*ptr_to_ptr = 12;

ptr에 12의 값을 할당하고 ptr은 이미 다른 어떤 값이 저장되어 있는 주소 12를 지적하게 된다. 이것은 분명히 잘못된 것이다.

포인터의 포인터를 선언하고 사용하는 것을 이중 간접 사용(multiple indirection)이라고 한다. <그림 15.1>에는 변수, 포인터, 포인터에 대한 포인터의 관계가 나타나 있다. 간접 사용의 단계에는 아무런 제한이 없다. 필요하다면 무한한(ad_infinitum) 단계의 포인터의 포인터를 사용할 수도 있지만, 일반적으로 2단게를 초과하는 포인터의 사용에는 특별한 장점이 없다. 오히려 복잡해짐에 따라 실수가 발생할 가능성만 높아진다.

<그림 15.1> 포인터의 포인터를 설명하는 그림

그렇다면 포인터의 포인터는 어떤 경우에 사용될까? 포인터의 포인터는 이 장의 후반부에서 설명할 포인터의 배열에서 가장 많이 사용된다.

2. 포인터와 다차원 배열
: 8번째 강의 "숫자 배열 사용하기"에서는 포인터와 배열간의 특별한 관계에 대해서 설명했었다. 특히, 대괄호를 포함하지 않는 배열의 이름이 배열의 첫 번째 요소에 대한 포인터라는 사실은 중요하다. 결과적으로, 특정 형태의 배열을 참조할 때에는 포인터식 표기 방법을 사용하는 것이 낫다. 그러나 지금까지의 예제는 일차원 배열에만 국한된 것이었다. 다차원 배려의 경우는 어떨까? 다차원 배열은 각각의 차원에 대해 대괄호를 사용하여 선언된다는 것을 기억하자. 예를 들어, 다음 문장은 8개의 int형 변수를 가지는 2차원 배열을 선언한다.

int multi[2][4];

배열은 행과 열을 가지는 것으로 생각할 수 잇다. 앞의 배열은 2행 4열로 구성된다. 그러나 다차원 배열의 구조를 표현하는 다른 한 가지 방법이 있다. C가 실제로 배열을 다루는 방법에 더 가까운 것으로 multi를 두 개의 요소를 가지는 배열로 생각할 수 있다. 각각의 요소는 4개의 정수를 가지는 배열이다. 이런 사실을 이해하기 어렵다면 배열의 선언문을 4개의 구성 요소로 나누어서 설명하고 있는 <그림 15.2>를 참고하기 바란다.

<그림 15.2> 다차원 배열 선언문의 구성 요소

각각의 구성 요소는 다음과 같은 뜻을 가진다.

1. multi라는 이름의 배열을 선언한다.
2. 배열 multi는 두 개의 요소를 가진다.
3. 각각의 요소는 다시 네 개의 요소를 가지고 있다.
4. 네 개의 요소는 int형이다.

다차원 배열의 선언문은 배열의 이름에서부터 시작하여 오른쪽으로 이동하며 대괄호 내에 포함된 내용을 처리한다. 마지막 대괄호의 내용이 처리되고 나면 배열의 기본 데이터형을 결정하기 위해 선언문의 시작 부분으로 이동하게 된다.

이 장에서는 포인터에 대한 내용을 다루므로, 이제 원래의 주제인 포인터로 사용되는 배열의 이름에 대해서 다시 살펴보자. 1차원 배열에서와 마찬가지로 다차원 배열의 이름은 배열의 첫 번째 요서에 대한 포인터이다. 앞에서 사용된 예제의 경우, multi는 int multi[2][4]를 통해서 선언된 2차원 배열의 첫번째 요소에 대한 포인터이다. multi의 첫번째 요소는 정확히 무엇일까 multi의 첫번째 요소는 int형 변수인 multi[0][0]이 아니라 multi가 배열을 가지는 배열이므로 네 개의 int형 변수를 가지는 배열 multi[0]이라는 것을 기억하자. multi[0]은 multi에 포함된 두 배열의 하나이다. 또한, multi[0]이 하나의 배열이라면 어떤 값을 지적하는가? 실제로, multi[0]은 첫 번째 요소인 multi[0][0]을 지적한다. 이런 사실에 대해서는 의문을 가질 수 있다. 대괄호를 포함하지 않는 배열의 이름이 배열의 첫번째 요소에 대한 포인터라는 것을 기억하자. multi[0]은 대괄호를 포함하고 있는 multi[0][0]의 이름이므로 하나의 포인터라고 볼 수 있다. 지금까지의 내용이 혼란스럽더라도 걱정할 필요는 없다. 사실, 포인터와 배열의 관계는 이해하기 어려운 내용이다. n차원의 배열을 사용할 때 다음과 같은 규칙을 기억한다면 유용할 것이다.

·n개의 대괄호와 적절한 색인을 포함하는 배열의 이름은 배열의 데이터를 뜻한다. 즉, 지정된 배열의 요소에 저장된 데이터를 나타내는 것이다.

·n개 이하의 대괄호를 포함하는 배열의 이름은 배열의 요소에 대한 포인터를 뜻한다. 그래서 앞의 예제에서는 multi가 포인터이고, multi[0]도 포인터이며, multi[0][0]은 배열의 데이터를 나타내는 것이다.

이제, 이런 모든 포인터가 실제로 지적하는 것이 무엇인지 살펴보도록 하자. <리스트 15.1>에 있는 프로그램은 앞에서 사용된 것과 비슷한 2차원 배열을 선언하고 관련된 포인터의 값을 출력한다. 또한 첫 번째 배열 요소의 주소를 출력한다.

<리스트 15.1> 다차원 배열과 포인터의 관계

/* 포인터와 다차원 배열의 사용 예 */

#include

 

int multi[2][4];

 

main()

{

printf("\nmulti = %u", multi);

printf("\nmulti[0] = %u", multi[0]);

printf("\n&multi[0][0] = %u", &multi[0][0]);

return(0);

}

 

=> 실제 값을 시스템에 따라 1.328이 아닐 수도 있겠지만 세 값이 동일하다는 사실은 변함 없다. 배열 multi의 주소는 배열 multi[0]의 주소와 동일하고, 이런 값은 배열 multi[0][0]에 저장되어 있는 첫 번째 정수값의 주소와 동일하다. 세 포인터가 동일한 값을 가진다면 프로그램의 측면에서 실제 차이점은 무엇일까? 9번째 강의에서는 포인터가 지적하는 것을 C 컴파일러가 '알고 있다'고 설명했다. 더욱 정확히 표현하자면, 컴파일러는 포인터가 지적하는 항목의 크기를 알고 있다.
앞에서 사용된 각 항목은 어떤 크기를 가지고 있을까? <리스트 15.2>는 이런 각 항목의 크기를 바이트 단위로 출력하기 위해서 sizeof() 연산자를 사용하고 있다.

<리스트 15.2> 각 항목의 크기 확인하기

/* 다차원 배열 요소의 크기 */

#include

 

int multi[2][4];

 

main()

{

printf("\nThe size of multi = %u", sizeof(multi));

printf("\nThe size of multi[0] = %u", sizeof(multi[0]));

printf("\nThe size of multi[0][0] = %u", sizeof(multi[0][0]));

return(0);

}

 

=> IBM의 OS/2와 같은 32비트 운영체제를 사용중이라면 결과는 32, 16, 4가 될 것이다. 이것은 OS/2와 같은 운영체제에서 int형이 4바이트이기 때문이다. 결과에 대해서 생각해보자. 배열 multi는 네 개의 정수값을 가지는 두 개의 배열을 포함하고 있다. 각각의 정수는 2바이트를 차지한다. 전체적으로는 8개의 정수가 존재하므로 16바이트 라는 크기는 정확한 값이다. 다음으로, multi[0]은 네 개의 정수를 가지는 배열이다. 각각의 정수는 2바이트를 차지하므로 multi[0]의 크기가 8바이트로 표현된 것도 정확하다. 마지막으로, multi[0][0]은 정수이므로 정수형의 크기는 당연히 2바이트이다.

이제, 이런 결과에 유의해서 9번째 강의에서 설명한 포인터 연산에 대해 생각해보도록 하자. C 컴파일러는 포인터가 지적하는 값의 크기를 '알고' 잇고 포인터 연산에서는 이런 크기를 사용한다. 포인터를 증가시키면 현재 지적하고 있는 어떤 내용의 '다음 위치에 있는 것'을 지적하기 위해서 필요한 만큼 증가된다. 즉, 포인터가 지적하고 있는 값의 크기만큼 증가된다.

이런 포인터 연산의 개념을 앞의 예제에 적용해보자. multi는 8개의 요소를 가지는 정수형 배열에 대한 포인터로 크기는 8이다. 만약 multi를 증가시키면 포인터의 값을 네 개의 요소를 가지는 정수형 배열의 크기인 8만큼 증가된다. multi가 multi[0]을 지적하고 있다면 (multi + 1)은 multi[1]을 지적할 것이다. <리스트 15.3>에 있는 프로그램은 이런 사실을 증명한다.

<리스트 15.3> 다차원 배열에서의 포인터 연산

/* 다차원 배열에 대한 포인터로 포인터 연산하기 */

#include

 

int multi[2][4];

 

main()

{

printf("\nThe value of (multi) = %u", multi);

printf("\nThe value of (mulit + 1) = %u", (multi + 1));

printf("\nThe address of multi[i] = %u", &umlti[1]);

return(0);

}

 

=> 정확한 값은 시스템에 따라 달라질 수 있지만 개념은 같을 것이다. multi를 1증가시키면 실제 값은 8(32비트 환경에서는 16) 증가되고 배열의 다음 요소인 multi[1]을 지적하게 된다. 이 예제에서는 multi가 multi[0]에 대한 포인터라는 사실을 알 수 있다. 또한, multi[0] 자체는 multi[0][0]에 대한 포인터라는 것을 알 수 있다. 그래서 multi는 포인터에 대한 포인터이다. 배열의 데이터를 참조하는 경우에 multi를 사용하기 위해서는 이중 간접 연산자를 사용해야 한다. multi[0][0]에 저장된 값을 출력하기 위해서는 다음과 같은 세 문장의 하나를 사용할 수 있을 것이다.

printf("%d", multi[0][0]);
printf("%d", *multi[0]);
printf("%d", **multi);

이런 개념은 3차원 이상의 배열에도 적용된다. 그래서 3차원 배열은 2차원 배열을 요소로 가지는 배열이라고 할 수 있다. 다시, 각각의 요소는 1차원 배열을 요소로 가진다. 여기서 설명한 다차원 배열과 포인터에 대한 내용은 다소 혼란스러울 것이다. 다차원 배열을 사용할 때에는 한 가지 사실만 기억하자. n차원의 배열은 n - 1차원의 배열을 요소로 가진다. n의 값이 1일 때 배열의 요소는 배열 선언문의 앞에서 지정한 데이터형의 변수이다. 지금까지는 포인터 상수이고 변경이 불가능한 배열의 이름을 사용하였다. 그렇다면 다차원 배열의 요소를 지적하는 포인터 변수를 어떻게 선언할 수 있을까? 2차원 배열을 선언하는 앞의 예를 다시 한 번 살펴보자.

int multi[2][4];

multi의 각 요소, 즉 네 개의 요소를 가지는 정수형 배열을 지적할 수 있는 포인터 변수를 선언하기 위해서는 다음과 같은 문장을 작성할 수 있다.

int (*ptr)[4];

그리고 나서 다음의 문장을 사용하여 ptr이 multi의 첫 번째 요소를 지적하도록 할 수 있다.

ptr = multi;

포인터 선언에서 괄호를 사용한 이유를 이해할 수 있는가? 대괄호([])는 포인터 연산자(*)보다 우선 순위를 가진다. 만약 다음과 같은 문장을 작성했다면

int *ptr[4];

int형에 대한 네 개의 포인터의 배열을 선언할 것이다. 사실, 포인터의 배열을 선언하고 사용할 수는 있다. 그러나 여기서 필요한 것은 포인터의 배열이 아니다. 다차원 배열의 요소에 대한 포인터를 어떻게 사용할 수 있을까? 일차원 배열에서와 마찬가지로 포인터는 함수에 배열을 전달하는 경우에 사용된다. 이런 사실은 함수에 다차원 배열을 전달하는 두 가지 방법을 사용하고 잇는 <리스트 15.4>에서 설명된다.

<리스트 15.4> 포인터를 사용하여 다차원 배열을 함수에 전달하기

/* 다차원 배열에 대한 포인터를 함수에 전달하는 예 */

#include

 

void printarray_1(int (*ptr)[4]);

void printarray_2(int (*ptr)[4], int n);

 

main()
{

int multi[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};

 

/* ptr은 4 정수 배열에 대한 포인터이다. */

 

int (*ptr)[4], count;

 

/* ptr이 multi의 첫 요소를 지적하게 한다. */

 

ptr = multi;

 

/* 순환문에서 ptr은 multi의 다음요소, 즉 다음 4요소 정수배열을 지적하도록 증가된다. */

 

for(count = 0; count < 3; count++)

printarray_1(ptr++);

 

puts("\n\nPress Enter...");

getchar();

printarray_2(multk, 3);

printf("\n");

return(0);

}

 

void printarray_1(int (*ptr)[4])

{

/* 4 요소 정수 배열의 요소들을 출력한다. */

/* p는 INT형에 대한 포인터이다. */

/* P를 PTR의 주소와 같게 하기 위해 형 변환을 사용해야 한다. */

 

int *p, count;

p = (int *)ptr;

 

for(count = 0; count < 4; count++)

printf("\n%d", *p++);

}

 

void printarray_2(int (*ptr)[4], int n)

{

/* n번째 4 요소 정수 배열의 요소들을 출력한다. */

 

int *p, count;

p = (int *)ptr;

 

for(count = 0; count < (4 * n); count++)

printf("\n%d", *p++);

}

 

3. 포인터의 배열
: 8번째 강의 "숫자 배열 사용하기"에서는 배열이 동일한 데이터형을 사용하고 같은 이름으로 사용되는 집단적인 데이터 저장 영역이라고 설명했다. 포인터는 C에서 제공되는 한 가지 데이터형이므로 포인터의 배열을 선언하고 사용할 수 있다. 포인터의 배열은 특별한 상황에서 매우 유용할 수 있다. 아마도 포인터의 배열을 가장 많이 사용하는 경우는 문자열을 처리할 때일 것이다. 10번째 강의 "문자와 문자열"에서 배웠듯이 문자열은 메모리에 저장되는 일련의 문자들을 말한다. 문자열의 시작 부분은 첫 번째 문자에 대한 포인터, 즉 char형에 대한 포인터로 지적된다. 문자열의 마지막은 널 문자로 구분된다.. char형에 대한 포인터의 배열을 선언하고 초기화하면 포인터의 배열을 통해서 방대한 양의 문자열을 사용하거나 처리할 수 있다. 배열의 각 요소는 서로 다른 문자열을 가리키므로 배열을 통해서 순환하면서 차례대로 배열 요소를 이용할 수 있는 것이다.

3.1 문자열과 포인터에 대한 복습
: 문자열 할당과 초기화를 되새기면서 열 번째 강의에서 설명했던 내용을 다시 한번 살펴보도록 하자. 문자열을 할당하고 초기화하는 한 가지 방법은 다음과 같이 char형 배열을 선언하는 것이다.

char message[] = "This is the message.";

char형에 대한 포인터를 선언하여 문자열을 할당하고 초기화할 수도 있을 것이다.

char *message = "This is the message.");

두 문장은 동이한 것이다. 어떤 문장을 사용하든지 컴파일러는 널 문자를 포함하는 문자열을 저장하기 위한 영역을 할당하며, 수식 message는 문자열의 시작 부분에 대한 포인터가 된다. 다음 두 문장은 어떤 뜻을 가지고 있는가?

char message1[20];
char *message2;

첫 번째 문장은 20자 길이의 char형 배열을 선언하고, message1은 배열의 첫 번째 요소에 대한 포인터이다. 배열의 저장 영역은 할당되었지만 배열이 초기화되지는 않았고 배열의 내용은 정해지지 않았다. 두 번째 문장은 char형에 대한 포인터 message2를 선언한다. 이 경우, 문자열을 저장하기 위한 영역이 할당되지 않고 단지 포인터를 저장하기 위한 공간만이 할당되어 있다. 만약 문자열을 생성하여 message2가 문자열을 지적하기 원한다면, 우선 문자열을 저장하기 위한 영역을 할당해야 한다. 열 번째 강의에서 이런 용도로 메모리 할당 함수 malloc()을 사용하는 방법을 배웠다. 모든 문자열을 저장하기 위해서는 컴파일 과정에서 미리 준비하거나 도는 프로그램 실행 과정에서 malloc()을 사용하여 필요한 공간을 할당해야 한다는 것을 기억하자.

3.2 char에 대한 포인터의 배열
: 잠시동안 앞에서 배웠던 것을 복습했으므로 포인터의 배열을 선언해보자. 다음 문장은 char형에 대한 10개의 포인터를 가지는 포인터의 배열을 선언한다.

char *message[10];

배열 message[]의 요소는 각각 char형에 대한 포인터이다. 짐작할 수 있듯이 배열을 선언할 대에는 문자열을 저장하기 위한 저장 영역을 할당하는 동시에 문자열을 초기화할 수 있다.

char *message[10] = {"one, "two", "three"};

이 문장은 다음과 같은 동작을 수행한다.

·10개의 요소를 가지는 message라는 이름의 배열을 선언한다. 배열의 요소는 각각 char형에 대한 포인터이다.

·메모리 내에서 저장 영역을 할당하여 널 문자를 포함하는 초기화 문자열을 저장한다. 메모리의 정확한 위치는 신경 쓸 필요가 없다.

·message[0]은 첫 번째 문자열의 첫 번째 문자를 지적하고, message[1]은 두 번째 문자열의 첫 번째 문자를 지적하며, message[3]은 세 번째 문자열의 첫 번째 문자를 지적하도록 초기화된다.

이제 포인터의 배열을 사용하는 예제를 살펴보자.

<리스트 15.5> char형에 대한 포인터의 배열을 초기화하고 사용하는 프로그램

/* char형에 대한 포인터의 배열 초기화 */

#include

 

main()

{

char *message[8] = { "Four", "score", "and", "seven", "years", "ago,", "our", "forefathers"};

 

int count;

 

for(count = 0; count < 8; count++)

printf("%s ", message[count]);

printf"\n");

return(0);

}

 

=> <리스트 15.5>에 있는 프로그램은 char형에 대한 8개의 포인터를 가지는 배열을 선언하고, 7번째 줄과 8번째 줄에서는 배열 요소인 포인터가 8개의 문자열을 지적하도록 초기화하고 있다. 그리고 나서 11번째 줄과 12번째 줄은 배열의 각 요소를 화면 상에 출력하기 위해서 for문을 사용한다.

여기에서는 포인터의 배열을 다루는 것이 문자열 자체를 다루는 것보다 쉽다는 사실을 알 수 있을 것이다. 이런 장점은 이 장에서 나중에 설명할 복잡한 프로그램에서 더욱 분명하게 드러난다. 또한, 나중에 설명할 것처럼 이런 장점은 함수를 사용할 때 가장 강력하다. 함수에서 문자열을 출력하기 위해서 여러 개의 문자열을 전달하는 것보다는 포인터의 배열을 전달하는 것이 더욱 쉽다. 문자열을 출력하기 위해 함수를 사용하도록 <리스트 15.5>에 있는 프로그램을 변경하여 포인터의 배열을 전달하는 것이 더 쉽다는 사실을 확인할 수 있다. <리스트 15.6>에는 변경된 프로그램이 나타나 있다.

<리스트 15.6> 포인터의 배열을 함수에 전달하기

/* 함수에 포인터의 배열 전달하기 */

#include

 

void print_strings(char *p[], int n);

 

main()

{

char *message[8] = { "Four", "score", "and", "seven", "years", "ago,", "our", "forefathers"};

 

printf_strings(message, 8);

return(0);

}

 

void print_strings(char *p[], int n)

{

int count;

 

for(count = 0; count < n; count++)

printf("%s", p[count]);

printf("\n");

}

 

앞에서 포인터의 포인터를 설명할 때 나중에 예제를 설명할 것이라고 언급했던 것을 기억하는가? 앞의 예제가 바로 포인터의 포인터를 사용하는 예제이다. <리스트 15.6> 에서는 포인터의 배열을 선언했다. 배열의 이름은 첫 번째 요소에 대한 포인터이다. 함수에 배열을 전달할 때에는 포인터(배열의 첫번째 요소)에 대한 포인터 (배열 이름)를 전달하는 것이다.

3.3 예제
: 이제 좀더 복잡한 예제를 살펴보자. <리스트 15.7>에 있는 프로그램은 포인터의 배열을 포함하여 지금까지 설명한 여러 가지 내용을 활용하고 있다. 프로그램은 문자열이 입력될 때 문자열을 저장하기 위한 영역을 할당하고, char형에 대한 포인터의 배열을 사용하여 저장 영역을 관리하며, 여러 줄의 입력을 키보드에서 받아들인다. 빈 줄을 입력하여 마지막이라는 것을 알려주면 프로그램은 입력된 내용을 알파벳 순으로 정렬하고 화면 상에 출력한다. 만약 이 프로그램을 처음부터 작성한다면 구조화 프로그래밍 방식으로 작성할 것이다. 우선, 프로그램이 수행해야 하는 동작을 목록으로 만들어보자.

① 빈 줄이 입력될 때까지 한 번에 한 줄씩 키보드에서 입력을 받아들인다.

② 입력된 내용을 알파벳 순으로 정렬한다.

③ 정렬된 내용을 화면 상에 출력한다.

이 목록에서 알 수 있듯이 프로그램은 적어도 세 개의 함수를 사용해야 한다. 하나는 입력을 받아들이고, 다른 하나는 입력된 내용을 정렬하며, 세 번째 함수는 입력된 내용을 출력한다. 이제, 독립적으로 각각의 함수를 작성할 수 있을 것이다. get_lines()라는 입력 함수는 무엇을 수행할 필요가 있을까? 다시, 목록을 만들어 보자.

① 입력된 문장의 수를 일단 모든 내용이 입력되면 함수를 호출한 프로그램으로 문장의 수를 돌려준다.

② 미리 설정되어 있는 문장의 수보다 많은 내용은 받아들이지 않는다.

③ 각각의 문장을 저장하기 위한 영역을 할당한다.

④ 문자열에 대한 포인터를 배열에 저장하여 모든 내용을 관리한다.

⑤ 빈 줄이 입력되면 원래의 프로그램으로 돌아간다.

이제, 알파벳 순으로 문장을 정렬하는 두 번째 함수에 대해서 생각해보자. 이 함수를 sort() 라고 하자. 여기서 사용되는 정렬 방식은 인접한 두 개의 문자열을 비교해서 두 번째 문자열이 첫 번째 문자열보다 작으면 서로 교환하는 아주 간단한 방법이다. 더욱 정확히 말하자면, 함수는 포인터의 배열 내에서 인접한 두 개의 문자열을 비교하여 필요하다면 포인터를 교환한다.

문자열을 완전히 정렬하기 위해서는 배열의 처음부터 마지막까지 필요할 때마다 각 쌍의 문자열을 비교해야 한다. n개의 요소를 가지는 배열에서는 배열을 n - 1번 통과하며 비교해야 한다. 왜 n - 1번이나 배열을 통과해야 하는 것일까? 배열을 한 번 통과할 때 각각의 요소는 기껏해야 한 칸씩 이동될 수 있다. 예를 들면, 맨 앞에 위치되어야 하는 문자열이 실제로 끝에 위치되어 있다면 처음에는 배열을 통과하며 문자열을 맨 끝에서 앞으로 한 번 이동시키고, 다음에는 다시 한 번 앞으로 이동시키며, 계속해서 이런 과정을 반복해야 한다. 맨 끝에 위치되어 있는 문자열을 배열의 맨 앞으로 옮기려면 n - 1번 이동시켜야 한다. 이것은 아주 비효율적이고 좋지 못한 정렬 방식이라는 것을 기억하자. 그러나 예제 프로그램에서는 간단한 데이터를 정렬할 것이므로 이 방법이 사용하기 쉽고 이해하기 쉬우며 가장 적절하다. 마지막 함수는 화면 상에 정렬된 문자열을 출력한다. 사실, 이 함수는 이미 <리스트 15.6>에서 작성한 것을 약간 변경한 것이므로 <리스트 15.7>에서 사용하려면 약간 수정하면 된다.

<리스트 15.7> 키보드에서 여러 줄의 텍스트를 읽어들이고 알파벳순으로 정렬하여 출력하는 프로그램

/* 키보드에서 문자열을 읽어들이고 정렬하여 화면에 출력한다. */

#include

#include

#include

 

#define MAXLINES 25

 

int get_lines(char *lines[]);

void sort(char *p[], int n);

void print_strings(char *p[], int n);

 

char *lines[MAXLINES];

 

main()

{

int number_of_lines;

 

/* 키보드에서 여러 줄을 읽어들인다. */

 

number_of_lines = get_lines(lines);

 

if(number_of_lines < 0)

{

puts("Memory allocation error");

exit(-1);

}

 

sort(lines, number_of_lines);

print_strings(lines, number_of_lines);

return(0);

}

 

int get_lines9char *lines[])

{

int n = 0;

char buffer[80]; /* 각 줄에 대한 임시 저장 공간 */

 

puts("Enter one line at time; enter a blank when done.");

 

while((n < MAXLINES) && (gets(buffer) != 0) &&

(buffer[0] != '\0'))

{

if((lines[n] = (char *)malloc(strlen(buffer) + 1)) == NULL)

return -1;

strcpy(lines[n++], buffer);

}

return n;

 

} /* get_lines()의 끝 */

 

void sort9char *p[], int n)

{

int a, b;

char *x;

 

for(a = 1; a < n; a++)

{

for(b = 0; b < n-1; b++)

{

if(strcmp(p[b], p[b+1]) > 0)

{

x = p[b];

p[b] = p[b+1];

p[b+1] = x;

}

}

}

}

 

void print_strings(char *p[], int n)

{

int count;

 

for(count = 0; count < n; count++)

printf("\n%s ", p[count]);

}

 

=> 이 프로그램을 상세히 살펴보면 도움이 될 것이다. 프로그램에서는 문자열을 다루기 위해서 여러 가지 새로운 라이브러리 함수를 사용하고 있다. 여기에서는 이런 함수들을 간단히 설명하고, 상세한 내용은 낭중에 "문자열 다루기"에서 설명하도록 하겠다. 이런 함수들을 사용하는 프로그램에는 헤더 파일 STRING.H가 포함되어야 한다.

get_lines() 함수에서 41번째 줄과 42번째 줄의 다음과 같은 while문은 입력을 제어한다. 한 줄로 살펴보자.

while((n < MAXLINES) && (gets(buffer) != 0) && (vuffer[0] != '\0'))

while문에서는 세 가지 조건이 확인되고 있다. 첫 번째 조건인 n < MAXLINES는 지정된 수의 문장이 입력되지 않았는지 확인한다. 두 번째 조건인 get(buffer) != 0은 키보드에서 버퍼로 문장을 읽어들이기 위해서 라이브러리 함수 gets()를 호출하여 파일의 마지막에 도달하거나 또는 다른 어떤 에러가 발생하지 않았는지 확인한다. 세 번째 조건인 buffer[0] != '\0'는 방금 입력된 문장의 첫 문자가 널 문자가 아닌지 확인한다. 널 문자는 빈 줄이 입력되었다는 것을 알려주는 역할을 한다.

세가지 조건의 어떤 것도 만족되지 않으면, while문은 종료되고 함수는 지금까지 입력된 문장의 수를 프로그램으로 돌려주며 제어를 전달한다. 만약 세가지 조건이 모두 참이라면 44번째 줄에 있는 다음과 같은 if문이 실행된다.

if((lines[n] = (char *)malloc9strlin(buffer) + 1)) == NULL)

이 문장은 방금 입력된 문자열의 저장 영역을 할당하기 위해서 malloc()를 호출한다. strlen() 함수는 인수로 전달된 문자열의 길이를 돌려준다. 복귀값에는 malloc() 함수가 문자열과 함께 널 문자를 저장할 수 있는 영역을 할당하도록 하기 위해서 1이 더해진다. 앞에서 설명했던 malloc() 함수는 포인터를 돌려준다. 이 문장은 malloc()이 돌려주는 포인터의 값을 포인터의 배열에서 대응하는 요소에 할당한다. 만약 malloc()이 NULL을 돌려주면, if문에서는 함수를 호출한 프로그램으로 -1의 복귀값을 돌려준다. main()에서는 get_lines()의 복귀값을 확인하여 함수가 0보다 작은 값을 돌려주는지 확인한다. 23번째 줄부터 27번째 줄까지는 메모리 할당에러를 알려주고 프로그램을 종료한다. 메모리의 할당이 성공적이었다면 프로그램의 46번째 줄에서는 임시 저장 영역의 버퍼에서 방금 malloc()에 의해 할당된 저장 영역으로 문자열을 복사하기 위해 strcpy()함수를 사용한다. 그리고 나서 while문은 다른 문장을 읽어들이기 위해 전체 과정을 반복한다. 메모리 할당에러가 발생하지 않았다고 가정하고, 일단 get_lines()에서 main()으로 제어가 전달되면 다음과 같은 동작이 수행된 것이다.

·키보드에서 여러 줄의 텍스트가 입력되고 널 문자를 포함하는 문자열 형태로 메모리에 저장되었다.

·배열 lines[]는 각 문자열에 대한 포인터를 가진다. 배열 내의 포인터의 순서는 문자열이 입력된 순서이다.

·변수 number_of_lines는 입력된 문장의 수를 가진다.

이제, 문자열의 정렬 부분을 살펴보자. 프로그램에서는 실제로 문자열을 이동시키는 것이 아니라 단지 배열 lines[]에 포함되어 있는 포인터의 순서만을 이동시킨다는 것을 기억하자. 함수 sort()의 내용을 살펴보자. 57번째 줄부터 68번째 줄까지는 for문 내에 종속되어 있는 다른 하나의 for문이 사용되고 잇다. 바깥쪽의 순환문은 number_of_lines - 1번 실행된다. 바깥쪽의 순환문이 실행될 때마다 안쪽의 순환문은 n = 0에서부터 n = number_of_lines-1이 될 때까지 포인터의 배열에서 (string n)을 (string n + 1)과 비교한다. 실제 비교 동작은 두 개의 문자열에 대한 포인터를 바다아들이는 61번째 줄의 라이브러리 함수 strcmp()에 의해서 수행된다. 함수 strcmp()는 다음 중에서 하나의 값을 돌려준다.

·첫 번째 문자열이 두 번째 문자열보다 크다면 -> 0보다 큰 값

·두 문자열이 동일하다면 -> 0

·두 번째 문자열이 첫 번째 문자열보다 크다면 -> 0보다 작은 값 프로그램에서 strcmp()가 0보다 큰 값을 돌려준다면 첫 번째 문자열이 두 번째 문자열보다 '크다'는 것을 뜻하므로 문자열을 서로 교환해야 한다. 즉, lines[]내의 포인터를 교환해야 한다

이런 교환 동작은 임시 변수인 x를 사용하여 수행된다. 63번째 줄부터 65번째 줄까지는 실제 교환을 수행한다.

sort()에서 프로그램으로 제어가 전달될 때 lines[]내의 포인터는 순서대로 정렬된 상태가 된다. '가장 작은' 문자열에 대한 포인터는 lines[0]에 저장되고, 다음으로 '작은' 문자열에 대한 포인터는 lines[1]에 저장된다. 마지막으로, 프로그램은 화면 상에 정렬된 문자열을 출력하기 위해서 함수 print_strings()를 호출한다. 이 함수는 앞의 예제와 비슷하다. <리스트 15.7>에 있는 프로그램은 지금까지 여기서 사용한 것들 중에서 가장 복잡한 예제이다 이 프로그램은 지금까지 설명한 C 프로그래밍에 대한 많은 내용을 다루고 있다. 코드 분석을 참고로 해서 이 프로그램의 동작과 각 단계별 내용을 이해하기 바란다. 만약 이해할 수 없는 부분이 있다면, 다음 주제로 진행하기 전에 프로그램을 완전히 이해할 할 수 있을 때까지 여기서 관련된 내용을 다시 한 번 읽어보도록 하자.

4. 함수에 대한 포인터
: 함수에 대한 포인터는 함수를 호출하는 또다른 방법을 제공해준다. 여기서 아마도 '함수에 대한 포인터를 어떻게 구할 수 있지? 포인터는 변수가 저장된 주소값을 가지는 것이 아닌가?'라는 의문을 가질 것이다. 이 질문에 대한 해답은 양면성을 가지고 있다. 포인터가 주소값을 가진다는 것을 사실이지만 반드시 변수가 저장된 주소값일 필요는 없다. 프로그램이 실행될 때 각 함수의 코드는 특정 주소에서부터 시작하는 메모리 영역에 위치된다. 함수에 대한 포인터는 이렇게 메모리 영역에 저장된 함수의 시작 주소값을 가진다. 그렇다면 함수에 대한 포인터를 사용하는 이유는 무엇일까? 앞에서도 언급했듯이, 함수를 호출하는 방법에 많은 융통성을 제공해준다. 함수에 대한 포인터는 프로그램이 여러 가지 함수들 중에서 현재 상황에 적합한 것을 '선택하여' 실행하게 해준다.

4.1 함수에 대한 포인터 선언
: 다른 일반적인 포인터와 마찬가지로 함수에 대한 포인터를 사용하려면 먼저 선언해야 한다. 포인터는 다음의 형식을 사용한다.

type (*ptr_to_func)(parameter_list);

이 문장은 type형을 돌려주고 parameter_list에 포함되어 있는 매개 변수를 받아들이는 함수에 대한 포인터 ptr_to_func를 선언한다. 다음은 몇 가지 구체적인 예이다.

int (*func1)(int x);
void (*func2)(double y, double z);
char (*func3)(char *p[]);
void (*func4)();

첫 번째 문장은 하나의 int형 인수를 받아들이고 int형 값을 돌려주는 함수에 대한 포인터 func1을 선언한다. 두 번째 문장은 두 개의 double형 인수를 받아들이고 void의 복귀형을 가지는, 즉 복귀값이 없는 함수에 대한 포인터 func2를 선언한다. 세 번째 문장은 chart형에 대한 포인터의 배열을 인수로 받아들이고 char형의 값을 돌려주는 함수에 대한 포인터 func3을 선언한다. 마지막 문장은 아무런 인수도 받아들이지 않고 void의 복귀형을 가지는 함수에 대한 포인터 func4를 선언한다. 포인터의 이름 주위에 괄호를 사용할 필요가 있을까? 첫 번째 예제의 경우 다음과 같은 문장을 사용할 수 없는가?

int *func1(int x);

이 것은 간접 연산자인 *의 우선 순위에 관련된 문제이다. 간접 연산자는 매개 변수의 목록을 둘러싸고 있는 괄호보다 상대적으로 낮은 우선 순위를 가진다. 첫 번째 예제에서 괄호를 생략한 선언문은 func1을 int형에 대한 포인터를 돌려주는 함수로 선언한다. 포인터를 돌려주는 함수는 8번째 강의 "함수를 효율적으로 사용하는 방법"에서 다루어진다. 함수에 대한 포인터를 선언할 때에는 항상 포인터의 이름과 간접 연산자 주위에 괄호를 사용해야 한다는 사실을 기억하자. 그렇지 않으면 분명히 문제가 발생할 것이다.

4.2 함수에 대한 포인터의 초기화와 사용
: 함수에 대한 포인터를 사용하려면 포인터를 선언해야 할 뿐 아니라 어떤 것을 지적하도록 초기화해야 한다. 물론 여기서 '어떤 것'은 함수이다. 포인터가 지적해야 하는 함수에는 아무런 제한이 없다. 한가지 주의해야 할 사항이 있다면 함수의 복귀형과 매개 변수의 목록이 포인터를 선언할 때 지정된 복귀형이나 매개 변수의 목록과 일치해야 한다는 것이다. 예를 들어, 다음 문장은 함수와 함수에 대한 포인터를 선언하고 정의한다.

float square(float x); /* 함수 원형 */
float (*p)(float x); /* 포인터 선언 */
float square(float x) /* 함수 정의 부분 */
{
return x * x;
}

함수 square()와 포인터 p는 동일한 매개 변수와 복귀 형태를 사용하므로 다음과 같이 p가 square를 지적하도록 초기화할 수 있다.

p = square;

이제, 다음과 같이 포인터를 사용하여 함수를 호출할 수 있다.

answer = p(x);

함수에 대한 포인터는 이처럼 간단한 것이다. 실제 사용 예는 <리스트 15.8>에 있는 프로그램 을 컴파일하고 실행하여 살펴보도록 하자. 이 프로그램은 함수에 대한 포인터를 선언하고 초기화한 후에, 처음에는 함수의 이름을 사용하고 그 다음에는 포인터를 사용하여 함수를 두 번 호출한다. 함수를 어떤 방법으로 호출하든지 결과는 같다.

<리스트 15.8> 함수를 호출하기 위해서 함수에 대한 포인터를 사용하는 프로그램

/* 함수에 대한 포인터 선언과 사용 예 */

#include

 

/* 함수 원형 */

 

double square(double x);

 

/* 포인터 선언 */

 

double (*p)(duble x);

 

main()

{

/* p가 square()를 지적하도록 초기화 */

 

p = square;

 

/* square()를 두 가지 방법으로 호출 */

printf("%f %f", square(6.6), p(6.6));

return(0);

}

 

double square(double x)

{

return x * x;

}

 

=> 7번째 줄에서는 square()를 선언한고 ,11번째 줄에서는 double형 인수를 받아들이고 double값 을 돌려주며 square()의 선언문과 일치하는 함수에 대한 포인터 p를 선언한다. 17번째 줄에 서는 포인터 p를 square로 설정한다. square나 p에서 괄호가 사용되지 않았다는 것에 주의하자. 20번째 줄은 square()와 p()의 호출에서 복귀되는 값을 출력한다.
괄호를 포함하지 않는 함수의 이름은 함수에 대한 포인터이다. 이것은 배열의 경우와 비슷하다. 함수에 대한 포인터를 선언하고 사용하는 것과 어떤 차이점이 있을까? 함수의 이름 자체는 포인터 상수로 변경이 불가능하다. 이 사실은 배열에서와 같다. 그러나 포인터 변수는 변경할 수 있다. 특히, 필요할 때마다 다른 함수를 지적하도록 설정할 수 있다.

<리스트 15.9>에 있는 프로그램은 함수에 정수형 값을 인수로 전달하여 함수를 호출한다. 전달되는 인수의 값에 따라 함수는 포인터가 세 가지 다른 함수의 하나를 지적하도록 초기화하고 나서 대응하는 마수를 호출하기 위해서 포인터를 사용한다. 이런 세 함수의 각각은 화면 상에 독특한 메시지를 출력한다.

<리스트 15.9> 상황에 따라 다른 함수를 호출하기 위해서 함수에 대한 포인터를 사용하는 프로그램

/* 서로 다른 함수를 호출하기 위한 포인터의 사용 예 */

#include

 

/* 함수 원형 */

 

void func1(int x);

void one(void);

void two(void);

void other(void);

 

main()

{

int a;

 

for(;;)

{

puts("\nEnter an integer between 1 and 10, 0 to exit: ");

scanf("%d", &a);

 

if(a == 0)

break;

func1(a);

}

return(0);

}

 

void func1(int x)

{

/* 함수에 대한 포인터 */

 

void(*ptr)(void);

 

if(x == 1)

ptr = one;

else if(x == 2)

ptr = two;

else

ptr = other;

 

ptr();

};

 

void one(void)

{

puts("You entered 1.");

}

 

void two(void)

{

puts("You entered 2.");

}

 

void other(void)

{

puts("You entered something other than 1 or 2.");

}

 

=> 이 프로그램의 16번째 줄에서는 0의 값이 입력될 때가지 프로그램을 계속 실행하기 위해서 무한 루프를 사용하고 있다. 0이 아닌 값이 입력될 때 입력된 값은 func1()에 전달된다. func1()의 32번째 줄에 나타나 있는 함수에 대한 포인터 ptr의 선언문을 주의해서 살펴보자. 이 선언문은 ptr이 func1()에 대해서 지역 변수의 상태가 되도록 해주는데, 프로그램의 다른 부분에서는 이 포인터를 사용할 필요가 없으므로 이렇게 지역 변수로 선언하는 것이 좋다. func1()의 34번째 줄부터 39번째 줄까지는 입력된 값에 따라 ptr을 특정 함수로 설정한다. 41번째 줄은 특정 함수에 설정된 ptr()을 호출한다. 물론, <리스트 15.9>에 있는 프로그램은 예제로 사용하기 위한 것이다. 이 프로그램에서는 함수에 대한 포인터를 사용하지 않고도 쉽게 동일한 결과를 얻을 수 있을 것이다. 이제, 여러 가지 함수를 호출하기 위해서 함수의 인수로 포인터를 전달하여 사용하는 방법을 알아보도록 하자. <리스트 15.10>에 있는 프로그램은 <리스트 15.9>를 수정한 것이다.

<리스트 15.10> 함수에 대한 포인터를 인수로 전달하기

/* 함수에 대한 포인터를 인수로 전달하기 */

#include

 

/* 함수 원형. 함수 func1()은 어떤 인수도 받아들이지 않고 복귀값을 가지지 않는 함수에

대한 포인터를 하나의 인수로 받아들인다. */



void func1(void (*p)(void));

void one(void);

void two(void);

void other(void);

 

main()

{

/* 함수에 대한 포인터 */

 

void (*ptr)(void);

int a;

 

for(;;)

{

puts("\nEnter an integer between 1 and 10, 0 to exit: ");

scanf("%d", &a);

 

if(a == 0)

break;

else if(a == 1)

ptr = one;

else if(a == 2)

ptr = two;

else

ptr = other;

func1(ptr);

}

return(0);

}

 

void func1(void (*p)(void)

{

p();

}

 

void one(void)

{

puts("You entered 1.");

}

 

void two(void)

{

puts("You entered 2.");

}

 

void other(void)

{

puts("You entered something other than 1 or 2.");

}

 

=> <리스트 15.9>와 <리스트 15.10>의 차이점을 주의해서 살펴보자. 함수에 대한 포인터의 선언문은 main()의 18번째 줄로 이동되었다. 이제 main()의 26번째 줄부터 33번째 줄까지는 사용자가 입력한 값에 따라 포인터가 정확한 함수를 지적하도록 초기화하고 나서 초기화된 포인터를 func1()에 전달한다. <리스트 15.10>에서는 함수 func1()이 어떤 특별한 동작을 수행하지 않고 있다. 단지 ptr이 지적하는 함수를 호출한다. 이 프로그램도 또한 예제로 사용된 것이다. 여기서 설명하는 내용은 잠시 후에 설명할 것과 같은 실용적인 프로그램에 응용될 수 있다.

함수에 대한 포인터를 사용하는 프로그래밍 작업의 한 가지 예는 정렬 작업이 필요한 경우이다. 프로그램에서는 가금 여러 가지 정렬 방식을 사용하기 원할 것이다. 예를 들어, 한 번은 알파벳 순서로 데이터를 정렬하고, 한 번은 알파벳의 역순으로 정렬하기 원할 수 있다. 함수에 대한 포인터를 사용하면 프로그램은 정확한 정렬 함수를 호출할 수 있다. 더욱 정확히 말하자면, 일반적으로 여러 가지 비교 함수를 호출하여 사용할 수 있는 것이다. <리스트 15.7>에 있는 프로그램을 다시 한 번 살펴보자. sort() 함수의 실제 정렬 순서는 라이브러리 함수 strcmp()가 돌려주는 값에 의해서 결정된다. 이 함수는 프로그램에서 사용되는 문자열이 다른 하나의 문자열보다 '작은지' 또는 '큰지'의 여부를 알려준다. 만약 A가 Z보다 작은 것으로 평가되는 알파벳 순서에 의해서 정렬 동작을 수행하는 하나의 함수와 Z가 Z보다 큰 것으로 평가되는 알파벳 역순에 의해서 정렬 동작을 수행하는 하나의 함수를 작성한다면 어떨까? 프로그램은 사용자에게 어떤 순서로 정렬하기 원하는지 결정하도록 요구하고, 정렬 함수는 포인터를 사용하여 적절한 비교 함수를 호출할 수 있다. <리스트 15.11>은 <리스트 15.7>에 있는 프로그램을 수정하고 이런 기능을 결합시킨 것이다.

<리스트 15.11> 여러 가지 정렬 방식을 사용하기 위해서 함수에 대한 포인터를 사용하는 프로그램

/* 키보드에서 문자열을 읽어들이고 오름차순이나 내림차순으로 정렬하여 화면에

출력한다. */

 

#include

#include

#include

 

#define MAXLINES 25

 

int get_lines(char *lines[]);

void sort(char *p[], int n, int sort_type);

void print_strings(char *p[], int n);

int alpha(char *p1, char *p2);

int reverse(char *p1, char *p2);

 

char *lines[MAXLINES];

 

main()

{

int number_of_lines, sort_type;

 

/* 키보드에서 여러 줄을 읽어들인다. */

 

number_of_lines = get_lines(lines);

 

if(number_of_lines < 0)

{

puts("Memory allocation error");

esit(-1);

}

 

puts("Enter 0 for reverse order sort, 1 for alphabetical: ");

scanf("%d", &sort_type);

 

sort(lines, number_of_lines, sort_type);

print_strings(lines, number_of_lines);

return(0);

}

 

int get_lines(char *lines[])

{

int n = 0;

char buffer[80]; /* 각 줄에 대한 임시 저장 공간 */

 

puts("Enter one line at a time; enter a blank when done.");

 

while(n < MAXLINES && gets(buffer) != 0 && buffer[0] != '\0')

{

if((lines[n] = (char *)malloc(strlen(buffer)+1)) == NULL)

return -1;

strcpy(lines[n++], buffer);

}

return n;

 

} /* get_lines()의 끝 */

 

void sort(char *p[], int n, int sort_type)

{

int a, b;

char *x;

 

/* 함수에 대한 포인터 */

 

int(*compare)(char *s1, char *s2);

 

/* 포인터가 인수 sort_type에 따라 적절한 비교 함수를 지적하도록 초기화한다. */



compare = (sort_type) ? reverse : alpha;

 

for(a = 1; a < n; a++)

{

for(b = 0; b < n - 1; b++)

{

if(compare(p[b], p[b+1]) > 0)

{

x = p[b];

p[b] = p[b+1];

p{b+1] = x;

}

}

}

} /* sort()의 끝 */

 

void print_strings(char *p[], int n)

{

int count;

 

for(count = 0; count < n; count++)

printf("\n%s", p[count]);

}

 

int alpha(char *p1, char *p2)

/* 알파벳 여순 비교 */

{

return(strcmp(p1, p2));

}

 

=> main()의 32번째 줄과 33번째 줄은 어떤 정렬 방식을 사용하기 원하는지 묻는다. 선택된 정렬 순서는 sort_type에 저장된다. 이 값은 <리스트 15.7>에서 설명했던 다른 값과 함께 sort() 함수에 전달된다. sort() 함수는 약간 변경되었다. 64번째 줄은 두 개의 문자형 포인터(문자열)를 인수로 받아들이는 함수에 대한 포인터 compare()를 선언한다. 69번째 줄에서는 sort_type의 값을 기본으로 하여 compare()를 리스트에 추가된 두 가지 새로운 함수의 하나로 서정한다. 두 개의 새로운 함수는 alpha()와 reverse()이다. alpha()는 <리스트 15.7>에서 사용했던 것과 같은 라이브러리 함수 strcmp()를 사용한다. reverse()는 역순으로 정렬을 수행하기 위해서 전달된 매개 변수의 위치를 변경하여 사용한다.

 

 

 

 

'pc관련 > C언어' 카테고리의 다른 글

C언어_디스크파일의 사용  (0) 2019.04.11
C언어_링크리스트  (0) 2019.04.07
고급 프로그램제어문  (0) 2019.04.03
C언어_변수에 범위  (0) 2019.03.28
C언어_구조체  (0) 2019.03.25
Posted by 둥이파파^^
etc.2019. 4. 3. 21:55

[A]

 

A piece of cake. 식은 죽 먹기지요
Absolutely. 절대적으로 그렇지요
After you. 먼저 가시지요
Always. 항상 그렇지요
Amazing. 신기 하군요
And then? 그리고 나서는요?
Any good ideas? 어떤 좋은 생각 있어요?
Any time. 언제라도요
Anybody home? 집에 누구있어요?
Anything else? 그 밖에 뭐 있어요?
Are you in line? 당신은 줄에 서 있어요?
Are you kidding? 당신 농담이에요?
Are you serious? 당신은 심각 해요?
At last. 드디어
Attention, please! 좀 주목 해 주세요
Awesome! 와우~ 멋지다

[B]
Back me up. 나를 지원해 주세요
Be my guest. 사양하지 마세요
Be patient. 좀 참으세요
Be punctual! 시간좀 맞춰
Be right back with you. 곧 당신에게 돌아 올께요
Be seated. 앉으세요
Beat it. 이자리에서 꺼져
(Beer), please. (맥주) 주세요
Behave yourself. 행동자제를 하세요
Better late than never. 늦는 것이 안 하는 것보다 낫지요
Better than nothing. 없는 것 보다 낫지요
Boy! It hurts. 야, 그것 아픈데
Break it up. 그만 싸워요

[C]
Call me Sam, please. 샘이라고 불러 주세요
Can I get a ride? 나를 태워다 줄 수 있어요?
Can you hear me now? 지금 나와 이야기 할 수 있어요?
Can't argue with that. 그것에 대해서 왈가왈부 할 필요가 없지요.
Can't be better than this. 이것보다는 좋을 순 없지요
Cash or charge? 현찰이요 아니면 달아 놓을 까요?
Catch you later. 나중에 보자구요.
Certainly. 확실히 그렇지요.
Charge it please. 대금을 크레디 카드에 달아 놓으세요
Check it out. 이것을 확인해 보세요
Check, please. 계산서 좀 주세요
Cheer up! 기운을 내세요
Cheers! 건배
(Coffee), please. (커피) 주세요
Come and get it. 와서 가지세요 와서 먹어요
Come on in. 들어 오세요
Come on. 설마
Congratulations! 축하 합니다
Could be. 그럴 수도 있겠지요
Couldn't be better then this. 이보다 더 좋을 순 없어

[D]
Definitely. 확실히 그렇지요
Delicious! 맛있어요
Depends. 경우에 따라 다르지요
Did you get it? 알아 들었어요?
Didn't I make myself clear? 제 입장을 확실하게 말하지 않았나요?
Disgusting! 기분 나빠. 재수 없어
Do I know it? 저도 압니다. 누가 아니래요?
Do I look all right? 제가 괜찮아 보여요?
Do you follow me? 내말 알아 듣겠어요?
Do you have everything with you? 모든 것을 가지셨나요?
Do you? 당신은요?
Doing okay? 잘 하고 있어요?
Don’t get too serious. 너무 심각하게 그러지 말아요
Don’t miss the boat. (보트를 놓치듯이) 기회를 놓지지 마세요
Don’t press (push) your luck. 너무 날 뛰지 마세요 (행운을 밀지 말아요)
Don't ask. 묻지 말아요
Don't be a chicken. 너무 소심하게 굴지 말아요. 너무 겁먹지 마
Don't be afraid. 두려워 하지 마세요
Don't be foolish. 멍청하게 굴지 말아요
Don't be modest. 겸손해 하지 말아요
Don't be shy. 부끄러워 하지 마세요
Don't be silly. 싱겁게 놀지 말아요
Don't bother. 신경쓰지 마세요
Don't bother me. 나를 괴롭게 하지 말아요
Don't change the subject! 화제를 다른데로 돌리지 마요
Don't get into trouble. (Stay out of trouble.) 사고 치지마
Don't get upset. 너무 화 내지 말아요
Don't mess with me. 날 함부로 대하려고 하지 말아요.
Don't let me down. 나를 실망시키지 말아요
Don't make me laugh. 나를 웃게 하지 말아요
Don't push me! 너무 강요 하지 말아요
Don't push (press) your luck! 행운을 밀어 내지 마세요. 너무 까불지 마세요
Don't push! 밀지 말아요.
Don't worry about it. 걱정하지 말아요
Drive safely! 안전하게 운전해요

[E]
Easy does it. 천천히 해요. 천천히 하는 것이 잘 하는 거에요
Either will do. (Anything will do.) 둘중에 어떤 것이든 되요 (어떤 것이든 되요)
Enjoy your meal. 맛있게 드세요
Enough is enough. 충분 하니까 이제 그만 해요
Exactly. 정확하게 맞어요
Excellent! (Super!) 잘 했어요
Excuse me. 실례합니다

[F]
Far from it. 아직 멀었지요
Fifty-fifty. 50:50 입니다.
Follow me. 따라 오세요
For good? 영원히?
For what? 왜? 무엇을 위해서요?
Forget it. 그것에 대해서는 잊어 버리세요. 신경꺼요.

[G]
Get in the line. 줄을 서세요
Get lost! 당장 꺼져 버려
Get off my back. (등에 업혀 있지 말고) 이제 나를 고만 괴롭혀요
Get real! 현실적이 되세요. 냉정해 지세요
Get the picture? 이제 뭔가 그림이 보이세요?
Give it a rest. 이제 그만 두세요. (이만 좀 쉬세요)
Give it a try. 노력 해 보세요
Give me a call. 제게 전화 주세요
Gladly. 기꺼이 하지요
Go ahead. 어서 그렇게 하세요
Go fifty-fifty. 반반 나누어 내지요
Go for it. 그것을 한번 해 보시지요. 노력 해 보시지요
Go get it. 가서 가지세요
Go on, please. 어서 계속 하세요
Going down? 내려 가세요?
Going up? 올라 가세요?
Good enough. 그 정도면 충분 합니다. 좋습니다
Good for you. 당신에게 좋은 일이지요
Good luck to you! 당신에게 행운을 빕니다
Good luck. 행운을 빕니다
Good talking to you. 당신과의 대화는 즐거 웠어요
Grow up! 좀 철좀 들어라
Guess what? 뭔지 알아 맞추어 봐요

[H]
Hang in there. 좀 견디어 봐요
Hang loose. 좀 편히 쉬고 있어요.
Hang on! 잠깐 기다리세요
Have a nice day. 나이스한 (좋은) 날 되세요
Have fun! 재미있게 지내세요
He didn't show up. 그 는 나타나지 않았어요
He is history to me. 그 는 나에게 지난 일이에요
Help me! 도와 주세요
Help yourself. 마음껏 하세요
Here is something for you. 여기 작은 선물 받으세요
Here you are. 여기에 있어요
Hi ! 안녕
Hold it ! 움직이지 마요
Hold on. 잠깐 기다리세요
How about you? 당신은 어때요?
How big is it? 얼마나 큰데요?
How come? (Why?) 왜요?
How do you like here? 여기 좋아 하세요?
How have you been? 그 동안 어떻게 지냈어요?
How many times do I have to say? 몇번이나 말해야 알겠어요?
How many? 수가 얼마지요?
How much? 양이 얼마지요?
How was your trip (vacation)? 여행 (휴가)는 어땠어요?
How? 어떻게?
How's everything? 모든 것이 어떻세요?
How's work? 일은 어때요?
How's you family? 가족은 잘 있어요?

[I]
I agree. 동의합니다
I am (deeply) touched. 감동 정말 되었어요
I am a little disappointed. 좀 실망했어요
I am all set. 난 모든 준비 완료
I am aware of that. 그것을 파악하고 있습니다
I am back. 저 돌아 왔습니다
I am broke. 나는 무일품입니다
I am coming. 지금 가요
I am crazy about her. 나는 그녀에 빠졌어요
I am exhausted. 난 기진맥진입니다
I am fed up with this. 이것에 진저리가 났어요
I am free. 한가 합니다
I am full. 배불러요
I am getting hungry. 배가 슬슬 고파 오는데요
I am going to miss you. 나는 너를 그리워 할 거야
I am impressed. 인상이 좋았어요. 감동 받았어요.
I am in a hurry. 좀 바쁩니다
I am in need. 궁색 합니다
I am nearsighted. 근시입니다
I am on duty. 근무중입니다
I am scared to death. 난 무서워 죽겠어요
I am serious. 난 진심이에요
I am short-changed. 잔돈이 모자라는데요
I am single. 나는 미혼입니다
I am sorry. 미안해요
I am starving to death. 배가 고파 죽겠네여
I am stuffed. 배가 부르네요
I am upset. 화가 납니다
I bet. 내기를 할정도로 자신있다
I can tell. 그렇게 말할 수 있어요. 그렇게 보이는데요
I can handle it. 내가 다룰 수 있어요
I can not handle it anymore. 난 더 이상 다룰 수 가 없어요
I can’t afford that. (주로 재정적으로) 그것을 감당 할 수 없어요
I can’t help it. 어쩔수 없어요
I can't say for sure. 확실히는 말 못 하겠어요
I can't stand it. 견딜 수 가 없군
I can't thank you enough. 너무 감사해서 뭐라고 할말이 없네요
I didn't mean to. (I didn't mean it.) 난 그렇게 할 의도는 아니었어요.

(나는 그것을 뜻 한 것은 아니었어요)
I don’t believe it. 난 그것을 믿지 않아요
I don't care. 상관하지 않아요
I don't get it. 이해를 못하겠네
I don't like it. 난 좋아 그것을 좋아 하지 않아요
I doubt it. 의심이 가는데요 그렇지 않게 생각 하는데요
I fee the same way. 저도 같은 느낌입니다
I get it. 난 알았어요
I got lost. 난 길을 잃었어요
I have got to go now. 난 가야 겠어요
I have had enough. I quit. 난 이제 진저리가 나요. 그만 둘래요
I hardly know him. 나는 그 사람을 잘 모릅니다
I hate to eat and run but ... 먹자마자 가기는 싫지만…
I have a long way to go. 난 갈길이 멀었지요
I have no appetite. 난 식욕이 없네요
I have no clue. 난 아이디어가 전혀 없네요
I have no energy. 나는 에너지가 없어요
I have no idea. 난 별 생각이 없네요
I have no time. 나는 시간이 없어요. 바쁘네요
I haven't got all day. 제가 지금 시간이 없어요. 좀 빨리좀 해 주세요
I hear you loud and clear. 잘 들고 있습니다.
I know what! 뭔가 알아요. 뭔가 아이디어가 있어요.
I love it. 난 그것을 좋아해
I made it. 그것을 달성 해냈다
I mean it. 정말입니다. 농담아니에요.
I owe you one . 신세를 지네요
I see. 알겠습니다
I still love you. 나는 너를 아직도 사랑해
I swear to God. 난 하나님한테 맹세 합니다
I taught myself. 난 고학 했습니다
I was lucky. 내가 행운이었지요
I was told that. (누군가 나에게) 그것을 말해 주었어요. 그렇게 들었어요
I will be in touch. 제가 연락을 할께요
I will do it for you. 제가 해 드리지요
I will drink to that. 그것에 동감 입니다
I will get it. (전화등을) 제가 받을 께요
I will miss you. 난 너를 그리워 할거야
I will never make it on time. 내가 제시간에 가기는 틀렸군
I wouldn't say no. 아니라고는 말하지 않을께여
I'm coming. 가요, 갑니다
In a sense, he is nothing but a suit. 어떤 면에서는 그는 헛깨비 지요
Incredible. 신뢰가 안가는 (군요)
Is that all? 그게 전부에요?
It is chilly. 날이 쌀쌀 하네
It is humid. 후덥지근 하네
It is muggy 날이 찌프듯 하네
It is out of style. 유행이 아니네요.
It is painful for me. 나에겐 아픈 (슬픈) 일입니다
It is time for lunch. 점심식사할 시간입니다
It is time to go. 갈 시간 입니다
It is windy. 바람이 부네
It makes sense. 이해가 되네요
It takes time. 시간이 걸립니다
It’s for you. 여기요 전화 왔어요
It’s not fair. (It's unfair) 불공평 합니다
It's all right. 괸 찮습니다
It's beautiful. 아름 답군요
It's cool. (Cool) (세련되어 보이네요) 멋있네요
It's free. 공짜 입니다
It's freezing. 얼어 붙네
It's my fault. (It's not my fault) 내 잘못 이지요 ( 내 잘못이 아닙니다.)
It's all your fault. 모든게 네 잘 못이야
It's my pleasure. 제게 기쁨입니다
It's my turn. 이번에 내 차례입니다
It's now or never. 지금이던지 아디던지 입니다. (지금이 절호의 기회입니다.)
It's on me. It's on the house. 이건 제가 쏘는 겁니다 이것은 주인집에서 그냥 주는 겁니다
It's really bad. 아주 나빠요
It's tough. 터프 하네요. (힘들군요)
It's your turn. 당신 차례입니다

[J]
Just about. 거의
Just kidding. 그냥 농담이에요
Just looking. 그 냥 보는 거에요
Just a moment. 잠깐 만요

[K]
Keep an eye on this, will you? 이것좀 봐줘여, 그렇래요?
Keep going. 계속 가세요
Keep in touch. 계속 연락해요
Keep it confidential. 대외 비밀로 해 주세요
Keep it to yourself. 당신만 알고 계세요. (비밀로 해 주세요)
Keep looking. 계속해서 찾아 봐요
Keep out of my way. 제 길을 막지 마세요
Keep the change. 잔돈을 가지세요
Keep your chin up! 고개를 드세요. 낙담 하지 마세요 기운을 내요
Knock it off. 그만 두세요

[L]
Large or small? 큰거요 아니면 작은 거요
Let it be! 그렇게 되도록 두지요.
Let me see… 자 어떻게 된건지 보자
Let me think about it. 그것에 대해서 좀 생각 해 봅시다
Let's give him a big hand. 그에게 큰 박수를 보냅시다
Let's call it a day. 오늘은 이것으로 마칩시다
Let's eat out. 자, 외식 하지요
Let's get down to business. 이제 일을 시작 하지요
Let's get together sometime. 언제 같이 모여 보지요
Let's go over it one  more time. 자 한번 더 살펴 보지요
Let's see. 좀 봅시다
Let's split the bill. 나누어서 내지요
Let's try. 한번 해보지요
Look who's here. 아니 이게 누구야
Lucky you! 자네 운이 좋았어

[M]
Make a way! 길을 비켜 주세요
Make mine well done. 내것은 잘 익도록 해줘요
Make that two, please. 그것을 2 개로 해 주세요
Make yourself at home. 집처럼 편하게 하세요
Many thanks in advance. 미리 감사 드려요
Many thanks. 정말 고마워요
May I interrupt you? 제가 좀 실례를 해도 될까요?
Maybe. 그럴지도 모르지요
Maybe not. 그렇지 않을지도 모르지요
Maybe some other time. 다른 때 해 보자구요.
Me, too. 나도 그래
Money talks. 돈이 만사를 좌우해
Most likely. 아마도 그렇 것입니다
My pleasure. 제 기쁨입니다

[N]
Never better. 아주 좋아요. 최고에요.
Never mind. 신경쓰지 않아도 되요
Never say die. 죽는다는 소리 마라
Never too late. 언제나 늦지 않습니다
Next time. 다음번에
Nice meeting you. 만나서 반가워요
Nice talking to you. 좋은 대화 였어요
No kidding. 설마 농담이겠지
No problem. (No sweet) 문제가 아니네요
No sweat. 문제 없어요
No way. 절대 안되요
No wonder. 어쩐지 그렇더라
Not a chance. 기회가 없어요 (절대 안되지요)
Not bad. 나쁘지 않은데요 ( 그런대로 좋군요)
Not really. 그렇지는 않아
Not too good. (Not too bad) 썩 좋지가 않네요 ( 썩 나쁘지 않네요)
Nothing much. 별거 없어
Nothing new. 새로운 것은 없어요
Nothing new about that. 그것에 대해선 새로운게 없어요
Now what? 자 이제는 뭐죠?
Now you are talking. 이제사 바르게 말을 하시는군요

[O]
Occupied. 사용중
Oh, dear! 아니 저런
Okay. 그래. 알았어요.
Okeydokey ( 가까운 사이에서만 사용) 좋아요
On the contrary. 반대로
Once in a blue moon. 아주 가끔요
Ouch! 아야
Out of question. 질문의 여지가 없습니다 (불가능 합니다)

[P]
Pick it up! 주어세요
Please enjoy yourself. 좀 즐겁게 지내세요
Please relax. 좀 느긋해 지세요
Please! 제발
Poor thing. 안스러워요
Pretty good! 정말 좋지요
Really? 정말이에요?
Relax. 좀 느긋해져요.

[S]
Same here. 저도 동감입니다
Same to you. 당신도요
Say cheese! 치즈라고 말하세요
Say hello for me. 나대신 안부 전해줘요
Say that again? 다시 말씀 해 주실래요?
Say when. ( 그만 하기를 원할때 ) when 이라고 하세요
See you later! (Later!) 나중에 봐요
See you. 나중에 봐요
Serious? 진심에요?
Shame on you. 창피 한줄 아세요
She is my style. (She is not my style.) 그녀는 내 타입이에요

(그녀는 내 타입이 아니에요)
She is very sophisticated. 그녀는 매우 세련되었어요
Shoot! 어서 말해 봐요
Skip it! 다음으로 넘어 가요
So much for that. 이제 그일은 그만 하지요
So soon? 그리 빨리?
So what? 그래서 어떻다는 겁니까?
Sold out. 팔렸어요
Something's fishy. 뭔가 이상한데
Something's never changed. 어떤 것은 정말 안변하는 군
Sorry to bother you. 번거롭게 해서 죄송 합니다
Sorry? (누구의 말을 잘못 이해했을 때) 뭐라구 하셨지요?
Sounds good. 듣기에 좋군요
Speak out. 말좀 크게 하세요
Speaking. 말하세요
Speaking Spanish? 서반어어 하세요?
Stay cool. 진정해요
Stay longer. 좀더 계시지요.
Stay out of trouble. 말썽을 부리지 말아요
Stick around. 옆에 있어 보세요
Stick with it. 표기 하지말고 계속 해 봐요.
Stop complaining. 불평좀 그만 하시지요
Suit yourself! 좋은 대로 하세요
Super. 잘 하는 군요
Sure. 물론
Sure thing. 확실한 것이지요
Sweet dreams. 즐거운 꿈 꾸세요



[T]
Take a guess. (Can you guess?) 맞춰 보세요
Take care! 조심하세요 잘가: 떠날 때
Take my word for it. 그것에 대해서는 내 말을 따라요
Take your time. 천천히 하세요
Tell me about it. 그것에 대해서 한번 말해 보세요
Thank God. 하나님 감사 합니다
Thanks for calling. 전화 주셔서 감사 해요
Thanks for everything. 여러가지로 고마워요
Thanks for the compliment. 칭찬해 주셔서 감사 합니다
Thanks for the ride. 차를 태워다 주어서 고마워요
Thanks, but no thanks. 감사해요, 그러나 사양해요
That depends. 그야 경우에 따라서 이지요
That figures. 알겠네요
That happens. 그런일이 일어나지요
That should help. 도움이 될 것입니다
That sounds good. 듣기에 좋군요
That will be the day. 그렇게 되면 오죽 좋겠어요
That's a steal. 거저 가져 가는 셈이지요 쌉니다
That's all right. 그냥 되었어요
That's all there is to it. 그렇게 하면 되는 그게 전부야
That's all? 그게 전부에요?
That's enough about that. 그 것은 그정도로 충분합니다
That's enough. 이제 되었어요
That's good. 잘 되었어요
That's hard to say. 말하기 곤란 한데요
That's it. 바로 그거야
That's a nice surprise! 이거 뜻밖인데요
That's not fair.(That's unfair) 불공평 합니다
That's right. 맞습니다
That's the way to go. 바로 그겁니다
That's what I mean. 그게 제가 말하는 것이지요
There you are. 여기 있습니다
Things will work out all right. 일이 잘 될 것입니다
This is just between you and me. 우리들 끼리의 비밀입니다
This is not much. 약소 합니다
This is urgent. 긴급입니다
This one ? 이것 말이에요?
Time will tell. 시간이 말해 줄것입니다
Time's up. 이제 시간이 되었어요
Too bad! 안 되었군요
Too expensive. 너무 비싸네
To the best of my knowledge~ 내가 알기로는~
Trust me. 나를 믿으세요
Try again. 다시 해 보세요

[U]
Uh-uh 오오 아닌데요
Unbelievable. 믿을 수가 없네
Up to here. (목까지 손으로 대어 보이면서)폭발 일보전이다
Up, or down? 올라가요? 아니면 내려가요?

[W]
Wait a minute. 잠시만 기다리세요
Watch out! 위험해, 주의해요
Watch your language. 말 조심해요
We are in the same boat. 우리는 같은 처지/운명이지요
Welcome home! 집에 온것을 환영합니다
Well done. 잘 했어요
What a nerve! 뻔뻔 하군요
What a relief! 이제 맘이 놓인다
What a shame. 이게 무슨 창피한 노릇인가?
What about it? 그게 어떤데요?
What about you?(What about me?) 당신은 어때요? (나는 어때요?)
What brings you here. 어떻게 오셨지요?
What did you say? 뭐라구요?
What do you do? 직업이 뭐지요?
What do you know? 무엇을 알고 있지요?
What do you mean? 무슨 의미지요?
What do you say? 뭐라고 하실래요? 어떠세요?
What do you think of it? 이것에 대해서 뭐라고 생각 하세요?
What do you think? 무엇이라고 생각 하세요?
What for? (For what?) 뭐 때문이지요?
What is it? 무슨 일이지요?
What makes you say that? 무슨 근거로 그렇게 말 하세요?
What time is it? 몇시지요?
What? 뭐라구요?
What’s it called? 그것을 뭐라고 부르지요?
What’s today's special? 오늘 특선 요리가 뭐지요?
Whatever you say. 뭐라고 하시던지요
What's happening? 어떻게 지내요?
What's new? 그동안 새로운 거 있었어요?
What's the big deal? 뭐가 그 난리에요?
What's the point? 요점이 뭐지요?
What's up? 어떠세요?
What's wrong? 뭐가 문제야요?
When? 언제?
Where are we? 우리가 어디에 있지요?
Where did you stay? 어디에 머물렀지요?
Where do you live? 어디에 사세요?
Where is a drugstore? 약국이 어디에 있지요?
Where to ? 어디로?
Which one ? 어느 것이요?
Who cares! 알게 뭐야 상관하지 않아
Who is it? 누구시지요?
Who knows? 누가 알겠어
Who's there? 거기 누구죠?
Who's calling? (전화를 받으면서) 누구시지요?
Why didn't I think of that? 왜 그걸 생각 못했지?
Why not? 왜 않되겠어/왜 않되는데 ?

Why? 왜 요?
Win-win situation. 둘다 이기는 셈이지요
With pleasure. 기쁨으로 해 드리지요
Would you like some? 좀 해 볼래요?
Wow! 와우

[Y]
Yeah. Yes 네,
Yes and no. yes 나 no 라고 할 수 없네요
You are a lucky duck. 당신은 행운아 입니다
You are driving me crazy. 나를 신경질 나게 만드네요
You are getting better. 당신은 점점 좋아지네요
You are soaked! 흠뻑 젖었군요
You are teasing me. 나를 놀리시는 군요
You're telling me. (당신이 말 안해도 ) 안 들어도 알고 있어요
You are too much. 당신 너무 하는 군요
You bet. (내기를 해도 좋울 만치 좋을) 틀림 없어요 물론이지요
You bet? 내기 할래?
You cannot fool me. 날 속이지는 못하지요
You can say that again. 지당한 말씀이지요
You first. 먼저 하세요
You flatter me. 칭찬이 과하시네요
You have a wrong number. 전화를 잘 못 거셨어요
You got it. 이해를 하셨군요
You have lost me. 저를 놓치셨어요. (제가 말을 놓쳤네요)
You look good. 좋아 보이네요
You must be crazy. 당신은 미쳤군요
You name it. 말씀만 하세요
You said it. 말한게 맞아요
You should get in shape. 몸을 좀 가꾸는게 좋겠는데요
You stay out of it. 넌 이것에 끼어 들지 마
You went too far this time. 이번엔 좀 과하셨군요
You win. 당신이 이겼어요
You're wasting your time. 당신은 당신의 시간만 낭비 하고 있어요
You're welcome. 천만에요


'etc.' 카테고리의 다른 글

생활영어 패턴 100가지~~  (0) 2019.04.18
재미있는 심리학 지식에 대해~~  (0) 2019.04.16
생활영어표현  (0) 2019.03.25
영어표현500문장  (0) 2019.03.17
세계 10대 슈퍼푸드  (0) 2019.03.10
Posted by 둥이파파^^
pc관련/C언어2019. 4. 3. 21:26

여섯번째 강의 "기본적인 프로그램 제어문"에서는 프로그램에서 다른 문장의 실행을 제어하는 C의 프로그램 제어문을 간단히 살펴보았다. 이 장에서는 지금까지 다루지 않았던 goto문과 프로그램의 순환문에서 사용할 수 있는 유용한 프로그램 제어문을 살펴보도록 하겠다. 오늘은 다음과 같은 내용을 배운다.

·break와 continue문을 사용하는 방법
·무한 루프는 무엇이고, 왜 필요한가?
·goto문을 사용하는 방법과 프로그램에서 goto문을 사용하지 않아야 하는 이유
·switch문을 사용하는 방법
·프로그램의 종료를 제어하는 방법
·프로그램이 종료될 때 자동으로 함수를 실행하는 방법
·프로그램 내에서 시스템의 명령을 실행하는 방법

1. 순환문을 미리 종료하는 방법
: 여섯번째 강의에서는 for문, while문, do...while문이 프로그램의 실행을 제어한다는 것을 배웠다. 이런 순환문은 프로그램의 조건에 따라 C의 문장이나 블록을 전혀 실행하지 않거나 한 번 실행하거나 또는 여러 번 실행한다. 이런 세 가지 순환문에서는 주어진 조건에 일치하는 상황이 발생하는 경우에만 순환문을 종료할 수 있다. 그러나 가끔 순환문의 반복 상태를 직접 제어할 수 있기를 원할 것이다. break와 continue문은 이런 순환문의 실행을 제어할 수 있게 해준다.

1.1 break문
: break문은 for문, while문 또는 do...while문 내에서만 사용할 수 있다. switch문에서도 사용되지만 자세한 내용은 이 장의 후반부에서 다시 설명할 것이다. 간단히 말해서 break문이 나타나면 순환문은 즉시 종료된다. 다음은 예제이다.

for(count = 0; count < 10; count++)
{
if(count == 5)
break;
}

일반적인 경우에 for문은 10번 실행될 것이다. 그러나 여기에서는 for문이 6번째 반복되기 전에 count가 5의 값을 가지므로 break문이 실행되고 for문이 종료된다. 제어는 for문 바로 다음에 있는 문장으로 전달될 것이다. 종속된 순환문에서 break문이 사용될 때에는 가장 내부에서 사용되는 순환문이 종료된다.

<리스트 13.1> break문 사용하기

/* break문의 사용 에 */

#include

 

char s[] = "This is a test string. It contains two sentences.";

 

main(0

{

int count;

 

printf("\noriginal string: %s", s);

 

for(count = 0; s[count] != '\n0'; count++)

{

if(s[count] == '.')

{

s[count = 1] = '\n';

break;

}

}

printf("\nModified string: %s\n", s);

 

return 0;

}

 

-> 출 력

Original string: This is a test string. It contains two sentences.
modified string: This is a test string.

1.2 continue문
: break문과 마찬가지로 continue문은 for문, while문, do...while문 내에서만 사용될 수 있다. continue문이 실행되면 제어는 순환훈의 마지막 부분으로 전달되고 다음 순환 동작이 시작된다. continue문과 순환문의 마지막 부분으로 전달되고 다음 순환 동작이 시작된다. continue문과 순환문의 마지막 부분 사이에 있는 문장은 실행되지 않는다. <리스트 13.2>에는 continue를 사용하는 프로그램이 나타나 있다. 이 프로그램은 키보드에서 문자열을 받아들이고 모든 소문자 형태의 모음을 제거하여 다시 출력한다.

<리스트 13.2> continue문 사용하기

/* continue문의 사용 예 */

#include

 

main()

{

/* 입력용 버퍼와 카운터 변수를 선언 */

 

char buffer[81];

int ctr;

 

/* 한 줄의 텍스트 입력 */

 

puts("Enter a line of text: ");

gets(buffer);

 

/* 소문자 모음이 아닌 문자만을 출력하며 문자열을 차례대로 사용 */

 

for(ctr = 0; buffer[ctr] != '\n0'; ctr++)

{

 

/* 문자가 소문자 모음이면 출력하지 않고 다음으로 진행 */

 

if(buffer[ctr] == 'a' || buffer[ctr] == 'e' || buffer[ctr] == 'i'

|| buffer[ctr] == 'o' || buffer[ctr] == 'u')

continue;

 

/* 모음이 아니면 출력 */

 

putchar(buffer[ctr]0;

}

return 0;

}

 

2. goto문
: goto문은 C에서 조건 없이 이동(unconditional jump)하거나 분기(branching)하는 명령의 하나이다. 프로그램에서 goto문이 나타날 때 제어는 즉시 goto문에 의해 지정된 위치로 이동하거나 분기한다. goto문이 실행될 때에는 조건 없이(unconditional) 항상 분기 동작이 발생한다. 분기 동작은 if문처럼 프로그램의 어떤 조건에 의해서 발생하는 것이 아니다. goto문과 목적지의 레이블은 서로 다른 블록에 존재할 수 있지만 동일한 함수 내에 존재하는 것이어야 한다. goto문의 사용 예를 보여주는 <리스트 13.3>의 간단한 프로그램을 살펴보자.

<리스트 13.3> goto문 사용하기

/* goto문의 사용 예 */

#include

 

main()

{

int n;

 

start: ;

 

puts("Enter a number between 0 and 10: ");

scanf("%d", &n);

 

if(n < 0 || n > 10)

goto start;

else if(n ==0)

goto location0;

else if(n == 1)

goto location1;

else

goto location2;

 

location0: ;

puts("You entered 0.\n");

goto end;

 

location1: ;

puts("You entered 1.\n");

goto end;

 

location2: ;

puts("You entered something between 2 and 10.\n");

 

end: ;

return 0;

}

 

goto문의 목적지는 프로그램 내에서 goto문의 앞이나 뒤에 위치될 수 있다. 앞에서도 언급했듯이 goto문에 대한 유일한 제한 사항은 goto문과 목적지가 같은 함수 내에 존재해야 한다는 것이다. 그러나 함수 내의 서로 다른 블록에 위치될 수 있다. for문과 같은 순환문에서 제어를 순환문의 바깥으로 이동시키기 위해서 goto를 사용할 수도 있지만 goto문을 이렇게 사용해서는 안된다. 그리고 프로그램에서는 가능하다면 goto문을 사용하지 않는 것이 좋은데, 두 가지 이유가 있다.

·goto문은 필요하지 않다. goto문을 사용해야 하는 프로그래밍 작업은 없다. goto문이 필요한 경우가 있더라도 항상 C에서 제공되는 다른 분기문을 사용하여 필요한 작업을 할 수 있다.

·goto문은 위험하다. goto문이 특별한 프로그래밍 문제에 대한 이상적인 해결 방법처럼 생각되겠지만 실제로는 전혀 그렇지 않다. goto문에 의해서 프로그램이 분기될 때에는 어디에서 분기되었는지 알 수 없으므로 프로그램이 혼란스러워질 수 있다. 이런 형태의 혼잡한 프로그래밍(spaghetti-code)은 좋지 않다. 이런 두 가지 사실을 알고 주의해서 프로그램을 작성한다면 goto문을 사용하더라도 문제가 없는 프로그램을 작성할 수 있을 것이다. 어떤 경우에는 goto문을 주의해서 사용한다면 프로그래밍 문제를 가장 간단한 방법으로 해결할 수도 있을 것이다 .그러나 goto문의 사용이 유일한 해결 방법은 아니다. 앞에서 설명한 두 가지 이유를 무시하더라도 goto문을 사용할 때에는 적어도 주의할 필요는 있을 것이다.

3. 무한 루프
: 무한 루프는 무엇이고, 프로그램 내에서 무한 루프가 필요한 이유는 무엇일까? 무한 루프는 실행을 마치는 상황이 발생하지 않고 계속해서 반복되는 순환문이다. 무한 루프는 for, while, do...while문이 될 수 있다. 예를 들어, 다음 문장은

while(1)
{
/* 다른 프로그램 문장들 */
}

무한 루프가 될 것이다. while네 주어진 조건은 항상 참으로 평가되고 프로그램이 실행되더라도 변경되지 않는 상수이다. 1은 결코 바뀌지 않는 값이므로 순환문은 절대로 끝나지 않을 것이다. 앞에서는 순환문을 벗어나기 위해서 break문을 사용할 수 있다는 것을 설명했다. break문을 사용하지 않는다면 무한 루프의 가치는 없다. 무한 루프는 break문과 함께 사용될 때 유용하다. 또한, 다음과 같이 for나 do...while을 사용하여 무한 루프를 생성할 수 있다.

for( ; ; )
{
/* 다른 프로그램 문장들 */
}
do
{
/* 다른 프로그램 문장들 */
} while(1);

이론적으로 이런 세 가지 형태의 순환문은 같은 것이다. 여기에서는 while문을 사용할 것이다. 무한 루프는 여러 개의 조건을 확인해서 순환문을 끝내야 하는지 결정하는데 사용할 수 있다 while문의 실행 조건이 입력되는 부분에 여러 개의 조건을 포함시키는 것은 어려울 것이다. 대신에, 순환문 내에서 개별적으로 여러 개의 조건을 확인해서 필요에 따라 break를 사용하여 종료하는 것이 더 낫다.

또한, 무한 루프는 프로그램의 동작을 지시하는 메뉴 체계를 구성하기 위해서 사용될 수 있다. 다섯번째 강의 "함수의 기본"에서는 프로그램의 main() 함수가 여러 가지 동작을 수행하는 다양한 함수의 실행을 지시하는 '교통 경찰'과 같은 역할을 수행하는 예를 살펴보았다. 프로그램의 사용자에게는 여러 가지 항목이 제공되고, 원하는 항목의 하나를 선택하여 필요한 동작을 수행할 수 있다. 또한, 이런 항목 중에는 프로그램을 종료하는 선택 사항이 포함되어야 한다. 일단 항목이 선택되면 선택된 항목에 따라 프로그램이 실행된다. <리스트 13.4>에 있는 프로그램은 메뉴 체계의 사용 예를 보여준다.

<리스트 13.4> 메뉴 체계를 사용하기 위한 무한 루프의 사용 예

/* 메뉴 체계를 구현하기 위한 무한루프의 사용 예 */

#include

#define DELAY 1500000 /* 지연에 사용되는 값 */

 

int menu(void0;

void delay(void);

 

main()

{

int choice;

 

while(1)

{

 

/* 사용자의 선택을 요구 */

 

choice = menu();

 

/* 입력에 따라 분기 */

 

if(choice == 1)

{

puts("\nExecuting choice 1.");

delay();

}

else if(choice == 2)

{

puts("\nExecuting choice 2.");

delay();

}

else if(choice == 3)

{

puts("\nExecuting choice 3.l");

delay();

}

else if(choice == 4)

{

puts("\nExecuting choice 4.");

delay();

}

else if(choice == 5)

{

puts("\nExiting program now...\n");

delay();

break;

}

else

{

puts("\nInvalid choice, try again.");

delay();

}

}

return 0;

}

 

/* 메뉴를 출력하고 사용자의 선택을 읽어들인다. */

int menu(void)

{

int reply;

 

puts("\nEnter 1 for task A.");

puts("Enter 2 for task B.");

puts("Enter 3 for task C.");

puts("Enter 4 for task D.");

puts("Enter 5 to exit program.");

 

scanf("%d", &reply);

 

return reply;

}

 

void delay(void)

{

long x;

for(x = 0; x < DELAY; x++)

;

}

 

4. switch문
: C에서 제공되는 가장 융통성 있는 프로그램 제어문은 프로그램 내에 포함되는 두 가지 이상의 값을 기본적으로 하여 여러 가지 문장을 실행하게 해주는 switch문이다. 지금까지 설명했던 if문과 같은 제어문에서는 단지 두 개의 값인 참이나 거짓으로 평가되는 수식만을 사용할 수 있었다. 두 개 이상의 값을 기준으로 해서 프로그램의 흐름을 제어하기 위해서는 <리스트 13.4>에 나타나 있듯이 여러 개의 종속된 if문을 사용해야 한다. switch문은 이런 종속문을 대신할 수 있다. switch문의 일반적인 형식은 다음과 같다.

switch(expression)
{
case template_1: statement(s);
case template_2: statement(s);

case template_n: statement(s);
default: statement(s);
}

여기서 expression은 long, int, char형과 같은 정수값으로 평가되는 수식이다. switch문은 expression을 평가하여 결과를 각각의 case 다음에 포함되어 있는 template과 비교하고 나서 다음과 같은 동작을 수행한다.

·expression의 결과와 template의 어떤 것이 일치한다면 해당 case에 포함되어 있는 문장이 실행된다.
·아무 것도 일치하지 않으면 선택적으로 사용되는 default에 포함되어 있는 문장이 실행된다
·아무 것도 일치하지 않고 default도 포함되어 있지 않다면 switch문의 바로 다음에 있는 문장이 실행된다.

<리스트 13.5>에는 사용자가 입력한 값에 따라 메시지를 출력하기 위해 switch문을 사용하는 간단한 프로그램이 나타나 있다.

<리스트 13.5> switch문의 사용 예

/* switch문의 사용 예 */

#include

 

main()

{

int reply;

 

puts("Enter a number between 1 and 5: ");

scanf("%d", &reply);

 

switch(reply)

{

case 1:

puts("You entered 1.");

case 2:

puts("You entered 2.");

case 3:

puts("You entered 3.");

case 4:

puts("You entered 4.");

case 5:

puts("You entered 5.");

default:

puts("Out of range, try again.");

}

return 0;

}

 

=> 분명히 무언가 잘못되었다는 것을 알 수 잇을 것이다. 여기서 switch문은 처음에 일치하는 템플릿(template)을 발견하고 해당 case에 있는 문장뿐 아니라 이후의 모든 문장들을 실행하고 있다. 잘못된 결과이기는 하지만 switch문은 실제로 이렇게 조건에 따라 정해진 동작을 수행한다. switch문은 값이 일치하는 템플릿으로 분기한다. 그러나 값이 일치하는 템플릿에 포함된 문장만 실행하기 위해서는 필요한 부분에 break문을 포함시켜야 한다. <리스트 13.6>에는 break문을 사용하여 다시 작성한 프로그램이 나타나 있다. 이제 프로그램은 정상적으로 동작할 것이다.

<리스트 13.6> 필요한 부분에 break문을 포함시켜서 정상적으로 실행되는 switch문의 사용예

/* switch문의 빠른 사용 예 */

#include

 

main()

{

int reply;

 

puts("Enter a number between 1 and 5: ");

scanf("%d", &reply);

 

switch(reply)

{

case 0:

break;

case 1:

{

puts("You entered 1.");

break;

}

case 2:

{

puts("You entered 2.");

break;

}

case 3:

{

puts("You entered 3.");

break;

}

case 4:

{

puts("You entered 4.");

break;

}

case 5:

{

puts("You entered 5.");

break;

}

default:

{

puts("Out of range, try again.");

}

} /* switch의 끝 */

return 0;

}

 

=> 프로그램을 컴파일하고 실행해보자. 정상적으로 동작할 것이다. switch문은 <리스트 13.4>에 나타나 있는 것과 같이 메뉴를 처리하는 경우에 가장 많이 사용된다. <리스트 13.7>에 있는 프로그램은 메뉴를 구현하기 위해 if문 대신에 switch문을 사용하고 있다. switch를 사용하는 것은 <리스트 1.34>에 나타나 있는 메뉴 프로그램의 이전 버전에서 사용되었던 종속된 if문을 사용하는 것보다 훨씬 낫다.

<리스트 13.7> 메뉴 체계를 구성하기 위한 switch문의 사용 예

/* 메뉴 체계를 구현하기 이한 무한 루프와 switch문의 사용 예 */

#include

#include

 

#define DELAY 150000

 

int menu(void);

void delay(void);

 

main()

{

while(1)

{

/* 사용자의 선택을 받아들이고 입력에 따라 분기 */

 

switch(menu())

{

case 1:

{

puts("\nExecuting choice 1.");

delay();

break;

}

case 2:

{

puts("\nExecuting choice 2.");

delay();

break;

}

case 3:

{

puts("\nExecuting choice 3.");

delay();

break;

}

case 4:

{

puts("\nExecuting choice 4.");

delay();

break;

}

case 5: /* 프로그램의 끝 */

{

puts("\nExiting program now...\n");

delay();

exit(0);

}

default:

{

puts("\nInvalid choice, try again.");

delay();

}

} /* switch의 끝 */

} /* while의 끝 */

return 0;

}

 

/* 메뉴를 출력하고 사용자의 선택을 받아들인다. */

int menu(void)

{

int reply;

 

puts("\nEnter 1 for task A.");

puts("Enter 2 for task B.");

puts("Enter 3 for task C.");

puts("Enter 4 for task D.");

puts("Enter 5 to exit program.");

 

scanf("%d", &reply);

 

return reply;

}

 

void delay(void)

{

long x;

for(x = 0; x < DELAY; x++)

;

}

 

=> 이 프로그램에서는 새로운 함수가 사용되고 있다. case 5:에 포함된 48번째 줄의 라이브러리 함수 exit()를 주목하기 바란다. 여기에서는 <리스트 13.4>와 같이 break문을 사용할 수 없다. 여기서 break를 사용하면 무한 루프인 while문을 벗어나는 것이 아니라 switch문을 벗어나게 된다. 잠수 후에 설명하겠지만 exit() 함수는 프로그램 자체를 종료하는 기능을 가진다. 가끔 switch를 구성하는 여러 개의 항목을 동일하게 '처리'하는 것이 유용할 때가 있다. 예를 들어, 여러 가지 항목에서 어떤 것을 선택하든지 특정 문장을 실행할 필요가 있다고 하자. 이 때에는 break문을 사용하지 말고 필요한 문장 앞에 모든 case 템플릿을 입력하면 된다. 만약 조건 수식이 어떤 case에 일치한다면 여러분이 실행하기 원하는 코드 블록에 도달할 때까지 case 다음의 모든 문장이 실행될 것이다. <리스트 13.8>에 있는 프로그램은 이런 경우를 보여준다.

<리스트 13.8> switch문을 사용하는 다른 한 가지 방법

/* switch문을 사용하는 다른 한 가지 방법 */

#include

#include

 

main()

{

int reply;

 

while(1)

{

puts("\nEnter a value between 1 and 10, 0 to exit: ");

scanf("%d", &reply);

 

switch(reply)

{

case 0:

exit(0);

case 1:

case 2:

case 3:

case 4:

case 5:

{

puts("You entered 5 or below.\n");

break;

}

case 6:

case 7:

case 8:

case 9:

case 10:

{

puts("You entered 6 or higher.\n");

break;

}

default:

puts("Between 1 and 10, please!\n");

} /* switch의 끝 */

} /* while의 끝 */

return 0;

}

 

=> 이 프로그램은 키보드에서 값을 읽어들이고 5이하인지 6이상인지 또는 1과 10사이의 값인지 알려준다. 입력된 값이 0이라면 18번째 줄에서는 exit() 함수를 호출하므로 프로그램은 종료.

5. 프로그램의 종료
: C 프로그램은 일반적으로 main() 함수의 실행이 끝날 때 종료된다. 그러나 라이브러리 함수 exit()를 사용하면 언제든지 원하는 시기에 프로그램을 마칠 수 있다. 또한, 프로그램이 종료될 때 하나 이상의 함수를 자동으로 실행하게끔 지정할 수도 있다.

5.1 exit() 함수
: exit() 함수는 프로그램 실행을 종료하고, 제어를 운영체제에 돌려준다. 이 함수는 프로그램이 성공적으로 실행되었는지 또는 실행에 문제가 있었는지를 지적하기 위해서 운영체제에 전달하는 하나의 int형 인수를 가진다. exit() 함수의 형식은 다음과 같다.

exit(status);

status의 값이 0이라면 프로그램이 정상적으로 종료되었다는 것을 뜻한다. status가 1의 값을 가지면 어떤 에러가 발생해서 프로그램이 비정상적으로 종료되었다는 것을 뜻한다. 이런 복귀값은 대개 무시된다. DOS에서는 배치 파일과 if errorlevel문을 사용하여 이런 복귀값을 확인하고 사용할 수 있다. 그러나 여기서는 DOS 설명서가 아니므로 복귀값을 사용하는 방법에 대해서 알라보기 원한다면 DOS 관련 서적을 참고하기 바란다.

exit() 함수를 사용하기 위해서는 프로그램에서 헤더 파일 STDLIB.H를 포함시켜야 한다. 또한, 이 헤더 파일에서는 exit() 함수에 대한 인수로 사용되는 두 개의 기호 상수를 다음과 정의하고 있다.

#define EXIT_SUCCESS 0
#define EXIT_FAILURE 1

그래서 복귀값을 0으로 설정하여 프로그램을 마치기 원한다면 exit(EXIT_SUCCESS)를 사용할 수 있다. 1의 값을 돌려주기 위해서는 exit(EXIT_FAILURE)를 사용하면 된다.

6. 프로그램 내에서 운영테제의 명령을 실행하는 방법
: C의 표준 라이브러리는 실행 중인 C 프로그램 내에서 운영테제의 명령을 실행하게 해주는 함수 system()을 제공한다. 이 함수는 프로그램을 종료하지 않은 상태에서 디렉토리 목록을 사렾보고나 디스크를 초기화에 해주므로 때에 따라 유용하게 사용할 수 있다. system() 함수를 사용하기 위해서는 프로그램에 헤더 파일 STDLIB.H를 포함시켜야 한다. system()의 형식은 다음과 같다.

system(command);

인수 command는 문자열 상수나 문자열에 대한 포인터가 될 수 있다. 예를 들어 DOS의 디렉토리 목록을 살펴보기 위해서 다음과 같은 내용을 사용할 수 있을 것이다.

system("dir");

또는 다음 문장을 사용할 수도 있다.

char *command = "dir";
system(command);

운영체제의 명령을 실행하고 나면 프로그램의 제어는 system() 함수 바로 다음의 문장으로 전달된다. system()에서 사용한 명령이 운영체제에서 유효하지 않은 명령이라면 프로그램이 다시 실행되기 전에 'Bad command or file name'이라는 에러 메시지가 출력된다. system()의 사용 예가 <리스트 13.9>에 나타나 있다.

<리스트 13.9> 운영체제의 명령을 실행하기 위해서 system() 함수를 사용하는 프로그램

/* system() 함수의 사용 예 */

#include

#include

 

main(0

{

/* 입력을 저장할 버퍼 선언 */

 

char input[40];

 

while(1)

{

/* 사용자의 명령을 받아들인다. */

 

puts("\nInput the desired system command, blank to exit");

gets(input);

 

/* 빈 줄이 입력되면 마친다. */

 

if(input[0] == '\0'

exit(0);

 

/* 명령을 실행한다. */

 

system(input);

}

return 0;

}

 

system()에서 사용할 수 있는 명령은 디렉토리 목록을 살펴보거나 디스크를 초기화하는 것과 같은 간단한 명령에만 제한되지 않는다. 또한, 실행 가능한 파일이나 배치 파일의 이름을 전달하여 프로그램을 정상적으로 실행할 수도 있다. 예를 들어, system의 인수로 LIST1308을 전달하면 LIST1308이라는 프로그램이 실행될 것이다. 프로그램의 실행이 끝나면 제어는 다시 system() 함수가 사용되었던 곳으로 전달된다.

system()을 사용할 때 유일한 제한 사항이 있다면 메모리와 관련된 문제이다. system()이 실행될 때 원래의 프로그램은 컴퓨터의 RAM에 남게 되고 운영체제의 명령을 처리하는 코드와 함수를 통해서 실행되는 프로그램이 메모리에 읽어들여지게 된다. 이렇게 다른 프로그램을 실행하는 것은 컴퓨터에 충분한 메모리가 남아 있는 경우에만 가능하다. 그렇지 않다면 에러 메시지가 출력될 것이다.

'pc관련 > C언어' 카테고리의 다른 글

C언어_링크리스트  (0) 2019.04.07
C언어_포인터,고급기능  (0) 2019.04.07
C언어_변수에 범위  (0) 2019.03.28
C언어_구조체  (0) 2019.03.25
C언어_문자,문자열  (0) 2019.03.24
Posted by 둥이파파^^
pc관련/C언어2019. 3. 28. 22:17

함수 내에서 선언된 변수가 함수의 밖에서 선언된 변수와 분명히 구분된다는 것을 설명했다. 이런 사실은 C 프로그래밍에서 중요한 변수의 범위(variable scope)를 부분적으로 설명하는 예이다.

·변수의 범위
·외부 변수의 의미와 외부 변수를 사용하지 않아야 하는 이유
·지역 변수에 대해서
·정적 변수와 자동 변수의 차이
·지역 변수와 블록에 대해서
·적절한 변수의 형태를 선택하는 방법

1. 변수의 범위란?
: 변수의 범위(scope)는 프로그램에서 변수를 사용할 수 있는 범위나 또는 변수가 프로그램 내에서 효과를 나타낼 수 있는(visible) 범위를 말한다. C에서 변수에 대해서 설명할 때에는 변수의 값을 사용할 수 있는 가능성(accessibility)과 주어진 범위 내에서 효과를 나타내는 것을 뜻하는 유효성(visibility)을 함께 사용한다. 변수의 범위를 언급할 대 변수(variable)는 C의 모든 데이터형을 뜻한다. 즉, 간단한 변수에서부터 배열, 구조체, 포인터 등을 모두 포함한다. 또한, const 키워드로 정의된 기호 상수도 포함한다. 변수의 범위는 변수의 생명, 즉 메모리에서 변수의 값이 보존되는 기간이나 변수에 저장 영역이 할당되고 해제되는 시기에 영향을 준다. 우선, 변수의 유효성(visibility)에 대해서 알아보도록 하자.

1.1 변수의 범위를 설명하는 예제
: <리스트 12.1>에 있는 프로그램을 살펴보자. 프로그램은 5번째 줄에서 변수 x를 정의하고, 11번째 줄에서 x의 값을 출력하기 위해서 printf() 함수를 호출하며 다시 x의 값을 출력하기 위해 printf_value()를 호출한다. 함수 print_value()에는 x의 값이 인수로 전달되지 않는다는 것에 주의하기 바란다. 이 함수는 19번째 줄에서 printf() 함수에 대한 인수로 x를 사용하고 있다.

 

<리스트 12.1> 변수 x는 함수 print_value내에서도 사용될 수 있다.

/* 변수의 범위를 설명하는 에제 */

#include

 

int x = 999;

 

void print_value(void);

 

main()

{

printf("%d\n", x);

print_value();

 

return 0;

}

 

void print_value(void)

{

printf("%d\n", x);

}

 

-> 출 력
999
999

이 프로그램은 아무런 문제 없이 컴파일되고 실행된다. 이제 이 프로그램에서 x를 정의하는 문장을 main() 함수 내의 위치로 이동시켜 보자. 변경된 소스 코드는 <리스트 12.2>에 나타나 있다.

 

<리스트 12.2> 변수 x는 함수 print_value 내에서 사용될 수 없다.

/* 변수의 범위를 설명하는 예제 */

#include

 

void print_value(void);

 

main()

{

int x = 999;

 

printf("%d\n", x);

print_value();

 

return 0;

}

 

void print_value(void)

{

printf("%d\n", x);

}

 

리스트 12.2>를 컴파일하면 컴파일러는 다음과 비슷한 에러 메시지를 출력할 것이다.

list1202.c[19] : Error: undefined identifier 'x'.

에러 메시지에서 괄호 내에 나타나는 숫자는 에러가 발생한 프로그램의 문장 번호를 뜻한다. 19번째 줄은 print_value() 함수 내에서 printf() 함수를 호출하는 문장이다. 에러 메시지는 print_value() 함수 내에서 변수 x가 '정의되지 않았다.'는 것을 알려준다. 즉, 함수 내에서는 변수 x가 유효하지 않다. 그러나 11번째 줄에 있는 printf()함수에서는 에러 메시지가 발생하지 않는다는 것에 주목할 필요가 있다. 프로그램의 11번째 줄에서는 변수 x가 유효한 것이다.

<리스트 12.1>과 <리스트 12.2>의 유일한 차이는 변수 x가 정의된 위치이다. x를 정의하는 문장을 이동시키면 변수의 범위가 바뀐다. <리스트 12.1>에서 x는 외부(external) 변수로 선언되었고, 이때 변수의 유효 범위는 전체 프로그램이다. 변수 x는 main() 함수와 print_value() 함수에서 모두 사용할 수 있다. 그러나 <리스트 12.2>에서 x는 지역(local) 변수로 선언되었고, 이때 변수의 유효 범위는 main() 함수로 제한된다. 그래서 함수 print_value()에서는 x가 존재하지 않는 것으로 간주된다 지역 변수와 전역 변수에 대해서는 나중에 상세히 설명할 것이므로 여기서는 변수 범위의 중요성을 알아둘 필요가 있다.

1.2 변수의 범위가 중요한 이유
: 변수의 범위가 중요하다는 것을 이해하기 위해서는 구조화 프로그래밍에 대해서 다시 한 번 언급할 필요가 있다. 구조화 프로그래밍에서는 특정 작업을 수행하는 독립된 함수로 프로그램을 분할한다. 여기서 중요한 것은 독립적(independent)이라는 단어이다. 완전히 독립된 함수를 작성하기 위해서는 함수 내의 변수가 다른 함수에 의해서 간섭되지 않아야 한다. 함수 내에서 독립된 변수를 사용하면 프로그램의 다른 부분에 영향을 받지 않고 주어진 작업을 정상적인 상태로 수행하는 함수를 작성할 수 있다. 여기서, 함수들 간에 완전히 독립된 데이터를 사용하는 것이 항상 바람직하다는 것을 짐작할 수 있을 것이다. 사용되는 변수의 범위를 지정하면 함수들간에 독립된 데이터를 사용할 수 있다. 변수의 범위를 지정하는 것은 이처럼 함수의 독립성에 영향을 준다.

2. 외부 변수
: 외부(external) 변수는 어떤 함수의 바깥에서 정의되는 것이다. main() 함수도 하나의 함수이므로 외부 변수는 main()의 박에서 선언되는 것을 포함한다. 외부 변수는 가금 전역(global) 변수라고도 한다.

2.1 외부 변수의 범위
: 외부 변수의 범위는 전체 프로그램이다. 그래서 외부 변수는 main()이나 프로그램 내의 다른 모든 함수에서 사용될 수 있다. 예를 들어, <리스트 12.1>에 있는 변수 x는 외부 변수다. 프로그램을 컴파일하고 실행하여 살펴보았듯이 x는 두 개의 함수 main()과 print_value()에서 유효하다. 그러나 정확히 말해서 외부 변수의 유효 범위가 전체 프로그램이라는 사실은 잘못된 것이다. 외부 변수의 범위가 변수를 정의하는 전체 소스 코드 파일이라는 표현이 더 정학하다. 전체 프로그램이 하나의 소스 코드 파일로 구성된다면 두 가지 범위는 같다. 대부분의 간단한 C 프로그램은 하나의 파일로 구성되고 프로그램도 이렇게 하나의 파일로 구성된다.

2.2 외부 변수를 사용하는 시기
: 몇 가지 예제 프로그램에서 외부 변수를 사용하고 있지만 실제로는 외부 변수를 사용하지 않도록 해야 한다. 그 이유는 무엇일까? 외부 변수를 사용하면 구조화 프로그래밍에서 중심이 되는 모듈의 독립성(modular independence)이 사라지게 된다. 모듈의 독립성은 프로그램을 구성하는 각각의 함수나 모듈이 주어진 작업을 수행하기 위해서 필요로 하는 모든 코드와 데이터를 내부에 포함하고 있는 것을 뜻한다. 지금까지 사용된 간단한 프로그램에서는 이런 모듈의 독립성이 중요하지 않게 생각될 수도 있지만, 더 크고 복잡한 프로그램을 작성하게 되면 외부 변수에 대한 함수의 의존성은 중요한 문제가 될 수 있다. 그렇다면 외부 변수는 어떤 경우에 사용해야 할까? 프로그램을 구성하는 대부분의 함수나 또는 모든 함수가 사용해야 하는 변수를 선언하는 경우에만 외부 변수를 사용하는 것이 좋다 const 키워드를 사용하여 정의되는 기호 상수는 외부 변수로 선언하기에 적합한 것이다. 변수가 몇 개의 함수에서만 사용된다면 외부 변수로 선언하지 말고 함수에 인수로 전달하자.

2.3 extern 키워드
: 함수에서 외부 변수를 사용할 필요가 있을 때에는 extern 키워드를 사용하여 함수 내에서 변수를 선언하는 것이 좋다. 변수는 다음과 같은 형식으로 선언된다.

extern type name;

type은 변수의 형태이고, name은 변수의 이름이다. 예를 들어, <리스트 12.1>에 포함디어 있는 함수 main()과 print_value()에는 변수 x를 선언하는 문장을 포함시킬 수 있다. <리스트 12.3>에는 수정된 프로그램이 나타나 있다.

 

<리스트 12.3> 변수 x는 함수 main()과 print_value에서 외부 변수로 선언된다.

/* 외부 변수 선언 */

#include

 

int x = 999;

 

void print_value(void);

 

main()

{

extern int x;

 

printf("%d\n", x);

print_value();

 

return 0;

}

 

void print_value(void)

{

extern int x;

printf("%d\n", x);

}

 

3. 지역 변수
: 지역 변수(local variable)는 함수 내에서 정의되는 변수이다. 지역 변수의 범위는 변수가 정의된 함수로 제한된다. 다섯번째 강의 "함수의 기본"에서 함수 내에서 사용되는 지역 변수, 지역 변수를 정의하는 방법, 지역 변수의 장점에 대해서 설명했다. 지역 변수는 컴파일러에 의해서 자동으로 0의 값으로 초기화되지 않는다. 지역 변수를 정의할 때 변수를 초기화하지 않으면 지역 변수에는 임의의 값(garbage)이 저장된다. 그래서 지역 변수를 사용하기 전에는 반드시 필요한 값을 저장해야 한다. 또한, main() 함수 내에서도 지역 변수를 사용할 수 있다. <리스트 12.2>에 있는 변수 x는 main() 함수에서 사용되는 지역 변수의 예이다. 변수 x는 main() 내에서 정의되고 프로그램의 결과로도 알 수 있듯이 main() 내에서만 유효하다.

3.1 정적 변수와 자동 변수
: 지역 변수는 기본적으로 자동 변수이다. 자동 변수는 함수가 호출될 때마다 새롭게 생성되고 함수의 실행이 끝나면 사라지는 변수를 뜻한다. 좀더 정확히 표현하자면 자동 변수는 변수가 정의되어 있는 함수가 호출될 때마다 변수의 값을 보존하지 않는다는 것을 뜻한다. 지역 변수 x를 사용하는 함수가 프로그램에서 호출된다고 하자. 또한, 함수가 처음 호출될 때 변수 x에는 100의 값이 할당된다고 가정하자. 함수의 동작이 안료되면 제어는 다시 함수를 호출한 프로그램으로 돌아가고 함수는 나중에 다시 호출된다. 변수 x는 여전히 100의 값을 가지고 있을까? 그렇지 않다 처음에 생성되었던 변수 x는 제어가 프로그램으로 다시 전달될 때 사라졌다. 함수가 다시 호출될 때에는 변수 x가 새롭게 생성된다. 과거의 x는 완전히 사라진다. 그렇다면 함수가 호출될 때마다 지역 변수의 값을 보존해둘 필요가 있을 때에는 어떻게 할 것인가? 예를 들어, 데이터를 인쇄하는 함수는 새로운 페이지로 진행해야 하는 시기를 결정하기 위해서 이미 프린터로 전송된 줄(line)의 수를 기억할 필요가 있을 것이다. 함수가 호출될 때 계속해서 지역 변수의 값을 보존하기 위해서는 static 키워드를 사용하여 변수를 정적(static) 변수로 정의해야 한다. 예를 들어, 다음과 같이 할 수 있다.

void func1( int x )
{
static int a;
/* 추가적인 코드 */
}

<리스트 12.4>에 있는 프로그램은 자도 변수와 정적 지역 변수 간의 차이를 보여준다.

<리스트 12.4> 자동 변수와 정적 지역 변수 간의 차이를 보여준다.

/* 자동 변수와 정적 지역 변수의 사용 예 */

#incude

 

void func1(void);

 

main()

{

int count;

 

for(count = 0; count < 20; count++)

{

printf("At iteration l%d: ", count);

func1();

}

 

return 0;

}

 

void func1(void)

{

static int x = 0;

int y = 0;

 

printf("x = %d, y = %d\n", x++, y++);

}

 

프로그램은 다음과 같은 결과를 보여준다. 정적 변수로 선언된 x는 함수가 호출될 때 값을 보존하므로 변수의 값은 계속해서 증가한다. 반면에, 자도 변수로 선언된 y는 함수가 호출될 때마다 0으로 다시 초기화된다. 또한, 이 프로그램은 두 가지 형태의 변수에서 초기화가 수행되는 방법의 차이점을 보여준다. 즉, 변수가 정의되는 것과 초기화되는 시기에 대해서 알려준다. 정적 변수는 단지 함수가 처음 호출될 때에만 초기화된다. 나중에 다시 함수가 호출될 때 프로그램은 정적 변수가 이미 초기화되었다는 사실을 기억하고 있으므로 다시 초기화하지 않는다. 변수는 함수가 최근에 실행되었을 때 저장되어 있던 값을 그대로 가지게 된다. 이와는 대조적으로, 자동 변수는 함수가 호출될 때마다 지정된 값으로 다시 초기화된다. 자동 변수를 사용해보면 지금까지 설명한 내용을 충분히 이해할 수 있을 것이다. 예를 들어, <리스트 12.4>에 있는 프로그램에서 두 개의 지역 변수가 초기화되지 않도록 변경하면 함수 func1()은 다음과 같은 내용이 될 것이다.

void func1(void)
{
static int x;
int y;

printf("x = %d, y = %d\n", x++, y++);
}

프로그램을 이렇게 변경하여 실행하면 y의 값은 함수가 호출될 때마다 증가한다는 사실을 알 수 있다. 함수가 호출될 때 y의 값이 계속 보존된다는 것을 알 수 있는 것이다. 그렇다면 지금까지 자동 변수에 대해서 설명했던 내용은 잘못된 것일까? 절대로 그렇지 않다. 지금까지 설명한 내용은 모두 사실이다. 앞에서 설명한 내용과 프로그램의 실행 결과를 보고 알 수 있듯이, 함수가 반복적으로 호출될 동안 자동 변수가 값을 보존하는 것은 단지 우연한 일에 불과하다. 좀더 구체적으로 살펴보자. 함수가 호출될 때마다 새로운 y가 생성된다. 컴파일러는 이전에 함수가 호출되었을 때 사용하던 곳과 동일한 메모리 영역을 새로운 y에 할당하여 사용할 것이다. y가 함수 내에서 분명하게 초기화되지 않는다면 메모리의 저장 영역에는 y가 이전에 가지고 있던 값이 저장되어 있을수 있다. 그래서 변수는 계속해서 값을 보존하는 것처럼 보일 수 있지만, 사실은 우현한 일치에 불과한 것이다. 이렇게 같은 값을 가지게 되는 상황이 항상 발생한다고 볼 수는 없는 것이다.
지역 변수는 기본적으로 자동 변수의 형태를 가지므로 변수를 정의할 때 지정할 필요가 없다 그러나 필요하다면 다음과 같이 변수의 데이터형을 표현하는 키워드 앞에 auto 키워드를 추가할 수 있다.

void func1(int y)
{
auto int count;
/* 그 밖의 프로그램 문장 */
}

3.2 매개 변수의 범위
: 함수의 헤더에서 매개 변수로 사용되는 변수는 지역(local) 변수와 같은 유효 범위를 가진다. 예를 들어, 다음 함수를 살펴보자.

void func1(int x)
{
int y;
/* 그 밖의 프로그램 문장 */
}

x와 y는 모두 함수 func1() 내에서만 유효하게 사용되는 지역 변수이다. 물론, x는 처음에 함수를 호출한 프로그램에서 전달되는 어떤 값을 가진다. 이렇게 전달된 값을 사용할 때에는 x를 다른 어떤 지역 변수와 같은 방법으로 사용할 수 있다. 매개 변수는 항상 대응하는 인수에 의해서 전달되는 값을 가지게 되므로 static이나 auto로 지정하는 것은 아무런 의미가 앖다.

3.3 외부 정적 변수
: 외부 변수를 정의할 때 static 키워드를 포함시키면 정적 변수로 말들 수 있다.

static float rate;
main()
{
/* 그 밖의 프로그램 문장 */
}

일반적인 외부 변수와 정적 외부 변수와의 차이는 유효 범위에 있다 .일반적인 외부 변수는 파일 내에 포함되어 있는 모든 함수에서 유효하고 다른 파일에 포함되어 있는 함수에 의해서도 사용될 수 있다. 정적 외부 변수는 단지 변수가 정의된 파일 내에서 변수가 정의된 부분 이후에 있는 함수에만 유효하다.

3.4 레지스터 변수
: register 키워드는 자동 지역 변수가 메모리 대신에 프로세서의 레지스터에 저장되도록 컴파일러에게 지시하는 데 사용된다. 프로세서의 레지스터(processor register)는 무엇이고 레지스터를 사용할 때의 장점은 무엇일까? 컴퓨터의 CPU(central processing unit)에는 레지스터라는 몇 개의 데이터 저장 영역이 존재한다. 덧셈이나 뺄셈과 같은 실제 데이터 연산은 CPU의 레지스터에서 수행된다. CPU는 데이터를 처리하기 위해서 메모리 내에서 레지스터로 값을 읽어들이고 처리된 결과를 다시 메모리에 저장한다. 데이터를 메모리에서 읽어들이고 다시 메모리로 저장하는 일련의 과정에서는 약간의 시간이 소모된다. 만약 어떤 변수의 값을 레지스터에 저장하여 사용한다면 변수의 값을 더 빨리 사용할 수 있을 것이다.

자동 변수를 정의할 때 register 키워드를 포함시키면 변수를 레지스터에 저장하도록 '요쳥' 할 수 있다. 다음 예제를 살펴보자

void runc1(void)
{
register int x;
/* 그 밖의 프로그램 문장 */
}

앞에서 '요청'이라는 단어를 사용했다는 것에 주의하기 바란다. 프로그램의 상태에 따라 변수의 값을 저장하기 위한 레지스터가 남아 있지 않을 수도 있다. 이때 컴파일러는 변수를 일반적인 레지스터 변수가 아니라 일반적인 자동 변수로 취급할 것이다. register 키워드는 명령하는 것이 아니라 일반적인 자동 변수로 취급할 것이다.

register 키워드는 명령하는 것이 아니라 '요청'하는 것이다 .레지스터 변수의 가장 큰 장점은 순환문의 카운터와 같이 함수 내에서 자주 사용되는 변수에서 증명된다. register 키워는 배열이나 구조체가 아니라 간단한 숫자 변수에 대해서만 사용될 수 있다. 또한 , 정적 변수나 외부 변수로 사용할 수 없다. 레지스터 변수에 대한 포인터를 정의하는 것도 불가능하다.

4. 지역 변수와 main() 함수
: 지금가지 설명한 내용은 모두 main()과 다른 함수에 적용되는 사항이다. 정확히 말하면, main()은 다른 모든 함수와 마찬가지로 하나의 함수이다. main() 함수는 프로그램이 운영체제에서 실행될 때 호출되고 프로그램이 종료될 때 제어는 main()에서 운영체제로 돌아간다. 이것은 main()에서 정의된 지역 변수가 프로그램이 시작될 때 생성되고 프로그램이 종료될 때 사라진다는 것을 뜻한다. 그래서 main() 함수가 호출될 때 값을 보존하는 static 상태의 지역 변수를 생성하는 것은 아무런 의미가 없다.

변수는 프로그램이 실행될 때마다 값을 보존할 수 없다. 결과적으로, main() 함수에서는 자동 변수와 정적 지역 변수의 차이가 없다. main() 내에서는 물론 static 키워드를 사용하여 지역 변수를 정의할 수 있지만 아무런 효과를 나타내지 않는다.

# 잠깐 노트
·main()은 다른 모든 함수와 비슷한 구조를 가지는 하나의 함수라는 사실을 기억하자
·main() 내에서 정적 변수는 아무런 효과도 나타내지 않으므로 정적 변수를 생성한지 않도록 하자.

5. 어떤 형태의 변수를 생성해야 하는가?
: 프로그램에서 특정 변수의 형태를 결정해야 할 때에는 다음 도표를 참고하면 도움이 될 것이다. <표 12.1>에는 C에서 사용할 수 있는 5가지 종류의 변수 형태가 나타나 있다.

<표 12.1> C 5가지 변수 형태 

변수의 형태를 결정할 때에는 가능하다면 자동 변수를 사용해야 하고, 필요한 경우에만 다른 형태를 선택해야 한다. 다음은 몇 가지 참고 사항이다.

·일단 모든 변수를 자동 지역 변수의 형태로 사용하자.
·변수가 자주 사용된다면 register 키워드를 사용하여 레지스터 변수로 정의하자.
·main()이 아닌 다른 함수 내에서 함수가 호출될 때 변수의 값이 보존되어야 한다면 정적 변수로 정의하자.
·변수가 프로그램의 대부분이나 또는 모든 함수에 의해서 사용된다면 외부 변수로 정의하자

6. 지역 변수와 블록
: 지금까지 설명한 내용은 단지 함수 내에서 지역 변수를 사용하는 것을 중심으로 했다. 지역 변수는 기본적으로 함수 내에서만 사용되지만 중괄호 내에 포함된 프로그램의 어떤 블록 내에서도 지역적으로 사용되는 변수를 생성할 수 있다. 블록 내에서 사용되는 변수를 선언할 때에는 가장 먼저 변수가 선언되어야 한다는 것을 기억하자. 예를 들어, <리스트 12.5>를 살펴보자.

<리스트 12.5> 프로그램의 블록 내에서 지역 변수 정의하기

/* 블록 내에서 지역 변수 사용하기 */

#include

 

main()

{

/* main()에 지역적인 변수 정의 */

 

int count = 0;

 

printf("\nOutside the block, count = %d", count);

 

/* 블록의 시작 */

{

/* 블록에 지역적인 변수 정의 */

 

int count = 999;

printf("\nWithin the block, count = %d", count);

}

 

printf("\nOutside the block again, count = %d\n", count);

return 0;

}

 

이렇게 지역 변수를 사용하는 것은 C 프로그래밍에서 흔하지 않고 필요하다고 생각되지도 않을 것이다. 실제로, 이렇게 같은 이름의 변수를 지역적으로 사용하는 방법은 프로그램에서 문제점을 찾는 경우에 가장 많이 사용된다. 즉, 프로그램의 일부분을 괄호 내에 포함시켜 일시적으로 독립시키고 잘못된 내용을 찾기 위해서 지역 변수를 사용할 수 있다. 다른 한 가지 장점은 변수가 사용되는 부분과 가까운 곳에서 변수를 선언하고 초기화할 수 있다는 것이다. 이렇게 하면 프로그램을 이해하기 쉽게 만들 수 있다.

'pc관련 > C언어' 카테고리의 다른 글

C언어_포인터,고급기능  (0) 2019.04.07
고급 프로그램제어문  (0) 2019.04.03
C언어_구조체  (0) 2019.03.25
C언어_문자,문자열  (0) 2019.03.24
C언어_포인터에 대하여...  (0) 2019.03.24
Posted by 둥이파파^^
etc.2019. 3. 25. 21:52

1. 됐거든. Please don't say anything more. (Used to show that you are tired of hearing someone's excuses.)

사전적 의미:제발 그것에 대해 더 이상 아무말도 하지 마세요.
2.
낚였어. You got me. You had me going there.

사전적 의미: 깜박 속아 넘어갈뻔 했네.
3.
낚았지? I got you, didn't I? I had you going, didn't I?

4. 당근이지. You bet./ Absolutely.

사전적 의미: 물론,틀림없이
5.
썰렁하군. That's a lame joke.
6.
분위기 망치게 (조지게) 하지마. / 초치지마. Don't spoil the mood.
7.
너나 잘 해. None of your business. Mind your own business.

사전적 의미: 당신이 상관할 일이 아니예요.
8.
내성질 건드리지 마. Don't get on my nerves.

사전적 의미: 신경 거슬리는 짓 좀 하지 마.
9.
뒷북치지마. Thanks for the history lesson. Hindsight is 20/20.  
10.
잘났어 정말. / 너 잘났다. You are somethng else.

11. 어제 필름이 끊겼어. I blacked out (from drinking) last night.
12.
그 사람 그거 참 잘~ 됐다. / 쌤통이다. He deserves it. Serves him right.
13.
그래 니 팔뚝 (또는 니 X) 굵다. Yes, you're the man!
14.
죽을만큼 마셔보자. Let's drink ourselves into stupor.
15.
니가 나한테 어떻게 그럴 수 있니? How could you do that to me?
16.
놀구 있네~~ 삽질 하네~~ Yeah. Right~
17.
거기 물 좋다 That place rocks! That place kicks!
18. (
문제의 답 등이) 너무쉽네/애걔() 그게다야? That's it? /Is that all?
19.
너도 내 입장이 되어봐 Put yourself in my shoes.
20.
저리 가. 꺼져! Take a hike.

21. 너 정말 치사하다. You're so cheap.
22.
음식 잘 먹었습니다. I've never eaten better.
23.
이 짓이 지겨워 죽겠어. This sucks!
24.
몇 시에 퇴근해요? What time do you call it a day?
25.
, 친구 좋다는 게 뭐야? Come on, what are friends for?
26.
너무 감격해서 눈물이 난다. It was so touching, I almost cried.
27.
미안해 할 것까지는 없어. There's nothing to be sorry about.
28.
내게 고마워할 것까지는 없어. There's no need to thank me.
29.
이보다 더 좋을 순 없다. It couldn't be better than this!
30.
메롱 Neh Neh Neh Boo Boo

31. 섭섭(실망)하지않게 해드리겠습니다! You won't be disappointed!
32.
나를 만만하게 보지마. Don't think I am that easy.
33.
니가 하는 일이 다 그렇지 뭐. That's what you always do.
34.
분위기 파악 좀 해라, 인간아. Consider your surroundings, you fool.
35.
두고보자. Just wait! I'll get (또는 pay) you back.
36.
가만히 있으면 중간이나 가지. You should've kept quiet
37.
이번 한 번만 봐준다. I'm gonna let it slide only this time.
38.
쟤는 어디가도 굶어죽진 않겠다. No matter where he goes, he'll do just fine.
39.
너무많은걸 알려고하면 다쳐. Knowing too much will only hurt you.
40.
제발 잘난 척 좀 그만해. Stop acting like you're something special.

41. 네가 없으니 뭔가 허전한 기분이야. I feel like something is missing when you're not here.
42.
장난이 좀 심하군. Your joking is going a little too far.
43.
말장난 그만 합시다. Let's stop playing word games
44.
내가 만만하게 보여? Do I look that easy?
45.
다 엎어버리고 뛰쳐 나가고싶다. I just want to drop everything and run away.
46.
여기 분위기 엄청 살벌하다. (삭막하다) The atmosphere here is very tense (hostile).
47.
몸이 찌뿌둥하다 I feel heavy.
48.
오해 하지 마세요. Don't get me wrong.
49.
몸이 날아갈 것 같애. /가뿐해. I feel light as a feather. I feel good.
50.
기가 막혀 말이 안나오네. It's so upset I'm speechless.

51. 니 맘대로 하세요. Suit yourself.
52.
괜히 나만 실없는 사람 되었잖아. It just made me look irresponsible.
53.
허리가 삐걱했어. I hurt my back.
54.
허리를 다쳤어요. I threw out my back.
55.
아직 옛날 실력 안 죽었어. I've still got it.
56.
넌 이제 죽었어. You're dead meat!
57.
너 들으라고 한 소리 아냐. Don't take it personally.
58.
까꿍! Peekaboo!
59.
알랑거리지마. Don't try to butter me up.
60.
배째 Sue me!

61. 그게 어딘데? That's better than nothing
62.
머리뚜껑이 열렸다. My head is about to split.
63.
그녀는 이중 성격을 가졌어. She has a multiple personality (split personality)
64.
어디론가 멀리 훌쩍 떠나고 싶다. I just want to go somewhere far away.
65. (
나에게) 너무 심한 것 아니예요? Don't you think you're being too harsh (on me)?
66.
그렇게까지 할 필요는 없어. You don't have to do all that
67.
나도 맘이 편하지는 않아. I don't feel good about it, either.
68.
그다지 썩 내키지는 않는데. I don't really feel like doing it
69.
생각보다 '별로'인데. It's not as good as I expected.
70.
몸살에 걸려 온몸이 쑤신다. 삭신이 쑤신다. My whole body aches.

71. 그 사람 똥배가 나왔어. He has a big belly.
72.
넌 내 밥이야. You're mine now! I've got you just where I want you.
73.
저 사람은 인간이 왜 저래? What's his problem? What's wrong with him?
74.
바늘로 꼭꼭 찌르는 것 같다. It feels like a needle poking me
75.
걔 원래 그런 애야. He's usually like that.
76.
너 삐졌니? Are you mad at me? Are you pissed off?
77.
이 싸가지 없는 녀석아 (싸가지 means 싹수.) You're a hopeless case.
78.
그는 밥만 축낸다. Food is wasted on him.
79.
그는 성격이 털털하고 시원시원하다. He has an easy-going and cool attitude.
80.
있는 척 좀 하지 마. Stop acting (Don't act) like you are rich.

81. 사람보면 아는척 좀 해봐라. Why are you acting like you don't know me?
82.
쟨 정말 짜다! 어떻게 밥 한번 안사니? He's so cheap. How can he not buy lunch even once?
83.
너 공주병이구나. You think you're special, don't you?
84.
저 애는 내가 찍었어. That boy (girl) is mine. He (She) is on my list.
85.
쟤는 날라리야. He's a player (with the girls).
86.
그는 앞뒤가 꽉 막혔어. He is so stubborn.
87.
내 입장이 정말 난처해. My position is very uncomfortable.
88.
그 사람은 건방지게 굴어. He acts like he's something special.
89.
쟤 손 좀 봐 줘야겠다. He needs a lesson.
90.
잘난체 하는 걸 아니꼬와서 못 보겠군! I can't stand watching him acting like he is something special.

91. 그녀는 마음을 잘 주지 않고 튕겨. She's playing hard to get.
92.
그는 뒤로 호박씨 깐다. He does things behind people's backs
93.
~~. 신난다. Yeah! This is fun!
94.
놔둬! 그냥 그러다가 말겠지 뭐. Leave him alone. He'll stop it eventually.
95.
이 숙제 정말 짜증난다. This homework is very tedious.
96.
그 사진 너무 야하다. That picture is too sexy.
97.
너무 오바 하지마. Stop exaggerating. Don't over do it. (오바 is Konglish for "'over' exaggerate.")
98.
쟤랑 걔랑 그렇고 그런 사이래. They are said to have a relationship.
99.
걘 늘 요리조리 빠져나간단 말이야. He always gets away with stuff.
100.
그냥 그렇다고 해, 뭘 자꾸 따져? Just say it. Don't argue.

101. 넌 왜 맨 날 그 모양이니? Why are you always like that?
102.
뭐 이런 놈이 다 있어~! What kind of person is this! / I can't believe this guy!
103.
저 사람 변태 아니야? Is he a pervert or something?
104.
보자보자 하니 해도 너무 한다. I've tried to be patient, but this is going too far.
105.
애들은 싸우면서 크는 거야. Fighting is a part of growing up.
106.
어휴~ 난 이제 죽었다. Man.. I'm dead now.
107.
걔 생각하면 지금도 가슴이 아프다. When I think about him, it still hurts.
108.
옷이 촌스럽다. Those clothes are out of style.
109.
기본부터 돼 먹지 않았다. It was wrong from the beginning.
110.
지나가던 개도 웃겠다. A passing dog would even laugh.

111. 나 나이 헛 먹은 거 아냐! Do you think I was born yesterday?
112.
누구 맘대로? With whose permission?
113.
니가 잘나면 얼마나 잘났니? If you've special, how special could you be?
114.
! 사는 게 왜 이럴까. Why is my life like this?
115.
그 여자는 너무 코가 높아 (콧대가 세다/도도하다). She's too snobby.
116.
내 일은 내가 알아서 다 할거야. I'll take care of my own business.
117.
뭐 찔리는 거라도 있는 거야? Do you feel guilty about something?
118.
니 입만 입이냐? You're not going to share your food?
119.
내 방은 지저분해서 발 디딜 틈이 없어. My room is so messy, there is no place to step.
120.
좋은 게 좋은 거지. If it's nice, it's nice.

121. 넌 꼭 그런 말만 골라 하는군. Why do you always say things like that?
122.
찍어둔 사람 있습니까? Do you have someone in mind?
123.
너 시치미 떼지마. Don't you try to lie. (you 에 강세)
124.
그거 수상한 냄새가 나는데. There's something fishy about it.
125.
느낌이 오는데. / 감이 잡히는데. I got a hunch.
126.
그는 자신감으로 똘똘 뭉쳤다. He is full of self-confidence
127.
좋은 사람 있으면 소개 시켜 줘. If you know someone good, hook me up.
128.
팀웍이 중요하다. 혼자 너무 튀지 마라. Teamwork is important, so don't try to stick out.
129.
난 쓸데없는 오해받기 싫어. I don't want to risk being misunderstood.
130.
네가 보고 싶으면 어떻게 참지? What do I do if I start missing you?

131. 우정이 갈수록 부실해진다. Friendship fades with time.
132.
모든 게 귀찮아. Everything is a hassle.
133.
누가 니 성격을 받아주겠니. Who would put up with your attitude?
134.
감쪽같이 (사귀는 관계를) 속이다니. How could you hide your relationship from everyone!
135.
결코 고의가 아니었다. I didn't mean to do that. / It's not what I mean.
136.
넌 배신자다. You are a traitor!
137.
다 티 난다. Everything shows./ It's too obvious. / You can't hide it.
138.
과연 얼마나 버틸 수 있을까? How long could it last?
139.
좀 봐 주면서해라. (불쌍하다) Be a little gentle with him.
140.
너하곤 게임이 안 된다. (상대가 안 된다) You are no match for me.

141. 그래서?내가 어떻게 하길 바라니? So? What do you want me to do?
142.
이젠 돌이킬 수가 없다. (소용없다) There is no turning back now.
143.
농담도 (장난도) 사람 봐가면서 해라. Don't take a joke too far.
144.
네게 섭섭하다. I"m disappointed with you.
145.
사랑이 식었구나 The love is gone.
146.
우리 사이가 애매하다.. Our relationship is confusing.
147.
이 엄살쟁이. You big baby.
148.
너의 잘못을 남에게 떠넘기지 마. Don"t try to put your mistakes on others.
149.
까불고 있어. 다음부터는 까불지마. Don't try joking anymore.
150.
! 너 인물이 훤해졌구나. Hey, your face looks a lot better.

151. 도대체가 끝이 안 보이네, 끝이.... I don't see the end of this.
152.
귀가 멍멍하다. (비행기를 탔을때) My ears are muffled.
153.
얌전한 남자아이. Calm and reserve boy. Sissy boy.
154.
화장 잘 받았다! Your makeup looks good!
155.
, 화장 떳어! Your makeup doesn't look good!
156.
내 말 안 듣더니, 그래 꼴 좋다. You didn't listen to me, so now look at you!
157.
그렇게 함부로 말하면 안 돼. You shouldn't talk like that
158.
전 간지럼을 잘 타요. I am ticklish.
159.
마음이 붕 떠 있어서 일이 손에 안 잡혀. I'm so excited, I can't work.
160.
행복한 고민 하시네요. You have pleasant worries.

161. 잔머리 돌리지 마. Don't try to take the easy way out.
162.
친구 지간에 그런 게 어딨니? How could you do that to a friend?
163.
어휴! 넌 아무도 못 말린다니까~~~! You're hopeless. What am I going to do with you.
164.
입에 침이나 바르고 거짓말해라. If you are going to lie, at least do a better job of it.
165.
그 사람 참 분위기 있더라. He has a lot of charisma. / She has class.
166.
그 넘은 화를 자초했군. He asked for it!
167.
외유내강. A steel hand in a velvet glove.
168.
무게 잡지마. Don't try to act tough.
169.
내 모든 걸 걸었어. I put everything into it.
170.
골라먹는 재미가 있다. It's fun picking out my favorite.

171. 너에겐 내가 있잖아. But you've got me.
172.
원샷! Bottoms up!
173.
강심장이군. His heart is made of stone.
174.
오늘은 내가 쏜다. Today, it's on me! I'll pay.
175.
왜 너 찔리니? Why? You feel guilty?
176.
여기서 지척에 살아. I live a stone's throw away from here.
177.
그녀에게 뿅갔어. I got a crush on her!
178.
왜 나한테 화풀이야? Why are you taking it out on me?
179.
말이 청산유수로군. He's a good talker.
180.
내숭 떨지마. Don't play innocent!

181. 흔들리면 안돼. Don"t waffle.
182.
남자는 여자하기 나름이야. The woman makes the man.
183.
쪽 팔리는 줄 좀 알아라. Shame on you!
184.
그래도 그만하길 다행이다. It could've been worse.
185.
그는 골칫 덩어리야. He's a pain in the neck.
186.
모든 일엔 다 때가 있다. There is a time for everything.
187.
그걸 꼭 말로 해야되니? Do I really have to say it?
188.
좀 책임감을 가져라. Try to be more responsible.
189.
너 많이 컸다! You've come a long way!
190.
기분 짱인데. I feel like a million dollars.

191. 난 타고난 체질이야. I was born for this.
192.
아까워라! What a waste!
193.
음매 기죽어! That hurts! What a blow to the ego!
194.
맞장구 좀 쳐 주라. Back me up here.
195.
괴롭히지 좀 마세요! Hey, get out of my hair!
196.
잠깐만 시간 좀 내주실 수 있으세요? Have you got a minute to spare?
197.
너 제정신이니? Are you out of your mind?
198.
너 뭔가 믿는 구석이 있구나. You've got "an ace in the hole" (a card up your sleeve), right?
199.
이거 장난이 아닌데! Man, this isn't a joke!
200.
간뎅이가 부었군. What a nerve!

201. 벌써 김샜어. I've already lost interest.
202.
돈은 문제가 아니에요. Money is no object.
203.
그냥 몸만 와라. Just bring yourself!
204.
나 거지 됐어! I'm flat broke!
205.
말도 안 되는 소리야. It doesn't make any sense.
206.
꿈 깨 Get real! / Not in your life time!
207.
얼굴이 많이 부었어요. Your face looks puffy.
208.
아직도 다 하려면 멀었어. I have still got a long way to go.
209.
내 곁에 있어줘. Keep me company.(Stay with me.)
210.
점심때 뭐 먹었니? What did you have for lunch?

211. 안타깝게도... Regretfully,...
212.
밑져봐야 본전이다. It doesn't hurt to try. You've got nothing to lose!
213.
내 맘대로 하도록 내버려 둬! Let me do it my way! Let me live my own life.
214.
벼룩의 간을 내먹어라. You're trying to get blood from a stone.
215.
거의 다 왔어. We are almost there.
216.
날 물로 보지마. Who do you think you're talking to?
217.
지킬 건 지켜야지. Let's be responsible!
218.
쟤네들 닭살이야! Those guys are too lovey-dovely!
219.
닭살 났어. I've got goose bumps.
220.
닭살 커풀 a lovey-dovey couple.

221. 너무 심하군/엽기적이야 That's gross! That's cultic!
222.
우리 그냥 친구로 지내자. Let's just be friends.
223.
그건 내가 할 소리야. Those are my words. That's what I was going to say.
224.
너는 사진보다 실물이 더 예뻐. You look much better in person.
225.
내 방문이 안에서 잠겼어. I'm locked out of my room.
226.
하루 쉬겠습니다. I'm taking the day off.
227.
내 맘대로 되는 게 또 있네. Another thing that's going my way.
228.
집에 바래다줄게. I'll see you home.
229.
내가 그걸 어찌 아니? How am I supposed to know?
230. (
야구장에서) 파도타기 하자 Let's do the wave thing.

231. 좋은 기억만 간직할게. I'll always remember the good times.
232.
물 흐리고 있네! You're ruining our image. (Stop dirtying our image.)
233.
사랑은 움직이는 거야. Love is always on the move.
234.
입장 바꿔 생각해봐. Put yourself in my shoes.
235.
난 그렇게 씀씀이가 헤픈 여자가 아냐 I don't waste money like that.
236.
흥분하지마. Chill out! Just relax!
237.
너 하나도 겁 안나! You don't scare me!
238.
오리발 내밀지마. Don't try to get out of it. (빠져나갈 생각 마)
239.
난 맥주병이에요. I swim like a rock.
240.
나 그런 쪽으로 빠삭해 (잘 알아). I've been down that road before.

241. 내가 모르는 뭔가 새로운 것 좀 알려주세요. Tell me something I don't know.
242.
대체 그런 얘기를 어디서 들었니? Where did you hear such a thing?
243.
그건 아무 것도 아냐 더 심한 것도 있는데. That's nothing. I know worse than that.
244.
깜박 하고 잊었어요. It slipped my mind.
245.
나를 바람 맞혔어요. She stood me up.
246.
딱지맞았어. She turned me down.
247.
너 맛이 갔구나. You look trashed!

248. 지금 가는 중이야. 곧 도착해. I'm on my way. I'll be there soon.
249.
그냥 지나갑시다. Let's skip that topic. 
250.
따져보길 잘했어. I was right to be picky. (꼼꼼히 실리를 따지다)

251. 너한테 딱 안성맞춤이네. It's perfect for you! / It's a perfect fit. (안성맞춤)
252.
나 지금 저기압이야. I'm in a bad mood now!
253.
마음이 탁 놓인다! It is such a weight off my shoulders!
254.
입맛 맞추기 힘드네. It's hard to satisfy your taste buds.
255.
음식을 가리는군. You're a picky eater.
256.
지킬 수 있는 약속만 해라! Only make promises you can keep!
257.
네 맨 얼굴을 보고싶어! I wanna see the real you! (정관사 the 필수)
258.
그러면 그렇지 I expected as much. / It never fails!
259.
정말 재밌었어 I had such enormous fun.
260.
나는 노는 물이 달라! I'm into different things! We're not in the same league.

261. 넌 그 바탕으로 사니? How can you walk around with a face like that?
262.
감 잡았어. I've got the picture.
263.
넌 양이 그렇게 중요하니? Is quantity all that you think about?
264.
라면이 불었습니다. The noodles are overcooked.
265.
에이, 좋다 말았네! Oh, I almost had it!
266.
, 밥이나 먹겠냐? You can't be a bread winner if you act like that!
267.
넌 물먹은 거야. You're stuck in second gear. You've been set back.
268.
네 마음의 창이 되어줄께. I'll be the window to your heart.
269.
넌 국물도 없어. Sorry, not today.
270.
딴 여자들한테 한 눈 팔지마. Keep your eyes off other women!

271. 민박 있어요? Do you have any cabins?
272.
넌 양이 그렇게 중요하니? Is quantity all you think about?
273.
사기가 하늘을 찌르는구나. You're so fired up!
274.
너 통 크다. You're a big spender.
275.
너 통 정말 작구나 You're so cheap!
276.
세상을 다 가져라. The world is at your door's step.
277.
엎어지면 코 닿을 데야. It's just around the corner.
278.
어머, 부끄러워. Oh, I'm so embarrassed.
279.
아이고, 진짜 웃긴다. That's so stupid / ridiculous
280.
아름다웠던 시절로 돌아가고 싶어. I want to go back to the good old days.

281. 내 모든 걸 걸었어. I put everything into it.
282.
나 여기 7 년 동안 단골이야. I've been a regular here for 7 years.
283.
와우, 몸매 죽이는데! Wow! She's got some curves.
284.
자알 빠졌다. Wow! She's got some curves.
285.
반말하지마. Don't use that impolite tone with me.
286.
미운 정 고운 정 다 들었다. I've got mixed feelings for her.
287.
그의 인기는 시들지 않아. His popularity never goes down!
288.
당당하게 살아라. Stand tall!
289.
저 여자 끝내주는데! She's really something!
290.
따라 올테면 따라와 봐. Catch me if you can. (공원에서 슬로우 모션으로)

291. 날씬한 게 좋죠! The thinner the better!
292.
폼 잡지마! Get real!! Get a job! Get a life! (싸울 때 쓰는 말)
293.
너 참 귀가 엷구나! You're so gullible!
294.
너 좀 너무 심한 거 아냐? Don't you think you're over-reacting a little?
295.
왜 사서 고생하니? Why go through all the troubles?
296.
속이 울렁거린다. I'm feeling a little queasy.
297.
너 돈독이 올랐구나! You are too money hungry.
298.
잘 자. 내 꿈꿔. Sleep tight. Dream of me.
299.
너무 지나치게 멋 부리지마! Don't overdress.
300.
세상에 공짜가 어딨어? There's no such thing as a free lunch!

301. 한 번만 봐주세요. Give me a break!.

302. 등 떠밀려 할 수 없이 그렇게 됐어. I was dragged into doing it.
303.
잠깐 숨 좀 돌리자! Let's take a breath! (우리말과 발상이 똑 같음)
304.
뒷일은 내가 책임질게! Just do it, I'll smooth everything over.
305.
너 얼굴 반쪽이 됐어. You look like you're about to drop.
306.
, 철 좀 들어라! Why don't you act your age?
307.
너랑 안 어울려. That doesn't go well with you.
308.
게임은 계속되어야 한다. The game must go on.
309.
말꼬리 잡지마. Stop twisting my words around.
310.
지퍼 열렸어요. Your fly is open.

311. 남대문 열렸어요. Your fly is open.
312.
넘겨 집지마. Don't jump to the conclusion.
313.
뭘 그리 꼬치꼬치 따지니? You're so nitpicky.
314.
왜 꼬치꼬치 캐물어? Why are you being so inquisitive?
315.
너 얼굴 참 두껍다. You're so brash.
316.
우린 천생연분이야. We're made for each other.
317.
너 눈 너무 높아. You're way too picky.
318.
왜 나한테 그래? Why are you accusing me?
319.
헌팅하러 가자. Let's pick up some chicks (guys: 남자를 헌팅할 때).
320.
비행기 태우지 마 Stop trying to flatter me!

321. 오리발 내밀지마. Don't try to get out of it.
322.
나를 꼭 좀 도와줘야 해요. You've gotta help me out.
323.
그냥 놓칠 수 없어요. I'll never let it go.
324.
뭐라고 감사를 드려야 할지 모르겠습니다. I can't thank you enough.
325.
그는 항상 한 발 늦어요. He is always running behind.
326.
그넘은 버릇 없는 넘이야. He's really rude.
327.
이거 정말 끝내준다. This is totally awesome. (쥑여주는군)
328.
황당하군. It leaves me speechless. (기가 막히는군)
329.
애기처럼 징징거리지 마. Stop whining like a baby. (짜증내다 to whin)
330.
우리 정리 좀 합시다. Let's all get on the same page.

331. 난 이 일에 재주가 있어. I got knack for this.
332.
두고 보자. You'll get yours. You'll be sorry for this.
333.
너도 만만치 않아. You're just as bad as me.
334.
거봐, 내가 뭐라고 그랬어. See, what did I tell you?
335.
농땡이 치지마! Don't goof off!
336.
너 개기니? Are you goofing off?
337.
똑바로 얘기해 주세요. Give it to me straight. (더함과 덜함 없이)
338.
내게 그런 핑계 대지마. Don't give me any excuses.
339.
마음에 여유가 있을 때 하겠어요. I'll do it when I'm ready.
340.
걱정 마. 내가 있잖아. I've got your back. / I'll be behind you.

341. 집들이 할거예요. I'm going to have a house warming party.
342.
머리를 좀 써봐. Use your head.
343.
정말 놀랐어요. You nearly gave me a heart attack. (과장법)
344.
좀 비켜 주세요! Please make way! (짐이요, 생선이요!)
345.
왜 사니? What's on your mind?
346.
정신을 어디다 두고 사니? What's on your mind?
347.
음악 좀 크게 들읍시다! Crank up the tunes!
348.
엄살 좀 피우지 마. Stop exaggerating. It didn't hurt that much.
349.
넌 대책이 없어. You are helpless. (콩글리쉬: You have no big book)
350.
니가 먼저 시작했잖아! You started it!

351. 니 팔뚝 니 흔들고, 내 팔뚝 내 흔들자. You go your way, I'll go mine.
352.
너 때문에 피곤해 죽겠어. I'm drained because of you.
353.
이번에는 정말 잘 됐으면 좋겠어. I really want it to happen this time.
354.
당신의 18번이 뭡니까? Which song can you sing the best?
355.
난 누구한테든 갈 수 있어! I could be with anyone!
356.
날 소유하려 하지마. Don't try to own me.
357.
면목 없습니다. Sorry, I have no excuse.
358.
너 이빨사이에 뭔가가 끼었어. You've got something between your teeth.
359.
너무 기뻐 죽겠어요. I'm so happy, I could die.
360.
가위 눌렸다. I had one of those nightmares where you can't move.

361. 난 정신없이 바빴어. I've been busy as a bee!
362.
눈 코 뜰 새 없이 바빴어. I've been busy as a bee!
363.
나 회사에서 짤렸다. I got canned.
364.
물 먹었다. I was fired.
365.
눈썰미가 있구나. You pick things up quickly.
366.
당신과 연락하려면 어떡해하죠? How can I get a hold of you?
367.
글쎄? 도통 모르겠어 I haven't the slightest idea. ("왜 전화 했어?"란 질문에)
368.
그냥.... Just because....... (그냥 전화 했어. 그냥 왔어)
369.
이름 값 좀 해라. Live up to your name.
370.
가위, 바위, . Paper, rock, scissors.

371. 어리광 부리지 마. Don't play the baby.
372.
기말 시험을 망쳤어. I bombed my final exam.
373.
그녀는 정말 여우야. She is as sly as a fox.
374.
김밥 옆구리 터지는 소리 하고있네! That's just ridiculous!
375.
가끔은 별 일이 없을 때가 좋습니다. Sometimes no news is good news.
376.
사각거리는 시원한 사과 먹었으면 좋겠다. I'd love to eat a refreshing crunchy apple.
377.
너 그거 진짜 할거야? Are you really going to do it?
378.
그거 할거야, 말거야? Are you gonna do it or not?
379.
관계를 더욱 돈독히 해야합니다. We've got to cement our relationship.
380.
벌써 다 끝내다니! You can't be done already!

381. 왕입니다요 You're the man.
382.
건방지게 행동하지마. Don't be so arrogant.
383.
국물이 끝내줘요. Good to the last drop.
384.
상사병에 걸렸어요. The love bug has bitten me.
385.
속이 거북하다. My gut is killing me. / My stomach is funny.(약한 통증)
386.
그는 진짜 부자다. He is rich with a capital R.
387.
자판기가 돈을 먹어버렸어요. The vending machine ate my money.
388.
백지장도 맞들면 낫다. Two heads are better than one.
389.
내게 행운을 빌어 줘. Wish me luck.
390.
왜 안가고 서성대고 있어? Why are you lingering around?

391. 버스 떠난 뒤에 손 흔들어 봤자야. That ship has already sailed.
392.
다리 떨지마. 복 나가. It's unlucky to shake your foot.
393.
그저 이런 저런 잡담이나 하고 싶어. I just wanna shoot the breeze.
394.
어떻게 될지 누가 알겠니? No one can say how it'll turn out.
395.
일찍 와도 안 잡아먹어. It wouldn't hurt to come early.
396.
숨이 차 죽겠어. I'm out of breath.
397.
그 사람 가까이 가지마. Stay clear of that guy. / Stay away from him.
398.
제발 나를 괴롭히지 마. Hey, just lay off me.
399.
내 컴퓨터가 다운되었어. My system crashed.
400.
너 지금 까불고 있는 거니? Are you being a wise guy?

401. 너 완전히 오해했구나! You really missed the point!
402.
마음 한 구석이 좀 불편하다. It doesn't settle well with me.
403.
이제 속이 시원하다. It doesn't get on my nerves anymore. I feel much better!
404.
그 사람은 너무 튀어. That guy is a real odd ball.
405.
나름대로는 열심히 했어. I think I did my best./ I did the best I could.
406.
한 입으로 두 말 하지 마세요. Don't go back on your word.
407.
사람을 외모만 보고 판단하지 마라. Don't judge a book by its cover.
408.
늦었다고 생각할 때가 가장 빠를 때다. Better late than never.
409.
난 추위를 잘 타요. The cold really gets to me.
410.
이 책은 내용이 정말 알차다. This book's got it all.

411. , 네가 못하는 게 뭐니? You can do anything you set your mind to.
412.
우왕! 진짜 짜증나. Man, I'm so ticked. (tick은 모기처럼 무는 벌레)
413.
네가 보고싶어 죽겠어. I'm dying to see you!
414.
일을 하려면 제대로 해! If you're going to do, do it right!
415.
, 대체 비결이(요령이) 뭐야? Hey, what's the secret?
416.
넌 빠져. Don't get nosy (코를 들이밀지 마 = 간섭 마)
417.
가문에 먹칠하다. Give someone's family a bad name.
418.
혈통 문제로군. It runs in the family.
419.
나이 값을 좀 해라. Act your age!
420.
엎어지면 코 닿을 데야. It's just around the corner.

421. 어머, 부끄러워. I'm so embarrassed.
422.
세상에 공짜가 어딨어? There's no such thing as a free lunch!
423.
잠깐 숨 좀 돌리자! Let's take a breather.
424.
, 철 좀 들어라! Why don't you act your age?
425.
뭐라고 감사를 드려야 할지 모르겠습니다. I can't thank you enough.
426.
그넘은 버릇 없는 넘이야. He's really rude.

 

 

'etc.' 카테고리의 다른 글

재미있는 심리학 지식에 대해~~  (0) 2019.04.16
많이쓰는 영어문장  (0) 2019.04.03
영어표현500문장  (0) 2019.03.17
세계 10대 슈퍼푸드  (0) 2019.03.10
다시마에 효능  (0) 2019.03.10
Posted by 둥이파파^^
pc관련/C언어2019. 3. 25. 20:20

많은 프로그래밍 작업은 구조체(structures)라고 하는 C의 데이터형을 통해서 단순화될 수 있다. 구조체는 프로그램에서 필요한 데이터형을 프로그래머가 직접 구성하여 사용하는 데이터 저장 방식의 한 가지이다. 오늘은 다음과 같은 내용을 배운다.

·단순 구조체와 복합 구조체에 대해서
·구조체를 정의하고 선언하는 방법
·구조체에 포함되어 있는 데이터를 사용하는 방법
·배열을 포함하는 구조체와 구조체의 배열을 생성하는 방법
·구조체 내에서 포인터를 선언하는 방법과 구조체에 대한 포인터를 선언하는 방법
·함수의 인수로 구조체를 전달하는 방법
·공용체를 정의하고, 선언하고, 사용하는 방법
·구조체로 새로운 데이터형을 정의하는 방법

1. 단순 구조체
 : 구조체(structure)는 여러 개의 변수를 쉽게 사용할 수 있도록 하나의 이름으로 묶은 하나 이상의 변수의 집합이다. 구조체에 포함되는 변수는 배열에서와는 달리 여러 가지 데이터형 이 될 수 있다. 구조체는 배열이나 다른 구조체 등 C의 모든 데이터형을 포함할 수 있다. 구조체에 포함되는 각각의 변수를 구조체 멤버(member)라고 한다. 우선, 단순 구조체에 대해서 알아보도록 하자. 이렇게 구조체를 두 가지 형태로 구분하여 설명하는 것이 이해하기 쉽지만, 실제로 C 언어에서는 단순 구조체와 복합 구조체를 구분해서 사용하지 않는다는 것을 기억하기 바란다.

1.1 구조체의 정의와 선언
 : 만약 그래픽 프로그램을 작성한다면, 프로그램은 화면 상에 출력되는 점의 좌표를 다룰 필요가 있을 것이다. 화면 좌표는 수평 좌표를 나타내는 x값과 수직 좌표를 나타내는 y값으로 구성된다. 다음과 같이 화면에서 점의 위치를 x와 y값으로 나타내는 coord라는 이름의 구조체를 정의할 수 있다.

  struct coord {
     int x;
     int y;
  };

구조체 정의가 시작된다는 것을 알려주는 struct 키워드 다음에는 반드시 구조체의 이름이나 태그(tag)가 포함되어야 한다. 이것은 C에서 다른 변수를 선언할 때와 같다. 구조체 이름 뒤에 있는 중괄호에는 구조체 멤버인 변수의 목록이 포함된다. 각각의 구조체 멤버에 대해서 변수의 형태와 이름이 사용되어야 한다. 앞의 예제는 두 개의 정수형 변수 x와 y를 가지는 coord라는 이름의 구조체를 정의한다. 그러나 실제로 구조체 coord형의 변수(instances)를 생성하지는 않는다. 즉, 구조체형에 대한 메모리 영역을 보존하지 않는다. 구조체형의 변수를 실제로 생성하는 두 가지 방법이 있는데, 하나는 다음과 같이 구조체 정의문 다음에 하나 이상의 변수 이름을 포함시키는 것이다.

  struct coord {
     int x;
     int y;
  } first, second;

앞의 문장은 구조체 coord를 정의하고 coord형 변수인 first와 second라는 이름의 두 구조 체를 선언한다. first와 second는 coord형으로 선언된 구조체형 변수이다. first는 x와 y라는 이름의 두 가지 정수형 멤버를 가지고, second도 first와 같은 멤버를 가짐. 구조체를 선언하는 첫 번째 방법은 구조체의 선언문과 정의를 결합시킨 형태이다. 두 번째 방법은 소스 코드의 서로 다른 부분에서 구조체를 정의하고 구조체형 변수를 선언하는 것이다. 그래서 다음과 같은 문장도 coord형의 두 변수를 선언한다.

  struct coord {
     int x;
     int y;
     };
  /* 그 밖의 프로그램 문장 */
  struct coord first, second;


 

1.2 구조체 멤버를 사용하는 방법
 : 개별적인 구조체 멤버는 동일한 형태의 일반적인 변수와 마찬가지 방법으로 사용될 수 있다. 구조체 멤버는 구조체 멤버 연산자(structure member operator)나 멤버 연산자(dot operator)라고 하는 마침표(.)를 사용하여 참조할 수 있다. 그래서 first라는 이름의 구조체가 좌표 x=50, y=100의 값으로 화면 위치를 표현하도록 하기 위해서는 다음과 같은 문장을 사용할 수 있을 것이다.

  first.x = 50;
  first.y = 100;

구조체 second에 저장된 화면의 좌표값을 출력하기 위해서는 다음과 같은 문장을 사용할 수 있다.

  printf("%d, %d", second.x, second.y);

여기서, 개별적인 변수 대신에 구조체를 사용하는 것이 어떤 장점을 제공하는지 의문을 가질 것이다. 한 가지 중요한 장점은 간단한 할당문으로 동일한 형태의 구조체 간에 모든 값을 복사할 수 있다는 것이다. 앞의 예에서 다음 문장은

  first = second;

다음과 같은 뜻을 가진다.

  first.x = second.x;
  first.y = second.y;

프로그램에서 많은 멤버를 가지는 복잡한 구조체를 사용할 때 이렇게 한 번의 할당문으로 값을 복사할 수 있다는 사실은 매우 효율적인 시간 절약 방법이다. 구조체의 다른 한 가지 장점은 약간 더 고급 기능을 배울 때 이해할 수 있을 것이다. 일반적으로, 여러 가지 형태의 변수를 동시에 다룰 필요가 있을 때에는 항상 구조체를 유용하게 사용할 수 있을 것이다. 예를 들어, 우편용 주소록 데이터베이스를 처리할 때 각각의 자료를 구조체로 만들어 하나의 자료에 포함되는 이름, 주소, 전화번호 등을 구조체 멤버로 취급할 수 있을 것이다.

2. 더욱 복잡한 구조체
지금까지 간단한 형태의 구조체에 대해서 알아보았으므로, 이제 더욱 흥미롭고 복잡한 형태의 구조체에 대해서 알아보자. 복잡한 형태의 구조체란 다른 어떤 구조체를 멤버로 가지거나 또는 배열을 멤버로 가지는 구조체를 말한다.

2.1 구조체를 가지는 구조체
: 앞에서도 설명했듯이, C의 구조체는 C에서 사용되는 모든 데이터형을 포함할 수 있다. 예를 들어, 구조체는 다른 어떤 구조체를 가질 수 있다. 앞에서 사용했던 예를 다시 보자. 그래픽 프로그램이 좌표뿐 아니라 사각형을 다루어야 한다고 가정해 보자. 사각형(rectangle)은 축을 중심으로 상반되는 위치에 있는 좌표로 표현할 수 있다. 앞에서는 이미 하나의 점을 표현하기 위해서 두 좌표값을 가지는 구조체를 정의하는 방법에 대해서 설명했다. 사각형을 표현하는 구조체를 정의하기 위해서는 두 개의 점을 표현하는 구조체가 필요할 것이다. coord형의 구조체가 이미 정의되어 있다고 가정하면 다음과 같은 구조체를 정의할 수 있을 것이다.

  struct rectangle {
     struct coord topleft;
     struct coord bottomrt;
  };

이 문장은 두 개의 coord형 구조체 변수를 가지는 rectangle이라는 구조체를 정의한다. 두 개의 coord형 구조체 변수는 topleft와 bottomrt라는 이름을 가진다. 앞의 에는 단지 rectangle형의 구조체를 정의한다. 실제로 구조체형 변수를 선언하기 위해서는 다음과 같은 문장을 사용해야 한다.

  struct rectangle mybox;

또한, 앞에서 coord형 구조체를 선언할 때와 마찬가지로 구조체 정의와 선언을 결합시킬 수 있을 것이다.

  struct rectangle{
      struct coord topleft;
      struct coord bottomrt;
   } mybox;

여기서 int형의 데이터를 참조하기 위해서는 멤버 연산자(.)를 두 번 사용해야 할 것이다. 그래서 다음 수식은

  mybox.topleft.x

mybox라는 이름의 rectangle형 구조체 변수에서 topleft라는 멤버 내의 x라는 멤버를 나타내는 것이다. 좌표가 (0, 10), (100, 200)인 사각형을 정의하기 위해서는 다음과 같은 문장을 작성할 것이다.

  mybox.topleft.x = 0;
  mybox.topleft.y = 10;
  mybox.bottomrt.x = 100;
  mybox.bottomrt.y = 200;

이 문장은 다소 혼란스러울 것이다. rectangle형 구조체와 여기에 포함된 두 개의 coord형 구조체, 각각의 coord형 구조체가 가지는 두 개의 int형 변수의 관계를 보여주는 <그림 11.1> 을 살펴보면 지금까지 설명한 내용을 쉽게 이해할 수 있을 것이다.

구조체는 앞의 예에서와 같은 이름을 가진다.

 

<그림 11.1> 구조체, 구조체 내의 구조체, 구조체 멤버 간의 관계를 보여주는 그림

이제, 구조체를 포함하는 구조체의 사용 예를 살펴보도록 하자. <리스트 11.1>에 있는 프로그램은 사각형의 좌표값을 읽어들이고, 사각형의 면적을 계산하여 출력한다. 프로그램의 앞 부분에 있는 주석문에서 설명되는 프로그램의 조건을 주의해서 살펴보자.

<리스트 11.1> 다른 구조체를 가지는 구조체의 사용 예

 

 /* 다른 구조체를 가지는 구조체의 사용 예 */


 

 /* 사각형의 구석 좌표를 읽어서 면적을 구한다.

    오른쪽 하단 구석의 y좌표가 왼쪽 상단 구석의 y좌표보다 크고,

    오른쪽 하단 구석의 x좌표가 왼쪽 상단 구석의 y좌표보다 크며,

    모든 좌표가 양수라고 가정한다. */


 

 #include <stdio.h>


 

 int length, width;

 long area;


 

 struct coord{

     int x;

     int y;

 };


 

 struct rectangle{

     struct coord topleft;

     struct coord bottomrt;

 } mybox;


 

 main()

 {

    /* 좌표 입력 */


 

    printf("\nEnter the top left x coordinate: ");

    scanf("%d", &mybox.topleft.x);


 

    printf("\nEnter the top left y coordinate: ");

    scanf("%d", &mybox.topleft.y);


 

    printf("\nEnter the bottom right x coordinate: ");

    scanf("%d", &mybox.bottomrt.x);


 

    printf("\nEnter the bottom right y coordinate: ");

    scanf("%d", &mybox.bottomrt.y);


 

    /* 길이와 높이 계산 */


 

    width = mybox.bottomrt.x - mybox.topleft.x;

    length = mybox.bottomrt.y - mybox.topleft.y;


 

    /* 면적 계산과 출력 */


 

    area = width * length;

    printf("\nThe area is %ld units.\n", area);


 

    return 0;

 }

 

 

C는 구조체의 종속 단계에 제한을 두지 않는다. 메모리가 허용하는 범위 내에서는 구조체를 가지는 구조체, 이런 구조체를 가지는 또다른 구조체등 몇 단계로 구성되는 구조체를 사용할 수 있다. 그러나 여러 단계의 종속된 구조체가 효율적이지 않은 경우도 있다. 대부분의 C 프로그램에서는 3단계 이상 종속된 구조체를 사용하지 않는다.

2.2 배열을 가지는 구조체
 : 또한, 하나 이상의 배열을 멤버로 포함하는 구조체를 정의할 수 있다. 이때 사용되는 배열은 int, char등 C의 어떤 데이터형이든지 될 수 있다. 예를 들어, 다음 문장은

  struct data{
     int x[4];
     char y[10];
  };

4개의 요소를 가지는 x라는 이름의 정수형 배열과 10개의 요소를 가지는 y라는 이름의 문자형 배열을 구조체 멤버로 포함하는 data형 구조체를 정의한다. 그리고 나서 다음과 같이 data형 구조체 변수 record를 선언할 수 있다.

  struct data record;

이 구조체의 상태가 <그림 11.2>에 나타나 있다. 그림에서 배열 x의 각 요소는 배열 y의 각 요소보다 2배나 많은 공간을 차지한다는 것을 주목하기 바란다. 이것은 int형이 대개 2바이트의 저장 영역을 요구하는 반면에 char형이 1바이트를 차지하기 때문이다.

<그림 11.2> 배열을 구조체 멤버로 가지는 구조체

 

구조체 멤버인 배열의 개별적인 요소를 사용하기 의해서는 멤버 연산자와 배열의 첨자를 함께 사용해야 한다.

   record.x[2] = 100;
   record.y[1] = 'x';

아마도 문자 배열은 문자열을 저장하기 위해서 가장 많이 사용된다는 것을 기억할 것이다. 또한 "포인터에 대해서"에서 설명했듯이 괄호를 포함함지 않는 배열의 이름은 배열에 대한 포인터를 뜻한다는 사실을 기억할 것이다. 이런 사실은 구조체 멤버로 사용되는 배열에도 적용되므로 다음 수식은

   record.y

구조체 record의 멤버인 배열 y[]의 첫 번째 요소에 대한 포인터이다. 그래서 다음과 같은 문장을 사용하여 y[]의 내용을 화면 상에 출력할 수 있을 것이다.

   puts(record.y);

이제 다른 예제를 살펴보자. <리스트 11.2>에 있는 프로그램은 float형 변수와 두 개의 char형 배열을 가지는 구조체를 사용하고 있다.

 

<리스트 11.2> 배열을 멤버로 가지는 구조체의 사용 예

 /* 배열을 멤버로 가지는 구조체 */


 

 #include <stdio.h>


 

 /* 데이터를 저장할 구조체 정의와 선언 구조체는 하나의 부동 소수형 변수와

    두 개의 문자형 배열을 가진다. */


 

 struct data{

    float amount;

    char fname[30];

    char lname[30];

 } rec;


 

 main(}

 {

    /* 키보드에서 데이터 입력 */


 

    printf("Enter the donor's first and last names,\n");

    printf("separated by a space: ");

    scanf("%s %s", rec.fname, rec.lname);


 

    printf("\nEnter the donation amount: ");

    scanf("%f", &rec.amount);


 

    /* 정보 출력 */

    /* 참고 : %2f는 부동 소수형 값을 소수점 이하 두 자리까지 출력하도록 지정한다. */

    /* 화면상에 데이터 출력 */


 

    printf("\nDonor %s %s gave $%.2f.\n", rec.fname, rec.lname, rec.amount);


 

    return 0;

 }

 

 

3. 구조체 배열
 : 배열을 멤버로 가지는 구조체를 사용할 수 있다면 구조체의 배열을 사용하는 것도 가능할까? 가능하다고 생각할 것이다. 실제로 구조체의 배열은 아주 강력한 프로그래밍 도구다. 구조체의 배열에 대해서 알아보도록 하자.
 앞에서는 프로그램에서 필요한 데이터형을 정의하기 위해서 구조체를 사용할 수 있다고 설명했다. 프로그램은 대부분의 경우 여러 개의 데이터를 사용한다. 예를 들어, 전화번호부를 관리하는 프로그램에서는 사람의 이름과 전화번호를 저장하기 위해서 구조체를 정의할 수 있다.

   struct entry{
      char fname[10];
      char lname[12];
      char phone[8];
   };

그러나 전화번호부는 여러 사람의 데이터를 포함하는 것이므로 entry 구조체형 변수를 한 번만 선언하는 것은 유용하지 않을 것이다. 여기서 필요한 것은 entry형 구조체의 배열이다. 그래서 구조체를 정의하고 나면 다음과 같이 구조체의 배열을 선언할 수 있다.

   struct entry list[1000];

이 문장은 1,000개의 요소를 가지는 list라는 이름의 배열을 선언한다. 모든 요소는 entry형 구조체 변수이고, 각 요소는 일반적인 배열 요소와 마찬가지로 첨자에 의해서 구분된다. 배열 요소인 각각의 구조체는 세 개의 char형 배열을 구조체 멤버로 포함하고 있다. 구조체의 배열을 선언할 때에는 다양한 방법으로 데이터를 다룰 수 있다. 예를 들어, 한 배열 요소의 데이터를 다른 배열 요소에 할당하기 위해서 다음과 같이 할 수 있을 것이다.

   list[1] = list[5];

이 문장은 list[5]의 각 멤버에 저장된 값을 구조체 list[1]에서 대응하는 멤버에 할당한다. 또한, 개별적인 구조체의 멤버들 간에 데이터를 이동시킬 수도 있다. 다음 문장은

   strcpy(list[1].phone, list[5].phone);

list[5].phone에 저장된 문자열을 list[1].phone에 복사하는 것이다. 라이브러리 함수 strcpy()는 한 문자열을 다른 문자열에 복사한다. 자세한 내용은 "문자열 다루기"에서 상세히 설명하겠다. 또한, 필요하다면 구조체 멤버의 하나인 배열의 개별적인 요소들 간에도 데이터를 이동시킬 수 있다.

   list[5].phone[1] = list[2].phone[3];

이 문장은 list[5]에서 전화번호 값을 저장하는 배열의 두 번째 위치에 list[2]에서 전화번호값을 저장하는 배열의 네 번째 문자를 할당한다. 첨자는 항상 0에서부터 시작한다는 것을 잊지 않도록 하자.

<리스트 11.3>에 있는 프로그램은 구조체 배열의 사용 예를 보여준다. 또한, 이 프로그램은 배열을 멤버로 가지는 구조체의 배열을 사용하고 있다.

 

    <리스트 11.3> 

 

 /* 구조체의 배열 사용 예 */


 

 #include <stdio.h>


 

 /* 항목을 저장할 구조체 정의 */


 

 struct entry{

    char fname[20];

    char lname[20];

    char phone[10];

 };


 

 /* 구조체의 배열 선언 */


 

 struct entry list[4];


 

 int i;


 

 main()

 {

    /* 네 명의 데이터 입력 */


 

    for(i = 0; i < 4; i++)

    {

       printf:\nEnter first name: ");

       scanf("%s", list[i].fname);

       printf("Enter last name: ")"

       scanf("%s", list[i].lname);

       printf("Enter phone is 123-4567 format: ");

       scanf("%s", list[i].phone);

    }

    /* 두 개의 빈줄 출력 */

    printf("\n\n");


 

    /* 데이터 출력 */


 

    for(i = 0; i < 4; i++)

    {

       printf("Name: %s %s", list[i].fname, list[i].lname);

       printf("\t\tPhone: %s\n", list[i].phone);

    }

    return 0;

 }

 

<리스트 11.3>에서 사용된 프로그래밍 방식에 익숙해지도록 하자. 많은 프로그래밍 작업에서는 배열을 멤버로 가지는 구조체의 배열을 사용할 필요가 있을 것이다.

4. 구조체의 초기화
 : C의 다른 변수형과 마찬가지로 구조체를 선언하는 동시에 초기화할 수 있다. 수행 과정은 배열을 초기화하는 것과 비슷하다. 구조체 선언문에는 등호와 함께 쉼표에 의해서 구분되고, 중괄호에 포함되는 초기화값의 목록이 나타난다. 예를 들어, 다음 예제를 살펴보자.

   struct sale {
      char customer[20];
      char item[20];
      float amount;
   } mysale = {"Acme Industries",
               "Left-handed widget",
                1000.00
              };

 이 문장이 실행되면 다음과 같은 동작이 수행된다.

① 1번째 줄부터 5번째 줄까지는 sale이라는 이름의 구조체형을 정의한다.
② 5번째 줄에서는 mysale이라는 이름의 sale형 구조체 변수가 선언된다.
③ 5번째 줄에서는 구조체 멤버 mysale.customer를 문자열 'Acme Industries'로 초기화한다.
④ 6번째 줄에서는 구조체 멤버 mysale.item을 문자열 'Left-handed widget'으로 초기화한다.
⑤ 7번째 줄에서는 구조체 멤버 mysale.amount를 1000.00의 값으로 초기화한다.

구조체를 멤버로 가지는 구조체의 경우에는 순서대로 초기화값을 나열하면 된다. 초기화값은 구조체를 정의하는 문장에서 멤버가 나타나 있는 순서대로 구조체 멤버에 할당된다. 다음은 앞의 예제를 약간 보충한 다른 하나의 예제이다.

   struct customer {
      char firm[20];
      char contact[25];
   }

   struct sale {
      struct customer buyer;
      char item[20];
      float amount;
   } mysale = {{"Acme Industries", "George Adams"),
                "Left-handed widget",
                 1000.00
               };

이 예제는 다음과 같은 초기화를 수행한다.

① 10번째 줄에서 구조체 멤버 mysale.buyer.firm은 문자열 'Acme Industries'로 초기화된다.
② 10번째 줄에서 구조체 멤버 mysale.buyer.contact는 문자열 'George Adams'로 초기화된다.
③ 11번째 줄에서 구조체 멤버 mysale.item은 문자열 'Left-handed widget'으로 초기화된다.
④ 12번째 줄에서 구조체 멤버 mysale.amount는 1000.00의 값으로 초기화된다.

또한, 구조체의 배열을 초기화할 수 있다. 입력되는 초기화값은 배열에 포함되어 있는 구조체에 순서대로 할당된다. 예를 들어, sale형 구조체의 배열을 선언하고 나서 처음 두 배열 요소인 두 개의 구조체를 초기화하기 위해서는 다음 예제와 같이 할 수 있을 것이다.

  struct customer {
     char firm[20];
     char contact[25];
     };

  struct sale {
     struct customer buyer;
     char item[20];
     float amount;
     };

  struct sale y1990[100] = {
     {{"Acme Industries", "George Adams"},
       "Left-handed widget",
       1000.00
      }

     {{"Wilson & Co.", "Ed Wilson"},
       "Type 12 gizmo",
        290.00
      }
     };

이 코드에서는 다음과 같은 동작이 수행된다.

① 14번째 줄에서 구조체 멤버 y1990[0].buyer.firm은 문자열 'Acme Industries'로 초기화된다.
② 14번째 줄에서 구조체 멤버 y1990[0].buyer.contact는 문자열 'George Adams'로 초기화됨.
③ 15번째 줄에서 구조체 멤버 y1990[0].item은 문자열 'Left-handed widget'으로 초기화된다.
④ 16번째 줄에서 구조체 멤버 y1990[0].amount는 1000.00의 값으로 초기화된다.
⑤ 18번째 줄에서 구조체 멤버 y1990[1].buyer.firm은 문자열 'wilson & Co.'로 초기화된다.
⑥ 18번째 줄에서 구조체 멤버 y1990[1].buyer.contact는 문자열 'Ed Wilson'으로 초기화된다.
⑦ 19번째 줄에서 구조체 멤버 y1990[1].item은 문자열 'Type 12 gizmo'로 초기화된다.
⑧ 20번째 줄에서 구조체 멤버 y1990[1].amount는 290.00의 값으로 초기화된다.

5. 구조체와 포인터
 : 포인터가 C에서 아주 중요한 부분을 차지한다는 사실을 인식한다면 구조체와 함께 사용할 수 있다는 사실도 쉽게 짐작할 수 있을 것이다. 포인터를 구조체의 멤버로 사용할 수 있으며, 구조체에 대한 포인터를 선언할 수도 있다. 이런 내용들을 하나씩 살펴보도록 하자.

5.1 구조체 멤버로 사용되는 포인터
 : 포인터를 구조체 멤버로 사용하면 더욱 융통성 있는 프로그램을 작성할 수 있다. 포인터는 구조체 멤버로 사용될 때 일반적인 경우와 동일한 방법으로 선언된다. 즉, 간접 연산자(*)를 사용하여 선언된다. 다음은 예이다.

   struct data {
      int *value;
      int *rate;
      } first;

이 문장은 int형에 대한 두 개의 포인터를 멤버로 가지는 구조체를 정의하고 선언한다. 다른 모든 포인터에서와 마찬가지로 포인터를 선언하는 것만으로 사용할 수는 없다. 포인터에는 변수의 주소를 할당하여 포인터가 어떤 영역을 지적하도록 초기화해야 한다. 만약 cost와 interest가 int형 변수로 선언되어 있다면 다음과 같은 문장을 작성할 수 있을 것이다.

   first.value = &cost;
   first.rate = &interest;

이제 포인터가 초기화되었으므로 간접 연산자(*)를 사용할 수 있다. 수식 *first.value는 cost에 저장된 값을 뜻하고, 수식 *first.rate는 interest에 저장된 값을 뜻한다. 구조체 멤버로 가장 많이 사용되는 포인터는 아마도 char형에 대한 포인터일 것이다. 앞 강의 "문자와 문자열"에서는 '첫 번째 문자를 지적하는 포인터와 마지막을 표시하는 널 문자 사이의 일련의 문자로 문자열이 구성된다'는 것을 설명했었다. 메모리를 효율적으로 사용하기 위해서는 다음과 같이 char형에 대한 포인터를 선언하고 문자열을 지적하도록 초기화해야 한다.

   char *p_message;
   p_message = "Teach Yourself C in 21Days";

또한, char형에 대한 포인터를 멤버로 가지는 구조체를 사용하여 동일한 결과를 얻을 수 있다.

   struct msg {
      char *p1;
      char *p2;
      } myptrs;
   myptrs.p1 = "Teach yourself C in 21Days";
   myptrs.p2 = "By SAMS Publishing";

구조체 멤버로 포함되어 있는 포인터는 일반적인 포인터를 사용할 수 잇는 어떤 곳에서든지 사용할 수 있다. 예를 들어, 포인터가 지적하는 문자열을 출력하기 위해서는 다음과 같은 문장을 사용할 수 있을 것이다.

   printf("%s %s", myptrs.p1, myptrs.p2);

구조체 멤버로 char형의 배열을 사용하는 것과 char형에 대한 포인터를 사용하는 것의 차이점은 무엇일가? 두 가지 모두 구조체에 문자열을 '저장'하기 위한 방법이다. 다음은 두 가지 방법을 모두 사용하는 구조체 msg이다.

   struct msg(
       char p1[30];
       char *p2;
       } myptrs;

괄호를 포함하지 않는 배열의 이름은 배열의 처 번째 요소에 대한 포인터라는 것을 기억하자 그래서 앞의 구조체 멤버는 다음과 같이 사용할 수 있다.

   strcpy(myptrs.p1, "Teach Yourself C In 21 Days");
   strcpy(myptrs.p2, "By SAMS Publishing");
   /* 그 밖의 프로그램 문장 */
   puts(myptrs.p1);
   puts(myptrs.p2);

두 가지 방법의 실제 차이점은 무엇일까? 만약 구조체 멤버로 char형 배열을 가지는 구조체를 정의하면, 이 구조체형으로 선언되는 모든 변수는 일정한 크기의 배열만을 저장할 수 있는 저장 영역을 가지게 된다. 또한, 저장 영역의 크기는 제한되고 구조체에는 지정된 것보다 긴 문자열을 저장할 수 없다. 다음은 사용 예이다.

   struct msg {
      char p1[10];
      char p2[10];
      } myptrs;
      …
   strcpy(p1, "Minneapolis");  /* 문자열이 배열보다 길다. */
   strcpy(p2, "MN");          /* 문자열이 배열보다 잛지만 저장 영역이 낭비되고 있다. */

반면에, char형에 대한 포인터를 가지는 구조체를 정의하면 이런 제한은 적용되지 않는다. 각각의 구조체 변수는 포인터를 저장하기 위한 저장 영역만을 가지게 된다. 실제 문자열은 메모리 내의 다른 어떤 곳에 저장되지만 메모리의 위치에 대해서는 신경 쓸 필요가 없다. 이렇게 포인터를 사용하면 문자열의 길이에 제한이 없고 저장 영역이 낭비되지 않는다. 실제 문자열은 구조체의 일부분이 아닌 상태로 메모리의 특정 부분에 저장된다. 구조체에 포함되는 각 포인터는 어떤 길이의 문자열도 지적할 수 있다. 실제 문자열이 구조체에 저장되는 것은 아니지만 구조체의 일부분과 관련된다.

5.2 구조체에 대한 포인터
: C 프로그램에서는 다른 어떤 데이터형에 대한 포인터와 마찬가지로 구조체에 대한 포인터 를 선언하고 사용할 수 있다. 이 장에서 나중에 설명할 것처럼 구조체에 대한 포인터는 함수 의 인수로 구조체를 전달할 때 가끔 사용된다. 또한, 구조체에 대한 포인터는 링크드 리스트 (linked lists)라고 알려져 있는 매우 강력한 데이터 저장 방법에서 사용된다. 링크드 리스트는 나중에 "포인터 : 고급 기능들"에서 설명할 것이다.
 여기서는 프로그램에서 구조체에 대한 포인터를 생성하고 사용하는 방법에 대해서 알아보도록 하자. 우선, 구조체를 정의하겠다.

   struct part {
      int number;
      char name[10];
      };

이제 part형 구조체에 대한 포인터를 선언한다.

   struct part *p_part;

선언문에서 사용되는 간접 연산자(*)는 p_part가 part형 구조체 변수가 아니라 part형 구조체 에 대한 포인터라는 사실을 알려준다는 것을 기억하기 바란다. 이제 포인터를 초기화할 수 있을까? 그렇지 않다. 구조체 part는 정의되었지만 어떤 구조체 변수도 선언되지 않았다. 앞의 문장은 구조체 변수 선언문이 아니라 구조체에 대한 포인터 선언문이라는 것을 기억하자. 이제 데이터를 저장하기 위해서 메모리 내의 저장 영역을 보존 하는 변수 선언문이 필요하다. 포인터가 어떤 주소를 지적하도록 하기 위해서는 우선 part형 구조체 변수를 선언해야 한다. 다음은 선언문이다.

   struct part gizmo;

이제 포인터를 초기화할 수 있다.

   p_part = &gizmo;

이 문장은 gizmo의 주소값을 p_part에 저장한다.

<그림 11.5>에는 구조체와 구조체에 대한 포인터의 관계가 나타나 있다.

<그림 11.5> 구조체에 대한 포인터는 구조체의 첫 번째 바이트를 지적한다.

 

이제, 구조체 gizmo에 대한 포인터를 생성했다. 이 포인터를 어떻게 사용할 것인가? 한 가지 방법은 간접 연산자(*)를 이용하는 것이다. 이런 사실을 여기에 적용하면 p_part는 구조체 gizmo에 대한 포인터이고, *p_part는 gizmo 자체를 뜻한다는 것을 알 수 있다. 이제 gizmo의 각 멤버를 사용하기 위해서는 구조체 멤버 연산자(.)를 사용하자. gizmo.number에 100의 값을 저장하기 위해서는 다음과 같은 문장을 사용할 수 있을 것이다.

   (*p_part).number = 100;

여기서 멤버 연산자(.)는 간접 연산자(*)보다 높은 우선 순위를 가지고 있으므로 *p_part는 괄호 내에 포함되어야 한다. 구조체에 대한 포인터를 사용하여 구조체 멤버를 참조하는 두 번째 방법은 하이픈(-)과 부등호(>)로 구성되는 간접 멤버 참조 연산자(indirection membership operator)를 사용하는 것이다. 하이픈과 부등호를 함께 사용하면 C에서 하나의 새로운 연산자로 간주된다는 것을 기억하기 바란다. 이 연산자는 포인터 이름과 멤버 이름 사이에 위치된다. p_part 포인터를 사용하여 gizmo의 멤버인 number를 참조하기 위해서는 다음과 같은 문장을 사용할 수 있다.

   p_part -> number

다른 하나의 예를 살펴보자. 만약 str이 구조체이고, p_str이 str에 대한 포인터이며, memb가 str의 멤버라면 다음과 같은 문장을 통해서 str.memb를 사용할 수 있을 것이다.

   p_str->memb

그래서 전체적으로 구조체 멤버를 참조하는 세 가지 방법이 있음을 알 수 있다.

·구조체의 이름을 사용한다.
·간접 연산자(*)와 구조체에 대한 포인터를 함께 사용한다.
·간접 멤버 참조 연산자(->)와 구조체에 대한 포인터를 함께 사용한다.

p_str이 구조체 str에 대한 포인터라면 다음의 세 가지 수식은 모두 동일한 것이다.

   str.memb
   (*p-str).memb
   p_str->memb

5.3 포인터와 구조체의 배열
: 앞에서는 구조체의 배열과 구조체에 대한 포인터가 매우 강력한 프로그래밍 도구라는 것을 설명했다. 또한, 구조체를 참조하는 포인터를 배열 요소로 사용해서 두 가지를 결합시킬 수도 있다. 앞에서 예제로 사용했던 구조체를 다시 한 번 살펴보도록 하자.

   struct part{
      int number;
      char name[10];
      };

구조체 part가 정의되면 part형 배열을 선언할 수 있다.

   struct part data[100];

다음으로 part형에 대한 포인터를 선언하고, 포인터가 배열 data의 첫 번째 구조체를 지적하도록 초기화할 수 있다.

   struct part *p_part;
   p_part = &data[0];

괄호를 포함하지 않는 배열의 이름이 배열의 첫 번째 요소에 대한 포인터라는 것을 기억하자 그래서 두 번째 문장은 다음과 같이 작성할 수도 있을 것이다.

   p_part = data;

이제 part형 구조체의 배열과 배열의 첫 번째 요소, 즉 배열의 첫 번째 구조체에 대한 포인터를 생성했다. 다음과 같은 문장을 사용하여 첫 번째 요소의 내용을 출력할 수 있을 것이다.

   printf("%d %s", p_part->number, p_part->name);

만약 배열의 모든 요소를 출력하기 원한다면 어떻게 해야 할까? 아마도 순환문이 한 번 반복될 때마다 배열의 요소를 하나씩 출력하는 for문을 사용할 것이다. 포인터를 사용하여 구조체 멤버를 참조하기 위해서는 순환문이 반복될 때마다 포인터 p_part가 배열의 다음 요소, 즉 배열의 다음 구조체를 지적하도록 변경해 주어야 한다. 어떻게 해야 여기서 의도 하는 대로할 수 있을까? 여기서는 C의 포인터 연산을 사용할 필요가 있다. 단항 증가 연산자(++)는 포인터와 함께 사용될 때 특별한 의미를 가지게 된다. 즉, '포인터가 지적하는 데이터형의 크기를 반영하여 포인터의 값을 증가시켜라'는 것을 뜻한다. 예를 들어, obj형의 변수를 지적하는 포인터 ptr이 있다면 다음 문장은

   ptr++;

다음과 같은 뜻을 가진다.

   ptr += sizeof(obj);

포인터 연산의 이런 특징은 배열에서 특히 유용하게 사용된다. 배열의 각 요소는 메모리에 순서대로 저장된다. 포인터가 배열의 요소를 지적하고 있을 때 증가(++) 연산자를 사용하면 포인터는 n + 1번째의 요소를 지적하게 된다. <그림 11.6>에서는 4바이트 크기의 요소로 구성되는 x[]라는 이름의 배열을 예로 들어 이런 사실을 설명한다. 그림에서는 2바이트 길이의 두 int형 변수를 멤버로 가지는 구조체가 사용되고 있다. 포인터 ptr은 x[0]을 지적하도록 초기화되어 있고 ptr의 값이 증가될 때마다 배열의 다음 요소를 지적한다.

<그림 11.6> 포인터의 값이 증가될 때 포인터는 배열의 다음 요소를 '지적'한다.

 

결국, 프로그램에서 포인터를 증가시켜 구조체의 배열이나 다른 어떤 데이터형의 배열을 순서대로 사용할 수 있다는 사실을 알 수 있다. 이 방법은 같은 작업을 수행하기 위해서 배열의 첨자를 사용하는 것보다 쉽고 간단하다. <리스트 11.4>에 있는 프로그램은 지금까지 설명한 내용을 예제로 보여준다.

 

<리스트 11.4> 포인터를 증가시켜 배열의 요소를 순서대로 사용하는 프로그램

 /* 포인터 표기를 사용하여 구조체의 배열을 차례대로 사용하는 예 */


 

 #include <stdio.h>


 

 #define MAX 4


 

 /* 구조체를 정의 후 4구조체의 배열을 선언하고 초기화한다. */


 

 struct part {

    int number;

    char name[10];

 } data[MAX] = {1, "Smith", 2, "Jones", 3, "Adams", 4, "Wilson"};


 

 /* part형에 대한 포인터와 카운터 변수 선언 */


 

 struct part *p_part;

 int count;


 

 main()

 {

    /* 첫 배열 요소에 대한 포인터 초기화 */


 

    p_part = data;


 

    /* 순환할 때마다 포인터를 증가시키며 배열을 사용한다. */


 

    for (count = 0; count < MAX; count++)

    {

       printf("\nAt address %d: %d %s", p_part, p_part->number, p_part->name);

       p_part++;

    }


 

    return 0;

 }

 

 

출력되는 주소값을 자세히 살펴보자. 실제 값은 시스템마다 달라질 수 있지만 간격은 일정하게 증가할 것이다. 이 간격은 구조체 part의 크기로 대부분의 시스템에서는 12가 될 것이다. 이 프로그램은 포인터를 증가시킬 때 포인터가 지적하는 변수의 크기를 반영하여 주소값이 증가된다는 사실을 분명히 보여준다.

5.4 함수의 인수로 구조체를 전달하는
 : 다른 데이터형과 마찬가지로 함수의 인수로 구조체를 전달할 수도 있다. <리스트 11.5>에 있는 프로그램은 이런 사실을 증명한다. 이 프로그램은 <리스트 11.2>에 있는 프로그램을 수정한 것이다. 여기에서는 화면 상에 데이터를 출력하는 함수를 사용하는 반면에, <리스트 11.2>는 main()의 일부분에 해당하는 문장에서 데이터를 출력한다.

 

<리스트 11.5> 함수의 인수로 구조체를 전달하는 프로그램

 /* 함수에 구조체를 전달하는 예 */


 

 #include <stdio.h>


 

 /* 데이터를 저장할 구조체 선언과 정의 */


 

 struct data {

    float amount;

    char fname[30];

    char lname[30];

 } rec;


 

 /* 함수 원형. 함수는 복귀값이 없고 하나의 인수로 data형의 구조체를 받아들인다. */


 

 void print_rec(struct data x);


 

 main()

 {

    /* 키보드에서 데이터 입력 */


 

    printf("Enter the donor's first and last name,\n");

    printf("separated by a space: ");

    scanf("%s %s", rec.fname, rec.lname);


 

    printf("\nEnter the donation amount: ");

    scanf("%f", &rec.amount);


 

    /* 출력 함수 호출 */

    print_rec( rec );


 

    return 0;

 }


 

 void print_rec(struct data x)

 {

    printf("\nDonor %s %s gave $%.2f.\n", x.fname, x.lname, x.amount);

 }

 

구조체의 주소, 즉 구조체에 대한 포인터를 인수로 사용하여 함수에 구조체를 전달할 수도 있다. 이전에는 C에서 구조체를 인수로 사용하는 것이 유일한 방법이었다. 이제는 이렇게 구조체를 직접 전달하지 않지만, 아직까지도 가끔 구조체를 전달하는 경우를 볼 수 있다. 구조체에 대한 포인터를 인수로 전달하면 함수에서는 구조체 멤버를 참조하기 위해서 간접 멤버 참조 연산자(->)를 사용해야 한다는 것을 기억하기 바란다.

6. 공용체
: 공용체(unions)는 구조체와 비슷하다. 공용체는 구조체와 같은 방법으로 선언되고 사용된다. 공용체는 한 번에 하나의 멤버만이 사용될 수 있다는 점에서 구조체와 다르다. 그 이유는 간단하다. 공용체의 모든 멤버는 메모리에서 같은 영역을 차지하고 있다. 모든 멤버는 겹쳐져 있는 셈이다.

6.1 공용체 정의와 선언, 그리고 초기화
: 공용체는 구조체와 같은 방법으로 정의되고 선언된다. 선언문에서 유일한 차이점은 키워드 struct 대신에 union이 사용된다는 점이다. char 변수와 정수형 변수의 간단한 공용체를 정의하기 위해 다음과 같이 작성할 수 있다.

   union shared {
      char c;
      int i;
      };

이 공용체 shared는 문자값 c나 정수값 i의 하나를 가질 수 있는 공용체 변수(instance)를 생성하는 데 사용될 수 있다. 이것은 OR 조건문과도 같다. 두 값을 모두 가지게 되는 구조체와 달리 공용체는 한 번에 하나의 값만을 가질 수 있다.

<그림 11.7>은 shared 공용체가 메모리에 저장되는 방법을 보여준다.

<그림 11.7> 공용체는 한 번에 하나의 값을 가질 수 있다.

 

공용체를 선언하는 동시에 초기화할 수도 있다. 그러나 한 번에 하나의 멤버만이 사용될 수 있으므로 하나만 초기화될 수 있다. 일반적으로, 혼란을 막기 위해서 공용체의 첫 멤버만이 초기화될 수 있다 .다음 코드는 shared 공용체형 변수가 선언되고 초기화되는 것을 보여준다.

   union shared generic_variable = ('@'};

6.2 공용체 멤버 사용하기
 : 개별적인 공용체 멤버는 구조체 멤버와 마찬가지로 멤버 연산자인 마침표(.)를 이용해서 사용할 수 있다 .그러나 공용체 멤버를 사용할 때에는 중요한 한 가지 차이점이 있다. 한 번에 하나의 공용체 멤버만이 사용되어야 한다는 것이다. 공용체는 서로 겹쳐져 있으므로 한 번에 하나의 멤버를 사용하는 것은 매운 중요하다. <리스트 11.6>은 예제를 보여준다.

 

<리스트 11.6> 공용체의 잘못된 사용 예

 

 /* 한번에 하나 이상의 공용체 멤버 사용 */

 #include <stdio.h>


 

 main()

 {

    union shared_tag {

       char c;

       int i;

       long l;

       float f;

       double d;

    } shared;


 

    shared.c = '$';


 

    printf("\nchar c = %c", shared.c);

    printf("\nint i = %d", shared.i);

    printf("\nlong l = %ld", shared.l);

    printf("\nfloat f = %f", shared.f);

    printf("\ndouble d = %f", shared.d);


 

    shared.d = 123456789.8765;


 

    printf("\nchar c = %c", shared.c);

    printf("\nint i = %d", shared.i);

    printf("\nlong l = %ld", shared.l);

    printf("\nfloat f = %f", shared.f);

    printf("\ndouble d = %f", shared.d);


 

    return 0;

 }

 

7. typedef와 구조체
 : 구조체를 정의할 때에는 동의어인 typedef 키워드를 사용할 수 있다. 예를 들어, 다음 문장은 구조체에 대한 동의어로 coord를 사용할 수 있도록 정의해준다.

   typedef struct {
      int x;
      int y;
      } coord;

이제 coord를 사용하여 구조체형의 변수를 선언할 수 있다.

   coord topleft, bottomright;

typedef는 이 장의 앞부분에서 설명한 구조체 태그와는 다르다는 것을 주의하자. 만약 다음과 같은 구조체를 정의하면

   struct coord {
       int x;
       int y;
       };

coord는 구조체의 태그로 사용된 것이다. 구조체 변수를 선언하기 위해서 이런 태그를 사용할 수 있지만 typedef와 달리 반드시 struct 키워드를 포함시켜야 한다

   struct coord topleft, bottomright;

구조체를 선언할 때 typedef를 사용해야 하는지 구조체 태그를 사용해야 하는지의 여부는 결정하기 어려운 문제이다. typedef를 사용하는 경우에는 struct 키워드가 필요하지 않으므로 더 간결하다고 볼 수 있다. 반면에, 태그를 사용하고 struct 키워드를 포함시키면 구조체라는 사실을 분명히 알 수 있어 편리하다.

'pc관련 > C언어' 카테고리의 다른 글

고급 프로그램제어문  (0) 2019.04.03
C언어_변수에 범위  (0) 2019.03.28
C언어_문자,문자열  (0) 2019.03.24
C언어_포인터에 대하여...  (0) 2019.03.24
C언어_숫자배열사용  (0) 2019.03.24
Posted by 둥이파파^^