티스토리 뷰

C++

[개념] C/C++의 헷갈리는 const

다음김 2022. 6. 18. 17:00

 

해당 글은 '윤성우의 열혈 C++ 프로그래밍'을 참고하여 작성함.



나름 C언어 공부를 많이 했다고 생각하고 있었는데 처음 보는 개념이 나왔다. 정리도 할 겸 기록으로 남겨둔다.
먼저 C/C++에서의 const 키워드가 의미하는 바는 '초기화 후 재초기화가 불가능한 수' 즉 변하지 않는 수이다. 초기화 후 재초기화 함으로써 계속해서 값을 변경할 수 있는 일반적인 변수와 차이가 있다.

이러한 특성으로 const 키워드가 붙은 변수는 정의 시에 항상 초기화 해야 하고 이후 다시 값을 변경하지 못한다.
즉 다음과 같은 경우 컴파일 오류가 발생한다.

//const int var; //오류 발생! (초기화 하지 않음)

const int var2 = 10;
//var2 = 20; //오류 발생! (재초기화)



책에서 제시한 개념은 다음 4개 코드의 차이점이다. 정말 그런지 하나씩 검증해보고자 한다.

const int num = 10; //변수 num을 상수화 

const int* ptr1 = &val1; //포인터 ptr1을 이용해서 val1의 값을 변경할 수 없음 

int* const ptr2 = &val2; //포인터 ptr2가 상수화 됨 

const int* const ptr3 = &val3; //포인터 ptr3가 상수화 되었으며, ptr3를 이용해서 val3의 값을 변경할 수 없음



const int num = 10;

#include <iostream>
using namespace std;

const int num = 10;
//num = 5; //오류 발생!



const int* ptr1 = &val1;

int val1 = 10;
int val2 = 20; 

const int* ptr1 = &val1;
cout << *ptr1 << endl; //10
val1 = 5;
cout << *ptr1 << endl; //5
//*ptr1 = 7; //오류 발생!

ptr1 = &val2;
cout << *ptr1 << endl; //20

ptr1 포인터를 통해 해당 포인터가 가리키고 있는 val1 변수의 값 변경은 불가능하지만, val1 변수 자체는 상수가 아니므로 직접 변경은 가능하다. 또한 ptr1 포인터 변수 또한 상수가 아니므로 기존 val1 변수를 가리키던 것을 val2 변수를 가리키도록 변경할 수 있다. 즉 포인터 변수 값의 변경은 가능하다.



int* const ptr2 = &val2;

int val2 = 20;
int val3 = 30;

int* const ptr2 = &val2;
cout << *ptr2 << endl; //20
*ptr2 = 15;
cout << *ptr2 << endl; //15

//ptr = &val3; //오류 발생!

이 경우 ptr2 포인터 변수를 통해 해당 포인터가 가리키고 있는 val2 변수 값의 변경이 가능하다. 그러나 ptr2 포인터 변수는 const 키워드가 붙은 상수이므로 변수 정의 시 초기화한 val2 변수의 주소값을 val3 변수의 주소값으로 변경하는 것은 불가능하다.



const int* const ptr3 = &val3;

int val3 = 30;
int val4 = 40;

const int* const ptr3 = &val3;
//*ptr3 = 25; //오류 발생!
//ptr3 = &val4; //오류 발생!

val3 = 35;
cout << val3 << endl; //35

이 경우는 ptr3 포인터 변수가 가리키고 있는 변수 값을 ptr3를 통해 변경하는 것도 불가능하고, ptr3 포인터 변수 자체의 값 변경도 불가능한 것을 볼 수 있다. 당연히 val3, val4 변수는 const 키워드가 없으므로 직접 값 변경은 가능하다.

 

 


추가적으로 C에는 없는 개념인 reference 즉 참조 변수에 const를 붙이면 어떻게 될까 궁금증이 생겼다.
먼저 참조 변수란 '자신이 참조하는 변수를 대신할 수 있는 또 하나의 이름' 이며 다음과 같이 선언 및 초기화한다.

int num = 10;
int& ref = num;

cout << num << endl; //10
cout << ref << endl; //10

이 경우 변수 num에 대한 ref라는 별칭(alias)이 생긴 것이다. 따라서 10이라는 값이 저장되어 있는 메모리 공간에 num 또는 ref 변수를 통해 접근할 수 있다.



참조자의 특징으로는

1. 참조자는 변수에 대해서만 선언이 가능하고 선언됨과 동시에 누군가를 참조해야 한다. (상수 불가)
2. 참조 대상 변경이 불가능하다.
3. 참조자를 선언하면서 NULL로 초기화가 불가능하다.

 

//int& ref = 10; //오류 발생! (상수 참조)
//int& ref2; //오류 발생! (선언 시, 참조 대상 없음)

int num = 10;
int num2 = 20;
int& ref3 = num;
ref3 = num2; //**
//&ref3 = num2; //오류발생! (참조 대상 변경)

//int& ref4 = NULL; //오류발생! (NULL 참조)

여기서 살펴볼 점은 &ref3 = num2;는 컴파일 오류가 발생하는 한편 ref3 = num2;는 오류가 발생하지 않는다는 점이다. 먼저 위 int& ref3 = num; 코드를 통해 num이라는 변수에 ref3 라는 별칭이 생성되었다. ref3 = num2; 코드는 별칭인 ref3를 통해 참조하는 변수의 메모리 공간에 접근하여 기존 10의 값을 20으로 변경하는 것이지만, &ref3 = num2; 코드는 ref3 변수의 참조를 기존 num 변수에 대한 것에서 num2 변수로 변경하는 것이므로 오류가 발생하게 되는 것이다.



밑의 int& ref4 = NULL; 코드에서는 'C++ 비const 참조에 대한 초기 값은 lvalue여야 합니다.' 라는 오류 메시지가 뜨는데 오류가 발생하는 이유를 좀더 생각해보자면, ref4 라는 별칭을 통해 접근할 메모리 공간이 없기 때문이라고 볼 수 있다.
여기서 lvalue란 '= 연산자 왼쪽에 올 수 있는 것으로서, 하나의 값을 저장할 수 있는 메모리에 대한 참조'이다. 만약 ref4 참조 변수에 const 키워드가 붙는다면(const int& ref4 = NULL;) 해당 코드에 대한 오류는 발생하지 않는다. (ref4 별칭을 통해 참조하는 값을 변경할 일이 없기 때문 즉 ref4 변수를 통해 메모리 공간에 접근할 일이 없기 때문, 이 부분이 이해되지 않는다면 아래를 먼저 읽어보시길.)




이제 참조 변수에 const 키워드를 붙여보자.

cout << endl;
int num = 10;
int num2 = 20;
int& const ref = num;

cout << ref << endl; //10
ref = 15;
cout << ref << endl; //15
num = 17;
cout << num << endl; //17
cout << ref << endl; //17

ref = num2;
cout << ref << endl; //20

이 경우 오류가 발생하지 않고 실행이 잘 된다. ref 변수 바로 앞에 const 키워드가 붙었으므로 참조 변수 ref 자체가 상수화 되었다고 해석하면 된다. 다만 ref 변수가 참조하는 num 변수는 상수가 아니므로 ref 변수를 통해 값 변경이 가능하다. 여기서 ref 변수가 상수화되었다는 의미는 ref 변수의 참조 대상 변경이 불가능하다는 것으로 해석할 수 있는데 이는 원래 참조 변수의 특징이다. 따라서 참조 변수 바로 앞의 const 키워드 유무에 따른 차이는 없는 듯하다.



int num = 10;
int num2 = 20;
const int& ref = num;

cout << ref << endl; //10
//ref = 15; //오류 발생!
num = 15;
cout << num << endl; //15
cout << ref << endl; //15

//ref = num2; //오류 발생!

반면 const 키워드를 맨 앞에 붙였을 때에는 ref 별칭을 통해 num 변수 값을 변경하는 것이 불가능하다로 해석할 수 있다. 다만 num 변수 자체는 상수가 아니므로 직접 재초기화가 가능하며 따라서 별칭인 ref로 해당 변수에 접근하였을 때 값이 변경된 것을 확인할 수있다.



const int num = 10;
const int& ref = num;
const int& ref2 = 20;

cout << ref << endl; //10
cout << ref2 << endl; //20
//ref = 15;
//num = 15;

추가로 위와 같이 참조 변수가 상수를 참조할 경우에는 참조 변수에 const 키워드를 붙여주어야 한다. 즉 참조 변수를 통해 상수를 변경하지 못하도록 막아두어야 한다는 것이다.




끝.




'C++' 카테고리의 다른 글

[개념] C++의 형변환 연산자  (0) 2022.07.02
[개념] C++의 다형성 - virtual 함수  (0) 2022.06.27
[개념] C++의 상속 3계명?  (0) 2022.06.24
[개념] C++의 복사 생성자  (0) 2022.06.22
[개념] C++의 멤버 이니셜라이저  (0) 2022.06.20
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/06   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함