[C] 구조체

JOOYEUN SEO·2023년 9월 1일

C 언어

목록 보기
10/13
post-thumbnail

1. typedef

typedef → "type + definition"으로, 그대로 해석하면 형을 정의하는 것이라 할 수 있다. 정확히는 이미 존재하는 자료형의 이름을 자신이 만든 이름으로 바꾸거나, 구조체형을 선언할 때 사용한다.

⌨️ 예제 1

#include <stdio.h>

int main() {
    // 자료형의 이름 변경
    typedef float Decimal; // 기존 float형의 이름을 decimal이라는 새로운 이름으로 변경
    Decimal n = 3.14;
    printf("%f\n", n);

    // 배열의 이름 변경(요소 2개가 점의 x, y좌표를 가리키는 순서쌍을 의미하는 배열)
    typedef int Pair[2];   // 길이 2짜리 int형 배열의 이름을 Pair로 붙임([2] 포함)
    Pair point = {3, 4};   // == int point[2] = {3, 4}; ([2]를 적으면 안 됨)
    printf("(%d, %d)\n",point[0], point[1]);

    // 포인터의 이름 변경
    typedef char *String;  // 문자열의 주소를 저장하는 char형 포인터의 이름을 String으로 붙임(* 포함)
    String name = "JY";    // == char *name = "JY"; (*를 적으면 안 됨)
    printf("이름 : %s\n", name);
}

🖥️ 실행 결과

3.140000
(3, 4)
이름 : JY

2. 구조체 만들기

구조체는 (서로 다른 자료형인)여러 개의 변수를 모아서 하나의 새로운 자료형으로 정의하는 것이다. struct라는 구조체 지정자와 사용자가 붙이는 구조체명(structure tag)을 합하여 구조체형을 정하고, 구조체에 속하는 변수들을 구조체 멤버로 기술한다.

⌨️ 예제 2

// 구조체 정의와 구조체 변수 선언 → 예약어 typedef 사용한 방법
#include <stdio.h>

int main() {
    // 구조체 정의 → 중괄호 안에 구조체 멤버들의 자료형과 이름 나열
    typedef struct {int x, y;} Point;  // 구조체 멤버 x, y를 묶어서 Point형으로 정의한다는 뜻

    // 구조체 변수 선언
    Point p;                           // == struct {int x, y;} p;
    
    // 구조체 멤버의 참조 → 멤버 연산자 도트(.)를 사용
    p.x = 3;                           // 구조체 멤버 x애 3 대입
    p.y = 4;                           // 구조체 멤버 y에 4 대입

    printf("(%d, %d)\n", p.x, p.y);    // 순서쌍의 형태로 출력됨
}
// 구조체 정의와 구조체 변수 선언 → typedef 사용하지 않는 방법
#include <stdio.h>

// 구조체 정의와 구조체 변수 선언을 동시에 가능
struct Point {int x, y;} p;     // 구조체는 주로 전역변수로 정의된다


int main() {
    // 구조체 멤버의 참조
    p.x = 3;
    p.y = 4;

    printf("(%d, %d)\n", p.x, p.y);
}
// 구조체 정의와 구조체 변수 선언 → 변수의 초기화까지 하는 방법
#include <stdio.h>

struct Point {int x, y;};   // 구조체 정의
struct Point p = {3, 4};    // 구조체 변수 선언과 초기화(배열의 초기화와 같은 방법)

int main() {
    printf("(%d, %d)\n", p.x, p.y);
}

🖥️ 실행 결과

(3, 4)

구조체를 만드는 방법에는 여러 가지가 있으며, 실행 결과는 동일하다.
예제에서 p는 Point형을 가진 구조체 변수다. 구조체 변수 p를 선언하는 것은 p에 int형 2개 크기(8byte)의 기억공간을 할당한다는 것을 뜻한다.


⌨️ 예제 3

#include <stdio.h>

struct ProductInfo {      // 구조체 안에 다양한 형의 변수(멤버)들 나열 가능
    int num;
    char name[100];
    int cost;             
};

int main() {
    struct ProductInfo myProduct = {355, "샤인머스캣", 19900}; // 구조체 멤버 나열 순
    
    printf("상품 번호 : %d\n", myProduct.num);
    printf("상품 이름 : %s\n", myProduct.name);
    printf("상품 가격 : %d원\n", myProduct.cost);
}

🖥️ 실행 결과

상품 번호 : 355
상품 이름 : 샤인머스캣
상품 가격 : 19900원

3. 구조체 포인터(구조체 가리키기)

구조체 포인터는 포인터명 앞에 *를 붙여서 선언하고, 구조체 변수의 주소값(구조체 변수 앞에 &)으로 초기화한다. 일반 포인터와 동일하게 주소값을 갖고, 자료가 있는 곳을 가리킨다.

⌨️ 예제 4

// 예제 3에 구조체 변수를 가리키는 구조체 포인터 추가
#include <stdio.h>

struct ProductInfo {
    int num;
    char name[100];
    int cost;             
};

int main() {
    struct ProductInfo myProduct = {355, "샤인머스캣", 19900}; // 구조체 변수 선언, 초기화
    struct ProductInfo *ptr_product = &myProduct;           // 구조체 포인터 선언, 초기화    
    
    // 구조체 포인터의 멤버 참조 2가지 방법
    printf("상품 번호 : %d\n", (*ptr_product).num);     // 도트 연산자(.)
    printf("상품 이름 : %s\n", ptr_product -> name);    // 포인터 연산자(->)
    printf("상품 가격 : %d원\n", ptr_product -> cost);
}

🖥️ 실행 결과

상품 번호 : 355
상품 이름 : 샤인머스캣
상품 가격 : 19900원

구조체 포인터로 멤버를 참조할 때 구조체 연산자인 도트 연산자를 이용하거나 포인터 연산자를 이용할 수 있다. 도트 연산자를 이용하는 경우에는 .*보다 연산 순서상 먼저인데, *를 먼저 계산해야 하므로, 괄호를 꼭 사용해야 한다.


⌨️ 예제 5

// call-by-reference로 매개변수의 자료 전달
#include <stdio.h>

struct ProductInfo {
    int num;
    char name[100];
    int cost;             
};

void productSale(struct ProductInfo *p, int percent) {    // 가격에 n% 할인을 적용하는 함수
    p->cost -= p->cost * percent / 100;    // 할인가 = 원가 - 원가 × 할인률(%)/100
}

int main() {
    struct ProductInfo myProduct = {355, "샤인머스캣", 20000};
    
    productSale(&myProduct, 10);          // myProduct의 주소값 넘겨주고 percent에 10 대입

    printf("상품 번호 : %d\n", myProduct.num);
    printf("상품 이름 : %s\n", myProduct.name);
    printf("상품 가격 : %d원\n", myProduct.cost);
}

🖥️ 실행 결과

상품 번호 : 355
상품 이름 : 샤인머스캣
상품 가격 : 18000원

10% 할인된 가격이 적용됐다.

이와 같이 함수를 사용할 때 함수의 매개변수로 구조체를 사용할 수 있다. 크게 구조체 자체를 함수의 매개변수로 사용하는 방법과 구조체 포인터를 함수의 매개변수로 사용하는 방법으로 나뉘는데, 구조체 포인터를 사용하면 구조체를 복사하지 않아도 되기 때문에 주로 이 방법을 많이 사용한다.


⌨️ 예제 6

// 2개의 구조체 포인터를 매개변수로 받아 값을 서로 바꾸는 함수
#include <stdio.h>

struct ProductInfo {
    int num;
    char name[100];
    int cost;             
};

void productSwap(struct ProductInfo *a, struct ProductInfo *b) {    // 두 상품의 가격을 교환
    struct ProductInfo tmp = *a;    // a가 가리키는 값을 임시변수 tmp에 저장
    *a = *b;                        // a가 가리키는 구조체 변수에 b가 가리키는 구조체 변수를 대입
    *b = tmp;                       // b가 가리키는 구조체 변수에 tmp 값 저장
}

int main() {
    struct ProductInfo myProduct = {355, "샤인머스캣", 20000};	// 구조체 변수 선언, 초기화
    struct ProductInfo otherProduct = {356, "납작복숭아", 10000};

    productSwap(&myProduct, &otherProduct);		// 참조 호출(& 사용)

    printf("상품 번호 : %d\n", myProduct.num);
    printf("상품 이름 : %s\n", myProduct.name);
    printf("상품 가격 : %d원\n", myProduct.cost);
}

🖥️ 실행 결과

상품 번호 : 356
상품 이름 : 납작복숭아
상품 가격 : 10000원

myProduct에 들어있던 "샤인머스캣"이라는 상품의 정보가 "납작복숭아"라는 상품의 정보로 바뀌었다.


⌨️ 예제 7

#include <stdio.h>

typedef int Point[2];       // 길이 2인 int형 배열을 Point형으로 선언
typedef Point *PointPtr;    // Point형 배열을 가리키는 포인터를 PointPtr형으로 선언

int main() {
    Point p = {3, 4};       // == int p[2] = {3, 4};
    PointPtr pp = &p;       // == Point *pp = &p;

    printf("%d %d %d\n", **pp, (*pp)[0], (*pp)[1]);
}

🖥️ 실행 결과

3 3 4

**pp == *(*pp) == *p == p[0] → 3 출력
(*pp)[0] == p[0] → 3 출력
(*pp)[1] == p[1] → 4 출력


4. 구조체 안에 함수 정의(C++)

C에서는 불가능하지만 C++에서는 구조체 안에 함수를 정의할 수 있다.

⌨️ 예제 8
C 언어

// 시간을 초로 계산하는 프로그램(1시간 = 60분 = 3600초)
#include <stdio.h>

struct Time {
    int h, m, s;
};

int totalSec(struct Time t) {
    return (3600 * t.h) + (60 * t.m) + t.s; 
}

int main() {
    struct Time t = {1, 15, 30};

    printf("%d\n", totalSec(t));
}

C++ 언어

#include <stdio.h>

struct Time {
    int h, m, s;

    // 구조체 내에 멤버 함수 선언(구조체 안의 다른 변수들에 접근 가능하므로 매개변수 필요 없어짐)
    int totalSec() {    
        return (3600 * h) + (60 * m) + s;   // 변수 h, m, s를 그대로 사용 가능해짐
    }
};


int main() {
    Time t = {1, 15, 30};

    printf("%d\n", t.totalSec());   // 구조체의 멤버 함수에 접근 가능
}

🖥️ 실행 결과

4530

Time이라는 구조체 안에 totalSec 함수를 넣어서 totalSec는 Time에 종속적인 관계라는 것을 명확하게 알 수 있는 방법이다.


⌨️ 예제 9
C++ 언어

#include <stdio.h>

struct Point {
    int x, y;
    
    // 구조체 안에 상하좌우로 움직이는 멤버 함수 선언
    void moveRight() {x++;}     
    void moveLeft() {x--;}
    void moveUp() {y++;}
    void moveDown() {y--;}
};


int main() {
    Point p = {2, 5};                   // 점의 초기 좌표 설정

    p.moveDown();
    p.moveLeft();

    printf("(%d, %d)\n", p.x, p.y);     // 이동 후 점의 좌표 출력
}

🖥️ 실행 결과

(1, 4)

⌨️ 예제 10
C 언어

// x좌표와 y좌표를 교환
#include <stdio.h>

struct Point {int x, y;};

void pSwap(struct Point *p) {
    int tmp = p->x;     // 포인터 변수가 구조체 멤버에 접근하기 위해 연산자 사용
    p->x = p->y;        // p의 y 값을 p의 x에 대입
    p->y = tmp;
}

int main() {
    struct Point pos = {3, 4};

    pSwap(&pos);

    printf("(%d, %d)\n", pos.x, pos.y);
}

C++ 언어

#include <stdio.h>

struct Point {
    int x, y;

    void pSwap() {		// pSwap 함수를 구조체 안에 선언
    int tmp = x;
    x = y;
    y = tmp;
    }
};

int main() {
    struct Point pos = {3, 4};
    pos.pSwap();

    printf("(%d, %d)\n", pos.x, pos.y);
}

🖥️ 실행 결과

(4, 3)




▷ 김형근·곽덕훈·정재화, 『C프로그래밍』, 한국방송통신대학교출판문화원, 2020
▷ 서현우, 『뇌를 자극하는 C 프로그래밍』, 한빛미디어, 2005
▷ 두들낙서, “두들낙서의 C/C++ 강좌”, https://youtube.com/playlist?list=PLlJhQXcLQBJqywc5dweQ75GBRubzPxhAk&si=s8RNpRcpJTi_Lz8M

0개의 댓글