• 2.7.1 Literal Constants
  • 2.7.2 Symbolic Constants
  • 2.7.3 매크로와 상수의 차이점
  • 2.7.4 상수를 사용하여 퇴근을 빨리 하는 방법
  • 2.7.5 const 키워드가 매개변수에 있는 경우

2.7.1 Literal Constants

 

상수는 변하지 않는 수를 의미하며 그중 하나가 리터럴 상수이다. 리터럴 상수에 대한 개념을 처음에는 받아들이기 힘들 수 있는데, 변수처럼 메모리에 이름이 지어져서 할당되는 것이 아니며, 메모리 공간을 가리키는 이름을 가지지 않은 수라 생각하면 좋을 것 같다.

 

일반적으로 우리가 변수에 값을 대입할 때 사용하는 숫자 및 문자가 리터럴 상수가 되겠다. C++ 14에서 Binary Literals도 추가가 되었다.

#include <iostream>

int main()
{
	using namespace std;

	// 숫자나 글자로 표기되는 r-value = literals
	float pi = 3.14f;

	unsigned int n = 5u;
	long n2 = 5L;

	// Decimal	: 0 1 2 3 4 5 6 7 8 9 10
	// Octal	: 0 1 2 3 4 5 6 7 10
	// Hexa		: 0 1 2 3 4 5 6 7 8 9 A B C D E F 10

	int x = 0xF;
	cout << x << endl;
	x = 012;
	cout << x << endl;
	x = 0b1010;
	cout << x << endl;
	x = 0b1011'1111'1010; // 컴파일러는 '를 무시한다. 10진수에도 사용가능하다.
	cout << x << endl;

	return 0;
}

위의 코드에서는 대입 연산자(=) 오른쪽에 있는 것들이 모두 리터럴 상수이다. 바이너리 리터럴 상수를 보면 숫자들 사이에 문자가 들어간 것을 볼 수가 있는데 바이너리 리티럴 상수를 4bit 단위로 끊어서 볼 수 있도록 생겨난 표기법이다. 컴파일러는 '를 무시하므로 써도되고 안 써도 된다. 십진수에서도 사용할 수 있다.

 

숫자 앞에 여러 표현을 활용하면 프로그래밍에서 자주 쓰이는 진수 표현을 쓸 수 있다.

0x : 16진수
0  :  8진수
0b : 2진수

2.7.2 Symbolic Constants

 

심볼릭 상수란 const 키워드와 constexpr 키워드를 통해서 명시적으로 데이터를 상수화시키는 것이다. 심볼릭 상수를 만들면 추후에 해당 상수의 값을 변경하려 할 때 컴파일러가 막아준다. 즉, 우리가 실수할 수도 있는 부분을 컴파일러가 서포트해주는 것이다. constexpr은 C++ 11에서 생긴 것으로 완벽하게 컴파일타임에서 상수로 결정 난다는 것을 키워드로 표현한다.

#include <iostream>

using namespace std;

int main()
{
	const double gravity = 9.8;
	//double const gravity = 9.8; // 이처럼 선언해도 가능하다.

	int number;
	cin >> number;
	const int special_number(number); // 사용자가 입력하는 심볼릭 상수

	constexpr int price_per_item = 123;
    
    return 0;
}

컴파일타임에서 상수임이 결정 나는 것을 컴파일타임 상수라고 칭하며, 런타임에서 결정 나는 경우 런타임 상수라고 말한다. 런타임 상수의 경우 사용자가 입력하는 심볼릭 상수가 된다. 심볼릭 상수는 상수이기 때문에 선언과 동시에 초기화를 해야 한다.


2.7.3 매크로와 상수의 차이점

 

C언어를 먼저 배우는 사람의 경우 매크로를 상수처럼 취급하여 쓰는 경우가 많다. 매크로를 활용하면 컴파일 단계에서 매크로로 만든 리터럴 상수를 치환하여 입력해주기 때문에 편하다. 하지만 매크로를 C++에서는 잘 안 쓰게 되는데 그 이유는 바로 적용 범위가 너무 넓기 때문이다. 매크로가 존재하는 파일을 include하는 경우 그 파일에도 해당 매크로가 적용되기 때문에 객체 지향 언어로 넘어간 C++에서 보는 매크로가 전역 변수처럼 작동하는 게 좋지 않다고 보는 것이다.

#include <iostream>
#define PRICE_PER_ITEM 30 // c에서 많이 쓰지 c++에서는 잘 안쓴다. why? 적용범위가 너무 넓다.

using namespace std;

int main()
{
	constexpr int price_per_item = 123; // 매크로보다 const가 더 좋다.

	return 0;
}

2.7.4 상수를 사용하여 퇴근을 빨리 하는 방법

 

과학 계산을 수행하는 곳에서 업무를 수행하다 보면 많은 상수를 사용하게 되는데, 이를 한 곳에서 모아서 심볼릭 상수로 잡아두면 관리가 편해진다.

 

상수를 모아둔 헤더 파일

 

위처럼 상수를 한 곳에 모아서 두고 상수를 써야 하는 곳에서 include를 해준다.

#include <iostream>
#include "MY_CONSTANTS.h"

using namespace std;

int main()
{
	double radius;
	cin >> radius;
	double circumference = 2.0 * radius * constants::pi;

	return 0;
}

헤더에서 사용한 namespace와 함께 파이를 사용한 코드이다.


2.7.5 const 키워드가 매개변수에 있는 경우

 

함수가 선언된 코드들을 보면 간혹 매개변수에 const 키워드가 붙어 있는 경우를 볼 수가 있다. 매개변수에 const 키워드가 붙어 있는 이유는 해당 함수에서 인자로 들어온 매개변수를 함수 내에서 변경하면 안 된다는 제약 조건을 컴파일 수준에서 정하기 위함이다.

#include <iostream>

using namespace std;

void printNumber(const int my_number)
{
	cout << my_number << endl;
}

int main()
{
	printNumber(123);

	return 0;
}

이처럼 코드를 작성하면 printNumber 함수 안에서 my_number를 수정하게 될 때 컴파일 에러가 뜨게 된다.

  • 2.6.1 Char type
  • 2.6.2 limits 헤더를 이용한 char 한계치 확인
  • 2.6.3 입력이 문자열이고 받는 메모리가 문자인 경우 버퍼의 작동 방식
  • 2.6.4 강제 형 변환

2.6.1 Char type

 

Char type은 문자를 다루는 자료형이다. 문자의 경우 ASCII CODE로 대응시켜서 다루게 되며, 이 때문에 char 자료형에 숫자를 저장해서 출력하는 경우 숫자가 아닌 문자가 나오게 된다. 출력 시 문자가 나오지만 숫자로 다룰 수 있는 특징이 있다. 문자를 대입하는 경우 홑따옴표(' ')를 사용하게 된다.

#include <iostream>

int main()
{
	using namespace std;

	char c1(65);
	char c2 = 'A';
    
	cout << c1 << " " << c2 << " " << int(c1) << " " << int(c2) << endl;

	return 0;
}

위의 코드를 실행하게 되면 둘 다 문자로는 A가 출력되고 숫자로는 65가 출력되는 것을 확인할 수 있다.


2.6.2 limits 헤더를 이용한 char 한계치 확인

 

아래의 코드를 통해 char 자료형의 한계치를 확인할 수 있다.

#include <iostream>
#include <limits>

int main()
{
	using namespace std;

	cout << sizeof(char) << endl;
	cout << (int)numeric_limits<char>::max() << endl;
	cout << (int)numeric_limits<char>::lowest() << endl;

	return 0;
}

char 자료형은 1byte의 크기를 가지며 양수는 127, 음수로는 -128까지 표현이 가능하다. 이러한 특징 덕분에 네트워크 통신 분야에서는 이를 작은 크기의 정수로 다루어서 사용하기도 한다.


2.6.3 입력이 문자열이고 받는 메모리가 문자인 경우 버퍼의 작동 방식

 

앞에서도 언급하였듯이 문자로 할당된 메모리는 하나의 값만 받는다. 하지만 사용자가 입력을 문자가 아닌 문자열로 입력하는 실수를 저지르면 어떻게 작동이 될까?

#include <iostream>

int main()
{
	using namespace std;

	char c1(65);

	cin >> c1;
	cout << c1 << " " << static_cast<int>(c1) << endl;

	return 0;
}

c1의 초기화는 65로 A가 저장되어 있다. 여기에 cin을 통한 사용자 입력을 받기로 한다. 하지만 사용자가 실수로 qwe를 입력하였다. 결과는 아래와 같다.

 

q만 받는다.

 

qwe가 입력되었지만 q만 c1에 저장해서 출력하는 것으로 프로그램이 끝나게 된다. 그럼 we는 자동으로 버려지도록 동작할까? 이번에는 위의 코드에서 입력을 두 번 더 받을 수 있도록 작성해서 코드를 작성하고 똑같이 qwe를 입력한다.

#include <iostream>

int main()
{
	using namespace std;

	char c1(65);

	cin >> c1;
	cout << c1 << " " << static_cast<int>(c1) << endl;

	cin >> c1;
	cout << c1 << " " << static_cast<int>(c1) << endl;

	cin >> c1;
	cout << c1 << " " << static_cast<int>(c1) << endl;

	return 0;
}

실행 결과는 아래와 같다.

 

qwe를 하나씩 출력한다.

 

이처럼 stream을 사용하게 되었을 때 문자열을 길게 나열해서 넣으면 받는 메모리 길이에 따라 자동으로 받고 남는 부분을 버퍼에 남기도록 동작하게 된다.


2.6.4 강제 형 변환

 

형 변환으로 여러 가지가 존재한다. 프로그래밍하면서 자주 본건 c-style casting이었다. cpp-style casting을 직접 써보진 않았지만 지금 보면서 생각해보면 아래처럼 제대로 감싸서 형 변환시키는 것이 안전할 거란 생각이 든다. static_cast도 존재하는데 이는 나중에 추가적으로 설명할 예정이다.

#include <iostream>
#include <limits>

int main()
{
	using namespace std;

	// c-style casting(강제 변환)
	cout << (char)65 << endl;
	cout << (int)'A' << endl;

	//cpp-style casting(강제 변환)
	cout << char(65) << endl;
	cout << int('A') << endl;

	// 추후 설명(기본형에 대한 변환을 컴파일러에게 미리 알림)
	cout << static_cast<char>(65) << endl;
	cout << static_cast<int>('A') << endl;

	// 위의 3개는 같은 결과를 보인다.


	//static_cast를 사용한다고해서 기존에 있는 것이 변하는 것은 아님
	char ch(97);
	cout << ch << endl;
	cout << static_cast<int>(ch) << endl;
	cout << ch << endl;

	return 0;
}
  • 2.5.1 Boolean type
  • 2.5.2 사용 시 주의사항

2.5.1 Boolean type

 

Boolean 자료형은 참인지 거짓인지에 대한 값을 가지는 자료형이다. true는 1의 값을 가지며, false는 0의 값을 가진다. 따라서 일반적인 출력을 실행할 경우 true와 false는 각각 1과 0을 출력하게 된다. 콘솔 창에서 true/false로 출력하고 싶다면 std::boolalpha를 통해 변경할 수 있다.

 

#include <iostream>

int main()
{
	using namespace std;

	bool b1 = true;	// copy initialization
	bool b2(false);	// direct initialization
	bool b3{ true };	// uniform initialization
	b3 = false;	// assignment operator

	cout << std::boolalpha;
	cout << b3 << endl;
	cout << b1 << endl;
	cout << std::noboolalpha;
	cout << b3 << endl;
	cout << b1 << endl;

	return 0;
}

2.5.2 사용시 주의사항

 

Boolean 자료형을 사용할 경우 여러 가지 주의사항이 있다. 첫 번째로 not operator인 !를 사용하는 경우인데, 해당 연산자는 눈에 잘보이지 않기 때문에 협업하는 사람들이 힘들어질 수 있으므로 최대한 사용 안 하도록 프로그래밍을 하는 것이 좋다. 또한 c++에서는 0을 제외한 모든 수를 true로 인식하고서 조건을 받아들인다.

 

#include <iostream>

int main()
{
	using namespace std;

	// ! not operator 안쓸 수 있다면 안쓰는게 좋다.

	// 논리 연산자 &&(and) 
	cout << (true	&& true)	<< endl; // true
	cout << (true	&& false)	<< endl; // false
	cout << (false	&& true)	<< endl; // false
	cout << (false	&& false)	<< endl; // false

	// 논리 연산자 ||(or) 
	cout << (true	|| true)	<< endl; // true
	cout << (true	|| false)	<< endl; // true
	cout << (false	|| true)	<< endl; // true
	cout << (false	|| false)	<< endl; // false

	// 0을 제외한 모든 수는 true
	if (5)
		cout << "This is true" << endl;
	else
		cout << "This is false" << endl;

	bool b;
	cin >> b;
	cout << "Your input : " << b << endl;

	return 0;
}

 

입력값에 true 혹은 false라고 입력하면 모두 true로 출력할 수도 있다. 이는 방금 언급한 내용에 의한 것이며, 입력값을 문자열로 다루기 때문에 false를 0으로 인식하지 못하기 때문이다. 하지만 컴파일러마다 다르고 버전마다 다르니 이처럼 모호한 입력을 주지 않도록 해야 한다. 0을 입력하면 정확히 false로 인식한다.

'Programming Language > C++' 카테고리의 다른 글

Section 2.7. 리터럴 상수와 심볼릭 상수  (0) 2021.10.19
Section 2.6. char 자료형  (0) 2021.10.18
Section 2.4. 부동소수점수(floating point numbers)  (0) 2021.10.14
Section 2.3. void  (0) 2021.10.12
Section 2.2. 정수형  (0) 2021.10.11
  • 2.4.1 부동소수점수(floating point numbers) 종류
  • 2.4.2 limits 헤더를 이용한 부동소수점수 한계치 확인
  • 2.4.3 과학적 표기법(scientific notation)
  • 2.4.4 Floating error
  • 2.4.5 inf and nan

2.4.1 부동소수점수(floating point numbers) 종류

 

부동소수점수는 float, double, long double로 총 3가지가 c++에 존재한다. 부동소수점이라고 불리는 이유는 과학적 표기법(scientific notation)에 의해 주어진 수의 소수점 위치가 이동하기 때문이며, 부동은 움직이지 않는다는 의미가 아닌 떠다닌다의 의미로 받아들이면 된다.

 

float 자료형은 딥러닝에서 자주 쓰고 기본 실수 계산은 double을 선호하게 된다. 딥러닝이 아닌 일반 수치해석적 과학 계산에서는 double을 사용하는 것을 선호한다.

 

float-point numbers(32 bit)

 

float-point numbers(64 bit)

 

부동소수점수는 부호와 지수, 가수를 통해 구현이 된다. 이러한 표현 방식 때문에 실제 값과 입력 값 차이의 오차가 생기는 한계가 생긴다.


2.4.2 limits 헤더를 이용한 부동소수점수 한계치 확인

 

sizeof 연산자를 이용하면 각 자료형에 대한 크기를 알 수 있다. 이전과 동일하게 limits 헤더를 이용하면 해당 자료형의 한계치를 확인할 수 있다. lowest 함수를 사용해야 가장 작은 음수를 확인할 수 있다.

 

#include <iostream>
#include <limits>

int main()
{
	using namespace std;

	cout << sizeof(float)			<< endl; // 4 bytes
	cout << sizeof(double)		<< endl; // 8 bytes
	cout << sizeof(long double)	<< endl; // 8 bytes

	cout << numeric_limits<float>::max()		<< endl; // min() lowest()
	cout << numeric_limits<double>::max()		<< endl;
	cout << numeric_limits<long double>::max()	<< endl;

	return 0;
}

2.4.3 과학적 표기법(scientific notation)

 

지수 표현을 활용하여 과학적 표기법을 쓸 수 있다. 지수 표현의 경우 과학 계산에서 상수로 쓸만한 값들에 대해 쓰는 것 이외에는 볼 수가 없지만 실무에서의 규칙은 모든 회사가 다르기 때문에 그에 맞게 사용하면 된다.

 

#include <iostream>

int main()
{
	using namespace std;

	// scientific notation
	cout << 3.14		<< endl;
	cout << 31.4e-1	<< endl;
	cout << 31.4e-2	<< endl;
	cout << 31.4e1		<< endl;
	cout << 31.4e2		<< endl;

	return 0;
}

 

e-1은 1/10을 의미하며, e-2은 1/100을 의미한다. +에 대해서는 역수를 취하면 된다. 또한 +는 생략이 가능하다.


2.4.4 Floating error

 

float 자료형이 아닌 double 자료형도 흔히 사용하는 이유는 바로 floating error 때문이다. 메모리를 많이 사용하면 더 세밀한 실수를 다룰 수 있는데, float 자료형을 쓸 경우 소수점 아래 6번째 자리부터 신뢰하기 힘들어진다. double 자료형도 소수점 아래 16번째 자리부터 신뢰하기 힘들어지는데 일반적으로 10자리를 넘어가는 자리수에 대해서는 고려하지 않는다. 또한 콘솔 창에서 표기된 수는 setprecision으로 정확히 보는 게 아닌 이상 믿을 수 없다.

 

#include <iostream>
#include <iomanip> // 입출력 조작

int main()
{
	using namespace std;

	cout << std::setprecision(25);
	cout << 1.0f / 3.0f	<< endl;
	cout << 1.0 / 3.0	<< endl;

	float f1 = 123456789.0f; // 10 significant digits
	cout << f1 << endl;

	double d1 = 1.0;
	cout << d1 << endl;
	cout << std::setprecision(30);
	cout << d1 << endl;

	double d2 = 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1;
	cout << d2 << endl;

	return 0;
}

 

위의 코드를 실행하면 여러가지 의미 있는 결과들을 볼 수 있다. 특히 d1과 d2의 결과는 기억해둘 필요가 있다.


2.4.5 inf and nan

 

0으로 나누는 것을 수학에서 정의하지 않지만 코드를 작성하면서 간혹 0으로 나누는 일을 수행할 수 있다. 0으로 나누는 경우 그 수는 inf로 표기되는데, 무한대라고 보면 된다. 또한 수가 아닌 경우엔 not a number의 약자인 nan으로 표기된다. cmath 헤더에는 인자로 들어오는 수가 inf인지 nan인지 체크해주는 함수가 있다.

 

#include <iostream>
#include <cmath>

int main()
{
	using namespace std;

	double zero	= 0.0;
	double posinf	= 5.0 / zero;
	double neginf	= -5.0 / zero;
	double nan	= zero / zero;

	cout << posinf	<< endl;
	cout << neginf	<< endl;
	cout << nan	<< endl; // -nan(ind) indeterminate
	cout << posinf	<< " " << std::isinf(posinf) << endl;
	cout << neginf	<< " " << std::isinf(neginf) << endl;
	cout << nan	<< " " << std::isnan(nan) << endl;
	cout << 1.0	<< " " << std::isnan(1.0) << endl;

	return 0;
}

'Programming Language > C++' 카테고리의 다른 글

Section 2.6. char 자료형  (0) 2021.10.18
Section 2.5. Boolean 자료형  (0) 2021.10.15
Section 2.3. void  (0) 2021.10.12
Section 2.2. 정수형  (0) 2021.10.11
Section 2.1. 기본 자료형  (0) 2021.09.29

+ Recent posts