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 지정자는 함수 오버로딩부터 클래스의 복사 생성자까지 다양한 범위에서 사용합니다. 다른 게시물에서 다루겠습니다.
오류나 오타 지적해주시면 감사하겠습니다. :)
'C++' 카테고리의 다른 글
[C/C++ 공부] 깊은 복사와 얕은 복사 차이? (feat.복사 생성자) (0) | 2021.08.12 |
---|