본문 바로가기

C++

[C/C++ 공부] 수정이 불가능하도록 만들어주는 const 상수(또는 const 변수)

const란?

 

↳ 변수에 저장된 데이터는 언제 어느때라도 수정이 가능합니다. 그러나 특정 변수나 포인터는 경우에 따라 수정이 불가하도록 만들어 줄 필요가 있는데, 이러한 성격을 가진 변수를 바로 const 변수 또는 const 상수 라고 합니다.

 

 

const 상수 사용법

 

↳ 아래 코드와 같이 변수 선언 시에 const 키워드를 앞에 사용하면 됩니다. 

만약에 const 상수를 수정하고자 한다면 에러가 발생합니다.

 

const double pi = 3.14159;

pi = 3.1415926535 //pi에서 에러가 발생합니다.

 

 

#define 또는 리터럴과의 차이점?

 

리터럴#define 지시문은 컴파일 시점에 값이 결정되는 반면에 const 상수는 프로그램이 실행되는 시점에 값이 결정됩니다. 

const 상수는 변수처럼 저장소를 가지고 있으며, 마치 상수(1,2, 45, 30...)처럼 사용하는 변수입니다.

 

 

const의 주된 사용

 

↳ ① 클래스 내 멤버 변수를 초기화 시점에 상수로 만들거나, ② "void func(const char* str)"처럼 포인터로 전달된 저장소의 값이 변경되지 않도록 만들 때 주로 사용합니다. 

 

 

① 경우에서의 사용

 

const int id로 id를 const 상수로 선언했습니다. const 멤버 변수는 초기화를 할 때 꼭 초기화리스트(이니셜라이저)를 사용하여 초기화 해야합니다.  객체 생성자를 통해 const 상수를 초기화 해주면 오류가 납니다.

 

 

Why?

객체를 생성할 때 제일 처음으로 하는 것은 메모리 공간의 할당입니다. 그렇기 때문에 생성자가 호출되지 않은 상태에서 메모리가 할당되는 순간, id와 age 등 이러한 멤버변수가 쓰레기 값으로 초기화됩니다. const상수가 이미 초기화 되었으니 생성자에서 그 값을 다시 초기화 할 수 없는 것은 당연합니다. 

이 문제를 해결하는 것이 바로 초기화리스트(이니셜라이저) 입니다.

 

 

#include <iostream>

class Student {
	const int id;
	int age;

public:
	//초기화 리스트
	Student(int _id, int _age) : id(_id), age(_age){}
	
	//오류
	Student(int _id, int _age){
		id = _id;
		age = _age;
	}
};

 

 

② 경우에서의 사용

 

main 함수 안에서 선언한 char x의 저장소의 값이 변경되지 않게 하기 위해 function1(const char* str) 인자를 const 포인터로 처리하고 있습니다.

포인터의 상수 처리는 혹여나 변경되지 말아야 할 값을 변경하는 등의 개발자의 실수(ex)영역 침범 오류)를 최소화하기 위한 조치입니다.

 

 

영역 침범 오류

 

특히 char x[] = "Hello World!"; 과 같이 선언한 변수의 크기는 선언할 당시에 결정하는데, 프로그래밍을 작성하다 변수가 할당받은 크기보다 더 큰 문자열을 재 입력할 수 있습니다. 이때 프로그램은 영역 침범("out of bound")이라고 하는 에러가 발생합니다.

 

 

#include <iostream>
void function1(const char* str) {
	printf("%s", str);
}

int main() {

	char x[] = "Hello World!";
	function1(x);

	return 0;
}

 

 

const 포인터

 

 

포인터의 기능
1. 변수나 함수를 가리키는 메모리 주소로 사용
2. 간접 참조 연산자('*')를 사용하여 메모리 주소가 가리키는 저장소를 특정 타입의 변수처럼 사용

 

 

int* n = (int*)malloc(sizeof(int));
*n = 100;
std::cout << *pi << ' ' << pi << "포인터는 정수의 저장소를 가리킨다.\n";
free(pi);

 

 

1) 포인터 상수화

 

const 지정자가 포인터 앞에 선언하여 포인터의 주소를 고정시키는 목적입니다.

 

 

char mybuf[100];
const char *yourbuf = "Hello World";	//yourbuf 포인터가 가리키는 변수를 상수로 만든다.

//const 지정자는 aptr 포인터가 가리키는 메모리 주소를 고정시킬 때 사용
char *const aptr = mybuf;

*aptr = 'a';		//포인터가 가리키는 저장소의 값에 대한 수정은 허용
aptr = yourbuf;		//포인터 ㅜ소의 변경은 허용X, 에러 발생

 

 

2) 저장소 상수화

 

const 지정자를 *bptr 앞 어디든 선언하여 포인터가 가리키는 저장소를 고정시키는 목적입니다.

 

포인터 상수화와 저장소 상수화를 동시에 하고 싶다면, 맨 아래 나와있는 코드를 참고하시면 됩니다.

(앞에 있는 const가 저장소 상수화를, 뒤에 있는 const가 포인터 상수화를 하고 있습니다.)

 

int mybuf = 1000;

const int *bptr = &mybuf;	//두 문장 다 저장소 상수화하는 문장
int const *bptr = &mybuf;

*bptr = =0; //에러 발생

//포인터 상수화와 저장소 상수화를 동시에 하고 싶다면
int mybuf = 1000;
const int* const bptr = &mybuf;

 

 

const 멤버 함수

 

const 지정자를 클래스나 구조체 내 멤버 함수의 본문 앞에 적용하여 읽기 위주의 함수(read-only function)로 만드는 기능을 제공합니다.

 

아래 Print() 함수가 바로 const 멤버 함수입니다. 

 

 

#include <iostream>

class Account {
	char account[20];
	char name[20];
	int balance;

public:
	Account(const char* id, const char* _name, int bal) {
		strcpy(account, id);
		strcpy(name, _name);
		balance = bal;
	}

	//아래 함수를 'const 멤버 함수'라 한다.
	void Print() const {
		printf("계좌: %s, 소유자: %s", account, name);
		printf(", 잔액: %d\n", balance);
	}
    
    void Withdraw(int money){
    	balance -= money;
    }
};

 

 

위 프로그램 내 Withdraw() 함수는 balance 멤버 변수를 수정하는 기능을 수행합니다. 따라서 만약 const 지정자를 사용하여 'void Withdraw(int money) const{...}'로 수정한다면 에러가 발생합니다.

 

 

const 멤버 함수를 만드는 이유는?

- 사소한 실수로 인해 클래스나 구조체 내 멤버 변수의 값 변경 방지

- 멤버 변수에 대한 보안 목적

 

 

const 지정자는 함수 오버로딩부터 클래스의 복사 생성자까지 다양한 범위에서 사용합니다. 다른 게시물에서 다루겠습니다.

 

오류나 오타 지적해주시면 감사하겠습니다. :)