데브코스

[10주차 - DAY4] 사용자 정의 자료형, 동적 할당, 객체 지향

미안하다 강림이 좀 늦었다 2024. 5. 2. 13:47

 

 

함수 포인터

함수도 변수에 담아서 사용할 수 있다.

실행 시(런타임)에 어떤 함수를 가리키는지 결정되기 때문에 동적 바인딩 된다. 또한, 함수 포인터는 callback 함수를 구현할 수 있게 해 준다.

 

아래 코드의 main 함수에서 pFunc가 함수 포인터이며, add 함수를 pFunc라는 포인터 변수에 담아서 사용하고 있다.

int add(int a, int b) {
	return a + b;
}

int main(void) {
	int a = 10, b = 20, result = 0;
	int (*pFunc)(int a, int b); // 함수 포인터 선언

	pFunc = add; // 동적 바인딩

	result = pFunc(a, b);

	printf("a + b = %d", result);

	return 0;
}

 

 

구조체

하나 이상의 서로 다른 종류의 변수들을 묶은 데이터 타입이다.

struct structName {
	int member1;
	char member2[10];
};
  • struct 키워드: 구조체라는 데이터 타입을 의미
  • structName: 구조체의 이름
  • member: 구조체 멤버

연관된 변수들을 하나로 묶어서 관리하기 때문에 데이터 관리에 유용하며, 변수의 개수가 많아지면 구조체를 사용하는 것이 유리하다.

예를 들어, 학생의 정보(이름, 나이, 생년월일)을 저장한다고 할 때, 각 정보마다 변수를 선언하는 것보다 하나의 구조체에 변수들을 모아두는 것이 낫다.

또한, 구조체도 자료형이기 때문에 배열로 만들 수 있다.

 

아래 코드에서는 Student라는 구조체를 정의했다. main 함수에서는 구조체 배열을 생성하여 초기화하고, 각 구조체 멤버들을 (.) 연산자로 직접 접근하여 출력한다.

struct Student {
	char name[10];
	int age;
	char birth[7];
};

int main(void) {
	struct Student st[] = { 
		{ "김철수", 24, "010512" }, 
		{ "이철수", 22, "031225" }, 
		{ "최철수", 26, "990129" } 
	};

	for (int i = 0; i < 3; i++) {
		printf("이름: %s, 나이: %d, 생년월일: %s\n", st[i].name, st[i].age, st[i].birth);
	}

	return 0;
}

 

 

공용체

공용체도 사용자 정의 자료형이다.

위 그림과 같이 공용체는 메모리 공간을 공유한다는 점에서 구조체와 차이가 난다.

공용체를 사용하는 이유는 특정 공간을 효율적으로 관리하기 위함이다.

union unionName
{
	char a;
	int b;
	double c;
};
  • union 키워드: 공용체라는 데이터 타입을 의미
  • unionName: 공용체의 이름

 

 

열거형

데이터들을 열거한 집합이다. 컴파일러는 열거형 멤버들을 정수형 상수로 취급한다.

첫 번째 멤버를 어떤 상수로 설정하면, 다음 멤버는 1씩 증가한다.

아래 코드로 예시를 들면, mon은 1, tue는 2, wed는 3 ... 이런 식이다.

enum Week {sun = 0, mon, tue, wed, thu, fri, sat};

 

 

동적 할당

동적으로 메모리를 할당하는 이유는 배열의 크기를 실행 시에 결정하기 위함이다. 동적으로 할당된 메모리는 힙에 저장되며, 힙은 큐 구조를 가진다. 

동적 메모리 할당 함수의 원형은 다음과 같다.

void* malloc(size_t size);
  • size: 할당할 메모리의 크기로, 바이트 단위로 입력한다.
  • 메모리가 할당되면 메모리 주소값을 리턴하고, 메모리가 부족하면 NULL 포인터를 리턴한다.

malloc의 사용 예시는 다음과 같으며, 해당 메모리를 더 이상 사용하지 않으면 free()로 메모리를 해제해야 한다. 해제하지 않을 경우 메모리 누수가 발생한다. 참고로 malloc을 사용하려면 stdlib.h를 include 해야 한다.

int main(void) {
	int* nums;
	int num = 10;

	nums = (int*)malloc(sizeof(int) * num);
	if (nums == NULL) {
		printf("메모리가 부족합니다.");
		return 0;
	}

	for (int i = 0; i < num; i++) {
		nums[i] = i;
	}

	for (int i = 0; i < num; i++) {
		printf("num[%d] = %d\n", i, nums[i]);
	}

	free(nums); // 메모리 해제

	return 0;
}

 

 

객체 지향

추상화

객체의 공통적인 특징을 하나의 개념으로 다루는 것을 추상화라고 한다.

예를 들어, 개와 고양이를 동물이라는 하나의 추상적인 개념으로 다룰 수 있다.

dog라는 객체와 cat이라는 객체를 따로 만들어서 관리할 수도 있지만, animal이라는 객체를 만들어 공통된 특징을 저장한 후 dog와 cat에서는 추가적인 부분만 정의해 주면 된다.

추상화를 사용하면 코드 중복을 줄일 수 있으며, 유지보수, 가독성을 높일 수 있다.

 

캡슐화

외부에서 내부를 볼 수 없도록 하는 것을 말한다. 그래서 외부 또는 내부에서 데이터를 조작할 수 있도록 인터페이스가 필요하다.

클래스는 데이터와 메서드로 구성되어 있다. 데이터는 은닉되어 있으며, 은닉된 데이터에 접근하기 위한 인터페이스를 멤버 함수(메서드)라고 한다.

멤버 함수를 사용해야만 데이터에 접근할 수 있기 때문에 안정성을 높일 수 있고, 데이터가 은닉되어 있기 때문에 다른 코드에 대한 의존성을 줄일 수 있으며, 유지보수성을 높일 수 있다.

 

 

배운 점

  • 포인터를 사용하면 함수를 변수에 담을 수 있다.
  • 원하는 변수들을 묶은 구조체라는 사용자 정의 데이터 타입이 있다.
  • malloc을 사용하여 동적으로 메모리를 할당할 수 있고, 더 이상 쓰지 않는 메모리는 해제해줘야 메모리 누수가 발생하지 않는다.
  • 객체 지향 프로그래밍의 특징인 추상화와 캡슐화를 통해 코드의 안정성, 가독성, 유지보수성을 높일 수 있다.