C 구조체
주요 개념
어떤 프로그래밍 문제를 나타내는 데 필요한 정보는, 하나의 수 또는 수들의 리스트보다는 복잡하다. 프로그램은 여러 가지 속성을 지닌 실체나 실체들의 집합을 다룰 수 있다. 예를 들어 이름, 주소, 전화번호, 기타 정보로 한 명의 고객을 표현할 수 있다.
이러한 경우에 C 구조체를 이용하여 이 모든 정보를 하나의 단위로 묶을 수 있다. 이것은 프로그램을 구성하는 데 매우 유용하다.
구조체를 사용하려면, 개별적으로 흩어진 여러 변수들의 집합으로 정보를 저장하지 않고, 관련된 모든 정보를 한 장소에 저장할 수 있다.
구조체를 설계할 때 구조체와 함께 사용할 함수들의 패키지를 함께 개발하는 것이 일반적으로 유용하다. 예를 들어 구조체의 내용을 출력하고 싶을 때마다 한 무더기의 printf()문들을 작성하지 않고, 그 구조체(또는 구조체의 주소)를 전달인자로 사용하는 하나의 디스플레이 함수를 작성할 수 있다. 모든 정보가 구조체 안에 들어 있기 때문에 전달인자는 하나만 있어도 된다. 이것은 설계를 수정할 경우에 대단히 유용하다.
공용체 선언은 구조체 선언과 비슷하다. 그러나 공용체 멤버들은 같은 메모리 공간을 공유한다. 그러나 공용체에는 한 번에 한 멤버만 들어갈 수 있다. 공용체를 사용하면 데이터형은 다양하지만 한 번에 하나의 값만 넣을 수 있는 변수를 생성할 수 있다.
enum은 정수 기호 상수들을 정의하는 수단을 제공한다. typedef는 기본 데이터형이나 유도 데이터형에 새로운 식별자를 부여하는 수단을 제공한다.
함수를 가리키는 포인터는 어떤 함수에게 그것이 어떤 함수를 사용해야 하는지 알리는 수단을 제공한다.
요약
C구조체는 일반적으로 데이터형이 서로 다른 여러 개의 데이터 항목들을 하나의 데이터 객체(object)에 저장하는 수단을 제공한다.
태그(tag)를 사용하여, 특정 구조체 템플릿을 식별하고 그 구조체형의 변수들을 선언할 수 있다.
멤버 연산자 또는 도트 연산자(.)는 구조체 템플릿에 있는 레이블을 사용하여 구조체의 개별 멤버들에 접근할 수 있다.
구조체를 가리키는 포인터를 선언하면, 이름과 도트 연산자를 사용하는 대신에 포인터와 간접 멤버 연산자(->)를 사용하여 구조체의 개별 멤버에 접근할 수 있다.
&연산자를 사용하면 구조체의 주소를 얻을 수 있다. 배열과는 달리, 구조체 이름은 구조체의 주소 역할을 하지 않는다.
전통적으로, 구조체 관련 함수들은 구조체를 가리키는 포인터를 전달인자로 사용해왔다. 최신형 C는 구조체 자체를 전달인자와 리턴값으로 사용할 수 있고, 같은 유형의 다른 구조체에 대입할 수 있다.
공용체는 구조체로, 둘은 동일한 신택스를 사용한다. 그러나 공용체에서 멤버들은 동일한 기억 공간을 공유하고, 구조체처럼 여러 가지 데이터형을 동시에 저장하는 대신 공용체는 선택 리스트에 들어있는 한 가지 데이터형만 저장한다.
즉, 구조체는 하나의 int형 그리고 하나의 double형 그리고 하나의 char형을 동시에 저장할 수 있다. 그러나 이에 상응하는 공용체는 하나의 int형 또는 하나의 double형 또는 하나의 char형만 저장할 수 있다.
한 무리의 정수 기호 상수(열거된 상수)들을 설정하고, 관련 열거형을 정의할 수 있다.
typedef 기능을 사용하면, C의 표준 데이터형들에 다른 이름이나 약식 표기를 부여할 수 있다.
함수의 이름은 함수의 주소 역할을 한다. 그러한 주소는 다른 함수에 전달인자로 사용할 수 있고, 그 다른 함수는 포인터가 가리키는 함수를 사용할 수 있게 된다. pf가 특정 함수의 주소가 대입되어 있는 함수 포인터라면, 그 특정 함수를 다음과 같은 두 가지 방법으로 호출할 수 있다.
#include <math.h> // double sin(double) 함수를 선언한다
…
double (*pdf)(double);
double x;
pdf = sin;
x = (*pdf)(1.2); // sin(1.2)를 호출한다.
x = pdf(1.2); // sin(1.2)를 호출한다.
구조체 생성
- 홍길동 씨는 소장하고 있는 도서 목록을 출력하기를 원한다. 각 도서마다 책 제목, 저장명, 출판사, 출판일, 페이지 수, 권 수, 정가를 출력하기를 원한다.
- 이 항목들 중에서 책 제목, 저자명과 같은 일부 항목들은 문자열들의 배열에 저장할 수 있다.
- 그리고 다른 항목들은 int형의 배열이나 float형의 배열을 요구한다. 책 제목을 기준으로 정렬한 목록, 저자명을 기준으로 정렬한 목록, 정가를 기준으로 정렬한 목록 등과 같이 특별히 홍길동 씨가 몇 가지 완전한 목록을 만들기를 원할 때, 서로 다른 7개의 배열을 만들어 모든 것을 추적한다는 것은 매우 복잡하다.
- 더 좋은 해결책은, 배열을 하나만 사용하는 것이다. 이 배열의 각 원소에 책 한 권에 관한 모든 정보를 넣는 것이다. 그렇게 하려면 문자열들과 수들을 함께 저장하되, 정보를 구분하여 유지할 수 있는 어떤 데이터형이 필요하다. 이러한 경우에 C 구조체가 사용된다.
문제01 <도서 목록 만들기>
- 구조체의 레이아웃(형식) 설정하기
- 레이아웃에 맞는 변수 선언하기
- 구조체 변수의 개별 성분에 접근하기
/* 관리하는 도서가 하나뿐인 도서 목록 */
#include <stdio.h>
#include <string.h>
char * s_gets(char * st, int n);
#define MAXTITL 41 // 최대 책 제목 길이 +1
#define MAXAUTL 31 // 최대 저자명 길이 +1
struct book { // 구조체 템플릿 : 태그는 book
char title[MAXTITL];
char author[MAXAUTL];
float value;
}; // 구조체 템플릿의 끝
int main(void)
{
struct book library; // library를 book형 변수로 선언한다
printf("도서명을 입력하세요.\n");
/* 책 제목(title)에 접근한다 */
s_gets(library.title, MAXTITL);
printf("저자명을 입력하세요.\n");
s_gets(library.author, MAXAUTL);
printf("정가를 입력하세요.\n");
scanf("%f", &library.value);
printf("%s (%s) : $%.2f\n",library.title,
library.author, library.value);
printf("%s \"%s\" ($%.2f)\n", library.author,
library.title, library.value);
printf("\n//종료//.\n\n");
return 0;
}
char * s_gets(char * st, int n)
{
char * ret_val;
char * find;
ret_val = fgets(st, n, stdin);
if (ret_val)
{
find = strchr(st, '\n'); // 개행 찾기
if (find) // 주소가 NULL이 아니면,
*find = '\0'; // null 문자를 넣어준다.
else
while (getchar() != '\n')
continue; // 행의 나머지 삭제
}
return ret_val;
}
# 실행 결과
구조체 선언
- 구조체 선언은 구조체를 어떻게 구성할 것인지를 나타내는 마스터플랜이다.
- 선언의 형식
struct book
{
char title[MAXTITL];
char author[MAXAUTL];
float value;
}
- 키워드 struct는 그 뒤를 따르는 것이 구조체라는 것을 나타낸다.
- 태그 book은 구조체를 참조하는데 사용할 수 있는 약식 레이블이다.
- book 구조체 설계를 사용하는 구조체 변수 library의 선언은 다음과 같다.
struct book library;
- book 구조체 설계를 사용하는 구조체 변수 library의 선언은 다음과 같다.
- 구조체 선언에서 그 다음에는 중괄호{ }로 둘러싸인 구조체 멤버들의 리스트가 나온다.
- C의 모든 데이터형이 멤버가 될 수 있다.
- 각 멤버는 완전하게 세미콜론으로 끝나는 자신만의 선언을 가진다.
- title 멤버는 MAXTITL개의 원소를 가지는 char형의 배열이다.
- author 멤버는 MAXAUTL개의 원소를 가지는 char형의 배열이다.
- value 멤버는 float형의 변수이다.
- 다른 구조체들까지도 멤버가 될 수 있다.
- 닫는 중괄호 } 뒤에 오는 세미콜론이 구조체 설계의 정의를 끝낸다.
- 이 선언은 실제 데이터 객체를 생성하지 않고, 무엇이 그러한 구조체를 구성하는지만 나타낸다. = 템플릿이라고 부르는 이유
- 이 선언을 어떤 함수 정의의 내부에 놓으면, 그 태그는 그 함수 안에서만 사용할 수 있다.
- 이 선언을 모든 함수의 외부에 놓으면, 그 선언 뒤에 나오는 그 파일에 있는 모든 함수에서 사용할 수 있다.
- 또 다른 함수에서 다음과 같이 정의할 수 있다.
struct book dickens; - 그리고 그 함수는 book 구조체 설계를 따르는 구조체 변수 dickens를 사용할 수 있다.
- 또 다른 함수에서 다음과 같이 정의할 수 있다.
- 태그 이름은 선택사항이다. 구조체 선언과 변수 정의를 결합하면 태그를 사용할 필요가 없다.
- struct {
char title[MAXTITL];
char author[MAXAUTL];
float value;
} library; // 변수 이름을 가지는 선언이 뒤에 붙는다,
- struct {
- 그러나 구조체 템플릿을 여러 번 사용할 계획이거나 어느 한 장소에서 구조체 설계를 정의하고, 다른 장소에서 실제 변수들을 정의할 경우에는, 구조체 선언을 설정할 때 반드시 태그 이름을 사용해야 한다.
구조체 변수의 정의
- "구조체 설계"
- 그 데이터를 표현하는 방법을 컴파일러에게 알려 주지만, 컴퓨터는 그 데이터를 위한 기억 공간을 할당하지 않는다.
- "구조체 변수를 만드는 것"
- struct book library; 이 문장을 만났을 때 변수 library를 생성하는데,
- 컴파일러는 book 템플릿을 사용하여 MAXTITL개의 원소를 가지는 char형의 배열,|
MAXAUTL개의 원소를 가지는 char형의 배열, 하나의 float형 변수를 위한 기억 공간을 할당한다. - 이 기억 공간은 library라는 단일 이름으로 한 덩어리를 이룬다.
- 컴파일러는 book 템플릿을 사용하여 MAXTITL개의 원소를 가지는 char형의 배열,|
- struct book library; 이 문장을 만났을 때 변수 library를 생성하는데,
- 구조체 변수의 선언에서 struct book은 간단한 변수의 선언에서 int나 float가 하는 것과 같은 역할을 한다.
- 예를 들어 두 개의 struct book형 변수와 그러한 구조체를 가리키는 포인터를 다음과 같이 선언할 수 있다.
- struct book doyle, panshin, * ptbook;
- 구조체 변수 doyle와 panshin은 자신만의 title, author, value 멤버를 가진다.
- 포인터 ptbook은 doyle, panshin 또는 어떤 다른 book 구조체를 가리킬 수 있다.
- 사실상 book 구조체 선언은 struct book이라는 새로운 데이터 형을 만든다고 생각할 수 있다.
- struct book doyle, panshin, * ptbook;
구조체의 초기화
- 구조체의 초기화는 배열의 초기화와 비슷한 신택스를 사용한다.
- struct book library = {
"The Pirate and the Devious Damsel",
"Renee Vivotte",
1.95
}; - 각각의 초기화자는 초기화하려는 구조체 멤버의 데이터형과 일치해야 한다.
title, author 멤버는 문자열로 초기화하고 value 멤버는 수로 초기화할 수 있다.
- struct book library = {
구조체 멤버에 접근하기
- 구조체는 하나의 특별배열(superarray)과 비슷하다. 여기서는 한 원소가 char형이고, 다음 원소는 float형, 그 다음 원소는 int형일 수 있다.
- 인덱스를 사용하여 배열의 개별 원소에 접근할 수 있듯이 구조체 멤버 연산자인 도트(.)를 사용하여 구조체의 멤버에 접근할 수 있다.
- library.value는 다른 float형 변수를 사용하는 것과 정확히 동일한 방식으로 사용할 수 있다.
- library.title은 다른 char형의 배열을 사용하는 것과 정확히 동일한 방식으로 사용할 수 있다.
- 사실상 .title / .author / .value는 book 구조체에 대해 인덱스 역할을 한다.
- library는 구조체이지만, library.value는 float형이므로 다른 float형과 동일한 방식으로 사용된다.
- &library.float에서 도트(.)는 & 연산자보다 우선순위가 높다. → &(libraty.float)와 같다.
구조체를 위한 지정 초기화자
- 구조체를 위한 지정 초기화자의 신택스는 도트 연산자와 멤버 이름을 사용한다.
- book 구조체의 value멤버만 초기화하려면 다음과 같이 한다.
struct book surprise = {.value = 10.99}; - 지정초기화자는 순서에 상관없이 사용할 수 있다.
struct book gift = { .value = 25.99,
.author = "James Broadfool",
.title = "Rue for the Toad" };
구조체의 배열
- 구조체를 위한 지정 초기화자의 신택스는 도트 연산자와 멤버 이름을 사용한다.
선언하기
- 구조체의 배열을 선언하는 것은 다른 종류의 배열을 선언하는 것과 같다.
struct book library[MAXBKS];- 이것은 MAXBKS개의 원소를 가지는 배열 library를 선언한다.
- 이 배열의 각 원소들은 book형의 구조체다. 그러므로 library[0]은 하나의 book 구조체이고, library[1]도 하나의 book 구조체이다.
- library라는 이름 자체는 구조체 이름이 아니라, struct book형 구조체들을 원소로 가지는 배열의 이름이다.
멤버 식별하기
- 구조체의 배열에서 멤버를 식별하려면 개별적인 구조체에 적용되는 규칙을 동일하게 적용한다.
- 구조체 이름 뒤에 도트 연산자가 오고, 그 뒤에 멤버 이름이 붙는다.
library[0].value // 첫 번째 배열 원소와 관련된 value 멤버
library[4].title // 다섯 번째 배열 원소와 관련된 title 멤버 - 배열 인덱스가 이름의 끝에 붙는 것이 아니라 library 뒤에 붙는다.
library.value[2] // 틀리다
library[2].value // 맞다 - 요약하면 다음과 같은 순서이다.
─ library : book 구조체의 배열
─ library[2] . : book 구조체의 배열의 한 원소, 그러므로 하나의 book 구조체
─ library[2].title : char형 배열, library[2]의 title 멤버
─ library[2].title[4] : char형 배열, library[2]의 title 멤버의 한 문자
문제02 <여러 권의 책을 관리하는 도서 목록>
- 구조체 배열을 선언하는 방법
- 개별 멤버에 접근하는 방법
/* 여러 권의 책을 관리하는 도서 목록 */
#include <stdio.h>
#include <string.h>
char * s_gets(char * st, int n);
#define MAXTITL 40
#define MAXAUTL 40
#define MAXBKS 100 // 책의 최대 권수
struct book { // book 템플릿을 설정한다
char title[MAXTITL];
char author[MAXAUTL];
float value;
};
int main(void)
{
struct book library[MAXBKS]; // book 구조체의 배열
int count = 0;
int index;
printf("도서 제목을 입력하세요.\n");
printf("끝내려면 라인의 시작 위치에서 [enter]키를 누르세요.\n");
while (count < MAXBKS && s_gets(library[count].title, MAXTITL) != NULL
&& library[count].title[0] != '\0')
{
printf("저자명을 입력하세요.\n");
s_gets(library[count].author, MAXAUTL);
printf("정가를 입력하세요.\n");
scanf("%f", &library[count++].value);
while (getchar() != '\n')
continue; // 입력 라인을 깨끗이 비운다
if (count < MAXBKS)
printf("다음 도서 제목을 입력하세요.\n");
}
if (count > 0)
{
printf("다음은 소장하고 있는 도서들의 목록입니다.\n");
for (index = 0; index < count; index++)
printf("%s by %s: $%.2f\n", library[index].title,
library[index].author, library[index].value);
}
else
printf("책 사러 갑시다.\n");
return 0;
}
char * s_gets(char * st, int n)
{
char * ret_val;
char * find;
ret_val = fgets(st, n, stdin);
if (ret_val)
{
find = strchr(st, '\n'); // 개행을 찾는다
if (find) // 주소가 NULL이 아니라면,
*find = '\0'; // 널 문자를 거기에 놓는다
else
while (getchar() != '\n')
continue; // 행의 나머지를 처리한다
}
return ret_val;
}
# 실행 결과 (입력한 데이터가 없을 때)
# 실행 결과
여러 권의 책을 입력 받기 위해 루프를 사용한 것이 문제01과의 주된 차이점이다.
while (count < MAXBKS && s_gets(library[count].title, MAXTITL) != NULL
&& library[count].title[0] != '\0')
- count < MAXBKS는 입력한 책의 수가 배열의 크기 한계를 초과하지 않도록 검사한다.
- 표현식 s_gets(library[count].title, MAXTITL)은 책의 타이틀에 해당하는 문자열을 읽는다. s_gets()가 파일의 끝을 지나쳐 읽으려고 할 때 그 표현식은 NULL로 평가된다.
- 표현식 library[count].title[0] != '\0'은 입력받은 문자열의 첫 문자가 널 문자인지 검사한다.
사용자가 라인의 시작 위치에서 엔터를 입력하면 빈 문자열이 전송되고, 루프가 종료된다.
사용자 입력 값 : 12.50[enter]
while (getchar() != '\n')
continue;
- scanf() 함수는 1, 2, ., 5, 0을 읽는다. 그러나 [enter](\n)는 그 자리에 남겨져 다음에 어떤 입력문이 실행될 때까지 대기한다.
- 스페이스와 개행을 읽어서 폐기하는 예방 코드가 없다면, 다음 입력문은 입력에 남아 있던 그 개행을 빈 라인으로 읽고, 프로그램은 사용자가 입력 정지 신호를 보냈다고 판단할 것이다.
- 위 코드로 개행을 발견하여 폐기할 때까지 모든 문자들을 먹어 없애는, 입력 큐(queue)를 깨끗이 비우면 s_gets()는 새롭게 출발할 수 있다.
구조체를 가리키는 포인터
- 구조체를 가리키는 포인터를 사용하는 이유
- 구조체를 가리키는 포인터가 구조체 자체보다 다루기가 쉽다.
- 일부 구형 시스템에서 구조체를 전달인자로 함수에 전달할 수 없지만, 구조체를 가리키는 포인터는 전달할 수 있다.
- 구조체를 함수에 전달할 수 있다 해도 포인터를 전달하는 것이 효율적이다.
- 많은 데이터 표현들이 다른 구조체를 가리키는 포인터를 멤버로 가지는 구조체를 사용한다.
문제04
- 구조체를 가리키는 포인터를 정의하는 방법
- 그것을 사용하여 구조체의 멤버에 접근하는 방법
/* 구조체를 가리키는 포인터 사용*/
#include <stdio.h>
#define LEN 20
struct names {
char first[LEN];
char last[LEN];
};
struct guy {
struct names handle;
char favfood[LEN];
char job[LEN];
float income;
};
int main(void)
{
struct guy fellow[2] = {
{{ "Ewen", "Villard"},
"grilled salmon",
"personality coach",
68112.00
},
{{"Rodney", "Swillbelly"},
"tripe",
"tabloid editor",
432400.00
}
};
struct guy* him; // 구조체를 가리키는 포인터
printf("[주 소] #1: %p #2: %p\n", &fellow[0], &fellow[1]);
him = &fellow[0]; // 포인터에게 가리킬 곳을 알려준다.
printf("[포인터] #1: %p #2: %p\n", him, him + 1);
printf("him->income은 $%.2f: (*him).income은 $%.2f\n",
him->income, (*him).income);
him++; // 다음 구조체를 가리키게 한다.
printf("him->favfood은 %s: him->handle.last는 %s\n\n",
him->favfood, him->handle.last);
return 0;
}
# 실행 결과
포인터를 사용하여 멤버에 접근하기
- 간접 멤버 연산자 (->)를 사용한다.
- him -> income은 him == &barney 이면 barney.income이다.
- him == &fellow[0]이면, him -> income은 fellow[0].income이다.
- him은 포인터이고, him -> income은 그 포인터가 가리키는 구조체의 한 멤버이다.
- him -> income은 float형 변수이다.
- &와 *가 서로 상반하는(reciprocal) 연산자라는 것을 이용한다.
- him == &fellow[0]이면, *him == fellow[0]이다.
- 따라서 다음과 같이 대체하여 사용할 수 있다.
fellow[0].income == (*him).income이다. - . 연산자가 * 연산자보다 우선순위가 높기 때문에 괄호가 필요하다.
- him이 barney라는 이름을 가진 guy형 구조체라면(him == &barney), 다음의 것들은 모두 동등하다.
barney.income == (*him).income == him->income
함수에 구조체 알리기
- 함수의 전달인자들은 함수에 값을 전달한다. 구조체는 하나의 값보다는 약간 복잡하다.
- 최신 컴파일러에서는 함수의 전달인자로 구조체를 사용하는 세 가지 방법이 있다.
- 구조체 자체를 전달할 것인지,
- 아니면 구조체를 가리키는 포인터를 전달할 것인지
- 또는 구조체의 한 부분에 대한 정보만 필요하다면, 구조체의 멤버를 전달인자로 전달할 수 있다.
(1) 구조체의 멤버 전달하기
- 구조체의 멤버가 하나의 값을 가지는 데이터형이라면, 구조체의 멤버를 그 특정 데이터형을 받아들이는 함수에 전달인자로 사용할 수 있다.
문제05
- 고객의 은행 잔고와 상호신용금고 잔고를 더하는 프로그램
- 구조체의 멤버를 함수에 전달하는 방법을 사용한다.
/* 구조체의 멤버를 전달인자로 전달한다 */
#include <stdio.h>
#define FUNDLEN 50
struct funds {
char bank[FUNDLEN];
double bankfund;
char save[FUNDLEN];
double savefund;
};
double sum(double, double);
int main(void)
{
struct funds stan = {
"국민은행",
4032.27,
"동아상호신용금고",
8543.94
};
printf("신정히 님의 총 잔고는 $%.2f입니다.\n\n",
sum(stan.bankfund, stan.savefund) );
return 0;
}
/* 두 개의 double형 값을 더한다 */
double sum(double x, double y)
{
return(x + y);
}
# 실행 결과
- 프로그램이 바르게 동작한다.
- 함수 sum()은 실전달인자들이 구조체의 멤버여부를 신경쓰지 않고 double형일 것을 요구한다.
- 물론, 피호출 함수가 호출 함수에 있는 한 멤버의 값에 영향을 주고자 한다면 그 멤버의 주소를 다음과 같이 전달할 수 있다.
modify(&stan.bankfund); // Stan 씨의 은행 잔고를 변경시키는 함수가 될 수 있다.
(2) 구조체의 주소 사용하기
문제06
- 동일한 문제를 구조체의 주소를 전달인자로 사용하여 해결한다.
- funds 구조체를 가지고 작업해야 하므로 함수도 역시 funds 선언을 사용해야 한다.
/* 구조체를 가리키는 포인터를 전달한다 */
#include <stdio.h>
#define FUNDLEN 50
struct funds {
char bank[FUNDLEN];
double bankfund;
char save[FUNDLEN];
double savefund;
};
double sum(const struct funds *); // 전달인자 : 포인터
int main(void)
{
struct funds stan = {
"국민은행",
4032.27,
"동아상호신용금고",
8543.94
};
printf("신정히 님의 총 잔고는 $%.2f입니다.\n\n", sum(&stan));
return 0;
}
double sum(const struct funds * money)
{
return(money->bankfund + money->savefund);
}
# 실행 결과
- 프로그램이 바르게 동작한다.
- 사용하지는 않았지만 은행 이름과 상호신용금고 이름에 접근할 수 있다.
- 구조체의 주소를 얻기 위해 & 연산자를 사용한다는 점에 주의하라. 배열 이름과는 달리, 구조체 이름은 주소가 아니다.
- 함수 sum()은 funds 구조체를 가리키는 하나의 포인터(money)를 전달인자로 사용한다.
- 주소 &stan을 함수에 전달하면, 포인터 money는 구조체 stan을 가리키게 된다.
- 포인터가 가리키는 값의 내용을 함수가 변경하면 안 되기 때문에, money가 const를 가리키는 포인터로 선언되었다.
- 그러고 나서 함수는 -> 연산자를 사용하여 stan.bankfund와 stan.savefund의 값을 얻는다.
(3) 전달인자로 구조체 전달하기
문제07
- 구조체를 전달인자로 전달할 수 있다.
/* 구조체를 전달한다 */
#include <stdio.h>
#define FUNDLEN 50
struct funds {
char bank[FUNDLEN];
double bankfund;
char save[FUNDLEN];
double savefund;
};
double sum(struct funds moolah); // 전달인자 : 구조체
int main(void)
{
struct funds stan = {
"국민은행",
4032.27,
"동아상호신용금고",
8543.94
};
printf("신정히 님의 총 잔고는 $%.2f입니다.\n\n", sum(stan));
return 0;
}
double sum(struct funds moolah)
{
return(moolah.bankfund + moolah.savefund);
}
# 실행 결과
- 프로그램이 바르게 동작한다.
- struct funds를 가리키는 포인터인 money를 struct funds 변수인 moolah로 대체했다.
- sum()이 호출되었을 때, mollah라는 이름의 변수가 funds 템플릿에 따라 만들어진다.
- 이 구조체의 멤버들은 구조체 stan의 대응하는 멤버들이 가지는 값의 복사본으로 각각 초기화된다.
- 따라서, 원본 구조체의 복사본을 사용하여 각각 초기화된다.
- moolah가 구조체이기 때문에 프로그램은 money->bankfund가 아니라 moolah.bankfund를 사용한다.
구조체 대입
- 최신의 C는 한 구조체를 다른 구조체에 대입하는 것을 허용한다. 배열의 경우에는 불가능한 일이다.
- n_data와 o_data가 같은 데이터형의 구조체라면, 다음과 같이 할 수 있다.
o_data = n_data; // 한 구조체를 다른 구조체에 대입한다. - 이것은 o_data의 각 멤버에 n_data의 대응하는 각 멤버의 값이 대입되게 한다.
- n_data와 o_data가 같은 데이터형의 구조체라면, 다음과 같이 할 수 있다.
- 한 구조체를 다른 구조체로 초기화할 수 있다.
- struct names right_field = {"Ruthie", "George"};
- struct names captain = right_field; // 한 구조체를 다른 구조체로 초기화
함수의 리턴값
- 최신의 C에서는, 구조체를 함수의 전달인자로 전달할 수 있을뿐만 아니라 함수의 리턴값으로도 리턴할 수 있다.
- 구조체를 함수의 전달인자로 사용하면, 함수에 구조체 정보를 전달할 수 있다.
- 구조체 포인터는 양방향 커뮤니케이션을 허용한다. 그러므로 프로그래밍 문제를 해결하는데 적당한 방법을 사용자가 선택할 수 있다.
문제08
- 전달인자와 리턴값으로 구조체를 사용하도록 그 프로그램을 수정한다.
- 프로그램은 사용자에게 이름과 성씨를 입력할 것을 요청하고, 이름과 성씨를 이루고 있는 글자들의 총 개수를 보고한다.
- 포인터를 사용하여 구조체를 다루는 형식이다.
/* 구조체를 가리키는 포인터를 사용한다 */
#include <stdio.h>
#include <string.h>
#define NLEN 30
struct namect {
char fname[NLEN];
char lname[NLEN];
int letters;
};
void getinfo(struct namect *);
void makeinfo(struct namect *);
void showinfo(const struct namect *);
char * s_gets(char * st, int n);
int main(void)
{
struct namect person;
getinfo(&person);
makeinfo(&person);
showinfo(&person);
return 0;
}
void getinfo (struct namect * pst)
{
printf("이름을 입력하세요.\n");
s_gets(pst->fname, NLEN);
printf("성씨를 입력하세요.\n");
s_gets(pst->lname, NLEN);
}
void makeinfo (struct namect * pst)
{
pst->letters = strlen(pst->fname) +
strlen(pst->lname);
}
void showinfo (const struct namect * pst)
{
printf("%s %s, 당신의 이름은 %d개의 글자를 가지고 있습니다. \n",
pst->fname, pst->lname, pst->letters);
}
char * s_gets(char * st, int n)
{
char * ret_val;
char * find;
ret_val = fgets(st, n, stdin);
if (ret_val)
{
find = strchr(st, '\n'); // 개행을 찾는다
if (find) // 주소가 NULL이 아니면,
*find = '\0'; // 널문자를 거기에 놓는다.
else
while (getchar() != '\n')
continue; // 행의 나머지를 처리한다.
}
return ret_val;
}
# 실행 결과
- main()에서 호출하는 세 개의 함수에 작업을 분담시킨다. 각각의 경우에 person 구조체의 주소가 함수에 전달된다.
int main(void)
{
struct namect person;
getinfo(&person); // ①
makeinfo(&person); // ②
showinfo(&person); // ③
return 0;
}
① getinfo() 함수는 그 자체에서 main()에 정보를 전달한다.
void getinfo (struct namect * pst)
{
printf("이름을 입력하세요.\n");
s_gets(pst->fname, NLEN);
printf("성씨를 입력하세요.\n");
s_gets(pst->lname, NLEN);
}
- 사용자로부터 이름과 성씨를 얻어, person 구조체에 넣는다.
- 이때 person 구조체를 pst 포인터를 사용하여 찾는다.
- pst->lname은 pst가 가리키는 구조체의 lname 멤버를 의미하며, 이것을 char형 배열 이름과 동등한 것으로 만든다.
그러므로 gets()의 인자로 전달할 수 있다. - getinfo()가 main()에 정보를 제공하고 있지만, 이 함수는 return문 방식을 취하지 않는다.
② makeinfo() 함수는 양방향 정보 전달을 수행한다.
void makeinfo (struct namect * pst)
{
pst->letters = strlen(pst->fname) +
strlen(pst->lname);
}
- 이 함수는 person을 가리키는 포인터를 사용하여, 그 구조체에 저장된 이름과 성씨를 찾는다.
- 이 함수는 C 라이브러리 함수 strlen()을 사용하여, 이름과 성씨를 이루는 글자 수를 각각 구하여 더한 후, person의 주소를 사용하여 그 합을 저장한다.
- 이 함수도 마찬가지로 void형이다. return하지 않는다.
③ showinfo() 함수는 포인터를 사용하여 출력할 정보를 찾는다.
void showinfo (const struct namect * pst)
{
printf("%s %s, 당신의 이름은 %d개의 글자를 가지고 있습니다. \n\n",
pst->fname, pst->lname, pst->letters);
}
- 이 함수가 배열의 내용을 변경시키면 안 되기 때문에 그 포인터는 const로 선언한다.
문제09
- 문제08에서는 하나의 구조체 변수person이 있고, 각 함수는 구조체의 주소를 사용하여 person에 접근한다.
- 전달인자와 리턴값으로 구조체를 사용함으로써, 포인터를 사용하지 않고 구조체를 다루는 방법을 이용한다.
- 구조체 자체를 전달하기 위해, 전달인자로 &person 대신에 person을 사용한다.
대응하는 형식매개변수는 구조체를 가리키는 포인터 대신에 struct name형으로 선언한다. - main()에 구조체 값을 제공하기 위해, 구조체를 리턴할 수 있다.
- 구조체 자체를 전달하기 위해, 전달인자로 &person 대신에 person을 사용한다.
/* 구조체 자체를 전달하고 리턴한다 */
#include <stdio.h>
#include <string.h>
#define NLEN 30
struct namect {
char fname[NLEN];
char lname[NLEN];
int letters;
};
struct namect getinfo(void);
struct namect makeinfo(struct namect);
void showinfo(struct namect);
char * s_gets(char * st, int n);
int main(void)
{
struct namect person;
person = getinfo();
person = makeinfo(person);
showinfo(person);
return 0;
}
struct namect getinfo(void)
{
struct namect temp;
printf("이름을 입력하세요.\n");
s_gets(temp.fname, NLEN);
printf("성씨를 입력하세요.\n");
s_gets(temp.lname, NLEN);
return temp;
}
struct namect makeinfo(struct namect info)
{
info.letters = strlen(info.fname) + strlen(info.lname);
return info;
}
void showinfo(struct namect info)
{
printf("%s %s, 당신의 이름은 %d개의 글자를 가지고 있습니다. \n\n",
info.fname, info.lname, info.letters);
}
char * s_gets(char * st, int n)
{
char * ret_val;
char * find;
ret_val = fgets(st, n, stdin);
if (ret_val)
{
find = strchr(st, '\n'); // 개행을 찾는다
if (find) // 주소가 NULL이 아니면,
*find = '\0'; // 널문자를 거기에 놓는다.
else
while (getchar() != '\n')
continue; // 행의 나머지를 처리한다.
}
return ret_val;
}
# 실행 결과
- 세 함수들이 제각기 person의 복사본을 만든다. 그래서 프로그램은 하나가 아니라 서로 구별되는 4개의 구조체를 사용한다.
- makeinfo() 함수를 살펴보면, 문제08에서는 person의 주소가 전달되었다. 그리고 그 함수는 person의 실제 값으로 작업했다.
struct namect person;
[문제08] makeinfo(&person); → void makeinfo (struct namect * pst)
[문제09] person = makeinfo(person); → struct namect makeinfo(struct namect info)
struct namect makeinfo(struct namect info)
{
info.letters = strlen(info.fname) + strlen(info.lname);
return info;
}
- 이 버전에서는 info라는 이름의 새로운 구조체가 생성된다. person에 저장되어 있는 값들이 info에 복사되고,
그 함수는 그 복사본을 가지고 작업한다. 그러므로, 총 글자수가 계산되었을 때, 그것은 person이 아니라 info에 저장된다. - return info; 문은 main()에 있는 대입문과 결합하여 info에 저장된 값들을 person으로 복사한다.
- makeinfo() 함수는 구조체를 리턴하기 때문에 struct namect형으로 선언되었다.
typedef : 자료형 별명 만들기
- 구문 형식
typedef <type_name> <type_new_name>;
- 복잡한 type 이름을 간단히 표시하기 위해 주로 사용한다.
- line 3는 unsigned int라는 type에 uint32라는 새로운 별명을 부여한다.
- 포인터에도 적용 가능 : line 4는 int *에 pint32는 별명을 부여한다.
- 배열에 대해서도 적용 가능, 단 작성 방식에 유의해야 한다.
- line 5는 배열 요소 수가 3개인 정수 배열에 aint3이라는 별명을 부여한다. .
int main(void)
{
typedef unsigned int uint32;
typedef int * pint32;
typedef int aint3[3];
unsigned int a;
uint32 b; // T(a) == T(b)
int * pa;
pint32 pb; // T(pa) == T(pb)
int aa[3];
aint3 bb; // T(aa) == T(bb)
return 0;
}
typedef와 구조체 자료형
- 구조체 자료형 변수 선언
- 구조체 이름 앞에 struct 덧붙여 변수를 선언하므로 번거롭고 코드도 복잡해 보인다.
- typedef로 구조체 자료형에 간단한 이름 부여하면 복잡한 자료형을 간단한 이름으로 표현할 수 있다.
- 구조체 자료형 변수/매개 변수가 자주 쓰일 경우 쓰이는 기법이다.
#include <stdio.h>
typedef struct student
{
int id;
char *pname;
double points;
} STUD;
void stud_printx(STUD s)
{
printf("[%d:%s] = %lf\n", s.id,
s.pname, s.points);
}
int main(void)
{
STUD s1; // 둘은 같은 자료형이며,
struct student s2; // 선언 시 사용한 자료형 이름만 다르다.
s1.id = 1;
s1.pname ="Choi";
s1.points = 9.9;
s2.id = 2;
s2.pname = "Park";
s2.points = 0.1;
stud_printx(s1);
stud_printx(s2); // 자료형이 맞지 않다는 에러가 발생하지 않는다.
return 0;
}
// 위 코드와 동일한 결과를 가지지만, 위 코드의 3~8 라인이 더 간결하다.
struct student
{
int id;
char *pname;
double points;
};
typedef struct student STUD;
'C언어 > 스터디' 카테고리의 다른 글
스터디 - c언어 (안)간단한 프로그램 만들기 (0) | 2024.08.14 |
---|---|
스터디 - c언어 과제 (0) | 2024.08.14 |
ch11 문자열과 문자열 함수 (0) | 2024.08.13 |
ch11 문자열 p.551~ (0) | 2024.08.12 |
ch10 배열과 포인터 (0) | 2024.08.11 |