[C]구조체(struct)[1]
- 이번 포스트는 구조체(struct)에 관한 내용입니다.
1️⃣ 구조체(struct)를 사용하는 이유
- 실수를 줄이기 위해: 같은 형의 데이터 여러개를 매개변수로 받을 때 순서가 바뀌면 컴파일러가 실수를 찾을 방법이 없습니다.
- 수정이 용이: 나중에 누군가 매개변수를 바꾸면 문제가 생기고 일리리 수정하는 것도 쉽지 않습니다.
2️⃣ 구조체 사용
< 구조체 선언 >
struct date
{
int year;
int month;
int day;
};
(1) 기본적인 구조체일때
int main(void)
{
struct date birth;
birth.year = 1993;
birth.month = 05;
birth.day = 25;
printf("My birthday is %d.%d.%d", birth.year, birth.month, birth.day);
}
/*---출력---*/
My birthday is 1993.5.25
(2) 구조체(포인터)일때
int main(void)
{
struct date *my_birth;
birth->year = 1993;
birth->month = 05;
(*birth).day = 25; //이런식으로 사용이 가능하지만 잘 사용하지않는 방법
printf("My birthday is %d.%d.%d", birth->year, birth->month, birth->day);
}
/*---출력---*/
My birthday is 1993.5.25
(3) 구조체(배열)일때
int main(void)
{
/* 이런식의 선언은 실수할 가능성이 큼 */
struct date birth[2] = { {1993, 05, 25}, {2000, 06, 11}};
/* 각값을 변경하는 것이 실수를 줄이는 방법*/
// birth[0].year = 1993;
// birth[0].month = 05;
// birth[0].day = 25;
// birth[1].year = 2000;
// birth[1].month = 06;
// birth[1].day = 11;
for (int i = 0; i < 2; i++)
printf("birthday is %d.%d.%d\n", birth[i].year, birth[i].month, birth[i].day);
}
/*---출력---*/
birthday is 1993.5.25
birthday is 2000.6.11
(4) 구조체(포인터배열)일때
int main(void)
{
struct date *birth[2];
struct date Timo = { 1997, 12, 03};
struct date Nono = { 2000, 02, 23};
birth[0] = &Timo;
birth[1] = &Nono;
for (int i = 0; i < 2; i++)
printf("birthday is %d.%d.%d\n", birth[i]->year, birth[i]->month, birth[i]->day);
}
/*---출력---*/
birthday is 1997.12.3
birthday is 2000.2.23
3️⃣ typedef를 이용해서 구조체 선언하기
/* typedef사용 example */
typedef unsigned int size_t;
- 위의 코드는
unsigned int
에 새로운 별명을 준 것 입니다. usigned int
와size_t
어느 것을 사용해도 상관없습니다.- C에서
_t
로 끝나는 자료형은 보통 이런식으로typedef
로 선언된 것 입니다.
/* 방법 1 */
struct date {
int year;
int month;
int day;
};
typedef struct date date_t;
/* 방법 2 */
typedef struct date {
int year;
int month;
int day;
} date_t;
/* 방법 3 */
typedef struct {
int year;
int month;
int day;
} date_t;
위의 코드와 같이 3가지 방법으로 typedef
를 사용하여 구조체를 선언할 수 있지만 방법 3
의 방법이 제일 깔끔하고 많이 쓰입니다.
4️⃣ 특징 & 주의사항
(1) 구조체 변수 초기화
- 구조체는 일반 변수와 마찬가지로 선언과 동시에 초기화가 안됩니다.(스텍에 남아있는 데이터를 그대로 사용)
- 사실 기계는 구조체라는 개념자체를 모릅니다.(전처리기를 통해 일반변수와 같이 확장)
->구조체
는 프로그래밍 언어가 개발자들 편하라고 제공 해준 개념입니다. body_t body = { 51, 163};
와 같이 구조체를 선언과 동시에 초기화하면 실수를 할 가능성이 큽니다.typedef struct { float height; float weight; } body_t;
만약 위와 같이
구조체
가 선언되어 있었다면 키가 51cm, 몸무게가 163kg이 될 겁니다.
이런 실수
를 줄이기 위해 구조체를 사용한 것인데 사용의 의미가 없어지게 됩니다.const
멤버 변수는 사용하지 않는 것이 좋습니다. (쓸일도 별로없고 업계에서도 쓰지말라는 쪽)
(2) 구조체 매개변수 전달(값 vs 주소)
기본 자료형을 전달할 때는 기본 데이터 크기가 작으니 원본 바꿀 때만 주소로 전달하면 됬었습니다.
구조체
를 주소로 전달하는 것이 좋습니다.
< 멤버 변수가 엄청많을 경우>
- 구조체의 경우 데이터 크기가 클수도 있습니다.(int형 멤버 변수가 수만개 => 수백KB)
typedef struct { int point1; /* ... */ int porint30000; } characteristic_t;
다음과 같이 구조체의 주소로 전달
void note_characteristic(const characteristic_t *crt);
< 매개변수가 많은 경우 >
typedef struct {
id_t id;
char name[20];
int age;
float height;
float weight;
/* ... */
char address[50];
} person_info_t
다음과 같이 전달하면 복잡하고 실수할 가능성이 큽니다.
void note_person_info(id_t id, char *name, int age,
float height, float weight, ..., char *address);
그 대신 다음과 같이 주소로 구조체를 전달
void note_person_info(const person_info_t *person);
(3) 여러 자료형 변수를 반환하고 싶을 때(구조체 반환)
- C언어의 함수는 반환값이 한개 입니다. 하지만 구조체를 반환하면 실질적으로 여러 개의 값을 반환하는 격입니다.
date_t get_day(void)
{
date_t date;
date.year = 2021;
date.month = 02;
date.day = 22;
return date;
}
int main(void)
{
date_t date;
date = get_day();
printf("today is %d.%d.%d\n", date.year, date.month, date.day);
}
/*---출력---*/
today is 2021.2.22
5️⃣ 구조체의 멤버변수가 포인터일 때 생길 수 있는 실수
< 실수가 난 케이스(얕은 복사) >
#include <stdio.h>
#define NUM_CHAMP (2)
typedef struct {
char *name;
} champion_t;
int main(void)
{
champion_t champ[NUM_CHAMP];
FILE *stream;
stream = fopen("test.txt", "wb");
champ[0].name = "Teemo";
champ[1].name = "Nono";
fwrite(champ, sizeof(champ[0]), NUM_CHAMP, stream);
fclose(stream);
}
/*---test.txt 적힌 내용---*/
P@@?V@@? // 주소값이 내용에 적힘
프로그램 종료 후에도 test.txt
에 적힌 주소값은 그대로 이지만 그 문자열은 다른 주소에 저장되어 있습니다.(보안상의 이유로 주소값이 임의로 바뀜)
즉 주소값을 저장한 파일은 쓸모가 없게 됩니다.
< 해결방법(깊은 복사) >
포인터 멤버변수
대신배열형 멤버변수
를 사용하여 값을 복사하면 됩니다.
#include <stdio.h>
#include <string.h>
#define NUM_CHAMP (2)
#define NAME_LEN (20)
typedef struct {
char name[NAME_LEN];
} champion_t;
int main(void)
{
champion_t champ[NUM_CHAMP];
FILE *stream;
stream = fopen("test.txt", "wb");
const char* name1 = "Teemo";
const char* name2 = "Nono";
strncpy(champ[0].name, name1, NAME_LEN);
champ[0].name[NAME_LEN - 1] = '\0';
strncpy(champ[1].name, name2, NAME_LEN);
champ[0].name[NAME_LEN - 1] = '\0';
fwrite(champ, sizeof(champ[0]), NUM_CHAMP, stream);
fclose(stream);
}
위와 같이 주소가 아닌 값
을 test.txt에 복사 합니다. 그리고 프로그램을 종료
한 뒤에 다시 파일을 읽으면 정상적으로 값을 읽을 수 있습니다.
int main(void)
{
FILE *stream;
char champ[NUM_CHAMP][NAME_LEN];
stream = fopen("test.txt", "rb");
/* 오류처리 생략 */
fread(champ, sizeof(champ[0]), NUM_CHAMP, stream);
for (int i = 0; i < NUM_CHAMP; i++)
printf("champ%d name is %s\n", i + 1, champ[i]);
}
/*---출력---*/
champ1 name is Teemo
champ2 name is Nono
- 결론적으로 파일에 저장할 때는 구조체 뿐만아니라 일반 변수들도
포인터로 복사하면 안됩니다.
- 가능하면 구조체 멤버변수로
포인터를 사용하지 않는 것
이 좋습니다.