Team Curio

비주얼 노벨 한국어 패치 팀 큐리오

프로그래밍

13.c 언어강좌 (10)

연이v 2009. 7. 10. 20:23
반응형
SMALL

=============================================================================
C 프로그래밍 10회
=============================================================================

이번 강좌 에서는 포인터와 배열의 관계와 함수 포인터, 그리고 구조체에 대해서도 알아보겠습니다.

1. 배열이 이상하다?
배열을 사용하다가 보면 이상한 점을 많이 발견하셨을 겁니다.
예를 들어 scanf함수로 정수 변수에 어떤 정수를 입력 받을땐
int Var; scanf("%d",&Var);
이렇게 했죠? 그런데 문자배열에 문자열을 입력 받을땐
char Var[100]; scanf("%s",Var);
이렇게 했습니다.
왜 문자배열에 문자열을 입력 받을땐
scanf("%s",&Var);
이렇게 &를 붙이지 않았을까요? 그리고 함수에 값을 전달할때 배열을 전달하면 왜
Passing by reference로 전달 되었을까요? 정말 궁금하시지 않습니까?
이유는 배열명이 포인터이기 때문이죠.
그러니까 배열명은 배열의 첫번째 원소의 주소를 가지고 있는 포인터 입니다.
단 그 주소를 바꿀수 없으므로 포인터 중에서 상수 포인터라고 하면 되겠죠?

2. 또다른 방법의 배열 사용.
우선 예제를 보죠.

/* 파일 이름 : 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] . . 이렇게 되는 것이죠.

3. 포인터로 문자열 처리를?
문자열을 배울때 포인터로도 문자열을 처리할수 있다고 배웠습니다.
그런데 그 방법은 아직 배우지 않았죠?
그걸 지금 설명드리도록 하겠습니다.
사실 포인터로는 문자열을 처리한다기 보다 그냥 지시한다고 표현하는 것이 좋을것 같군요.
하여튼 잘 읽어 보세요.
배열은 포인터 상수입니다.
그래서 문자배열에 문자열을 넣을때는 반드시 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");
이렇게 할 경우 잘못하다간 다운이 되버리죠.
그래서 포인터로는 문자열 처리를 하지 못하고 대부분 문자열을 지시할때 쓰이는 것 입니다.

4. 함수 포인터
지금까지 배운 포인터는 어떤 변수의 주소를 기억시키는 포인터였습니다.
그런데 함수의 주소를 기억시키는 포인터는 없을까요? 당연히 있죠.
그런데 지금까지 배운 포인터와는 선언부터 사용까지 방법이 조금 다릅니다.
지금부터 이걸 알아볼껀데 이걸 배우기 전에 우선 한가지 알아두실 것이 있습니다.
아까 배열명이 상수 포인터라고 했는데, 함수명도 바로 상수 포인터 입니다.
즉 함수의 시작 주소를 기억하고 있는 상수 포인터 이죠.

(1) 함수 포인터의 선언
함수 포인터 선언은 리턴형태
(*함수포인터명)([인수, 인수, ...]);
이렇게 합니다.
예를 들어 정수를 리턴하고 인수는 없는 함수의 주소를 넣는 FuncPointer라는 함수 포인터는
int (*FuncPointer)();
이렇게 선언합니다.
이렇게 선언한 FuncPointer라는 함수 포인터에는 정수를 리턴하고
인수가 없는 함수의 주소만 넣을수 있게 되죠.

(2) 함수 포인터의 사용
함수 포인터에 함수의 주소를 넣는 방법은 다음과 같습니다.
함수포인터 = 함수;
예를 들어 아까 나왔던 FuncPointer라는 함수 포인터에
int Function() { . . . }
위와 같은 Function이라는 함수의 주소를 기억시키려면
FuncPointer=Function;
이렇게 하면 됨니다.
그런데 여기서 Function의 주소를 FuncPointer에 넣으려고 하는데 왜?
FuncPointer=&Function;
이렇게 주소 연산자를 사용하지 않았을까요?
아까 함수명은 상수 포인터라고 했죠? 그러므로 Function은 포인터 이기 때문에
&를 붙이지 않은 것 입니다.
포인터 안에는 주소가 들어 있으니까요.
함수 포인터에는 함수의 주소가 들어있는데 그 함수를 호출할때는 어떻게 할까요?
아까 함수명은 함수의 시작 주소가 들어있는 포인터라고 했습니다.
그런데 함수 포인터 역시 함수의 시작 주소를 기억시키고 있는 포인터 이죠.
그러므로 그냥 함수 호출 하는 것과 똑같은 방법으로 호출하면 됨니다.
예를 들어 위에 나왔던 FuncPointer라는 함수 포인터의 함수를 호출하려면
FuncPointer();
이렇게 해 주면 되는 것이죠.
이것으로 함수 포인터에 대한 내용은 마치도록 하겠습니다.

5. 구조체
구조체가 무엇일까요?
여러분이 만약 학교에서 학생 정보 관리 프로그램을 만든다고 합시다.
그리고 학생 한사람당 다음과 같은 데이터가 있다고 하고, 학생수는 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;
이렇게 해 주면 더 간단해 지게 되죠.
이것으로 이반 강좌는 마치도록 하겠습니다.
다음 강좌에서는 공용체와, 열거형 상수등을 배우도록 하겠습니다.
반응형

'프로그래밍' 카테고리의 다른 글

15.c 언어강좌 (12)  (0) 2009.07.10
14.c 언어강좌 (11)  (0) 2009.07.10
12.c 언어강좌 (9)  (0) 2009.07.10
11.c 언어강좌 (8)  (0) 2009.07.09
10.c 언어강좌 (7)  (0) 2009.07.09