프로그래밍 언어/[ C ]

[ C ] 08. 구조체

kim.svadoz 2020. 8. 13. 10:13
반응형

[ 구조체 ]


구조체는 struct 키워드로 정의한다.

struct 구조체이름{
    자료형 멤버이름;
    ...
};

// 구조체는 정의만 해서 사용할 수가 없다. 구조체도 변수로 선언해서 사용한다.

struct 구조체이름 변수이름;

ex.

#define _CRT_SECURE_NO_WARNINGS            // strcpy 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>
#include <string.h>        // strcpy 함수가 선언된 헤더 파일

struct Person{        // 구조체 정의
    char name[20];            // 구조체 멤버 1
    int age;                // 구조체 멤버 2
    char address[100];        // 구조체 멤버 3
}

int main(){
    struct Person p1;        // 구조체 변수 선언

    // 점으로 구조체 멤버에 접근하여 값 할당
    strcpy(p1.name, "홍길동");
    p1.age = 30;
    strcpy(p1.address, "경기도 분당구 삼평동");

    // 점으로 구조체 멤버에 접근하여 값 출력
    printf("이름: %s\n", p1.name);        // 이름 : 홍길동
    printf("나이: %d\n", p1.age);            // 나이 : 30
    printf("주소: %s\n", p1.address);        // 주소 : 경기도 분당구 삼평동

    return 0;
}
# 실행 결과
이름 : 홍길동
나이 : 30
주소 : 경기도 분당구 삼평동
  • 구조체는 보통 main함수 바깥에 정의한다. 만약 함수 안에 구조체를 정의하면 해당 함수안에서만 구조체를 사용할 수 있다.
  • 정의한 구조체를 사용하려면 구조체 변수를 선언해야하고 구조체 이름 앞에 반드시 struct 키워드를 붙인다.
  • 구조체 멤버에 접근할 때는 .을 사용한다. p1.age = 30과 같이 구조체 멤버에 접근한 뒤 값을 할당하고, p1.age같이 값을 가져온다. p1.name등의 문자열 멤버는 =(할당연산자)로 저장할 수 없으므로 strcpy함수를 사용해야한다.
  • 구조체정의와 선언을 따로하지않고 동시에 변수를 선언할 수 있다.
struct 구조체이름{
    자료형 멤버이름;
} 변수명;
  • 구조체 변수를 선언함과 동시에 초기화 할 수 있다.
struct Person{
    char name[20];
    int age;
    char address[100];
};

int main(){
    struct Person p1 = { .name = "홍길동", .age = 30, .address="경기도 분당구 삼평동"};
    struct Person p2 = { .name = "김성현", .age = 28, .address="서울시 관악구 신림동"};
    ...
}
...

typedef로 구조체 선언하기

일일이 struct 키워드를 붙이기 귀찮으니 별칭을 지정하는 방법으로 strcut 키워드를 생략해보자.

typedef struct 구조체이름{
    자료형 멤버이름;
} 구조체별칭;
#define _CRT_SECURE_NO_WARNINGS            // strcpy 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>
#include <string.h>        // strcpy 함수가 선언된 헤더 파일

typedef struct _Person{
    char name[20];            // 구조체 멤버 1
    int age;                // 구조체 멤버 2
    char address[100];        // 구조체 멤버 3
} Person;                // typedef를 사용하여 구조체 별칭을 Person으로 정의

int main(){
    Person p1;        // 구조체 별칭 Person으로 변수 선언

    // 점으로 구조체 멤버에 접근하여 값 할당
    strcpy(p1.name, "홍길동");
    p1.age = 30;
    strcpy(p1.address, "서울시 용산구 한남동");

    // 점으로 구조체 멤버에 접근하여 값 출력
    printf("이름: %s\n", p1.name);            // 이름 : 홍길동
    printf("나이: %s\n", p1.age);                // 나이 : 30
    printf("주소: %s\n", p1.address);            // 주소 : 서울시 용산구 한남동

    return 0;
}
# 실행 결과
이름 : 홍길동
나이 : 30
주소 : 서울시 용산구 한남동
  • 만약에 구조체 별칭을 사용하지 않고 구조체 이름으로 변수를 선언하고 싶다면?
struct _Person p1;        // 구조체 이름으로 변수 선언

=>struct _Person p1;Person p1은 완전히 같다.

typedef 활용하기

typedef는 자료형의 별칭을 만드는 기능이다. 따라서 구조체뿐만 아니라 모든 자료형의 별칭을 만들 수 있다.

typedef 자료형 별칭
typedef 자료형* 별칭
typedef int MYINT;            // int를 별칭 MYINT로 정의
typedef int* PMYINT;        // int 포인터를 별칭 PMYINT로 정의

MYINT num1;            // MYINT로 변수 선언
PMYINT numPtr1;        // PMYINT로 포인터 변수 선언

numPtr1 = &num1;        // 포인터에 변수의 주소 저장
                       // 사용방법은 일반 변수, 포인터와 같다.
  • 이렇게 typedef로 정의한 별칭을 사용자정의 자료형, 사용자 정의 타입이라 부른다.
  • 여기서 PMYINT는 안에 *가 이미 포함되어 있으므로 포인터 변수를 선언할 때 *를 붙여버리면 이중포인터가 되므로 사용에 주의해야한다.
PMYINT *numPtr1;            // PMYINT에는 *가 이미 포함되어 있어서 이중 포인터가 선언됨
int* *numPtr2;                // PMYINT와 같은의미. 이중 포인터!
  • struct 뒤에 붙는 구조체 이름은 원래 구조체 태그(tag)라고 부른다.(공용체, 열거형도 마찬가지로 공용체 태그, 열거형 태그라 부름). 그리고 typedef로 정의한 구조체 별칭은 사용자 정이 타입의 이름이라 할 수 있다.
struct TAG{
    자료형 멤버이름;
};

typedef struct TAG{
    자료형 멤버이름;
} 타입이름;
  • C언어는 나온지가 오래되다 보니 여러 관습이 남아 있는데 구조체 태그와 타입 이름을 구분하기 위해 관례상 태그 앞에 _, _tag, tag를 붙이고 있따. 코드에 따라서 태그 뒤에 _t를 붙이기도 한다.
  • 구조체 태그와 타입이름은 사실상 같은 구조체를 지칭하므로 이름을 완전히 다르게 지을 필요는 없다. 요즘은 구조체 태그와 타입이름을 똑같이 만들기도 한다.

익명구조체

typedef 구조체 별칭을 정의할 때 매번 구조체 이름을 지정해주는것도 번거롭다. 이때는 익명 구조체(anonymous structure)를 사용하면 구조체 이름을 지정하지 않아도 된다.

typedef struct {
    자료형 멤버이름;
} 구조체 별칭;
#define _CRTR_SECURE_NO_WARNINGS        // strcpy 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>
#include <string.h>

typedef struct{
    char name[20];
    int age;
    char address[100];
} Person;            // typedef를 사용하여 구조체 별칭을 Person으로 정의

int main(){
    Person p1;            // 구조체 별칭 Person으로 변수 선언

    // 점으로 구조체 멤버에 접근하여 값 할당
    strcpy(p1.name, "홍길동");
    p1.age = 30;
    strcpy(p1.address, "서울시 용산구 한남동");

    // 점으로 구조체 멤버에 접근하여 값 출력
    printf("이름: %s\n", p1.name):
    printf("나이: %d\n", p1.age);
    printf("주소: %s\n", p1.address);

    return 0;
}

** 구조체 포인터 사용하기

보통 구조체는 멤버 변수가 여러 개 들어있어서 크기가 큰 편이다. 그래서 구조체 변수를 일일이 선언해서 사용하는 것 보다는 포인터에 메모리를 할당해서 사용하는 편이 효율적이다.

  • 다른 자료형과 마찬가지로 구조체도 포인터를 선언할 수 있으며 구조체 포인터에는 malloc 함수를 사용하여 동적 메모리를 할당할 수 있다.
struct 구조체이름 *포인터이름 = malloc(sizeof(struct 구조체이름));
#define _CRT_SECURE_NO_WARNIGNS            // strcpy 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>
#include <string.h>            // strcpy 함수
#include <stdlib.h>            // malloc, free 함수

struct Person{
    char name[20];
    int age;
    char address[100];
};

int main(){
    struct Person *p1 = malloc(sizeof(struct Person));        // 구조체 포인터 선언, 메모리할당

    // 화살표 연산자로 구조체 멤버에 접근하여 값 할당
    strcpy(p1->name, "홍길동");
    p1->age = 30;
    strcpy(p1->address, "서울시 용산구 한남동");

    // 화살표 연산자로 구조체 멤버에 접근하여 값 출력
    printf("이름: %s\n", p1->name);
    printf("나이: %d\n", p1->age);
    printf("주소: %s\n", p1->address);

    free(p1);        // 동적 메모리 해제

    return 0;
}
# 출력 결과
이름: 홍길동
나이: 30
주소: 서울시 용산구 한남동
  • 다소 문법이 복잡하지만 구조체 이름 앞에는 반드시 struct 키워드를 붙여야 한다는 점만 기억하면 된다. 즉, 포인터를 선언할 때도, sizeof로 크기를 구할 때도 struct 키워드를 넣어준다.
  • 특이하게 지금 까지는 구조체의 멤버에 접근하는 방법이 .(점)을 이용해서 접근했지만 구조체 포인터의 멤버에 접근할 때는 ->(화살표 연산자)를 사용한다.
# 참고
구조체 포인터와 -> 구조체포인터에 접근할 때 -> 를사용하는데 ->는 화살표연산자(arrow operator)라고 부른다.
포인터는 메모리 주소를 저장하므로 어떤값이 있는 곳을 "가리키다"라는 의미가 있다. 그래서 연산자도 어떤 값이 있는 곳을 가리킨다는 의미에서 화살표 사용
# 구조체 포인터에서 .으로 멤버에 접근하기
구조체 포인터에서 멤버에 접근하려면 p1->age와 같이 화살표 연산자를 사용하는데 괄호와 역참조를 사용하면 .(점)으로 멤버에 접근할 수 있다.
p1->age;        # 화살표 연산자로 멤버에 접근
(*p1).age;        # 구조체 포인터를 역참조한 뒤 .으로 멤버에 접근
=> 위와 같이 구조체 포인터를 역참조하면 pointer to struct Person에서 pointer to가 제거되서 struct Person이 된다. 따라서 .으로 멤버에 접근 가능!
# 구조체의 멤버가 포인터일 때 역참조하기
구조체의 멤버가 포인터일 때 역참조를 하려면 맨 앞에 *를 붙여야 한다.
이때 구조체 변수 앞에 *가 붙어 있더라도 멤버의 역참조이지 구조체 변수의 역참조가 아니다.
- *구조체변수.멤버
- *구조체포인터 -> 멤버     
// ^^^^^ //
#include <stdio.h>
#include <stdlib.h>

struct Data{
    char c1;
    int *numPtr;        // 포인터
}

int main(){
    int num1 = 10;
    struct Data d1;        // 구조체 변수
    struct Data *d2 = malloc(sizeof(struct Data));        // 구조체 포인터에 메모리 할당

    d1.numPtr = &num1;
    d2->numPtr = &num1;

    printf("%d\n", *d1.numPtr);        // 10, 구조체의 멤버를 역참조
    printf("%d\n", *d2->numPtr);    // 10, 구조체 포인터의 멤버를 역참조

    d2->c1 = 'a';
    printf("%c\n", (*d2).c1);        // a, 구조체 포인터를 역참조 하여 c1에 접근 ( d2->c1과 같음 )
    printf("%d\n", *(*d2).numPtr);    // 10, 구조체 포인터를 역참조 하여 numPtr에 접근한 뒤 다시 역참조( *d2->numPtr과 같음 )

    free(d2);

    return 0;
}

** 구조체 별칭으로 포인터를 선언하고 메모리 할당하기

구조체별칭 *포인터이름 = malloc(sizeof(구조체별칭));
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

typedef struct _Person{
    char name[20];
    int age;
    char address[100];
} Person;

int main(){
    Person *p1 = malloc(sizeof(Person));        // 구조체 별칭으로 포인터 선언, 메모리 할당

    // 화살표 연산자로 구조체 멤버에 접근하여 값 할당
    strcpy(p1->name, "홍길동");
    p1->age = 30;
    strcpy(p1->address, "서울시 용산구 한남동");

    // 화살표 연산자로 구조체 멤버에 접근하여 값 출력
    printf("이름: %s\n", p1->name);
    printf("나이: %d\n", p1->age);
    printf("주소: %s\n", p1->address);

    free(p1);        // 동적 메모리 해제

    return 0;
}
  • 이처럼 구조체 별칭을 사용하면 포인터를 선언하고 메모리를 할당하는 방법이 더 간단해진다 Person *p1과 같이 구조체 별칭으로 포인터를 바로 선언한 뒤 malloc 함수로 메모리를 할당한다. 이때 할당할 메모리 크기도 sizeof(Person)처럼 구조체 별칭으로 구하면 된다.
  • 익명 구조체 선언하는 방법 또한 이전의 방법과 동일하다

** 구조체 포인터에 구조체 변수의 주소 할당하기

지금까지 malloc 함수로 구조체 포인터에 동적 메모리를 할당하였는데, 동적메모리를 할당하지 않고 구조체 포인터를 사용하는 방법이 있다? 이 때는 구조체 변수에 &(주소 연산자)를 사용하면 된다.

구조체포인터 = &구조체변수;
#include <stdio.h>

struct Person{
    char name[20];
    int age;
    char address[100];
};

int main(){
    struct Person p1;        // 구조체 변수 선언
    struct Person *ptr;        // 구조체 포인터 선언

    ptr = &p1;        // p1의 메모리 주소를 구하여 ptr에 할당

    // 화살표 연산자로 구조체 멤버에 접근하여 값 할당
    ptr->age = 30;

    printf("나이: %d\n", p1.age);            // 나이:30, 구조체 변수의 멤버 값 출력
    printf("나이: %d\n", ptr->age);        // 나이:30, 구조체 포인터의 멤버 값 출력

    return 0;
}
  • ptr에 p1의 메모리 주소를 할당했으므로 ptr의 멤버를 수정하면 결국 p1의 멤버도 바뀌게 된다. 접근하는 방식만 차이가 있을 뿐 결국 같은 곳의 내용을 수정하게 된다. ( 메모리 주소는 컴퓨터마다, 실행할 때마다 달라진다. )

image-20200803151945762

반응형