모두의 코드
씹어먹는 C 언어 - <16 - 2. 모아 모아 구조체(struct) - 구조체 인자로 가진 함수>

이번 강좌에서는

씹어먹는 C 언어

안녕하세요 여러분~ 드디어 구조체의 두번째 강의를 시작하게 되었습니다. 지금 강좌를 쓰다가 느낀 건데 제가 구조체를 배웠을 때 에는 정말로 재미있게 배웠던 것 같습니다. 일단 이전 강좌에서도 말했듯이 -> 기호가 상당히 매력적으로 다가왔는데 그것 이외에도 "struct" 라는 단어를 정말 좋아했던 것 같네요. 여러분은 안그러시나요?

 구조체 포인터 연습하기

일단, 이번 장의 진도를 나가기 위해선 구조체 포인터에 아주 능숙해 져야 하므로 지난번의 내용을 잠깐 복습하도록 하겠습니다.

/* 포인터 갖고 놀기 */
#include <stdio.h>
struct TEST {
  int c;
};
int main() {
  struct TEST t;
  struct TEST *pt = &t;

  /* pt 가 가리키는 구조체 변수의 c 멤버의 값을 0 으로 한다*/
  (*pt).c = 0;

  printf("t.c : %d \n", t.c);

  /* pt 가 가리키는 구조체 변수의 c 멤버의 값을 1 으로 한다*/
  pt->c = 1;

  printf("t.c : %d \n", t.c);

  return 0;
}

성공적으로 컴파일 했다면

만일 지난번의 강좌를 어렴풋이 나마 기억하고 있는 분들이라면 별로 어려운 내용은 아닐 듯 싶습니다.

struct TEST t;
struct TEST *pt = &t;

일단 struct TEST 형의 구조체 변수 tstruct TEST 형을 가리키는 포인터" pt 를 선언하였습니다. 다시 강조하지만, 우리가 int, char 로 생각하는 것 처럼 struct TEST 도 우리가 창조해 낸 하나의 타입이며, 이를 가리키는 포인터 역시 다른 모든 포인터와 같은 크기라는 것입니다. 즉 pt 는절대로 구조체가 아니며, pt 는 단순히 구조체 변수 t 가 저장되어 있는 메모리 공간의 주소값을 보관하고 있을 뿐입니다.

이 때, ptt 의 주소값을 가지고 있으므로 ptt 를 가리키게 됩니다.

(*pt).c = 0;

printf("t.c : %d \n", t.c);

이제, ptt 를 가리키고 있으므로 우리는 pt 를 가지고 t 의 값을 마음대로 조작할 수 있게 되었습니다.

이전에 int *pi = &i 를 한 후, *pi 를 쓰면 i 를 간접적으로 나타낼 수 있었듯이 *pt 를 이용하면 pt 가 가리키고 있는 struct TEST 형의 변수, 즉 t 를 나타낼 수 있게 됩니다.

따라서 (*pt).c 를 하면 t 의 멤버 c 를 의미하게 되죠. 이 때, *pt 를 괄호로 감싸주는 이유는 . 이 우선순위가 * 보다 높기 때문에 그냥 *pt.c 라고 쓰면 "pt 의 c 멤버가 가리키는 것" 을 의미하게 됩니다.

아무튼 (*pt).c = 0; 을 통해 우리는 tc 멤버의 값을 성공적으로 바꿀 수 있었습니다.

하지만 (*pt).c 는 너무 쓰기 불편합니다. 항상 *pt 를 괄호로 닫아 주어야 하는데, 괄호는 Shift 를 누르고 키보드의 9 번과 0 번을 눌러야 하니 정말로 손가락도 아프로 불편할 따름이지요. 그래서 훌륭한 C 언어 제작자들은 새로운 편리한 연산자를 만들었습니다.

pt->c = 1;

printf("t.c : %d \n", t.c);

바로 -> 이죠. -> 연산자의 의미는 "pt 가 가리키는 구조체 변수의 멤버" 를 의미합니다. 따라서 pt->c 는 "pt 가 가리키는 구조체 변수, 즉 t 의 멤버 c" 를 의미하게 됩니다. 따라서 pt->c = 1; 을 통해 우리는 t 의 멤버 c 의 값을 1 로 바꿀 수 있었습니다.

/* 헷갈림 */
#include <stdio.h>
struct TEST {
  int c;
  int *pointer;
};
int main() {
  struct TEST t;
  struct TEST *pt = &t;
  int i = 0;

  /* t 의 멤버 pointer 는 i 를 가리키게 된다*/
  t.pointer = &i;

  /* t 의 멤버 pointer 가 가리키는 변수의 값을 3 으로 만든다*/
  *t.pointer = 3;

  printf("i : %d \n", i);

  /*

  -> 가 * 보다 우선순위가 높으므로 먼저 해석하게 된다.
  즉,
  (pt 가 가리키는 구조체 변수의 pointer 멤버) 가 가리키는 변수의 값을 4 로
  바꾼다. 라는 뜻이다/

  */
  *pt->pointer = 4;

  printf("i : %d \n", i);
  return 0;
}

성공적으로 컴파일 했다면

아마 위 예제만 제대로 이해하신 다면 더이상 구조체 포인터 가지고 혼동하는 일은 없을 듯 합니다.먼저, TEST 구조체의 멤버들 부터 살펴봅시다.

struct TEST {
  int c;
  int *pointer;
};

흠. 쟁쟁한 녀석이 나왔군요. 포인터가 있습니다. 하지만 괜찮습니다. 우리는 포인터를 잘 다루거든요.

struct TEST t;
struct TEST *pt = &t;
int i = 0;

마찬가지로 ptt 를 가리키게 됩니다.

t.pointer = &i;

일단, 위 문장에서 tpointer 라는 멤버에는 i 의 주소값이 들어갑니다. 따라서 pointeri 를 가리키게 됩니다. 그렇다면 pointer 를 가지고 i 의 값을 바꾸며 놀 수 있겠죠? 바로 다음 문장을 봅시다.

*t.pointer = 3;

흠. 우선 순위를 고려하면 .* 보다 높으므로 t.pointer 가 먼저 해석되고 그 다음에 *(t.pointer) 형태로 해석되게 됩니다. 따라서, *t.pointer 를 통해 구조체 변수 tpointer 멤버가 가리키는 변수를 지칭할 수 있게 됩니다.

*pt->pointer = 4;

. 과 마찬가지로 ->* 보다 우선순위가 높습니다. 즉, *(pt->pointer)*pt->pointer 는 동일한 의미라는 것입니다. 아무튼, pt->pointer 를 통해 "pt 가 가리키는 구조체 변수의 pointer 멤버", 즉 t.pointer 을 의미할 수 *(pt->pointer) = 4 를 통해 pointer 가 가리키는 변수의 값을 4 로 바꿀 수 있게 됩니다.

/*

구조체 포인터 연습

 */
#include <stdio.h>
int add_one(int *a);
struct TEST {
  int c;
};
int main() {
  struct TEST t;
  struct TEST *pt = &t;

  /* pt 가 가리키는 구조체 변수의 c 멤버의 값을 0 으로 한다*/
  pt->c = 0;

  /*
  add_one 함수의 인자에 t 구조체 변수의 멤버 c 의 주소값을
  전달하고 있다.
  */
  add_one(&t.c);

  printf("t.c : %d \n", t.c);

  /*
  add_one 함수의 인자에 pt 가 가리키는 구조체 변수의 멤버 c
  의 주소값을 전달하고 있다.

  */
  add_one(&pt->c);

  printf("t.c : %d \n", t.c);

  return 0;
}
int add_one(int *a) {
  *a += 1;
  return 0;
}

성공적으로 컴파일 했다면

이제, 마지막으로 구조체 포인터 연습을 해볼까 합니다.

struct TEST t;
struct TEST *pt = &t;

이전과 마찬가지로 ptt 를 가리키고 있습니다.

add_one(&t.c);

그리고 add_one 함수에 t 의 멤버 c 의 주소값을 전달하였습니다. 역시 & 보다 . 이 우선순위가 높으므로 위 식은 &(t.c) 와 동일합니다. 아무튼, add_one 함수에 의해 c 의 값이 1 증가 합니다.

add_one(&pt->c);

마찬가지로 ->& 보다 우선순위가 높습니다. 따라서, pt 가 가리키는 구조체의 멤버 c 의 값이 1 증가하게 됩니다.

이해가 잘 되시죠?

구조체의 대입

구조체의 복사라 하면 무언가 거창할 것 같지만 사실은 상당히 단순한 내용입니다.

바로, 구조체도 보통의 변수들과 같이 = 를 사용할 수 있다는 것이지요. (= 가 대입 연산자 라는 사실은 기억하시죠??)

#include <stdio.h>
struct TEST {
  int i;
  char c;
};
int main() {
  struct TEST st, st2;

  st.i = 1;
  st.c = 'c';

  st2 = st;

  printf("st2.i : %d \n", st2.i);
  printf("st2.c : %c \n", st2.c);

  return 0;
}

  성공적으로 컴파일 했다면

여러분은 아마도 위 소스 코드를 한눈에 이해하셨을 수 있을 것입니다.

struct TEST {
  int i;
  char c;
};

멤버가 icstruct TEST 를 정의하였고, 이 구조체의 변수인

struct TEST st, st2;

stst2 를 정의하였습니다. 그리고 st 의 각 멤버에

st.i = 1;
st.c = 'c';

를 넣였죠.

st2 = st;

그리고 우리는 위와 같이 stst2 에 대입하였습니다. 우리가 변수 ij 에 대입하면 i 의 값이 j 에 그대로 복사되듯이, st2 의 멤버 i 의 값은 st 의 멤버 i 의 값과 같아지고, st2 의 멤버 c 의 값은 st 의 멤버 c 의 값과 동일해졌습니다. 이는 상당히 합리적이고 대입 연산자의 역할을 잘 해내는 것 같네요.

#include <stdio.h>
char copy_str(char *dest, char *src);
struct TEST {
  int i;
  char str[20];
};
int main() {
  struct TEST a, b;

  b.i = 3;
  copy_str(b.str, "hello, world");

  a = b;

  printf("a.str : %s \n", a.str);
  printf("a.i : %d \n", a.i);

  return 0;
}
char copy_str(char *dest, char *src) {
  while (*src) {
    *dest = *src;
    src++;
    dest++;
  }

  *dest = '\0';

  return 1;
}

성공적으로 컴파일 했다면

위 코드 역시 구조체의 대입이 무엇인지 잘 이해만 했다면 별 무리 없이 이해하실 수 있으리라 생각 합니다.

struct TEST {
  int i;
  char str[20];
};

위와 같이 struct TEST 를 정의하였습니다. 이번에는 int ichar str[20] 을 멤버로 가지고 있습니다.

struct TEST a, b;

b.i = 3;
copy_str(b.str, "hello, world");

이제 구조체를 정의한 뒤, 위와 같이 각각의 멤버를 초기화 합니다. copy_str 함수는 15 - 3 강에서 만들어본 함수죠?

a = b;

그리고 우리는 위와 같이 b 구조체를 a 구조체에 대입하였습니다. 따라서, b 구조체의 모든 멤버의 데이터가 a 구조체에 일대일로 대응이 되어 값이 복사됩니다. 즉, ii 끼리, strstr 의 각 원소 끼리 쭈르륵 복사가 됩니다. 결과적으로 각각의 멤버의 값을 출력해 보면 동일하게 나옵니다.

구조체를 인자로 전달하기

/*구조체를 인자로 전달하기 */#include <stdio.h>
struct TEST {
  int age;
  int gender;
};
int set_human(struct TEST a, int age, int gender);
int main() {
  struct TEST human;
  set_human(human, 10, 1);
  printf("AGE : %d // Gender : %d ", human.age, human.gender);
  return 0;
}
int set_human(struct TEST a, int age, int gender) {
  a.age = age;
  a.gender = gender;
  return 0;
}

  성공적으로 컴파일 하였다면 다음과 같은 오류를 만날 수 있으셨을 것입니다.

허걱. 오래간만에 만나는 오류이군요. 오류의 내용을 보자 하니, human 이라는 구조체 변수가 값이 초기화되지 않은 채 사용되었다고 하네요. 일단 소스 부터 살펴보도록 합시다.

struct TEST {
  int age;
  int gender;
};

일단 우리는 위와 같이 TEST 구조체를 정의하였습니다. 그리고

int set_human(struct TEST a, int age, int gender) {
  a.age = age;
  a.gender = gender;

  return 0;
}

set_human 이라는 함수를 만들어서 TEST 구조체 변수들을 초기화 하도록 했습니다. 따라서,

set_human(human, 10, 1);

와 같이 한다면 humanagegender 멤버들이 초기화 될 것 처럼보이지요. 하지만 그렇지 않습니다. 왜냐구요? 아마 여태까지 강좌를 잘 따라오셨더라면 한 번에 짐작하실 수 있으실 텐데 말이죠.

바로 제가 13 - 2 강에서 말한 규칙, "특정한 변수의 값을 다른 함수를 통해 바꾸려면 변수의 주소값을 전달해야 한다" 라는 룰을 지키지 않았기 때문입니다. 다시 말해 위 경우에서 a.age = age; 를 했을 때 age 의 값이 바뀌는 것은 실제 main 함수에서의 human 이 아니라 set_human 함수의 a 라는 human 과 별개의 구조체변수의 age 멤버의 값이 바뀌게 되는 것이지요.

따라서 실제 human 구조체변수의 멤버들은 전혀 초기화 되지 않은 채 출력이 실행되어 오류가 발생했습니다.

이를 해결하기 위해서는 역시 human 구조체 변수의 주소값을 인자로 받는 함수를 만들어야 할 것입니다.

/* 인자로 제대로 전달하기 */
#include <stdio.h>
struct TEST {
  int age;
  int gender;
};
int set_human(struct TEST *a, int age, int gender);
int main() {
  struct TEST human;

  set_human(&human, 10, 1);

  printf("AGE : %d // Gender : %d ", human.age, human.gender);
  return 0;
}
int set_human(struct TEST *a, int age, int gender) {
  a->age = age;
  a->gender = gender;

  return 0;
}

성공적으로 컴파일 했다면

위와 같이 human 구조체 변수의 멤버의 값들이 제대로 변경되었음을 알 수 있습니다.

int set_human(struct TEST *a, int age, int gender) {
  a->age = age;
  a->gender = gender;

  return 0;
}

set_human 함수는 이전 예제에서의 set_human 함수와는 다르게 구조체의 포인터를 인자로 취하고 있습니다. 그렇기 때문에 set_human 함수를 호출할 때 에서도

set_human(&human, 10, 1);

위와 같이 human 의 주소값을 인자로 전달하고 있었죠. 따라서, ahuman 을 가리키게 됩니다. (역시 주의할 점은 a 는 절대로 구조체 변수가 아니라는 것이죠. 단순히 human 구조체 변수가 메모리 상에 위치한 곳의 시작 지점의 주소값을 보관하고 있을 뿐입니다) 아무튼 위와 같이 전달한다면 이제 a-> 를 통해 a 가 가리키고 있는 구조체 변수의 멤버, 즉 위의 경우에서는 human 의 멤버를 지칭할 수 있게 됩니다. 따라서 a->age = age; 를 하게 되면 humanage 멤버의 값이 바뀌게 되는 것입니다.

물론 주의할 점은 a->ageage 는 다르다는 것이죠. a->agehuman 구조체 변수의 int 형 멤버 age 를 지칭하는 것이고, age 는 단순히 set_human 함수에서 인자로 받아들여진 int 형의 age 라는 변수를 가리키는 말입니다. 이 둘은 다른 것이고 실제로 컴퓨터 내부에서도 다르게 처리됩니다.

아무튼 위와 같이 제대로 값이 바뀌어서 출력됨을 알 수 있습니다.

/* 살짝 업그레이드*/
#include <stdio.h>
struct TEST {
  int age;
  int gender;
  char name[20];
};
int set_human(struct TEST *a, int age, int gender, char *name);
char copy_str(char *dest, char *src);

int main() {
  struct TEST human;

  set_human(&human, 10, 1, "Lee");

  printf("AGE : %d // Gender : %d // Name : %s \n", human.age, human.gender,
         human.name);
  return 0;
}
int set_human(struct TEST *a, int age, int gender, char *name) {
  a->age = age;
  a->gender = gender;
  copy_str(a->name, name);

  return 0;
}
char copy_str(char *dest, char *src) {
  while (*src) {
    *dest = *src;
    src++;
    dest++;
  }

  *dest = '\0';

  return 1;
}

성공적으로 컴파일 했다면

기본적으로 이전의 예제와는 동일하지만 멤버를 하나 더 추가했습니다.

struct TEST {
  int age;
  int gender;
  char name[20];
};

위와 같이 name[20] 이라는 멤버를 새로 추가해주었습니다.

int set_human(struct TEST *a, int age, int gender, char *name);

그리고 set_human 함수에서 name 멤버 역시 같이 초기화해주기 위해 인자로 char * 형의 name 이라는 인자를 추가로 받게 됩니다.

set_human(&human, 10, 1, "Lee");

이제 TEST 구조체 변수인 human 을 초기화 하기 위해서 set_human 함수를 호출하였습니다.

int set_human(struct TEST *a, int age, int gender, char *name) {
  a->age = age;
  a->gender = gender;
  copy_str(a->name, name);

  return 0;
}

위 함수는 a 가 가리키는 구조체 변수의 각 멤버들을 초기화 하게 됩니다. 이 때, main 함수의 human 구초제 변수의 name 멤버를 초기화 하기 위해서는 copy_str 함수를 이용해야 합니다. 이를 위해서는 name 배열의 주소값과, 복사해 넣으려는 문자열의 주소값을 넣어야 하는데 a->name 을 통해 human 구조체 변수의 name 멤버의 주소값과, name (이는 두 번째 인자로 a->name 과 전혀 다른 것이다) 을 통해 복사해 넣으려는 문자열의 주소값을 copy_str 에 전달할 수 있게 됩니다. 아무튼, 위를 통해 성공적으로 human 의 각각의 멤버들을 초기화 할 수 있게 되었죠.

자, 그럼 이번 강좌는 여기서 마치도록 하겠습니다. 아무래도 이번 강좌를 통해 구조체에 대한 확실한 자신감이 생겼으면 하네요. 다음 강좌에서는 구조체에 대해서 조금 더 살펴보고 실습을 해보던지, 아니면 새로운 C 의 기능들에 대해 탐구해 보도록 하죠. (아마 여기까지 도달하신 여러분들은 C 의 모든 고비를 넘겼다고 하셔도 무방합니다. 이제 모든 것이 술술 풀리게 될 것입니다)

생각해보기

문제 1

(이전에 만든)도서 관리 프로그램을만들되,구조체를 이용해 봅시다. 또한 register_book 과 같은 함수를 이용하여 책을 등록해봅시다. (난이도 : 下)

문제 2

큰 수를 다루는 구조체를 생각해봅시다. 그 구조체의 이름은 BigNum 입니다. BigNum 구조체에는 다음과 같은 멤버들이 있을 수 있습니다.

struct BigNum {
  int i_digit[100];   // 정수 부분
  int d_digit[100];   // 소수 부분
  int i_total_digit;  // 전체 사용되고 있는 정수부분 자리수
  int d_total_digit;  // 전체 사용되고 있는 소수부분 자리수
  char sign;  // 부호, 0 이면 양수, 1 이면 음수. 0 은 양수로 간주한다.
};

참고로 BigNum 구조체를 다룰 때 중요한 점은 수의 크기가 위 배열에 들어가지 않을 정도로 클 때를 적절히 처리해 주어야 한다는 점에 있습니다.

강좌를 보다가 조금이라도 궁금한 것이나 이상한 점이 있다면꼭 댓글을 남겨주시기 바랍니다. 그 외에도 강좌에 관련된 것이라면 어떠한 것도 질문해 주셔도 상관 없습니다. 생각해 볼 문제도 정 모르겠다면 댓글을 달아주세요.

현재 여러분이 보신 강좌는<<씹어먹는 C 언어 - <16 - 2. 모아 모아 구조체(struct) - 구조체 인자로 가진 함수>>입니다. 이번 강좌의모든 예제들의 코드를 보지 않고 짤 수준까지 강좌를 읽어 보시기 전까지 다음 강좌로 넘어가지 말아주세요


 다음 강좌 보러가기
프로필 사진 없음
댓글에 글쓴이에게 큰 힘이 됩니다