[Modern C++] 따배씨++ Chapter 2 정리
따베씨++ Chapter 2을 듣고 정리해보자!
Modern C++을 복습하기 위해 인프런에서 홍정모 교수님의 따라하며 배우는 C++를 정리해보자.
쳅터 2만 되어도 중간 중간 두루뭉술하게 잡힌 개념들이 있었다.
조금 시간이 걸리겠지만 기초를 탄탄히 한다는 마음으로 정리를 해보았다.
쳅터 2은 변수와 기본적인 자료형이라는 이름으로 되어있다.
이 포스팅에서는 강의에서 나눈 쳅터와 동일하게 쳅터를 나누었다.
1. 기본 자료형 소개
Chapter 1에서는 변수를 선언할 때 int
자료형을 사용했다.
이번 쳅터에서는 C++에 있는 다양한 자료형에 대해 배우고, 초기화 하는 방법을 배웠다.
자료형은 Data Type에 따라서 차지하는 bit수에 따라서도 다양하게 존재한다.
참고로 1byte = 8bit이다!
변수가 선언이 되면 메모리의 어떤 공간에 변수가 들어갈 공간이 생기는데,
이 때 자료형마다 차지하는 공간의 크기가 다르다.
주소 값을 보기 위해 아래와 같은 코드를 사용해보자.
#include <iostream>
#include <bitset>
using namespace std;
int main()
{
int i = -1;
cout << (uintptr_t)static_cast<void*>(&i) << endl; // #1
cout << static_cast<void*>(&i) << endl; // #2
cout << &i << endl; // #3
return 0;
}
출력 결과는 다음과 같다.
16119588 // #1 : 주소 값을 10진수로 출력
00F5F724 // #2 : 주소 값을 16진수로 출력
00F5F724 // #3 : 주소 값을 16진수로 출력
static_cast
는 형변환을 할 때 사용하고, void*
같은 경우는 자료형을 몰라도 주소 값을 출력할 때 사용한다.
(uintptr_t)
의 경우, 주소값을 16진수에서 10진수로 바꾸어 출력하기 위해 사용한다.
bool형의 초기화 방법은 다음과 같다.
bool형은 내부적으로 숫자로 저장된다.
// bool형 초기화
bool bValue = true;
cout << (bValue ? 1 : -1) << endl; // 삼항 연산자 사용 : 1 출력
cout << bValue << endl; // bool형도 내부적으로 숫자로 저장 : 1 출력
문자(char형)의 초기화 방법은 다음과 같다.
문자도 내부적으로 숫자로 저장된다.
// 문자도 결국 숫자로 저장한다.
char chValue = 65; // 아스키 코드
cout << chValue << endl; // A를 출력
Float형과 Double형을 이용해서 실수를 표현할 수 있다. Float형이 Double형보다 더 정밀하게 수를 표현할 수 있다. 이 두 자료형의 차이점에 대해서는 다음 쳅터에서 조금 더 자세히 다룬다고 하셨다.
// float형 double형 초기화
float fValue = 3.141592f;
double dValue = 3.141592;
cout << fValue << endl; // 3.14159 출력
cout << dValue << endl; // 3.14159 출력
위 코드의 경우 cout이 자동적으로 소수 5번째 자리까지만 출력하도록 되어있기 때문에 같은 수가 출력이 된다.
또한 Modern C++의 경우 Datatype을 자동적으로 잡아주는 auto
자료형이 추가되었다.
// Modern C++에 생긴 자료형 - 자동으로 자료형을 결정
auto aValue = 3.141592f;
auto aValue2 = 3.141592;
// sizeof()는 Datatype, 변수 둘다 넣어줄 수 있다.
cout << sizeof(aValue) << endl; // 4 출력
cout << sizeof(aValue2) << endl; // 8 출력
cout << sizeof(double) << endl; // 8 출력
참고로 sizeof() 함수는 메모리 공간에 차지하는 Byte 수를 알려준다.
위 주석대로 변수뿐만 아니라 자료형도 Input으로 넣어줄 수 있다.
C++에서는 초기화 방법이 크게 3가지가 있다.
// C++ 초기화 방법 1 : Copy initialization
int a = 123;
// C++ 초기화 방법 2 : Dircet initialization
int b(224);
// C++ 초기화 방법 3 : Uniform initialization
int c{ 244 };
Copy initialization은 우리가 흔히 초기화할 때 사용하는 방법이고,
Direct initialization과 Uniform initalziation은 객체 지향을 배우고 그것을 초기화 할 때 종종 사용한다.
Uniform initialization의 방법이 Direct initialization보다 조금 더 엄격하다.
예를 들어 Datatype을 맞춰주지 않고 초기화를 할 경우 Direct 초기화 방법은 Warning이 뜨지만, Uniform 초기화 방법은 Error가 뜬다.
그렇다면 변수 선언은 언제 하는것이 좋을까?
최근에는 변수 사용 직전에 선언을 하는 것이 최근 Trend이다.
그 이유는 다음과 같다.
- 디버깅을 하기 편하기 때문
- 리팩토링이 편하기 때문
물론 개발하는 팀이나 스타일 가이드가 있을 경우 그를 따르는 것이 좋다.
2. 정수형 (Integers)
이번 강의를 통해서 여러가지 정수형 Type과 정수로 나눗셈을 할 때 유의해야 할점
그리고 표현할 수 있는 범위를 넘어가면 발생하는 문제인 Overflow에 대해서 배웠다.
int형이 2바이트라고 되어있는 이유는 최소 크기를 나타내기 위함이다.
실제로 코드에서는 출력을 하는 것이 더 이해하기 쉽다.
위 그림은 int형의 데이터 구조를 시각화한 자료이다.
1 Byte는 8bits로 되어 있고 맨 첫 번째 bit는 부호를 표현하기 위해 사용한다.
Unsigned int는 부호 bit가 없어 모든 비트가 수를 표현하는데 사용한다.
아래 코드를 통해 자료형의 Size를 출력해보자.
#include <iostream>
using namespace std;
int main()
{
cout << sizeof(short) << endl; // 2 출력
cout << sizeof(int) << endl; // 4 출력
cout << sizeof(long) << endl; // 4 출력
cout << sizeof(long long) << endl; // 8 출력
}
자료형 Short가 표현할 수 있는 수 중 가장 큰 수 출력해보자.
Short는 2바이트이므로 즉, 16비트로 수를 표현한다.
맨 첫번째 비트로 부호를 결정하고, 모든 비트가 0일 경우 숫자 0을 표현하므로,
가장 큰 수는 $(2 ^{sizeof(short)* 8 - 1} ) -1$이다.
최대값, 최솟값을 표현하는데 사용하는 헤더파일인 #include <limits>
를 활용하여 구한 값이 맞는 지 확인해보자.
#include <iostream>
#include <cmath> // pow()를 사용하기 위함
#include <limits> // 컴파일러한테 최대로 최소로 표시할 수 있는 범위를 물어봄
using namespace std;
int main()
{
cout << std::pow(2,sizeof(short) * 8 -1) -1 << endl; // 32767 출력
cout << std::numeric_limits<short>::max() << endl; // 32767 출력
}
그럼 여기서 +1을 한 값을 출력하게 되면 어떻게 될까?
아마 32768이 나오기를 기대할 것이다.
그렇다면 실제로 그렇게 되는지 확인해보자.
#include <iostream>
using namespace std;
int main()
{
short s = std::numeric_limits<short>::max();
s += 1; // s = s + 1;
cout << s << endl; // -32768 출력
}
우리의 예상과는 달리 -32768
이라는 값이 출력되었다. 바로 최대로 표현할 수 있는 수의 범위를 넘어갔기 때문에 최소로 표현될 수 있는 수가 출력된 것이다. 이를 Overflow라고 한다.
조금 더 자세한 설명은 7.2 오버플로우와 언더플로우 알아보기 - C언어 코딩 도장을 참고하자.
Unsigned int형과 int형이 표현할 수 있는 수의 범위도 한번 출력해보자.
#include <iostream>
#include <limits> // 컴파일러한테 최대로 최소로 표시할 수 있는 범위를 물어봄
using namespace std;
int main()
{
unsigned int ui_max = std::numeric_limits<unsigned int>::max();
unsigned int ui_min = std::numeric_limits<unsigned int>::min();
cout << ui_max << endl; // 4294967295
cout << ui_min << endl; // 0
int i_max = std::numeric_limits<int>::max();
int i_min = std::numeric_limits<int>::min();
cout << i_max << endl; // 2147483647
cout << i_min << endl; // -2147483648
}
Unsigned int는 맨 앞 비트를 수를 표현하는데 사용하기 때문에 0 ~ 4294967295
까지 표현을 할 수 있고, int형은 맨 앞 비트를 부호를 표현하는데 사용하기 때문에 -2147483648 ~ 2147483647
까지 표현 할 수 있다.
Overflow는 큰 수를 다룰 때 한번 쯤은 만나게 될 수 있으므로 주의하면서 코딩을 하자!
다음은 나눗셈을 할 때 주의해야할 점이다. 정수끼리 나눗셈을 하면 몫만 저장이 된다. 따라서 소수점까지 표현을 하고 싶을 때는 float형이나 double형을 써야한다.
int div_i = 22 / 4;
cout << div_i << endl; // 5 출력
만약 소숫점까지 계산을 하고 싶을 경우 형변환을 통해 float형으로 변환을 해야한다.
float div_f = (flaot)22 / 4; // 정수끼리의 연산은 정수로 저장하려고 한다!
cout << div_f << endl; // 5.5 출력
3. C++ 11 고정 너비 정수 (Fixed-width Integers)
C++의 소수를 표현할 때 플랫폼마다 컴파일러마다 실제 구현된 Data size는 다를 수 있다.
따라서 C++ 11에서 고정 너비 정수라는 것을 채택하여 똑같은 Data size를 정의해줄 수 있다.
추후 멀티 플랫폼 프로그래밍을 많이 한다면 중요하게 생각해야한다.
멀티 플랫폼이란 단어가 생소했는데, [별별 개발 용어] 크로스 플랫폼(Cross Platform)이란?에서 감을 잡을 수 있었다.
고정 너비 정수를 초기화 하는 방법은 다음과 같다.
// #include <cstdint>
// 고정 너비 정수에 대한 헤더 파일<iostream>안에 고정 너비 정수를 사용할 수 있도록 되어있음
#include <iostream>
using namespace std;
int main()
{
std::int16_t i(5); // 16비트 = 2바이트 정수형
std::int8_t my_int(65); // 8비트 = 1바이트 정수형(= char형)
cout << my_int << endl; // char형이기 때문에 'A' 출력
std::int_fast8_t fi(5); // 8비트 size 중에 가장 빠른 것
std::int_least64_t li(5); // 적어도 64비트를 갖는 Data type
}
4. 무치형 (Void type)
Void는 포인터를 다룰 때 많이 사용되는데 여기서는 간단하게만 짚고 넘어가셨다.
Void 형은 메모리를 차지하지 않기 때문에 Void로 변수를 선언을 할 수 없다.
void my_void; // 이렇게 선언할 수 없다.
다만 Data type이 다르고 Data의 크기가 다르더라도 Data의 주소를 표현하는 Data의 양은 동일하다. 따라서 주소값을 알아볼 때 void*
를 많이 활용한다.
int i = 123;
float f = 123.456;
void* my_void;
my_void = (void*)&i; // 주소값을 알아보기 위해 사용
my_void = (void*)&f;
5. 부동소수점 수 (Floating point numbers)
소수점을 표현하는 방법을 한번 알아보자.
소수점을 표현할 수 있는 자료형은 3가지이다.
Double형보다 Float형이 최소 크기가 더 적기 때문에 메모리적인 측면에선 Float형을 사용하는 것이 좋다.
Float형이 숫자를 어떻게 저장할 때 메모리를 3 부분으로 나누어 이해를 할 수 있다.
Float형은 4 바이트이므로 32 비트를 사용한다.
맨 첫번째 비트는 부호비트, 8개의 비트를 활용해 지수, 나머지를 가수를 표현하는데 활용한다.
밑의 수를 분석해보면, 가수 부분을 표현하는데, $(1 + 0.75)$ 지수 부분을 표현하는데 $2^{7-127}$, 127은 Bias로 메모리에 저장하는 방식이라고 생각하면 된다.
따라서 이 복잡한 수를 통해 이해해야 할 것은 이진수, 부동 소수점 표현법을 거치면서 우리가 생각한 수와 다른 수가 저장될 수 있다는 것이다.
#include <limits>
를 이용해서 각각 자료형이 나타낼 수 있는 최대, 최소 수를 한번 출력해보자.
#include <iostream>
#include <limits>
int main()
{
using namespace std; // 함수 안에서도 namespace 사용 정의 가능
float f;
double d;
long double ld;
cout << numeric_limits<float>::max() << endl; // 3.40282e+38 출력
cout << numeric_limits<double>::max() << endl; // 1.79769e+308 출력
cout << numeric_limits<long double>::max() << endl; // 1.79769e+308 출력
// min은 표현할 수 있는 가장 작은 숫자
cout << numeric_limits<float>::min() << endl; // 1.17549e-38 출력
cout << numeric_limits<double>::min() << endl; // 2.22507e-308 출력
cout << numeric_limits<long double>::min() << endl; // 2.22507e-308 출력
// -부호를 활용해서 가장 작은 수를 보고 싶으면 lowest() 사용
cout << numeric_limits<float>::lowest() << endl; // -3.40282e+38 출력
cout << numeric_limits<double>::lowest() << endl; // -1.79769e+308 출력
cout << numeric_limits<long double>::lowest() << endl; // -1.79769e+308 출력
}
Float형을 초기화 할 때, e
를 활용하는 방법도 알아야 한다.
e = 10
이고 $3.14 * 100 = 3.14e2$ 라고 초기화를 하는 경우도 있다.
또한 Float형의 정밀도가 떨어지는 경우도 있는데 아래 예시 코드를 살펴보자.
#include <iostream>
#include <iomanip> // 입출력을 조작할 때 사용하는 헤더파일 - setprecision() 사용
int main()
{
using namespace std;
float f(123456789.0f); // 숫자 뒤 f를 붙여야 float형 그렇지 않으면 double형
// 10 significant digits
cout << std::setprecision(9); // cout할 때 출력되는 소숫점 조작
cout << f << endl; // 123456792 출력
}
내부적으로 2진수를 이용해서 저장을 하기 때문에 우리가 생각하는 것처럼 저장이 안될 수 있다.
정밀도가 떨어질 수 있다는 것을 알고 있어야 한다!
Double 형은 어떤지 한번 알아보자.
#include <iostream>
#include <iomanip> // 입출력을 조작할 때 사용하는 헤더파일 - setprecision() 사용
int main()
{
using namespace std;
double d(0.1);
cout << d << endl; // 0.1 출력 (#1)
cout << std::setprecision(17); // cout할 때 출력되는 소숫점 조작
cout << d << endl; // 0.10000000000000001 출력 (#2)
}
#1
과 #2
가 차이가 나는 이유는 무엇일까?
2진수로 0.1을 표현할 때 부동소수점 방법을 사용해서 0.1을 표현할 수 있는 가장 가까운 수이기 때문이다.
이런 오차가 누적되면 우리가 생각하는 것과 다른 수가 나온다.
또 다른 예시를 살펴보자.
int main()
{
using namespace std;
double d1(1.0);
double d2(0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1); // 0.1을 10번 더함
cout << std::setprecision(17); // cout할 때 출력되는 소숫점 조작
cout << d1 << endl; // 1.0 출력 (#3)
cout << d2 << endl; // 0.99999999999999989 출력 (#4)
}
오차가 누적되면 내가 원하는 대로 프로그램이 실행되지 않으니 꼭 알아두자.
또한 계산을 하는데 inf
, nan(ind)
와 같은 무한대 수가 나오면 이를 예외처리를 해주는 것이 좋은데, 그 때 쓰는 함수가 isnan()
과 isinf()
이다.
예시를 통해 살펴보자.
#include <iostream>
#include <cmath> // // isNan() 함수를 사용하기 위해서
int main()
{
double zero = 0.0;
double inf = 5.0 / zero;
double neginf = -5.0 / zero;
double nan = zero / zero;
cout << inf << "," << std::isnan(inf) << endl;
cout << neginf << "," << std::isinf(neginf) << endl;
cout << nan << "," << std::isnan(inf) << endl;
cout << 1.0 << "," << std::isnan(1.0) << endl;
}
출력 결과는 다음과 같다.
inf, 0
-inf,1
-nan(ind),0
1,0
이 때 nan
과 inf
는 다르다는 것도 알아둬야 한다!
6. 불리언 자료형과 조건문 if
Boolean은 표현할 수 있는 것이 True or False, 총 2가지이다.
Boolean을 초기화할 수 있는 방법은 앞장에서 배웠던 것과 같이 3가지이다.
bool b1 = true; // copy initialization
bool b2(false); // direct initialization
bool b3{ true }; // uniform initialization
이번 강의에서 총 3가지의 연산자를 배웠다.
!
- Not operator : 피연산자의 값을 반전시킨다.&&
- AND operator : 조건들이 모두 true일 경우 true, 그렇지 않으면 false||
- OR operator : 조건들 중 하나라도 true일 경우 true, 그렇지 않으면 false
아래 코드로 이해를 해보자.
#include <iostream>
using namespace std;
int main()
{
bool b2(false);
cout << std::boolalpha; // 출력할 때 1,0이 아니라 true, false로 나오게 해줌.
// NOT 연산자 !
cout << b2 << endl; // false 출력
cout << !b2 << endl; // true 출력
cout << std::noboolalpha; // 출력을 할 때 true, false가 아니라 1,0로 나오게 해줌
// 논리 연산자 %%
cout << (true && true) << endl; // 1 출력
cout << (true && false) << endl; // 0 출력
cout << (false && true) << endl; // 0 출력
cout << (false && false) << endl; // 0 출력
// OR 연산자 ||
cout << (true || true) << endl; // 1 출력
cout << (true || false) << endl; // 1 출력
cout << (false || true) << endl; // 1 출력
cout << (false || false) << endl; // 0 출력
return 0;
}
std::noboolalpha
와 std::boolalpha
도 출력을 할 때 적절히 활용해보자.
boolean 타입에서 중요한 것은 0이 아니면 모두 true라고 인식하는 것이다.
// if문에 0이면 false, 나머지는 true
if (5)
cout << "True" << endl;
else
cout << "false" << endl;
위 코드는 True
로 인식하고 True
를 출력할 것이다.
또한 Boolean type으로 cin을 받고 false
를 입력하면, 이를 숫자 0으로 인식하지 않기 때문에 참(true)값으로 이해를 하게 된다.
bool b; // 숫자 0이 아니면 true
cin >> b;
cout << std::boolalpha;
cout << "Your input : " << b << endl;
위 코드에 false
를 입력하게 되면 출력 값이 true
로 나온다!
숫자 0
을 입력해야 우리가 원하는 false
값이 출력되게 된다.
7. 문자형 char type
컴퓨터는 문자 하나를 숫자로 변환하여 인식하게 된다.
변환을 할 때는 우리가 규칙에 따라 변환을 하는데 그 때 사용하는 Table이 “ASCII Table”이다.
char 자료형을 사용하면 숫자로 초기화를 해도 문자로 컴퓨터가 인식을 한다. 문자 하나를 초기화때는 ' '
를 사용한다. 문자열을 초기화할 때는 " "
를 사용한다.
char c1 = 65;
char c2('A'); // 한글자는 '', 문자열은 "" C++은 std::String을 사용
cout << c1 << " " << c2 << " " << (int)c1 << " " << (int)c2 << endl; // A A 65 65 출력
위와 같이 char를 숫자로 변환하여 출력하고 싶으면 형변환을 진행해야한다.
형변환을 하는 방법은 크게 3가지로 아래와 같다.
// c-style casting
cout << (char)65 << endl; // A 출력
cout << (int)'A' << endl; // 65 출력
// cpp style casting
cout << char(65) << endl; // A 출력
cout << int('A') << endl; // 65 출력
// 기본 타입들 변환할 때 사용
cout << static_cast<char>(65) << endl; // A 출력
cout << static_cast<int>('A') << endl; // 65 출력
char형은 한 글자밖에 담지 못하는 자료형인데 cin을 이용하여 여러 문자를 입력받으면 어떻게 될까?
나머지 문자들은 출력이 되지 않을 뿐 버퍼에 담겨있다!
#include <iostream>
using namespace std;
int main()
{
char c3;
cin >> c3;
cout << c3 << " " << static_cast<int>(c3) << endl;
return 0;
}
위와 같은 코드에 abc
를 입력으로 넣어주면 a
만 출력된다.
반대로 버퍼에 문자가 있는데 cin으로 입력을 받으려고 한다면 어떻게 될까?
#include <iostream>
using namespace std;
int main()
{
char c3;
cin >> c3;
cout << c3 << " " << static_cast<int>(c3) << endl;
cin >> c3;
cout << c3 << " " << static_cast<int>(c3) << endl;
cin >> c3;
cout << c3 << " " << static_cast<int>(c3) << endl;
cin >> c3;
cout << c3 << " " << static_cast<int>(c3) << endl;
return 0;
}
첫번째 cin에 abc
를 입력한다면 두번째, 세번째 cin은 건너뛰고 네번째 cin에서 문자를 입력받고 출력한다!
char형과 unsigned char의 Size를 출력해보면 다음과 같다.
cout << sizeof(char) << endl; // 1 출력
cout << (int)std::numeric_limits<char>::max() << endl; // 127 출력
cout << int(std::numeric_limits<char>::min()) << endl; // -128 출력
cout << sizeof(unsigned char) << endl; // 1 출력
cout << (int)std::numeric_limits<unsigned char>::max() << endl; // 250 출력
cout << int(std::numeric_limits<unsigned char>::min()) << endl; // 0 출력
줄바꿈을 할 때 보통 \n
과 std::endl
을 사용한다.
이 둘의 가장 큰 차이점은 \n
은 단순히 줄바꿈을 하지만, std::endl
는 버퍼에 담겨져 있는 것을 모두 출력한 후 줄바꿈을 한다는 것이다.
버퍼에 담겨져 있는 것을 쏟아낸 후 줄바꿈을 하지 않으려면 std::flush
를 사용한다.
유니코드나 특수문자같은 8비트로 표현하기 어려운 문자들은 char32_t
와 같은 자료형을 사용하기도 한다.
8. 리터럴 상수 literal constants
리터럴 상수란 말그대로 숫자를 직접 입력한 상수이다.
대부분의 경우는 숫자만 활용해서 초기화를 하지만, 자료형을 조금 더 명확하게 하기 위해 뒤에 문자를 사용하여 초기화하기도 한다.
float pi = 3.14f; // float 자료형
int i = 12334u; // unsigned int로 명확하게 하기
int j = (unsigned int)12334; // 보통 형변환을 명시해주는 것이 좋다.
long n2 = 5L; // long 자료형
double d = 6e10; // e는 10을 뜻함. d = 6x10^10
대부분은 10진수로 수를 나타내지만 8진수와 16진수를 이용해서 상수를 표현하기도 한다.
2진수를 활용해서 코딩을 할 때 보통 16진수로 표현한다.
8진수는 숫자 앞에 0
을 추가하고, 16진수는 숫자 앞에 0x
를 추가한다.
int x = 012; // 8진수로 표현하고 싶을 때
int y = 0xF; // 16진수로 표현하고 싶을 때
cout << x << endl; // 10 출력
cout << y << endl; // 15 출력
C++14부터 binary(2진법) literal 상수를 사용할 수 있다.
int z = 0b1010; // 2진수
int z1 = 0b000'0001'0101; // 사람이 읽기 편하도록 '를 추가 가능, 10진수도 사용가능
여기서 발견한 꿀팁은 숫자 중간 중간 '
를 넣어도 컴파일러가 이를 무시하고 수로 인식을 한다는 것이다. 가독성을 높일 때 사용하면 좋을 것 같다.
또한 매직 넘버를 사용하지말고 const
를 활용해서 리터럴 상수가 어떤 것을 의미하는지 먼저 선언하고 const로 선언한 변수를 사용하자.
매직 넘버란 컨텍스트(context)가 없는 코드 중간에서 하드 코딩된 리터럴(일반적으로 숫자)을 말한다.
int num_item = 123;
int price = num_item * 30; // 이렇게 사용하지 말자.
위 코드는 아래와 같이 사용하는 것이 좋다.
const int price_per_item = 30;
int num_item = 123;
int price = num_item * price_per_item;
9. 심볼릭 상수 symbolic constants
심볼릭 상수는 변하지 않는 수인데 변수처럼 문자로된 이름을 가진 상수이다.
const
를 활용해서 변하지 않는 수를 정의할 수 있다.
const double gravity; // 잘못된 예
double const gravity2{9.8};
gravity2 = 1.0; // 값을 바꿀 수 없다.
const를 활용해서 변수를 만들면 다시 값을 변경할 수 없으며 처음에 반드시 초기화를 진행해줘야한다.
자료형 뒤에 붙기도 하고, 앞에 붙기도 한다.
int doSometing(const int x)
{
...
return new_number;
}
함수를 만들 때 입력에 const
를 붙이는 경우가 많다. 함수 내에서 값이 변하는 것을 방지하고, 함수의 입출력을 명확하게 해주기 위해서다.
const int my_number(123);
int number;
cin >> number;
const int special_number(number);
위 코드에서 my_number
과 special_number
의 차이점은 무엇일까?
컴파일 타임과 런 타임이 다르다는 것을 이해해야 한다.
my_number
const 상수는 컴파일을 할 때, 변수가 어떤 변하지 않는 수로 결정이 되는데, special_number
와 같은 상황에선 프로그램을 실행해야 const에 어떤 값이 들어가고 그 값에 의해 const 상수가 초기화가 된다.
이 두 const 상수의 차이를 명시해주기 위해 C++11에서 constexpr
이라는 것이 추가되었다.
constexpr
는 컴파일 타임에 값이 완전히 결정되는 컴파일 상수라는 것을 컴파일을 하면서 체크를 하겠다는 의미이다.
constexpr int my_number(123); // 올바른 사용
constexpr int special_number(number); // 잘못된 사용
따라서 위 예시에서 my_number
는 constexpr
를 활용할 수 있지만, special_number
같은 경우는 constexpr
를 사용할 수 없다.
매크로(#define)을 활용하여 심볼릭 상수를 만들기도 하는데, C++에서는 매크로를 활용해서 매직 넘버를 대체하는 경우는 거의 없다!
쓰지 않는 이유는 다음과 같다.
- 디버깅이 힘들다.
- #define을 활용해서 심볼릭 상수를 만들면 적용되는 범위가 너무 넓어진다.
그렇다면 const 상수는 어디에 선언하는 것이 좋을까?
내가 정의해둔 const 상수를 한 곳에 모아두는 것이 좋다. 따라서 헤더 파일을 하나 만들고 그 안에 namespace를 만든다음 그 안에 내가 정의한 const 상수를 적는 것이 추후 코딩을 할 때 도움이 된다. 재사용성이 있기 때문에 코딩하는 시간을 줄일 수 있고, 객체지향 프로그래밍 할 때 비슷한 스타일로 코딩을 하기 때문이다.
이렇게 쳅터 2를 모두 정리해보았다. 그동안 무심코 썼던 것들을 확실하게 짚고 넘어가는 느낌이다. 차곡차곡 정리하면 분명 도움이 될 것이다.
댓글남기기