C 프로그래밍 입문/구조체와 유니온 데이터: 두 판 사이의 차이
내용 삭제됨 내용 추가됨
추가 →구조체와 유니온 |
|||
20번째 줄:
</source></div>
-->
<!-- 다른 편집자분들께:
포인터를 먼저 설명하지 않은 관계로 포인터를 이용한 구조체 멤버 액세스 방법에 대해서는 포인터에서 설명할 것 입니다! -->
C 스펙에 있는 구조체의 정의는 '동일하지 않은 데이터 타입을 갖는 일련의 데이터의 집합'이다. 그에 비해 배열의 정의는 '동일한 데이터 타입을 갖는 일련의 데이터의 집합' 이다. 두 정의에 들어있는 '일련의(sequential)'이란 단어의 의미는 '연달아 놓여있는'의 의미 이다. 위에서 배열의 정의에 관해 정확하게 이해 했다면 '연달아 놓여있는 데이터의 집합'이라는 말의 의미를 정확하게 이해할 수 있을 것이다.구조체는 배열과 마찬 가지로 데이터들이 연달아 놓여 있는데 배열과의 차이점 이라면 구성 요소가 되는 데이터의 타입이 다르다는 말이다. 여기에서 '변수'의 정의를 다시 한 번 기억해 보도록 하자. 변수는 '메모리 공간'이었고, 배열은 같은 종류의 변수가 여러개가 메모리상에 연달아 존재 한다. 배열의 특성상 같은 종류의 메모리가 여러개 연달아 놓여있기 때문에 배열 내 n 번째 메모리 공간을 사용하고 싶으면 '(n - 1) * 배열내 요소 하나의 크기'라는 간단한 계산식으로 사용하고자 하는 메모리 공간의 주소를 얻을 수 있기 때문에 배열에서는 인덱스(index)라는 개념을 이용해서 배열 멤버를 다룰 수 있었다. 그러나 구조체의 경우에는 '동일하지 않은 데이터 타입'이라는 전제 조건이 있기 때문에 구조체 내의 멤버를 다루기 위해 멤버가 위치하는 메모리내 위치를 계산하기 위해서는 위에서 배열내 멤버의 위치를 계산하는 방법을 사용할 수 없다. 그렇기 때문에 구조체는 인덱스가 아닌 멤버의 이름을 일일히 지정하고 그 이름을 사용하는 방법을 사용한다.
줄 31 ⟶ 35:
};
int year, grade, seq;
struct articles_type scores;
줄 48 ⟶ 52:
int main (int argc, char *argv[]) {
/* 여기에 전체 데이터 베이스에서 한 학생의 성적을 추출해 내는 프로그램 코드가 있다고 가정 합니다.
줄 59 ⟶ 63:
</source></div>
구조체를 사용하기 위해서는 구조체의 형태를 먼저 '''
위 코드에서 주의할 점이 두 가지가 있는데, 첫째, 구조체의 끝에는 '''반드시''' 세미콜론(;)을 찍어 주어야 한다. 아직 설명되지 않았지만 구조체의 닫힘 중괄호(}) 다음에는 생략 가능한 내용이 있으며(실제로 위의 프로그램에서는 두 구조체 모두 생략했다), C 컴파일러는 그 생략가능한 내용이 생략된 것 인지 아닌지 확인할 방법이 없기 때문에 중괄호 다음에 더이상 내용이 없다면 반드시 세미콜론을 찍어서 문장이 더 이상 없다는 것을 표시해 주어야 하는 것 이다. 둘째, articles_type과 score_card_type은 변수 이름이 아니고 타입 이름의 일부 일 뿐이다. 많은 초보 C 프로그래머들이 쉽게 실수하는 내용이므로, 아무리 당연하다고 생각 되어도 헷갈리지 않도록 주의할 필요가 있다.
줄 71 ⟶ 75:
위와 같이 선언된 구조체 배열은 일반 배열과 동일하게 액세스 할 수 있으며, 배열내 n번째 구조체 변수의 멤버 변수를 액세스 하는 방법에 대해서는 해당 구조체 변수 배열이 메모리에 저장되어 있는 방식을 잠시 생각해 보면 쉽게 유추해 볼 수 있을 것이다. 전체 구조를 컨테이너라 가정한다면 가장 외부의 컨테이너는 배열이 된다. 그러므로 배열 컨테이너에 있는 구조체 변수 컨테이너 하나를 꺼낸 다음, 다시 구조체 안에 있는 내용물인 변수를 끄집어 내 주면 되는 것 이다.
구조체의 기본형은 다음과 같다:
struct struct_type_name {
type variable;
type variable;
...
} variable_name, variable_name, ..., variable_name;
위의 구조에서 struct_type_name이나 variable_name 목록중 하나는 생략이 가능하다. 그렇기 때문에 다음과 같은 형태로 선언하여 사용하는 것이 가능하다.
<div style='border: dashed 1px #0000ff; background: #f0f0ff; padding: 7px; margin: 10px 0px 10px 0px;'>
<source lang="c" line="GESHI_NORMAL_LINE_NUMBERS">
#include <stdio.h>
struct articles_type {
int korean, english, math;
};
struct score_card_type {
int year, grade, seq;
struct articles_type scores;
int total;
double average;
} a_student;
int print_a_scorecard (struct score_card_type sc) {
printf ("%2d 학년 %2d 반 %3d 번\n", sc.year, sc.grade, sc.seq);
printf ("=========");
printf ("국어: %4d 영어: %4d 수학: %4d\n", sc.scores.korean, sc.scores.english, sc.scores.math);
printf ("총점: %5d 평균: %5.1f\n", sc.total, sc.average);
return 0;
}
int main (int argc, char *argv[]) {
/* 여기에 전체 데이터 베이스에서 한 학생의 성적을 추출해 내는 프로그램 코드가 있다고 가정 합니다.
나중에 포인터를 배운 후에 이해될 수 있는 부분이기 때문에 이 내용은 잠시 비워둡니다. */
print_a_scorecard (a_student);
return 0;
}
</source></div>
앞서 사용했던 코드에서 11번 라인이 수정되고 23번 라인이 제거 되었다. 위와 같이 타입을 선언함과 동시에 변수를 할당하는 것 역시 가능하다.
<div style='border: dashed 1px #0000ff; background: #f0f0ff; padding: 7px; margin: 10px 0px 10px 0px;'>
<source lang="c" line="GESHI_NORMAL_LINE_NUMBERS">
#include <stdio.h>
struct score_card_type {
int year, grade, seq;
struct articles_type {
int korean, english, math;
} scores;
int total;
double average;
} a_student;
int print_a_scorecard (struct score_card_type sc) {
printf ("%2d 학년 %2d 반 %3d 번\n", sc.year, sc.grade, sc.seq);
printf ("=========");
printf ("국어: %4d 영어: %4d 수학: %4d\n", sc.scores.korean, sc.scores.english, sc.scores.math);
printf ("총점: %5d 평균: %5.1f\n", sc.total, sc.average);
return 0;
}
int main (int argc, char *argv[]) {
/* 여기에 전체 데이터 베이스에서 한 학생의 성적을 추출해 내는 프로그램 코드가 있다고 가정 합니다.
나중에 포인터를 배운 후에 이해될 수 있는 부분이기 때문에 이 내용은 잠시 비워둡니다. */
print_a_scorecard (a_student);
return 0;
}
</source></div>
첫번째, 두번째 코드에서 struct articles_type은 struct score_card_type에서 단 한번 사용된다. 그런경우 위와 같이 타입 이름은 지정하지 않은 상태로 바로 해당 구조체를 사용하는 변수를 선언하여 사용하는 것도 가능하다. 위의 프로그램을 보면 앞서 프로그램의 2, 3, 4번 라인이 제거되고 structure score_card_type 구조체로 그 내용이 옮겨진 것을 볼 수 가 있을 것이다. 동일 형태의 구조체 타입이 다시 사용되지 않는다면 이와 같이 다른 구조체 내부에 구조체를 정의하고 구조체 타입의 이름을 생략한 상태로 바로 변수 이름을 지정하여 사용할 수 도 있다.
====== 구조체의 초기화 ======
구조체는 배열과 많은 다른 점이 있지만 동시에 유사한 점도 많다. 그중의 하나가 '일련의 데이터 모음'이라는 특성인데, 이 특성 덕분에 배열과 동일한 방법으로 구조체를 선언과 동시에 값을 초기화 할 수 있다. 앞서의 코드에서 사용되었던 구조체는 다음과 같이 초기화 될 수 있다.
struct score_card_type sdata = {
1, 1, 12,
{10, 10, 10},
30,
10.0
};
마찬가지로 다음과 같이 하여 일부분만 값을 할당하거나 모두 0으로 초기화 할 수 있다:
struct score_card_type sdata = { 1, 1, 12 };
struct score_card_type zdata = { 0 };
물론 일부분만 값을 할당한 경우에는 나머지는 모두 0으로 초기화 된다.
배열과 구조체의 차이점 중의 하나는 배열은 배열끼리 값의 할당이 안되지만 구조체는 구조체 끼리 값이 할당될 수 있다는 점이다. 다음의 코드를 보자:
char arr1[20] = "Hello World!";
char arr2[20];
arr2 = arr1; /* 여기에서 에러가 발생합니다. */
위의 코드는 컴파일 시에 에러가 발생한다. 에러 메시지는 컴파일러 마다 조금씩 다르겠지만 대충 '값을 옮겨 넣을 수 없다'는 의미 일 것이다. 그러나 다음의 코드는 문제없이 동작한다.
struct str_type {
char message[20];
};
struct str_type msg1 = { "Hello World!" };
struct str_type msg2;
msg2 = msg1;
printf ("%s\n", msg2.message);
위의 프로그램은 에러없이 정상적으로 두 문자열이 복사가 된다. 이전에 문자열 배열을 다룰때 문자열 변수에는 나중에 값을 대입할 수 없기 때문에 strcpy()나 memcpy() 함수를 이용해서 복사해야 한다는 내용을 예를 들어가며 설명을 했었는데, 이 경우 실제 복사되는 데이터는 문자열 배열임에도 불구하고 깔끔하게 변수의 내용이 복사 되었다. 이런 차이를 있게한 위의 두 코드의 차이점이 무엇일까? 실제로 배열은 그 길이가 결정되어 있는 타입이 아니다. 그렇기 때문에 두 배열내 멤버의 타입이 동일 하다는 보장이 있다고 하더라도 두 배열의 길이가 동일 할 것이라는 보장이 없는데다가 배열의 길이를 실행시에 확인할 방법이 없다는 문제가 있다. 그에 반해 구조체는 먼저 타입을 '정의'하고 나서 해당 타입의 변수를 선언해서 사용하는 것 이기 때문에 두 구조체 변수의 크기가 항상 동일할 것 이라는 보장이 있다. 그렇기 때문에 구조체형 변수는 변수값들 간에 대입이 가능한 것이다.
<div style='border: dashed 1px #ff0000; background: #fff0f0; padding: 7px; margin: 10px 0px 10px 0px;'>'''참고''':
* 구조체간에 값의 대입이 가능하다고 해도 아래와 같이 '상수'를 대입하는 일은 할 수 없으며:
: msg2 = { "Hello Another World!" };
* C컴파일러가 구조체의 값을 일일히 복사하는 작업을 별도로 수행하기 때문에 컴파일된 프로그램의 코드가 커진다.
: 그렇기 때문에 구조체를 복사하는 경우에도 대입을 하는 방법 보다는 memcpy() 함수를 이용하여 복사하는 편이 프로그램 코드의 크기가 작을 수 있다.
: 반대로 일일히 메모리를 복사하는 코드를 컴파일러가 작성해 주기 때문에 memcpy() 함수를 이용하여 복사하는 것 보다 대입을 하여 구조체의 값을 복사하는 편이 속도면 에서는 더 빠를 수 있다.
</div>
===== 유니온 (Union) =====
|