C++/C++

참조자 '&' 와 const 키워드

Elan 2021. 2. 16. 05:30

const 키워드


const는, constant의 줄임말로 "변하지 않는"이란 의미를 가진다.

변수명 또는 타입명 앞에 붙이면 실수로라도 값 또는 주소를 변경할 수 없도록 만들어 준다.

 

즉, 변수나 함수에 const가 붙으면 프로그래머가 실수로 수정하지 못하게 하는 것이다.

바꿔말하면 대상에 const가 붙어있으면 대상을 변경하지 않겠다는 생각하면된다.

 

포인터를 const키워드와 함께 사용할 경우 const 키워드가 붙은 위치에 따라 다양하게 동작하게된다.

포인터 형식 앞에 const를 붙인 경우

int main() {	

	int value = 42;
	int value2 = 55;
	
	const int* p2 = &value;
	p2 = &value2;		//OK		원래 가키리던 대상이 아닌 다른 대상을 가리킬 수 있다
	*p2 = 3;				//NG		가리키는 대상의 내용을 변경하는 것은 불가능
    
    return 0;
}

- 이 경우 포인터가 가리키는 대상의 내용을 변경(Update)하는 것이 금지된다.

 

- 원래가리키던 대상이 아닌 다른 대상을 가리키는(Pointing)행위는 허용된다.

 

변수명 바로 앞에 const 키워드를 붙인 경우

int main() {

	int value = 42;
	int value2 = 55;
    
	int* const p3 = &value;
	p3 = &value2;		//NG		원래 가키리던 대상이 아닌 다른 대상을 가리키는 것은 금지 ( 참조자와 똑같은 동작을 하게된다 )
	*p3 = 10;			//OK		가리키는 대상의 내용을 변경하는 것은 가능

	return 0;
}

- 포인터가 가리키는 대상의 내용을 변경(Update)하는 것은 허용된다.

 

- 원래가리키던 대상이 아닌 다른 대상을 가리키는(Pointing)행위는 금지된다.

 즉, 한번 가리킨 대상은 끝까지 가리켜야한다.(선언시 초기화를 하지 않으면 컴파일 에러 발생)

포인터 형식과 변수명 양쪽 앞에 각각 const 키워드를 붙인 경우

int main() {

	const int* const p = &value;
	p = &value2;		//NG		원래 가키리던 대상이 아닌 다른 대상을 가리키는 것은 금지 
	*p = 10;				//NG		가리키는 대상의 내용을 변경하는 것은 불가능

	return 0;
}

맥락상으로만 봐도 그냥 다 금지라고보면 된다.

Get을 제외한 모든 행위가 금지된다.

 

 

참고

 

const 키워드 사용 시 주의사항

1. 선언시 초기화 할 것

1) 구조체의 경우

- 초기화를 안하면 이와 같이 컴파일 에러가 발생한다.

typedef struct Test {
	const int data;//정의부에서 초기화되지 않은 상수 존재
	int data2;
}Test;

int main() {
	Test a;			//NG		const 변수를 초기화하지 않았기 때문
	Test b{ 3 };	//OK		const 변수 초기화하였음

	return 0;
}

- 생성자에서 초기화하는 방법도 고려해보자.

 

typedef struct Test {
	int data;
	const int data2;
	
	Test(int _data2):data2(_data2){}
}Test;

int main() {
	Test a(5);
	cout << a.data2 << endl;
	return 0;
}

 

2) 전역변수

const int cc;		//NG		선언 즉시 초기화 안했다고 컴파일러가 화냄
//에러 - C2734 'cc': 'const' object must be initialized if not 'extern'

int main() {
	//에초에 전역에서 선언시 오류가 났지만 혹시나 봐줄까 싶어 main에서 초기화 시도 해보았다
	cc = 54;		//NG		에러 - C3892 'cc': you cannot assign to a variable that is const
    
	return 0;
}

- 전역변수 일지라도 초기화 안 하면 컴파일러가 안 받아준다.

 

3) 클래스

class Test {
public:
	int data = 0;		//클래스 구현부에서 멤버를 직접 초기화하는 문법은 C++ 11 부터 지원
	const int data2;

	Test() :data(0), data2(0) {}
	Test(int _data2) :data2(_data2) {}

	friend ostream& operator << (ostream& os, const Test& test) {
		os << "data: " << test.data << ", data2: " << test.data2;
		return os;
	}
};

int main() {

	Test t;
	cout << t << endl;
	return 0;
}

 

- 구조체와 마찬가지로 생성자에서 const 멤버를 초기화 해주는 것이 좋다.

 

4) 함수

함수 뒤에 const가 붙는다는 것은 어떤 의미 일까?

class Test {
public:
	int data = 0;// 클래스 구현부에서 멤버를 직접 초기화하는 문법은 C++ 11 부터 지원
	const int data2;

	Test() :data(0), data2(0) {}

	int GetData() const {
		data = 0;// 오류	C3490	'data' cannot be modified because it is being accessed through a const object

		return data;
	}
};

- 그것은 해당함수에게 this 클래스의 멤버 변수를 변경하는 어떤 행위도 금지한다는 의미이다.

class Test {
public:
	int data = 0;// 클래스 구현부에서 멤버를 직접 초기화하는 문법은 C++ 11 부터 지원
	const int data2;

	Test() :data(0), data2(0) {}

	int GetData() const {
		ChangeData(5);// 오류	C3490	'data' cannot be modified because it is being accessed through a const object

		return data;
	}
	void ChangeData(int _data) { data = _data; }
};

함수 내부에서 다른 함수를 통해 변경하는 것 또한 당연히 불가하다.

다시 말하면, const 함수 내부에서는 const 함수만 호출할 수 있다.

 

5) 매개변수

Test SumTest(const Test& a, const Test& b) {
	Test test{ a.data + a.data2,b.data + b.data2 };
	return test;
}

전달받은 매개변수가 실수로 인해 수정되는 것을 막기위함.

다시말해, 매개변수가 변경될 가능성을 차단하기 위함.


참조자 &


 

int main() {
		
	int nData = 10;
	int nNewData = 20;
	
	int* pnData = &nData;//포인터가 가리키는 대상을 nData로
	*pnData = 5;//nData = 5 와 같은 행위

	pnData = &nNewData;//가리키는 대상을 nData에서 nNewData로 변경
	*pnData = 5;//nNewData = 5 와 같은 행위

	return 0;
}

포인터는 주소 핸들링을 통해 가리키는 대상을 바꿀 수 있고, 그 대상을 수정하는 것까지도 가능하다.

 

하지만 이 포인터라는 것이 문제가 발생할 가능성을 제공한다.

그것은 바로 가리키는 대상이 고정되지 않아 특정 시점에서 어떤 것을 가리키게 될지 보장할 수 없다는 것이다.

 

그래서 등장한 것이 참조자이다.

 

참조자는 한번 가리킨 대상을 끝까지 가리킨다.

 

따라서 참조자는 R-Value로 대입 되는 변수의 별명이라고 생각해도 무방하다.

 

int main() {

	int nData = 0;
	int& rData = nData;//참조형변수 선언 및 정의
	rData = 10;// nData = 10 과 같은 행위

	//rData = &nNewData;//가르키는 것을 바꾸려할 경우 컴파일 에러 발생

	int& temp = 10;// NG  ( 이런건 안됨, 반드시 변수를 참조해야 함 )
	MyClass& tempClass = new MyClass() // NG  ( 이런건 안됨, 반드시 변수를 참조해야 함 )
    
	return 0;
}

위의 2가지 (포인터로 다른 변수를 참조하는 것과 참조자를 사용하는 것)를 Visual Studio의 디버그 모드에서 Alt + 6 을 눌러 Memory윈도우를 켜놓고 관찰해보자.

포인터와 참조자가 각각이 가리키는 대상의 값을 변경할 때의 과정에 어떤 차이도 없음을 알 수 있다.