상수는 변하지 않는 수를 의미한다.
보통의 변수는 다음과 같다.
int age = 20;
age = 30;
변수 age를 선언하고 언제든지 사용자가 원하면 변수를 수정하거나 바꿀 수 있다.
이러한 변수 앞에 const를 붙이면 상수화가 된다.
const int age = 20;
age = 30; // 에러 발생
위와 같이 변할 수 있는 값을 l-value
아래와 같이 변하지 않는 수는 r-value라고 한다.
const로 선언하면 r-value가 된다.
r-value라고 무조건 값을 변경할 수 없는 것은 아니다.
강제 캐스팅을 하면 값을 변경할 수 있다.
// 강제 캐스팅하여 주소 값 할당.
int* pInt = (int*)&age;
// 값 변경
*pInt = 30;
printf("%d\n", age);
그러나 개발자는 보통 const를 사용할 때 값을 변경하지 말라는 의도를 가지고 작성하기 때문에 위와 같이 값을 변경할 일은 거의 발생하지 않는다.
포인터를 선언할 때 const로 선언할 수있다.
const의 위치에 따라 어떤 것을 상수를 만들지 결정하게 되는데 포인터 변수의 상수화는 다음 2가지로 선언할 수있다.
포인터 변수가 가르키는 값에 대한 상수를 진행할 수 있다. 가르키는 값에 대한 상수화를 하였더라도 참조되는 변수 자체는 별도로 상수화되지 않는다.
int a = 0;
// 가르키고 있는 원본이 상수가 됨
const int* pA = &a;
//아래와 같이 접근해서 값을 변경하는 것은 제한된다.
//*pA = 20;
int b = 9;
//원본을 수정하는 것이 아니면 포인터 변수의 값을 변경할 수 있다.
pA = &b;
가르키는 주소 값에 대한 상수화라는 것은 변수 a가 상수가 되는 것이 아니라 주소값으로 접근하는 값이 상수가 되는 것이다. 따라서 포인터로 접근하여 값을 바꿀 수는 없다.
포인터 변수에 새로운 주소값을 넣는 것은 가능하다.
포인터 변수 자체가 상수화되는 것은 포인터 변수가 가르키는 값을 변경할 수는 있지만 가르키고 있는 주소를 변경할 수 없다.
int* const pB = &b;
// 가르키고 있는 값의 수정은 가능함.
*pB = 20;
//다른 주소값을 할당 할 수 없음.
//pB = &a;
추가로 포인터 변수 자체와 가르키고 있는 주소의 값에 대해 상수화 시킬 수 있다.
// 두가지 모두 상수화 할 수 있다.
const int* const pC = &a;
// 주소 변경 X, 값 변경 X, 읽기만 가능.
* 을 기준으로 * 좌측이면 포인터(주소)를 우측이면 변수자체를 const화 하는 것이다.
함수의 매개변수를 전달할때 값 자체를 넘기게 되면 그 값을 복사하게 되고 큰 데이터는 자주 호출하면 성능저하를 시킬 수 있다. 함수의 매개변수 자체를 넘겨서 사용하기보다는 포인터 변수로 접근할 수 있는 주소값을 넘긴다. 그러나 포인터 변수로 접근하게 되면 원본 값도 수정할 수 있기에 원본 데이터의 무결성을 유지하려면 포인터를 const화 한다.
void Test(int* pI) {
printf("%d\n", *pI);
*pI = 30;
}
int main() {
int data = 10;
Test(&data);
printf("함수 호출 뒤 원본 데이터 : %d \n", data);
return 0;
}
위 코드와 같이 CBR 방식으로 함수를 전달하면 해당 변수를 접근할 수 있다.
그러나 접근이 가능하면 원본 데이터가 훼손되고 원본 데이터의 무결성을 지켜야 하는 데이터에서는 치명적인 결함이 될 수 있다. 매개변수에 const 포인터를 설정해주면 원본 데이터의 무결성을 지킬 수 있다.
int Test(const int* pI) {
printf("%d\n", *pI);
// 원본의 값을 수정할 수 없다.
//*pI = 30;
//원본 데이터를 읽을 수만 있고 값을 복사해서 return 하는 건 가능하다.
int i = 0;
i = *pI +10;
return i;
}
int main() {
int data = 10;
Test(&data);
printf("함수 호출 뒤 원본 데이터 : %d \n", data);
return 0;
}
void로도 포인트 변수를 만들 수는 있으나 다음과 같은 특징을 가진다.
{
//void
// 원본의 자료형을 정하지 않았다.
// 어떠한 타입의 변수든 주소를 저장할 수 있다.
// 역참조를 할 수 없고 주소 연산도 할 수 없다.
void* pVoid = nullptr;
//포인터 변수의 자료형을 정하지 않았다는 것은 어떤 주소든지 전부 받을 수 있다.
int a = 0;
float b = 0.2f;
char c = 1;
pVoid = &a;
pVoid = &b;
pVoid = &c;
// 주소 접근은 가능하지만 역참조가 불가능하다.
}
원본의 자료형을 정하지 않았기에 역참조와 주소 연산도 할 수 없다.