Developer

21.(C언어) 구조체(2) 본문

Programming Language/C

21.(C언어) 구조체(2)

DPhater 2020. 8. 1. 21:54

구조체도 다른 자료형과 같이 포인터, 배열을 사용할 수 있다.

 

구조체 포인터

#include<stdio.h>
#include<string.h>

typedef struct Student{
	char name[30];            //이름 
	char phone_number[30];    //전화번호 
	int student_id;           //학번 
}Student;

int main(){
	Student s={"김모군","010-xxxx-xxxx",123456};
	Student *sp;
	sp=&s;
	printf("-> 사용\n");
	printf("%s\n",sp->name);
	printf("%s\n",sp->phone_number);
	printf("%d\n",sp->student_id);
	
	printf("\n역참조 사용\n");
	printf("%s\n",(*sp).name);
	printf("%s\n",(*sp).phone_number);
	printf("%d\n",(*sp).student_id);
	
	return 0;
}

코드1 실행 결과

Student 구조체 변수 s를 선언과 동시에 초기화 하였다. 그런 후 구조체 포인터에 s의 주소를 할당하였다. 현재 상태는 아래 그림과 같다.

구조체 포인터 sp가 구조체 s를 가리키고 있다.

이제 구조체 포인터를 통해 멤버에 접근해보자. 구조체 변수에서는 .(점)을 이용해 멤버에 접근했다. 하지만 구조체 포인터는 ->(화살표) 를 사용한다. 포인터는 어떤 위치를 가리키고 있으므로 화살표를 사용한다고 생각하면 쉽다.

	printf("%s\n",sp->name);
	printf("%s\n",sp->phone_number);
	printf("%d\n",sp->student_id);

sp->name은 sp가 가리키고 있는 곳의 name이라는 뜻이다.

이제 역참조를 알아보자. 이전 포인터를 배울 때 역참조는 "가리키고 있는 곳의 값"이라고 했다. 그렇다면 sp가 가리키고 있는곳은 s이므로 *sp는 s와 같다. 그렇기 때문에 역참조를 사용하면 .(점)을 이용해 멤버에 접근할 수 있다.

	printf("%s\n",(*sp).name);
	printf("%s\n",(*sp).phone_number);
	printf("%d\n",(*sp).student_id);

주의할 점은 역참조 연산자와 포인터를 괄호로 묶어야 한다는 것이다.

 

https://dphater.tistory.com/entry/12C%EC%96%B8%EC%96%B4-%EB%85%BC%EB%A6%AC-%EC%97%B0%EC%82%B0%EC%9E%90%EB%B9%84%ED%8A%B8-%EC%97%B0%EC%82%B0%EC%9E%90

 

에 나오는 연산자의 우선순위를 보면 우선순위가 가장 높은것 중 구조체 .(점)이 있는걸 볼수있다. 역참조 연산자의 경우 그것보다 우선순위가 낮다. 따라서 괄호가 없다면 sp.name이 먼저 수행되는데 포인터는 .(점)으로 멤버에 접근할 수 없으므로 컴파일 에러가 발생한다.

이제 구조체 포인터 메모리 동적할당을 해보자.

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

typedef struct Student {
	char name[30];            //이름 
	char phone_number[30];    //전화번호 
	int student_id;           //학번 
}Student;

int main() {
	Student* sp =(Student*) malloc(sizeof(Student));
	if (sp == NULL)return -1;
	strcpy(sp->name, "김모군");
	strcpy(sp->phone_number, "010-xxxx-xxxx");
	sp->student_id = 123456;


	printf("%s\n", sp->name);
	printf("%s\n", sp->phone_number);
	printf("%d\n", sp->student_id);

	free(sp);
	return 0;
}

코드2에는 malloc을 사용하기 위해 stdlib.h헤더를 포함해주었다. 이전에 배운 동적할당과 똑같다. 메모리할당 크기는 sizeof(Student)로 구조체 크기를 넣어주었다. 만약 sizeof(Student)*size 를 malloc에 넣어준다면 size갯수만큼 연속되게 구조체에게 메모리가 할당되고 이는 배열과 같이 접근할 수 있다.

구조체 배열

구조체 배열도 일반 배열과 다르지 않다.

#include<stdio.h>
#include<string.h>

typedef struct Student{
	char name[30];            //이름 
	char phone_number[30];    //전화번호 
	int student_id;           //학번 
}Student;

int main(){
	int i;
	Student s[3];
	
	strcpy(s[0].name,"김모군");
	strcpy(s[0].phone_number,"010-xxxx-xxxx");
	s[0].student_id=123456;
	
	strcpy(s[1].name,"이모군");
	strcpy(s[1].phone_number,"010-yyyy-yyyy");
	s[1].student_id=234567;
	
	strcpy(s[2].name,"박모군");
	strcpy(s[2].phone_number,"010-zzzz-zzzz");
	s[2].student_id=345678;
	
	for(i=0;i<3;i++){
		printf("%d 번째 학생\n",i+1);
		printf("이름 : %s\n",s[i].name);
		printf("전화번호 : %s\n",s[i].phone_number);
		printf("학번 : %d\n",s[i].student_id);
	}
	
	return 0;
}

코드3 실행 결과
구조체 배열

구조체 배열의 상태는 이런식이다. 구조체가 메모리의 연속된 위치에 생성되고, 접근은 대괄호와 index를 사용하거나 배열의 이름은 포인터이므로 포인터 연산을 통해서 접근할 수 있다.

s[i].name;
(s+i)->name;
(*(s+i)).name;

위의 3가지는 모두 같은 뜻이다. 첫 번째 줄은 그림을 보면 쉽게 이해할 수 있다.

두 번째 줄의 경우 s는 배열의 이름이므로 포인터이다. 포인터에 연산을 하게되면 그 자료형의 크기만큼 연산이 진행 된다고 하였다. 따라서 s+i는 배열의 시작 지점에서 구조체의크기*i만큼 이동한 것이다. 포인터 연산의 결과는 포인터이므로

->(화살표 연산자)를 사용해 name에 접근하는 것이다.

세 번째 줄의 경우 s+i를 역참조하면 s[i]와 같으므로 .(점)을 이용해 name에 접근하는 것이다.

구조체 배열 동적할당

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
typedef struct Student {
	char name[30];            //이름 
	char phone_number[30];    //전화번호 
	int student_id;           //학번 
}Student;

int main() {
	int i;
	Student *s;
	s = (Student*)malloc(sizeof(Student) * 3);
	if (s == NULL)return -1; //메모리 할당 실패하면 종료
	strcpy(s[0].name, "김모군");
	strcpy(s[0].phone_number, "010-xxxx-xxxx");
	s[0].student_id = 123456;

	strcpy(s[1].name, "이모군");
	strcpy(s[1].phone_number, "010-yyyy-yyyy");
	s[1].student_id = 234567;

	strcpy(s[2].name, "박모군");
	strcpy(s[2].phone_number, "010-zzzz-zzzz");
	s[2].student_id = 345678;

	
	for (i = 0; i < 3; i++) {
		printf("%d 번째 학생\n", i + 1);
		printf("이름 : %s\n", s[i].name);
		printf("전화번호 : %s\n", (s+i)->phone_number);
		printf("학번 : %d\n", (*(s + i)).student_id);
	}
    free(s);
	return 0;
}

코드4 실행 결과

1차원 배열 동적할당 처럼 연속적인 메모리에 구조체 3개의 크기만큼 동적할당 한것이다. 멤버 접근은 배열과 같다. 이해를 돕기위해 출력하는 부분은 멤버에 접근하는 3가지 방법을 사용해 출력하였다.

동적 할당한 메모리를 해제하는것도 잊지말자!

이번엔 포인터 배열을 이용해보자.

include<stdio.h>
#include<string.h>
#include<stdlib.h>
typedef struct Student {
	char name[30];            //이름 
	char phone_number[30];    //전화번호 
	int student_id;           //학번 
}Student;

int main() {
	int i;
	Student *s[3];
	for (i = 0; i < sizeof(s) / sizeof(Student*); i++) {
		s[i] = (Student*)malloc(sizeof(Student));
		if (s[i] == NULL)
			return -1;
	}
		

	strcpy(s[0]->name, "김모군");
	strcpy(s[0]->phone_number, "010-xxxx-xxxx");
	s[0]->student_id = 123456;

	strcpy(s[1]->name, "이모군");
	strcpy(s[1]->phone_number, "010-yyyy-yyyy");
	s[1]->student_id = 234567;

	strcpy(s[2]->name, "박모군");
	strcpy(s[2]->phone_number, "010-zzzz-zzzz");
	s[2]->student_id = 345678;

	
	for (i = 0; i < sizeof(s) / sizeof(Student*); i++) {
		printf("%d 번째 학생\n", i + 1);
		printf("이름 : %s\n", s[i]->name);
		printf("전화번호 : %s\n", s[i]->phone_number);
		printf("학번 : %d\n", s[i]->student_id);
	}

	for (i = 0; i < sizeof(s) / sizeof(Student*); i++)
		free(s[i]);
	return 0;
}

코드5 실행 결과

코드4와 코드5의 차이를 그림으로 살펴보자 편의를 위해 멤버변수는 변수이름의 앞 두글자만 사용하겠다. 우선 코드 4의 경우이다.

구조체 포인터s가 구조체 3개 만큼의 공간이 할당된 메모리의 첫 시작주소를 가리키고있다. 따라서 코드4처럼 마치 배열을 사용하듯이 사용할 수 있다.

이제 코드5의 경우를 보자.

포인터 배열은 포인터를 담을 수 있는 배열이라고 이미 배웠다.

그렇다면 s[3]은 Student의 포인터를 3개 담을 수 있는 배열인 것이다.

그리고 각 요소별로 구조체 하나의 크기 만큼 동적할당을 해주었다.

코드4 처럼 구조체 각각의 메모리주소는 연속되지 않는다.

포인터 배열 s의 각 요소가 동적할당된 주소를 각각 가리키고 있는것이다.

배열의 요소들로 ->(화살표) 연산자를 사용해 구조체의 멤버에 접근하는것이다.

또한 포인터 배열의 요소별로 동적할당을 했으니 메모리 해제도 요소마다 해야한다.

'Programming Language > C' 카테고리의 다른 글

23.(C언어) 공용체, 열거형  (0) 2020.08.01
22.(C언어) 구조체(3)  (0) 2020.08.01
20.(C언어) 구조체(1)  (0) 2020.08.01
19.(C언어) 문자열 함수  (0) 2020.08.01
18.(C언어) 문자열  (0) 2020.08.01
Comments