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