비슷한 형태의 데이터를 관리하려면?
사람 5명의 나이, 키 그리고 몸무게를 관리하는 프로그램을 만든다고 생각해 보자. 다음과 같이 5명의 나이, 키, 몸무게를 별도의 변수로 선언하면 5 x 3 = 15개의 변수가 필요하다.
int age1, age2, age3, age4, age5; /*5명의 나이를 저장할 변수*/
float height1, height2, height3, height4, height5; /*5명의 키를 저장할 변수*/
float weight1, weight2, weight3, weight4, weight5; /*5명의 몸무게를 저장할 변수*/
그리고 5명의 나이, 키, 몸무게를 사용자에게 입력 받으려면 변수마다 각각 하나씩 입력 받아야 하기 때문에 다음과 같은 작업을 해야 한다.
scanf("%d",&age1); /*순서대로 나이를 입력 받음*/
scanf("%d",&age2);
scanf("%d",&age3);
scanf("%d",&age4);
scanf("%d",&age5);
그런데 이렇게 일일이 입력 받다보면 작성해야 하는 코드가 많아지고, 나중에 코드를 변경하거나 다른 데이터를 추가할 때 문제가 될 수 있다.
데이터의 그룹화 1 : 배열
앞에서 배운 배열을 사용하면 같은 형태의 데이터를 묶어서 관리할 수 있다. 위에서 선언한 변수들을 배열을 사용해서 다시 선언해 보겠다. 다음과 같이 선언하면 변수를 개별적으로 선언하는 것보다 코드가 훨씬 간단해진다.
int age[5];
float height[5];
float weight[5];
그리고 배열은 각 요소에 값을 대입하거나 읽을 때 색인을 사용한다. 색인은 상수 뿐만 아니라 변수도 사용할 수 있기 때문에 반복문을 적용하여 데이터를 좀 더 편리하게 관리할 수 있다. 예를 들어 사용자에게 5명의 나이를 입력 받는 코드를 반복문을 사용해서 구성해 보면 다음과 같다.
int i;
for(i = 0; i < 5; i++) scanf("%d", age + i); /*5번 반복하면서 나이를 입력받음*/
변수를 개별적으로 사용할 때는 scanf 함수를 5번 사용했는데, 데이터를 그룹으로 묶어서 사용하면 반복문에서 scanf 함수를 한번만 적으면 되기 때문에 코드를 작성하기가 훨씬 편리해진다. 이처럼 같은 형식의 데이터를 여러개 처리할 때는 배열처럼 그룹으로 묶어서 관리하는 것이 좀 더 바람직한 프로그래밍 방법이다.
배열의 한계
배열은 크기가 같은 데이터만 그룹으로 묶을 수 있다. 따라서 int형인 나이끼리 묶거나 float형인 키 또는 몸무게끼리 묶는 것처럼 같은 종류의 데이터를 묶을 때 많이 사용한다. 그런데 실제 프로그램에서 이러한 형태로 묶는 방법을 자주 사용할까? 나이, 키, 몸무게 같은 데이터는 결국 한 사람이 가지고 있는 정보이다. 그래서 특정 사람의 정보를 모아서 하나의 그룹으로 만드는 것이 더 좋다. 왜냐하면 결국 프로그램은 데이터를 입력 받거나 출력할 때 사람 단위로 처리할 확률이 높기 때문이다. 다음 출력 형식을 비교해 보면 이 내용을 이해할 수 있다.
요소별 출력 | 개인별 출력 |
<나이> 신현준님의 나이는 38세입니다 마재승님의 나이는 30세입니다 김현철님의 나이는 25세입니다 이연진님의 나이는 20세입니다 박진희님의 나이는 15세입니다 <키> 신현준님의 키는 189cm입니다. ... ... |
<신현준>님의 정보입니다. 나이 : 38세 키 : 189cm 몸무게 : 87kg <마재승>님의 정보입니다. 나이 : 30세 ... ... ... |
데이터의 구룹화 2 : 구조체
C언어는 크기나 형식이 다른 데이터를 그룹으로 묶어 사용할 수 있도록 '구조체'문법을 제공한다. 구조체는 기본 자료형이나 사용자가 정의한 자료형을 그룹으로 묶어서 새로운 자료형을 만들 수 있다. 그래서 구조체는 다양한 형태의 메모리 구조를 만들 수 있다.
구조체로 새로운 자료형 만들기
구조체는 아래와 같은 형식으로 새로운 자료형을 정의한다. 구조체 문법을 사용한다고 컴파일러에 알리기 위해 struct키워드로 시작하며 그 다음에 구조체 이름을 적는다. 이 이름은 우리가 새로 정의할 자료형의 이름(int, char 같은 역할)이다. 그리고 중괄호 { }안에 이 자료형을 구성할 요소들을 변수를 선언하듯 나열해 주면 된다.
struct 구조체 이름 { 자료형1 변수 이름1; 자료형2 변수 이름2; 자료형3 변수 이름3; } |
예를 들어 구조체 문법을 사용하여 이름(name), 나이(age), 키(height), 몸무게(weight) 정보를 담고있는 people이라는 새로운 자료형을 만들면 다음과 같다.
struct People
{
char name[12]; /*이름 12바이트*/
unsigned short int age; /*나이, 2바이트*/
float height; /*키, 4바이트*/
float weight; /*몸무게, 4바이트*/
}
구조체로 만든 자료형으로 변수 선언하기
이제 나만의 새로운 자료형인 People가 만들어졌다. 구조체로 만든 자료형의 크기는 {}안에 선언한 요소들의 크기를 모두 더한 것과 같다. 따라서 People 자료형의 크기는 22바이트(12+2+4+4)이다. 그리고 우리가 지금까지 사용했던 자료형처럼 다양한 형태의 변수를 선언할 수 있다.
struct People data; /*일반 변수 : data 변수의 크기는 22바이트*/
struct People friend_list[64]; /*배열 변수 : friend_list 변수의 크기는 22 x 64 바이트*/
struct People *p; /*포인터 변수 : p 변수의 크기는 4바이트 (주소 값 저장)*/
그런데 struct 문법을 사용해서 만든 자료형은 일반 자료형과 달리 변수를 선언할 때 struct 키워드를 반드시 붙여야 하는 불편함이 있다. 이러한 불편함을 해결하고 구조체 변수를 선언하는 작업을 간편하게 하기 위해 앞에서 배운 typedef 문법을 사용할 수 있다.
struct People
{
char name[12]; /*이름 12바이트*/
unsigned short int age; /*나이, 2바이트*/
float height; /*키, 4바이트*/
float weight; /*몸무게, 4바이트*/
}
typedef struce People Person; /*typedef를 사용해 Person이라는 새로운 자료형을 정의함*/
위와 같이 typedef를 사용하여 struct People 자료형을 Person자료형으로 재정의하면 매번 struct 키워드를 적지 않고도 변수들을 편리하게 선언할 수 있다.
Person data; /*struct People data;*/
Person friend_list[64]; /*struct People friend_list[64];*/
Person *p; /*struct People *p;*/
struct와 typedef를 조합해서 구조체 변수를 선언하는 방법
struct와 typedef는 둘 다 자료형을 정의하는 문법이다. 다음처럼 조합해 문법을 표현하면 코드가 훨씬 간단해지고 의미도 좀 더 확실하게 부여할 수 있다.
struct와 typedef를 따로 선언 | struct와 typedef를 조합해서 사용 |
struct People { char name[12]; /*이름 12바이트*/ unsigned short int age; /*나이, 2바이트*/ float height; /*키, 4바이트*/ float weight; /*몸무게, 4바이트*/ }; typedef struct People Person; |
typedef struct People { char name[12]; /*이름 12바이트*/ unsigned short int age; /*나이, 2바이트*/ float height; /*키, 4바이트*/ float weight; /*몸무게, 4바이트*/ }Person; |
또한 struct와 typedef를 조합해서 새로운 자료형을 선언하는 형식에서는 오른쪽처럼 구조체 이름인 People이 없더라도 Person만으로 충분히 사용할 수 있다.
typedef struct
{
char name[12];
unsigned short int age;
float height;
float weight;
} person;
구조체로 선언한 변수의 요소 사용하기
배열을 사용해서 그룹으로 묶은 데이터는 각 요소의 크기가 같기 때문에 색인 개념을 사용할 수 있다./ 하지만 구조체로 묶인 데이터는 각 요소의 크기가 같지 않기 때문에 사용할 요소의 이름을 직접 적어주어야 한다. 그래서 오른쪽과 같이 구조체로 선언한 변수는 연산자와 자신이 사용할 요소의 이름을 함께 적어서 사용한다.
구조체 변수 이름 . 사용할 요소 |
typedef struct People
{
char name[12];
unsigned short int age;
float height;
float weight;
} Person;
void main()
{
Person data; /*Person 자료형으로 data 변수를 선언함*/
data.age = 21; /*data변수 안의 age에 값 21을 대입함*/
data.height = 178.3; /*data 변수 안의 height에 값 178.3을 대입함*/
}
위의 예제에서 data 변수는 Person 자료형으로 선언된 구조체이며 name, age, height, weight를 구조체의 요소로 갖는다. 따라서 총 22바이트 크기의 메모리가 할당된다. 그리고 구조체의 각 요소가 메모리에 나열되는 순서는 구조체 내부 요소를 선언한 순서와 같다. 따라서 메모리에는 name 요소가 12바이트 크기로 가장먼저 놓이며 age 요소가 2바이트 크기로 그 다음에 놓이고 height 요소와 weight 요소가 각각 4바이트로 그 다음 순서에 놓이게 된다.
지금까지 설명한 구조체 문법을 간단하게 예제로 구성해서 실습해 보겠다. 다음 예제는 구조체 문법을 사용하여 이름, 나이, 키, 몸무게를 저장할 수 있는 People(Person) 자료형을 정의한다. 그리고 표준 입력 함수를 사용해서 사람의 신체정보를 입력받아서 출력해주는 예제이다.
그리고 다음과 같이 구조체 자료형인 Person으로 배열 변수를 선언한 경우에도 각 요소에 접근하는 방식은 같다. 먼저 자신이 변경하려는 배열 요소의 색인을 []안에 적는다. 그리고 선택한 배열 요소가 Person자료형이기 때문에 .(요소지정) 연산자를 사용해 구조체에 포함된 요소를 사용하면 된다.
Person friends[3]; /*Person 데이터 3개를 저장할 수 있는 메모리를 할당함*/
friend[1].age = 22; /* 두 번째 요소의 age에 값 22를 대입함*/
구조체로 선언한 변수를 포인터로 사용하기
다음과 같이 Person 형으로 선언한 변수의 주소를 Person *형 선언한 포인터 변수에 저장해서 사용할 수도 있다.
Person data; /*Person 자료형으로 data 변수를 선언함*/
Person *p; /*Person 형식으로 선언한 메모리에 접근할 수 있는 포인터를 선언함*/
p = &data; /*포인터 변수 p는 data 변수의 주소 값을 저장함*/
(*p).age = 23; /*p에 저장된 주소에 가서 age 요소에 값 23을 대입함*/
구조체 내부 요소에 접근하려면 *연산자를 사용해서 data 변수의 주소로 이동한 다음.(요소지정) 연산자를 사용해야 한다. 그런데 * 연산자가 . 연산자보다 연산자 우선순위가 낮아서 *p.age = 23;처럼 사용하면 오류가 발생한다.
따라서 * 연산자가 먼저 수행되도록 괄호 ()를 사용해서 (*p).age = 23; 형태로 명령문을 구성해야 한다.
연산자 우선순위 문제를 해결하는 -> 연산자
하지만 구조체로 선언한 변수를 포인터 문법으로 사용할 때마다 (*p) 형태를 사용한다면 불편하고 귀찮다.
구조체 문법은 이러한 연산자 우선순위 문제를 핵셜할 수 있는 -> 연산자를 추가로 제공한다. 다음과 같이 -> 연산자를 사용하면 * 연산자와 . 연산자를 각각 사용하지 않고 하나의 연산자로 사용하기 때문에 연산자 우선순위 문제가 자연스럽게 해결된다.
Person data;
Person *p;
p = &data;
p-> age = 23;
구조체 문법으로 선언한 변수의 초기화 방법
배열로 선언한 변수는 데이터가 그룹으로 묶인 형태이기 때문에 초깃값을 대입할 때 다음처럼 중괄호{}를 사용했다.
int data[3] = {1, 2, 3}; /*배열의 각 요소에 순서대로 1, 2, 3값이 대입됨*/
배열과 마찬가지로 구조체 문법도 데이터를 묶는 형태이기 때문에 같은 형식으로 초깃값을 대입하면 된다. 예를 들어 다음과 같이 people 구조체를 선언했을 때 이 구조체 변수를 초기화 하려면 중괄호를 사용하여 초깃값을 적어주면 된다.
struct People
{
char name[5];
unsigned short int age;
float height;
flodat weight;
};
void main()
{
/*구조체로 선언한 변수를 초기화함*/
struct People data = {"홍길동", 51, 185.6, 86.2};
}
여기에서는 main 함수에서 People 구조체로 data 변수를 선언하면서 name에 "홍길동", age에 51, height에 185.6, weight에 86.2로 각 요소를 초기화 했다.
구조체 변수를 초기화 할 때 주의할 점은 구조체 내부에 선언한 변수의 순서와 초깃값의 순서가 같아야 한다는 것이다.
'C언어' 카테고리의 다른 글
15-2)구조체로 만든 자료형의 크기 (0) | 2021.10.05 |
---|---|
15-1) 배열과 구조체 (1) | 2021.09.27 |
14) 구조체와 연결 리스트 (0) | 2021.07.12 |
13-2) 2차원 포인터와 2차원 배열 (1) | 2021.06.29 |
13) 다차원 포인터 (0) | 2021.06.01 |