2009. 3. 30.

[리눅스] C언어

C언어 란?

1969년에......, AT&T 연구소의 Ken Thompson은 연구소의
한쪽 구석에 방치되어 있던 작은 컴퓨터를 보고 운영체제
'UNIX'의 개발을 시작했습니다. 이 작업에 가담하고 있었던
Dennis Ritchie 라는 사람이 'C' 라고 하는 언어를 만들었고.
결국 UNIX 는 C 언어로 다시 프로그래밍 되었습니다.

그런데, 왜 이름이 'C' 일까요?
원래 'CPL(Combined Programming Language)' 이라는 언어가
있었는데, 그 언어에서 'BCPL(Basic CPL)' 이라는 언어가
파생되었고, 다시 그 언어에서 파생한 언어를 BCPL의 선두
문자를 따서 'B' 라고 이름을 붙였습니다. C 언어는 다시 그
B 언어에서 파생되었기 때문에 BCPL의 2번째 문자를 따서
(혹은 B의 다음 알파벳을 따서) 'C' 라고 지어졌다고 합니다.

요약하자면 C언어는 Unix 운영체제 하에서 시스템 프로그래밍을
하기 위해 개발된 아주 강력한 기능을 가진 프로그래밍 언어입니다.

C언어는 구조화 프로그래밍을 지향하는 언어이고 정말로 가장
쓸모있는 언어라고 할수 있습니다.
이유는 C언어에서는 못하는게 없기 때문이죠.
좀 과장된 표현 같기도 하지만 다른 언어들과 비교해 볼때
못하는거란 없습니다.
그리고 C언어로 만든 프로그램의 속도는 어셈블리어 만큼이나
빠른 속도를 내기 때문에 빠른 속도를 요구하는 프로그램에도
적합하죠.
단 게임과 같은 곳에서는 C언어의 속도조차도 느리기 때문에..
어셈블리가 요구되는 곳이 있지만, 요즘 C언어 컴파일러들은
인라인 어셈블러라는 것을 지원해서 C소스 내에 어셈블리 코드를
추가할수 있습니다. 이런 점으로 볼때 C로는 못하는게 없다고
봐도 되겠죠?

그래도 실감이 나지 않으시면 C언어로 만들어진 대표적인
프로그램을 예로 들어 볼까요?
그런데 그럴려면 한가지 문제가 있네요.
이 문제가 뭐냐 하면.. C언어로 만들어진 유명한 프로그램이
너무나도 많다는 것이죠. 그래도 몇가지 예를 들어 보죠.
우선 OS에서는 Unix의 커널의 일부분을 제외한 모든 부분이
C로 만들어졌습니다.
정말 굉장하죠..?
그리고 Windows 95도 커널의 일부분과 *.VxD만 빼면 모두
C로 만들어졌습니다. 그것두 우리가 잘 알고 있는 Visual C++의
버전 2.1로 만들어졌죠.

또하나 굉장한 사실이 있는데..
Windows NT는 커널 부분까지 C로 만들어졌다는 것이죠.
C언어로 만들어진 프로그램에는 게임도 많은데, 대부분의
게임은 C언어로 만들어졌다고 보면 됩니다.
게임을 시작할때

DOS/4GW Protected Mode Run-time Version 1.97
Copyright (c) Rational Systems Inc. ............
.
.

이런 것을 많이 보셨을 겁니다. 유명한 게임을 시작할땐 다
이게 나오는데, 이게 나오는건 C언어도 만들어진 것 입니다.
Watcom C++라는 개발 툴로 만들어진 것이죠.
또 한가지 놀라운 사실이 있는데, C언어 컴파일러도 C언어로
만든다는 것이죠.
이정도면 C언어로 못하는게 없다는 말이 이해가 되시겠죠?

그럼 이제 그 C언어의 세계로 들어가 보도록 하죠.

C언어에는 어떤 종류가..

'C언어에는 어떤 종류가..'라는 제목을 보고 글이 자꾸 읽고
싶어 지신다면 그 분은 분명 C언어 초보자 입니다.
하지만 이 제목을 보고선 무언가 이상하다고 느끼시면
진정한 전문가라고 할수 있죠.

이유가 궁금하시죠? 이유는 C언어에는 종류가 없기 때문이죠.
종류가 없는데 제목이 저러니 당연히 이상할수 밖에요.
C를 처음 하시는 분들이 많이 질문하시는 것들 중 하나가
'C와 Visual C의 차이점이 무엇인가요?' 입니다.
제가 이 질문을 받았다면 이렇게 답해 드리고 싶군요.
'C는 언어이고 Visual C는 언어가 아닙니다.' 라구요.
여기서 Visual C라는 건 Microsoft라는 회사에서 만든
C언어로 프로그래밍할때 필요한 도구를 말합니다.
즉 언어가 아니라는 것이죠.
단지 C언어로 프로그래밍을 할때 필요한 도구에 지나지
않습니다. 언어는 바로 C이죠.

그러므로 Visual C와 Borland C등은 문법에선 차이가
있을 수 없습니다.

정리하면 C는 언어이고, Visual C, Borland C등은 언어가
아니라 C언어를 사용해 프로그램을 제작하기 위한 도구이며,
모두 C언어 프로그램을 제작하기 위한 도구이므로 기본 문법의
차이는 있을수 없다는 것을 꼭 기억하시기 바랍니다.
그리고 여기서 한가지 말씀드릴것이 있는데, 위에 Visual C라고
썼지만 실제는 이게 아니고 Visual C++입니다.

그럼 또 한가지 의문이 생기실건데 Visual C++에서 C++가 아닌
C언어 프로그램을 작성할수 있는지 말입니다.

이 의문의 답은 Yes!입니다.

C++라는 언어는 C언어를 객체 지향 프로그래밍이란 것을 할수
있도록 확장시킨 언어이죠.
그래서 C++언어는 C언어를 포함한다고 할수 있죠.
그러므로 Visual C++에서 아무리 C++라고 쓰여져 있어도
C 프로그래밍은 가능합니다.

C언어는 어떻게..

이제 C언어 프로그램이 어떻게 실행되어 지는가를 알아봅시다.
C언어로 프로그램을 만들면 실행파일(.EXE)을 만들 수가 있습니다.
실행파일이 만들어질때는 다음과 같은 과정을 거칩니다.

(1)소스 코드 -------------> (2)소스 파일 ---------->
텍스트 에디터 선행처리기


(3)선행처리기 지시어가 번역된 소스 파일 --------->
컴파일러

라이브러리
(4)목적 파일 -----------> (5)실행파일
링커

(1) 소스 코드 (Source)

프로그램의 내용 자체를 소스 코드라고 합니다.

(2) 소스 파일 (Source file)

소스 코드를 텍스트 파일에 기술하여 만들어진 파일을
소스 파일이라고 합니다.
C언어의 소스 파일은 확장자가 .C입니다.
C언어 프로그램 만드는 가장 첫번째 과정이라고 할 수 있죠.

(3) 선행처리기 지시어가 번역된 소스 파일

C언어 프로그램 내에는 여러가지 지시어 들이 있는데
이건 C언어 문법과는 별개이고 번역도 컴파일러가 하지
않습니다.
이걸 번역하는 프로그램을 선행처리기라고 합니다.

(4) 목적 파일 (Object file)

지시어가 번역된 소스 파일은 다음으로 컴파일러라는 프로그램에
의해 기계어로 번역됩니다.
이 번역된 파일을 목적 파일이라고 하고, 확장자는 .OBJ가 됩니다.
그러데 여기서 이상한 점이 있죠?
기계어로 번역하면 실행할수 있을 건데 왜???????? 실행파일을
만들지 않고 목적파일이라는 만들까요?
하지만 기계어로 번역되었다고 해서 실행할수는 없습니다.
실행파일이 되기 위해선 런타임 라이브러리라는 것이 목적 파일과
합쳐져야 하기 때문이죠. 이 런타임 라이브러리가 무엇인지는
나중에 설명하기로 하고 그냥 런타임 라이브러리라는 것이
합쳐져야 한다는 것만 알아두세요.

(5) 실행 파일 (Executable file)

목적파일은 위에서 말한 런타임 라이브러리와 합쳐져서
실행파일이 되는데,이때 합쳐주는 프로그램을 링커라고 합니다.
링커에 의해 목적 파일은 실행파일이 되죠.
그럼 실행할수가 있게 됩니다.

C의 구조.

이정도 알았으면 이제 C언어 프로그램의 문법적인
구조를 살펴보기로 합시다.
이제부터 정말 C의 문법에 대한 공부를 들어갈텐데,
이번에 나올 C 프로그램은 기본적인 구조만 이해하시면 됩니다.
세부적인 부분은 모르셔도 됩니다.
기본적인 구조를 알고 계셔야 다음 강좌의 이해가 쉽기 때문이죠.

(1) 자유로운 형식

C언어는 다른 언어들과는 달리 형식이 매우 자유롭습니다.
우선 예제 소스 하나를 보며 공부해보도록 하죠.

/* 파일 이름 : C1-1.C
프로그램 내용 : 화면에 메시지를 출력하는 프로그램. */
#include
void main()
{
printf("This is a first program.");
}


이 소스는 아주 간단한 소스 입니다.
하지만 C를 처음 대하시는 분 이라면 아주 생소하게
느껴질 것 입니다.
이 소스 내용은 아직은 이해하지 못하셔도 되니까 겁먹지
마시고 잘 봐주세요.
하여튼 그건 그렇고 아까 그 소스(C1-1.C)와 다음에 나오는
소스를 비교해 보세요.

/* 파일 이름 : C1-2.C
프로그램 내용 : 화면에 메시지를 출력하는 프로그램. */
#include
void main(){printf("This is a first program.");}


이 소스들을 비교해 보세요.
주시해서 봐야 할 곳은 void...); }부분 입니다.
두 소스중 어떤 것이 보기가 쉽죠???
당연히 첫번째 소스가 더 읽기 쉬울 것 입니다.
하지만 이 두 소스는 완전히 똑같은 기능을 수행합니다.
결과를 봐볼까요???
두 개를 컴파일해서 C1-1.EXE, C1-2.EXE를 만들고 실행하면..

C:\>C1-1.EXE
This is a first program.
C:\>C1-2.EXE
This is a first program.
C:\>

똑같은 소스 이지만, 두번째 소스는 첫번째 소스를 한줄에
다 써 버린 것 입니다.
하지만 컴파일러는 똑같은 내용으로 인식하게 되죠.
여기서 말하고자 하는 것은 C언어 프로그램은 자유로운 형식을
갖는다는 것 입니다.
즉 여러줄에 써야 할 것을 한줄에 써 버려도 상관 없다는 것이죠.
또한 이 말은 한줄에 써야 할 것을 여러줄에 쓸 수도
있다는 말 입니다.
더 정확히 말하자면 C언어에서는 공백을 인식하지 않습니다.
즉 공백이 많이 있어도 그건 없는거나 마찬가지 이죠.
그러므로 첫번째 소스도 실제로 컴파일러에서는 두번째 소스처럼
인식하게 되는 것 입니다.
하지만 공백 없이 한 줄에 다 써 버리면 읽기가 힘들어 지므로,
되도록 보기 쉽게 공백을 두고 쓰는 것이 좋습니다.

(2) 대소문자 구별

C언어는 대문자와 소문자를 구별한다는 것을 기억해 두시기 바랍니다.
예를들어 Basic에서 화면에 메시지를 출력하는 명령어인 PRINT는
print라고 써도 상관 없고 Print, prinT, pRint등으로 써도 모두
같은 것으로 인식합니다.
하지만 C에선 그렇지 않습니다.
베이직의 PRINT의 기능을 하는 것으로 아까 예제 소스에서
나온 것인데..
C에서는 printf라는 것이 있습니다.
그런데 이걸 Printf, pRintf, PRINTF등등.. 이렇게 써서는
절대 안됩니다.
모두 다른 것으로 인식하기 때문에 반드시 printf로 써 주어야 하죠.
보통 C에서는 거의 다 소문자로 되어 있어요.
그리고 C언어가 지원하는 기본 어휘 그러니까..
베이직에서는 PRINT, INPUT, DIM등등의 명령어들과 같은 역할을
하는 기본적 어휘를 키워드(예약어)라고 하는데, 이것들도 모두
소문자로 되어 있죠.

(3) 주석 (설명문)

주석문은 프로그램과는 아무 상관이 없어 없는것과 마찬가지이지만,
프로그램의 내용을 설명하기위해 쓰는 글 입니다.
C에서 주석문을 쓰는 것은 간단합니다.
설명하는 글 앞에는 /* 를 붙이고 뒤에는 */를 붙이면 됩니다.
예를들어..

/* 주석문 입니다 */

이렇게 하면 주석문이 됨니다.
즉 없는 거나 마찬가지인 글이 되는 것이죠.
C++ 에서는 // 과 같은 한행 주석문도 허용합니다.
한 줄에서 // 라고 쓰인 부분 뒤로는 모두 주석으로
인식하는 거죠.
예제 소스를 하나 들어 보죠.

/* 파일 이름 : C1-3.C
프로그램 내용 : 주석문을 설명하기 위한 프로그램. */
void main()
{
int a, b, c; /* 정수 변수 a, b, c를 선언 */
a=10; /* a에 10을 넣는다. */
b=20; /* b에 20을 넣는다. */
c=a+b; /* c에 a와 b를 더하여 넣는다. */
}

여기서 /* */로 묶인 것은 모두 주석문 입니다.
즉 /* */로 묶인 것은 없어도 상관 없습니다.
그리고 주석문은 여러줄이 되어도 상관 없습니다.

/* 주석문 입니다..
이것두 주석문 입니다.. */

이렇게 써도 상관 없다는 것 입니다.
C에서는 한줄에 쓸 것을 여러줄에 써두 된다고 배웠는데,
위와 같이 주석문을 쓴 것두

/* 주석문 입니다.. 이것두 주석문 입니다.. */

이렇게 써야 할 것을 위와 같이 쓴 것 뿐이라고 생각하시면
이해가 되실 것입니다.
주석문을 쓰는 이유는 프로그램에 대한 설명을 써 둠으로써
읽기 쉽게하고 오류를 찾아내거나 수정하는 작업을 쉽게 하기
위해서입니다.

물론 간단한 프로그램의 경우 특별히 주석 없이도 충분히
읽을수가 있지만, 정말로 복잡한 프로그램에선 주석문이 큰
도움을 줄수 있죠.
주석문을 쓸 때는 공백을 최대한 이용하는 것이 좋습니다.
공백을 잘 이용하여 최대한 보기 쉽게 해야 하죠.
주석문이 필요한 이유가 프로그램을 읽기 쉽게 해서 오류를
찾거나 수정을 하는 일이 쉬워지게 하기 위해서인데, 주석문을
보기 어렵게 쓰면 않되겠죠?
예를 들어 봅시다.
위에 나왔던 소스와 다음 소스를 비교해 보세요.

/* 파일 이름 : C1-4.C
프로그램 내용 : 주석문을 설명하기 위한 프로그램. */
void main()
{
int a, b, c; /* 정수 변수
a, b, c를 선언 */
a=10; /* a에
10을 넣는다. */
b=20; /* b에 20을 넣는다. */
c=a+b; /* c에 a와 b를
더하여 넣는다. */
}

C1-4.C는 주석때문에 보기가 더 힘들어 졌죠?
주석문을 이렇게 써서는 절대로 않됩니다.

Previous:C언어 란
Next:C의 구성요소
C의 구성 요소

C프로그램을 구성하는 요소들에 대해 알아보도록 하죠.
C프로그램을 구성하는 요소에는 키워드, 심볼, 문장, 블럭등이 있습니다.

(1) 키워드

키워드는 저번 강의때 간략히 설명을 했었는데, 더 정확히 말하자면..
C 컴파일러가 특별한 의미로 인식하는 단어를 말합니다.
다른 단어들과는 달리 특별한 기능을 수행하게 되죠.
C언어 키워드는 다른 언어에 비해 적은 편 입니다.
이유는 C언어 키워드에는 표준 입출력에 관한 키워드가 들어있지 않기 때문이죠.
단지 데이터 처리와 프로그램의 흐름을 제어하는 키워드 밖에 들어있지 않습니다. 그렇기 때문에..
표준 입출력에 관한 것은 외부에 의존합니다.
저번히 예제에 나온 printf라는 것도 키워드가 아닙니다.
이것이 무엇인지는 오늘 배우게 됩니다.

(2) 심볼

심볼은 무언가를 상징하는 단어로 키워드와는 반대의 의미를 가지는 단어라고 할수 있습니다.
즉 이건 컴파일러가 특별한 의미로 인식하는 단어가 아닌 프로그램을 만드는 사람이 특별한 의미를 부여하는 단어 입니다.
키워드와는 다르게 프로그램을 만드는 사람이 특별한 기능을 수행하도록 만들게 되죠.
이 심볼에 해당하는 것은 오늘 배울 변수와 함수 이름등이 있습니다.
심볼은 한개 이상의 문자로 만들수 있으며 이때 사용할수 있는 문자는 알파벳과, 숫자, 언더 스코어('_')가 있습니다.
그런데 멘 처음 문자는 반드시 알파벳 또는 언더스코어가 와야 합니다.
예를 들자면..

symbol, _symbol, symbol1

이런것들은 심볼이 될수 있지만..


1symbol, 2symbol

이런건 에러가 나게 됨니다..

(3) 문장

문장은 프로그램을 구성하는 중요한 요소로 실행 단위가 된다고 할수 있습니다.
문장에는..


a=b+c;

printf("Example");


이런 것들이 있으며, 문장 이 끝나면 반드시 ';'를 써 주어야 합니다.
;이 문장의 끝을 알리는 표시 이죠.


(4) 블럭

블럭은 문장을 묶어 둔 것 입니다.
즉 한개 이상의 문장이 모여 있는 것을 말하죠.
블럭은 {로 시작해 }로 끝남니다.
예를들어..


{
a=b+c;
b=a+c;
}


이건 블럭이라 할수 있습니다.
그리고 블럭은 중첩될수도 있습니다.



{
a=b+c;
{
b=a+c;
}
}


이런 식으로 말입니다.


C 프로그램의 구조.

예제 소스를 하나 보며 공부하도록 하죠.



/* 파일 이름 : C2-1.C
프로그램 내용 : 계산에 대한 결과를 출력하는 프로그램 */
#include

int add(int,int); /* 두 수를 더하는 함수 */

void main()
{
int result; /* 변수 선언 */

result=add(3,5); /* add함수를 호출해 3과 5를 더한다. */
printf("Result:%d\n",result); /* result의 값을 출력한다. */
}

int add(int a, int b)
{
return(a+b); /* a와 b를 더해서 리턴 */
}


이 프로그램은 아주 간단하지만 처음 하시는 분은 좀 복잡해 보일 겁니다.
우선 저번 강좌 내용을 들춰 보면서 그때 나온 소스들과 이 소스를 비교해 보세요.
이 소스와 저번 강좌에 나왔던 여러 소스를 비교해 보면 공통점이 몇 가지 있을 것 입니다.
우선 프로그램 처음에 #include 라는 것이 꼭 있고 void main()이라는 것이 어느 소스에나 있을 겁니다.
이것들이 왜 공통적으로 들어 있는지 소스 분석을 통해 알아보죠.

(1) 선행처리기 지시어.

여기서 한 줄씩 살펴보기로 하죠.
우선 처음에는


#include

이런 문장이 있습니다.
이 문장처럼 #가 붙은 것을 선행처리기 지시어라고 합니다.
저번 강의때 들은적이 있으실 겁니다.
이 선행처리기 지시어는 메크로 기능, 파일 포함 기능, 선택적 컴파일 기능등 여러 기능을 수행합니다.
그중 여기 나온 #include라는 지시어는 지정된 파일을 프로그램 내에 포함시키라는 지시어 입니다.
그러니까 위와 같은 경우 stdio.h라는 파일을 프로그램 내에 포함시키라는 것이죠.
자세한 사용법을 살펴보면

#include <포함할 파일>
#include "포함할 파일"

이렇게 두가지 형식이 있습니다.
처음 방법은 지정된 디렉토리에서 파일을 찾아 포함시키는 것 입니다.
지정된 디렉토리는 흔히 개발 도구의 INCLUDE디렉토리를 가르키는 것으로 그 안에 들어 있는 파일은 개발 도구를 만든 곳에서 지원하는 것 들입니다.
두번째 방법은 현제 위치한 디렉토리에서 파일을 찾아 포함시키는 것 입니다.
예를들어 현제 디렉토리의 a.h라는 파일이 있는데, 그걸 포함시키고 싶다면.


#include "a.h"

이렇게 하면 됨니다.
대부분의 프로그램에서 stdio.h라는 파일을 포함시키는 이유는 차차 배우게 될 것입니다.


(2) 함수와 main함수

두번째 줄은 일단 넘어가기로 하고 다음을 보면



void main()
{
int result; /* 변수 선언 */

result=add(3,5); /* add함수를 호출해 3과 5를 더한다. */
printf("Result:%d\n",result); /* result의 값을 출력한다. */
}


이것이 있습니다.
이렇게 생긴 것들을 함수라고 하는데 함수란 어떤 내용을 처리하고 그 결과를 리턴하는 프로그램의 한 부분입니다.
반드시 하나의 블럭으로 되어 있어야 하죠.
그리고 그 안에는 그 함수가 처리할 내용이 들어가게 됨니다.
함수는 다음과 같은 구조로 만듬니다.


리턴형태 함수이름([인수, 인수, 인수...])
{
/* 프로그램 내용 */
.
.
}


여기서 리턴형태이란 처리하고 난 후의 리턴할 결과의 형태가 무엇인지를 쓰는 것 입니다. 즉 정수형태인지, 문자열인지 등등..
결과를 리턴하지 않는 경우 void를 씀니다.
함수 이름은 그 함수를 상징하는 심볼 이죠.
인수는 그 함수가 어떤 내용을 처리할때 필요해서 전달받아야 할 데이터 입니다. 예를 들어 화면에 무언가를 출력하는 함수를 만든다고 합시다.
출력하기 위해선 출력할 내용을 전달받아야 하는데 그때 인수를 통해 전달받게 됨니다. 전달받을 데이터가 없으면 안써도 됩니다.
우선 지금 나온 함수는 main이라는 함수인데 이 함수는 어떤 프로그램이든지 존재하고 있었죠?

왜 그럴까요????????????????????
이유는 C언어 프로그램은 바로 main함수에서 시작하게 되기 때문이죠.
C프로그램의 실행되면 이 main함수를 시작으로 프로그램이 진행되고 main함수가 끝나면 프로그램도 끝나게 됩니다.
그러므로 반드시 만들어야할 함수 입니다.

main함수안에 나오는 것을 하나씩 분석해 보면.


int result; /* 변수 선언 */



처음에 이게 나오는데.. 이건 변수를 선언하는 것 입니다.
그냥 어떤 값을 저장하기 위해 기억장소를 마련하는 것이라 생각하시면 됨니다.

다음줄은


result=add(3,5); /* add함수를 호출해 3과 5를 더한다. */



이것인데..
이것은 바로 함수를 호출하는 것 입니다.
그리고 나서 그 결과를 result라는 기억장소에 보관하라는 뜻이죠.
그러니까 지정한 함수 내에 처리 내용을 실행시키고 그때 나온 결과를 result라는 변수에 보관하는 문장입니다.
함수 호출법은 다음과 같습니다.


[변수=]함수이름([인수,인수,인수..]);

여기서 변수는 함수의 내용이 처리된 후 리턴하는 결과를 넣을 기억장소를 말합니다. 결과를 리턴하지 않으면 쓰지 않아도 됩니다.
또한 리턴한 결과를 따로 저장할 필요가 없을때도 쓰지 않아도 됩니다.
그리고 함수이름은 호출할 함수를 상징하는 심볼, 인수는 함수가 하는 처리에 필요한 데이터입니다.
함수를 호출할 때는 그 함수가 만들어져 있어야 합니다.
그리고 함수가 호출하려는 곳보다 밑에 만들어 져 있으면 좋지 않죠. 이유는 위에서는 함수가 있는지를 모르거든요.
그런데 여기선 add라는 함수가 밑에 있죠?
이럴땐 함수를 위에 선언해 주면 좋습니다.
함수가 있다는 것을 알려주는 것이죠.
꼭 그래야 하는 건 아니지만, 요즘 컴파일러들은 경고를 주게 되죠.
그래서 add함수를 선언한 것이 main함수 위에 있는 것인


int add(int,int); /* 두 수를 더하는 함수 */

이거 입니다. 함수 선언은 간단한데..


리턴형 함수이름([인수, 인수, 인수...]);

이렇게 하면 됩니다. 이것들이 무슨 뜻 인지는 다 아시겠죠?
이렇게 함수를 선언한것을 C에서는 프로토타입을 선언한다고 합니다. 이렇게 프로토타입을 선언하면 에러가 방지되는 등의 좋은 점이 있으므로 프로토타입을 선언하는 습관을 기르는 것이 좋습니다.
이제 add를 호출했으니 add내의 내용을 살펴보죠

add함수를 보면

int add(int a, int b)
{
return(a+b); /* a와 b를 더해서 리턴 */
}



이렇게 되어 있습니다.
우선 리턴하는 결과는 정수입니다. int가 정수를 의미하거든요.
int와 같은 것에 대해서는 다음 강좌때 배움니다.
그리고 인수는 a, b두개인데, 아까


result=add(3,5);

이렇게 호출했으므로 a에는 3이, b에는 5가 각각 들어갑니다.
그리고 다음줄은


return(a+b); /* a와 b를 더해서 리턴 */


이것인데..
이건 함수의 처리를 끝내고 함수를 빠져 나오라는 것 입니다.
또한 함수를 빠져 나올때 a와 b를 더해서 결과로 리턴하라는 것이죠.
여기에 쓰인 return은 이렇게 함수를 빠져 나올때 쓰입니다.
사용법은


return [리턴값];


이때 리턴값이란 결과로 리턴을 해 줄 값을 말합니다.

예를들어 3을 리턴해 주려고 한다면.


return 3;

이렇게 써 주면 됨니다..
그리고 리턴값을 (와 )로 묶어주면 더 좋겠죠?


return(3);

이렇게..
특히 리턴값이 식으로 표현될때는 묶어주는 습관을 기르도록 하세요..


return(a+5);

이렇게 말이에요.
만약 함수가 아무 값도 리턴을 하지 않는다면, 리턴값은 생략해도 됨니다.

add함수가 3+5를 더한 값인 8을 결과로 리턴하겠네요?
그리고 리턴을 했으니 다시 main함수로 돌아와야 겠죠?
이때 아까


result=add(3,5);

이렇게 호출했으므로 result에는 8이 들어가겠군요.
그럼 다음줄을 볼까요.


printf("Result:%d\n",result); /* result의 값을 출력한다. */

이것 역시 함수를 호출하는 건데..
result값을 화면에 표시하는 기능을 하는 것 입니다.
그런데 좀 이상한게 있지 않나요??
printf라는 함수는 만들지도 않았는데 어떻게 호출했을까요??
그리고 선언 조차도 되어 있지 않죠?
이 함수는 바로 stdio.h라는 파일에 선언되어 있습니다.
stdio.h파일을 포함하는 이유가 바로 이것 때문이죠.
물론 선언을 하지 않아도 상관 없지만 말입니다.
그런데 이 함수를 선언했다 해도 실제 함수가 만들어져 있지 않죠?
이 함수는 C언어 개발 패키지를 만든 곳에서 지원하는 함수로 그곳에서 만들어 컴파일해서 목적 파일로 만든 후 기타 다른 함수들의 목적 파일과 같이 하나의 파일로 묶여 있습니다.
이렇게 목적 파일 한개 이상을 묶은 것을 라이브러리 라고 하고 확장자는 .LIB가 됨니다.
저번 강의때 들은적이 있죠? 라이브러리란 말을..
그리고 저번 강의때 나온 런타임 라이브러리가 바로 printf함수가 들어있는 라이브러리로 이런 이유때문에 링크시에 런타임 라이브러리가 합쳐지는 것이죠.
런타임 라이브러리가 없으면 printf라는 함수를 쓰지 못하게 되거든요.
이렇게 해서 main함수가 끝났네요 그럼 프로그램이 종료되게 됩니다.
이제 프로그램의 결과를 보기로 하죠.
분석한게 맞았다면 결과가 8이 되겠죠?



C:\>C2-1.EXE
Result:8

C:\>




역시 맞군요..


(3) 다시한번 정리..
이제 간단히 C프로그램의 구조를 정리해보겠습니다.
C프로그램은 기본적으로 다음과 같은 구조로 되어 있습니다.


1. 선행처리기 지시어 부분.

2. 전역 데이터 및 함수 선언 부분.

3. 함수 부분.

처음으로 선행처리기 지시어 부분은
말 그대로 선행처리기 지시어들을 쓰는 부분입니다.
#include외에 여러가지 지시어들은 나중에 배우게 됩니다.
그리고 선행처리기 지시어는 반드시 #로 시작한다는 것을 기억하세요.

두번째 부분은 전역 변수 및 함수 선언 부분 인데 전역 데이터를 선언하거나 함수를 선언하는 곳 입니다.
전역 데이터란 모든 함수에서 사용할수 있는 데이터를 말하는 것으로 다음에 자세히 배움니다.
그리고 함수 선언은 무엇인지 배우셨고...

세번째 부분은 함수 부분 인데 실제적인 프로그램을 기술하는 곳으로서 가장 중요하다고 볼수 있죠.
특히 main함수는 반드시 만들어야 한다는 것을 잊지 마시기 바랍니다.

마지막으로 두번째부분은 다른 파일에 따로 기술하여 #include지시어를 통해 포함시키기도 하는데
이때 그 다른 파일을 헤더(앞부분이기 때문)파일이라고 하고 확장자는 .H입니다
그리고 stdio.h도 헤더파일 입니다.

Previous:C의 구조
Next:자료형(DATA TYPE)
C언어에서의 데이터의 종류

C언어에서 데이터를 크게 분류하자면 수치 데이터와 문자 데이터로 분류할수 있습니다.

수치 데이터는 0, 1, 100, 200등의 정수와 1.0, 2.0등등의 소수가 있고, 문자 데이터에는 'A', 'B'등의 문자와, "ABCDEFG"등의 문자열이 있습니다.

그리고 데이터는 상수와 변수로도 구분할수 있습니다.


변수

변수란 프로그램에서 변할수 있는 값을 말합니다.

정확히 말하지면 어떤 기억장소가 주어지는데 그 안의 값을 마음대로 바꿀수 있는 기억장소를 말합니다.

변수를 사용하기 위해선 반드시 변수를 선언해야 합니다.

즉 기억장소를 마련해야 쓸수 있는 것이죠 그럼 변수를 선언하는 방법에 대해 알아볼까요? 변수 선언은..


변수의자료형태 변수명[=초기값, 변수명=초기값...];


이렇게 합니다.

여기서 변수의자료형태는 변수에 저장할 값이 정수인지 소수인지 문자인지 등등을 정하는 키워드를 쓰는 것 입니다.

그리고 변수명은 그 변수를 상징하는 심볼이고 초기값은 처음에 들어갈 값인데 초기값이 필요 없으면 쓰지 않아도 됨니다.


그럼 자료형 키워드에 대해 알아보죠.


(1) 문자형

문자형은 아주 작은 범위의 숫자를 다룰 때나, 아스키(ASCII) 문자를 표현하기 위해 사용되는데 부호에 따라 char, unsigned char의 2가지가 있습니다.

이건 8비트로 되어 있고 1문자를 넣을수 있습니다.


(2) 정수

C에서 정수형을 나타내는 키워드는 int입니다.

int는 정수라는 뜻의 integer를 줄여쓰는 것입니다. 정수형에는(signed) int, unsigned (int), (signed) long (int), unsigned long (int)의 4가지가 있습니다.

( ) 표시한 부분은 생략 가능하다는 뜻이죠. 그래서 통상 C의 정수형은 int, unsigned, long, unsigned long 의 4가지가 있다고 기억하면 됩니다.

이 형태의 크기는 환경에 따라 달라집니다.

즉 16비트 프로그램의 경우 이 크기는 16비트이고, 윈도우즈 95와 같은 32비트 프로그램에선 32비트 입니다.

하지만 지금은 16비트 환경인 도스에서 하므로, 이 크기는 16비트가 됩니다.

그러므로 여기에는 -32768에서 32767까지 넣을수 있습니다.

그런데 크기를 16비트 또는 32비트로 정확히 정하고 싶을때가 있죠?

그럴땐 short, long키워드를 사용합니다.

short또는 long를 int앞에 써주면 되죠


short int a=3;

long int b, c;


이런 식으로..

여기서 short를 써주면 16비트가 되고 long를 써주면 32비트가 됨니다.

그리고 이때는 int를 생략해도 됨니다.


short a=3;

long b, c;


이렇게..

만약 long를 썼다면 32비트 이므로 -2147483648에서

2147483647까지 넣을수 있죠.


(3) 소수

그리고 다음은 소수에 형태의 키워드 입니다.

소수 형태에 키워드에는 float와 double이 있습니다.

두개의 차이는 float는 32비트이고 double는 64비트라는 것 입니다.

float에는 -3.4E-38에서 3.4E+38까지를 넣을수 있고

double에는 -1.7E-308에서 1.7E+308까지를 넣을수 있죠.

여기서 E다음에 나오는 수는 지수를 말합니다.

즉 -3.4E-38은 -3.4의 -38승을 말합니다.


(4) 숫자와 문자간의 관계

숫자와 문자간의 관계를 알아보죠.

C에서는 숫자가 곧 문자이고 문자가 곧 숫자라는것을

알아두시기 바랍니다. 즉 숫자 65는 곧 아스키 코드 65인 문자 A이고

문자 A는 곧 숫자 65입니다. 그러니까 65는 A라는 것이죠.

그러므로


char a='A';


이것은


char a=65;


이것과 똑같습니다.

또한 여기서처럼 char형에도 숫자를 넣을수가 있습니다.

char형에는 -128에서 127까지 넣을수 있죠.

물론 int형에도 문자를 넣을수가 있습니다.


(5) 부호 처리

이번엔 부호 처리를 결정하는

signed와 unsigned에 대해 알아보도록 하겠습니다.

우선 이것은 char형과 int형에 대해서만 사용할수 있다는 것을 알아두세요.

char과 int형에는 +와 -부호가 있습니다.(물론 float, double에도 있지만..)

int형의 경우 16비트 중에서 1비트가 부호를 나타내는 것이고,

나머지 15비트가 숫자 크기를 나타내죠.

이 1비트를 sign비트라고 합니다.

이런 sign비트가 있는 변수를 만드느냐 아니면 없는 변수를 만드느냐를

정하는 키워드가 signed와 unsigned입니다.

이것은 변수형 앞에 써 주면 되고 signed를 쓰면 sign비트를 만들고

unsigned를 쓰면 sign비트를 만들지 않습니다.

예를 들자면..


signed int a;

unsigned int b;


이런 식으로..

그런데 signed는 디폴트 입니다.

즉 쓰지 않으면 signed로 하는 것이죠.

그러니까


signed int a;


이건


int a;


이거랑 똑같습니다..

만약 unsigned를 쓰면 sign비트가 없고 모든 비트를

숫자 크기를 나타내는데 씀니다. 그래서 부호를 처리하지 않습니다.

그러므로 양수만을 넣을수 있지만 더 큰 수를 넣을수 있죠.

unsigned char에는 0에서 255까지

unsigned int나 unsigned short에는 0에서 65535까지

unsigned long에는 0에서 4294967296까지 넣을수 있습니다.


(6) 변수에 값 치환

다음으로 변수에 값을 넣는 방법을 알아야 하는데

이건 아주 간단합니다.


변수명=값;


이렇게만 해주면 되거든요.

예를 들자면..


a=10;


이렇게 하면 a에 10을 넣으라는 것이죠.


이것으로 변수에 대한 설명을 마치겠습니다.

그럼 지금까지 나온 변수 형태를 정리해 보죠.



bit
byte
범 위

문자형
char
unsigned char
8

8
1

1
-128∼127

0∼255

열거형
enum 16
2
-32768∼32767

정수형
int
unsigned

long

unsigned long
16

16

32

32
2

2

4

4
-32768∼32767

0∼65535

-2147483648∼2147483647

0∼4294967295

부동형
float
double

long double
32

64

80
4

8

10
±3.4e-38∼±3.4e+38

±1.7e-308∼±1.7e+308

±3.4e-4916∼±1.1e+4932


여기서 문자열에 관한 데이터형은 없는데 C에는 문자열 데이터형이 없습니다.

그래서 특별한 방법으로 문자열을 처리하기 때문에 조금 후에 배우기로 하죠.


상수

상수는 프로그램에서 변할수 없는 값을 말합니다.

상수는 크게 두가지로 나눌수 있는데 바로 실재값과

기억장소를 갖는 상수 입니다.


(1) 실재값

실재값은 말 그대로 실재값 입니다. 실재값이라고 밖에는 설명할수가 없겠군요.

실재값중 수를 나타내는 실재값을 알아보죠.

수를 나타내는 실재값은 정말 쉽습니다.

10진수의 경우 부호를 쓴 후 값을 그대로 써주면 됨니다.

예를 들어


+20, -20, 5.2


이것 처럼 말입니다.

그런데 부호가 +일 때는 생략해도 됨니다.

그리고 8진수의 경우는 앞에 0을 써주면 됨니다.

예를 들어 013은 8진수 13이고, -015는 8진수 -15입니다.

또한 8진수는 0에서 7까지의 8개의 수만 쓴다는 것도 기억하세요.

마지막으로 많이 쓰이는 16진수는

앞에 0x를 써 주면 됨니다.

예를 들어 0x1A는 16진수 1A가 되고, -0x15는 16진수 -15가 되는 것 입니다.

16진수는

0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F

이렇게 16개를 사용해 수를 표현합니다.


다음으로 문자를 나타내는 실재값이 있습니다.

문자를 나타낼 때는 반드시 문자를 ''로 묶어야 합니다.

""로 묶으면 문자열로 취급하기 때문에 반드시 ''로 묶어야 하죠.

''로 묶을때와 ""로 묶을때의 차이는 조금 후에 알아보기로 하고..

문자 A를 나타낼 때는


'A'


이렇게 쓰면 됨니다. 더이상 설명이 필요 없죠?

이제 실재값에 대해서는 이해하셨을 겁니다.


(2) 기억 장소를 갖는 상수

다음은 기억 장소를 갖는 상수 인데, 이것은 변수와 똑같으나

값을 변경하지 못하는 것 입니다.

이것도 변수처럼 선언해야 사용할수 있는데,

선언하는건 변수 선언과 똑같지만 앞에 const키워드를 써야 하고

반드시 초기값이 있어야 한다는 것이 다름니다.

예를 들어..

100이란 정수 값을 같는 a라는 이름의 상수는


const int a=100;


이렇게 선언합니다.

사용법도 변수와 똑같으나 안에 있는 값을 변경할수 없다는 것이 다름니다.

이해가 되셨죠?

Previous:C의 구성요소
Next:문자열과 특수문자
문자열

C언어 에는 문자열에 관한 데이터 형태가 없기 때문에

문자열을 처리하기 위해서는 특별한 방법을 사용해야 합니다.

그 방법 중에는 배열을 사용한 방법과 포인터를 사용한 방법이 있는데

포인터를 사용한 방법은 포인터를 모른상태에선 이해가 어려울 것 입니다.

그래서 여기서는 배열을 이용한 방법만을 배우기로 하고

포인터를 사용한 방법은 포인터를 배운 후에 배우겠습니다.


(1) 문자열 상수 (실재값).

상수 중에서도 실재값 표현을 대해 배워보겠습니다.

문자열 실재값은 아주 간단한데

그냥 ""로 문자열을 묶어 주기만 하면 됨니다.

예를 들자면


"String"


이런 식으로

정말 간단하죠? 그럼 이 상수가 내부적으로는 어떻게 처리될까요?

메모리 상에는 이런 식으로 들어갑니다.


주소 0x01 0x02 0x03 0x04 0x05 0x06 0x07
아스키 코드 0x53 0x74 0x72 0x69 0x6E 0x67 0x00
문자 S t r i n g (NULL)
(주소는 임의로 정한 주소임)




이렇게 문자열은 6문자 인데 7바이트를 사용하게 되죠.

여기서 주소 0x01부터 0x06까지는 String의 각 문자에 해당하는

아스키 코드 값이 들어가 있습니다. 그리고 마지막 주소 0x07에는

0x00이 들어가 있는데

이렇게 아스키 0x00인 문자를 NULL문자라고 합니다.

C에서는 이 NULL문자를 사용해 문자열을 처리하는데

시작 주소부터 이 NULL문자가 나올때 까지를 문자열로 하는 것이죠.

즉 모든 문자열의 끝에는 NULL이 있다는 것 입니다.

물론 이 NULL문자는 컴파일러가

알아서 넣어 줌니다.

그리고 문자열의 길이는 이 NULL문자 전까지의

길이를 문자열의 길이로 하게 됨니다.


그럼 문자를 ''로 묶는 것과 ""로 묶는 것의 차이점은 다 아셨겠죠?

'A'와 "A"가 있다고 합니다.

''로 묶으면 그냥 하나의 문자로

처리하게 되고 단지 1바이트가 되는 것이죠.

하지만 ""로 묶으면 문자열로 처리하게 되고 뒤에 NULL문자가 붙으므로

1바이트가 아닌 2바이트가 됩니다.


(2) 문자열 변수

C에서는 변수에서 문자열 형태가 없으므로 문자 배열을 사용하면

문자열 처리가 가능합니다.

배열은 아직 배우지 않았는데 자세히는 나중에 배우고

배열에 대해 간단히 알아보죠.

배열은 어떤 같은 형태의 데이터를 쭉 연결해서 늘어 놓은

구조라고 생각하시면 됨니다.

그러면 배열 선언 방법을 알아보죠. 아주 간단한데..


데이터형태 배열이름[배열크기];


이렇게 선언합니다.

데이터형태는 배열의 데이터형태 이고, 배열이름은 그 배열을 상징하는 심볼

배열크기는 그 배열의 크기 즉 몇개의 데이터를 늘어 놓을지 쓰는 것 입니다.

사용할때에는


배열이름[첨자]=값;

변수=배열이름[첨자];


이런 식으로 사용합니다.

여기서 첨자는 배열에서 늘어놓은 데이터중 몇번째것을 사용할 것인지를

정하는 것 입니다. 이때 첨자는 0부터 시작해야 하므로

첫번째 것을 지정하기 위해선 0을 써 주어야 합니다.


그럼 이제 진짜로 문자배열을 사용한 문자열 처리법을 알아보죠.

문자열을 저장할 문자배열을 선언하는 방법은


char 배열이름[문자열길이]=초기값;


이렇게 알아두면 됨니다.

여기서 문자열 길이는 실재 길이보다 1이 더 크게 써야 합니다.

이유는 문자열 뒤에는 꼭 NULL문자가 더 붙기 때문이죠.

그럼 이렇게 선언되었으면

이 변수 안에 문자열을 넣는 방법을 알아야 겠죠??


char str[10];


이런 문자 배열이 있다고 합시다.

그럼


str="String";


이렇게 하면 될까요??

절대로 이렇게 할수는 없습니다.

이유는 아까 배열을 사용할때는


배열이름[첨자]=값;

변수=배열이름[첨자];


이렇게 사용해야 한다고 배웠기 때문이죠. 그런데 위에는 첨자를 주지 않았습니다.

그럼 어떻게 해야 할까요??

바로..

str[0]='S';
str[1]='t';
str[2]='r';
str[3]='i';
str[4]='n';
str[5]='g';
str[6]=NULL;


이렇게 하면 되겠죠...퍽퍽~(사람들에게 맞는 소리)...으악..!

이 방법이 좀 복잡하다구요??????

만약 문자열의 길이가.. 100문자라면....윽....정말 큰일이군요..

그래서 제공하는 함수가 있는데 string.h에 정의되어 있는

strcpy라는 함수 입니다.

이 함수를 쓰면 문자배열에 간단히 문자열을 넣을수 있죠

사용법은


strcpy(문자배열이름,문자열);


이런 식으로 씀니다.

예를 들어 아까 그것은


strcpy(str,"String");


이렇게 간단히 할수 있습니다. 이제 잘 아시겠죠?


(3) 문자열 상수 (기억 장소를 갖는 상수)

문자열 상수 중 기억장소를 갖는 문자열 상수에 대해 알아보죠.

문자열 상수도 다른 상수처럼 선언하는 문장 앞에 const만 붙여 주고

초기값을 주면 됨니다.

예를 들어..


const char str[10]="String";


이렇게 하면 되죠.

그런데 이때 문자열의 크기는 생략해도 됨니다.

생략하면 컴파일러가 알아서 지정해 줌니다.

예를 들어..


const char str[]="String";


이렇게 하면

String의 길이인 6에 1을 더해 7로

자동으로 지정해 줌니다.


이것으로 문자열 설명을 마치고 예제 하나를 보며 분석해 보죠.



/* 파일 이름 : C3-1.C
프로그램 내용 : 문자열 처리 프로그램. */
#include
#include
void main()
{
char str[10];

strcpy(str,"String");
printf("%s\n",str);
str[1]='A';
printf("%s\n",str);
}


이 프로그램을 분석해볼까요? 우선 입출력 함수인 printf와

문자열 복사 함수인 strcpy를 썼으므로 stdio.h와 string.h를

포함시켰습니다.

그리고 main함수가 있고 내용을 보면

처음엔 str이라는 문자배열을 선언한 것이죠.

그리고


strcpy(str,"String");


이건 str에 "String"를 넣은 것이고요.


printf("%s\n",str);


이건 str에 들어있는 문자열을 출력한 것 입니다.


str[1]='A';


이건 str배열 중 2번째 문자를 'A'로 바꾼 것이죠

왜 두번째 문자냐구요??

첨자의 시작은 0부터이니 1은 두번째 문자가 되죠.

그 다음에는 다시 출력한 것 입니다.

결과를 볼까요??


C:\>C3-1.EXE
String
SAring
C:\>


특수 문자

이제 마지막으로 특수 문자에 대해 알아보죠.

특수 문자는 말 그대로 특별한 문자인데, 예를 들자면

""안에서 "를 쓴다던지 할 때에 쓰입니다.

특수 문자는 모두 \(역슬래쉬)로 시작합니다.

그럼 특수 문자를 자세히 살펴보죠.



\a Beep음을 컴퓨터 스피커로 출력
\b Back space(한칸 뒤로 갑니다..)
\n 현재 위치한 줄의 다음 줄로 내려갑니다.
\r 현재 위치한 줄의 멘 처음으로 갑니다..
\t 수평 Tab
\v 수직 Tab
\\ \(역슬래쉬)
\' 작은 따옴표
\" 큰 따옴표
\0 NULL문자
\0?? 8진수 ??에 대한 문자
\x?? 16진수 ??에 대한 문자.





위에서 다 읽어보면 아실건데

\0??의 예를 들어보면..

\013은 아스키 코드 8진수 13에 해당하는 문자이고..

\x1F는 아스키 코드 16진수 1F에 해당하는 문자입니다..


마지막으로 한가지 예제 소소를 보도록 합시다..



/* 파일 이름 : C3-2.C
프로그램 내용 : 특수 문자 예제 프로그램. */
#include
void main()
{
printf("C Programming\n");
printf("C \bProgramming\n");
printf("C Program\nming\n");
printf("\"C Programming\"");
printf("C Program\tming\n");
printf("C Programming\a\n");
}





이 프로그램을 한번 잘 보시기 바랍니다

그리고 어떤 결과가 나올지 맞춰 보세요

이정도는 충분이 분석하실수 있겠죠?



C:\>C3-2.EXE
C Programming
CProgramming
C Program
ming
"C Programming"
C Program ming
C Programming('삐'소리 발생)

C:\>





결과는 이렇습니다

왜 그런지는 잘 분석해 보세요.

이번 강좌를 잘 보셨다면 충분히 이해가 되실 겁니다.

Previous:자료형(DATA TYPE)
Next:지역변수,전역변수,정적변수
지역 변수

지역 변수란 어떤 한정되 지역 에서만 사용할수 있는 변수를 말합니다.

블럭 안에서 선언된 변수는 모두 지역 변수이고, 이때 이 블럭이라는

한정된 지역에서만 이 변수를 사용할수 있습니다.

그리고 지역 변수는 그 변수가 선언된 블럭이 끝나면

그 변수에 들어있는 값을 잃게 됨니다.

참고로 지역 변수를 선언할때는 블럭의 윗부분에 선언을 해야 합니다.

모든 작업 전에 선언해야 하죠. 그렇지 않으면 에러가 납니다.

예제 소스 하나를 보도록 하죠.



/* 파일 이름 : C4-1.C
프로그램 내용 : 지역 변수를 설명하는 프로그램. */
void main()
{
int a;
{
int b;
a=5;
b=10;
}
a=10;
}





이 소스는 지역 변수를 설명하기 위한 간단한 소스 입니다.

우선 a라는 변수는 main함수의 블럭 안에 있으므로 지역 변수가 됨니다.

그러므로 main함수의 블럭 안에서만 사용 가능하죠.

그리고 main함수 안에는 또 블럭이 있습니다.

그리고 그 블럭 안에는 변수 b가 선언 되어 있는데

이것두 블럭 안에 있으므로 지역 변수가 되죠 또한 이거 역시 그 블럭 안에서만

사용할수 있습니다.

그런데 그 블럭에서 변수 a에 5를 대입하고 있는데

a는 이 블럭 밖에 선언되어 있죠..?

이렇게 블럭 밖에 있는 변수에 대해서는

모두 사용이 가능합니다.


전역 변수

전역 변수는 지역 변수와는 다르게 한정된 지역이 아닌 모든 지역에서

사용이 가능한 변수를 말하는 것 입니다.

블럭 밖에서 선언된 변수는 모두 전역 변수 이죠.

그리고 전역 변수는 프로그램이 끝날때까지 들어있는 값을

계속 유지합니다.

그럼 예제 소스를


/* 파일 이름 : C4-2.C
프로그램 내용 : 전역 변수를 설명하는 프로그램. */
int a;

void func()
{
a=5;
}

void main()
{
func();
a=10;
}




이 소스에서 모든 블럭 밖에 a라는 변수가 선언되어 있으므로

a는 전역 변수 입니다.

그러므로 어느 곳에서든지 사용이 가능하죠

소스에소 볼수 있는것 처럼 a라는 변수는 main함수와 func라는 함수에서

모두 사용할수 있습니다.

그러면 에제 하나를 더 볼까요?



/* 파일 이름 : C4-3.C 프로그램 내용 : 전역 변수를 설명하는 프로그램. */ void func() { a=5; } int a; void main() { func(); a=10; }


입출력 함수 1

이제 입출력 함수 2가지를 배워 보죠.

화면에 입출력 하는 함수로 가장 많이 쓰이는 것이

printf함수와 scanf함수 입니다.

이것들에 대해 자세히 공부해 보도록 하죠.


(1) 출력 함수 printf

화면 출력 함수중 가장 많이 쓰이는 printf함수를 배워 보겠습니다.

이 함수는 지금까지 예제 소스에 많이 나왔던 것 이지만 따로 배운적은

없습니다.

이제 그 printf함수에 대해 알아보죠.

printf함수는 stdio.h라는 헤더 파일에 정의되어 있습니다.

그리고 사용법을 간단히 본다면 다음과 같죠


printf(포맷문자열[,인수,인수..]);


여기서 포맷 문자열이란 출력하는 형태를 지정하는 문자열이고

인수는 출력할 내용들 입니다.


그럼 포맷문자열을 자세히 알아볼까요

포맷문자열을 구성하는 것은 일반 문자와 변환지정이 있습니다.

일반 문자에 대해서는 화면에 그냥 출력되게 됨니다.

예를 들어


printf("Example");


이렇게 하면 Example이 그냥 화면에 출력되죠

그런데 printf에서 중요한건 변환지정 입니다.

변환지정은 %로 시작합니다. 사실 변환지정은 정말 무지무지하게

복잡하지만 지금은 간단한 것들만 배우도록 하죠.

나머지는 나중에 더 자세히 배우기로 합시다.

사실 자세하게 알 필요도 없지만..

변환지정은 간단히


%[크기]타입


이런 구조를 가진다고 보시면 됨니다.

우선 타입부터 배우도록 하죠

타입 지정은 여러가지가 있는데 다음을 보도록 하세요.


문자 인수 형태 출력
------------------------------------------------------------------
d 또는 i 정수 부호있는 10진 정수
u 정수 부호없는 10진 정수
o 정수 부호없는 8진 정수
x 정수 부호없는 16진 정수(a,b,c,d,e,f사용)
X 정수 부호없는 16진 정수(A,B,C,D,E,F사용)
f 소수 부호있는 소수([-]???.??형태)
e 소수 부호있는 소수([-]???.??..e[+/-]???형태)
g 소수 주어진 값에 따라 f또는 e를 자동으로 선택..
E 소수 부호있는 소수([-]???.??..E[+/-]???형태)
G 소수 주어진 값에 따라 f또는 E를 자동으로 선택..
c 문자 1문자
s 문자열 문자열
% None %를 출력
p 포인터 포인터를 출력(????:????형태)
------------------------------------------------------------------
(e와 E의 차이는 지수 표시에 e를 쓰느냐와 E를 쓰느냐의 차이)




이것을 잘 읽어 보세요.

그럼 한가지 예를 들어 보죠.


printf("%d",10);


이렇게 하면 10이 출력 됨니다.

물론


int a=10;
printf("%d",a);


이렇게 해도 10이 출력되죠.

이런 식으로 다른 것들도 사용하면 됨니다.

printf함수는 직접 해보는 것이 가장 쉽게

배우는 방법입니다.

그런데 만약

long int형이라면 어떻게 출력할까요?

이런걸 지정하는 것이..


%[크기]타입


여기서 크기 입니다.

크기를 지정하는 문자는 2가지만 알아두도록 하세요.

h와 l두가지 말입니다.

h를 써 주면 short int가 되고 l을 쓰면 long int가 됨니다.

예를 들어


short a=10;
long b=20;
printf("%hd",a);
printf("%ld",b);


이럴때 사용합니다.

그리고 인수는 여러개일수도 있는데 변환지정이 여러개이면

인수도 여러개이어야 합니다.

예를 들어


printf("%d %c %f",10,'A',1.1);


이런 식으로..

이제 예제 하나만 보죠


/* 파일 이름 : C4-7.C
프로그램 내용 : printf를 설명하는 프로그램. */
#include
void main()
{
int a=10;
long int b=12345678;
float c=0.2934;
double d=0.193303944665433356;
char e='C';
char f[]="String";

printf("%d\n%ld\n",a,b);
printf("%f\n%e\n",c,d);
printf("%c\n%s\n",e,f);
}




이 프로그램은 printf를 사용하여 여러가지 형태로 출력하는 프로그램인데

잘 보시면 금방 이해 되실 겁니다.

특별히 설명을 필요 없겠죠?

그럼 결과를 볼까요?


C:\>C4-7.EXE
10
12345678
0.293400
1.933039e-01
C
String

C:\>




(2) 입력 함수 scanf

scanf는 printf와 사용법이 매우 비슷 하지만,

출력용이 아니라 입력용 함수 입니다.

scanf함수 역시 printf함수와 마찬가지로 stdio.h라는 헤더 파일에

정의되어 있습니다.

사용법은


scanf(포멧문자열[,주소,주소..]);


이런식으로 알아두시면 편합니다. printf와 똑같죠?

여기서 포멧 문자열은 입력받을 형태를 지정하는 문자열 입니다.

그리고 주소는 입력받은 데이터를 저장할 변수의 주소이죠.

그런데 변수의 주소는 어떻게 알수 있을까요??

이건 사실 포인터를 베울때 베우는 건데

그냥 주소를 알아내는 방법만 가르쳐 드리죠.

어떤 변수의 주소를 알아낼때는 &연산자를 씁니다.

예를 들어 a라는 변수가 있다면, a의 주소는 &a가 됨니다.

그런데 문자열을 입력 받기 위해 문자 배열을 만들었을경우

예를 들어


char str[10];


이런 변수가 있을때는 str그 자체가

주소가 됨니다. 이유는 배열명은 배열의 시작 주소를 가지고 있기 때문이죠.

그러므로 이때만은 &str로 사용해서는 안됨니다.


그럼 포멧 문자열에 대해 자세히 알아보죠.

포멧 문자열을 구성하는 문자는 printf와 비슷한데..

일반 문자와, 입력 지정 문자가 있습니다.

일반 문자는 그냥 입력 형태를 지정할때 쓰이는데 그냥 베우다 보면 아시게 됨니다.

그리고 입력 지정 문자는 printf에서와 완전히 똑같습니다.

모두 %로 시작하고


%[크기]타입

이런 구조로 되어 있죠 printf를 베울때 가르쳐 드렸지만.

그래도 다시 한번 타입 지정하는 문자들을

보여 드리죠.


문자 인수 형태 출력
------------------------------------------------------------------
d 또는 i 정수 부호있는 10진 정수
u 정수 부호없는 10진 정수
o 정수 부호없는 8진 정수
x 정수 부호없는 16진 정수(a,b,c,d,e,f사용)
X 정수 부호없는 16진 정수(A,B,C,D,E,F사용)
f 소수 부호있는 소수([-]???.??형태)
e 소수 부호있는 소수([-]???.??..e[+/-]???형태)
g 소수 주어진 값에 따라 f또는 e를 자동으로 선택..
E 소수 부호있는 소수([-]???.??..E[+/-]???형태)
G 소수 주어진 값에 따라 f또는 E를 자동으로 선택..
c 문자 1문자
s 문자열 문자열
% None %를 입력
p 포인터 포인터를 입력(????:????형태)
------------------------------------------------------------------




이것들을 한번 보시고....... 한가지 예를 들어 보죠.

만약 정수를 입력받고 싶다면 어떻게 할까요?

바로


int a;
scanf("%d",&a);


이런 식으로 하면 되겠죠?

그럼 다음으로 넘어가서 입력 지정 문자의


%[크기]타입


이 형식에서 크기는 printf에서와 똑같은 의미 입니다.

크기 지정 문자는 h와 l이 있는데,

h를 쓰면 short int를 입력 받을때 쓰는 것이고,

l을 쓰면 long int를 입력 받을때 쓰는 것 입니다.

예를 들어 보면


short a;
long b;
scanf("%hd",&a);
scanf("%ld",&b);


이렇게 사용하는 것 이죠 scanf함수도 printf함수처럼

많이 사용해보는 것이 가장 쉽게 배우는 길 입니다.

그러므로 많이 연습해보시길 바랍니다.

scanf함수를 쓰면 여러가지 형태의 데이터를

쉽게 입력받을수 있습니다.

만약


1, 2, 3


이런 형식의 데이터를 입력받고 싶다면

포멧 문자열을


"%d,%d,%d"


이렇게 만들어 주면 됨니다.


int a,b,c;
scanf("%d,%d,%d",&a,&b,&c);


이런 식으로 하면 되겠죠?

이정도면 scanf함수도 이해 하셨으리라 봅니다.

그리고 마지마으로 한가지 알아두실 점이 있는데

scanf로 문자열을 입력 받을때




char str[100];
scanf("%s",str);


이런 식으로 할 때

입력 받을 문자열에는 공백이 없어야 합니다.

scanf라는 함수로 문자열 입력을 받을때는

공백을 허용하지 않는다는 것 이죠.

만약 공백이 있을 경우 심각한 현상이 발생하는데,

직접 경험해 보시기 바랍니다..!

Previous:지역변수,전역변수,정적변수
Next:입출력함수 2 (문자 및 문자열입출력)
이번 강의에서는 저번에 배우지 못한 입출력 함수인

getchar, putchar, getch, putch, getche등을 배워 보겠습니다.


문자 입출력

C언어에는 printf, scanf말고도 문자 입출력 함수가 있습니다.

그것들에 대해 배워 보죠.


(1) getchar과 putchar

문자 입출력 함수인 getchar과 putchar함수를 배워보겠습니다.

이 함수들은 각각 한 문자를 입력, 출력하는 함수 입니다.

둘다 stdio.h에 정의되어 있죠.

getchar함수의 사용법은


[변수=]getchar();


이런 식으로 쓰면 됨니다.

여기서 변수는 입력 받은 문자가 들어갈 변수 입니다.

다음 putchar의 사용법은 간단한데


putchar(출력할문자);


이런 식으로 사용합니다.

예를 들자면


char a='A';
putchar('a');
putchar(a);




이런 식으로 쓰면 됨니다.


(2) getch와 putch그리고 getche

문자를 입출력하는 함수로는 getch,putch가 더 있는데,

이것들은 conio.h에 정의되어 있습니다.

getch함수는 한 문자를 입력받는 함수이고.

putch는 한 문자를 출력하는 함수 입니다.

사용법은


변수=getch();
putch(출력할문자);




이렇게 쓰는데 아까 꺼랑 똑같습니다.

getchar과 getch의 다른 점은 getchar은 한 문자를 입력한 후

Enter키를 눌려야 하지만.

getch는 키보드에 문자 하나만 누르면 됨니다.

그리고 getch로 입력할 경우 문자가 화면에 출려되지 않습니다.

그런데 화면에 출력해야 할 경우가 생기는데,

그럴땐

getche함수를 쓰시면 됨니다.

사용법은 getch와 똑같습니다. 단 입력 받은 문자열 화면에 출력하죠.

그리고 putchar과 putch는 같다고 보시면 됨니다.


문자열 입출력

문자열 입출력 함수인 gets와 puts에 대해 배워보죠.

이 함수는 각각 문자열을 입력받고 출력하는 함수 입니다.

사용법을 알아보면


gets(변수);


이렇게 사용하시면 됨니다.

여기서 변수는 문자열을 저장할 변수 입니다.

반드시 문자 배열이어야 하죠.

그리고 gets로 문자열을 입력받을때는

scanf와 다르게 공백이 있어도 상관 없습니다.

puts함수는


puts(출력할문자열);


이렇게 쓰시면 됨니다.

그리고 puts함수는 자동으로 문자열 끝에

'\n'를 집어 넣어 출력 한다는 것을 잊지 마세요.




printf("Example\n");


이렇게 할 것을 puts함수로 출력하면


puts("Example");


이렇게 하면 된다는 것 입니다..


이제 예제 하나 볼까요?


/* 파일 이름 : C5-1.C
프로그램 내용 : 입출력 함수 예제. */
void main()
{
int a;
char b, str[100];

printf("Enter the Integer: ");
scanf("%d%c",&a);
printf("Enter the String: ");
gets(str);
printf("Enter the Character: ");
b=getche();
putchar('\n');

printf("\n----------------------------\n");
printf(" Integer: %d\n",a);
printf(" String: ");
puts(str);
printf(" Character: ");
putchar(b);
printf("\n----------------------------\n");
}


이정도는 분석하실수 있겠죠?

이것두 따로 설명은 하지 않겠습니다.

결과는 어떻게 나올까요??????

결과는


C:\>C5-1.EXE
Enter the Integer: 10
Enter the String: Example String
Enter the Character: a

----------------------------
Integer: 10
String: Example String
Character: a
----------------------------

C:\>

Previous:입출력함수 1 (printf와 scanf)
Next:연산자 1 (기본 연산자)
연산자 1

이제부터는 좀 쉬운 것들을 배우도록 하죠

어렵지는 않지만 매우 중요한 것이니 잘 들으시길 바랍니다.

이제부터 배울껀 연산자 입니다.

C언어는 키워드는 적어도 연산자 만큼은 그 그 어떤 언어보다도 많습니다.

그것이 C언어가 강력한 이유중 하나이죠.

C에서 연산자는 매우 중요하므로 잘 들으셔야 할 부분입니다.


(1) 기본 수학 연산자

우선 간단히 기본 수학 연산자를 하겠습니다.

C를 모르는 그 어떤 사람이라도 다 아는 것이니 머리 식힐겸 잘 읽어보세요.

기본 수학 연산에는 덧셈, 뺄셈, 곱셈, 나눗셈이 있습니다.

이에 따른 연산자는 다음과 같습니다.


-------------
+ 덧셈
- 뺄셈
* 곱셈
/ 나눗셈
% 나머지
-------------




나머지 구하는 연산자 빼면 누구나 다 아는 것이죠?

특별히 설명할것두 없겠네요.


c=a+b;


이렇게 하면 c에 a와 b를 더해 넣는 것이고


c=a-b;


이렇게 하면 c에 a에서 b를 빼 넣는 것이고

더 이상의 설명은 필요 없겠죠?

%는 사용법은 똑같으나 단지 나머지를 구하는 것 입니다.


(2) 단항 연산자

다음에는 단항 연산자를 알아보죠.

단항 연산자에는 +와 -가 있습니다.

단항 연산자는 변수나 상수앞에 붙여서 부호 표시를 하거나

부호를 바꿀때 쓰입니다.

예를 들어


a=-b;


이렇게 하면

a에는 b의 부호를 바꾼 값이 들어가겠죠?

만약 b가 -10이라면 a에는 10이 b가 10이라면 a에는 -10이 들어갑니다.

단항연산자중 +는 생략하면 기본으로 지정되므로 거의 쓰이지는 않습니다.


(3) 우선 순위

다음으로 연산자 우선순위를에 대해 알아보죠.

모든 연산자가 우선순위가 있다는 것은 알고 계시겠죠?

수학에서도 곱셈을 덧셈보다 먼저 하자나요.

그것은 곱셈의 우선순의가 높기 때문이죠.

아직 연산자를 많이 배우지는 않았지만 기본 수학 연산자의 우선순위를 알아보죠.

우선순위는 *, /, % 이 가장 높고 그 다음이 +, -입니다.

그리고 같은 순위에서는 왼쪽부터 계산이 됨니다.

또한 수식에 괄호가 있다면 괄호 안에 식을 먼져 계산하게 됨니다.


(4) 치환 연산자

우선 치환 연산자 입니다.

치환 연산자는 지금까지 계속 써 왔던 것 입니다.

치환 연산자는 바로 = 입니다. 이 연산자의 쓰임세는 아주 간단하죠?

모두들 알고계실 겁니다.

예를 들어


a=b;


이런 문장이 있다면 a에 b의 값을 넣으라는 것이 되겠죠?

이렇게 아주 간단합니다.

이렇게 지금까지 쓰던 문장을 단일 치환문 이라고 합니다.

그런데 C에서는 이런 단일 치환문 외에도

다중 치환문이 있습니다. 이것에 대해서도 자세히 알아보죠.

다중 치환문도 그리 어렵지 않은 것 입니다.

이 문장을 보세요.


a=b=c=d=e=f=1;


이렇게 한 문자에 치환 연산자가 2개 이상 쓰인 것을

다중 치환문 이라고 합니다.

이 문장의 결과는 어떻게 될까요? a,b,c,d,e,f모두에 1이 들어가겠죠?

치환 연산자는 +,-,*,/등의 연산자와는 다르게

오른쪽에서 왼쪽으로 연산을 수행합니다.

예를 들어..


e=a+b+c+d;


이건 a와 b를 더하고 그 값에 c를 더하는 식으로 왼쪽에서 오른쪽으로

연산을 수행하지만


a=b=c=d=e=f=1;


이건 f에 1을 넣고 e에 f의 값 즉 1을 넣고

하는 식으로 오른쪽에서 왼쪽으로 연산을

수행합니다.

위에 있는 다중 치한문 까지는 쉽게 이해가 되셨을 겁니다.

그럼 다음을 보세요.


a=10+(c=12-2);


이해가 빨리 되시나요?

어떻게 보면 잘못된 문장 같기도 하지만 이건 에러 없는 정확한 수식 입니다.

그럼 그 문장을 조금 바꿔 두 문장으로 해 보죠.


c=12-1;

a=10+c;


이제 이해가 되시나요???

위의 수식을 줄여 쓴 것이


a=10+(c=12-2);


이거 입니다..

C에서는 이렇게 희귀한 수식도 인정합니다.

이제 예제 하나 볼까요??


/* 파일 이름 : C5-2.C
프로그램 내용 : 다중 치환문 예제 */
#include
void main()
{
int a,b,c,d,e,f;
a=b=c=d=e=f=0;
b=10;
c=20;
a=b+(d=c);
printf("%d %d %d %d %d %d\n",a,b,c,d,e,f);
}




정말 간단한 소스죠. 조금 햇갈리실 것 이지만

잘 분석하면 결과는..


C:\>C5-2.EXE
30 10 20 20 0 0

C:\>




이렇게 나옴니다.


(5) 타입 캐스팅

이제 치환 연산자도 다 끝났습니다.

그런데 배울것이 하나 더 있는데 그걸 배우기 전에 다음과 같은

경우를 생각해 봅시다.

만약 int형과 unsigned long형태를 같이 계산하면 어떻게 될까요?

자료형의 형태가 다르니 잘못하면 잘못된 결과가 나올수도 있습니다.

이럴때는 데이터 형태을 일시적으로 변경시켜 주면 되는데

이렇게 일시적으로 데이터 형태를 바꿔주는 것을

타입캐스팅(Typecasting) 이라고 합니다.

C에서 타입캐스팅을 하려면 타입캐스팅 연산자를 사용해야 합니다.

그런데 타입캐스팅 연산자는 아주 간단합니다.

타입 형태 키워드들 즉 int, char, float등등이

타입캐스팅 연산자 기능까지 해 주기 때문이죠.

타입캐스팅 방법은


(타입)데이터


이렇게 하면 타입캐스팅이 됨니다.

여기서 타입은 바꿀 형태의 타입캐스팅 연산자 이고 데이터는 형태를 바꿀

데이터 인데 변수나 상수가 올수 있습니다.

또한 수식이 와도 상관 없습니다.

예를 들어보죠


int a=10;
unsigned long=b,c;
c=(unsigned long)a+b;




이 소스는 int형인 a를 unsigned long로 타입캐스팅 해서

unsigned long형태인 b와 더해 c에 넣는 것 입니다.

이제 이해가 되셨겠죠??


(6) 접미사

한가지 여기서 알고 넘어갈 것이 있는데

(사실 지금 설명할건 연산자와는 상관 없는 거지만....)

상수중 실재값은 정수의 경우 무조건 signed형태로 인식하고

int형태 범위 내에 값이면 int형으로 인식하며

그것을 벗어나면 long형으로 인식하게 됨니다.

그리고 소수의 경우는 무조건 double로 인식하죠

그러므로 상수는 타입 캐스팅이 많이 필요합니다.

만약 123이란 상수가 있다고 합시다.

이건 int형태 범위 내에 있으므로 signed int로 인식됨니다.

그런데 이걸 unsigned long로 타입캐스팅 하려면


(unsigned long)123


이렇게 하면 됨니다

하지만 이 방법보다 편한 방법이 있는데

123을 그냥 unsigned long형태로 인식시키는 것이죠.

그렇게 하기 위해선 접미사를 쓰면 됨니다.

접미사에는 L,F,U의 3가지가 있습니다. 그리고 대소문자 구별은 없습니다.

L은 long를, F는 float를, U는 unsigned를 의미 합니다.

그러므로 위에 것을


123UL


이렇게 서 주면 이 123은 unsigned long로 인식되어

타입 캐스팅이 필요가 없어 집니다.

Previous:입출력함수 2 (문자및 문자열입출력)
Next:연산자 2 (관계.논리.비트.기타 연산자)
이번 강좌에서는 저번에 배우지 못한 연산자인 관계형 연산자, 논리 연산자, 비트별 연산자 등에 대해 알아보겠습니다.


관계형 연산자.

관계형 연산자는 ..보타 크다, ..보다 작다, ..와 같다, ..와 같지 않다

등등.. 어떤 데이터들을 비교할때 쓰이는 연산자 입니다.

주로 제어 구조, 특히 if문에서 쓰이지만 꼭 그런것은 아님니다.


우선 관계형 연산자에는 어떤것이 있는지 알아보죠.

관계형 연산자에는


------------------------------
== ..와 같다
> ..보다 크다
< ..보다 작다
>= ..보다 크거나 같다.
<= ..보다 작거나 같다.
!= ..와 다르다.
------------------------------




다음과 같은 것들이 있습니다.

그럼 각 연산자들의 이해를 돕기 위해 다음 표를 보시기 바랍니다.


-----------------------------------
a == b a와 b가 같다
a > b a가 b보다 크다
a < b a가 b보다 작다
a >= b a가 b보다 크거나 같다.
a <= b a가 b보다 작거나 같다.
a != b a와 b가 다르다.
-----------------------------------




이걸 보시면 이해가 되실겁니다.

그런데 대부분의 연산자들은 어떤 값을 돌려주죠.

예를 들어 +연산자는 두개의 값을 더해 돌려 주죠.

그러면 이 관계형 연산자는 무엇을 돌려 줄까요?

돌려주는 값은 0과 1중 한가지 입니다.

관계형 연산자가 쓰인 수식을 보면 어떤 조건을 나타내고 있음을 알게되실 겁니다.

그 수식이 참이면 1을 돌려주고, 거짓이면 0을 돌려주죠.

예를 들어


a=1==2;


이런 문장이 있다고 합시다.

그럼 a에는 어떤 값이 들어갈까요?

'1과 2는 같다' <- 이건 거짓이므로 a에는 0이 들어가겠죠?


논리 연산자.

논리 연산자는 아까 관계 연산자의 수식 두가지를

논리적으로 연관시키는 연산자 입니다.

쉽게 말하지면 관계형 연산자로 조건이 만들어 지는데

이런 조건 두개가 동시에 만족해야할 경우가 있다고 합시다.

그럴 경우 이 논리 연산자를 쓰면 됨니다.

논리 연산자에는 다음과 같은 것들이 있습니다.


--------------
&& AND
|| OR
! NOT
--------------




각각의 의미는 위에 쓰여진 그대로인데

이걸 보세요

그런데 여기서 a와 b는 관계형 연산자로 만든

조건이라고 합시다.


----------------------------
a && b a와 b모두 참일때
a || b a또는 b가 참일때
! a a의 반대
----------------------------




a && b의 경우 a라는 조건과 b라는 조건이 모두 참일때 1을 돌려주고.

하나라도 거짓이면 0을 돌려 주죠

a || b의 경우는 둘 중 하나만 참이면 1을 모두 거짓일때만 0을 돌려주죠.

! a의 경우 반대로 되는데

즉 참이면 0을 거짓이면 1을 돌려줌니다.

만약 다음과 같은 문장이 있다고 합시다


a=(2>1)&&(3>1);


이럴경우 a에는 어떤 값이 들어갈까요?

당연히 1이 들어가죠

하지만


a=(1>5)&&(3>1);


이럴땐 0이 들어가겠죠??


비트별 연산자

비트별 연산자는 비트단위 연산을 할때 쓰이는 연산자 입니다.

먼저 비트별 연산자의 종류를 알아보죠.

비트별 연산자에는 다음과 같은 것 들이 있습니다.


------------------------------
& AND (비트별 논리곱)
| OR (비트별 논리합)
^ XOR (비트별 배타 논리합)
~ 1의 보수
<< 왼쪽으로 쉬프트
>> 오른쪽으로 쉬프트
------------------------------




다음과 같은 것들이 있는데 하나씩 자세히 알아보죠.


(1) 비트별 논리곱

&는 비트 단위로 AND연산을 하는 건데,

연산하려는 두 개의 비트가 모두 1일때만 결과가 1이 되고

하나라도 0이면 결과는 0이 됨니다.

정리하면


------------------------------------
첫번째 비트 두번째 비트 결과
------------------------------------
1 & 1 1
1 & 0 0
0 & 1 0
0 & 0 0
-----------------------------------




이렇게 되죠

한가지 문제를 드리죠


0x0F & 0xFF = ?


알아맞춰 보세요.

답은 0x0F죠.

0x0F는 이진수로 00001111입니다. 0xFF는 11111111이죠.

그런데 &연산자는 모두 1일때만 1을 돌려 줌니다.

00001111과 11111111에서 모두 1인 부분은 뒤쪽 4개의 비트죠.

그러므로 결과는 00001111

이걸 16진수로 고치면 0x0F가 되죠.

다시 정리하면


-----------------------------
0x0F = 00001111
0xFF = 11111111
---------- (& 연산)
00001111 = 0x0F
-----------------------------




이해가 되시죠?


(2) 비트별 논리합

다음으로 |연산자에 대해 자세히 알아보죠.

|는 OR연산을 하는 것으로 &와는 달리 두개의 비트중 1개라도 1이면

1을 돌려주는 연산자 입니다.




------------------------------------
첫번째 비트 두번째 비트 결과
------------------------------------
1 | 1 1
1 | 0 1
0 | 1 1
0 | 0 0
------------------------------------




이런 연산을 하는 연산자 입니다.

그럼 이것두 문제를 드릴까요?

아까처럼 0x0F와 0xFF를 쓰도록 하죠

그런데 이 두개를 OR연산 하면 어떤 결과가 나올까요?

당연히 0xFF죠.

이유는 하나라도 1이면 1을 돌려 주므로.


-----------------------------
0x0F = 00001111
0xFF = 11111111
---------- (| 연산)
11111111 = 0xFF
-----------------------------




이렇게 되기 때문이죠.


(3) 비트별 배타 논리합

다음으로 ^연산자에 대해 알아보죠.

^는 비트단위 배타 OR연산 즉 비트단위 XOR연산을 하는 연산자 입니다.

이 연산자는 OR비슷하지만, 다른점은 두개의 비트가 모두 1일때는

0을 돌려준다는 것이죠.

OR에서는 1을 돌려주지만

즉..


------------------------------------
첫번째 비트 두번째 비트 결과
------------------------------------
1 ^ 1 0
1 ^ 0 1
0 ^ 1 1
0 ^ & 0
------------------------------------

다음과 같은 연산을 하게 됨니다.

그럼 이번엔 0x0F와 0xFF를 XOR연산 하면 어떤 결과가 나올까요?

이건


-----------------------------
0x0F = 00001111
0xFF = 11111111
---------- (| 연산)
11110000 = 0xF0
-----------------------------




이렇게 해서 0xF0이 나오게 됨니다.


(4) 1의 보수

다음으로 ^연산자에 대해 알아보죠.

^는 1의 보수를 구하는 연산자로 그냥 쉽게 비트들을 반대로

즉 0이면 1로 1이면 0으로 바꿔버린다고 알고계시면 됨니다.


-----------------------
비트 결과
-----------------------
~ 0 1
~ 1 0
-----------------------




이런 연산을 하는 것이죠.

1의 보수 연산을 잘 쓰면 좋은점이 많이 있습니다.

만약 여러분이 unsigned long가 가질수 있는 가장 큰 값을 써야 할 경우

어떻게 할까요?

unsigned long는 4294967296까지 넣을수 있는데 이걸 직접 써야 할까요?

그렇게 해도 되고 좀더 쉬운 방법으로 0xFFFFFFFF라고 써도 되겠지만

더 쉬운 방법은 ~0이라고 쓰는 것 입니다.

0은 모든 비트가 0인데 이걸 1의 보수 연산자로

~0이라고 쓰면 모든 비트가 1이 되므로 가장 큰값이 되는 것이죠.


(5) 쉬프트

다음으로 쉬프트 연산자인 <<와 >>에 대해 알아보죠

<<,>>는 지정된 방향으로 지정되 수 만큼 비트를 이동시키는 것 입니다.

예를 들어


?? = 0xF0 >> 4;


이렇게 하면 0xF0 즉 11110000을 >>방향으로 4만큼 이동시키는 것이죠

그래서 결과는 00001111 즉 0x0F가 됨니다.

만약 1이 오른쪽 끝까지 갔다면 그냥 없어집니다.

그리고 왼쪽은 0으로 계속 채워지구요.

<<도 이것과 똑같으나 방향만 다를 뿐 입니다.


이것으로 비트별 연산자는 마치도록 하죠.


기타 연산자.

다음으로 기타 연산자들을 배워 보기로 하겠습니다.

여기서 배울 것에는 조건 연산자, 증감 연산자, 그리고 sizeof연산자와,

혼합 연산자가 있습니다.


(1) 조건 연산자

조건 연산자는 주어진 조건에 따라 어떤 수식을 실행하고

그 결과를 돌려주는 연산자 입니다.

사용법은


조건 ? 수식1 : 수식2


이런 식으로 사용하죠.

여기서 조건은 관계형 연산자로 만든 조건입니다.

여기서 만약 조건이 참이면 수식1을 실행한 후 그 결과를 돌려주고,

거짓이면 수식2를 실행하는 것 입니다.

예를 들어 보죠.


a = 1>3 ? 1+3 : 2+6;


여기서 a에 들어가는 값은?

1>3이란 조건은 거짓이므로 두번째 수식인 2+6이 실행되고

결과로 8을 돌려주므로 a는 8이 되겠죠.

이 문장은


1>3 ? a=1+3 : a=2+6;


이렇게 써도 상관 없습니다. =이 들어가도 수식이므로


(2) 증감 연산자

이번엔 증감 연산자에 대해 알아보죠.

증감 연산자는 어떠 변수에 1을 더하거나 빼 주는 연산자이죠

연산자에는


---------------------
++ 1을 증가시킴
-- 1을 감소시킴
---------------------




이렇게 두 가지가 있습니다.

이 두 연산자는 변수 앞이나 뒤에 붙여 사용하면 되는데

앞에 붙일때와 뒤에 붙일때의 차이점은 일단 나중으로 넘기고

예를 들어보죠.

만약


a++;


이렇게 하면 a는 1이 증가하게 됨니다.

즉 a=a+1;가 되는 것이죠.

그리고


a--;


이렇게 하면 a=a-1;이 되는 것 이고요.

그럼 앞에 붙이느냐 뒤에 붙이느냐에 차이를 알아보죠.

이 차이가 나는 곳은 바로 이 연산자가 하나로만 쓰이지 않고

여러개의 연산자와 같이 쓰였을때 차이가 남니다.

이 연산자를 앞에 붙이면

이 연산이 가장 먼저 수행되고 다른 연산이 수행됨니다.

하지만 이 연산자를 뒤에 붙이면 다른 모든 연산을 수행하고

이 연산을 수행하게 되죠.

예를 들어


int a,b=1;
a = 6 - ++b;




이렇게 했을때는 앞에 붙었으므로

우선 b를 1 증가시켜 2로 만든 후 6에서 빼 a에는 4가 들어가지만


int a,b=1;
a = 6 - b++;




이럴경우 우선 다른 연산부터 해서 a에는 5가 들어가고

마지막으로 b가 1이 증가되어 2가 되게 됨니다.


(3) sizeof연산자

이번엔 sizeof연산자에 대해 알아보죠.

sizeof는 어떤 데이터 타입이나 변수, 또는 상수의 크기를

바이트 단위로 알아내는 연산자 입니다.

사용법은


sizeof(데이터타입) 또는
sizeof(데이터)




예를 들어


a=sizeof(char)


이렇게 할 경우 char형태는 1바이트 이므로

a는 1이 됨니다.

그리고


char c;
a=sizeof(c);




이렇게 해도 1이 되죠


a=sizeof(long);


이렇게 하면 4를 돌려 주겠죠?

그리고 만약 배열이라면 배열 크기를 바이트 수로 돌려줌니다.

예를 들어


int array[10];
a=sizeof(array);




이렇게 할 경우

int는 2바이트 이고 10개의 배열이므로

2*10=20이므로 20을 돌려줌니다.


(4) 혼합 연산자.

혼합 연산자는 말 그대로 두개의 연산자를 혼합한 것 입니다.

프로그램을 만들다 보면


a=a+3;
a=a-2;
a=a*3;
a=a/2;
a=a|0x0F;
a=a&0x0A;
a=a>>4;




이런 수식처럼 한개의 데이터 여기선 a가 겹치는 수식을 쓸 때가 있습니다.

이럴때 더 간단히 쓸 수가 있습니다.

바로 혼합 연산자를 쓰면 되는데 혼합 연산자의 종류에는


+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=


이런 것들이 있습니다.

각각의 기능을 살펴보면


------------------------------
a += b -> a = a + b
a -= b -> a = a - b
a *= b -> a = a * b
a /= b -> a = a / b
a %= b -> a = a % b
a &= b -> a = a & b
a |= b -> a = a | b
a ^= b -> a = a ^ b
a <<= b -> a = a << b
a >>= b -> a = a >> b
------------------------------




이런 기능을 합니다.

이렇게 혼합 연산자를 잘 쓰면 수식을 더 간단히 쓸수 있죠.

Previous:연산자 1 (기본 연산자)
Next:if 문
이번 강좌에서는 프로그램 흐름제어에 대해 알아보겠습니다.
프로그램 흐름 제어란 실제 프로그램에서 가장 중요한 부분으로 어떤 조건이 만족될때 어떤 부분을 실행하거나 어떤 특정 부분을 반복하는 기능을 수행하도록 프로그램의 흐름을 제어하는 것을 말합니다.
C에서는 프로그램 흐름 제어를 위해 많은 방법을 제공합니다.
그럼 그것들을 하나씩 알아보죠.


if문

프로그램을 만들다 보면

어떤 조건이 만족되면 특정 부분을 실행하고 그렇지 않으면

또다른 특정 부분을 실행해야 하는 등의 구조가 필요할때가 있습니다.

이때는 if문을 사용하면 됨니다.

if문은 어떤 조건이 만족 되면 특정 부분을 실행하는 기능을 합니다.

그럼 자세히 알아보죠.


(1) C언어 에서 참과 거짓

if문을 배우기 전에 한가지 알아 둘 것이 있습니다.

C에서 참과 거짓의 구분을 어떻게 하는지 알아야 하거든요.

저번 관계형 연산자에서 참은 1 거짓은 0을 돌려준다고 했습니다.

그럼 참과 거짓은 1과 0으로만 구분할까요?

그렇지 않습니다.

C에서 0이 아닌 모든 값은 참으로 인식하고,

0만 거짓으로 인식합니다.

지금은 별로 중요하지 않게 보여도 if문에서 정말 중요한 것 입니다.


(2) if문

그럼 이제 if문을 진짜로 공부해 보죠.

if문의 사용법은 가장 기본적인 형태가


if(조건)
{
/* 하나 이상의 C언어 문장 */
.
.
}




이것 입니다.

만약 조건이 만족되면 {}안의 문장들을 실행하는 것이죠.

여기서 조건은 관계형 연사자에 의해 만들어진

조건이어도 되고

참과 거짓을 나타내는 값이어도 됨니다.

즉 조건에 어떤 변수를 넣어도 되는데,

그럴때는 변수가 0이면 거짓으로 인식하고

0이 아니면 참으로 인식합니다.

예제를 보죠.


/* 파일 이름 : C7-1.C
프로그램 내용 : if문 예제.. */
#include
void main()
{
int a=10;
if(a==10)
{
printf("A = 10\n");
}
if(a)
{
printf("A = True\n");
}
if(!a)
{
printf("A = False\n");
}
}




다음 프로그램의 결과는 어떻게 나올까요?

당연히 "A = 10"이라는 문장이 출력되겠죠?

그리고 "A = True"란 문장도 이유는 a가 10이므로

a==10이라는 조건은 참이 됨니다.

그러므로 그 안에 문장을 실행시키게 되는 것이죠.

두번째의 if문에서 a는 0이 아니므로 그것도 역시 참이 됨니다.

하지만 세번째 if문에선 !a라는 조건을 주었는데

!연산자는 NOT연산자로 참을 거짓으로 거짓을 참으로 바꾸는

연산자라고 배워습니다.

a는 참인데 !를 붙이면 거짓이 되므로 그 안에 문장은 실행되지 않죠.

이 예제에서 처럼 {}안에 문장이 하나밖에 없을때는

{}로 묶지 않아도 됨니다. 그러니까


if(a==10)
printf("A = 10\n");




이렇게 써도 된다는 것이죠.


(3) if-else문

if문은 위와 같이 어떤 조건이 만족되면 특정 부분을 실행합니다.

그런데 그 조건이 만족되지 않을때도 특정 부분을 실행해 줄수는 없을까요?

당연히 할수 있죠. C로는 못하는게 없으니..

그때는 else라는 걸 씁니다.

이때는


if(조건)
{
/* A부분 */
.
.
}
else
{
/* else부분 */
.
.
}




이런 구조를 갖죠.

여기서 조건이 참이면 A부분을 실행하고, 그렇지 않으면

else부분을 실행합니다.

예제를 보죠


/* 파일 이름 : C7-2.C
프로그램 내용 : if-else 문 예제.. */
#include
void main()
{
int a=0;
if(a)
{
printf("True\n");
}
else
{
printf("False\n");
}
}




이 예제에서도 if의 {}안에 문장이 한개이므로

{}를 생략해서


/* 파일 이름 : C7-3.C
프로그램 내용 : if-else 문 예제.. */
#include
void main()
{
int a=0;
if(a)
printf("True\n");
else
printf("False\n");
}




이렇게 써도 됨니다.

그럼 이 프로그램의 결과는 어떻게 나올까요?

False가 출력 되겠죠?

이유는 a가 참이 아니므로 그렇게 되는 것이죠.


(4) if-else if-else문

만약 여러 조건별로 실행을 다르게 하려면 어떻게 할까요?

즉 A라는 조건이 만족하면 A부분을 B라는 조건이 만족하면

B부분을....

이렇게 여러가지 조건 별로 말입니다.

이럴땐 else if를 씀니다.

사용법은


if(조건A)
{
/* A부분 */
.
.
}
else if(조건B)
{
/* B부분 */
.
.
}
.
.
else
{
/* else부분 */
}




이런 식으로 사용하죠.

여기서 조건A가 참이면 A부분을, 조건B가 참이면 B부분을..

이렇게 계속 나가다가, 어떤 조건에도 만족하지 않으면,

else부분을 실행합니다.

이때 else부분은 생략해도 됨니다.

그때는 어떤 조건에도 만족하지 않으면, 아무것도 실행하지 않게

됨니다.

그럼 예제를


/* 파일 이름 : C7-4.C
프로그램 내용 : if-else if-else문 예제.. */
#include
void main()
{
int a=2;
if(a==1)
printf("A = 1\n");
else if(a==2)
printf("A = 2\n");
else if(a==3)
printf("A = 3\n");
else
printf("Error!\n");
}

이걸 실행하면. 결과는 무엇일까요?

A = 2가 나오겠죠?

왜 그런지는 다 아시죠? 구지 설명할 필요 없는것 같군요.

그럼 예제 하나 더.. 이번엔 else를 생략한 걸로


/* 파일 이름 : C7-5.C
프로그램 내용 : if-else if문 예제.. */
#include
void main()
{
int a=4;
if(a==1)
printf("A = 1\n");
else if(a==2)
printf("A = 2\n");
else if(a==3)
printf("A = 3\n");
}

이거의 실행 결과는 아무것도 출력되지 않습니다..

참이 되는 조건이 없기 때문이죠.


이것으로 if문에 대한 설명은 마치도록 하죠.

Previous:연산자 2 (관계.논리.비트.기타 연산자)
Next:switch 문, for 문
switch문

이번엔 switch문에 대해 알아 보죠.

switch문은 어떤 특정한 변수 또는 식의 값에 따라 특정 부분을 실행하는 것 입니다.


기본적인 사용법은


switch(식)
{
case 값A:
.
.
break;
case 값B:
.
.
break;
.
.
}

이렇습니다.

여기서 식에는 상수, 변수, 수식 어느것도 다 들어갈수 있습니다.

만약 식의 값이 값A이면 case 값A:에서 break가 나올때까지 실행하고

식의 값이 값B이면 case 값B:에서 break가 나올때까지 실행합니다.

예를 들어 보죠


/* 파일 이름 : C7-6.C
프로그램 내용 : switch문 예제.. */
#include
void main()
{
int a=1;
switch(a)
{
case 1:
printf("A = 1\n");
break;
case 2:
printf("A = 2\n");
break;
}
}




이걸 실행하면 A = 1이 출력됨니다.

이유는 a가 1이기 때문에

case 1:부터 break가 나올때 까지 실행하면 그게 출력되기 때문이죠.

여기서 break는 중요합니다.

만약 case 1:에 break가 없다면


/* 파일 이름 : C7-7.C
프로그램 내용 : switch문 예제.. */
#include
void main()
{
int a=1;
switch(a)
{
case 1:
printf("A = 1\n");
case 2:
printf("A = 2\n");
break;
}
}




이렇게 되어 있다면 결과는 어떻게 될까요?

break가 가장 밑에 있으므로.

거기까지 실행해서 A = 2까지 출력이 되게 됨니다.

만약 break가 하나도 없다면 switch문이 끝날때까지의 모든 문장을 실행합니다.

그러므로 break를 빼먹으면 절대로 않되죠.

어떤 if문은 switch문으로 쓰면 더 간결해 지는데

예를 들어 다음과 같은 프로그램.


/* 파일 이름 : C7-8.C
프로그램 내용 : if문 예제. */
#include
void main()
{
int a=4;
if(a==1)
printf("One\n");
else if(a==2)
printf("Two\n");
else if(a==3)
printf("Three\n");
else if(a==4)
printf("Four\n");
else if(a==5)
printf("Five\n");
}




이런 프로그램은 switch문으로 고치는게 보기에 쉽습니다.

switch문으로 고치면


/* 파일 이름 : C7-9.C
프로그램 내용 : switch문 예제. */
#include
void main()
{
int a=4;
switch(a)
{
case 1:
printf("One\n"); break;
case 2:
printf("Two\n"); break;
case 3:
printf("Three\n"); break;
case 4:
printf("Four\n"); break;
case 5:
printf("Five\n"); break;
}
}




보기가 좀 쉽워졌죠?

즉 하나의 변수를 값 별로 특정 문장을 실행할땐

if문보다 switch문이 더 좋다는 것이죠.


이렇게 switch문은 if문을 대신할수도 있는데,

if문의 else문..즉.. 아무것에도 해당하지 않을때

실행하는 부분은 어떻게 만들까요?

이건 default라는 걸 사용해 만듬니다.

사용법은


switch(식)
{
case 값A:
.
.
break;
case 값B:
.
.
break;
.
.
default:
.
.
break;
}




이런 식으로 씀니다

만약 식이 어떤 값에도 해당하지 않으면

default아래에 나오는 것들을 실행해 주죠..

예제를 봅시다.


/* 파일 이름 : C7-10.C
프로그램 내용 : switch문 예제. */
#include
void main()
{
int a=4;
switch(a)
{
case 1:
printf("One\n"); break;
case 2:
printf("Two\n"); break;
case 3:
printf("Three\n"); break;
default:
printf("Another value\n"); break;
}
}




이 프로그램의 결과로는 Another value 가 출력되겠죠?

a가 어떤 값에도 해당하지 않으니..


이정도로 switch문의 설명도 마치도록 하죠.


for문

for문은 어떤 특정 부분을 반복해서 실행할때 사용합니다.

for문의 사용법은


for(수식1;조건;수식2)
{
문장들..
}




이런 구조 입니다.

수식1은 for문일 시작하기 전 한번 실행하는 것으로

보통 카운터 변수를 초기화 할때 씁니다.

그리고 수식2는 for문 내의 문장들을 반복해서 실행할때마다

한번씩 실행해 주는 수식으로 보통 카운터를 증가 시키거나

감소 시킬때 사용합니다.

for문은 조건이 만족할때 까지만 {}안의 문장을 반복해 실행해 줌니다.

그냥 보면 저걸로 어떻게 반복문을 만들수 있는지 이해가

잘 안 가실 거지만 예제를 보시면 이해가 되실 겁니다.


/* 파일 이름 : C7-11.C
프로그램 내용 : for문 예제. */
#include
void main()
{
int i;
for(i=1;i<=100;i++)
printf("%d ",i);
}




아주 간단한 예제 이지만 이걸 실행하면 1부터 100까지 출력이 됨니다.

프로그램을 잘 살펴보며 어떻게 반복이 되는지 알아보죠.

우선 카운터로 쓸 변수 i를 정의 했습니다.

그리고 for문이 있는데,

처음에 한번만 실행되는 수식으로 i에 1을 넣어 초기화 했습니다.

그리고 나서 i<=100인지 확인하죠. 그런데 이건 참이므로

for문 다음에 다오는 {}안의 문장들을 실행합니다.

for문 역시 반복실행할 문장이 하나이면 {}를 생략해도 됨니다.

그래서 여기선 생략했습니다. 이 문장은 i를 출력하고 있죠.

그러므로 처음엔 1이 출력됨니다.

이 문장 실행 후 i++를 실행합니다. 그래서 i가 2가 되죠.

그리고 나서 i<=100인지 검사해 다음에 나올 문장을 실행하죠

또 참이므로 i를 출력해 이번에 2가 출력되죠.

이런 식으로 계속 반복해 i가 101이 되면 for문이 끝나게 되죠..

이번 방법으로 for문을 통해 반복문을 만듬니다.

그리고 for문 안에 for문이 들어가도 상관 없습니다.


for문 안에서 쓸수 있는 키워드가 두가지 있는데

continue와 break입니다.

continue는 continue다음에 나오는 문장들은 다 무시하고

다시 for문의 처음으로 돌아가 실행을 계속하는 것이죠.

예를 들어 보죠..


/* 파일 이름 : C7-12.C
프로그램 내용 : for문 예제. */
#include
void main()
{
int i;
for(i=1;i<=100;i++)
{
if(i%2==0) continue;
printf("%d ",i);
}
}




우선 이걸 자세히 보면 i%2==0일때 continue가 실행되는 것을 알수 있습니다.

i%2가 0일때는 i가 2의 배수일 때 인데.. 그때 continue를 하면

밑에 i를 출력하는 문장은 무시되고 그냥 넘어가기 됨니다.

그러므로 이 예제를 실행하면 1부터 100까지 중 2의 배수가 아닌 수만

출력하게 되죠.


break는 for문을 빠져 나오는 기능을 합니다.

이것두 예를을 보면 쉽게 이해가 되실 겁니다.


/* 파일 이름 : C7-13.C
프로그램 내용 : for문 예제. */
#include
void main()
{
int i;
for(i=1;i<=100;i++)
{
if(i==51) break;
printf("%d ",i);
}
}




이 프로그램을 보면 i가 51일때 break를 했는데

그러면 for문을 바져 나오므로 1부터 50까지만 출력이 되다가,

for문을 빠져 나와 버립니다.

Previous:if 문
Next:while 문, do~while, goto, 함수로 값을 전달
오늘은 흐름 제어중 반복문에서 저번에 하지못한 while와 do-while에 대해 공부하겠습니다.
그리고 나서 함수에 값 전달하는 방법과 배열에 대해 공부하도록 하죠.


while문

우선 while문에 대해 알아보죠.

while문은 for문처럼 반복문이지만 for문보다 사용법은 간단합니다.

우선 사용법을 알아보죠.


while(조건)
{
/* 하나 이상의 C언어 문장 */
.
.
}

이렇게 사용하는데, 여기서 조건이 만족하는 동안만

while문 내의 문장들을 실행합니다.

이거 역시 문장이 하나이면 {}를 생략할수 있습니다.

그럼 예제를 볼까요?


/* 파일 이름 : C8-1.C
프로그램 내용 : while문 예제.. */
#include
void main()
{
int a=1;
int total=0;

while(a<=100)
{
total+=a;
a++;
}

printf("%d\n",total);
}

while문을 보면

a<=100일때 까지만 반복하는 반복문 입니다.

while문 안에를 보면

total+=a를 하고 있는데, 이건 total=total+a라는것은

언젠가 설명 했고 이걸 실행 한 후에 a++로 a를

1증가시키고 있습니다.

이렇게 계속 되다 보면 total=total+1, total=total+2...

이렇게 계속 실행이 되겠죠?

그래서 나중엔 1부터 100까지의 합이 total에 들어가고

그것이 출력되게 되죠. 그러므로 5050이 출력됨니다.


do-while문

그럼 이번엔 do-while문에 대해 알아보죠.

do-while문도 while문과 비슷하지만.

이건 조건이 밑에 있다는 것이 다름니다.

우선 사용법을 보죠.


do
{
/* 하나 이상의 C언어 문장 */
.
.
} while(조건);

이렇게 사용합니다.

이것은 처음에 문장들이 실행 된 후,

조건이 만족하면 또 실행시키고 하는 식으로 실행됨니다.

while문에서는 처음부터 조건이 만족하지 않으면

한번도 실행되지 않지만, do-while문은 처음부터 조건이

만족하지 않더라도 최소한 한번은 실행이 됨니다.


/* 파일 이름 : C8-2.C
프로그램 내용 : do-while문 예제.. */
#include
#include
void main()
{
int yn;
do
{
printf("Continue(Y/N)?");
yn=getche();
putchar('\n');
}while(yn!='Y' && yn!='N');
}

이 프로그램은 "Continue(Y/N)?"이라는 메시지를 출력하고

Y또는 N이 입력되면 종료되고 다른 것이 입력되면

계속 입력을 받는 것 입니다.

잘 분석해 보시면 왜 그렇게 되는지는 쉽게 아실수 있을겁니다.


goto문

goto문은 사실 강좌에 넣으려고 하지 않았지만,

그래도 C의 일부분 이므로 설명은 드리죠.

goto문은 특정 라벨로 뛰어 넘어 버리는 기능을 합니다.

사용법은...


goto 라벨;


이렇게 사용하죠.

여기서 라벨은 같은 함수 내에 존재해야 합니다.

라벨의 정의는 이렇게 하죠.


라벨명:


라벨명은 함수명이나 변수명과는 겹쳐도 상관 없고

서로 함수가 다르다면 라벨명이 겹쳐도 됨니다.

하지만 같은 함수 내에서 라벨명이 겹쳐서는 않되며,

라벨명도 심볼과 같은 규칙에 따라 지어져야 하죠.

즉 라벨명의 첫문자는 반드시 영문자 또는 언더스코어(_)가 와야 하고,

그 뒤로는 영문자, 숫자, 언더스코어만 올수 있다는 것이죠.

또한가지 라벨을 정의할때는 뒤에 :를 붙이지만,

goto문에서 라벨을 쓸때는 이걸 쓰면 않된다는 것 입니다.

예를 들어


Label:


이런 라벨이 있을때


goto Label:;


이렇게 해서는 않되고,


goto Label;


이렇게 해야 한다는 것 입니다.

goto문은 C같은 구조화 프로그래밍 언어 에서는

거의 쓸모가 없습니다. 또한 대부분의 C 프로그래머들은

goto문의 사용을 꺼려하고 있습니다.

이유는 goto문을 많이 사용할 경우 프로그램을 보기가 힘들어지고

복잡해 보이기 때문이죠.

그러므로 되도록 goto문의 사용은 줄이고, 함수나 C언어의 강력한

제어문들을 사용해 프로그래밍을 하는 습관을 들여야 합니다.


함수에 값 전달.

여러분이 어떤 함수를 만든다고 합시다.

그 함수는 printf와 같이 어떤 내용을 화면에 출력하는 함수이죠.

그런데 화면에 무언가를 출력하기 위해선 그 출력할 내용을

전달받아야 겠죠? 어떻게 하면 전달받을수 있을까요?

바로 그 방법에 대해서 자세히 알아보도록 하죠.

저번 2회때 함수에 대해 배울때 인수라는 것이 있었습니다.

그때 인수는 함수가 어떤 처리를 할때 필요해서 전달받을 데이터라고

했죠. 이 인수를 통해서 함수가 값을 전달받을수 있습니다.


(1) 인수가 정확히?

인수에 대해 정확히 말하자면 함수에서 값을 전달받는데 쓰이는

변수를 말합니다. 즉 변수의 일종이죠. 아니 일종이 아니고

변수라고 생각하세요. 단 함수 시작시에 전달받을 값이 들어가게 됨니다.


(2) 인수 만들기.

인수는 어떻게 만들까요? 저번 2회때 함수 만드는 방법에 나와 있던것처럼

함수이름 옆에 있는 ()안에 인수를 나열해 주면 됨니다.

()안에 마치 변수를 선언하듯이 인수를 만들어 주면 되죠.

(인수가 변수니까 당연한 거죠?)

예를 들어 아무 값도 리턴하지 않고, 인수는 정수형태 a와,b를 가진

func라는 함수는


void func(int a, int b)
{
.
.
.
}

이런식으로 만들수 있겠죠?


(3) 인수의 사용

이제 인수가 변수라는건 알고 계시죠? 그러므로 인수 사용은

변수와 똑같다는 것도 아시겠죠? 뭐 더이상 설명드릴 것두 없군요.

그럼 중요한것 한가지 말씀드리죠.

우선 예제를 보세요.


/* 파일 이름 : C8-3.C
프로그램 내용 : 함수 예제 */
#include
void func(int data)
{
data=10;
}
void main()
{
int var=1;
func(var);
printf("%d\n",var);
}

이 예제를 잘 보세요.

여기서 main함수에는 var이란 변수가 있죠?

그리고 그 변수에 초기값으로 1을 주었죠.

그런다음 func를 호출했는데, 인수로 var이란 변수를 주었죠?

그럼 func함수를 보세요. func함수에는 data라는 인수가 있는데

이 함수 호출시에 var이란 변수에는 1이 들어있었으므로

data에도 1이 들어갑니다. func에서는 data를 10으로 바꾸고 있군요.

그럼 이렇게 data를 10으로 바꿨는데, main함수의 var이란 변수도

10으로 바뀔까요? 이 프로그램을 실행해 보시면 아시겠지만

바뀌지 않습니다. 함수 호출시에 변수를 인수로 주면 단지

변수에 들어있는 값을 복사해서 전달해 줄 뿐이죠.

그러므로 바뀌지 않습니다. 이렇게 함수에 값 전달시

변수에 들어있는 값을 그냥 복사해서 전달해 주는 호출 방법을

Call by value라고 합니다.

Previous:switch 문, for 문
Next:배열
배열

배열은 저번에 문자열을 배울때 문자배열 때문에 간략히

설명 드린 적이 있습니다.

이번엔 그 배열에 대해 좀더 자세히 배워보도록 하겠습니다.

배열이란 데이터 구조의 하나로 데이터들을 일정 수 만큼

늘어놓은 구조라고 생각하시면 됨니다. 이때 각각의 데이터를

배열의 원소라고 합니다.

배열은 많은 데이터를 관리할때 편하죠.


(1) 배열의 선언

배열은 변수와 마찬가지로 사용전에 선언해야 합니다.

선언은 다음과 같이 합니다.


데이터형태 배열명[배열크기];


이렇게 하죠.

여기서 데이터 형태는 배열에 저장할 데이터에 형태이고,

배열명은 그 배열을 상징하는 심볼, 배열 크기는 몇개의 데이터를

늘어놓을 것 인지를 정하는 것 입니다.

예를 들어 정수 형태의 데이터를 10개 늘어놓은 Array라는 배열은


int Array[10];


이렇게 선언합니다.


(2) 배열의 사용

배열을 선언했으면 사용법도 알아야 겠죠? 배열 사용은


배열명[첨자] = 값;

변수 = 배열명[첨자];


이런 식으로 사용합니다.

여기서 첨자는 배열의 늘어놓은 데이터중 몇번째 원소를 사용할 건인지를

정하는 것 입니다. 그리고 첨자의 시작은 0부터라는 것을 기억하세요.

예를 들어 아까 Array라는 배열의 첫번째 원소에 10을 넣을때는


Array[0]=10;


이렇게 하는 것이죠. (첨자의 시작이 0이므로 첫번째 원소의 첨자는 0)


(3) 배열의 초기값

보통 변수들은 선언할때 초기값을 줄수 있습니다.

그런데 배열에 초기값을 줄순 없을까요?

당연히 할수 있죠..!

그런데 일반 변수의 초기값을 주는 것보다 좀 복잡합니다.

배열의 초기값을 주는 방법은


데이터형태 배열명[배열크기]
= { 1번째 원소의 초기값, 2번째 원소의 초기값, ... };




이렇게 하면 됨니다.

즉 {}안에 각 원소의 순서대로 초기 값을 적어주면 되는 것이죠.

예를 들어 아까 Array배열에 1부터 10까지의 초기값을 넣어 선언하면


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


이렇게 됨니다. 이렇게 하면 Array[0]에는 1이, Array[1]에는 2가...

Array[9]에는 10이. 이런식으로 들어갑니다.

배열을 초기값을 주어서 선언할때는 배열크기를 생략해도 됨니다.

즉 위에것은


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


이렇게만 해 줘도 된다는 것이죠.

이렇게 해 주면 초기값이 10개이므로 자동으로 배열크기를 10으로 지정해 줌니다.

하지만 초기값이 없을땐 반드시 크기를 지정해야 합니다.




int Array[];


이렇게 선언하는 건 절데로 안됨니다.

배열의 초기값을 주는건 일부만 주어도 됨니다.

즉 배열의 원소수가 10개이어도 앞에 5개만 초기값을 주어도 된다는 것이죠.

예를 들어


int Array[10] = { 1, 2, 3, 4, 5 };


이렇게 하면 앞에 5개의 원소만 초기값이 들어가고

나머지는 초기값이 없게 됨니다.

만약 배열이 문자 배열일때는 문자열 그대로를 초기값으로 주어도 됨니다.




char Array[10] = "String";


이렇게해도 된다는 것이죠.

이렇게 선언한것과


chat Array[10] = { 'S', 't', 'r', 'i', 'n', 'g' };


이렇게 선언한것의 차이는 무엇인지 아시겠죠?

처음 방법으로 선언한건 문자열을 그대로를 초기값으로 주었으므로

끝에 NULL문자가 들어가겠죠? 그러므로


chat Array[10] = { 'S', 't', 'r', 'i', 'n', 'g', NULL };


이것과 똑같은 것이 됨니다.


다차원 배열

위에서 배운 형태의 배열을 1차원 배열 이라고 합니다.

하지만 배열에는 2차원 배열 3차원 배열등등 계속 이어지죠.

이렇게 2차원 배열 이상을 통틀어 다차원 배열 이라고 합니다.

사실 다차원 배열이라고 해서 1차원 배열과 크게 다를것이 없습니다.

C에서 다차원 배열은 쉽게 배열의 배열.. 이라고 생각하시면 됨니다.

예를 들어 2차원 배열은 배열의 배열이고, 3차원 배열은 배열의 배열의 배열이

되는 것이죠.


(1) 다차원 배열의 선언

1차원 배열은 선언할때 일반 변수 선언과 똑같으나 []를 붙여주고

그 안에 배열의 크기만 정해주면 되었었죠?

2차원 배열은 배열의 배열이니 선언은 1차원 배열과 똑같으나

[]를 더 붙여주고 그 안에 배열 크기를 넣어주면 되는 것이죠.



데이터형태 배열명[배열크기][배열크기];


이렇게 선언해 주면 되는 것 입니다.

3차원 배열 역시 2차원 배열 선언과 똑같으나 []를 더 붙여주고

그 안에 배열 크기를 넣어 주면 됨니다. 그러니까


데이터형태 배열명[배열크기][배열크기][배열크기];


이렇게 선언해 주면 3차원 배열이 되는 것 입니다.

4차원 5차원...도 이런 식으로 []만 늘어날 뿐이죠.

예를 들어 Array라는 10*10의 크기를 갖는 2차원 정수 배열은


int Array[10][10];


이렇게 선언하면 됨니다.

만약 10*10*10의 크기를 갖는 3차원 배열이라면


int Array[10][10][10];


이렇게 선언하면 되겠죠?


(2) 다차원 배열의 사용

다차원 배열 사용시에도 단지 []만 늘어나게 됨니다.

2차원 배열의 경우


배열명[첨자][첨자] = 값;

변수 = 배열명[첨자][첨자];


이렇게 사용할수 있고, 3차원 배열의 경우는


배열명[첨자][첨자][첨자] = 값;

변수 = 배열명[첨자][첨자][첨자];


이런 식으로 사용할수 있죠.

예를 들어 보죠. 아까 예로 들었던 Array라는 10*10크기의

2차원 정수 배열이 있을때, 1번째 배열의 3번째 원소에 3을 넣으려면


Array[0][2]=3;


이렇게 해 주면 됨니다.

만약 Array라는 10*10*10크기의 3차원 정수 배열이 있다고 하고,

1번째 배열의 3번째 배열의 2번째 원소에 2를 넣으려면


Array[0][2][1]=2;


이렇게 하면 됨니다.


(3) 다차원 배열의 초기값.

다차원 배열 역시 초기값을 가질수 있습니다.

다차원 배열에 초기값을 넣는것 역시 다차원 배열이 배열의 배열이라는 것을

생각하시면 쉽게 하실수 있습니다.

1차원 배열에 초기값을 지정할때는 {}안에 초기값을 원소의 순서데로

써 주면 되었었죠?

그러므로 2차원 배열은 배열의 배열 이므로 {}안에

또 {}를 써서 초기값을 주면 됨니다. 예를 들어


int Array[2][3];


이런 배열이 있다고 합니다. 이걸 초기값을 주어서 선언하면


int Array[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } };


이렇게 하면 되는 것이죠.

3차원 배열 이라면 {}안에 {}안에 또다시 {}안에 원소의 초기값을

써 주면 되는 것이고요. 즉



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





이렇게 하면 됨니다.

다차원 배열에 초기값을 지정하는 방법은 이것 말고도

그냥 1차원 배열처럼 지정하는 방법도 있습니다.

1차원 배열처럼 {}안에 배열의 모든 원소의 순서대로 초기값을 적어 주는 것이죠.

예를 들어 아까


int Array[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } };


이건


int Array[2][3] = { 1, 2, 3, 4, 5, 6 };


이렇게 써 주어도 상관 없습니다.


배열을 인수로 전달.

아까 함수에 값 전달하는 방법을 배웠죠?

그런데 그때 배운건 일반 데이터에 대한 것이었습니다.

그럼 배열을 인수로 전달한다면 어떻게 할까요?

배열 역시 일반 데이터와 전달 방법이 똑같습니다.

그냥 인수를 배열로 만들어 주면 되죠.

예를 들어 아무 값도 리턴하지 않고, 인수로는

정수형태의 10개의 원소를 가진 a라는 배열이 있는 func라는 함수는



void func(int a[10])
{
.
.
.
}


이렇게 만들고 인수 사용도 일반 배열과 똑같이 사용하면 됨니다.

그런데 여기서 배열의 크기를 생략해도 됨니다.




void func(int a[])
{
.
.
.
}

이렇게 해도 된다는 거죠. 이렇게 해도 아무 상관 없어요.

두가지의 차이점은 전혀 없죠.

그럼 여기서 중요한것 한가지를 말씀드릴께요.

예제를 보시죠.



/* 파일 이름 : C8-4.C
프로그램 내용 : 함수 예제 */
#include
void func(int data[])
{
data[0]=10;
}
void main()
{
int i;
int var[10]={ 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };

func(var);
for(i=0;i<10;i++)
printf("%d\n",var);
}


이 소스는 아까 나왔던 예제를 조금 변형 시켜서 func함수의 인수를

배열로 한 것인데요.

여기서 func함수를 호출할때 var이란 변수를 인수로 주었습니다.

그리고 func함수에서는 data라는 인수로 배열을 전달받았는데,

data의 첫번째 원소를 10으로 바꾸고 있죠.

그럼 이때 main함수에 있는 var이란 배열의 첫번째 원소도

10으로 바뀔까요?

아까의 경우를 생각해 보면 바뀌지 않을것 같지만, 바뀜니다.

이유는 배열을 인수로 전달할 때는 그 주소를 전달해 주기 때문이죠.

자세한 이유는 지금 설명하기에는 무리이고, 포인터 강좌가 끝난 후에

설명해 드리죠. 지금은 그냥 바뀐다는 것만 꼭 알아두세요.

그리고 이렇게 인수 전달시 주소를 전달해 주어 함수에서 인수의

값을 바꾸면 함수를 호출한 곳의 변수도 값이 바뀌는 호출 방법을

Call by reference라고 합니다.

Previous:while 문, do~while 문, goto. 함수로 값을 전달
Next:포인터 1
이번 강좌 에서는 포인터에 대해서 알아보도록 하겠습니다.
포인터는 C언어에서 정말로 중요하죠.
C가 강력한 이유중 하나가 이 포인터 때문입니다.
대부분의 사람들이 C언어를 공부할때 바로 이 포인터를 가장 어렵게 생각합니다. 하지만 제가 생각하기엔 이 포인터는 그렇게 어려운 것이 아니라고 생각합니다. 어렵다는 생각을 갖지 마시고 포인터는 정말 쉬운 것이라는 생각을 갖고 이 강좌를 읽어 보시기 바랍니다.


포인터가 도대체 무엇?

포인터의 정확한 이름은 '포인터형 변수' 입니다.

그냥 줄여서 포인터라고 하는 것이죠.

그럼 여기서 포인터는 변수라는걸 아셨겠죠?

변수에는 정수 형태, 장정수, 부동 소숫점 수, 문자 형태 등등이

있습니다. 그럼 포인터는 도대체 어떤 형태 일까요?

포인터는 바로 주소 형태 입니다. 그러니까 정수도 아니고,

문자도 아닌 메모리의 주소를 기억시키는 변수라는 것이죠.

포인터는 변수이고, 변수 중에서도 주소를 기억시키기 위한

변수라는 것을 꼭 기억해 두시면 다음은 잘 이해가 되실 겁니다.


포인터는 어떻게? 선언을..

변수는 사용전에 선언을 해 주어야 합니다.

포인터 역시 변수 이므로 선언을 해 주어야 하겠죠?

포인터의 선언은 다음과 같이 합니다.


데이터형태* 포인터이름;


아까 포인터는 주소를 기억시키는 변수라고 했습니다.

그런데 주소에도 종류가 있죠?

정수 형태 변수의 주소, 문자 형태 변수의 주소 등등..

이런 어떤 형태의 주소인지를 지정해 주는 것이 데이터형태 입니다.

만약 주소의 형태가 특별히 정해져 있지 않았다면 void를 씀니다.

void를 써서 포인터를 선언하면 어떤 형태의 주소든지 넣을수 있죠.

그리고 *는 그 변수가 포인터임을 알리기 위해 써 주는 것이고,

포인터이름은 그 포인터를 상징하는 심볼입니다.

예를 들어 정수 형태의 변수의 주소를 넣는 포인터는


int* Pointer;


이렇게 선언합니다.

포인터 선언시 *의 위치는 데이터형태와 포인터이름 사이에라면

어느 곳에 와도 상관 없습니다.

예를 들어 아까 Pointer라는 변수는


int * Pointer;


이렇게 선언해도 되고,


int *Pointer;


이렇게 선언해도 상관 없습니다.


포인터 연산자

C언어 에서는 포인터 사용시에 사용하는 연산자가

2가지가 있습니다. 그 두가지는 기존의 비트별 논리곱 연산자인

&와 곱셈 연산자인 *입니다.

포인터 연산자로 쓰일때 &는 주소 연산자라고 하고,

*는 참조 연산자라고 합니다.


(1) 주소 연산자

주소 연산자란 어떤 변수의 주소를 얻는 연산자를 말합니다.

사용법은 주소를 얻고자 하는 변수의 앞에 &를 붙여 주기만 하면 됨니다.

그러니까


포인터 = &변수;


이렇게 하면 변수의 주소가 포인터에 들어가죠.

예를 들어


int Variable=10;
int *Pointer;

이렇게 두 변수를 선언했다고 할때,


Pointer = &Variable;


이렇게 해 주면 Variable라는 변수의 주소가 Pointer라는 변수로 들어갑니다.


이것을 실제 메모리 상의 구조를 통해 다시한번 살펴보죠.

처음에


int Variable=10;
int *Pointer;

이렇게 변수를 선언할 당시는


변수이름 | Variable Pointer
주소 | 1 2 3 4 . . .
---------+--------------------------------------------
값 | 10 ? ? ?

다음과 같은 메모리 구조를 갖습니다.

여기서 Variable라는 변수는 실재 메모리 주소 1을 할당받았다고 하고,

Pointer라는 변수는 실재 메모리 주소 3을 할당받았다고 합시다.

다음에


Pointer = &Variable;


이렇게 하면 Pointer라는 변수에 Variable의 주소인 1이 들어가서


변수이름 | Variable Pointer
주소 | 1 2 3 4 . . .
---------+--------------------------------------------
값 | 10 ? 1 ?

이렇게 됨니다.


(2) 참조 연산자

포인터는 주소를 기억시키는 변수라는 것을 이미 배웠습니다.

그렇다면 그 기억시키고 있는 주소에 기억되어 있는 값을 사용하거나

다른 값으로 바꿀땐 어떻게 할까요? 그때 참조 연산자인 *를 사용 합니다.

사용법은


*포인터 = 값;

변수 = *포인터;


이런 식으로 사용하죠.

첫번째의 경우 포인터에 저장되어 있는 주소에 값을 넣어 줌니다.

두번째의 경우는 포인터에 저장되어 있는 주소에 기억되어 있는 값을

변수로 넣어 주게 되겠죠?

예를 들어..


int Var1=10, Var2;

int *Pointer;


이렇게 세개의 변수를 선언했고,


Pointer = &Var1;


이렇게 Pointer에 Var1의 주소를 넣었다고 합시다.

이때


*Pointer=5;


이렇게 해 주면 Pointer에 저장되어 있는 주소 즉

Var1이라는 변수의 주소에 5를 넣어 주므로 결국에는 Var1은 5가 됨니다.

그 다음으로


Var2=*Pointer;


이렇게 했다면 Pointer에 저장되어 있는 주소에 들어있는 값,

즉 Var1라는 변수의 주소에 들어있는 값인 5가 Var2에도 들어가

Var1, Var2가 모두 5가 되죠.


이걸 실재 메모리 구조를 보며 다시한번 살펴 볼까요?

처음에


int Var1=10, Var2;

int *Pointer;


이렇게 세개의 변수를 선언할 당시는


변수이름 | Var1 Var2 Pointer
주소 | 1 2 3 4 . . .
---------+--------------------------------------------
값 | 10 ? ? ?

다음과 같은 메모리 구조를 갖는데,

여기서 Var1라는 변수는 실재 메모리 주소 1을 할당받았고,

Var2라는 변수는 실재 메모리 주소 2를, Pointer라는 변수는

메모리 주소 4을 할당받았다고 합시다.

이때


Pointer = &Var1;


이렇게 Pointer라는 변수에 Var1의 주소인 1을 넣어주면


변수이름 | Var1 Var2 Pointer
주소 | 1 2 3 4 . . .
---------+--------------------------------------------
값 | 10 ? ? 1




이렇게 되겠죠?

그리고 다음으로


*Pointer=5;


이렇게 해 주면 Pointer에 기억되어 있는 주소 즉 1이라는 주소에

5를 넣어 주어


변수이름 | Var1 Var2 Pointer
주소 | 1 2 3 4 . . .
---------+--------------------------------------------
값 | 5 ? ? 1

이렇게 됨니다. 그러므로 Var1의 값은 5로 바뀌게 됨니다.

마지막으로


Var2=*Pointer;


이렇게 하면 Pointer에 저장되어 있는 주소

즉 1이라는 주소에 들어있는 값인 5가 Var2에도 들어가


변수이름 | Var1 Var2 Pointer
주소 | 1 2 3 4 . . .
---------+--------------------------------------------
값 | 5 5 ? 1

이렇게 Var1과 Var2에 모두 5가 들어가게 되죠.


포인터에 초기값 주기.

변수는 선언시에 초기값을 줄수 있었죠?

그런데 포인터도 변수이므로 당연히 초기값을 줄수 있겠죠?

포인터에 초기값을 주는 방법은 일반 변수와 똑같습니다.

그러니까


int* Pointer=1;


이렇게 하면 1이라는 주소를 초기값으로 준 것이죠.

그런데 뭔가 이상하죠?

주소는 자기 마음데로 정하는게 아니거든요.

그래서 저렇게 선언하는 사람을 '바보'라고 하죠.

일반적으로 포인터에 초기값을 줄 때는


데이터형 변수;
데이터형* 포인터이름 = &변수;




이렇게 합니다.

우선 변수를 선언하고 그 변수의 주소를 초기값으로 주는 것이죠.

예를 들어


int Variable;
int* Pointer=&Variable;

이렇게 하면 Variable라는 변수의 주소가 Pointer라는 포인터에

초기값으로 들어갑니다.


다시한번 포인터에 대해 정리.

포인터에 대해 다시한번 정리를 해 보죠.

우선 포인터는 변수이고, 변수 중에서도 주소를 기억시키는 변수 입니다.

포인터 선언은


데이터형태* 포인터명;


이렇게 해 주면 되고, 여기서 데이터형태는 그 포인터가 어떤 형태의

변수의 주소를 넣는 것인지를 지정하는 것이었죠?

다음으로 포인터 연산자에는 주소 연산자(&)와, 참조 연산자(*)가 있는데,

주소 연산자는 어떤 변수의 실제 주소를 얻고자 할때 사용하는 것으로

주소를 얻고자 하는 변수 앞에 붙여 주기만 하면 되고요.

참조 연산자는 포인터에 저장되어 있는 주소에 기억되어 있는

데이터를 변경시키거나 사용할때 쓰는 것으로, 포인터의 앞에

붙여 주기만 하면 됨니다.

예를 들어


int Variable;
int* Pointer;

이렇게 변수 두개가 있을때,


Pointer=&Variable;


이렇게 하면 Pointer라는 포인터에는 Variable라는 변수의

주소가 들어가고


*Pointer=10;


이렇게 하면 Pointer에 들어있는 주소 즉 Variable의 주소에 들어있는

값이 변경되므로 결국은 Variable가 변경되어 10이 들어가게 되죠.

마지막으로 포인터에 초기값을 줄때는 일반 변수와 똑같으나

주소는 마음데로 정하는 것이 아니므로 일반적으로

일단 변수를 선언하고 포인터에는 그 변수의 주소를 초기값으로 줍니다.

예를 들어


int Variable;

int* Pointer=&Variable;


이런 식으로.

그럼 예제를 하나 보죠.


/* 파일 이름 : C9-1.C
프로그램 내용 : 포인터 예제. */
#include
void main()
{
int Var1, Var2;
int *pVar=&Var1;

*pVar=10;
printf("%d\n",Var1);

Var2=*pVar;
printf("%d\n",Var2);

pVar=&Var2;
*pVar=5;
printf("%d\n",Var2);
}

한번 잘 분석해 보세요. 어떤 결과가 나올까요?

결과는


C:\>C9-1.EXE
10
10
5

C:\>

이렇게 나옴니다. 이유는 잘 분석해 보시면 알게 됩니다.

Previous:배열
Next:포인터 2 (포인터의 배열)
포인터의 배열

포인터를 배열로 만들수가 있을까요?
당연히 할수 있겠죠? C로는 못하는게 없으니..
포인터의 배열을 만드는 것은 정말 쉽습니다.
일반 변수의 배열에 대해서는 배웠는데 그거랑 똑같이 하면 되지만 단지 데이터형태와 포인터배열명 사이에 *를 넣어 주면 되죠.
그러니까


데이터형태* 포인터배열명[크기];


이렇게 해 주면 됨니다.

예를 들어 정수 형태의 변수의 주소를 저장하는 10개의

원소를 가진 포인터배열은


int* PointerArray[10];


이렇게 선언하죠.

2차원 배열 역시 일반 배열과 똑같지만 단지 *만 넣어 주면 되죠.

그러니까


int* PointerArray[10][10];


이런 식으로 해 주면 되죠. 그리고 3차원 배열은


int* PointerArray[10][10][10];


이런 식으로 하고요.


포인터를 어디에 써먹지?

지금까지 포인터에 대해 배웠습니다.

그런데 이 포인터를 과연 어디에 써먹을수 있을까요?

저번 강좌에서 일반 변수를 함수 호출시 인수로 전해주면

단지 변수에 들어있는 값을 복사해서 전해주는 CAll by value라는

호출 방법을 쓴다는 것을 배웠습니다.

그리고 배열의 경우 그 주소를 전해 주는 Call by reference라는 방법을

쓴다는 것도 알았죠.

그럼 일반 변수를 Call by reference로 전해 줄수는 없을까요?

포인터를 이용하면 이것이 가능합니다.

우선 방법을 알아보기 전에 예제 하나를 보죠.


/* 파일 이름 : C9-2.C
프로그램 내용 : 포인터 예제. */
#include
void func(int data)
{
data=10;
}
void main()
{
int var=1;
func(var);
printf("%d\n",var);
}

이 소스는 Call by value를 사용하는 함수 예제 입니다.

func함수에서는 data를 10으로 바꿨지만 main함수의

var은 바뀌지 않고 결국엔 1이 출력되죠.

그럼 이걸 Call by reference로 바꿔 보죠.

바꿀려면 과연 어떻게 해야 할까요? 우선 func함수 부터 고쳐야 겠죠?

Call by reference는 주소를 전달 받는 것이니 인수를 포인터로

바꿔주어야 겠군요.


void func(int* data)
{
data=10;
}

이렇게 말이죠. 그런데 여기서 바꿀게 하나 더 남았죠?

data는 이제 포인터이므로 사용시에는 참조 연산자를 써서 사용해야 겠죠?




void func(int* data)
{
*data=10;
}




이렇게 바꿔야 하죠. 그럼 func함수는 다 고쳤네요.

그런데 main함수에서도 바꿀게 있죠? func함수를 호출할때

그냥 변수를 보내면 않되고 주소를 보내야 하잖아요.

그러므로


func(&var);


이렇게 호출해주면 되겠죠?

이렇게 해서 바꾼 소스를 보도록 하죠.


/* 파일 이름 : C9-3.C
프로그램 내용 : 포인터 예제. */
#include
void func(int* data)
{
*data=10;
}
void main()
{
int var=1;
func(&var);
printf("%d\n",var);
}

이렇게 되겠내요. 그럼 이젠 Call by reference로 호출하게 됨니다.

그러므로 1이 아닌 10이 출력되게 되죠.


이렇게 해서 이번 강좌는 마치도록 하겠습니다.

다음 강좌에서는 포인터와 배열의 관계에 대해 알아보죠.

Previous:포인터 1
Next:포인터 3 (포인터와 배열의 관계, 포인터로 문자열 처리, 함수 포인터
이번 강좌 에서는 포인터와 배열의 관계와 함수 포인터,그리고 구조체에 대해서도 알아보겠습니다.


배열이 이상하다?

배열을 사용하다가 보면 이상한 점을 많이 발견하셨을 겁니다.

예를 들어 scanf함수로 정수 변수에 어떤 정수를 입력 받을땐


int Var;
scanf("%d",&Var);




이렇게 했죠? 그런데 문자배열에 문자열을 입력 받을땐


char Var[100];
scanf("%s",Var);

이렇게 했습니다. 왜 문자배열에 문자열을 입력 받을땐


scanf("%s",&Var);


이렇게 &를 붙이지 않았을까요?

그리고 함수에 값을 전달할때 배열을 전달하면 왜 Call by reference로

전달 되었을까요? 정말 궁금하시지 않습니까?

이유는 배열명이 포인터이기 때문이죠.

그러니까 배열명은 배열의 첫번째 원소의 주소를 가지고 있는 포인터 입니다.

단 그 주소를 바꿀수 없으므로 포인터 중에서 상수 포인터라고 하면 되겠죠?


또다른 방법의 배열 사용.

우선 예제를 보죠.


/* 파일 이름 : C10-1.C
프로그램 내용 : 포인터와 배열 예제. */
#include
void main()
{
int array[10]={ 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
int var;

var=*array;
printf("%d\n",var);
}

이 예제에서 array라는 배열에 참조 연산자를 붙여서


var=*array;


이렇게 했는데, 그럼 var에는 어떤 값이 들어갈까요?

아까 배열명이 배열의 첫번째 원소를 가르키고 있는 포인터라고 했습니다.

그러므로 첫번째 원소의 값인 1이 var에 들어가겠죠?

그러므로 1이 출력됨니다.

그럼 만약 두번째 원소의 값을 var에 넣고 싶다면 어떻게 할까요?

물론


var=array[1];


이렇게 해도 되겠지만, 포인터를 이용한 방법으로요.

두번째 원소는 첫번째 원소 주소의 다음 주소에 들어있습니다.

그러므로 첫번째 원소의 주소에 1을 더해준 주소에 들어있겠죠?

그래서


var=*(array+1);


이렇게 해 주면 됨니다. 그런데 int형태는 2바이트이죠?

그럼 2를 더해야 하는데, 1만 더해서 2번째 원소를 읽을수 있을까요?

C컴파일러는 이것을 알아서 계산해 1을 더했지만 실재로는 2를 더해 줌니다.

그러므로 프로그램 상에서는 1만 더해 주어야 하죠.

만약 세번째 원소를 var에 넣는 다면


var=*(array+2);


이렇게 하면 되겠죠?

이렇게 하면 컴파일러가 알아서 2가 아닌 4를 더해 주게 됨니다.

다시한번 정리하면


*(array+0)은 array[0]
*(array+1)은 array[1]
*(array+2)는 array[2]
.
.

이렇게 되는 것이죠.


포인터로 문자열 처리를?

문자열을 배울때 포인터로도 문자열을 처리할수 있다고 배웠습니다.

그런데 그 방법은 아직 배우지 않았죠?

그걸 지금 설명드리도록 하겠습니다.

사실 포인터로는 문자열을 처리한다기 보다 그냥 지시한다고

표현하는 것이 좋을것 같군요. 하여튼 잘 읽어 보세요.

배열은 포인터 상수입니다.

그래서 문자배열에 문자열을 넣을때는 반드시 strcpy함수를

써야만 하죠. 예를 들어


char Str[10];


이런 문자 배열이 있을때


strcpy(Str,"String");


이런건 가능했지만


Str="String";


이런건 불가능 하죠. 이렇게 하면 상수에 값을 넣는 것이므로

결국에는


120=2048;


이런거나 다름 없거든요.

그렇지만 Str을 문자배열이 아닌 포인터로 선언하면 어떻게 될까요?




char *Str;


이렇게 말이죠. 이렇게 하면 Str은 상수가 아닌 변수가 되므로


Str="String";


이런게 가능합니다. 하지만 이건 단순히 "String"라는 문자열 상수가

기억되어 있는 주소를 Str에 넣어 준 것이죠.

문자배열로 선언했을때는 "String"라는 문자열을 넣을 공간이

확보되어 있으므로 strcpy로 문자열을 넣는것이 가능하지만,

포인터로 선언한건 문자열을 넣을 공간은 확보하지 않고

단지 문자열이 있는 주소를 넣을 공간만 확보할 뿐입니다.

그러므로 Str이 포인터로 선언된 것이라면


strcpy(Str,"String");


이렇게 할 경우 잘못하다간 다운이 되버리죠.

그래서 포인터로는 문자열 처리를 하지 못하고 대부분

문자열을 지시할때 쓰이는 것 입니다.


함수 포인터

지금까지 배운 포인터는 어떤 변수의 주소를 기억시키는 포인터였습니다.

그런데 함수의 주소를 기억시키는 포인터는 없을까요?

당연히 있죠. 그런데 지금까지 배운 포인터와는 선언부터 사용까지

방법이 조금 다릅니다. 지금부터 이걸 알아볼껀데

이걸 배우기 전에 우선 한가지 알아두실 것이 있습니다.

아까 배열명이 상수 포인터라고 했는데, 함수명도 바로 상수 포인터 입니다.

즉 함수의 시작 주소를 기억하고 있는 상수 포인터 이죠.


(1) 함수 포인터의 선언

함수 포인터 선언은


리턴형태 (*함수포인터명)([인수, 인수, ...]);


이렇게 합니다.

예를 들어 정수를 리턴하고 인수는 없는 함수의 주소를 넣는

FuncPointer라는 함수 포인터는


int (*FuncPointer)();


이렇게 선언합니다. 이렇게 선언한 FuncPointer라는 함수 포인터에는

정수를 리턴하고 인수가 없는 함수의 주소만 넣을수 있게 되죠.


(2) 함수 포인터의 사용

함수 포인터에 함수의 주소를 넣는 방법은 다음과 같습니다.


함수포인터 = 함수;


예를 들어 아까 나왔던 FuncPointer라는 함수 포인터에


int Function()
{
.
.
.
}

위와 같은 Function이라는 함수의 주소를 기억시키려면


FuncPointer=Function;


이렇게 하면 됨니다.

그런데 여기서 Function의 주소를 FuncPointer에 넣으려고 하는데 왜?


FuncPointer=&Function;


이렇게 주소 연산자를 사용하지 않았을까요?

아까 함수명은 상수 포인터라고 했죠?

그러므로 Function은 포인터 이기 때문에 &를 붙이지 않은 것 입니다.

포인터 안에는 주소가 들어 있으니까요.

함수 포인터에는 함수의 주소가 들어있는데 그 함수를

호출할때는 어떻게 할까요?

아까 함수명은 함수의 시작 주소가 들어있는 포인터라고 했습니다.

그런데 함수 포인터 역시 함수의 시작 주소를 기억시키고 있는

포인터 이죠. 그러므로 그냥 함수 호출 하는 것과 똑같은 방법으로

호출하면 됨니다. 예를 들어 위에 나왔던 FuncPointer라는 함수 포인터의

함수를 호출하려면


FuncPointer();


이렇게 해 주면 되는 것이죠.

이것으로 함수 포인터에 대한 내용은 마치도록 하겠습니다.

Previous:포인터 2 (포인터의 배열)
Next:구조체
구조체

구조체가 무엇일까요?

여러분이 만약 학교에서 학생 정보 관리 프로그램을 만든다고 합시다.

그리고 학생 한사람당 다음과 같은 데이터가 있다고 하고, 학생수는

100명 이라고 합시다.


학년, 반, 번호, 이름, 나이, 전화번호


그렇다면 여러분을 어떻게 데이터를 처리하시겠습니까?

각 데이터별로 100개의 원소를 갖는 배열을 만들어 처리하시겠습니까?

그렇게 하면 복잡해지죠?

이럴때 구조체를 사용합니다. 구조체는 한개 이상의 변수를

묶어둔 것이라고 생각하시면 됨니다. 그렇게 묶어서 한개의 변수처럼

취급하는 것이죠.

예를 들자면 위와 같은 데이터를 넣을 변수들을 하나로 묶어서 학생이라는

구조체를 만들어 사용하면 처리가 쉬워지겠죠?


(1) 구조체 만들기와 구조체 변수

구조체를 일반적으로 다음과 같이 만듬니다.


struct 구조체이름
{
데이터형태 변수;
데이터형태 변수;
.
.
};

여기서 {}안에 들어있는 변수들이 구조체를 구성하는 변수로

멤버변수라고 합니다.

예를 들어 아까 학생 데이터를 처리하기 위한 구조체를 만들면


struct Student
{
int Grade;
int Class;
int Number;
char Name[16];
int Age;
char Phone[16];
};

이렇게 만들어주면 되겠죠?

이렇게 구조체를 만들었으면 사용해야 하는데,

여기서 만든걸 그냥 사용하는 것이 아닙니다.

여기서 만든건 단지 그 구조체가 어떤 멤버변수로 구성되어 있는지

형식을 만들어 준 것 뿐이거든요.

그러므로 사용하기 위해선 그 형식에 맞추어 기억 장소들을 마련하여

변수를 만들어 사용해야 합니다.

이때 만드는 변수를 구조체 변수라고 하죠.

구조체 변수를 만드는 것은 간단합니다.

다음과 같이 하면 되죠.


struct 구조체이름 구조체변수[, 구조체변수, ...];


예를 들어 위에 있는 Student라는 구조체에 대한 구조체 변수 s를 선언하려면


struct Student s;


이런 식으로 해 주면 됨니다.

구조체 변수는 구조체를 만들때 같이 만들어 줄수도 있는데

방법은 다음과 같이 하면 됨니다.


struct [구조체이름]
{
데이터형태 변수;
데이터형태 변수;
.
.
} 구조체변수[, 구조체변수, ...];

예를 들어 아까 나왔던 Student라는 구조체를 만들면서

s라는 구조체 변수도 같이 선언한다면


struct Student
{
int Grade;
int Class;
int Number;
char Name[16];
int Age;
char Phone[16];
} s;

이렇게 해 주면 됨니다.

그리고 이렇게 구조체와 구조체 변수를 같이 선언할때는

구조체이름을 쓰지 않아도 됨니다.

즉 위의 것은


struct
{
int Grade;
int Class;
int Number;
char Name[16];
int Age;
char Phone[16];
} s;

이렇게 써도 된다는 것이죠.

하지만 이렇게 하면


struct 구조체이름 구조체변수[, 구조체변수, ...];


이런 식으로는 구조체 변수를 만들수 없게 되겠죠?

이유는 쓸 구조체이름이 없으니까요.


(2) 구조체 변수의 사용

이제 구조체 변수를 선언하는 것까지 배웠으니

구조체 변수를 사용하는 알아야 겠죠?

구조체 변수 안에는 한개 이상의 멤버 변수가 들어 있죠?

구조체 변수를 사용한다는 말은 그 멤버 변수를 사용한다는 말이 됨니다.

사용은 다음과 같이 하죠.


구조체변수.멤버변수 = 값;
변수 = 구조체변수.멤버변수;

예를 들어 아까 그 Student라는 구조체의 변수를


struct Student s;


이렇게 선언했다고 합시다.

그리고 이 구조체 변수의 멤버 변수중 Class에 5을 넣으려면


s.Class=5;


이렇게 하면 되고, Name이라는 멤버 변수에 "AAA"를 넣으려면


strcpy(s.Name,"AAA");


이렇게 하는 것 입니다.


(3) 구조체 변수의 초기값

구조체 변수에도 과연 초기값을 줄수 있을까요?

당연히 할수 있겠죠?

방법은 배열에 초기값 주는 것과 비슷합니다.

다음과 같이 하면 되죠.


struct 구조체이름 구조체변수 =
{ 첫번째 멤버변수의 초기값, 두번째 멤버변수의 초기값, ... };

예를 들어 아까 나왔던


struct Student
{
int Grade;
int Class;
int Number;
char Name[16];
int Age;
char Phone[16];
};

이런 구조체가 있을때 s라는 구조체 변수를 초기값을 주어 선언하면


struct Student s = { 1, 5, 10, "AAA", 14, "000-0000" };


이렇게 합니다. 이때 s.Grade에는 1이, s.Class에는 5가, s.Number에는 10이,

s.Name에는 "AAA"가, s.Age에는 14가, s.Phone에는 "000-0000"이

들어가게 되는 것이죠.


(4) 구조체 배열

구조체 역시 배열을 만들수 있습니다.

방법은 다른 배열들과 똑같은 형식으로 만들면 되죠.

그러니까


struct 구조체이름 구조체배열명[크기];


이렇게 해서 만들수 있습니다.

2차원 구조체 배열은


struct 구조체이름 구조체배열명[크기][크기];


이렇게 하고요. 3차원은 이제 설명 안해도 아시겠죠?

구조체 배열 선언의 예를 들어 보죠.

위에 나온 Student라는 구조체의 배열로 10개의 원소를 가지고 있고

이름이 sarray인 것은


struct Student sarray[10];


이렇게 선언하면 됨니다.

구조체 배열의 사용 역시 다른 배열들과 똑같은 형식으로

해 주면 됨니다.

즉 다음과 같이 하면 되죠.


구조체배열명[첨자].멤버변수 = 값;
변수 = 구조체배열명[첨자].멤버변수;

2/3차원 구조체 배열의 사용법은 뭐 설명이 필요 없죠?

그럼 예를 들어 위에 나온 sarray라는 구조체 배열의 세번째 원소의

Class라는 멤버에 5를 넣으려면


sarray[2].Class=5;


이렇게 하죠.


(5) 구조체 포인터

구조체 포인터도 역시 다른 포인터 들과 똑같은 형식으로

선언해 주면 됨니다.


struct 구조체이름* 구조체포인터명;


예를 들어 위에 나온 Student라는 구조체의 포인터로

이름이 spointer인 것은


struct Student* spointer;


이렇게 선언합니다.

구조체 포인터의 사용은 일반 포인터와 비슷합니다.

예를 들어 다음과 같이 구조체 변수와 구조체 포인터를

선언했다고 합시다.


struct Student s;
struct Student spointer;

이때 spointer라는 구조체 포인터에 s라는 구조체 변수의 주소를 넣을땐

주소연산자를 사용하여


spointer=&s;


이렇게 해 주면 되죠.

그럼 spointer에 저장되어 있는 구조체 변수의 주소에 기억된 내용들을

사용할때는 어떻게 할까요?

이미 배운 참조 연산자를 사용해


*spointer.Grade=1;
*spointer.Class=5;

이런 식으로 하면 될까요?

이렇게 하면 절대 않됨니다. 이유는 처음 설명하는 것이지만

참조 연산자(*)보다 멤버 엑세스 연산자(.)가 우선 순위가

높기 때문이죠. 그러므로 에러가 발생합니다.

이걸 해결하기 위해선 ()를 사용해 참조 연산을 먼저 실행하게

하면 해결이 됨니다.

그러니까 위의 것들은


(*spointer).Grade=1;
(*spointer).Class=5;

이렇게 해 주면 되는 것이죠. 그런데 이렇게 쓰는건 좀 복잡하죠.

이걸 좀 편하게 쓰기 위한 연산자가 있는데

바로 ->이죠. 사용은


구조체포인터->멤버변수 = 값;
변수 = 구조체포인터->멤버변수;

이렇게 합니다.

예를 들어 위에 나왔던 것들은


spointer->Grade=1;
spointer->Class=5;

이렇게 해 주면 더 간단해 지게 되죠.

Previous:포인터 3 (포인터와 배열의 관계, 포인터로 문자열 처리, 함수 포인터)
Next:공용체, 열거형 상수, typedef 문
공용체

공용체란 한개 이상의 변수가 기억장소를 공유하고 있는

구조라고 생각하시면 됨니다.

공용체는 구조체와 완전히 똑같습니다. 단 그 안의 모든 멤버 변수들이

기억장소를 공유하고 있다는 점이 다르죠.

즉 모든 멤버 변수들의 주소가 같습니다.

그러므로 어떤 멤버 변수의 내용이 바뀌면 다른 모든 멤버 변수의

내용도 바뀌게 됨니다.

공용체의 선언부터 사용까지도 모두 구조체와 같다고 보시면 되는데

단 구조체는 struct라는 키워드를 썼지만 공용체는 union이라는

키워드를 쓰고 있죠. 그러므로 특별히 선언, 사용방법 등에 대해서는

설명 드리지 않겠습니다. 10회에 나왔던 구조체의 설명만 잘 읽으셨고

그것을 잘 이해하셨다면 구지 설명이 필요 없다고 봅니다.

그럼 몇가지 예를 들어 보고 공용체에 대해선 마치도록 하죠.

두 정수 변수 a와 b를 멤버 변수로 가지고 있는

Data라는 이름의 공용체는 다음과 같이 만들면 되겠죠?


union Data
{
int a;
int b;
};

구조체와 똑같지만 단지 union이라는 키워드를 쓴 것 뿐입니다.

그럼 사용할땐 어떻게 할까요? 구조체에선 구조체 변수라는 것을

선언해 사용했는데, 이와 마찬가지로 공용체에선 공용체 변수라는 것을 선언해

사용하면 됨니다.

예를 들어 d라는 이름을 가진 위의 Data라는 공용체의 변수는


union Data d;


이렇게 선언하겠죠?

그리고 멤버 변수인 a를 10으로 바꾼다고 하면 구조체와 같은 방법으로


d.a=10;


이렇게 해 주면 됨니다.

그런데 여기서 중요한 것이 있는데, 지금 멤버 변수인 a에 10을

넣었습니다. 그렇다면 또다른 멤버변수 b에는 어떤 값이 들어갈까요?

b에도 역시 10이 들어가겠죠? 이게 구조체라면 b라는 멤버변수에는

아무 변화가 없겠지만 공용체이기 때문에 a와 b는 기억장소를 공유하고

있으므로 그렇게 되는 것이죠.

구조체처럼 공용체에도 공용체 배열과 공용체 포인터가 있는데

이건 생략해도 상관 없을것 같군요. 어차피 struct라는 키워드 데신

union이라는 키워드를 쓴다는 것 빼고는 구조체와 선언및 사용법 등이

똑같으니까요.

구조체를 잘 공부하셨다면 그리 어렵지 않으셨을 걸로 알고

이정도로 공용체 설명은 마치겠습니다.


열거형 상수

열거형 상수란 정수형 상수의 일종으로 동일한 용도로 쓰이는 상수들에게

그들을 대신할수 있는 이름을 부여하여 열거해 둔 것입니다.

열거형 상수를 만드는 방법은 다음과 같습니다.


enum [열거명] { 상수명 [= 상수값], ... } [변수, 변수, ..];


여기서 열거명 열거해둔 상수가 어떤 상수들인지 그 상수들

전체에 대한 이름입니다.

그리고 상수명은 상수를 대신할 이름, 상수값은 그 상수명에대한 실재 값,

마지막으로 변수는 일반 변수를 선언한 것으로서 정수형(int) 변수입니다.

여기서 상수값은 생략 가능한데 생략하면 그 전에 있는 상수의 다음 값으로

지정됨니다. 예를 들어 전에 값이 100이면 그 다음 값인 101이 되죠.

그런데 그 전에 있는 상수가 없을때 즉 처음일때는 0이 됨니다.

그리고 열거명과 변수들도 생략해도 됨니다.

그럼 예를 들어 다음과 같은 열거형 상수가 있다고 합시다.


enum NUMBER { ZERO, FIRST, SECOND, THIRD, FORHT, FIFTH };


이렇게 열거형 상수를 만들고 나면 프로그램 내에서

0대신 ZERO를 1대신 FIRST를.... 이렇게 사용할수 있습니다.

그리고 열거형 상수를 만들때 열거명은 생략 가능하므로


enum { ZERO, FIRST, SECOND, THIRD, FORHT, FIFTH };


이렇게만 해도 됨니다.

또한 위와 같은 열거형 상수를 만들면서 변수 a, b를 선언하려면


enum NUMBER { ZERO, FIRST, SECOND, THIRD, FORHT, FIFTH } a, b;


이렇게 하면 됨니다. 이때 선언된 a, b는 정수형(int) 변수입니다.

그러므로


enum NUMBER { ZERO, FIRST, SECOND, THIRD, FORHT, FIFTH };
int a, b;

이렇게 하는 것과 똑같은 것이죠.

마지막으로 어떤 프로그램에는


enum 열거명 변수명[, 변수명, ...];


이렇게 선언된 변수가 있습니다.

이것은 일반 정수형(int) 변수와 완전히 똑같습니다.

그러니까 위와나온 NUMBER라는 열거형 상수가 만들어져 있을때


enum NUMBER a, b, c;


이렇게 한 것은


int a, b, c;


이것과 똑같은 의미 입니다.


타입 만들기

C언어에는 여러가지 데이터 타입 키워드 들이 있습니다.

int, char, float, double등이 있죠?

이런건 그냥 쓰기에 불편한 점이 없습니다.

하지만 unsigned short int형태의 데이터를 많이 써야 한다고 합시다.

그럴때는 너무 길어서 쓰기가 지겨워 지죠.

이걸 간단히 쓰는 방법이 있는데 방법은 unsigned short int와

똑같은 형태의 데이터 타입을 새롭게 만들어 주는 것이죠.

이렇게 새로운 데이터 타입을 만들때 쓰는 것이 typedef입니다.

그럼 사용 방법을 알아보죠. 사용은 다음과 같이 합니다.


typedef 기존타입 새로운타입;


예를 들어 unsigned short int와 똑같은 형태의 word라는 데이터

형태를 만들려면


typedef unsigned short int word;


이렇게 해 주면 됨니다.

그러면 프로그램 내에서


unsigned short int a;


이거 대신


word a;


이렇게만 해 주어도 됨니다.

그리고 typedef로는 구조체를 데이터 타입으로 만들어 줄 수가 있습니다.

저번에 구조체를 설명할때 구조체는 데이터 타입을 새롭게 만드는 것이라고

생각하시면 된다고 했는데 그것이 진짜로 새로운 데이터 타입을 만든것은

아닙니다. 하지만 typedef를 쓰면 진짜로 새로운 데이터 타입을 만들수

있게 되는 것이죠.

예를 들어 Student라는 구조체가 있다고 합시다.

그때


typedef struct Student StudentType;


이렇게 해 주면 Student라는 구조체를 StudentType라는 이름의

데이터 타입으로 만들어 준 것입니다.

그러면 프로그램에서


struct Student s;


이렇게 할 것을 단지


StduentType s;


이렇게만 해 주면 됨니다.

공용체 역시 구조체와 마찬가지로 데이터 타입을 만들수 있습니다.

Previous:구조체
Next:선행처리기 지시어
선행처리기 지시어

선행처리기에 대해서는 1회때 배운적이 있고

선행처리기 지시어중 #include에 대해서 2회때 이미 배우셨습니다.

하지만 지시어에는 이것 외에도 몇가지가 더 있습니다.

그럼 그것들에 대해서 알아보죠.


(1) #define

#define라는 지시어는 매크로 기능을 수행하는 지시어 입니다.

기본적인 사용법은 다음과 같습니다.


#define 매크로명 [값]


이렇게 해 두면 프로그램 내에서 매크로명과 같은 이름을 가진 단어들을

모두 값으로 바꿔 줌니다. 그리고 여기서 값은 없어도 되죠.

예를 들어


#define DATA 10


이렇게 해 주면 프로그램 내에 DATA라는 단어를 모두 10으로 바꿔 주게 됨니다.

즉 프로그램에서


if(a==DATA)
{
.
.
}

이렇게 쓴 것은 실재로는


if(a==10)
{
.
.
}

이렇게 되어 버리는 것 입니다.

#define라는 지시어로는 함수와 같은 매크로도 만들수 있습니다.

만드는 방법은 다음과 같죠.


#define 매크로함수명([인수, 인수, ...]) [문장]


이때 인수는 일반 함수의 인수와 똑같은 용도로 쓰입니다.

즉 값을 전달에 주는 역활을 하죠. 물론 없어도 상관 없고요.

그리고 문장은 매크로의 내용으로 인수들을 쓸수 있죠.

그럼 예를 들어 보죠.


#define FUNC(a,b) printf(a,b);


이렇게 매크로를 만들었다고 합시다.

그리고 프로그램 내에서 이 매크로를


FUNC("%d",10);


이렇게 사용했다면 이건 선행처리기가 지시어를 번역한 후에는


printf("%d",10);


이렇게 되어 버리는 것 입니다.

이정도면 이해가 되셨겠죠?


(2) ##와 \

매크로를 만들때만 사용할수 있는 몇가지 지시어가 있는데,

바로 ##와 \입니다.

##는 함수와 같은 매크로에서만 사용하는 것으로

두가지 인수를 연결시켜 버리는 기능을 합니다.

예를 들어


#define MACRO(a,b) a##b


이렇게 매크로를 만들었다고 합시다.

프로그램에서 이 매크로를


a=MACRO(First,Last);


이렇게 사용했다면

실재로는 두 인수 First와 Last가 연결되어


a=FirstLast;


이렇게 되는 것 입니다.

\는 매크로의 내용이 너무 길때 여러줄에 쓰기 위해 필요한 것입니다.

줄 맨 끝에 \를 붙여 주면 그 아랫줄에 계속 연결하여 쓸수 있게 되는 것이죠.

예를 들어


#define MACRO(a,b,c) a=b+c; printf("%d\n",a); a=b-c; printf("%d\n",a)


이런 매크로가 있다고 합시다.

이건 \를 써서 다음과 같이 쓰면 보기도 쉽고 만들기도 쉽죠.


#define MACRO(a,b,c) a=b+c; \
printf("%d\n",a); \
a=b-c; \
printf("%d\n",a)

(3) #undef

#define로 만든 매크로를 없엘때는 어떻게 할까요?

그때 #undef라는 지시어를 사용합니다.

사용법은 다음과 같죠.


#undef 매크로명


여기서 매크로명은 없에려고 하는 매크로의 이름으로

이미 만들어 져 있는 것이어야 합니다.

이렇게 해 주면 해당 매크로는 없어져서

더이상 사용할수 없게 됨니다.
(4) #if-#elif-#else-#endif

#if-#elif-#else-#endif는

선택적 매크로 선언이나 선택적 컴파일 기능을 하는 매크로 입니다.

C언어 프로그램 흐름제어중 if문과 비슷하죠.

사용법은 다음과 같습니다.


#if 조건A
/* A부분 */
.
.
#elif 조건B
/* B부분 */
.
.
.
.
#else
/* else부분 */
.
.
#endif

여기서 조건A가 만족하면 A부분만을 조건B가 만족하면 B부분만을...

그리고 어떤 조건에도 만족하지 않으면 else부분만을

컴파일 해 주게 됨니다.

그리고 #elif부분과 #else부분은 없어도 됨니다.

조건은 일반 C언어의 관계형 연산자와 논리 연산자 그리고 defined라는 것을

사용한 조건 이지만 변수같은건 사용될수 없고

매크로를 사용할수 있습니다.

예를 들어


DATA1 == DATA2


이런 조건은

DATA1이란 매크로와 DATA2라는 매크로의 값은 같다

라는 조건 입니다.

그리고 defined는 특정 매크로가 만들어져 있으면 참을

그렇지 않으면 거짓을 돌려 주는 기능을 하는데,

사용법은 다음과 같습니다.


defined(매크로명)


이때 매크로명에 해당하는 매크로가 만들어져 있으면 참을

그렇지 않으면 거짓을 돌려주죠.

그럼 한가지 예를 들어 보죠.


#define MACRO 10

#if !defined(MACRO)
printf("MACRO not found\n");
#elif MACRO == 0
printf("MACRO = 0\n");
#elif MACRO == 5
printf("MACRO = 5\n");
#elif MACRO == 10
printf("MACRO = 10\n");
#else
printf("MACRO = Another value\n");
#endif

다음과 같은 내용이 있을때

실재로 컴파일 되는 것은 어떤 문장 일까요?

당연히


printf("MACRO = 10\n");


이 문장 이겠죠.

잘 분석해 보시면 왜 그런지는 아시게 되실 겁니다.


(5) #ifdef-#else-#endif

#ifdef-#else-#endif는 #if-#elif-#else-#endif와 비슷하지만

다른 조건들을 줄수 없고 단지 어떤 매크로가 만들어져 있을겨우

특정 부분을 컴파일 시켜 주는 기능을 합니다.

사용은 다음과 같이 합니다.


#ifdef 매크로명
/* A부분 */
.
.
#else
/* B부분 */
.
.
#endif

여기서 매크로명에 해당하는 매크로가 만들어져 있을경우

A부분을 그렇지 않으면 B부분을 컴파일 합니다.

그리고 #else부분은 없어도 됨니다.


(6) #ifndef-#else-#endif

#ifndef-#else-#endif는 위에 #ifdef-#else-#endif와 거의

똑같습니다. 하지만 위에것에선 어떤 매크로가 만들어져 있을때

특정 부분을 컴파일 시키는 것이지만, 이건 특정 매크로가

만즐어 져 있지 않을때 특정 부분을 컴파일 시켜 주는 것 입니다.

사용은


#ifndef 매크로명
/* A부분 */
.
.
#else
/* B부분 */
.
.
#endif

이렇게 합니다. 여기서 매크로명에 해당하는 매크로가 만들어 져 있지

않으면 A부분을 그렇지 않고 만들어져 있으면 B부분을 컴파일 해 주고

#else부분을 없어도 됨니다.


(7) #error

#error라는 지시어는 에러를 발생시켜 컴파일을 중지 시키는 지시어 입니다.

지금은 별로 쓸 일이 없지만 언젠간 꼭 쓸 일이 있을 겁니다.

사용은 다음과 같이 합니다.


#error 에러내용


여기서 쓴 에러내용이 컴파일 할때 표시가 되며, 컴파일이 중지 됨니다.

Previous:공용체, 열거형 상수, typedef 문

댓글 없음: