구조체

김성진·2024년 1월 21일

1월 19일 수업 정리

typedef

타입디피니션은 구조체를 사용하기 전에 알아야될 기능인데, 특정한 데이터형에 내가 이름을 붙여서 사용할 수 있는것이다.

선언 방식은 typedef 데이터형(복수도 가능) 이름;

예를 들자면
typedef unsigned short age;
이렇게 선언하면 2바이트 양의정수(unsigned short)의 자료형을 age라고 별명을 붙여 사용하겠다는 뜻이다. 여기서도 식별자 규칙을 지켜야한다.
이후에는 자료형처럼 사용한다.. age number; 처럼 선언하면 된다.
이런게 필요한 이유는 데이터형 안에 데이터형이 쭉 연결되어서 데이터형만 50글자 100글자 길게 적히게 될 때가 있다. 그럴 때에 축약하는 용도로 사용한다.

구조체

구조체는 c++부터 배우는 클래스의 특성과 유사하다.
간단히 말해서 변수와 함수를 미리 선언해놓고 묶어서 가지고 있는 데이터를 구조체라고 한다.

데이터형에는 기본 데이터형과 사용자정의 데이터형이 있다. 사용자정의 데이터형중 대표적인것이 구조체이며 typedef는 정의하는 방법중 하나이다.

구조체 선언

struct 데이터형이름 { 멤버 } ;
struct Position
{
float x;
float y;
};

중괄호 안에 변수를 선언해두면 구조체의 멤버가 된다.
C에서는 멤버변수만 있지만 C++은 멤버함수도 존재한다.

구조체 사용법은 아까 만들어둔 Position 구조체를 사용해서
position pos; 로 구조체 변수를 선언한다.
이후 pos.x = 10.f;
pos.y = 20.f;
와 같은 방법으로 구조체 안의 변수를 꺼내서 초기화만 하면 된다.
구조체 안에 멤버 변수도 배열처럼 메모리상에 일렬로 등록되어 있다.

구조체 선언 응용

구조체 안에 변수는 각각 데이터형이 달라도 된다.
새로운 구조체를 하나 만들어보자.

struct ProductInfo
{
	int num;
	char name[100];
	int cost;
}

int main()
{
	ProductInfo myProduct;
    myProduct.num = 10;
	myProduct.name[0] = '\0';
	myProduct.cost = 100;
	printf("상품 번호: %d, myProdict.num); 
}

주의할 점으로는 배열을 구조체로 사용했을 경우 만든 후 초기화를 하면 인식하지 못하니 새로 배열을 만들어서 대입해주는 방식으로 만들어야 한다.
productInfo myProdect = { 10, "abc", 100 }
혹은 이렇게 만들때부터 초기화를 하는 방법도 있다.

구조체의 데이터 구조

구조체는 4바이트 단위로 끊어서 정리하는것을 이해해야 한다.
예를들어 멤버변수를 2바이트, 4바이트, 4바이트 로 만들어놓으면 구조체의 길이가 10바이트가 아니라 12바이트가 된다. 2바이트 변수에 4바이트 만큼의 메모리를 할당하기 때문.
5바이트 길이의 배열같은 경우도 초과된 1바이트는 4바이트만큼의 메모리에 들어가기 때문에
총 8바이트의 길이를 가지게 된다.

포인터를 사용한 멤버 변수 호출

prodctInfo myProduct = {0};
prodctInfo ptr = &myProduct;
이렇게 선언하면
ptr은 myProduct와 같은 말이기 때문에

프린트 같은 함수로 멤버 변수에 접근할 때 (*ptr).num 대신 ptr->num 으로 사용가능하다.
-> 는 포인터 접근 연산자로, 구조체에서는 포인터를 많이 사용하기 때문에 중요하다.

두 구조체의 변수의 값을 바꾸는 코드를 작성해보자

#include <stdio.h>
#include <stdlib.h>

struct ProductInfo
{
	int num = 0;
	char name[100] = "";
	int cost = 0;

};

void ProductSwap(ProductInfo* a, ProductInfo* b)
{

	ProductInfo swap = *a;
	*a = *b;
	*b = swap;
}

int main()
{
	ProductInfo myProduct = { 1010, "제주 한라봉", 100000 };
	ProductInfo yourProduct = { 9090, "성주 꿀참외", 990000 };

	ProductSwap(&myProduct, &yourProduct);

	printf("%d\n", myProduct.num); //9090
	printf("%s\n", myProduct.name); //성주꿀참외
	printf("%d\n", myProduct.cost); //990000

	return 0;
}

포인터 변수를 이용해 두 배열의 값을 변경해주었다.

배열과 구조체

배열은 매개변수로 통채로 넘길 수 없다. 따라서 주소와 사이즈를 넘겨받아야 한다고 배웠다.
구조체 변수는 배열 형태로 존재하는데 어떻게 매개변수로 받을 수 있을까?
당연하게도 복사해서 새로 만든 배열이 함수로 넘어가기 때문에 가능하다.
하지만 구조체 변수를 통채로 매개변수로 넘기면 긴 배열을 만들고 넘기고를 반복하며 메모리 용량을 크게 잡아먹는다. 따라서 포인터를 사용해 넘겨받아야 한다.

구조체를 만들어서 시 분 초를 받아 총 몇초인지 계산하는 함수를 만들어보자

struct Time
{
	int h = 0, m = 0, s = 0;
};

int TotalSec(Time t)
{
	return 60 * 60 * t.h + 60 * t.m + t.s;
}

int main()
{
	Time t = { 1, 22, 48 }; //t구조체는 시분초를 받는다
	printf("%d\n", TotalSec(t));
    
	return 0;
}

이렇게 만들면 배열 자체를 넘기기 때문에 비효율적이다.
따라서 return 60 60 t.h + 60 t.m + t.s; 부분을
return 60
60 t->h + 60 t->m + t->s; 로 바꾸면
포인터를 사용해서 적은 메모리값을 활용할 수 있다.

하지만 이 방법의 단점으로는 포인터를 사용시에 값이 널인 널포인터를 넘겨받을 수 있고 컴파일러는 이를 체크하지 못한다. 널 포인터를 만나면 프로그램이 바로 죽어버리니 언제나 널을 체크할 수 있도록 해야한다.

멤버함수

c++부터는 멤버함수 또한 사용할 수 있다.
멤버변수와 마찬가지로 함수의 정의를 구조체 안에 넣으면 된다.
아까 만들어본 구조체를 이용해보자.

struct Time
{
	int h = 0, m = 0, s = 0;

	int TotalSec()
	{
		return 60 * 60 * h + 60 * m + s;
	}
};

이렇게 멤버함수를 사용하면 생기는 장점으로는 같은 구조체 내에 존재하는 멤버변수들을 멤버함수가 같이 사용할 수 있다. 멤버 함수에는 매개 변수를 따로 명시하지 않는다. 구조체에서 대신 선언되어 있기 때문이다.

대신에 호출할 때 반드시 주인이 되는 변수가 있어야 하며
printf("%d\n", t.TotalSec()); 처럼 t. 같은 형식이 앞에 붙어야 한다. 여기서 멤버함수는 3개의 변수를 필요로 하니 주인이 되는 변수도 3개의 값을 가진 배열이어야 했다.

C언어 고급기능

const

상수 라는 뜻으로 변수 선언시에 데이터형 앞쪽에 붙을 수 있으며 이렇게 데이터형에 조건을 붙이는 명령어들을 모디파이어, 한정자 라고 부름.
이것을 사용한 변수는 선언하면서 초기화 할때 단 한번만 데이터 값을 쓸 수 있고 이후로는 변경이 절대 안된다. 읽는건 얼마든지 가능하다.

이런걸 어디다 사용하냐면 pi - 3.141592 처럼 절대로 변하지 않는 수에 할당하면 된다.
물론 프로그래머가 조심해서 사용한다면 필요 없지만 복잡한 코드중에 상수를 건드리려 한다면 컴파일 에러가 나서 알아 차릴 수 있으니 자주 사용하게 된다.

비슷한 기능으로 전처리기 #를 사용해 #define 으로도 구현할 수 있는데
#define PI (3.141592....) 처럼 사용하면 된다. 매크로 라고도 한다.
이러면 컴파일러가 PI 라고 붙은걸 모조리 () 안의 값으로 바꿔버리고 컴파일을 시작한다.

무엇이 다르냐면 상수는 전역변수로 선언되어서 메모리에 계속 잡혀있지만 매크로는 부를때 사용되고 사용이 끝나면 메모리를 해제한다는 차이가 있다.

상수화가 어디에 붙냐에 따라 미묘하게 달라지는 규칙이 있는데
const double r
은 r 내부의 값을 바꾸지 못한다.
r은 바뀌지 않는데 r = &temp 해서 주소값을 바꾸면 바뀐다.
double const r
r의 주소를 바꾸지 못한다.
r 로 r값을 바꾸면 바뀐다.
const double* const r
값도, 주솟값도 바꾸지 못한다.

문자형 데이터에서 주의할 점으로는
char str[100];
StrLen("ABCDe");
StrLen(str);
이렇게 있을때 매개변수로 char를 사용하면 str 만 받을 수 있다.
abcde는 이미 상수이기 때문이다. const char
를 해야 abcde도 받을 수 있다.

enum

열거형 데이터형으로 정수형 상수에 이름을 붙여서 코드를 이해하기 쉽게 해주는 사용자 정의 데이터형이다.

enum 열거형이름 { 값 = 초깃값); 으로 만들 수 있다.

profile
듀얼리스트

0개의 댓글