//챕터 7-1 : 포인터
// 1 : 33 : 00
//포인터 : 메모리 주소를 저장한 변수
//자료형 변수명 = 값; 예시 : int A = 5;
//자료형* 변수명 = 메모리주소값; // 자료형쪽에 Asterisk(*)를 붙힙시다. int* A = 메모리주소값; 자료형이 정해져야 메모리주소값의 크기가 정해지기에 꼭 같이 적어야한다.
// 변수명쪽에 Asterisk(*)를 붙히면 뒤에서 배울 역참조 연산자와 심히 헷갈립니다.
// 포인터를 사용할 시 시작 메모리주소값만 알 경우 많은량의 데이터를 인자값으로 주고받을 수 있다.
#include <stdio.h>
int main(void)
{
int A = 5; // 일반 정수형 타입 변수 A
int* A; // 정수형 타입 변수 A의 메모리주소값
printf("%d", &A); // &변수명 == 변수의 주소
return 0;
}
// Main.c
//챕터 7-2 : 주소 연산자와 역참조 연산자
//Ptr == 포인터를 가리키는 약칭
// 주소 연산자: &변수명 == 변수의 메모리주소. 예제 : int A; scanf("%d", &A); == 변수 A의 메모리주소에 %d형식으로 입력해라.
// 메모리 주소를 얻을 수 있는 방법
// 1. 주소 연산자
// 2. 배열
// 배열 : 배열의 이름이 변수고 그 변수가 배열의 시작 메모리 주소를 저장한 변수(포인터). 때문에 배열의 경우 별도의 포인터 연산자를 기입할 필요가 없다.
// 주소 연산자: &변수명 == 변수의 메모리주소.
// 변수타입* 변수명 == 포인터(변수). 예시 : int* A = &B;
// 변수타입 *변수명 == 메모리 주소를 저장한 변수에 그 메모리 주소에 저장되어 있던 값을 출력하는 역참조 연산자.
// int A == 정수형 변수 A
// int &A == 변수 A가 저장된 메모리 주소. 예시 : A = 10; 면 10을 가지고 있는 A의 집주소가 &A
// int* B == 메모리 주소를 저장할 수 있는 변수(포인터). 예시 : int* B = &A; 면 10을 가지고 있는 A의 집주소가 포인터 B에 대입됨.
// int *B == 받은 메모리 주소에 저장되어 있던 값을 출력하는 역참조 연산자. 예시 : *B; 라면 10을 가지고 있는 A의 집주소가 대입된 포인터 B의 그 집주소의 값을 출력하라는 뜻.
// 메모리 주소 != 값 -> 집주소와 집주인의 소지금의 차이라고 할 수 있음.
// int* B = &A; 전제하에 *B == *(&A) == A 라고 볼 수 있다.
#include <stdio.h>
//int main(void)
//{
// int X = 10; 정수형 변수 X에 10을 대입시킨다.
// int* PtrToX = &X; 변수 X의 메모리주소를 정수형 포인터 변수 PtrTox에 대입시킨다.
//
// printf("%d\n", *PtrToX); 포인터 변수 PtrTox가 가리키는 메모리주소의 값을 출력한다.
//
// *PtrToX = 100; 100을 포인터 변수 PtrTox가 가리키는 메모리주소의 값에 대입시킨다.
//
// printf("%d\n", *PtrToX); 메모리주소가 아닌 메모리주소가 가리키는 값이 바뀌었기에 100으로 변하며, 마찬가지로 메모리주소를 공유하는 int X의 값도 100으로 변경되었다.
//
// return 0;
//}
int main(void)
{
int A = 5;
printf("%d\n", A);
int* B = &A;
printf("%d\n", *B);
*B = 200;
printf("%d\n", *B);
printf("%d\n", A);
return 0;
}
// Main.c
#include <stdio.h>
int main(void)
{
int X = 50, Y = 10;
int* PtrToX = &X;
printf("%d\n", *PtrToX);
*PtrToX = *PtrToX * 5;
printf("%d\n", *PtrToX);
PtrToX = &Y; // 한 번 포인터 변수로 정의되면 역참조 연산자 의외일 때는 *를 붙일 필요가 없다.
printf("%d\n", *PtrToX);
return 0;
}
어려움
// Main.c
#include <stdio.h>
int main(void)
{
int X = 10;
int* PtrToX = &X;
printf("%d\n", X); // 10
printf("%d\n", (*PtrToX)++); // 소괄호 안에 있는 PtrToX가 가리키는 메모리주소의 값을 먼저 출력한 뒤 그 값에 +1 했기에 10
printf("%d\n", X); // 11
printf("%d\n", *PtrToX++); //
// 문제 속의 문제. 계산될 연산자의 순서를 맞추시오.
return 0;
}
// Main.c
#include <stdio.h>
int main(void)
{
int Num = 100;
int* PointerToNum = &Num;
printf("Num: %d\n", Num); // Num의 값
printf("&Num: %X\n", &Num); // Num의 메모리주소
printf("&Num: %p\n", (void*)&Num); // %q -> 포인터를 출력하는 서식지정자. 차후 배울 포인터 to void 라는 방식으로 형변환을 시켜야지 사용 가능. 위의 방식과 비슷하지만 좀 더 올바른 방식이다.
printf("PointerToNum: %X\n", PointerToNum);
printf("PointerToNum: %p\n", (void*)PointerToNum);
printf("*PointerToNum: %d\n", *PointerToNum);
printf("*PointerToNum: %d\n", *(&Num)); // 올바르진 않지만, 소거하는 식으로라도 이해해 보자.
printf("(*PointerToNum)*2: %d\n", (*PointerToNum) * 2);
return 0;
}
// Main.c
#include <stdio.h>
void Swap1(int Op1, int Op2);// 전방 함수 1 : 이 밑에 함수가 있을테니 컴파일 오류를 일으키지 말라는 코드
void Swap2(int* Op1, int* Op2); // 전방 함수 2
int main(void)
{
int Num1 = 10;
int Num2 = 20;
printf("before Swap1()\n");
printf("Num1: %d, Num2: %d\n", Num1, Num2); // Num1 == 10, Num2 == 20
Swap1(Num1, Num2); // Swap1 함수에 Num1 값과 Num2값을 Swap1의 매개변수에 인자값으로 대입시키고 함수를 실행.
printf("after Swap1()\n");
printf("Num1: %d, Num2: %d\n\n", Num1, Num2); // 여전히 Num1 == 10, Num == 20. 이유는 Swap1 함수의 매개변수와 main 함수의 변수는 별개의 존재이기 때문.
printf("before Swap2()\n");
printf("Num1: %d, Num2: %d\n", Num1, Num2);
Swap2(&Num1, &Num2); //Swap2 함수에 Num1, Num2 변수의 메모리주소를 Swap2의 매개변수에 인자값으로 대입시키고 함수를 실행.
printf("after Swap2()\n");
printf("Num1: %d, Num2: %d\n\n", Num1, Num2); // 이번에는 Num1 == 20, Num2 == 10으로 바뀌었다. 이유는 Swap2 함수의 매개변수(포인터)에 대입된 값이 변환되었기에, 같은 메모리주소를 공유하는 Num1과 Num2에게도 영향이 갔기 때문이다.
return 0;
}
void Swap1(int Op1, int Op2) // 값을 별도로 반환하지 않는 void 반환타입이기 때문에 변하지 않는다.
{
int Temp; // 두 숫자를 바꾸기 위해선 먼저 제 3의 변수를 설정한다.
Temp = Op1; // 설정한 제 3의 변수에 변수1의 값을 대입시킨다.
Op1 = Op2; // 변수1에 변수2의 값을 대입시킨다.
Op2 = Temp; // 변수2에 변수3에 대입되었던 변수1의 값을 대입시킨다.
// 결과 == Temp == Op1, Op1 <- Op2, Op2 <- Op1
return;
}
void Swap2(int* Op1, int* Op2) // main 함수에서 메모리주소 형태(&변수명)로 받았기에 포인터형 변수로 매개변수를 설정한다. int* A = &B;
{
int Temp;
Temp = *Op1; // 포인터형 변수로 받은 값을 역참조연산자( *변수명)를 통해 메모리주소에 있는 값을 불러왔기에 일반 변수로도 대입이 가능하다.
*Op1 = *Op2;
*Op2 = Temp;
return;
}
#include <stdio.h>
void Swap(int* A, int* B);
int main(void)
{
int A = 2;
int B = 3;
printf("before Swap1()\n");
printf("A: %d, B: %d\n", A, B); // A == 2, B == 3
Swap(&A, &B);
printf("after Swap1()\n");
printf("A: %d, B: %d\n", A, B); // A == 3, B == 2
return 0;
}
void Swap(int* A, int* B)
{
int C = *A;
*A = *B;
*B = C;
return;
}
// Main.c
//GetMinMax() 함수
#include <stdio.h>
#include <assert.h>
void GetMinMax(const size_t InArrayLength, const int InArray[], int* OutPtrToMin, int* OutPtrToMax);
int main(void)
{
const int Nums[5] = { 7, 8, 1, 10, 5 }; // const 수식자로 해당 배열의 값은 이후로 변하지 않는다. Nums 배열에 값을 대입시킨다.
int ResultMin;
int ResultMax;
GetMinMax(5, Nums, &ResultMin, &ResultMax); // GetMinMax 함수에 5(Nums배열의 메모리 크기), Nums, ResultMin의 메모리주소, ResultMax의 메모리주소를 인자값으로 매개변수에 대입시키고 반환값을 받는다.
//함수 내에서 처리된 OutPtrToMin, OutPtrToMax의 메모리주소가 가리키는 값이 &ResultMin, &ResultMax에 대입되었다.
printf("ResultMin: %d\n", ResultMin);
printf("ResultMax: %d\n", ResultMax);
return 0;
}
void GetMinMax(const size_t InArrayLength, const int InArray[], int* OutPtrToMin, int* OutPtrToMax) // 함수의 선언단계의 매개변수에서 배열은 메모리 크기를 기입할 필요가 없다.
{
size_t i;
*OutPtrToMin = InArray[0]; // InArray 배열의 1번째 메모리의 값을 OutPtrToMin의 메모리주소의 값에 대입시킨다.
*OutPtrToMax = InArray[0]; // InArray 배열의 1번째 메모리의 값을 OutPtrToMax의 메모리주소의 값에 대입시킨다.
for (i = 0; i < InArrayLength; ++i) // i의 초기값 0; i 가 InArrayLength 보다 작은 동안 true, 이후 i의 값을 1 올린다.
{
if (InArray[i] < *OutPtrToMin) // InArray 배열의 i 번째 메모리값이 OutPtrToMin가 가리키는 메모리주소의 값보다 낮을 때 true
{
*OutPtrToMin = InArray[i]; // OutPtrToMin의 메모리주소가 가리키는 값에 InArray 배열의 i 번째 메모리값을 대입시킨다.
} // 이렇게 되면 작은 값을 OutPtrToMin의 메모리주소가 가리키는 값에 계속 대입시킬 수 있다. if(5 < 1) 5 = 1; 과 같은 식이기 때문.
if (*OutPtrToMax < InArray[i]) // InArray 배열의 i 번째 메모리값이 OutPtrToMax가 가리키는 메모리주소의 값보다 높을 때 true
{
*OutPtrToMax = InArray[i]; // OutPtrToMax의 메모리주소가 가리키는 값에 InArray 배열의 i 번째 메모리값을 대입시킨다.
} // 이렇게 되면 큰 값을 OutPtrToMax의 메모리주소가 가리키는 값에 계속 대입시킬 수 있다. if(1 < 5) 1 = 5; 과 같은 식이기 때문.
}
return;
}
// Main.c
// 챕터 7-3 : NULL과 void 포인터
// 포인터를 초기화 하고 싶을 경우 : NULL 값을 넣으면 된다. 예시 : int* PtrToA = NULL; 자료형* 변수명 = 메모리주소값;
// NULL 포인터의 쓰임새
// 1. 포인터의 초기화
// 2. 포인터가 더이상 사용중이지 않음을 알리고 싶을 때.
// 3. 포인터가 유효한 메모리 주소를 저장하고 있는지 확인할 때.
#include <stdio.h>
int main(void)
{
int* Ptr = NULL; // 포인터 Ptr의 메모리주소값에 NULL을 대입한다.
if (NULL == Ptr) // NULL이 포인터 Ptr과 같다면 true
{
printf("NULL == Ptr");
}
if (Ptr != NULL) // Ptr과 NULL이 다르다면 true
{
printf("Ptr is not the \"NULL pointer\"\n"); // 위의 두 가지 방법을 사용하는게 보다 안전하다.
}
/* 간혹 다음과 같이 쓰기도 합니다. worst.*/
if (Ptr)
{
printf("Ptr is not the \"NULL pointer\"\n");
}
/* 올바르지 않은 예들. 명시적인 부분에서 올바르지 않음. */
if (0 == Ptr)
{
printf("Ptr is the \"NULL pointer\"\n");
}
if (Ptr != 0)
{
printf("Ptr is not the \"NULL pointer\"\n");
}
return 0;
}
// Main.c
// void 포인터
// void 포인터는 어떤 자료형의 포인터라도 대입 가능하다. void* 변수명 = 메모리 주소 값; <- 메모리 주소에 어떤 자료형의 포인터가 오더라도 괜찮음.
// 사용처 : 자료형이 당장 정해져있지 않았지만 포인터를 만들어야할 때 사용됨.
// 주의사항
// 1. void*에 역참조 연산은 불가능. 이유 : 역참조 연산을 하려면 메모리 주소 값을 가져와야하는데, 포인터의 자료형마다 메모리 주소 값의 크키가 다르기 때문에 void 포인터는 얼마만큼의 메모리 주소 값을 가졌는지 알 수 없기 때문.
// 2. void*에 정수를 더하거나 빼는 연산은 불가능. 이유 : 마찬가지로 메모리 주소 값의 크기가 정해져있지 않아서 몇 바이트 만큼 더하고 빼는지 모르기 때문.
#include <stdio.h>
int main(void)
{
float PI = 3.14f, Result;
void* p;
float* q;
p = &PI;
q = p;
printf("%f\n", *p);
printf("%f\n", *(float*)p);
Result = *p + *q;
Result = *(float*)p + *q;
printf("%f\n", Result);
return 0;
}