본문 바로가기
  • 適者生存
WorkOut/정보처리기사

C언어 | 포인터

by lcrvvxln 2024. 3. 19.

 

적자생존

 

13. 포인터 (Pointer)


- 변수 주솟값을 저장하는 공간


 

(1) 포인터 선언

자료형* 포인터_변수명 = &변수명;
  • 자료형 뒤 ' * '를 붙이면 주소 저장하는 포인터 변수, 주소에 해당하는 값 가리킬 때 ' * ' 사용
  • 일반 변수명 앞에 '&'를 붙이면 해당 변수의 주솟값
- * 연산
: 주소에 해당하는 값을 가리키는 연산

- & 연산
: 변수의 주솟값을 나타내는 연산

> 서로 반대 기능이기 때문에 *(&a)처럼 같이 사용하면 서로 상쇄되서 본래 a와 동일한 의미

- int 형 변수 가리키는 포인터 변수 선언 시, int*
- char 형 변수 가리키는 포인터 변수 선언 시, char*
- float 형 변수 가리키는 포인터 변수 선언 시, float*


#incldue <stdio.h>

int main(){
   int a=10;
   int* b=&a;
   
   printf('%d %d %d', a, *b, *(&a));
   
   return 0;
}

# 10 10 10
  • 정수형 변수 a 10 선언
  • 정수형 포인터 변수 b 선언 > 정수형 변수 a의 주솟값을 대입 > 포인터 b는 a의 주솟값을 가리킴
  • a 10 출력
  • *b는 포인터 변수 b가 가리키는 곳의 값을 의미,  a의 주솟값에 저장된 a 값인 10 > b가 가리키는(*) 값은 a 이므로 *b=a
  • *(&a)는 a의 주솟값이(&a) 가리키는 값(*)이므로 a를 의미 > &와 *은 반대 연산으로 만나면 상쇄됨

 

(3) 배열과 포인터


- 자료형 배열명[요소];

  • 배열 i번지 주소 : 배열+i == &배열[i];
  • 배열 i번지 값 : *(배열+i) == 배열[i];

 

[1] 1차원 배열과 1차원 포인터


- 1차원 배열은 배열명 단독 사용 시, 1차원 포인터와 동일

  • 1차원 배열 : 배열명[요소] 형태, *(배열명+요소) 형태 = 값
  • 1차원 포인터 : 포인터[요소]형태, *(포인터+요소) 형태 = 값

 

#include <stdio.h>

int main(){
   int a[3]={1,2};
   int *p=a;
   
   printf('%d %d %d\n', a[0],a[1],a[2]);
   printf('%d %d %d\n', *a, *(a+1), *(a+2));
   printf('%d %d %d\n', *p, *(p+1), *(p+2));
   printf('%d %d %d\n', p[0], p[1], p[2]);
   
   return 0;
}

# 1 2 0
# 1 2 0
# 1 2 0
# 1 2 0
  • 정수형 배열 a 선언, a[0]=1, a[1]=2 으로 초기화 후, 비어있는 값인 a[3]=0으로 초기화
  • 정수형 포인터 변수 p 선언 후 a 대입 > a는 &a[0]과 동일, &a[0]은 a 배열 0번째 값의 주솟값을 의미
  • int *를 빼면 p=a 이므로 p에 들어있는 값과 a에 들어있는 값이 같음, a 대신 p를 써도 동일한 결과 출력
  • a 배열 0, 1, 2번째 값 순서대로 출력
  • a=&a[0] 이므로 *a=*(&a[0]), *와 &는 반대 연산이므로 상쇄 되어 a[0], 배열+i==&배열[i] 이므로 a+0==&a[0]
  • > a+1=&a[1] 이므로 *(a+1)=*(&a[1])=a[1]
  • >a+2=&a[2] 이므로 *(a+2)=*(&a[2])=a[2]
  • p 포인터 변수에 저장된 주솟값 = a 배열 주솟값 이므로 *p=*a=a[0], *(p+1)=*(a+1)=a[1], *(p+2)=*(a+2)=a[2]
  • p 포인터 변수에 저장된 주솟값 = a 배열 주솟값 > p[0]=a[0], p[1]=a[1], p[2]=a[2]

 

- 1차원 배열과 1차원 포인터 정리

a = &a[0]
a+1 = &a[1]
..

*a= a[0]
*(a+1) = a[1]
*(a+2) = a[2]
...

 

[2] 2차원 배열과 1차원 포인터


- 2차원 배열은 배열명 단독 사용 시, 2차원 포인터와 동일

  • 2차원 배열 : 배열명[요소], *(배열명+요소)는 1차원 포인터와 동일
  • 1차원 포인터에 대해 * 과 [ ]을 사용해야 값을 가리킬 수 있음

 

#include <stdio.h>

int main(){
   int a[3][2] = {{1,2},{3,4},{5,6}};
   int *p=a[1];
   
   printf('%d %d %d\n', *a[0],*a[1],*a[2]);
   printf('%d %d %d\n', **a, **(a+1), **(a+2));
   printf('%d %d\n', *p, *(p+1));
   printf('%d %d\n', p[0], p[1]);
   
   return 0;
}

# 1 3 5
# 1 3 5
# 3 4
# 3 4
  • 정수형 2차원 배열 a 3행*2열 선언, a[0][0]=1, a[0][1]=2, a[1][0]=3, a[1][1]=4, a[2][0]=5, a[2][1]=6
  • 정수형 포인터 변수 p 선언 후 배열 a의 1번째 값인 a[1][0]의 주소를 대입, p=a[1]=&a[1][0] 이므로 *p는 a[1][0]이 가리키는 값인 3을 의미
  • *a[0]은 2차원 배열 a의 1행 0번째 주솟값이 가리키는 값을 의미, a[0]=&a[0][0] > *a[0]=*(&a[0][0])=a[0][0]= 1 출력
  • > *a[1]은 2차원 배열 a의 2행 0번째 주솟값이 가리키는 값을 의미, a[1]=&a[1][0] > *a[1]=*(&a[1][0])=a[1][0]= 3 출력
  • > *a[2]는 2차원 배열 a의 3행 0번째 주솟값이 가리키는 값을 의미, a[2]=&a[2][0] > *a[2]=*(&a[2][0])=a[2][0]= 5 출력
  • **a> a는 &a[0] 이므로 **a= **(&a[0])=*a[0]=*(&a[0][0])=a[0][0]=1 출력
  • **(a+1) > a+1은 &a[1] , **(a+1)=**(&a[1])=*a[1]=*(&a[1][0])=a[1][0]=3 출력
  • **(a+2) > a+2는 &a[2], **(a+2)=**(&a[2])=*a[2]=*(&a[2][0])=a[2][0]= 5 출력
  • *p > p=a[1] 이므로 *p=*a[1]=*(&a[1][0])=a[1][0]= 3 출력, *(p+1) > p=a[1] 이므로 *(p+1)=*(a[1]+1)=*(&a[1][0]+1)=*(&a[1][1])=a[1][1]=4 출력
  • p[0] > p=a[1]이므로 p[0]=(a[1])[0]= 3 출력, p[1] > p=a[1]이므로 p[1]=(a[1])[1]= 4 출력

 

- 2차원 배열과 1차원 포인터

a는 2차원 배열 a[3][2]

a[0]=&a[0][0]
a[1]=&a[1][0]
...

a=&a[0]
a+1=&a[1]
...

*a=*(&a[0])=a[0]
*(a+1)=*(&a[1])=a[1]
...

**a=**(&a[0])=*a[0]=*(&a[0][0])= a[0][0]
**(a+1)=**(&a[1])=*a[1]=*(&a[1][0])=a[1][0]
...



[3] 2차원 배열과 포인터 배열

 

#include <stdio.h>

int main(){
   int a[3][2]={{1,2},{3,4},{5,6}};
   int *p[3]={a[2],a[0],a[1]};
   
   printf('%d %d %d\n', a[0][0], a[1][0], a[2][0]);
   printf('%d %d %d\n', *a[0], *a[1], *a[2]);
   printf('%d %d %d\n', p[1][0], p[2][0], p[0][0]);
   printf('%d %d %d\n', *p[1], *p[2], *p[0]);
   
   retrun 0;
}

# 1 3 5
# 1 3 5
# 1 3 5
# 1 3 5
  • 정수형 2차원 배열 a 3행*2열 선언 > a[0][0]=1  a[0][1]=2, a[1][0]=3, a[1][1]=4, a[2][0]=5, a[2][1]=6
  • 정수형 포인터 배열 p 선언 > p[0]=a[2]=&a[2][0], p[1]=a[0]=&a[0][0], p[2]=a[1]=&a[1][0]
  • 2차원 배열 a의 0번째 행 0번째 값 1, 1번째 행 0번째 값 3, 2번째 행 0번째 값 5 출력
  • *a[0] > a[0]=&a[0][0] 이므로 *(&a[0][0])=a[0][0]=1, *a[1] > a[1]=&a[1][0]이므로 *(&a[1][0])=a[1][0]=3, *a[2] > a[2]=&a[2][0]이므로 *(&a[2][0])=a[2][0]=5 출력
  • p[1][0] > p[1]=a[0]이므로 p[1][0]=a[0][0]=1, p[2][0] > p[2]=a[1]이므로 p[2][0]=a[1][0]=3, p[0][0] > p[0]=a[2] 이므로 p[0][0]=a[2][0]=5 출력
  • *p[1] > p[1]=a[0]이므로 *a[0]=*(&a[0][0])=a[0][0]=1, *p[2] > p[2]=a[1]이므로 *a[1]=*(&a[1][0])=a[1][0]=3, *p[0] > p[0]=a[2]이므로 *a[2]=*(&a[2][0])=a[2][0]=5 출력



[4] 2차원 배열과 2차원 포인터

- 2차원 배열은 배열명 단독 사용 시, 2차원 포인터와 동일

  • 2차원 배열 : 배열명[요소][요소], *배열명[요소], **(배열명+요소) 일때, 값 가리킴
- 2차원 포인터

int **p, **q 형태로 선언할 수 있음

BUT, 2차원 포인터 p,q는 2차원 배열에서 **한 덩어리의 크기를 알 수 없으므로** 열 개수 명시하는 형태로 포인터 변수 선언

> 포인터 변수 선언 시, **괄호** 없으면 2차원 배열에 대한 포인터가 아닌 **포인터 배열**이 되므로 주의

자료형 (*포인터 변수) [열의 크기];  = 2차원 포인터 = 2차원 배열에 대한 포인터
자료형 *포인터 변수 [열 크기]; = 포인터 배열

 

# include <stdio.h>

int main(){
  int a[3][2]={{1,2},{3,4},{5,6}};
  int (*p)[2]=a;
  int (*q)[2]=a+1;
  
  printf('%d %d %d\n', a[0][0], a[0][1], a[1][0]);
  printf('%d %d %d\n', p[0][0], p[0][1], p[1][0]);
  printf('%d %d %d\n', q[0][0], q[0][1], q[1][0]);
  
  return 0;
}

# 1 2 3
# 1 2 3
# 3 4 5
  • 정수형 2차원 배열 a 3행*2열 선언 > a[0][0]=1, a[0][1]=2, a[1][0]=3, a[1][1]=4, a[2][0]=5, a[2][1]=6
  • 정수형 2차원 포인터 배열 p 선언 a 저장 > p가 2차원 배열이므로 a[0][0] 번지 2차 주소 저장
  • > a=&a[0]=&(&a[0][0]) > 변수 자료형인 int*와 열 개수 나타내는 [2] 제외 시, p=a이므로 p와 a는 동일한 값
  • 정수형 2차원 포인터 배열 q 선언 a+1 저장 > q는 2차원 배열이므로 a[1][0] 번지 2차 주소 저장
  • > a+1=&a[1]=&(&a[1][0]) > 변수 자료형인 int *와 열 개수 지정하는 [2] 제외 시, q=a+1이므로 q=a+1=&a[1]
  • a[0][0]=1, a[0][1]=2, a[1][0]=3 출력
  • p[0][0] > p=a이므로 a[0][0]=1, p[0][1] > p=a 이므로 a[0][1]=2, p[1][0] > p=a이므로 a[1][0]=3 출력
  • q[0][0] > q[0][0] =*(q[0]) 이고 q=a+1=&a[1]이므로 *(q[0])=*(&a[1][0])=a[1][0]=3 출력
  • > q[0][1] > q[0][1]=*(q[0]+1) 이고 q=a+1=&a[1] 이므로 *(q[0]+1)=*(&a[1][0]+1)=*(&a[1][1])=a[1][1]=4 출력
  • > q[1][0] > q[1][0]=*(q[1]) > q=a+1 이므로 q+1=a+2=&a[2], q[1]==a[2] > *(q[1])=*(a[2])=*(&a[2][0])=a[2][0]=5 출력

 

 

(4) 구조체와 포인터

[1] 구조체 변수와 구조체 포인터

  • 일반 구조체 변수 접근 시 > ' . ' 으로 접근
  • 구조체 포인터로 접근 시 > '->' 로 접근

 

# include <stdio.h>

struct Student{
   char gender;
   int age;
};

int main(){
   struct Student s={'F',21};
   struct Student *p=&s;
   
   printf('%c %d\n', s.gender, s.age);
   printf('%c %d\n', (&s)->gender, (&s)->age);
   printf('%c %d\n', p->gender, p->age);
   printf('%c %d\n', (*p).gender, (*p).age);
   printf('%c %d\n', p[0].gender, p[0].age);
   
   return 0;
}

# F 21
# F 21
# F 21
# F 21
# F 21
  • Student 구조체 선언 > 문자형 변수 gender, 정수형 변수 age로 구성
  • Student 구조체 변수 s 선언 > gender는 'F', age는 21로 초기화
  • Student 구조체 포인터 p 선언 > 구조체 변수 s의 주솟값 저장
  • 구조체 변수 s의 gender F, age 21 변수 출력
  • 구조체 변수 s의 주솟값에 포함된 gender F, age 21 출력
  • 구조체 포인터 변수 p가 가리키는 s 주솟값에 포함된 변수 gender와 age 출력
  • 구조체 포인터 변수 p가 가리키는 값(s 구조체)의 gender와 age 출력
  • 구조체 포인터 변수 p는 1차원 포인터이므로 p[0]=*(&s), s 구조체에 접근하여 변수 gender와 age 출력

 

 

[2] 1차원 구조체 배열과 1차원 구조체 포인터

 

- 1차원 구조체 배열, 배열명 단독 사용 시 1차원 구조체 포인터와 동일

 

  • 1차원 구조체 배열 : 배열명[요소].변수명, (*(배열명+요소)).변수명, 배열명-> 변수명, (배열명+요소)->변수명 
  • 1차원 포인터 : 포인터[요소].변수명, (*포인터+요소)).변수명, 포인터-> 변수명, (포인터+요소)-> 변수명

 

#include <stdio.h>

struct Student{
   char gender;
   int age;
};

int main(){
   struct Student s[3]={'F',21,'M',20,'M',24};
   struct Student *p=s;
   
   printf('%c %d\n', s[0].gender, s[0].age);
   printf('%c %d\n', (*s).gender, (*s).age);
   printf('%c %d\n', s-> gender, s-> age);
   printf('%c %d\n',(s+1)-> gender, (s+1)-> age);
   printf('%c %d\n', p[0].gender, p[0].age);
   printf('%c %d\n', (*p).gender, (*p).age);
   printf('%c %d\n', p-> gender, p-> age);
   printf('%c %d\n', (p+1)-> gender, (p+1)-> age);
   
   return 0;
}

# F 21
# F 21
# F 21
# M 20
# F 21
# F 21
# F 21
# M 20
  • Student 구조체 선언 > 문자형 gender 와 정수형 age 변수로 구성
  • Student 구조체 배열 s 선언 > s[0]=F, 21 / s[1]=M,20 / s[2]=M,34 로 초기화 
  • Student 구초제 포인터 변수 p 선언 > 구조체 배열 s 대입 > s=&s[0] 이므로 s[0]의 주솟값 대입 
  • > 자료형을 나타내는 Student * 를 빼면 p=s 이므로 p 대신 s를 넣어도 동일하게 성립
  • 구조체 배열 s의 0번째 값에 있는 gender와 age 출력 > s[0] = F , 21
  • 구조체 배열 s가 가리키는 값(=*s) 인데 s=&s[0]이므로 (*(&s[0]))= s[0] > s[0] = F, 21
  • 구조체 배열 s에 포인터로 접근 > s=&s[0]이므로 주솟값 &s[0]가 가리키는 gender와 age 출력 > s[0]= F, 21
  • > s가 배열이므로 s만 단독으로 쓰면 **1차원 포인터**가 됨 > 따라서 '->' 로 접근
  • 구조체 배열 s+1에 포인터로 접근 > s+1=&s[1] 이므로 주솟값 &s[1]이 가리키는 gender와 age 출력 > s[1]=M, 20
  • 구조체 포인터 변수 p의 0번째 값에 있는 변수 출력 > p=s 이므로 s[0] 변수 출력과 동일 > s[0] = F, 21
  • 구조체 포인터 변수 p가 가리키는 값(=*p)인데 p=s > (*s).gender=(*(&s[0])).gender=s[0].gender > s[0]= F, 21
  • 구조체 포인터 변수 p에 포인터로 접근 > p=&s[0] 이므로 구조체 배열 s의 0번째 주소에 있는 값 출력 > s[0] = F, 21
  • 구조체 포인터 변수 p+1에 포인터로 접근, p=s이므로 s+1=&s[1]= (&s[1])-> gender와 age 출력 > s[1] = M, 20

 

 

 

 

(5) 함수 포인터 

- 함수 포인터는 함수 주소 저장

- 저장한 주소의 함수 호출하는데 사용 

리턴타입 (*함수_포인터)(함수 파라미터);

 

#include <stdio.h>

void fn1(){
   printf('fn1 함수\n');
}

int fn2(int a){
   printf('fn2 함수: %d\n',a);
   return 0;
}

int main(){
   void (*pf1)();
   int (*pf2)(int);
   
   fn1();
   fn2(5);
   
   pf1=fn1;
   pf2=fn2;
   
   pf1();
   pf2(2);
   
   return 0;
   
   }
   
   # fn1 함수 
   # fn2 함수: 5
   # fn1 함수 
   # fn2 함수: 2
  • 반환값 없는 fn1 함수 선언 > 'fn1 함수' 문구 출력 
  • 정수값 반환하고 매개변수로 정수값 전달 받는 fn2 함수 선언 > 'fn2 함수: 전달받은 매개변수' 출력 후 0 반환
  • main 함수부터 시작 
  • 반환값 없고 전달인자도 없는 pf1 함수 포인터 선언
  • 정수값 반환하고 정수형 전달인자가 필요한 pf2 함수 포인터 선언 
  • fn1 함수 호출 > 'fn1 함수' 문구 출력
  • fn2 함수 호출하여 정수 5 전달 > 'fn2 함수 : 5' 출력
  • 함수 포인터 pf1에 함수 fn1 주소 저장 > pf1=fn1
  • 함수 포인터 pf2 에 함수 fn2 주소 저장 > pf2=fn2
  • 함수 포인터 pf1으로 주소값인 fn1 함수 호출 > 'fn1 함수' 문구 출력
  • 함수 포인터 pf2로 주소값인 fn2 함수 호출하여 정수 2전달 > 호출 받은 fn2 함수가 전달받은 매개변수 2를 사용하여 함수 실행 > 'fn2 함수: 2' 출력

 

 

(6) 사용자 정의 함수 포인터 반환 

- 사용자 정의 함수 반환 값으로 포인터 선택 가능

 

#include <stdio.h>
#include <string.h> #strcpy 함수 사용

char n[6];

char *hoxypot(){
   strcpy(n, 'Hello');
   return n;
}

int main(){
   char* p=hoxypot();
   
   printf('%s\n',p);
   
   return 0;
}

# Hello
  • 문자형 배열 n 선언 
  • 문자형 포인터 값 반환하는 사용자 정의 함수 hoxypot 선언 > 'Hello' 문자열을 문자형 배열 n에 복사 후, n 반환 > n=&n[0]
  • main함수 부터 시작 
  • 문자형 포인터 변수 p 선언 > 함수 hoxypot의 반환 값을 대입 
  • hoxypot의 반환 값은 strcpy 함수를 통해 n='Hello' 가 되므로 정수형 배열 n의 주솟값(=&n[0])이 됨 
  • 다시 main으로 돌아와서 문자형 포인터 변수 p를 출력하므로 배열 n의 0번째 주소부터 NULL 직전인 n[4]까지 출력 > Hello