Pointer는 가리키다라는 뜻이 있다.
C에서 다른 변수, 혹은 그변수의 메모리 공간 주소를 저장하는 변수로 사용된다.
메모리는 컴퓨터의 물리적인 공간으로, 변수는 이 메모리 공간에 값을 저장하고 접근하는데 사용된다.
포인터가 가리키는 값을 가져오는 것을 역참조라고 한다.
포인터 p라는 변수에 a라는
포인터 변수 p = (*p) | |||
*p의 주소 : 3000 *p = &a[0] = 1000 |
주소 : 3004 | 주소 : 3008 | 주소 : 3012 |
먼저 int a[4]의 변수가 있고,
p라는 포인터 변수에 a의 첫번째 인덱스 값을 넣는다.
int *p = &a[0]; (포인터 변수 p는 a[0]의 주소 1000을 가리키고 있다.)
-> 포인터 변수의 주소와 int a[4]의 주소는 임의로 설정한 주소이다.
int a[4] | |||
a[0] 주소 : 1000 a[0]의 값 : 15 |
a[1] 주소 : 1004 a[1]의 값 : 25 |
a[2] 주소 : 1008 a[2]의 값 : 35 |
a[3] 주소 : 1012 a[3]의 값 : 45 |
그리고 for문을 통해 출력해보면 a[0]부터 a[3]까지의 주소가 나온다.
이번엔 int Num, p, p2의 어떤 데이터를 저장하고,
어떤 데이터를 주고 받는지 알아보자.
int Num = 5;
printf("Num이 저장하고 있는 값은 %d 입니다.\n", Num); // 5
printf("Num의 위치(주소값)는 %p 입니다.\n ", &Num); // Num의 주소값
int* p = &Num; // 포인터 변수 p는 Num의 주소값을 저장하는 정수형 메모리 공간이다.
printf("p가 저장하고 있는 값은 %p 입니다.\n", p); // Num의 주소값 출력
printf("Num의 위치(주소값)는 %p 입니다.\n",&p); // p의 주소값 출력
printf("p가 가르키고 있는(주소값)은 %d 입니다.\n", *p); // p가 가르키고 있는 값은 5출력
*p = 10; // p가 가리키는 것을 10으로 초기화
printf(" p가 가르키고 있는(주소값)은 %d 입니다.\n", *p); // p가 가리키는 것의 값은 10 출력
printf("Num이 저장하고 있는(주소값)은 %d 입니다.\n",Num); // Num이 저장하고 있는 값은 10 출력
*p -= 1; // p가 가리키는 것에 -1
printf(" p가 가르키고 있는(주소값)은 %d 입니다. \n", *p); // -1된 p가 가리키는 것에 값은 9 출력
printf("Num이 저장하고 있는(주소값)은 %d 입니다. \n", Num); // Num이 저장하고 있는 값은 9 출력
int* p2 = &Num; // 포인터 변수 p2는 Num의 주소값을 저장하는 정수형 메모리 공간이다.
*p2 = 100; // p2가 가리키는 것을 100으로 초기화
printf(" p가 가르키고 있는(주소값)은 %d 입니다.\n", *p); // p가 가리키고 있는 값은 100 출력
printf(" p가 가르키고 있는 변수의 값은 %d 입니다.\n", *p); // p가 가리키고 있는 값은 100 출력
printf("Num이 저장하고 있는(주소값)은 %d 입니다.\n" ,Num);// Num이 저장하고 있는 값은 100 출력
포인터 변수 p, p2는 Num의 주소값을 저장하고 있다.
p와 p2가 가리키는 것을 초기화해준다면 p가 가리키는 것이나,
p2가 가리키는 것이나 같은 것을 볼 수 있다.

그렇다면 이번엔 내가 배웠던 배열 중 배열의 이름은 배열의 시작 주소를 의미한다.
예제를 통해 배열과 주소의 관계를 알아보자.
int Arr[5] = {1, 2, 3, 4, 5}; // 배열의 이름 Arr은 배열의 시작 주소이다.
printf("Arr : %p\n ", Arr); // 이를 출력시 Arr의 첫번째 주소가 나오게 된다.
printf("*Arr : %d\n ",*Arr); // 이를 출력시 Arr이 가르키는 첫번째 요소의 값인 1이 나오게 된다. *(Arr + 0) = 1
for(int i = 0; i < 5; ++i) printf("Arr[%d] 의 주소 : %p\n" , i, &Arr[i]);
// 이를 출력시
// Arr[0] = 첫번째 요소의 주소값
// Arr[1] = 첫번째 요소의 주소값 + 4
// Arr[2] = Arr[1] = 첫번째 요소의 주소값 + 4 + 4
// Arr[3] = Arr[1] = 첫번째 요소의 주소값 + 4 + 4 + 4
// Arr[4] = Arr[1] = 첫번째 요소의 주소값 + 4 + 4 + 4 + 4
for(int i = 0; i < 5; ++i) printf("Arr + %d : %p\n" , i, Arr + i);
// 이를 출력시
// Arr + 0 = Arr[0]의 주소값
// Arr + 1 = Arr[1]의 주소값
// Arr + 2 = Arr[2]의 주소값
// Arr + 3 = Arr[3]의 주소값
// Arr + 4 = Arr[4]의 주소값
int* pArr = Arr; // int* pArr = &Arr 과 같은 의미이다. 왜냐하면 Arr은 배열의 시작 주소이기 때문에
for(int i = 0; i < 5; ++i) printf("pArr이 가르키고 있는 Arr[%d] 주소의 값 : %d \n", i, pArr[i]);
// 이를 출력시
// pArr이 가르키고 있는 Arr[0] 주소의 값 : Arr[0]의 주소값이 나오게 된다.
// pArr이 가르키고 있는 Arr[1] 주소의 값 : Arr[1]의 주소값이 나오게 된다.
// pArr이 가르키고 있는 Arr[2] 주소의 값 : Arr[2]의 주소값이 나오게 된다.
// pArr이 가르키고 있는 Arr[3] 주소의 값 : Arr[3]의 주소값이 나오게 된다.
// pArr이 가르키고 있는 Arr[4] 주소의 값 : Arr[4]의 주소값이 나오게 된다.
for(int i = 0; i < 5; ++i) printf("pArr이 가르키고 있는 Arr[%d] 주소의 값 : %d \n", i, *(pArr + i));
// 이를 출력시
// pArr이 가르키고 있는 Arr[0] 주소의 값 : *(Arr + 0)의 주소값이 나오게 된다.
// pArr이 가르키고 있는 Arr[1] 주소의 값 : *(Arr + 1)의 주소값이 나오게 된다.
// pArr이 가르키고 있는 Arr[2] 주소의 값 : *(Arr + 2)의 주소값이 나오게 된다.
// pArr이 가르키고 있는 Arr[3] 주소의 값 : *(Arr + 3)의 주소값이 나오게 된다.
// pArr이 가르키고 있는 Arr[4] 주소의 값 : *(Arr + 4)의 주소값이 나오게 된다.
int A = 10; int B = 20;
int* p = &A;
printf("*p = %d \n", *p);
// 포인터 변수 p에 A의 주소값이 저장되어 있고 포인터 변수 p가 가르키고 있는 값은 10이다 .
p = &B; // p라는 메모리 공간에 B의 주소값을 초기화해준다. 만약 p를 가르킬 시 20이라는 값이 나오게 된다.
printf("*p = %d \n", *p);
// 포인터 변수 p에 B의 주소값이 저장되어 있고 포인터 변수 p가 가르키고 있는 값은 20이다.
요번엔 const를 사용하여 배열이 왜 포인터 상수인지 알아보고 포인터와 연관지어보자.
int A = 10; int B = 20;
// 포인터
int* p1 = &A;
*p1 = 50; // 포인터로 값의 변경이 가능하고
p1 = &B; // 주소값 또한 변경이 가능하다
printf(" p1 = %p \n" , p1);
printf(" p1 = %d \n" , *p1);
// 상수 포인터 : 값의 변경만 가능하다!
const int* p2 = &A;
*p2 = 30; // 포인터로 값의 변경이 불가능한 상수 포인터 (error)
p2 = &B; // 하지만 주소값의 변경은 가능하다.
// 포인터 상수 : 주소값의 변경만 가능하다!
int* const p3 = &A;
*p3 = 40; // 포인터로 값의 변경이 가능한 포인터 상수이다.
p3 = &B; // 하지만 주소값의 변경은 불가능하다. (error)
// 배열과 마찬가지로 주소의 변경은 불가능하나 요소의 값은 변경 가능하다.
// 배열은 포인터 상수이다.
// 상수 포인터 상수
const int* const p4 = &A;
*p4 = 50; // 포인터로 값의 변경이 불가능하다.
p4 = &B; // 주소값 역시 변경이 불가능한 상수 공간이다.
이젠 함수의 매개변수를 포인터 형식으로 만들어 어떠한 데이터들을 주고 받는지 알아보자.
void CallByValue(int a) // 일반 매개변수 : 메인 함수에서 int A = 5;라는 값을 받아왔다. (int a = 5;)
{
printf(" a value : %d \n" , a); // a라는 매개변수는 5
printf(" a address : %p \n" , &a); // a라는 매개변수의 주소값은 a의 매개변수 a의 주소값이 나옴
a = 10; // a를 10으로 초기화 즉 int A = 10; (다시 아래로)
}
void CallMyAddress(int* a) // 포인터 매개변수 : 메인 함수에서 &A를 받아왔다. (int* a = &A);
{
printf(" a value : %d \n" , a); // a라는 매개변수는 5가
printf(" a address : %p \n" , &a); // a라는 매개변수의 주소값은 A의 주소값이 나옴
*a = 10; // a가 가리키는 것(&A)을 10으로 초기화 (다시 아래로)
}
int main()
{
int A = 5; // A라는 메모리 공간에 5라는 값을 저장과 동시에 A의 메모리 주소가 생성된다.
printf("A address : %p \n" , &A); // A의 주소 출력
printf("A value : %d \n" , A); // A의 값 5를 출력
CallByValue(A); // 함수 호출 : CallByValue에 A라는 변수를 매개변수로 넘긴다. (위로가서)
printf("A value : %d \n" , A); // A의 값은 5가 나오게 된다. 왜냐하면 함수가 종료되고 나면 소멸된다.
CallMyAddress(&A); // 함수 호출 : CallMyAddress에 A의 주소값을 매개변수로 넘긴다. (다시 위로)
printf("A address : %d \n" , A); // A의 값은 10이 나오게 된다. 왜냐하면 함수의 포인터 매개변수를 통해
// 메인함수의 A라는 변수의 주소에 값을 넣어 저장(초기화)를했기 때문에
// A는 10이 나오게 된다.
return 0;
}
이젠 구조체(struct)와 typedef을 통해 나만의 메모리 공간을 정의하여
그 공간을 포인터 변수로 활용하고, 직접접근과 간접접근 연산자는 무엇이고 어떻게 사용하는지 알아보자.
typedef struct tag_Wallet // tag_Wallet이라는 구조체 변수를 선언과 동시에 사용자 정의 자료형 선언
{
const char* Name; // 상수 포인터 Name이라는 변수 생성 (배열과 같은 형태)
int Money; // Money라는 변수 생성
}Wallet; // Wallet이라는 별칭 선언
void AddSalary(Wallet* wallet) // Wallet이라는 구조체 자료형을 가진 포인터 매개변수 wallet을 포함하는 함수 선언
{
wallet->Money += 10000; // wallet 변수가 Wallet의 멤버 Money에 간접접근하여 값을 변경해줌
}
void PrintWallet(Wallet wallet) // Wallet이라는 구조체 자료형을 가진 매개변수 wallet을 포함하는 함수 선언
{
printf("Name : %s \n" , wallet.Name); // 구조체 변수 Wallet에 있는 Name에 직접접근하여 값을 출력
printf("Money : %d \n" , wallet.Money); // 구조체 변수 Wallet에 있는 Money에 직접접근하여 값을 출력
}
int main()
{
Wallet wallet; // 구조체 변수 Wallet 자료형을 가진 wallet 변수 선언
wallet.Name = "My Wallet"; // wallet의 멤버 Name에 직접접근하여 "My Wallet" 이라는 상수를 저장
wallet.Money = 1000; // wallet이 멤버 Money에 직접접근하여 1000을 저장
PrintWallet(wallet); // wallet을 PrintWallet 함수의 매개변수로 보내면서 호출
Wallet* pWallet = &wallet; // Wallet자료형인 포인터 변수 pWallet은 wallet의 주소값을 저장
(*pWallet).Money = 3000; // 포인터 변수 pWallet이 Money에 직접 접근하여 3000을 저장
pWallet->Money = 3000; // 변수 pWallet이 Money에 간접 접근하여 3000을 저장
PrintWallet(*pWallet); // *pWallet을 PrintWallet 함수의 매개변수로 보내면서 호출
AddSalary(pWallet); // pWallet을 AddSalary 함수의 매개변수로 보내면서 호출
PrintWallet(*pWallet); // *pWallet을 PrintWallet 함수의 매개변수로 보내면서 호출
AddSalary(&wallet); // wallet의 주소값을 AddSalary 함수의 매개변수로 보내면서 호출
PrintWallet(wallet); // wallet을 PrintWallet 함수의 매개변수로 보내면서 호출
return 0;
}
'c' 카테고리의 다른 글
17_정적변수(static) (2) | 2023.04.23 |
---|---|
16_지역, 전역변수(Local, Global Variable) (0) | 2023.04.20 |
14_함수(Function) (0) | 2023.04.14 |
13_문자열(string) (0) | 2023.04.11 |
12_배열(Array) (0) | 2023.04.08 |
댓글