본 내용은 해당 강의 토대로 작성
포인터
변수를 선언할 때, 컴퓨터는 변수의 메모리 공간을 만들고 그 값을 넣는다. 여기서, 변수의 메모리 공간의 위치는 주소값으로서 변수에게 할당된다. 이것은 아파트에 입주하는 것과 비슷하다. 철수, 영희, 민수가 아파트에 입주할 때, (변수선언) 각각 자신의 집을 찾아 간다.(메모리 공간) 집을 구분하고 찾아가려면 주소(주소값)를 알아야 한다. 그리고 각 문에는 암호가 있을 것이다.(변수의 값)
이를 토대로 예시를 들어보자.
#include <stdio.h>
int main(void)
{
int 철수 = 1; //변수 선언, 집의 암호
int 영희 = 2;
int 민수 = 3;
printf("철수네 주소 : %p, 암호 : %d\n", &철수, 철수);
printf("영희네 주소 : %p, 암호 : %d\n", &영희, 영희);
printf("민수네 주소 : %p, 암호 : %d\n", &민수, 민수);
return 0;
}
각 변수의 주소값은 &
로 불러올 수 있다. 이제 각 해당변수의 주소값과 값을 알 수 있다. 이 주소값은 매번 다르게 나온다. 포인터 출력타입은 %p
이다.
주소값 불러오기
포인터
는 변수의 주소값을 불러올 수 있다. 아래의 예시를 보자. 미션맨
은 각 집을 방문하여 문에 적힌 암호를 확인한다.
#include <stdio.h>
int main(void)
{
int 철수 = 1;
int 영희 = 2;
int 민수 = 3;
int * 미션맨; // 포인터 변수
미션맨 = &철수; //철수의 주소값을 가진다.
printf("미션맨이 방문하는 곳 주소 : %p, 암호 : %d\n", 미션맨, *미션맨);
미션맨 = &영희;
printf("미션맨이 방문하는 곳 주소 : %p, 암호 : %d\n", 미션맨, *미션맨);
미션맨 = &민수;
printf("미션맨이 방문하는 곳 주소 : %p, 암호 : %d\n", 미션맨, *미션맨); //주소에 있는 값이므로 *을 붙임
return 0;
}
*
을 포인터 변수 선언시 붙여준다. 데이터형은 문자형, 실수형 등이 올 수 있다. 미션맨
에 각 집의 주소가 입력되어, 이를 사용할 수 있다. 호출시 *
를 앞에 붙이면 그 주소의 값을 불러온다.
포인터로 곱하기
포인터 변수로 변수의 값을 곱할 수 있다. 각 집의 암호에 3을 곱해보자.
#include <stdio.h>
int main(void)
{
int 철수 = 1;
int 영희 = 2;
int 민수 = 3;
int * 미션맨;
미션맨 = &철수;
*미션맨 = *미션맨 * 3;
printf("미션맨이 방문하는 곳 주소 : %p, 암호 : %d\n", 미션맨, *미션맨);
미션맨 = &영희;
*미션맨 = *미션맨 * 3;
printf("미션맨이 방문하는 곳 주소 : %p, 암호 : %d\n", 미션맨, *미션맨);
미션맨 = &민수;
*미션맨 = *미션맨 * 3;
printf("미션맨이 방문하는 곳 주소 : %p, 암호 : %d\n", 미션맨, *미션맨);
return 0;
}
포인터로 빼기
새로운 포인터 변수 스파이
는 미션맨이 바꾼 암호에서 2를 뺀다.
#include <stdio.h>
int main(void)
{
int 철수 = 1;
int 영희 = 2;
int 민수 = 3;
int * 미션맨;
미션맨 = &철수;
//위의 코드 생략
int * 스파이 = 미션맨; //스파이 포인터 변수 선언
printf("\n... 스파이가 미션 수행하는 중 ...\n\n");
스파이 = &철수; //철수의 주소 입력
*스파이 = *스파이 -2; //철수 = 철수 -2
printf("스파이가 방문하는 곳 주소 : %p, 암호: %d\n", 스파이, *스파이);
스파이 = &영희;
*스파이 = *스파이 -2;
printf("스파이가 방문하는 곳 주소 : %p, 암호: %d\n", 스파이, *스파이);
스파이 = &민수;
*스파이 = *스파이 -2;
printf("스파이가 방문하는 곳 주소 : %p, 암호: %d\n", 스파이, *스파이);
return 0;
}
여기서, 스파이
변수를 선언할 때, 미션맨
으로 하였다. 즉, 두 개의 포인터 변수가 하나의 메모리 주소를 가르킬 수 있다.
포인터로 변수 값 변경
포인터 변수에 의해서 바뀐 값들은 변수에 적용된다.
#include <stdio.h>
int main(void)
{
//위의 코드 생략
printf("\n...철수 영희 민수는 집에 오고서는 바뀐 암호를 보고 깜놀 ...\n");
printf("철수네 주소 : %p, 암호 : %d\n", &철수, 철수);
printf("영희네 주소 : %p, 암호 : %d\n", &영희, 영희);
printf("민수네 주소 : %p, 암호 : %d\n", &민수, 민수);
}
포인터 변수의 주소값
포인터 변수들 또한 주소값을 가진다.
#include <stdio.h>
int main(void)
{
//위의 코드 생략
printf("미션맨의 주소 : %p\n", &미션맨);
printf("스파이의 주소 : %p\n", &미션맨);
return 0;
}
주소값
위에서 본 대로, 메모리 주소는 16진수로 표현된다. 형식은 0x16진수
이다. 시스템이 32비트이면 8자리이고 64비트이면 16자리이다. 또한, 높은 자릿수의 0은 생략된다.
배열과 포인터의 관계
포인터를 사용해서 배열을 표현할 수 있다.
#include <stdio.h>
int main(void)
{
int arr[3] = { 5, 10, 15 };
int * ptr = arr; //포인터에 배열이 입력
for(int i=0; i<3; i++) //배열의 모든 데이터 출력
{
printf("배열 arr[%d]의 값 : %d\n", i, arr[i]);
}
for(int i=0; i<3; i++) //포인터에 입력된 모든 배열의 데이터 출력
{
printf("포인터 ptr[%d]의 값 : %d\n", i, ptr[i]);
}
return 0;
}
포인터 ptr
에 배열arr
이 입력되면 그 각각의 원소들도 들어가게 된다. 즉, ptr[0]==arr[0]
이다.
포인터로 배열값 대입하기
포인터의 각 인덱스에 다른 데이터를 대입할 수 있다.
#include <stdio.h>
int main(void)
{
//위의 코드 생략
ptr[0] = 100; //포인터에 새로운 데이터 입력
ptr[1] = 200;
ptr[2] = 300;
for(int i=0; i<3; i++)
{
printf("배열 arr[%d]의 값 : %d\n", i, arr[i]);
}
for(int i=0; i<3; i++)
{
printf("포인터 ptr[%d]의 값 : %d\n", i, ptr[i]);
}
return 0;
}
그에 따라서 배열에도 새로운 값이 들어간다.
다른 표현법
배열과 포인터의 다른 표현법이 있다.
#include <stdio.h>
int main(void)
{
//위의 코드 생략
ptr[0] = 100;
ptr[1] = 200;
ptr[2] = 300;
for(int i=0; i<3; i++)
{
printf("배열 arr[%d]의 값 : %d\n", i, *(arr + i));
}
for(int i=0; i<3; i++)
{
printf("포인터 ptr[%d]의 값 : %d\n", i, *(ptr + i));
}
return 0;
}
이 코드의 출력은 위와 같다. *(arr + i) == arr[i]
이기 때문이다. 여기서 arr
자체는 arr
배열의 첫 째 주소와 동일하기 때문이다. 즉, &arr[0]
과 같은 의미이다. ptr
또한 이와 같다.
정리해서, 아래에 코드를 해보면,
#include <stdio.h>
int main(void)
{
//위의 코드 생략
printf("arr 자체의 값 : %p\n", arr);
printf("arr[0]의 주소 : %p\n", &arr[0]);
printf("arr 자체의 값이 가지는 주소의 실제 값 : %d\n", *arr);
printf("arr[0] 의 실제 값 : %d\n", *&arr[0]);
//*&는 아무것도 없는 것과 같다 &는 주소, *는 그것의 값이기 때문 서로 상쇄
return 0;
}
arr
: 해당 배열의 첫 주소 == &arr[0]
*arr
: 주소의 실제 값 == *&arr[0]
인 것을 알 수 있다. 기호 *
는 주소의 값을 부르고 &
는 주소를 불러온다. *&
처럼 서로 붙어있으면 의미가 상쇄된다.
swap
다른 두 값을 바꾸는 함수 swap
을 만들어보자.
#include <stdio.h>
void swap(int a, int b); //함수 선언
int main(void)
{
int a = 10;
int b = 20;
printf("a의 주소 : %p\n", &a); //변수의 주소 출력
printf("b의 주소 : %p\n", &b);
printf("Swap 함수 전 => a : %d, b : %d\n", a, b); //함수를 적용하기 전의 a,b값
swap(a, b); //함수 적용
printf("Swap 함수 후 => a : %d, b : %d\n", a, b); //함수 적용 후 a,b값
return 0;
}
void swap(int a, int b) //함수 정의
{
printf("(Swap 함수 내) a의 주소 : %p\n", &a); //swap 함수에서의 주소 출력
printf("(Swap 함수 내) b의 주소 : %p\n", &b);
int temp = a; //매개변수
a = b; //a -> temp, a->b, b->temp
b = temp;
printf("(Swap 함수 내) a : %d, b : %d %d\n", a, b); //swap 함수 내에서의 a,b값
}
이 함수의 결과는 아래와 같다.
함수 전의 a
와 b
의 값이 함수 처리후, 달라지지 않은 것 을 볼 수 있다. 반면에, 함수 내에서는 바뀐 것을 볼 수 있다. 그 이유는 swap
함수 내에서의 주소와 main
함수 내에서의 주소가 다르기 때문이다. 이러한 방식을 값에 의한 복사(call by value) 라고 한다. 이 방식은 매개변수temp
만 바뀔뿐 a
,b
는 변하지 않는다.
주소를 이용한다면 이를 해결할 수 있다.
#include <stdio.h>
void swap_addr(int *a, int *b); //함수 선언
int main(void)
{
int a = 10;
int b = 20;
printf("(주소값 전달)Swap 함수 전 => a : %d, b : %d\n", a, b);
swap_addr(&a, &b); //각 변수의 주소를 전달
printf("(주소값 전달)Swap 함수 후 => a : %d, b : %d\n", a, b);
return 0;
}
void swap_addr(int * a, int * b) //포인터 변수를 받음
{
int temp = *a; //주소를 값으로 변환
*a = *b;
*b = temp;
printf("(주소값 전달)Swap함수 내 a : %d, b : %d\n", *a, *b);
}
함수 내에서도 동일한 메모리 주소값을 가지므로 정상적으로 swap
이 실행된다.
포인터로 배열 값 변경하기
포인터를 이용한 함수로 배열 값을 변경할 수 있다.
#include <stdio.h>
void changeArray(int *ptr);
int main(void)
{
int arr2[3] = {10,20,30};
changeArray(arr2); //arr2은 주소이므로 &를 붙일 필요 없음
for(int i = 0; i < 3; i++)
{
printf("%d\n", arr2[i]);
}
return 0;
}
void changeArray(int * ptr)
{
ptr[2] = 50;
}
arr2
대신 changeArray(&arr2[0])
을 넣어도 된다.
포인터의 기초에 대해서 배웠다. 저번 프로젝트에서, 값을 받는 scanf
함수를 사용했을 때, 인자에 &
를 붙인 이유는 해당 주소로 값을 보내기 위해서라는 것을 알 수 있다.
물고기 키우기
포인터를 이용해 물고기 키우기 게임을 만들어보자
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
//물고가 6마리가 어항에 살고 있다.
//물이 증발한다.
//다 증발하기 전에 부지런히 어항에 물을 줘서 물고기를 살리자.
//물고기는 점점 성장한다.
int level;
int arrayFish[6]; //arrayFish 선언
int * cursor;
void decreaseWater(long ElapsedTIme);
int checkFishAlive();
void printFishes();
void initData(); //initData 선언
int main(void)
{
long startTime = 0; //게임 시작 시간, 시간은 주로 long자료형 사용
long totalElapsedTime = 0; //총 경과 시간
long prevElapsedTime = 0; //직전 경과 시간 (최근에 물을 준 시간 간격)
int num; //몇 번 어항에 물을 줄 것인지 사용자 입력
initData();
cursor = arrayFish; //cursor[0]..cursor[1]..
startTime = clock(); //현재 시간을 milisecnod(1000분의 1초)단위로 반환
while(1)
{
printFishes();
printf("몇 번 어항에 물을 주시겠어요? ");
scanf("%d", &num);
// 입력값 체크
if(num < 1 || num > 6)
{
printf("\n입력값이 잘못되었습니다\n");
continue;
}
//총 경과 시간
totalElapsedTime = (clock()-startTime) /CLOCKS_PER_SEC; //현재시간-시작시간 맨 처음 시작으로부터 입력을 받은 시간
printf("총 경과 시간 : %ld 초\n", totalElapsedTime);
//직전 물 준 시간(마지막으로 물 준 시간) 이후로 흐른 시간
prevElapsedTime = totalElapsedTime - prevElapsedTime;
printf("최근 경과 시간 : %ld 초\n", prevElapsedTime);
//어항의 물을 감소(증발)
decreaseWater(prevElapsedTime);
//사용자가 입력한 어항에 물을 준다
//1.어항의 물이 0이면? 물을 주지 않는다.
if(cursor[num-1] <= 0)
{
printf("%d번 물고기는 이미 죽었습니다. 물을 주지 않습니다\n",num);
}
//2.어항의 물이 0이 아닌 경우? 물을 준다. 100을 넘지 않는지 체크
else if(cursor[num-1]+1 <= 100)
{
//물을 준다
printf("%d번 어항에 물을 줍니다\n\n", num);
cursor[num-1]+=1;
}
//레벨업을 할 건지 확인(레벨업은 20초마다 한번씩 수행)
if (totalElapsedTime / 20>level-1) //20초에 한 번씩 레벨업 수행
{
//레벨업
level++; // level : 1 -> level : 2 -> level : 3...
printf(" ***축 레벨업! 기존 %d 레벨에서 %d레벨로 업그레이드 ***\n\n", level-1, level);
//최종 레벨 : 5
if (level ==5)
{
printf("\n\n축하합니다. 최고 레벨을 달성하였습니다. 게임을 종료합니다!");
exit(0);
}
}
//모든 물고기가 죽었는지 확인
if (checkFishAlive()==0)
{
//물고기 전부 사망
printf("모든 물고기가 사망했습니다\n");
}
else
{
//최소 한 마리 이상의 물고기는 살아 있음
printf("물고기가 아직 살아있습니다\n");
}
prevElapsedTime = totalElapsedTime; //임시로 현재 시간을 입력
// 10초 -> 15초 (5초 :prevElapsedTime -> 15초) -> 25초 (10초 계산은>)
}
return 0;
}
void initData()
{
level = 1; //게임 레벨(1~5)
for (int i = 0; i < 6; i++)
{
arrayFish[i] = 100; //어항의 물 높이 (0~100)
}
}
void printFishes()
{
printf("%3d번 %3d번 %3d번 %3d번 %3d번 %3d번\n", 1,2,3,4,5,6); //%3d 세칸, 한글 두칸, 공백 한칸 6칸 할당
for(int i=0; i <6; i++)
{
printf(" %4d ", arrayFish[i]); //6칸 할당
}
printf("\n\n");
}
void decreaseWater(long elapsedTIme)
{
for(int i=0; i<6; i++)
{
arrayFish[i] -=(level *3*(int)elapsedTIme); //3: 난이도 조절을 위한 값
if(arrayFish[i]<0)
{
arrayFish[i]=0;
}
}
}
int checkFishAlive()
{
for(int i=0; i<6; i++)
{
if(arrayFish[i]>0)
{
return 1; //하나라도 어항에 물이 있으면 참
}
return 0;
}
}
느낀점
포인터 기초는 생각보다 강의를 잘 해주셔서 이해가 잘 되었다!!
다만!!!!!!!!!!!! 문제는 마지막 프로젝트에서 문제가 생겼다. 실행은 되나 시간이 카운트가 안되는 것이었다. 강의를 다시 돌려보면서 확인하고 처음부터 입력도 다시 해보고 서치도 해봤는데 이번에는 정말 이유를 모르겠다.
다른 사람들의 코드도 긁어와서 해봤는데 나랑 똑같이 실행은 되나 시간 카운트가 안되는 것이었다. 내 짐작컨데, scanf_s
처럼 운영체제의 차이로 안되는 것일 수도 있다.
내 윈도우 컴퓨터로 해보려고 했으나 수년이 지나서 낡아버린 노트북은 크롬에도 버벅인다… 일단 보류하고 나중에 재시도 해봐야겠다. 이것때문에 3시간은 날린 것 같다…
'programming study > C' 카테고리의 다른 글
[나도코딩]C 프로그래밍 - 입문부터 게임 개발까지 (8)(2020.12.29) (0) | 2020.12.29 |
---|---|
[나도코딩]C 프로그래밍 - 입문부터 게임 개발까지 (7)(2020.12.29) (0) | 2020.12.29 |
[나도코딩]C 프로그래밍 - 입문부터 게임 개발까지 (5)(2020.12.27) (2) | 2020.12.27 |
[나도코딩]C 프로그래밍 - 입문부터 게임 개발까지 (4)(2020.12.27) (0) | 2020.12.27 |
[나도코딩]C 프로그래밍 - 입문부터 게임 개발까지 (3)(2020.12.26) (0) | 2020.12.26 |