프로그래밍 언어/C언어

C언어 17. 응용자료형

닉네임못짓는사람 2020. 7. 23. 00:45
반응형

이번 글에선 응용 자료형에 대해서 알아보도록 하겠습니다.

응용 자료형엔 여러 가지 종류가 있는데, 이번 글에선 구조체, 공용체, 열거형에 대해 알아보겠습니다.

여러 자료형을 포함하는 자료형?


먼저 구조체란 여러개의 변수를 묶어서 저장, 처리할 수 있도록 해주는 것을 이야기합니다.

기본적으로 배열과 흡사한데, 서로 어떻게 다른지 알아보도록 하겠습니다.

 

배열의 경우에는 배열의 타입에 맞는 데이터들을 저장할 때 사용합니다.

반면 구조체의 경우에는 저장할 데이터들의 타입들이 서로 달라도 사용 가능합니다.

예를 들어 배열과 구조체를 각각 컨테이너 박스라고 하고, 변수를 상자라고 생각해봅시다.

배열의 경우에는 과일상자만 담을 수 있는 컨테이너 박스이고,

구조체는 과일, 생선, 야채 등등을 여러가지 종류를 담을 수 있는 컨테이너 박스입니다.

이러한 구조체는 왜 사용하는 것일까요?

구조체는 왜 사용할까?


예를들어 주민들의 이름, 나이, 주민등록번호 등등을 저장해야 하는 경우를 생각해봅시다.

이들을 저장하려면 먼저 char형과 int형의 배열들을 생성하고 데이터들을 저장해야 할 것입니다.

이런 경우 모든 데이터가 각각 다른 배열에 저장되어 있기 때문에 이 데이터들을 가지고

뭔가 작업을 하기는 상당히 불편할 것입니다.

이름순으로 데이터를 정렬하려고 할 때, 이를 모든 배열에 각각 수행해야 하는 경우 등이 있죠.

 

이런 문제점을 해결할 수 있는 것이 바로 구조체입니다.

구조체는 기본적으로 C언어 내에서의 모든 기본 자료형과 응용 자료형을 포함시킬 수 있습니다.

우리가 흔히 사용하는 정수형, 실수형부터 시작해 포인터 변수, 심지어 구조체조차 포함시킬 수 있죠.

따라서 하나의 구조체에 위의 데이터들을 모두 묶어서 저장하고, 사용할 수 있습니다.

 

그럼 이러한 구조체를 만들고, 사용하는 법에 대해서 알아보도록 합시다.

구조체를 사용하기 위해선 먼저 자신이 사용하려는 구조체의 형태를 지정해 주어야 하는데,

이를 구조체의 형 선언이라고 합니다.

구조체의 형 선언


배열은 배열 선언시에 바로 메모리 공간이 할당되는 반면,

구조체는 형 선언을 하고, 구조체 변수를 선언하면 비로소 메모리 공간이 할당됩니다.

이러한 형 선언을 통해 프로그램에 새로운 자료형을 선언하는 것이라고 생각할 수 있습니다.

기본적인 형 선언 방법을 위의 예시에 맞춰 사용할 수 있도록 선언해보도록 합시다.

struct resident {
	char *name;
	int age;
	char *resi_Num;
};

구조체의 형 선언을 할 때에는 struct라는 예약어를 사용합니다.

resident는 구조체의 이름이며, 보통 태그명이라고도 부릅니다.

구조체 내에서 name, age등 들어갈 데이터들의 형태를 지정해주는데,

흡사 변수를 선언하는 것 같지만 위에서 말했듯이 형 선언 시엔 메모리 공간이 할당되지 않습니다.

 

이러한 형태를 기본으로 구조체 변수를 선언할 수 있으며,

변수 선언시 배열과 동일하게 메모리 공간에서 연속적인 공간을 차지합니다.

구조체 변수를 선언하는 방법은 다음과 같습니다.

struct resident s1;

이제 이 구조체 변수 내의 자료형들에 접근하는 방법에 알아보도록 합시다.

이들을 멤버라고 이야기 하며, 이 멤버에 접근하는 것을 참조라고 이야기합니다.

멤버 참조 연산자(.)로 구조체 멤버에 접근하자


이를위해선 멤버 참조 연산자(.)를 사용해주어야 합니다.

코드를 통해 구조체의 멤버를 참조하고, 값을 저장, 출력하는 방법을 알아보도록 하겠습니다.

#include <stdio.h>

struct resident {
	char *name;
	int age;
	char *resi_Num;
};

int main() {
	struct resident s1;

	s1.name = "홍길동";
	s1.age = 20;
	s1.resi_Num = "010816-1251611";

	printf("이름 : %s\n", s1.name);
	printf("나이 : %d\n", s1.age);
	printf("주민등록번호 : %s\n", s1.resi_Num);

	return 0;
}

또한 위와같이 구조체 각각 멤버를 참조하여 값을 변경하는 것 외에도

구조체 변수를 선언할 때 각 멤버들의 값들을 입력해 줄 수도 있습니다. 마치 배열처럼 말이죠.

struct resident s1 = {"홍길동", 20, "010816-1251611"};

혹은 이 모든것들을 한 번에 묶어서 처리할 수도 있습니다.

struct resident {
	char *name;
	int age;
	char *resi_Num;
}s1 = {"홍길동", 20, "010816-1251611"};

이때 주의해야 할 점은 구조체 변수 s1의 경우 main함수의 밖에서 선언되었기 때문에

초기화를 별도로 하지 않으면 모든값이 자동으로 0으로 채워지게 됩니다.

 

이처럼 구조체를 통해서 서로다른 형태의 자료들을 묶어서 관리할 수 있습니다.

구조체는 앞서 말했듯이 새로운 자료형을 선언하는 것이라고도 할 수 있기 때문에,

함수의 전달 인자로 넘겨줄 수도 있고, 반환값으로 받을 수도 있습니다.

코드를 통해서 사용법을 숙지해보도록 합시다.

#include <stdio.h>

struct student {
	char* name;
	int age;
	char grade;
};

struct student returnStruct(struct student st1);

int main() {
	struct student st1 = { "홍길동", 18, 'A' };

	printf("이름 : %s, 나이 : %d, 성적 : %c\n", st1.name, st1.age, st1.grade);
	st1 = returnStruct(st1);
	printf("이름 : %s, 나이 : %d, 성적 : %c", st1.name, st1.age, st1.grade);
    	return 0;
}

struct student returnStruct(struct student st1_T) {
	st1_T.name = "장발장";
	st1_T.age = 17;
	st1_T.grade = 'B';
	return st1_T;
}

 

포인터를 사용해 구조체에 접근하자


또는 반환값이 아닌 포인터를 사용해서 값을 변경할 수도 있을 것입니다.

#include <stdio.h>

struct student {
	char* name;
	int age;
	char grade;
};

void pointerStruct(struct student *st1);

int main() {
	struct student st1 = { "홍길동", 18, 'A' };
	struct student* st1_P = &st1;

	printf("이름 : %s, 나이 : %d, 성적 : %c\n", st1.name, st1.age, st1.grade);
	pointerStruct(st1_P);
	printf("이름 : %s, 나이 : %d, 성적 : %c", st1.name, st1.age, st1.grade);
    	return 0;
}

void pointerStruct(struct student *st1_P) {
	(*st1_P).name = "장발장";
	(*st1_P).age = 17;
	(*st1_P).grade = 'B';
}

구조체 포인터를 사용할 때 사용할 수 있는 것으로 간접 멤버 참조 연산자라는 것이 있습니다.

위에선 (*st1_P).name과 같이 사용하면 간접 멤버 참조 연산자 ->를 사용하면,

st1_P -> name 과 같은 형태로 사용할 수 있습니다.

st1_P -> name = "장발장";
st1_P -> age = 17;
st1_P -> grade = 'B';

그럼 이제 다시 처음으로 돌아가서 여러명의 사람의 정보를 구조체에 저장하기 위해선

어떻게 해야 할까요?

그 방법은 바로 구조체 배열을 사용하는 것입니다.

구조체로 배열을 만들 수 있다


구조체는 하나의 자료형이기 때문에 당연히 배열에도 사용이 가능할 것입니다.

구조체 배열을 사용해서 세명의 학생의 정보를 저장하도록 코드를 작성해봅시다.

#include <stdio.h>

struct student {
	char* name;
	int age;
	char grade;
};

int main() {
	struct student st_L[3] = { { "홍길동", 18, 'A' },
							{"장발장", 17, 'C'},
							{"김우주", 19, 'D'}
	};
	struct student st_T;
	int size = sizeof(st_L) / sizeof(st_L[0]);
	int i, j;
	
	for (i = 0; i < size; i++) {
		printf("이름 : %s, 나이 : %d, 성적 : %c\n", st_L[i].name, st_L[i].age, st_L[i].grade);
	}
	printf("나이 오름차순 정렬\n");
	for (i = 0; i < size-1; i++) {
		for (j = i; j < size; j++) {
			if (st_L[i].age > st_L[j].age) {
				st_T = st_L[i];
				st_L[i] = st_L[j];
				st_L[j] = st_T;
			}
		}
	}
	for (i = 0; i < size; i++) {
		printf("이름 : %s, 나이 : %d, 성적 : %c\n", st_L[i].name, st_L[i].age, st_L[i].grade);
	}
	return 0;
}

위의 코드는 학생 세명의 정보를 구조체 배열에 저장한 뒤,

이를 나이순으로 오름차순 정렬하는 코드입니다.

구조체를 사용하지 않았다면 정렬 시에 모든 배열의 데이터를 일일이 바꿔야 했었겠지만,

위와 같이 구조체를 사용하면 데이터를 효율적으로 관리하고 사용할 수가 있습니다.

 

그럼 이번엔 구조체와 같은 형태의 응용 자료형이며,

모든 멤버가 하나의 메모리 공간을 공유하는 공용체에 대해 알아보도록 하겠습니다.

공용체


공용체의 선언 키워드는 union이며, 그 외의 다른 부분은 구조체 선언 형식과 같습니다.

union student {
	char* name;
	int age;
	char grade;
};

구조체는 연속적인 메모리 공간에서 각 멤버가 따로 공간을 차지하지만,

공유체의 경우엔 모든 멤버가 하나의 멤버를 공유합니다.

이 공간의 크기는 멤버 중 크기가 가장 큰 멤버의 크기로 결정됩니다.

이 부분 외에는 구조체와 공용체는 특별히 다른 부분은 없습니다.

즉, 기본적인 사용방법은 동일하다는 이야기죠.

 

공용체 사용 시 주의해야 할 점은, 모든 멤버가 하나의 기억공간을 같이 사용하기 때문에

다른 멤버에 의한 데이터 변질의 위험이 있다는 것입니다.

때문에 사용 시에 항상 저장된 데이터의 형태를 파악하면서 적절한 멤버를 참조해야 합니다.

이런 세심한 주의가 필요하지만, 공유체를 사용하면 메모리 공간을 절약할 수 있습니다.

특히 하나의 기억공간에 저장된 데이터를 여러 가지 형태로 사용해야 할 때 적절합니다.

코드로 사용법을 확인해봅시다.

#include <stdio.h>

union student {
	char* name;
	int age;
	char grade;
};

int main() {
	union student st1 = { "홍길동" };
	printf("이름 : %s\n", st1.name);
	st1.age = 11;
	printf("나이 : %d\n", st1.age);
	printf("이름 : %s\n", st1.name);
}

위와 같이 코드를 작성하면 나이를 출력하는 줄까진 문제가 없습니다.

하지만 바로 그다음 이름을 다시 출력하는 부분에서 바로 에러가 발생할 것입니다.

그 이유는 st1의 age를 수정하면서 name이 가리키고 있는 포인터에 데이터 변질이 일어나

허용되지 않은 메모리 공간을 가리키고 있기 때문입니다.

실제로 이 값을 출력해서 확인해보도록 합시다.

#include <stdio.h>

union student {
	char* name;
	int age;
	char grade;
};

int main() {
	union student st1 = { "홍길동" };
	printf("이름 : %u\n", st1.name);
	st1.age = 11;
	printf("나이 : %d\n", st1.age);
	printf("이름 : %u\n", st1.name);
	return 0;
}

결과를 확인해보시면 첫 줄엔 st1.name이 가리키고 있는 메모리 공간의 주소값이 출력되지만,

세 번째 줄에선 그 값이 11로 바뀐 것을 볼 수 있습니다.

포인터는 메모리 공간에서 4바이트 크기를 차지하고, int형 변수 age또한 4바이트 크기를

차지하기 때문에 이 공간이 모두 변경되어 11이 출력된 것입니다.

이렇듯 공유체를 사용할 땐 세심한 주의가 필요합니다.

 

다음은 열거형에 대해서 알아보도록 하겠습니다.

열거형


열거형은 주로 switch문과 함께 사용할 때 상당히 유용합니다.

열거형의 선언 키워드는 enum이며 선언 방법은 다음과 같습니다.

enum fruit{apple, melon, banana, cherry};

열거형은 간단하게 숫자에 이름을 붙여준다고 생각하면 쉽습니다.

무슨 뜻이냐 하면, 열거형의 중괄호 안에 선언한 이름들은 실제 컴파일러가 정수형 상수로 취급하며

앞에서부터 0, 1, 2...의 순서를 가집니다.

 

따라서 위의 열거형은 apple = 0, melon = 1과 같은 형태란 이야기죠.

때문에 switch문과 함께 사용하기에 편하다는 겁니다.

코드를 통해서 사용법을 알아보도록 하겠습니다.

#include <stdio.h>

enum fruit{apple, melon, banana, cherry};

int main() {
	enum fruit fr;
	fr = melon;
	switch (fr) {
		case apple:
			printf("과일 : apple\n");
			break;
		case melon:
			printf("과일 : melon\n");
			break;
		case banana:
			printf("과일 : banana\n");
			break;
		case cherry:
			printf("과일 : cherry\n");
			break;
	}
	printf("melon의 값 : %d\n", melon);
	return 0;
}

위와 같이 switch문과 함께 유용하게 사용할 수 있습니다.

또한 열거형 멤버들의 값을 직접 지정해 줄 수도 있습니다.

enum fruit{apple=2, melon, banana=10, cherry=16};

이렇게 하면 apple은 2의 값을 가지며, melon은 2에서 1을 더한 3이 되고

나머지도 뒤에 지정한 숫자와 동일한 값을 가지게 됩니다.

 

구조체, 공용체, 열거형과 같은 응용 자료형의 변수를 선언할 때에는

키워드가 struct 구조체명 변수명 과 같은 형태로 선언문이 길어지게 됩니다.

이 키워드를 줄일 수 있다면 매우 편하게 사용할 수 있지 않을까요?

typedef로 재정의하자


이런 때 사용하는 것이 typedef문이며, 이를 사용해 자료형의 이름을 재정의 할 수 있습니다.

typedef의 사용법은 다음과 같습니다.

struct student {
	char* name;
	int age;
	char grade;
};

typedef struct student Student;

이렇게 사용하면 이제부턴 strcut student는 student로 대체하여 사용할 수 있게 됩니다.

간단한 코드로 사용법을 알아보도록 합시다.

#include <stdio.h>

struct student {
	char* name;
	int age;
	char grade;
};

typedef struct student Student;

int main() {
	Student st1 = { "홍길동", 17, 'A' };
	st1.name = "장발장";
	st1.age = 15;
	st1.grade = 'B';
	printf("이름 : %s, 나이 : %d, 성적 : %c", st1.name, st1.age, st1.grade);
}

예제 자체는 typedef를 사용해 struct student를 student로 대체한 부분을 제외하면

구조체 사용법을 알아봤을 때와 거의 동일합니다.

 

이상으로 C언어에서 사용하는 응용 자료형인 구조체, 응용체, 열거형에 대한 설명을 마치겠습니다.

다음 글에선 파일 입출력에 대해서 알아보도록 하겠습니다.

반응형