C Part7. 다차원 포인터, 2차원 포인터, 2차원 포인터와 함수의 매개변수, 2차원 포인터와 2차원 배열, 구조체와 연결 리스트(typedef 문법, struct), 구조체를 활용한 연결 리스트(링크드리스트)

17. 다차원 포인터

다차원 포인터란?

동훈아라고 직접 말하지 않고 친구야 또는 나의 친구야 같은 자신을 기준으로 대상을 가리키는 간접표현으로 여러 번 가리키는 포인터를 ‘다차원 포인터’라고 한다.


다차원 포인터 정의하기

*이 하나면 1차원, *이 두개면 2차원 포인터라고 한다. 이 포인터 변수를 선언할 때 사용하는 * 키워드는 최대 7개(컴파일러마다 다름)까지 사용할 수 있으며, 왠만하면 3개이상 쓰지 마라.. 3차원도 많이 안씀


일반 변수의 한계와 다차원 포인터
short data = 0;
int my_ptr = (int)&data; //&data는 short * 형식의 값을 가지기 때문에 int형 변수인 my_ptr에 저장하기 위해서 형변환한다. 4바이트 크기라서 정상적으로 주소 저장
*my_ptr = 3; //오류 발생. my_ptr은 포인터가 아니라서 * 연산자 사용 할 수 없다.

2차원 포인터

2차원 포인터는 1차원 포인터의 주소 값을 저장한다.

void main(){
    short data =3;
    short *p = &data;
    short **pp = &p; //1차원 포인터 p변수의 주소 값을 2차원 포인터 p에 저장

    printf("[before] data : %d\n", data);
    *p = 4;
    printf("[use *p] data : %d\n", data);
    **pp = 5;
    printf("[use **pp] data : %d\n", data);

}

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
O - > 값(자신이 저장하는 값)
** -> 직전 참조 pointer의 값
*** -> 직전 참조 pointer가 가리키는 값
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

예제) malloc 함수를 사용하여 2차원 포인터 구조 만들기

void main(){
    short **pp; //2차 포인터만 있기때문에 1차 포인터 공간 만들어줘야 함
    pp = (short **)malloc(sizeof(short *)); //1차 포인터 공간 만들어줌. 4바이트 할당. sizeof short*은 포인터공간이라 무조건 4바이트!. 앞에 short** 별 두개 붙은거는 pp가 2차포인터라서 맞춰줌
    *pp = (short *)malloc(sizeof(short)); //1차 포인터가 가리키는 실제 값 공간 만들어줌. 실제값이 저장되는 공간이라 sizeof 뒤에 별 안붙음. 앞에 short *별 하나 붙는거는 pp가 1차 포인터 별 1개붙은거라 맞춰줌.

    **pp = 10;
    printf("**pp : %d\n", **pp);
    free(*pp); //나중에 만든 것부터 지워야 함!
    free(pp);
}

2차원 포인터와 함수의 매개변수

매개변수에 포인터 변수를 잘못 사용한 경우
void GetMyData(int *q){ // 매개변수와 main의 포인터는 둘 다 1차포인터
  q = (int *)malloc(8);
}

void main(){
  int *p; // GetMyData의 매개변수와 이것 둘 다 1차포인터
  GetMyData(p); //주소가 아닌 자기의 값을 넘겨준것. 
  *p = 5; //오류발생. 할당된 메모리의 첫 4바이트에 값 5를 넣음.
  free(p);
}

함수의 매개변수로 2차 포인터 사용하기(위 예제 올바른 방법)

예제) 2차원 포인터로 8바이트 동적 메모리를 할당하는 함수 만들기

void GetMyData(int **q){
  *q = (int *)malloc(8);
}
void main(){
  int *p;
  GetMyData(&p);
  *p = 5;
  free(p);
}

★★★ p439 필기 그림 참조!!! ★★★


2차원 포인터와 2차원 배열

*(p+1) = p[1] 포인터와 배열은 비슷

여러 개의 1차원 포인터를 정적으로 할당하기
  short *p[100];  // short * 형식의 1차원 포인터를 100개 선언

위와 같은 표현은 두 가지 비효율성이 있다. 첫번 째는 배열을 사용했기 때문에 컴파일 할 때 변수 p의 메모리 크기가 400바이트로 고정되어 버린다. 만약 포인터 200개를 사용하도록 변경해야한다면 수정해야하고, 소스코드를 변경했기 때문에 소스 파일을 다시 컴파일해야하는 불편함이 발생한다. 두번 째는 메모리 낭비가 될 수 있다. 실제로 5개만 사용하는 경우 95개의 메모리 공간이 낭비이다.


여러 개의 1차원 포인터를 동적 할당하기
short **pp;
pp = (short **)malloc(sizeof(short *)); //pp = (short **)malloc(4); 와 같은 표현. 뒤에 (short *)로 1차 포인터 공간을 만들어줌. (포인터는 무조건 4바이트)

malloc 함수의 매개변수에 동적으로 할당할 메모리의 크기를 적을 때는 상수 뿐만 아니라 변수를 사용할 수 있다. 따라서 다음과 같이 short* 형식의 1차원 포인터를 n개 할당할 수 있다.

int n;
short **pp;
scanf("%d", &n);
pp = (short **)malloc(sizeof(short *) * n);

이렇게 2차원 포인터와 malloc 함수를 사용하면 포인터의 개수가 바뀌어도 다시 컴파일 할 필요가 없다. 그리고 사용자가 메모리를 사용하고 싶은 크기만큼 선택할 수 있기 때문에 메모리 효율성이 좋다.


예제) 2차원 포인터로 연령별 윗몸 일으키기 횟수 관리하기

#include <malloc.h>

void main(){
  unsigned char *p_limit_table; //연령별 인원수를 저장할 포인터. 사용자에게 입력받음
  unsigned char **p; //연령별 윗몸 일으키기 횟수를 저장할 2차원 포인터
  int age, age_step, member, temp, sum;

  printf("20대부터 시작해서 연령층이 몇 개 인가요 : ");
  scanf("%d", &age_step);

  p_limit_table = (unsigned char *)malloc(age_step);
  p = (unsigned char **)malloc(sizeof(unsigned char *) * age_step); //!!1차원 포인터 공간 먼저 확보!!

  for(age = 0; age < age_step; age++){ //연령별로 윗몸일으키기 횟수 입력받음
    printf("\n %d 0대 연령의 윗몸 일으키기 횟수 \n", age+2);
    
    printf("이 연령대는 몇 명입니까? : ");
    scanf("%d", &temp);
    *(p_limit_table + age) = (unsigned char)temp;

    *(p + age) = (unsigned char *)malloc(*(p_limit_table + age)); //입력받은 인원수만큼 메모리 할당. !!실제 값이 저장될 공간 만들기!!

    for(member = 0; member < *(p_limit_table + age); member++){
      printf("%d th: ", member + 1);
      scanf("%d", &temp);
      *(*(p + age) + member) = (unsigned char)temp;
    }
  }

  printf("\n\n 연령별 평균 윗몸일으키기 횟수 \n");
  for(age = 0; age <age_step; age++){
    sum = 0;

    printf("%d 0대 : ", age+2); //20대, 30대, 40대라고 출력함
    for(member = 0; member < *(p_limit_table + age); member++){
      sum = sum + *(*(p + age) + member); //해당 연령 사람들의 횟수 합산함
    }

    printf("%5.2f \n", (double)sum / *(p_limit_table + age));
    free(*(p_age));
  }

  free(p);
  free(p_limit_table);
}

연습문제 3) char data[2][3]; 과 같은 용도로 사용할 수 있도록 2차원 포인터 변수 p와 malloc 함수를 사용하여 코드를 구성해라.

void main(){
  char **p;
  int i;
  
  p = (char **)malloc(sizeof(char *) * 2);
  for(i = 0; i < 2; i++){
    *(p + i) = (char *)malloc(sizeof(char) * 3);
  }

  for(i = 0; i < 2; i++){
    free(*(p + i));
  }
  free(p)
}

18. 구조체와 연결 리스트

typedef 문법

타입을 정의한다는 의미의 ‘type define’의 줄임 표현이며, 기존의 자료형 중에 자료형 이름의 길이가 긴 경우 프로그래머가 짧고 간결하게 자료형을 재정의하는 문법이다. #define은 치환 작업을 수행하는 전처리기이고, typedef는 기존 자료형을 다른 이름으로 새롭게 정의하는 기능이다.

typedef unsifned short int US;
US temp; //unsigned short int temp;라고 선언한 것과 같음

변수 선언과 착각하지 않도록 새 자료형의 이름은 모두 대문자로 적는 경우가 많다. 모두 대문자는 다른사람이 만든거니 건들지마.

typedef의 장점은 복잡해 보이는 문법을 쉽게 표현할 수 있으며 자료형의 크기를 쉽게 바꿀 수 있다.(유지보수성 ↑, 데이터의 크기에 변화가 생겼을 때 쉽게 대처 가능)


데이터를 그룹으로 묶는 구조체

데이터의 그룹화 1 : 배열. 배열은 크기가 같은 데이터만 그룹으로 묶을 수 있다.

데이터의 그룹화 2 : 구조체

struct People{
  char name[12]; //이름, 12바이트. 얘네를 멤버라고 부름
  unsigned short int age; //나이 2바이트. 얘네를 멤버라고 부름
  float height; //키 4바이트. 얘네를 멤버라고 부름
  float weight; //몸무게 4바이트. 얘네를 멤버라고 부름
}

struct와 typedef를 조합해서 사용(그냥 이거알면 될듯)

typedef struct People{
  char name[12];
  unsigned short int age;
  float height;
  float weight;
}Person;   //첫번째줄 typedef와 맨마지막줄 Person 눈여겨 봐라!

예제) 구조체를 사용해서 사람의 신체 정보를 입력 받고 출력하기

typedef struct People{
  char name[12];
  unsigned short int age;
  float height;
  float weight;
}Person;

void main(){
  Person data; //person 자료형으로 data 변수 선언. 꼭 해줘야 함!

  printf("이름 : ");
  scanf("%s", data.name);

  printf("나이 : ");
  scanf("%hu", &data.age);

  printf("키 : ");
  scanf("%f", &data.height);

  printf("%s : %d세, %.1f cm", data.name, data.age, data.height);
}

구조체 자료형인 Person으로 배열 변수를 선언한 경우에도 각 요소에 접근하는 방식은 같다. 그냥 배열은 . 연산자를 사용

Person friends[3]; //person 데이터 3개를 저장할 수 있는 메모리를 할당
friends[1].age = 22; //두번째 요소의 age에 값 22를 대입

구조체로 선언한 변수를 포인터로 사용하기

포인터 적용시 -> 쓴다.

Person data; //person 자료형으로 data 변수 선언
Person *p; //person 형식으로 선언한 메모리에 접근할 수 있는 포인터 선언
p = &data; //포인터 변수 p는 data 변수의 주소 값을 저장
(*p).age = 23; //p에 저장된 주소에 가서 age 요소에 값 23 대입
p->age = 23;   //위와 같은 방식. 위에거 불편하니 이거 씀. ->양옆 공백을 띄우지 않음.  

배열과 구조체

skip~~~


struct Test *p1 = (struct Test *)malloc(16); //설정에 따라 오류가 발생할 수 있음
struct Test *p2 = (struct Test *)malloc(sizeof(struct Test)); // 이렇게 사용해야 함! 고정적으로 사이즈 주지 말고 동적이게. 변경되도 대응 가능하게.

구조체를 활용한 연결 리스트

구조체는 C의 꽃~~ 파이널~ 포인터와 배열이 다나옴

구조체를 사용하면(연결리스트, 링크드리스트) -> 효율적으로 저장할 수 있는 데이터 저장 방법. 메모리 관리에 효율적! 배열은 뭉쳐서 크게 써야하지만 링크드는 각자 떨어져도 연결되어있기 때문에 사용 가능.

#include <malloc.h>

typedef struct node{
  int number;
  struct node *p_next;    //다음 노드를 기리킬 포인터
}NODE;

void AddNumber(NODE **pp_head, NODE **pp_tail, int data){

  if(NULL != *pp_head){

    (*pp_tail)->p_next = (NODE *)malloc(sizeof(NODE));
    *pp_tail = (*pp_tail)->p_next;    //p_tail(*pp_tail)에 새 노드의 주소 값 저장. 꼬리 새로워짐

  }else{

    //p_head 값이 NULL이라서 첫 노드가 추가됨. p_head 값에 직접 대입함
    *pp_head = (NODE *)malloc(sizeof(NODE));
    *pp_tail = *pp_head;    //새 노드의 주소 값을 p_tail(*pp_tail)에 저장
  }

  
  (*pp_tail)->number = data;    //새 노드의 number에 data 값을 저장
  (*pp_tail)->p_next = NULL;    //다음 노드가 없음을 명시

}

void main(){

  NODE *p_head = NULL, *p_tail = NULL, *p;
  int sum = 0, temp;
  
  while(1){
    printf("숫자를 입력하세요(9999를 누르면 종료) : ");
    scanf("%d", &temp);

    if(9999 == temp){
      break;
    }

    AddNumber(&p_head, &p_tail, temp);
  }

  p = p_head;  
  while(NULL != p){

    if(p != p_head){
      printf(" + ");
    }
    printf("%d", p->number);    //입력한 숫자 출력
    sum = sum + p->number;    //입력한 숫자 합산
    p = p->p_next;    //다음 노드로 이동
  }
  printf(" = %d \n", sum);    //합산 값 출력

  while(NULL != p_head){
    p = p_head;    //현재 노드를 삭제하기 위해 p변수에 노드 주소 값을 저장
    p_head = p_head->p_next;    //시작 위치를 다음 노드로 옮김
    free(p);
  }
  p_tail = p_head;    //반복문을 나오면 p_head 값은 NULL. p_tail값도 NULL로 변함
}

p493 그림 참고…


Comments