공부했던 자료 정리하는 용도입니다.

재배포, 수정하지 마세요.

 

 


 

 구조체 

  1. 구조체
    • 구조체 정의
      * 구조체 정의와 동시에 변수 선언
    • 구조체 변수의 선언
      * 선언과 동시에 초기화
      * 구조체 정의와 typedef 선언
      * 익명 구조체
    • 구조체 변수의 멤버에 접근
    • 구조체 변수의 주소 값
    • 구조체 크기
      * 구조체 멤버 정렬
    • 구조체 변수를 대상으로 하는 연산
    • 중첩된 구조체의 정의와 변수의 선언
  2. 구조체 배열
    • 구조체 배열 선언
      * 선언과 동시에 초기화
    • 구조체 배열 접근
  3. 구조체 변수와 포인터
    • 구조체 포인터 변수 선언, 초기화
    • 포인터 변수를 구조체의 멤버로 선언
  4. 구조체 포인터와 동적 할당
    • 구조체 비교
    • 구조체와 메모리 활용
  5. 매개 변수나 반환 값으로 사용되는 구조체 변수
    • 함수로의 구조체 변수 전달과 반환
    • 구조체 변수를 대상으로 하는 Call-by-reference

 


 

1. 구조체(structure)

  • 구조체는 관련 정보를 하나의 의미로 묶을 때 사용한다.
    기본 자료형을 조합하여 만든 자료형을 파생형(derived type)이라 한다.
  • 구조체는 보통 main함수 바깥에 정의한다. 만약 함수 안에 구조체를 정의하면 해당 함수 안에서만 구조체를 사용할 수 있다.

  하나 이상의 변수(포인터 변수와 배열 포함)를 묶어 새로운 자료형을 정의하는 것을 의미한다. 기본 자료형 변수를 묶어 새로운 자료형을 만드는 것이기 때문에 사용자 정의 자료형(user defined data type)이라고도 한다. 구조체를 통해 연관 있는 데이터를 하나로 묶을 수 있는 자료형을 정의하면 데이터의 표현 및 관리가 용이해진다.

 

 

구조체 정의

struct person	// 이름이 person인 구조체 정의
{
    char name[20];	// 이름 저장을 위한 멤버
    char phoneNum[20];	// 전화번호 저장을 위한 멤버
    int age;		// 나이 저장을 위한 멤버
};

배열도 (값의 저장이 가능한) 변수의 성격을 띠기 때문에 구조체의 멤버가 될 수 있다.

 

 

   구조체(공용체)의 최대 멤버 개수 

  C언어 표준에는 1023개로 정의되어 있으며 컴파일러마다 최대 개수는 달라질 수 있다.

 

 

  ■ 구조체 정의와 동시에 변수 선언

struct point{
    int xpos;
    int ypos;
} pos1, pos2, po3;	// 구조체 정의 뒤에 구조체 변수를 바로 선언할 수 있다.

구조체를 정의함과 동시에 변수를 선언하는 것도 가능하다. 구조체 정의 뒤에 바로 구조체 변수명을 써주면 된다.

 

 

 

 

구조체 변수의 선언

struct [type_name] [val_name];

// 구조체 변수 선언
struct person man;	// person 구조체의 변수 man 선언
struct point pos;	// point 구조체의 변수 pos 선언
  • ``struct`` : 구조체 변수를 선언할 때 맨 앞에 사용
  • ``type_name`` : 정의한 구조체의 이름
  • ``val_name`` : 구조체 변수 이름

구조체 변수의 구성

앞서 정의한 ``person``과 ``point``라는 구조체를 정의하고 구조체 변수 ``man``과 ``pos``를 선언하면 위와 같은 형태로 존재하게 된다.

 

 

 

  ■ 선언과 동시에 초기화

// 구조체 point, person 선언
struct point{
    int xpos;
    int ypos;
};

struct person{
    char name[20];
    char phoneNum[20];
    int age;
};

// 구조체 변수 선언과 동시에 초기화
struct point pos = {10, 20};	// 구조체 정의에 선언된 멤버 순서대로 초기화 값으로 들어감
struct person man = {"홍길동", "010-1234-5678", 20};

기본형 변수들처럼 구조체 변수도 선언과 동시에 초기화할 수 있다. 초기화 방법은 배열의 초기화 방법과 동일하다.

값을 중괄호``{ }``에 나열하면 구조체 정의에 선언된 멤버의 순서대로 초기화 값이 들어간다.

초기화 과정에서는 문자열 저장을 위해 @@strcpy@@함수를 호출하지 않아도 되며, 바로 값을 넣어주면 된다.

 

 

 

  ■ 구조체 정의와 typedef 선언

typedef [자료형][별칭]
typedef [자료형*] [별칭]

typedef int INT;		// int에 또다른 이름 INT 부여
typedef name1, name2, name3;	// 가장 마지막에 등장하는 단어를 중심으로 이뤄지므로 name1, name2의 또다른 이름을 name3으로 지정하는 것

// 선언 이후에는 아래와 같이 서로 대체가 가능하다.
INT num;
int num;	// typedef 선언 이후에도 원래 이름 사용 가능
INT * ptr;

``typedef``는자료형의 별칭을 만들 때 사용한다. 구조체뿐만 아니라 모든 자료형의 별칭을 만들 수 있다. (포인터 별칭은 포인터라는 의미로 앞에 P를 붙이기도 한다.) typedef로 정의한 별칭을 사용자 정의 자료형, 사용자 정의 타입이라고 부른다.

 

  • 선언 이후에는 새로 부여한 이름을 사용하는 것이나 원래의 이름을 사용하는 것이나 차이가 없다.
  • 새 이름을 부여한 후에도 원래 이름을 사용할 수 있다.
  • 이름이 여러 개 일 경우 가장 마지막에 등장하는 단어를 중심으로 이뤄진다. (2행)
  • ##typedef##로 정의되는 자료형의 이름은 대문자로 시작하는 것이 관례이다.

 

// 1) 구조체를 정의한 이후 typedef를 사용하는 경우
// 구조체 point 정의
struct point{
    int xpos;
    int ypos;
};

typedef struct point Point;	// typedef로 struct point에 새로운 이름인 Point를 부여
Point pos;			// typedef 이후에는 이렇게 선언할 수 있다.

struct point pos;		// typedef를 사용하지 않으면 원래 이렇게 선언해야함
// 2) 구조체 정의와 동시에 typedef를 사용하는 경우
typedef struct point{
    int xpos;
    int ypos;
} Point;

// 이후에는 아래와 같이 구조체 변수 선언 가능
Point pos;


// typedef로 새로 부여한 이름이 일치하지 않는 경우 
// : typedef로 정의된 이름으로 해당 구조체를 부르는 것이 관례(아래의 예시는 simple도 되고 SoSimple도 됨)
typedef struct simple{
	...   
} SoSimple;

  이를 이용해서 구조체 변수를 선언할 때 ``struct``를 생략할 수 있도록 할 수 있다. 구조체를 정의한 이후에 ``typedef``를 사용할 수도 있지만, 구조체를 정의하면서 ``typedef``를 동시에 선언하는 2번 방법이 가장 많이 사용된다.

 

 

  익명 구조체(anonymous structure)

typedef struct{
	[자료형] [멤버이름];
} [구조체 별칭];

``typedef``를 사용하면 원래의 이름은 잘 사용하지 않기 때문에 아예 구조체를 선언할 때 이름을 생략하는 것도 가능하다.
이런 구조체를 익명 구조체라고 한다.

 

 

 

 

구조체 변수의 멤버에 접근

[구조체 변수 이름].[구조체 멤버 이름];

// 사용예시
pos.xpos = 20;				// 구조체 변수 pos의 멤버 xpos의 값을 20으로 저장
scanf("%d %d", &pos1.xpos, &pos1.ypos);	// 두 개의 정수를 입력받아 구조체 변수 pos1의 멤버 xpos, ypos에 저장
strcpy(man.name, "홍길동");		// 구조체 변수 선언 이후에 구조체 멤버에 문자열을 저장하기 위해서는 strcpy함수를 사용해야한다.

구조체 변수의 멤버에 접근할 때는 ``.``연산자를 사용한다.

구조체 멤버로 배열이 선언된 경우 배열의 접근방식을, 포인터 변수인 경우에는 포인터 변수의 접근방식으로 접근하면 된다.

 

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

// 구조체 person 선언 
struct person{
	char name[20];
	char phoneNum[20];
	int age;
};

int main(void)
{	
	struct person man1, man2;	// 구조체 변수 man1, man2 선언
	
	// 구조체 변수 선언 이후에 구조체의 멤버에 문자열을 저장하려면 strcpy함수를 호출해야 한다.
	strcpy(man1.name, "안성준");	 
	strcpy(man1.phoneNum, "010-1122-3344");
	man1.age = 23;
	
	printf("이름 입력 : "); scanf("%s", man2.name);
	printf("번호 입력 : "); scanf("%s", man2.phoneNum);
	printf("나이 입력 : "); scanf("%d", &(man2.age));	// 문자열이 아니기 때문에 & 연산자를 사용해야함	
	
	printf("이름 : %s \n", man1.name); 
	printf("번호 : %s \n", man1.phoneNum); 
	printf("나이 : %d \n", man1.age);

	printf("이름 : %s \n", man2.name); 
	printf("번호 : %s \n", man2.phoneNum); 
	printf("나이 : %d \n", man2.age);
	return 0;
}

구조체를 선언하고 접근하는 예시이다.

구조체의 멤버가 배열인 경우 문자열 저장을 위해 @@strcpy@@함수를 이용해야 한다.

 

실행 결과

 

 

 

 

구조체 변수의 주소 값

  구조체 변수의 주소 값은 구조체 변수의 첫 번째 멤버의 주소 값과 동일하다.

 

#include <stdio.h>

// 구조체 point, person정의 
struct point{
	int xpos;
	int ypos;
};

struct person{
	char name[20];
	char phoneNum[20];
	int age;
};

int main(void)
{
	struct point pos = {10, 20};	// point형 구조체 변수 pos 선언 및 초기화 
	struct person man = {"박만근", "010-1234-5678", 40};	// person형 구조체 변수 man 선언 및 초기화 
	 
	printf("%p %p \n", &pos, &pos.xpos);
	printf("%p %p \n", &man, man.name); 
	
	return 0;
}

구조체 변수의 주소 값과 첫 번째 멤버의 주소 값을 비교하는 예시이다.

 

실행 결과

두 주소 값이 같은 것을 확인할 수 있다.

 

 

 

 

구조체 크기

  sizeof 연산자를 사용하면 된다.

  • sizeof(struct 구조체)
  • sizeof(구조체 별칭)
  • sizeof(구조체 변수)
  • sizeof 구조체 변수

 

 

  ■ 구조체 멤버 정렬

  구조체가 메모리에 올라갔을 때 멤버를 정렬(alignment)하는 기능이 있다. CPU가 메모리에 접근할 때 32 bit는 4 bytes 단위, 64 bit는 8 byte 단위로 접근한다. 만약 32 bit CPU에서 4 byte보다 작은 데이터에 접근할 경우 내부적으로 시프트 연산이 발생해서 효율이 떨어진다. 그래서 C언어 컴파일러는 CPU가 메모리의 데이터에 효율적으로 접근할 수 있도록 구조체를 일정한 크기로 정렬하게 된다. (정렬하면 안 되는 경우도 있다. ex) 사진 파일, 네트워크로 데이터를 전송할 때..)

 

#include <stdio.h>

struct PacketHeader {
    char flags;    // 1바이트
    int seq;       // 4바이트
};

int main()
{
    struct PacketHeader header;

    printf("flags size: %d\n", sizeof(header.flags));           // 1: char는 1바이트
    printf("seq size : %d\n", sizeof(header.seq));             // 4: int는 4바이트
    printf("header size : %d\n", sizeof(header));                 // 8: 구조체 전체 크기는 8바이트
    printf("struct size : %d\n", sizeof(struct PacketHeader));    // 8: 구조체 이름으로 크기 구하기

    return 0;
}

실행결과

구조체의 크기를 확인하는 예시이다.

전체 크기가 5 byte(char 1 byte + int 4 byte)가 나와야 하는데 실제로는 8 byte라고 나온다. C언어에서 구조체를 정렬할 때 멤버 중에서 가장 큰 자료형 크기의 배수로 정렬하기 때문이다. 위의 예시에서는 char flags뒤에 4 byte를 맞추기 위해 남는 공간에 3 byte가 더 들어가는데 이때 남는 공간을 채우는 것을 패딩(padding)이라고 부른다.

 

#include <stdio.h>
#include <stddef.h>   // offsetof 매크로가 정의된 헤더 파일

struct PacketHeader {
    char flags;
    int seq;
};

int main()
{
    printf("flags offset : %d\n", offsetof(struct PacketHeader, flags));    // 0
    printf("seq offset : %d\n", offsetof(struct PacketHeader, seq));      // 4

    return 0;
}

구조체에서 멤버의 위치(offset)를 구할 때는 offsetof 매크로를 사용한다. ``stddef.h`` 헤더에 정의되어있다.

offsetof 매크로에 구조체와 멤버를 지정하면 구조체에서 해당 멤버의 상대 위치가 반환된다. (첫 멤버의 상대 위치는 0) 위의 예시에서는 구조체가 4 byte 단위로 정렬하므로 seq의 위치는 1이 아닌 4가 나온다.

 

+ 구조체의 정렬 크기를 지정하는 것도 가능하다.

   참고 : dojang.io/mod/page/view.php?id=432

 

 

 

 

구조체 변수를 대상으로 하는 연산

  구조체 변수를 대상으로는 매우 제한된 형태의 연산만 허용된다. 덧셈이나 뺄셈 등 기타 연산을 하려면 프로그래머가 직접 함수를 정의해야 한다.

 

   구조체 변수로 가능한 연산 

  1. 대입 연산 (구조체 변수간 대입 연산의 결과는 멤버 대 멤버의 복사가 된다.)
  2. 주소 값 반환을 목적으로 하는 ``&``연산
  3. ``sizeof`` 연산 (구조체 변수의 크기 반환)

 

#include <stdio.h>

// 구조체 point(Point) 정의 
typedef struct point{
	int xpos;
	int ypos;	
} Point;	// typedef로 struct point의 새로운이름 Point 부여 

int main(void)
{
	Point pos1 = {1, 2};
	Point pos2;
	pos2 = pos1;	// 구조체 변수로 대입연산을 하면 구조체 변수의 멤버간 복사가 이뤄진다. 
	
	printf("크기 : %d \n", sizeof(pos1));	// pos1의 전체 크기 반환 
	printf("[%d, %d] \n", pos1.xpos, pos1.ypos);
	printf("크기 : %d \n", sizeof(pos2));	// pos2의 전체 크기 반환 
	printf("[%d, %d] \n", pos2.xpos, pos2.ypos);

	return 0;
}

앞서 말했던 구조체 변수간 연산의 결과를 확인하는 예제이다.

 

실행 결과

 

 

#include <stdio.h>

// 구조체 point(Point) 정의 
typedef struct point{
	int xpos;
	int ypos;	
} Point;	// typedef로 struct point의 새로운이름 Point 부여 

// 구조체 변수간 덧셈을 진행하는 함수 
Point AddPoint(Point pos1, Point pos2){
	Point pos = {pos1.xpos + pos2.xpos, pos1.ypos + pos2.ypos};
	return pos;
}

// 구조체 변수간 뺄셈을 진행하는 함수 
Point MinPoint(Point pos1, Point pos2){
	Point pos = {pos1.xpos - pos2.xpos, pos1.ypos - pos2.ypos};
	return pos;
 } 

int main(void)
{
	Point pos1 = {5, 6};
	Point pos2 = {2, 9};
	Point result;

	result = AddPoint(pos1, pos2);
	printf("[%d, %d] \n", result.xpos, result.ypos);
	
	result = MinPoint(pos1, pos2);
	printf("[%d, %d] \n", result.xpos, result.ypos);

	return 0;
}

함수를 정의해 구조체 변수간 덧셈, 뺄셈을 진행하는 예시이다.

 

실행 결과

 

 

 

 

중첩된 구조체의 정의와 변수의 선언

  구조체 변수도 구조체의 멤버로 선언될 수 있다. 구조체 안에 구조체 변수가 멤버로 존재하는 경우를 가리켜 구조체의 중첩이라고 한다.

 

#include <stdio.h>

// 구조체 point(Point), circle(Circle) 정의 
typedef struct point{
	int xpos;
	int ypos;	
} Point;	// typedef로 struct point의 새로운이름 Point 부여 

typedef struct circle{
	Point cen;	// 구조체 변수의 멤버로 구조체 변수 선언 
	double rad;
} Circle;	// typedef로 struct Circle의 새로운이름 Circle 부여 

void ShowCircleInfo(Circle * cptr)
{
	printf("[%d, %d] \n", (cptr -> cen).xpos, (cptr -> cen).ypos);
	printf("radius : %g \n\n", cptr -> rad);
}

int main(void)
{
	Circle c1 = {{1, 2}, 3.5};	// 구조체 변수가 멤버로 올 경우 이중으로 중괄호{}를 사용해서 구분한다. 
	Circle c2 = {2, 4, 3.9};	// 중괄호를 사용하지 않으면 순서대로 초기화된다. 
	ShowCircleInfo(&c1);
	ShowCircleInfo(&c2);
	
	return 0;
}

  초기화 시 구조체 변수인 멤버는 중괄호``{ }``를 사용해서 구분한다. 중괄호를 사용하지 않으면 구조체에 선언된 멤버의 순서대로 초기화된다. 배열의 초기화와 마찬가지로 초기화하지 않은 멤버는 @@0@@으로 초기화된다.

 

실행 결과

 

 

 

 

 


2. 구조체 배열

  다수의 변수를 묶어 관리하기 위한 배열이 있는 것처럼 다수의 구조체 변수를 위한 구조체 배열이 있다.

 

   구조체 배열의 요소 개수 

  ``sizeof(구조체배열이름) / sizeof(struct 구조체이름)``

 

 

구조체 배열 선언

struct 구조체이름 변수이름[크기];

// 선언과 동시에 초기화 하려면 중괄호{}안에 중괄호를 사용한다. 각 요소는 콤마(,)로 구분한다.
struct 구조체이름 변수이름[크기] = {{.멤버이름1 = 값1, .멤버이름2 = 값2},
				  {.멤버이름1 = 값3, .멤버이름2 = 값4}};
struct 구조체이름 변수이름[크기] = {{값1, 값2}, {값3, 값4}};

// 모든 요소의 멤버를 초기화 하려면 구조체 배열을 선언하면서 {0, }을 할당하면 된다.
struct Point2D p[3] = {0, };

// 요소에 접근할 때는 인덱스, 멤버에 접근할 때는 점(.)을 이용한다.
p[0].x;

// 사용예시
struct point arr[4];

구조체 배열은 변수 이름 뒤에 대괄호[ ]를 붙인 뒤 크기를 설정한다.

각 요소에 접근할 때는 대괄호 안에 인덱스를 지정해 주면 되고, 각 요소의 멤버에 접근할 때는 점(.)을 사용한다.

 

구조체 배열의 구조와 접근

마지막 라인과 같이 배열 선언을 하면 그림과 같은 구조로 배열이 할당된다.

 

 

  ■ 선언과 동시에 초기화

// 구조체 person 정의
struct person{
    char name[20];
    char phoneNum[20];
    int age;
};

// 초기화 예시
struct person arr[3] = {	// 크기가 3인 구조체 배열 arr 선언
    {"박만근", "010-1111-1111", 20},	// 첫 번째 요소 초기화
    {"최창식", "010-2222-2222", 21},	// 두 번째 요소 초기화
    {"김말년", "010-3333-3333", 22}	// 세 번째 요소 초기화
}

구조체 배열을 선언과 동시에 초기화할 때는 배열의 길이만큼 중괄호``{ }``를 이용해서 초기화를 진행하면 된다.

 

 

 

 

구조체 배열 접근

#include <stdio.h>

// 구조체 point 정의 
struct point{
	int xpos;
	int ypos;
};

int main(void)
{
	struct point arr[3];	// 크기가 3인 구조체 배열 arr 선언
	
	for(int i = 0 ; i < 3 ; i++)
	{
		printf("점의 좌표 입력 : ");
		scanf("%d %d", &arr[i].xpos, &arr[i].ypos);
	}
	
	for(int i = 0 ; i < 3 ; i++)
		printf("[%d, %d] ", arr[i].xpos, arr[i].ypos);
	
	return 0;
}

구조체 배열도 배열과 마찬가지로 ``for``문을 통해 순차적으로 접근할 수 있다.

 

실행 결과

 

 

 

 

 


3. 구조체 변수와 포인터

  구조체 포인터 변수도 일반적인 포인터 변수의 선언 및 연산과 같다.

 

 

구조체 포인터 변수 선언, 초기화

struct point pos = {11, 22};	// point형 구조체 변수 pos의 멤버를 각각 11, 22로 초기화
struct point * pptr = &pos;	// point *형 구조체 포인터 변수 pptr에 구조체변수 pos의 주소값이 저장됨


// 구조체 포인터 변수를 이용해서 구조체 변수에 접근하는 방법은 2가지가 있다. (보통 2번 방법을 많이 사용)
// 1) *과 .연산자 사용
(*pptr).xpos = 10;	// pptr이 가리키는 구조체변수(pos)의 멤버 xpos에 10을 대입
(*pptr).ypos = 20;

// 2) 애로우 연산자(->) 사용
pptr->xpos = 10;	// pptr이 가리키는 구조체변수(pos)의 멤버 xpos에 10을 대입
pptr->ypos = 20;

구조체 포인터 변수를 이용해서 구조체 변수에 접근할 수 있다.

접근할 때 애로우 연산자 ##->##를 더 많이 사용한다.

 

#include <stdio.h>

// 구조체 point 정의
struct point{
	int xpos;
	int ypos;
};

int main(void)
{
	// 구조체 변수 pos1, pos2 선언
	struct point pos1 = {1, 2};
	struct point pos2 = {100, 200};
	struct point * pptr = &pos1;	// 구조체 포인터 변수 pptr 선언
	
    	// 구조체 포인터 변수 pptr을 이용해서 구조체 변수의 멤버에 접근
	(*pptr).xpos += 4;
	(*pptr).ypos += 5;
	printf("[%d, %d] \n", pptr->xpos, pptr->ypos);
	
	pptr = &pos2;
	pptr -> xpos += 1;
	pptr -> ypos += 2;
	printf("[%d, %d] \n", (*pptr).xpos, (*pptr).ypos);
	
	return 0;
}

구조체 포인터 변수를 정의하고, 두 가지 방법으로 구조체 변수에 접근하는 예시이다.

 

실행 결과

struct Data d[3] = { { 10, 20 }, { 30, 40 }, { 50, 60 } };
struct Data *ptr;
ptr = d;

printf("%d %d\n", (ptr + 1)->num1, (ptr + 1)->num2);	// d[1].num1, d[1].num2과 같음
printf("%d %d\n", (ptr + 2)->num1, (ptr + 2)->num2);	// d[2].num1, d[2].num2과 같음

구조체 포인터도 포인터 연산을 할 수 있다. void 포인터의 경우에는 struct [구조체이름] *로 형변환을 해야한다.

 

 

포인터 변수를 구조체의 멤버로 선언

// 구조체 point, circle 정의
struct point{
    int xpos;
    int ypos;
};

struct circle{
    double radius;
    struct point * center;	// 구조체 변수 circle의 멤버를 point *형 구조체 포인터 변수로 선언;
};
// TYPE형 구조체 변수의 멤버로 TYPE형 포인터 변수를 선언하는 것도 가능하다.
struct point{
    int xpos;
    int ypos;
    struct point * ptr;	// 구조체의 멤버로 선언한 구조체와 같은 형인 포인터 변수 선언
};

위와 같이 구조체의 멤버로 포인터 변수가 오는 것도 가능하다.

또한 2번째 코드처럼 선언한 구조체와 같은 형의 포인터 변수를 선언하는 것도 가능하다.

 

#include <stdio.h>

// 구조체 point, circle 정의 
struct point{
	int xpos;
	int ypos;
};

struct circle{
	double radius;
	struct point * center;    // 구조체 변수 circle의 멤버를 point *형 구조체 포인터 변수로 선언
};

int main(void)
{
	struct point cen = {2, 7};	// point형 구조체 변수 cen 선언 및 초기화
	double rad = 5.5;
	
	struct circle ring = {rad, &cen};	// circle형 구조체 변수 ring의 멤버인 center가 cen을 가리킴 
	printf("원의 반지름 : %g \n", ring.radius);
	printf("원의 중심 [%d, %d] \n", (ring.center) -> xpos, (ring.center) -> ypos);
	
	return 0;
}

멤버에 포인터 변수가 있는 구조체를 사용하는 예시이다.

구조체 변수 ``ring``의 멤버 ``center``가 다시 구조체 변수인 ``cen``을 가리키는 구조이다.

 

실행 결과

 

 

#include <stdio.h>

// 구조체 point 정의 
struct point{
	int xpos;
	int ypos;
	struct point * ptr;	// 구조체의 멤버로 선언한 구조체와 같은 형인 포인터 변수 선언
};

int main(void)
{
	struct point pos1 = {1, 1};
	struct point pos2 = {2, 2};
	struct point pos3 = {3, 3};
	
	pos1.ptr = &pos2;
	pos2.ptr = &pos3;
	pos3.ptr = &pos1;
	
	printf("점의 연결관계 ... \n");
	printf("[%d, %d]와(과) [%d, %d] 연결 \n", pos1.xpos, pos1.ypos, pos1.ptr -> xpos, pos1.ptr -> ypos);
	printf("[%d, %d]와(과) [%d, %d] 연결 \n", pos2.xpos, pos2.ypos, pos2.ptr -> xpos, pos2.ptr -> ypos);
	printf("[%d, %d]와(과) [%d, %d] 연결 \n", pos3.xpos, pos3.ypos, pos3.ptr -> xpos, pos3.ptr -> ypos);
	
	return 0;
}

두 번째 유형으로 설명한 선언한 구조체와 같은 TYPE의 포인터 변수를 멤버로 사용하는 구조체의 예시이다.

포인터 변수를 이용해서 삼각형을 이루는 세 점의 연결관계를 표현한다.

 

실행 결과

 

 

 

 

 


4. 구조체 포인터와 동적 할당

struct [구조체이름] *[포인터이름] = malloc(sizeof(struct [구조체이름]));	// sizeof안에 struct를 붙여줘야함 !
[구조체별칭] *[포인터이름] = malloc(sizeof(구조체별칭));		// typedef를 사용한 경우 struct 생략가능

// 접근할 때는 ->연산자를 사용한다.
strcpy(p1->name, "홍길동");
p1->age = 30;

// .으로 멤버에 접근하려면 괄호()와 역참조를 사용해야한다.
(*p1).age;

// 사용한뒤에는 항상 메모리를 해제해주어야 한다.
free([구조체 이름]);
  • 메모리의 사이즈를 할당할 때는 sizeof안에 struct를 넣어야 한다. (typedef로 지정한 경우는 생략 가능)
  • 구조체 포인터의 멤버에 접근할 때는 ``.``이 아닌 ``->`` 연산자(화살표 연산자, arrow operator)를 사용한다는 것에 주의한다. (.을 사용하려면 역참조를 이용해야 한다.)
  • 구조체 멤버가 포인터일 때 역참조를 하려면 맨 앞에 *를 붙이면 된다.
    ``*구조체변수.멤버``
    ``*구조체포인터->멤버``
  • 역참조한 것을 괄호( )로 묶으면 구조체 변수를 역참조한 뒤 멤버에 접근한다는 의미이다.
    ``(*구조체포인터).멤버``
    ``*(*구조체포인터).멤버``
  • ``구조체포인터 = &구조체변수``하면 구조체 변수의 메모리 주소를 구조체 포인터에 할당할 수 있다. 접근할 때는 마찬가지로 ->를 사용하면 된다.

 

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

struct Data {
    char c1;
    int *numPtr;    // 포인터
};

int main()
{
    int num1 = 10;
    struct Data d1;
    struct Data *d2 = malloc(sizeof(struct Data));    // 구조체 포인터에 메모리 할당

    d1.numPtr = &num1;
    d2->numPtr = &num1;

    printf("%d\n", *d1.numPtr);     // 10: 구조체의 멤버를 역참조
    printf("%d\n", *d2->numPtr);    // 10: 구조체 포인터의 멤버를 역참조

    d2->c1 = 'a';
    printf("%c\n", (*d2).c1);      //  a: 구조체 포인터를 역참조하여 c1에 접근
                                   // d2->c1과 같음
    printf("%d\n", *(*d2).numPtr); // 10: 구조체 포인터를 역참조하여 numPtr에 접근한 뒤 다시 역참조
                                   // *d2->numPtr과 같음

    free(d2);

    return 0;
}

구조체와 구조체 포인터를 역참조하는 예시이다.

 

 

구조체 비교

  구조체 변수를 선언한 경우 비교 연산자로는 두 구조체의 내용이 같은지 알 수 없다. (컴파일 에러) 두 구조체의 멤버에 저장된 값이 모두 같은지 비교해주는 코드를 따로 작성해야 한다. 하지만 구조체를 포인터로 선언한 경우에는 포인터 자체를 비교 연산자로 비교해도 된다. 포인터를 ``==``연산자로 비교하면 주소 값을 비교하게 되는데, 두 포인터에 저장된 메모리 주소가 같으면 같은 구조체를 가리키고 있는 것이므로 구조체의 내용도 당연히 같다.

 

 

 

구조체와 메모리 활용

  구조체도 결국 메모리 공간을 차지하므로 메모리 관련 함수를 사용할 수 있다.

  • ``memset( )``함수를 사용하면 일일이 멤버에 값을 설정하거나 중괄호를 사용하지 않고, 구조체 변수나 메모리의 내용을 특정 값으로 한 번에 바꿀 수 있다. ``string.h``헤더나 ``memory.h``헤더를 포함하면 사용할 수 있다.
    ex) ``memset(&p1, 0, sizeof(struct Point2D));`` // 구조체 포인터인 경우 &생략
  • ``memcpy( )``함수를 사용하면 이미 생성하여 값을 저장한 구조체나 메모리를 다른 곳에 복사하는 것이 가능하다.
    ``string.h`` 헤더에 선언되어 있다.

 

 

 

 

 


5. 매개변수나 반환 값으로 사용되는 구조체 변수

함수로의 구조체 변수 전달과 반환

  구조체 변수도 함수의 인자로 전달하거나 ``return``문을 통해 반환하는 것이 가능하다. 함수에 전달되거나 반환되는 구조체 변수의 값은 매개변수에 통째로 복사되며, 구조체의 멤버로 배열이 선언되어도 똑같이 복사가 진행된다.

 

#include <stdio.h>

// 구조체 person(Person) 정의 
typedef struct person {
	char name[20];
	char phoneNum[20];
	int age;
} Person;	// typedef로 struct person의 새로운 이름 Person 부여 

void ShowPersonInfo(Person man)	// 함수의 매개변수로 Person형 구조체 변수 선언 
{
	printf("name : %s \n", man.name);
	printf("phone : %s \n", man.phoneNum);
	printf("age : %d \n", man.age);
}

Person ReadPersonInfo(void)
{
	Person man;
	printf("name? "); scanf("%s", man.name);
	printf("phone? "); scanf("%s", man.phoneNum);
	printf("age? "); scanf("%s", &man.age);
	
	return man;	// 함수의 리턴값을 Person형 구조체 변수로 지정 
}

int main(void)
{
	Person man = ReadPersonInfo();	// 함수의 반환값을 구조체 변수 man에 저장(복사됨) 
	ShowPersonInfo(man);
	
	return 0;
}

구조체 변수를 함수의 매개변수나 ``return``값으로 사용하는 예시이다.

 

실행 결과

인자의 전달 과정과 값의 반환 과정에서 구조체의 멤버로 선언된 배열도 통째로 복사가 되는 것을 확인할 수 있다.

 

 

 

 

구조체 변수를 대상으로 하는 Call-by-reference

#include <stdio.h>

// 구조체 point(Point) 정의 
typedef struct point{
	int xpos;
	int ypos;	
} Point;	// typedef로 struct point의 새로운이름 Point 부여 

// 원점대칭하는 함수 
void OrgSymTrans(Point * ptr)	// 구조체 포인터 변수 ptr을 함수의 매개변수로 선언 
{
	ptr -> xpos = (ptr -> xpos) * -1;
	ptr -> ypos = (ptr -> ypos) * -1;
}

void ShowPosition(Point pos)
{
	printf("[%d, %d] \n", pos.xpos, pos.ypos);
}

int main(void)
{
	Point pos = {7, -5};
	OrgSymTrans(&pos);	// 구조체 변수 pos의 주소값을 함수의 인자로 보냄 
	ShowPosition(pos);
	OrgSymTrans(&pos);	// 구조체 변수 pos의 주소값을 함수의 인자로 보냄
	ShowPosition(pos);
		
	return 0;
}

구조체 포인터 변수도 매개변수로 선언 가능하다.

그래서 위와 같이 구조체 변수를 대상으로 Call-by-reference 형태의 함수 호출을 구성할 수 있다.

 

실행 결과

 

 

 

'기타 > C' 카테고리의 다른 글

[C] 메모리 구조와 동적할당  (0) 2020.06.15
[C] 공용체(Union Type), 열거형(Enumerated Type)  (0) 2020.06.08
[C] 문자, 문자열  (0) 2020.05.28
[C] 스트림, 버퍼  (0) 2020.05.27
[C] 함수 포인터, void 포인터  (0) 2020.05.22

+ Recent posts