'구조체(structure)'라는 것은 하나 이상의 변수(포인터 변수와 배열 포함)를 묶어서 새로운 자료형을 정의하는 도구이다.
즉, 구조체를 기반으로 새로운 자료형을 정의할 수 있다.
struct person // person이라는 이름의 구조체 정의
{
char name[20]; // 이름 저장을 위한 멤버
char phoneNum[20]; // 전화번호 저장을 위한 멤버
int age; // 나이 저장을 위한 멤버
}
앞서 person이라는 이름의 구조체를 정의하였다. 즉, 자료형을 정의한 것이다.
이제 이 자료형을 대상으로 변수를 선언할 수 있다.
그리고 이렇게 선언되는 변수를 가리켜 구조체 변수라고 한다.
struct type_name val_name; // 구조체 변수선언의 기본 형태
struct person man;
구조체의 멤버로 배열이 선언되면 배열의 접근방식을 취하면 되고, 구조체의 멤버로 포인터 변수가 선언되면 포인터 변수의 접근방식을 취하면 된다!
int형 변수를 선언과 동시에 초기화할 수 있듯이 구조체 변수도 선언과 동시에 초기화할 수 있다. 그리고 초기화 방법은 배열의 초기화와 유사하다.
즉, 멤버의 순서대로 초기화할 대상을 나열하면 된다.
#include <stdio.h>
struct point{
int xpos;
int ypos;
};
struct person{
char name[20];
char phoneNum[20];
int age;
};
int main(void){
struct point pos={10, 20}; // 각각 xpos와 ypos를 초기화
struct person man={"김허수", "010-1144-8844", 25};
printf("%d %d \n", ps.xpos, pos.ypos);
printf(" %s %s %d \n", man.name, man.phoneNum, man.age);
return 0;
}
초기화 과정에서는 문자열 저장을 위해서 strcpy 함수를 호출하지 않아도 된다.
다수의 int형 변수를 선언할 때 int형 배열의 선언을 고려하듯이, 다수의 구조체 변수를 선언할 때에는 구조체 배열의 선언을 고려해야 한다.
구초제 배열의 선언방법은 일반적인 배열의 선언방법과 동일하다.
int형 변수 : int num -> int형 배열 : int arr[10]
point형 변수 : struct point pos -> point형 배열 : struct point arr[10]
따라서 다음과 같이 point형 구조체 배열을 선언하면,
struct point arr[4];
다음의 구조로 배열이 할당된다.
#include <stdio.h>
struct point{
int xpos;
int ypos;
};
int main(void){
struct point arr[3];
int i;
for(i = 0 ; i < 3 ; i++){
printf("점의 좌표 입력 : ");
scanf("%d %d", &arr[i].xpos, &arr[i].ypos);
}
for(i = 0 ; i < 3 ; i++){
printf("[%d, %d]", arr[i].xpos, arr[i].ypos);
return 0;
}
구조체 배열을 선언과 동시에 초기화할 때에는 다음과 같이 배열의 길이만큼 중괄호를 이용해서 초기화를 진행하면 된다.
struct person arr[3]={
{"쇼메이커", "010-0101-1010", 25},
{"쵸비", "010-8884-8111", 24},
{"바이퍼", "010-1111-0000", 25},
구조체 포인터 변수의 선언 및 연산의 방법도 일반적인 포인터 변수의 선언 및 연산의 방법과 다르지 않다.
즉 다음과 같이 int형 포인터 변수를 선언하고 초기화하듯이,
int num = 10;
int * iptr=#
point형 구조체의 포인터 변수도 다음과 같이 선언하고 초기화한다.
struct point pos={11, 12}; // xpos, ypos 를 각각 11, 12로 초기화
struct point * pptr=&pos; // 포인터 변수 pptr이 구조체 변수 pos를 가리킴
그리고 위의 point형 포인터 변수 pptr을 이용해서 다음과 같이 구조체 변수 pos에 접근할 수 있다.
(*pptr).xpos=10; // pptr이 가리키는 구조체 변수의 멤버 xpos에 10 저장
(*pptr).ypos=20; // pptr이 가리키는 구조체 변수의 멤버 ypos에 20 저장
그리고 위의 두 문장은 각각 다음과 같이 쓸 수도 있다.
pptr->xpos=10;
pptr->ypos=20;
즉 *연산과 .연산을 하나의 -> 연산으로 대신할 수 있는것이다.
구조체 변수의 주소 값은 구조체 변수의 첫 번째 멤버의 주소 값과 동일하다.
#include <stdio.h>
struct point{
int xpos;
int ypos;
};
struct person{
char name[20];
char phone[20];
int age;
};
int main(void){
struct point pos={10, 20};
struct person man={"쵸비", "010-8884-8111", 24};
printf("%p %p \n", &pos, &pos.xpos);
printf("%p %p \n", &man, man.name);
return 0;
}
typedef
선언은 기존에 존재하는 자료형의 이름에 새 이름을 부여하는 것을 목적으로 하는 선언이다.
typedef int INT; // int의 또 다른 이름 INT 부여
이로 인해 다음의 뜻을 컴파일러에게 전달한다.
자료형의 이름 int에 INT라는 이름을 추가로 붙여준다.
INT num; // int num; 과 동일한 선언
INT * ptr; // int * ptr; 과 동일한 선언
typedef
선언에 있어서 새로운 이름의 부여는 가장 마지막에 등장하는 단어를 중심으로 이뤄진다.
즉, 다음의 형태로 typedef
가 선언되면,
typedef name1 name2 name3;
가장 마지막에 등장한 name3
가 name1 name2
에 부여된 새로운 이름이 되는 것이다.
구조체 변수의 선언에 있어서 struct 선언을 생략할 수 있는 방법을 알아보자.
다음과 같이 구조체가 정의되었다고 가정해본다.
struct point{
int xpos;
int ypos;
};
이 상황에서는 다음과 같이 구조체 변수를 선언해야 한다.
struct point pos;
그런데 다음과 같이 typedef
선언이 이루어지고 나면,
typedef struct point pyo; // struct point에 pyo라는 이름 부여
이후로는 다음과 같이 struct 선언을 생략한 형태로 구조체 변수를 선언할 수 있다.
pyo pos;
struct point{
int xpos;
int ypos;
} pyo;
대부분 위와 같이 구조체 정의의 뒤를 이어서 typedef 선언이 등장한다.
typedef struct {
int xpos;
int ypos;
}pyo;
위와 같이 구조체 이름 point를 생략할 수도 있지만, 구조체의 이름을 생략하면 아래와 같은 형태로는 구조체 변수를 선언할 수 없게 된다.
struct point man; // 불가넝한 선언
구조체 변수를 함수의 인자로 전달하거나 함수 내에서 return문을 통해서 구조체 변수를 반환하는 경우에 발생하는 모든 현상은 int형 변수를 인자로 전달하거나 int형 변수를 반환하는 경우에 발생하는 현상과 완전히 동일하다.
아래와 같은 코드를 실행하게 되면, 인자로 전달되는 변수의 값은 매개변수에 복사가 된다.
void SinpleFunc(int num){ .... }
int main(void){
int age = 24;
SimpleFunc(age); // age에 저장된 값이 매개변수 num에 전달(복사)
. . . .
}
마찬가지로 함수의 인자로 구조체 변수가 전달될 수 있으며, 이러한 인자를 전달받을 수 있도록 구조체 변수가 매개변수의 선언으로 올 수 있다.
그리고 전달되는 구조체 변수의 값은 매개변수에 통째로 복사가 된다.
#include <stdio.h>
typedef struct point{
int xpos;
int ypos;
} Point;
void ShowPosition(Point pos){
printf("[%d, %d]\n", pos.xpos, pos.ypos);
}
Point GetCurrentPosition(void){
Point cen;
printf("Input current pos : ");
scanf("%d %d", &cen.xpos, &cen.ypos);
return cen;
}
int main(void){
Point curPos=GetCurrentPosition();
ShowPosition(curPos);
return 0;
}
아래의 예제에서는 구조체 변수를 대상으로 하는 Call-by-reference
의 예를 보여준다.
#include <stdio.h>
typedef struct point{
int xpos;
int ypos;
} Point;
void OrgSymTrans(Point * ptr) // 원점대칭
{
ptr->xpos = (ptr->xpos) * -1;
ptr->ypos = (ptr->ypos) * -1;
}
void ShowPosition(Point pos){
printf("[%d, %d] \n", pos.xpos, pos.ypos);
}
int main(void){
Point pos={7, -5};
OrgSymTrans(&pos); // pos의 값을 원점 대칭이동시킴
ShowPosition(pos);
OrgSymTrans(&pos); // pos의 값을 원점 대칭이동시킴
ShowPosition(pos);
return 0;
}
void OrgSymTrans(Point * ptr)
에 정의된 함수에서 보이는 바와 같이 구조체의 포인터 변수도 매개변수로 선언이 되어서 Call-by-reference
형태의 함수호출을 구성할 수 있다.
기본 자료형 변수를 대상으로는 사칙연산을 비롯해서 비교연산 등 다양한 종류의 연산이 가능하다.
그러나 구초제 변수를 대상으로는 매우 제한된 형태의 연산만 허용이 된다.
허용되는 가장 대표젹인 연산은 대입연산이며, 그 이외로 주소 값 반환을 목적으로 하는 & 연산이나 구조체 변수의 크기를 반환하는 sizeof 정도의 연산만 허용이 된다.
#include <stdio.h>
typedef struct point{
int xpos;
int ypos;
} Point;
int main(void){
Point pos1={1, 2};
Point pos2;
pos2=pos1; // pos1의 멤버 대 pos2의 멤버간 복사가 진행됨
printf("크기: %d \n", sizeof(pos1)); // pos1의 전체 크기 반환
printf("%d, %d \n", pos1.xpos, pos1.ypos);
printf("크기: %d \n", sizeof(pos2)); // pos2의 전체 크기 반환
printf("%d, %d \n", pos2.xpos, pos2.ypos);
return 0;
}
위의 실행결과를 통해서 구조체 변수간 대입연산의 결과로 멤버 대 멤버의 복사가 이뤄짐을 확인하였다.
그렇다면 구조체 변수를 대상으로 덧셈이나 뺄셈을 하려면 어떻게 해야 할까?
아래 코드에서 보이듯이 함수를 정의해야 한다.
함수의 정의를 통해서 덧셈이나 뺄셈의 결과를 직접 정의해야 한다.
#include <stdio.h>
typedef struct point{
int xpos;
int ypos;
} Point;
Point AddPoint(Point pos1, Point pos2){
Point pos={pos1.xpos+pos2.xpos, pos1.ypos+pos2.ypos);
return pos;
}
Point MinPoint(Point pos1, Point pos2){
Point pos={pos1.xpos-pos2.xpos, pos1.ypos-pos2.ypos);
return pos;
}
int main(void){
Point pos1 = {5, 6};
Point pos2 = {2, 9};
Point result;
result = AddPoint(pos1, pos2);
printf("%d, %d \n", result.xpos, result.ypos);
result = MinPoint(pos1, pos2);
printf("%d, %d \n", result.xpos, result.ypos);
return 0;
}
먼저 구조체를 정의하는 이유에 대해 알아보자.
구조체를 정의하는 이유는 다음과 같다.
"구조체를 통해서 연관 있는 데이터를 하나로 묶을 수 있는 자료형을 정의하면, 데이터의 표현 및 관리가 용이해지고, 그만큼 합리적인 코드를 작성할 수 있게 된다.
구조체 안에 구조체 변수가 멤버로 존재하는 경우를 가리켜 구조체의 중첩 이라고 한다.
#include <stdio.h>
typedef struct point{
int xpos;
int ypos;
} Point;
typedef struct circle{
Point cen; // 3행에 정의된 구조체의 변수를 멤버로 선언
double rad;
} Circle;
void ShowCircleInfo(Circle * cptr){
printf("%d, %d \n", (cptr->cen).xpos, (cptr->cen).ypos);
printf("radius : %g \n", cptr->rad);
}
int main(void){
Circle c1 = {{1, 2}, 3.5};
// 구조체 변수가 멤버로 존재할 경우, 이렇듯 중괄호를 이용해 구조체 멤버의 초기화를 구분지을 수 있음
Circle c2 = {2, 4, 3.9};
ShowCircleInfo(&c1);
ShowCircleInfo(&c2);
return 0;
}
Circle c2 = {2, 4, 3.9};
를 보자.
중괄호를 이용해서 구조체 변수의 초기화를 구분짓지 않으면, 순서대로 초기화 된다.
Circle 구조체의 가장 상단에 존재하는 변수는 Point 구조체 변수인데, Point 구조체 변수의 첫 번째 멤버는 xpos이니 xpos가 먼저 초기화된다.
그리고 이어서 ypos가 초기화 된다.
구조체는 struct 라는 키워드를 사용해서 정의하는 반면, 공용체는 union 이라는 키워드를 사용해서 정의한다.
typedef struct sbox{ // 구조체 sbox의 정의
int mem1;
int mem2;
double mem3;
} SBox;
typedef union ubox{ // 공용체 ubox의 정의
int mem1;
int mem2;
double mem3;
} UBox;
정의방식에서의 유일한 차이점은 struct 선언을 하냐, union 선언을 하냐에 있다.
하지만 각각의 변수가 메모리 공간에 할당하는 방식과 접근의 결과에는 많은 차이가 있다.
위의 코드를 대상으로 각각 다음 연산을 하면,
printf("%d \n", sizeof(SBox)); // 16 출력
printf("%d \n", sizeof(UBox)); // 8 출력
16과 8이 출력된다.
16은 모든 멤버의 크기를 합한 결과이고, 8은 멤버 중에서 가장 크기가 큰 double의 크기만 계산된 결과이다.
위 그림에서 보이듯이 구조체 변수가 선언되면, 구조체를 구성하는 멤버는 각각 할당이 된다.
반면 공용체 변수가 선언되면, 공용체를 구성하는 멤버는 각각 할당되지 않고, 그 중 크기가 가장 큰 멤버의 면수만 하나 할당되어 이를 공유하게 된다.
앞서 보인 구조체와 공용체의 경우는 멤버에 저장할 값의 유형을 결정했다.(자료형의 선언을 통해서)
하지만 열거형의 경우에는 저장이 가능한 값 자체를 정수의 형태로 결정한다.
즉, 다음과 같은 선언이 열거형의 정의에 해당한다
"syllable형 변수에는 1, 2, 3, 4, 5, 6, 7이 저장 가능하다!
이렇게 변수에 저장이 가능한 값들을 열거하여 정의한다고 해서 '열거형'이라 한다.
enum syllable // syllable이라는 이름의 열거형 정의
{
Do=1, Re=2, Mi=3, Fa=4, So=5, La=6, Ti=7
};
Do는 정수 1을 의미하는 상수로 정의한다. 그리고 이 값은 syllable형 변수에 저장이 가능하다.
위와 같이 열거형을 정의하면 다음과 같이 enum 선언을 추가하여 열거형 변수를 선언해야 한다.(구조체 변수를 선언할 때 struct 선언을 추가하듯이)
enum syllable tone; // 열거형 syllable형 변수 tone의 선언
// 아래의 코드의 실행 결과를 예상하여 봅시다 #include <stdio.h> struct Point { int x, y; }; struct Point add_point(struct Point p1, struct Point p2) { struct Point result = {p1.x + p2.x, p1.y + p2.y}; return result; } int main() { struct Point p1 = {1, 2}; struct Point p2 = {3, 4}; struct Point p3 = add_point(p1, p2); printf("(%d, %d)\\n", p3.x, p3.y); return 0; }
4, 6
#include <stdio.h> #include <string.h> #define SIZE 200 struct Wc { char word[30]; int count; }; struct Wc simbol_table[SIZE]; int idx=0; void count(char* input) { for (int i = 0; i < idx; i++) { // 이미 단어가 존재하는 경우 if (strcmp(simbol_table[i].word, input) == 0) { simbol_table[i].count++; return; } } // 새로운 단어인 경우 strcpy(simbol_table[idx].word, input); simbol_table[idx].count = 1; idx++; } #define INPUT_SIZE 4 int main() { char buf[100]; for (int i = 0; i < INPUT_SIZE; i++) { printf("단어를 입력하세요 : "); scanf("%s", buf); count(buf); } printf("\n[빈도수]\n"); for (int i = 0; i < idx; i++) { printf("%s : %d\n", simbol_table[i].word, simbol_table[i].count); } return 0; }