ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • C언어 기초#10 구조체와 포인터, 공용체(union), 열거형(enum), typedef
    Programming 기초/C Language 2023. 4. 15. 20:28

    *구조체(structure)

    C에서의 자료형은 기본 자료형과 파생 자료형으로 분류할 수 있다. 기본 자료형은 char, int, double 등이 있다. 파생 자료형은 이들 기본 자료형에서 파생된 것으로 배열, 열거형, 구조체, 공용체 등을 들 수 있다. 구조체는 파생 자료형 중에서도 가장 일반적인 구조를 가진다.

    c언어 공식문서에서는 기본 자료형이라는 공식적인 카테고리가 없다.
    c언어의 type 체계는 function type과 object type이 있다. (여기서 object type은 object와 다르다.) object type은 아래와 같이 네 가지가 있다(네가지는 서로 배타적인 관계가 아니다.)
    1. scalar type(int, char ...)과 pointer type
    2. 구조체, 배열 유형과 같은 aggregate type(집계유형)
    3. 공용체 type
    4. void 유형 ; 1~3에 해당하지 않는 것들

    사실상 object type은 data type(자료형)과 동의어로 쓰인다. 아마 파생 자료형과 명시적으로 구분하기 위해서 기본 자료형이라는 표현을 쓴 것으로 보인다.

    포인터는 함수 타입이나 객체 타입에 의한 파생 타입인데, 파생 타입은 부모 타입에 의존하게 된다는 특징에 따라 포인터는 일반적으로 스칼라 객체 타입에 해당한다.

    참고 :  https://cstdspace.quora.com/Are-pointers-a-non-primitive-data-type-or-are-they-variables

     
    - 배열이 여러 개의 같은 자료형을 하나로 묶는 것이라면 구조체는 서로 다른 자료형들을 하나로 묶는 것.

    struct(키워드) 태그 {	// 태그라는 이름을 가지는 구조체를 정의.
    
    	자료형 멤버 1;	// 구조체 안에는 멤버1, 멤버2,, 등 포함
    	자료형 멤버 2;
    ...
    
    };

    - struct는 구조체를 선언할 때 사용하는 키워드.
    - 태그는 변수가 아니다. 구조체와 구조체를 구별하기 위하여 구조체에 붙여지는 이름이다.
    - 구조체를 정의하는 것도 하나의 문장이기 때문에 끝에 세미콜론을 붙여줘야 한다.
    - 구조체는 변수선언이 아니다.!!

    struct student {	// 키워드 : struct, 태그 : student
    	int number;
    	char name[10];
    	double grade;
    } s1= { 24, “kim”, 4.3 };		// 구조체 정의와 변수 선언을 동시에 할 수 있음.
    				
    int main(void){
    struct student s1;		// 구조체 student의 변수 s1을 선. 초기값은 쓰레기값.
    ...
    }

    - s1에 메모리 공간이 할당됨. number+name[10]+grade = 4+10+8 =22바이트 -> 컴파일러는 액세스 속도를 빠르게 하기 위하여 더 많은 메모리를 할당하는 경우도 있으므로 sizeof 연산자를 이용하는 편이 정확.

    # 태그 없이 구조체 정의
    struct {
    	int number;
        char name[10];
        double grade;
    } s1, s2, s3;

    '태그' 이름의 구조체를 정의한다. 구조체 안에는 멤버1, 멤버2,... 등이 포함된다.
    태그는 생략가능하나, 이 경우 구조체의 이름이 없으므로 이 구조체의 변수를 더 이상 생성할 수 없다. 
    따라서 이 경우에는 필요한 모든 구조체 변수를 구조체 정의화 함께 선언해야 한다.

     

    • 구조체 멤버 참조
    #include <stdio.h>
    #include<string.h>	// 문자열 함수 라이브러리가 포함된 헤더파일
    
    struct student {
    	int number;
    	char name[10];
    	double grade;
    };
    
    int main(void) {
    
    	struct student s;
    	
    	s.number = 20070001;
    	strcpy(s.name, "홍길동");	// strcpy(a,b)는 b에 있는 문자열을 a에 복사하는 함수
    	s.grade = 4.3;			// strcpy는 string+copy의 합성어
    
    	printf("학번 : %d\n", s.number);
    	printf("이름 : %s\n", s.name);
    	printf("학년 : %f\n", s.grade);
    
    	return 0;
    }
    ------------------------------------------------------------------
    학번 : 20070001
    이름 : 홍길동
    학년 : 4.300000

     
    -만약 멤버가 문자열이면 멤버에 값을 대입할 때, strcpy()를 사용해야 한다.
     

    * 구조체의 활용

    • 구조체도 다른 구조체의 멤버가 될 수 있다. 포인터도 구조체의 멤버가 될 수 있다.

     

    • 구조체 변수의 대입과 비교
    struct point {
    	int x;
    	int y;
    };
    struct point p1 = { 10, 20 };
    struct point p2 = { 30, 40 };

    여기서, p2 = p1; 과 같이, 하나의 구조체를 다른 구조체로 대입하는 연산이 가능하다.
    다음 위 아래의 문장은 동일한 효과다.

    p2=p1;
    --------------------
    p2.x = p1.x;
    p2.y = p1.y;

     

    • 구조체의 배열
    #include <stdio.h>
    #include<string.h>
    
    struct student {
    	int number;
    	char name[10];
    	double grade;
    };
    
    int main(void) {
    
    	struct student list[100];
    	
    	list[2].number = 20070001;
    	strcpy(list[2].name, "홍길동");
    	list[2].grade = 4.3;
    
    	list[3].number = 20120432;
    	strcpy(list[3].name, "임꺽정");
    	list[3].grade = 3.7;
    
    	printf("학생2의 데이터\n");
    	printf("학번 : %d\n", list[2].number);
    	printf("이름 : %s\n", list[2].name);
    	printf("학년 : %f\n", list[2].grade);
    
    	printf("\n학생3의 데이터\n");
    	printf("학번 : %d\n", list[3].number);
    	printf("이름 : %s\n", list[3].name);
    	printf("학년 : %f\n", list[3].grade);
    
    	return 0;
    }
    ------------------------------------------------------
    학생2의 데이터
    학번 : 20070001
    이름 : 홍길동
    학년 : 4.300000
    
    학생3의 데이터
    학번 : 20120432
    이름 : 임꺽정
    학년 : 3.700000

     
    - 구조체 배열의 초기화. 배열 초기화 안에 구조체 초기화가 들어가야 하므로 중괄호 안에 또 중괄호가 필요함.

    struct student list[3] = {
    	{1, "Park", 3.42},
    	{2, "Kim", 4.31},
    	{3, "Lee", 2.98}
    };

     

    • 구조체를 가리키는 포인터

    - int 자료형의 변수를 int 자료형 포인터에 넣는 것처럼 구조체 자료형의 변수는 구조체 자료형의 포인터에 넣는 것이다.

    struct student s = { 20070001, “홍길동”, 4.3 };
    struct student *p; 			//	구조체 student를 가리키는 포인터 선언
    
    p = &s;					// 구조체의 주소를 포인터에 대입
    
    printf(“학번=%d 이름=%s 학점 =%f \n”, (*p).number, (*p).name, (*p).grade);
    					// 포인터를 통하여 구조체의 정보에 접근한다. 
    					// (*p)가 구조체가 된다.

     

    < 혼동하기 쉬운 구조체 변수와 구조체 포인터의 조합 비교 >

    • 간접 멤버 연산자(indirect membership operator) : 포인터를 이용하여 구조체의 멤버를 가리키는 연산자(->)

    1) (*p).number : 포인터 p가 가리키는 구조체의 멤버 number를 의미.


    2) p->number : 포인터 p가 가리키는 구조체의 멤버 number를 의미. (*p).number와 완전 동일.


     
    3) *p.number : 연산자 우선순위에 의하여 *(p.number)와 같다. 구조체 p의 멤버 number가 가리키는 것이란
    의미. 이때 number는 반드시 포인터여야 한다. number가 포인터가 아니면 오류이다.


    4) *p->number : 연산자 우선순위에 의하여 *(p->number)와 같다. p가 가리키는 구조체의 멤버 number가
    가리키는 내용을 의미. 만약 number가 포인터가 아니면 오류이다.

     

    • 연결 리스트
    #define _CRT_SECURE_NO_WARNINGS
    #include <stdio.h>
    #include<string.h>
    
    struct student {
    	int number;
    	char name[10];
    	double grade;
    	struct student* next;
    };
    
    int main(void)
    {
    	struct student s1 = { 30, "kim", 4.3, NULL };
    	struct student s2 = { 31, "Park", 3.7, NULL };
    
    	struct student* first = NULL;
    	struct student* current = NULL;
    
    	first = &s1;		// 구현에 초점을 두면, first 없이 current 하나면 충분한데,
    	s1.next = &s2;		// 연결리스트의 첫 번째 구조체를 명확하게 보여주고자 first 포인터를 추가했다.
    	s2.next = NULL;		// 연결리스트의 마지막 꼬리이므로. 다음 연결값을 NULL로 지정.
    
    	current = first;
    	int i = 1;		// 몇 번째 학생인지 구분하기 위한 인덱스
    	while (current != NULL) 
    	{
    		printf("%d번 학생 정보\n", i);
    		i += i;
    
    		printf("학번 : %d \n이름 : %s \n성적: %f\n\n", current->number, current->name, current->grade);
    		current = current->next;
    	}
    	
    }
    ----------------------------------------------------
    1번 학생 정보
    학번 : 30
    이름 : kim
    성적: 4.300000
    
    2번 학생 정보
    학번 : 31
    이름 : Park
    성적: 3.700000

     

    • 문자 배열과 문자 포인터의 차이점
    • 구조체를 함수의 인수로 넘기는 방법

    "값에 의한 호출" 원칙이 적용되어, 복사본이 인수로 전달된다.
    구조체 전체를 복사하는 것보다 구조체의 주소를 넘겨주는 것이 시간과 메모리 공간을 절약할 수 있다.
    그러나 포인터를 사용하면 "참조에 의한 호출"로, 원본이 전달되므로 원본이 바뀔 수 있다.

    원본을 포인터를 통하여 읽기만 하고 쓸 필요는 없는 경우,
    매개 변수를 정의할 때 const 키워드를 사용하면 된다.
     
    const 키워드가 *p1 앞에 있으면 이 포인터가 가리키는 구조체의 값을 변경하려고 하면
    컴파일 과정에서 오류 메시지가 출력된다.

    int equal(struct student const* p1, struct student const* p2)
    {
    	if (p1->number == p2->number)
    		return 1;
    	else
    		return 0;
    }
    • 구조체를 함수의 반환값으로 넘기는 방법
    struct student create()	// 구조체 자료형의 사용자 정의 함수.
    {
    	struct student s;
        
    	s.number = 3;
    	strcpy(s.name, "Park");
    	s.grade = 4.0;
    	
    	return s;	// 구조체 student의 create 함수에서 구조체 s를 반환
    }
    
    int main(void)
    {
    	struct student a;
    	a = create();	// 구조체 s가 구조체 a로 복사된다.
    	return 0;
    }

     

    * 공용체(Union)

    - 같은 메모리 영역을 여러 개의 변수들이 공유할 수 있게 하는 기능. 메모리 절약
    - 멤버들이 같은 공간을 공유하기 때문에 동시에 모든 멤버 변수들의 값을 저장할 수 없다.
    - 공용체에는 가장 큰 멤버의 크기만큼의 메모리가 할당된다.

    union example {	// union은 키워드, example은 공용체 태그 이름
    	char c;	// 변수 c와 변수 i는 공용체 멤버 변수이다. 같은 기억 공간 공유.
        int i;	// char 보다 int가 크므로 int형 크기(4바이트)만큼 메모리가 할당됨.
    };

    - 공용체도 초기화가 가능한데, 한 번에 하나의 멤버만 사용되기 때문에 첫 번째 멤버만 초기화된다.

    union example v = { 'A' };

     

    * 열거형(enumeration)

    - 변수가 가질 수 있는 값들을 나열해놓은 자료형이다. 쉽게 말해 변수가 가질 수 있는 값들을 나타내는 상수들을 모아놓은 자료형이다. 열거형으로 선언된 변수는 열거형에 정의된 상수들만이 가질 수 있다.

    enum 태그 {기호상수1, 기호상수2, 기호상수3, ...};

    태그라는 이름을 가지는 열거형을 정의한다. 이 열거형은 정의된 기호상수만을 가질 수 있다.

    enum days { SUN, MON, TUE, WED, THU, FRI, SAT }; // SUN =0, MON = 1 ... 자동으로 설정된다.

    기호상수에 배정되는 정수값은 사용자가 변경할 수 있다.

    enum days { SUN=1, MON, TUE, WED, THU, FRI, SAT }; // SUN =1, MON = 2 ... 순서로 자동으로 설정된다.
    enum days { SUN=7, MON=1, TUE, WED, THU, FRI, SAT=8 }; // SUN =0, MON = 1 ... 자동으로 설정된다.
    #include<stdio.h>
    enum days{ SUN, MON=0, TUE, WED=18, THU, FRI, SAT=6 }; // SUN =0, MON = 1 ...
    
    int main(void)
    {
    	enum days dd;
    	dd = SUN;
    	printf("일요일 코드 값은 : %d 입니다.\n", dd);
    	dd = TUE;
    	printf("화요일 코드 값은 : %d 입니다.\n", dd);
    	dd = FRI;
    	printf("금요일 코드 값은 : %d 입니다.\n", dd);
    	
        return 0;
    }
    ----------------------------------------------------
    일요일 코드 값은 : 0 입니다.
    화요일 코드 값은 : 1 입니다.
    금요일 코드 값은 : 20 입니다.

     

    * typedef

    새로운 자료형(type)을 정의(define)하는 것. 

    typedef old_type new_type;

    old_type은 기존의 자료형이고 new_type은 새롭게 정의하려고 하는 자료형의 이름이다.
     

    // int형이 컴퓨터 사양에 따라 16비트이기도 32비트이기도 하기에 명확히 구분하려고 이름 변경
    typedef int INT32;
    typedef short INT16;
    
    INT32 i;  // int i; 와 같다.
    INT16 k; // short k; 와 같다.
    • 구조체에도 typedef가 가능하다.
    struct point {
    	int x;
        int y;
    };
    
    typedef struct point POINT;
    POINT a, b;	// struct point a
    • 구조체 선언과 typedef을 같이 사용할 수 있다.
    // 구조체를 새로운 자료형 POINT로 정의
    typedef struct point {
    	int x;
        int y;
    } POINT;
    • (예시) enum 키워드를 이용하여 BOOL이라는 새로운 자료형을 정의하기.
    typedef enum {FALSE, TRUE} BOOL;
    BOOL condition; //enum {FALSE, TRUE} condition;
    • (예시)배열로 새로운 자료형 만들기
    typedef float VECTOR[2] ;	// VECTOR는 실수 2개로 이루어진 1차원 배열
    typedef float MATRIX[10][10]; // MATRIX는 실수 100개로 이루어진 2차원 배열
    
    VECTOR v1, v2;	// v1과 v2는 1차원 배열
    MATRIX m1, m2;	// m1과 m2는 2차원 배열

     
     

    • typedef의 장점 
    1. 이식성을 높여준다. 위 예제에서 INT32, INT16과 같이 컴퓨터 시스템에 따라 INT의 크기가 달라지는 경우에 정의만 바꿔주면 코드를 한 번에 변경해줄 수 있다.
    2.  #define과 차이점 : 배열을 typedef로 재정의하는 것을 #define으론 못한다.
    3. 3문서화의 역할도 한다. 주석을 붙이는 것과 같은 효과가 있어서 가독성을 높인다.

    댓글

Designed by Tistory.