C언어 사용자정의 자료형 구조체2

    2018-02-08 02:11:43 작성

    구조체 배열

    구조체도 하나의 자료형 이기 때문에 구조체도 배열로 선언이 가능합니다.

    #include <stdio.h>
    
    typedef struct _person {
    	char name[20];
    	int age;
    	char gender;
    } Person;
    
    int main(void) {
    	Person arr[3] = {
    		{ "홍길동", 27, 'M' },
    		{ "임꺽정", 25, 'M' },
    		{ "황진이", 21, 'F' },
    	};
    	int i;
    	int len = sizeof(arr) / sizeof(arr[0]);
    
    	for (i = 0; i < len; i++) {
    		printf("이름 : %s, 나이 : %d, 성별 : %c\n", arr[i].name, arr[i].age, arr[i].gender);
    	}
    	return 0;
    }


    특별건 없습니다. 구조체도 일반 자료형과 동일하게 작동합니다.


    구조체 포인터

    구조체 포인터 역시 일반 자료형과 크게 다르지 않습니다.
    *(간접지정연산자)와 .(멤버연산자)를 더 편리하게 사용하는 ->(포인터연산자)를 사용합니다.

    #include <stdio.h>
    
    struct point {
    	int xPos;
    	int yPos;
    };
    
    int main(void) {
    	struct point pos = { 10,30 };
    	struct point * ptr = &pos;
    
    	(*ptr).xPos += 10;
    	ptr->yPos += 30;
    
    	printf("[%d, %d]\n", (*ptr).xPos, ptr->yPos);
    	return 0;
    }


    (*ptr).xPos 에서 *가 .보다 연산순위가 낮아 소괄호를 사용해야 하는데 비교하여,
    ptr->xPos 더 간결하고 편리합니다.


    구조체 변수의 주소

    구조체의 주소는 첫번째 멤버의 주소와 같습니다.
    구조체 배열에서도 일반 자료형의 배열과 같습니다.

    #include <stdio.h>
    
    typedef struct _person {
    	char name[12];
    	int age;
    } Person;
    
    int main(void) {
    	Person arr[3];
    
    	printf("arr의 주소           : %p\n", arr);
    	printf("arr[0]의 주소        : %p\n", &arr[0]);
    	printf("arr[0].name의 주소   : %p\n", &arr[0].name);
    	printf("Person의 size        : %d\n", sizeof(Person));
    	printf("arr[1]의 주소        : %p\n", &arr[1]);
    	printf("arr[2]의 주소        : %p\n", &arr[2]);
    	return 0;
    }


    arr[0]의 주소와 arr[0].name의 주소는 실제로 같습니다.
    Person의 길이는 16byte이므로 arr[1], arr[2]는 각각 16만큼 차이가 나는것을 확인할 수 있습니다.


    포인터 변수를 구조체 멤버로 선언하기

    포인터 변수도 구조체의 멤버가 될 수 있습니다.
    다음은 좌표정보를 갖는 Point 구조체와 중심점과 원의 반지름정보를 갖는 Circle 구조체 예제입니다.

    #include <stdio.h>
    
    typedef struct _point {
    	int xPos;
    	int yPos;
    } Point, *PTR_Point;
    
    typedef struct _circle {
    	double raius;
    	PTR_Point center;
    } Circle;
    
    int main(void) {
    	Point p1 = { 50,60 };
    	Circle c1 = { 3.4, &p1 };
    
    	printf("원의 반지름 : %g\n", c1.raius);
    	printf("원의 중심점 x: %d, y: %d\n", c1.center->xPos, c1.center->yPos);
    	return 0;
    }


    예제에서 구조체 선언 중*PTR_Point는 typedef _point * PTR_Point와 같은 내용입니다.

    구조체의 멤버 변수는 자기자신의 구조체타입의 포인터 변수를 포함할 수 있습니다.

    다음과 같이 선언이 가능합니다.

    #include <stdio.h>
    
    typedef struct _node {
    	int key;
    	struct _node * nextNode;
    } Node;
    
    int main(void) {
    	Node n1 = { 1 };
    	Node n2 = { 2 };
    	Node n3 = { 3 };
    
    	n1.nextNode = &n2;
    	n2.nextNode = &n3;
    	n3.nextNode = NULL;
    
    	printf("%d, %d, %d\n", n1.key, n1.nextNode->key, n1.nextNode->nextNode->key);
    }


    n1의 nextNode 값에 n2의 주소값을 넣었습니다.
    n1.nextNode->key로 n2.key에 접근 합니다. n3도 마찬가지입니다.
    n3.nextNode = NULL 한것은 포인터 변수를 사용하지 않을때에는 NULL 값으로 해주는것이 좋기 때문입니다.


    함수와 구조체

    구조체를 함수의 매개변수로 넘길때에도 일반 자료형과 똑같이 작동합니다.
    단지 구조체는 일반 자료형보다 크기가 클수 있고 크기가 매우 큰 구조체를 전달하면 구조체의 내용 전체가 복사되기 때문에 시간이 많이 걸리고 메모리낭비가 심한 단점이 있습니다.
    그렇기 때문에 구조체를 그냥 전달하기 보다는 포인터로 전달하면 이와같은 단점을 보안할수 있습니다.

    #include <stdio.h>
    
    struct person {
    	char name[20];
    	int age;
    };
    
    struct person createPerson();
    void viewPerson(struct person * p);
    
    int main(void) {
    	struct person man = createPerson();
    	viewPerson(&man);
    	return 0;
    }
    
    void viewPerson(struct person * p) { // 포인터 변수로 받음
    	printf("이름 : %s, 나이: %d\n", p->name, p->age);
    }
    
    struct person createPerson() {
    	struct person p;
    	printf("이름 : ");
    	scanf("%s", &p.name);
    	printf("나이 : ");
    	scanf("%d", &p.age);
    	return p; // struct person 자료형을 반환
    }


    createPerson 함수의 경우 struct person 구조체의 멤버값을 사용자로 입력받은 후 struct person을 반환하는 모습을 보여줍니다.
    viewPerson 함수는 person함수를 포인터변수로 받아 출력해줍니다.
    이렇게 하면 주소값(4byte)만 전달하면 되기때문에 시간 및 공간낭비가 없습니다.

    중첩된 구조체의 정의와 초기화

    구조체의 멤버 변수로 구조체자료형을 갖을수 있습니다.
    이러한경우 생성과 동시에 초기화 하는 방법은 다음과 같습니다.

    #include <stdio.h>
    
    typedef struct point {
    	int xPos;
    	int yPos;
    } Point;
    
    typedef struct circle {
    	Point center; // 구조체 변수 선언
    	double radius;
    } Circle;
    
    void showCircle(struct circle * c);
    
    int main(void) {
    	Circle ring1 = { { 10, 20 }, 3.5 }; // 중괄호로 구분하여 초기화
    	Circle ring2 = { 30, 50, 11.5 }; // 순차적으로 기입하여 초기화
    	showCircle(&ring1);
    	showCircle(&ring2);
    	return 0;
    }
    
    void showCircle(Circle * c) {
    	printf("중심점 좌표 : [%d,%d] 반지름 %g\n", c->center.xPos, c->center.yPos, c->radius);
    }


    구조체의 비트 필드

    0 또는 1의 값만 저장하는 변수가 있다고 하면 이 변수는 1bit의 공간만 있으면 될것입니다.
    하지만 일반적으로 정수를 저장할때에는 int 형으로 저장하므로 31bit의 공간이 낭비가 될 것입니다.
    구조체의 비트 필드는 기억공간을 byte 단위가 아닌 bit단위로 사용이 가능하도록 해줌으로써 구조체 비트 필드를 사용하면, 기억공간을 절약할 수 있는 데이터 구조체를 만들수 있습니다.
    다음과 같은 형식으로 작성하여 사용합니다.

    struct 구조체이름 {
        정수자료형 멤버이름 : 비트수;
    };
    비트필드 사용지 주의점
    • 비트필드 변수의 자료형은 int 또는 unsigned int로 선언하며, 부호의 혼동을 피하기 위해 unsigned int로 선언한다.
    • 구조체 비트 필드에 대한 포인터 및 배열은 사용할 수 없다.
    • 비트필드의 전체 크기는 시스템에서 제공하는 int 크기 이내이어야 한다
    #include <stdio.h>
    
    typedef struct flags {
    	unsigned int a : 1; // a는 1비트 크기
    	unsigned int b : 3; // b는 3비트 크기
    	unsigned int c : 7; // c는 7비트 크기
    } Flags;
    
    int main()
    {
    	Flags f1;
    
    	f1.a = 1;  // 1bit 0 ~ 1 저장
    	f1.b = 4;  // 3bit 0 ~ 7 저장
    	f1.c = 63; // 7bit 0 ~ 127 저장
    
    	printf("%u\n", f1.a);
    	printf("%u\n", f1.b);
    	printf("%u\n", f1.c);
    	printf("Flags의 size : %d\n", sizeof(Flags));
    	return 0;
    }


    Flags의 크기는 unsigned int 형이므로 4byte의 크기를 갖습니다.

    구조체는 연관있는 데이터를 하나로 묶어서 하나의 자료형으로 정의하므로 데이터의 표현 및 관리가 용이하고 그만큼 합리적인 코드를 작성할 수 있게됩니다.


    연습문제

    1. 이름, 전화번호를 저장할수 있는 PhoneBook 구조체를 선언합니다.
    2. 3명의 데이터를 입력받아 저장하는 PhoneBook 구조체 배열을 선언합니다.
    3. 사용자로부터 3개의 데이터 정보를 입력받습니다.
    4. PhoneBook 배열을 인자로 받아 안의 정보를 출력하는 함수를 정의하여 출력합니다.