C 프로그래밍 입문/구조체와 유니온 데이터: 두 판 사이의 차이

내용 삭제됨 내용 추가됨
Joshuajh (토론 | 기여)
 
Joshuajh (토론 | 기여)
20번째 줄:
</source></div>
-->
 
<!-- 다른 편집자분들께:
포인터를 먼저 설명하지 않은 관계로 포인터를 이용한 구조체 멤버 액세스 방법에 대해서는 포인터에서 설명할 것 입니다! -->
 
C 스펙에 있는 구조체의 정의는 '동일하지 않은 데이터 타입을 갖는 일련의 데이터의 집합'이다. 그에 비해 배열의 정의는 '동일한 데이터 타입을 갖는 일련의 데이터의 집합' 이다. 두 정의에 들어있는 '일련의(sequential)'이란 단어의 의미는 '연달아 놓여있는'의 의미 이다. 위에서 배열의 정의에 관해 정확하게 이해 했다면 '연달아 놓여있는 데이터의 집합'이라는 말의 의미를 정확하게 이해할 수 있을 것이다.구조체는 배열과 마찬 가지로 데이터들이 연달아 놓여 있는데 배열과의 차이점 이라면 구성 요소가 되는 데이터의 타입이 다르다는 말이다. 여기에서 '변수'의 정의를 다시 한 번 기억해 보도록 하자. 변수는 '메모리 공간'이었고, 배열은 같은 종류의 변수가 여러개가 메모리상에 연달아 존재 한다. 배열의 특성상 같은 종류의 메모리가 여러개 연달아 놓여있기 때문에 배열 내 n 번째 메모리 공간을 사용하고 싶으면 '(n - 1) * 배열내 요소 하나의 크기'라는 간단한 계산식으로 사용하고자 하는 메모리 공간의 주소를 얻을 수 있기 때문에 배열에서는 인덱스(index)라는 개념을 이용해서 배열 멤버를 다룰 수 있었다. 그러나 구조체의 경우에는 '동일하지 않은 데이터 타입'이라는 전제 조건이 있기 때문에 구조체 내의 멤버를 다루기 위해 멤버가 위치하는 메모리내 위치를 계산하기 위해서는 위에서 배열내 멤버의 위치를 계산하는 방법을 사용할 수 없다. 그렇기 때문에 구조체는 인덱스가 아닌 멤버의 이름을 일일히 지정하고 그 이름을 사용하는 방법을 사용한다.
 
줄 31 ⟶ 35:
};
 
structurestruct score_card_type {
int year, grade, seq;
struct articles_type scores;
줄 48 ⟶ 52:
 
int main (int argc, char *argv[]) {
structurestruct score_card_type a_student;
 
/* 여기에 전체 데이터 베이스에서 한 학생의 성적을 추출해 내는 프로그램 코드가 있다고 가정 합니다.
줄 59 ⟶ 63:
</source></div>
 
구조체를 사용하기 위해서는 구조체의 형태를 먼저 '''정의정의하고'''하고, 정의된 형태의 구조체 타입의 '''변수'''를변수를 만들어서 사용한다사용'''한다. 좀 다른 표현으로 설명하자면, 여러개의 데이터가 들어가는 새로운 변수의 타입을 하나 만든 다음에 그 타입의 변수를 만들어서 사용한다.<ref>실제로 정수형, 실수형등의 데이터 타입을 기본형(base type)이라고 부르고 배열, 구조체, 유니온 등을 딜리버드 타입(delivered type;유도형 이라는 표현을 사용하는 경우도 있습니다.)혹은 사용자 정의 타입(user define type)이라고 부릅니다.</ref> 배열의 경우 동일한 타입의 변수가 반복되는 것 이기 때문에 다로 그 내용에 대한 타입을 별도로 정의해 줄 필요가 없지만, 구조체의 경우에는 포함되는 데이터의 타입이 다르다는 전제가 있으므로 먼저 어떤 내용이 들어갈지를 정의해 주는 작업을 거쳐야 한다. 위의 코드를 보면 2번 라인에서 11번 라인까지의 프로그램 코드에 구조체의 구조를 정의 했다(혹은 struct articles_type 타입과 struct score_card_type 타입을 만들었다). 그리고 23번 라인에서 struct score_card_type 타입의 변수를 하나 만든 것 이다.
 
위 코드에서 주의할 점이 두 가지가 있는데, 첫째, 구조체의 끝에는 '''반드시''' 세미콜론(;)을 찍어 주어야 한다. 아직 설명되지 않았지만 구조체의 닫힘 중괄호(}) 다음에는 생략 가능한 내용이 있으며(실제로 위의 프로그램에서는 두 구조체 모두 생략했다), 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) =====