본문 바로가기
개인공부/C++ 기초플러스

C++ Primer 03

by 하고싶은건많은놈 2023. 2. 18.

학습목표

  • C++의 변수 이름을 정하는 규칙
  • C++에 내장된 정수형 : unsigned long, long, unsigned int, int, unsigned short, short, char, unsigned char, signed char, bool
  • C++11에서 추가된 변수형 : unsigned long long, long long
  • 여러 정수형의 시스템 한계값을 나타내는 climits 파일
  • 여러 정수형의 수치 상수들
  • const 제한자를 사용하여 기호 상수 만들기
  • C++에 내장된 부동 소수점형 : float, double, long double
  • 여러 부동 소수점형의 시스템 한계값을 나타내는 cfloat 파일
  • 여러 부동 소수점형의 수치 상수들
  • C++의 산술 연산자
  • 자동 데이터형 변환
  • 강제 데이터형 변환

3.1 간단한 변수

변수 이름

의미를 쉽게 알 수 있는 변수 이름을 사용할 것을 권장
C++에서 변수명을 설정할 때는 지켜야하는 규칙이 있음

  • 변수명에는 영문자, 숫자, 밑줄(_)만 사용 가능
  • 숫자를 변수 이름의 첫 문자로 사용할 수 없음
  • 변수 이름에서 대문자와 소문자는 별개로 취급
  • C++의 키워드는 변수명으로 사용할 수 없음
  • 두개의 밑줄 문자로 시작하거나 밑줄문자 + 대문자로 시작하는 이름은 컴파일러와 리소스가 사용하기로 예약되어있음
  • 하나의 밑줄 문자로 시작하는 이름은 컴파일러와 리소스가 전역 식별자(global identifier)로 사용하기로 예약되어있음
  • 변수명의 길이에는 제한이 없으며 변수명에 쓰인 모든 문자가 유효함
    ANSI C(C99)에서는 63개 문자만 유효한것으로 처리함

두 개 이상의 단어를 결합할때는 단어들 사이를 밑줄로 구분하거나 단어들의 첫 문자를 대문자로 쓰는 것이 관례


정수형

소수부가 없는 수, 메모리 용량의 한계가 있으므로 정수들의 부분 집합만을 나타낼 수 있음
C++에서는 데이터형에 따라 서로 다른 크기의 메모리를 사용함
기본 정수형은 크기 순서대로 char - short - int - long - long long이 있음
기본형들에 대해 signed형(양수 + 음수값 모두 포함)과 unsigned형(양수값만 포함)이 각각 따로 존재


short / int / long / long long

C++은 해당 데이터형들의 최소 크기만을 지정하여 융통성있는 표준을 제공함

  • short형은 최소한 16비트 폭을 가짐
  • int형은 최소한 short형 크기 이상, 일반적으로는 32비트를 사용
  • long형은 최소한 32비트 폭을 가지며 int형 크기 이상
  • long long형은 최소한 64비트 폭을 가지며 long형 크기 이상

sizeof연산자로 변수나 데이터형의 크기를 바이트 단위로 확인할 수 있음
또는 climits 헤더 파일에서 정의된 값을 통해서도 확인할 수 있음

// limits.cpp

#include <iostream>
#include <climits>

int main()
{
	using namespace std;
	int n_int = INT_MAX;
	short n_short = SHRT_MAX;
	long n_long = LONG_MAX;
	long long n_llong = LLONG_MAX;

	cout << "int는 " << sizeof (int) << " 바이트이다." << endl;
	cout << "short는 " << sizeof n_short << " 바이트이다." << endl;
	cout << "long은 " << sizeof n_long << " 바이트이다." << endl << endl;
	cout << "long long은 " << sizeof n_llong << " 바이트이다." << endl;
	cout << endl;

	cout << "최댓값 : " << endl;
	cout << "int : " << n_int << endl;
	cout << "short : " << n_short << endl;
	cout << "long : " << n_long << endl << endl;
	cout << "long long : " << n_llong << endl << endl;
	cout << "int의 최솟값 = " << INT_MIN << endl;
	cout << "바이트 당 비트수 = " << CHAR_BIT << endl; 
	return 0;
}

sizeof를 데이터형 이름에 사용할시 괄호가 필요, 변수명에 사용할시에는 괄호가 없어도 상관없음
INT_MAX, CHAR_BIT(CHAR형의 비트 수 = 바이트 당 비트 수) 등은 climits 파일에 정의되어있는 기호 상수임

  • #define SHORT_MAX 32767의 형식으로 정의되어있으며, #define#include와 같은 전처리 지시자임
  • #define 지시자가 INT_MAX가 나올때마다 32767로 대체하라고 전처리기에게 지시
  • 결과적으로 소스 코드 내의 모든 INT_MAX가 대체되어 컴파일 과정으로 넘겨짐
  • 단, 이는 C로부터의 유물이며 C++에서는 const 키워드를 사용하는 더 좋은 방법이 있음

정의된 변수를 초기화하지 않으면 변수의 값은 미확정(undefined) 상태가 되어 그 메모리 위치에 우연히 남아있던 것이 해당 변수의 값으로 행세하게 됨
따라서 변수를 선언할 때부터 값을 대입하여 초기화까지 하는 것을 권장함

전통적인 C 스타일의 초기화 문법 : int owls = 101;
C++의 새로운 초기화 문법 : int owls(101);
C++11에서의 초기화 문법 : int owls = { 101 }; / int owls{ 101 };
중괄호 안이 공백일시 0으로 초기화됨


unsigned형

음수 값을 저장할 수 없는 대신 변수에 저장할 수 있는 최대값이 늘어남

// exceed.cpp

#include <iostream>
#define ZERO 0
#include <climits>

int main()
{
	using namespace std;
	short Dohee = SHRT_MAX;
	unsigned short Insuk = Dohee;

	cout << "도희의 계좌에는 " << Dohee << "원이 들어있고, ";
	cout << "인숙이의 계좌에도 " << Insuk << "원이 들어있다." << endl;
	cout << "각각의 계좌에 1원씩 입금한다." << endl << "이제 ";
	Dohee = Dohee + 1;
	Insuk = Insuk + 1;
	cout << "도희의 잔고는 " << Dohee << "원이 되었고, ";
	cout << "인숙이의 잔고는 " << Insuk << "원이 되었다." << endl;
	cout << "이럴수가! 도희가 나 몰래 대출을 했나?" << endl;
	Dohee = ZERO;
	Insuk = ZERO;
	cout << "도희의 계좌에는 " << Dohee << "원이 들어있고, ";
	cout << "인숙이의 계좌에도 " << Insuk << "원이 들어있다." << endl;
	cout << "각각의 계좌에 1원씩 인출한다." << endl << "이제 ";
	Dohee = Dohee - 1;
	Insuk = Insuk - 1;
	cout << "도희의 잔고는 " << Dohee << "원이 되었고, ";
	cout << "인숙이의 잔고는 " << Insuk << "원이 되었다." << endl;
	cout << "이럴수가! 인숙이가 복권에 당첨되었나?" << endl;
	return 0;
}

데이터형의 표현 한계값을 벗어날시(오버플로우 또는 언더플로우) unsigned 정수형의 경우 항상 반대편 끝으로 넘어감
C++에서는 signed 정수형의 경우도 대부분 동일하게 동작하지만, 보장할 수는 없음


정수형 선택

일반적으로는 컴퓨터가 가장 효율적으로 처리하는 정수 형식인 int형을 사용하는 것을 권장
음수가 될 수 없는 수를 나타낼 때는 unsigned 정수형을 사용해 보다 큰 값을 나타냄
수를 나타내기 위해 16비트보다 더 많이 필요한 수는 long형을 사용함

  • int형이 32비트인 경우라도 long형을 사용하는데, 이는 16비트 int형을 사용하는 시스템으로 이식할 경우에 오동작을 막기 위함임
  • 이십억으로도 모자랄 경우에는 long long형을 사용

short형이 int형보다 크기가 작을 경우에는 메모리 절약을 위해 short형을 사용함

  • 메모리 절약이 중요하다면 두 데이터형의 크기가 같을 경우에도 short형을 사용해야함
  • 16비트 int형을 사용하는 시스템에서 32비트 int형을 사용하는 시스템으로 프로그램을 이식할경우, int형의 경우 두 배의 메모리를 요구하게 되지만 short형은 그렇지 않아 비트를 절약할 수 있음

1바이트만 필요할 경우에는 char형을 사용할 수 있음


정수형 상수

// hexoct1.cpp

#include <iostream>

int main()
{
	using namespace std;
	int chest = 42;
	int waist = 0x42;
	int inseam = 042;

	cout << "손님 몸매는 한마디로 끝내줍니다!\n";
	cout << "가슴둘레 " << chest << "\n";
	cout << "허리둘레 " << waist << "\n";
	cout << "인심길이 " << inseam << "\n";
	return 0;
}

정수형 상수 : 프로그램에 직접 써 넣는 정수

  • 처음 숫자가 1~9 : 10진수
  • 처음 숫자가 0, 두번째 숫자가 1~7 : 8진수
  • 처음 두개의 문자가 0x 혹은 0X : 16진수

cout은 기본적으로 정수를 10진수로 출력

// hexoct2.cpp

#include <iostream>

int main()
{
	using namespace std;
	int chest = 42;
	int waist = 42;
	int inseam = 42;

	cout << "손님 몸매는 한마디로 끝내줍니다!\n";
	cout << "가슴둘레 " << chest << " (10진수)\n";
	cout << hex;
	cout << "허리둘레 " << waist << " (16진수)\n";
	cout << oct;
	cout << "인심길이 " << inseam << " (8진수)\n";
	return 0;
}

cout << hex 구문은 cout이 정수를 나타내는 방식을 변경함

  • cout << dec : 십진수로 변경
  • cout << hex : 16진수로 변경
  • cout << oct : 8진수로 변경

C++가 상수의 데이터형을 결정하는 방법

cout << "Year = " << 1492 << "\n";
위와 같이 정수 형 상수를 저장하는 경우에 C++에서는 일반적으로 모두 int형으로 저장됨
단, 접미어를 붙었을 때는 해당 접미어에 맞는 데이터형으로 저장됨
접미어가 없는 10진 정수는 int, long, long long형 중에서 해당 정수가 범위 내에 속하는 가장 작은 크기의 데이터형으로 저장됨
접미어가 없는 16진 / 8진 정수는 int, unsigned int, long, unsigned long, long long, unsigned long long형 중에서 해당 정수가 범위 내에 속하는 가장 작은 크기의 데이터형으로 저장됨


char형

// chartype.cpp

#include <iostream>

int main()
{
	using namespace std;
	char ch;
	
	cout << "원하는 문자 하나를 입력하시오 : " << endl;
	cin >> ch;
	cout << "감사합니다. ";
	cout << "당신이 입력한 문자는 " << ch << "입니다." << endl;
	return 0;
}

각 문자는 특정 정수와 일대일로 대응하며, 문자 세트는 ASCII를 일반적으로 사용함
그러나 ASCII는 제한이 크기 때문에 C++에서는 보다 범용적인 Unicode를 사용할 수 있는 확장 문자형을 지원함
위 예제에서 입력된 문자는 해당 문자에 대응하는 정수로 변수 ch에 저장되며, 이를 출력할 때는 해당 정수에 대응하는 문자로 변환되어 출력됨

// morechar.cpp

#include <iostream>

int main()
{
	using namespace std;
	char ch = 'M';
	int i = ch;
	cout << ch << "의 ASCII 코드는 " << i << "입니다." << endl;
	
	cout << "이 문자 코드에 1을 더해보겠습니다." << endl;
	ch = ch + 1;
	i = ch;
	cout << ch << "의 ASCII 코드는 " << i << "입니다." << endl;

	cout << "cout.put(ch)를 사용하여 char형 변수 ch를 화면에 출력 : ";
	cout.put(ch);

	cout.put('!');

	cout << endl << "종료" << endl;
	return 0;
}

char형 변수도 정수형 변수이므로 정수 연산을 적용할 수 있음
cout.put() : ostream 클래스에 포함되어있는 put()이라는 멤버 함수를 cout객체를 통해 사용

  • 멤버 함수 : 클래스에 속하며 해당 클래스의 특정 객체를 통해서만 사용할 수 있음
    마침표로 객체 이름과 함수 이름을 연결하여 사용하며, 이 때의 마침표는 멤버 연산자(membership operator)로 작동함
  • C++ Release 2.0 이전에 cout이 문자 상수를 int형으로 저장했기 때문에 사용된 함수

'A', ' '의 형식과 같이 작은따옴표로 문자를 둘러싸는 것으로 문자 상수를 표기
특정 코드 체계에 종속적이지 않기 때문에 수치 코드를 사용하는 것보다 좋은 방식임
키보드로 직접 입력할 수 없거나, C++에서 특별한 의미가 부여된 문자(문자열 분리로 사용되는 큰따옴표 등)들은 이스케이프 시퀀스(escape sequence)라는 특수한 표기법을 사용함

  • '\n', '\t', '\\'
  • 이스케이프 시퀀스 역시 하나의 문자로 취급되기 때문에 문자 상수로 사용할 때에는 반드시 작은따옴표로 둘러쌓여있어야 함
  • '\x8', '\010'의 형식으로 사용되는 수치 이스케이프 시퀀스도 있으나 가능하면 '\b'와 같은 기호 이스케이프 시퀀스를 사용하는것이 권장됨
// bondini.cpp

#include <iostream>

int main()
{
	using namespace std;
	cout << "\a암호명 \"화려한 외출\" 작전이 방금 개시되었습니다!\n";
	cout << "8자리 비밀번호를 입력하십시오 : ________\b\b\b\b\b\b\b\b";
	long code;
	cin >> code;
	cout << "\a입력하신 비밀번호는 " << code << "입니다.\n";
	cout << "\a비밀번호가 맞습니다! Z3 계획을 진행하십시오!\n";
	return 0;
}

8개의 밑줄 문자가 출력된 후 프로그램은 \b 이스케이프 시퀀스에 해당하는 백스페이스 문자를 사용하여 커서를 첫번째 밑줄 문자 위치로 이동시킴

universal character names
C++ 표준은 기본적인 소스 문자 세트를 지원하는데 더해 확장 소스 문자 세트와 확장 실행 문자 세트를 제공하는 것을 허용함
유니버설 캐릭터 이름(universal character names) : 특정 키보드와는 무관한 국제 문자들을 표현하는 메커니즘

  • \u 또는 \U + 16진수로 나타낸 ISO 10646(국제 표준 문자 세트) 코드
  • C++에서는 유니버설 코드(universal code)가 아닌 유니버설 코드 네임(universal code name)이라는 용어를 사용하는데, 이는 문자에 해당하는 코드가 다양한 시스템에서 그 시스템에 맞는 다양한 인코딩을 가질 수 있기 때문임
    즉, 사용자의 소스 코드는 모든 시스템에 대해 동일한 유니버설 코드 네임을 사용할 수 있으나 컴파일러는 해당 코드를 특정 시스템에서 그 시스템에 적절한 내부 코드로 표현할 수 있음

signed char / unsigned char
기본 char형은 signed형인지 unsigned형인지 정해져있지 않으나, 사용자가 명시적으로 제한할 수는 있음
표준 ASCII 문자를 사용할 때는 둘 다 상관없으므로 그냥 char로 사용하면 됨

확장 char형 : wchar_t
1바이트로 표현할 수 없는 문자 세트의 경우(한국어 등)

  • 컴파일러 개발업체가 char형을 처음부터 2바이트 이상으로 만듬
  • 또는 기본 문자 세트와 확장 문자 세트를 동시에 지원 기본 문자 세트는 char형으로, 확장 문자 세트는 wchar_t(wide character type)형으로 나타냄
    • wchar_t형은 기초 데이터형(underlying type)이라는 정수형과 동일한 크기 및 부호 속성을 가지며, 기초 데이터형은 시스템에 따라 unsigned short형 / int형 등으로 달라질 수 있음
    • L'P', L"tall"과 같이 확장 문자 상수나 확장 문자 문자열은 앞에 L을 붙여서 나타냄
    • cin / coutwchar_t형을 처리할 수 없기 때문에 wcin / wcout객체가 제공됨

C++11형 : char16_t / char32_t
wchar_t의 부호와 길이는 가변이기 때문에 문제가 발생할 수 있어 C++11에서는 char16_t(unsigned 16비트)형과 char32_t(unsigned 32비트)형을 사용하기도 함

  • char16_t는 접두사로 u를 사용하며 /u00F6 형태의 유니버설 캐릭터 이름과 매칭됨
  • char32_t는 접두사로 U를 사용하며 /U0000222B 형태의 유니버설 캐릭터 이름과 매칭됨

bool형

참 또는 거짓 중 한가지 값만 가질 수 있는 변수이며, 미리 정의된 truefalse로 참과 거짓을 나타냄
어떠한 수치 값이나 포인터 값도 하나의 bool값으로 묵시적 변환이 가능함

  • 0이 아닌 값들은 true로 변환
  • 0 값은 false로 변환


3.2 const 제한자

상수를 기호 이름으로 나타내면 의미를 파악하기 쉽고, 상수의 값을 바꿀 때도 기호가 정의된 부분만 바꾸면 되어 편리하다는 장점이 있음
const 데이터형 상수이름 = 값

  • const는 선언의 의미를 제한하기 때문에 제한자(qualifier)라고 부름
  • 선언된 기호 상수는 설정할 값을 가진 상수로 고정되어 변경할 수 없음
  • 일반적으로 기호 상수는 상수임을 쉽게 알 수 있도록 모두 대문자로 쓰는 것이 관례
  • 선언과 동시에 초기화하지 않으면 변경할 수 없는 미확정 값으로 남겨짐

#define문으로도 기호 상수를 정의할 수 있으나 const 키워드를 사용하는게 더 좋음

  • const는 데이터형을 명시적으로 지정할 수 있음
  • C++의 활동 범위 규칙(scoping rules)에 의해 그 정의를 특정 함수 혹은 파일에서만 사용할 수 있도록 제한할 수 있음
    • 활동 범위 규칙 : 어떤 식별자가 서로 다른 여러 모듈에 얼마나 널리 알려지는가를 나타내는 규칙
  • 배열이나 구조체와 같은 보다 복잡한 데이터형에도 사용 가능
  • C++에서는 C와 달리 const를 이용하여 배열의 크기를 선언할 수 있음


3.3 부동 소수점수

소수부가 있는 수, 또는 너무 커서 long형으로 나타낼 수 없는 경우에 부동 소수점형을 사용
컴퓨터는 소수를 기본값과 스케일 두부분으로 나누어 저장하며, 소수점이 스케일에 따라 자리를 옮기기 때문에 부동 소수점형이라고 부름


부동 소수점수 표기

  • '8.0, 0.0023'과 같은 일반적인 소수점 표기법
  • '2.52e+8, 8.33E-4'와 같은 지수 표기법(Enotation), E 또는 e를 사용 가능하며 지수는 + / -를 부호로 가질 수 있으나 소수점은 생략할 수 없음

float / double / long double

유효 숫자의 개수와 지수의 최소 허용 범위에 따라 데이터형이 나뉘어짐
지수의 허용 범위는 모두 최소값은 -37 ~, 최대값은 37 ~

  • float형은 최소한 32비트 폭을 가짐, 일반적으로 32비트
  • double형은 최소한 48비트 폭을 가지며 float형 크기 이상, 일반적으로 64비트
  • long double형은 최소한 double형 크기 이상, 일반적으로 80 / 96 / 128비트
// floatnum.cpp

#include <iostream>

int main()
{
	using namespace std;
	cout.setf(ios_base::fixed, ios_base::floatfield);

	float tub = 10.0 / 3.0;
	double mint = 10.0 / 3.0;
	const float million = 1.0e6;

	cout << "tub = " << tub;
	cout << ", a million tubs = " << million * tub;
	cout << ",\nten million tubs = ";

	cout << "mint = " << mint << "and a million mints = ";
	cout << million * mint << endl;
	return 0;
}

setf() 함수의 인자로 iostream에서 제공하는 ios_base::fixedios_base:: floatfield를 사용하여 cout의 출력 포맷을 고정 소수점으로 변경함
float형의 유효숫자는 6개, double형의 유효숫자는 15개이기 때문에 위 코드에서 두 변수의 출력 결과가 다르며, double형이 정밀도가 더 높음


부동 소수점형 상수

기본적으로 double형으로저장됨

  • 접미어로 f 또는 F를 붙이면 float형으로 저장됨
  • 접미어로 l 또는 L을 붙이면 long double형으로 저장됨

부동 소수점수 장단점

부동 소수점수는 정수가 아닌 실수를 표현 가능하며, 스케일을 사용함으로써 매우 큰 범위의 값을 나타낼 수 있다는 장점이 있음
그러나 정수 연산보다 속도가 느리며, 정밀도를 잃을 수 있다는 단점이 있음

// fltadd.cpp

#include <iostream>

int main()
{
	using namespace std;
	float a = 2.34E+22f;
	float b = a + 1.0f;

	cout << "a = " << a << endl;
	cout << "b - a = " << b - a << endl;
	return 0;
}

b - a의 결과로 1이 출력되는 대신 0이 출력되는데, 이는 a가 소수점 위로 23개의 숫자를 가진 큰 수이기 때문에 발생되는 문제임
float형은 처음 6개 또는 7개 숫자까지만 나타낼 수 있기 때문에 23번째 숫자에 1을 더하는 것은 아무런 효과를 가지지 못함



3.4 C++ 산술 연산자

C++은 기본적인 계산을 위한 덧셈 +, 뺄셈 -, 곱셈 *, 나눗셈 /, 나머지셈 % 의 다섯가지 연산자를 제공함
각각의 연산자는 연산의 대상으로 삼을 피연산자 두개가 필요하며, 연산자와 두개의 피연산자가 결합되어 수식을 구성함

  • / 연산자의 경우 피연산자가 모두 정수이면 몫은 정수부분만 취함
  • % 연산자의 경우 피연산자는 모두 정수여야하며, 피연산자가 부동 소수점수일 경우 컴파일 에러가 발생함
// arith.cpp

#include <iostream>

int main()
{
	using namespace std;
	float hats, heads;

	cout.setf(ios_base::fixed, ios_base::floatfield);
	cout << "수를 하나 입력하십시오 : ";
	cin >> hats;
	cout << "다른 수를 입력하십시오 : ";
	cin >> heads;

	cout << "hats = " << hats << "; heads = " << heads << endl;
	cout << "hats + heads = " << hats + heads << endl;
	cout << "hats - heads = " << hats - heads << endl;
	cout << "hats * heads = " << hats * heads << endl;
	cout << "hats / heads = " << hats / heads << endl;
	return 0;
}

float형의 한계 때문에 +연산자의 값이 61.42가 아닌 61.419998이 됨
정밀한 연산을 수행하고싶다면 double형이나 long double형을 사용


연산 우선순위와 결합 방향

산술 연산자는 일반 대수학의 우선순위 규칙을 따름
우선순위가 같은 경우 왼쪽->오른쪽 결합이면 왼쪽에 있는 연산자를 먼저 적용하고, 왼쪽<-오른쪽 결합이면 오른쪽 연산자를 먼저 적용함
우선순위 규칙과 결합 방향은 하나의 피연산자에 두 개의 연산자가 걸려있을 때에만 적용됨


나눗셈 연산

// divide.cpp

#include <iostream>

int main()
{
	using namespace std;
	float hats, heads;

	cout.setf(ios_base::fixed, ios_base::floatfield);
	cout << "정수 나눗셈 : 9/5 = " << 9 / 5 << endl;
	cout << "부동 소수점수 나눗셈 : 9.0/5.0 = " << 9.0 / 5.0 << endl;
	cout << "혼합 나숫셈 : 9.0/5 = " << 9.0 / 5 << endl;
	cout << "double형 상수 : 1e7/9.0 = " << 1.e7 / 9.0 << endl;
	cout << "float형 상수 : 1e7f/9.0f = " << 1.e7f / 9.0f << endl;
	return 0;
}

두 피연산자가 모두 정수이면 정수 나눗셈을 수행하여 소수부를 버림
피연산자 중 하나 이상이 부동 소수점수이면 결과는 부동 소수점수가 됨
두 피연산자가 모두 double형이면 결과는 double형이 되고, 모두 float형이면 결과도 float형이 됨


나머지 연산

// modulus.cpp

#include <iostream>

int main()
{
	using namespace std;
	const int Lbs_per_stn = 14;
	int lbs;

	cout << "당신의 체중을 파운드 단위로 입력하십시오 : ";
	cin >> lbs;
	int stone = lbs / Lbs_per_stn;
	int pounds = lbs % Lbs_per_stn;
	cout << lbs << " 파운드는 " << stone
		 << " 스톤, " << pounds << " 파운드입니다.\n";
	return 0;
}

어떠한 양을 서로 다른 정수 단위로 분리할 때 유용


데이터형 변환

C++의 경우 특정 상황에서 데이터를 혼합하여 사용할시 데이터형의 불일치를 해결하기 위해 자동으로 데이터형 변환을 수행함

  • 특정 데이터형의 변수에 다른 데이터형의 값을 대입했을 때
  • 수식에 데이터형을 혼합하여 사용했을 때
  • 함수에 매개변수를 전달할 때

대입 구문에서의 데이터형 변환

범위가 작은 데이터형 값을 범위가 큰 데이터형에 대입하는것은 문제가 되지 않음
단, 그 반대의 경우 문제가 발생할 수 있음

// init.cpp

#include <iostream>

int main()
{
	using namespace std;
	cout.setf(ios_base::fixed, ios_base::floatfield);
	float tree = 3;
	int guess = 3.9832;
	int debt = 7.2E12;
	cout << "tree = " << tree << endl;
	cout << "guess = " << guess << endl;
	cout << "debt = " << debt << endl;
	return 0;
}
  • double -> float와 같이 큰 부동 소수점형을 작은 부동 소수점형으로 변환할시 정밀도가 손실되며 원래 값이 변환 데이터형의 범위를 벗어날 경우 결과를 예측할 수 없음
  • 부동 소수점형을 정수형으로 변환할시 소수부를 잃어버리며 원래 값이 변환 데이터형의 범위를 벗어날 경우 결과를 예측할 수 없음
  • long -> short와 같이 큰 정수형을 작은 정수형으로 변환할시 원래 값이 변환 데이터형의 범위를 벗어날 경우 하위 바이트들만 복사됨

C++11에서는 중괄호를 이용한 리스트 초기화가 가능한데, 이 때 변수형이 대입된 값으로 표현될 수 없는 경우인 narrowing인 경우에는 초기화가 허용되지 않음
단, 변환된 변수가 완벽하게 값을 유지할 수 있는 경우에는 허용될 수 있음

수식에서의 데이터형 변환

bool, char, unsigned char, signed char, short형 값은 수식에 사용될 시 모두 int형으로 변환되어 처리되며, 이를 정수 승급(integral promotion)이라고 함
이는 컴퓨터 내부에서 가장 자연스럽게 처리(가장 빠르게 계산)되는 데이터형이 int형이기 때문

  • short형이 int형보다 크기가 작을 경우에는 unsigned short형이 int형으로 변환
  • short형과 int형의 크기가 같다면 unsigned short형이 unsigned int형으로 변환
  • wchar_t형은 int, unsigned int, long, unsigned long형 중 표현 범위를 수용 가능한만큼 충분히 큰 첫번째 데이터형으로 승급
  • 이는 자료형의 승급시 데이터 손실이 일어나지 않도록 하기 위함

서로 다른 데이터형을 혼합하여 연산할 경우 작은 크기의 데이터형이 큰 크기의 데이터형으로 변환됨
C+11에서는 이러한 변환시 컴파일러가 검사를 진행하는 순서가 존재함

  • 한쪽 피연산자가 long double형이면 다른 피연산자를 long double로 변환
  • 그렇지 않을시 double형인지를 확인, 또다시 아니라면 float형인지를 확인
  • 위의 조건에 걸리지 않았을 시 피연산자들이 모두 정수형이므로 정수 승급이 발생
    • 양쪽 피연산자가 모두 signed 혹은 unsigned인 경우 크기가 큰 쪽으로 변환
    • 한쪽만 unsigned일 경우 unsgined형 피연산자가 signed형 피연산자보다 상대적으로 크다면 `unsigned 피연산자의 형으로 변환
    • 그렇지 않고 signed형이 unsigned형의 모든 값을 표현 가능하다면 unsigned형 피연산자가 signed형으로 변환
    • 위 경우에 해당되지 않을경우 양쪽 모두 signed형 피연산자의 unsigned형으로 변환

매개변수 전달시의 형 변환

이 때의 데이터형 변환은 C++의 함수 원형이 제어함
함수 원형이 매개변수를 제어하는 것을 막을 경우 char형과 short형에 대해 정수 승급을 수행하며, float형 매개변수를 double형으로 변환하여 전달함

데이터형 변환자

C++에서는 데이터형 변환자를 사용하여 강제로 데이터형을 변환할 수 있음
이 때 변수 자체는 변경되지 않고, 대신 지시된 데이터형의 새로운 값을 만들어 수식에 사용할 수 있음

  • (typeName) value와 같이 C에서 사용하던 방법이 가능함
  • typeName (value)와 같이 데이터형 변환이 함수 호출과 비슷하게 보이도록 할 수도 있음
  • C++에서는 static_cast<typeName> (value)의 형식으로 데이터형 변환 연산자를 통해 수치 데이터형을 다른 수치 데이터형으로 변환하는데 사용할 수 있음
// typecast.cpp

#include <iostream>

int main()
{
	using namespace std;
	int auks, bats, coots;

	auks = 19.99 + 11.99;

	bats = (int) 19.00 + (int) 11.99;
	coots = int (19.99) + int (11.99);
	cout << "auks = " << auks << ", bats = " << bats;
	cout << ", coots = " << coots << endl;

	char ch = 'Z';
	cout << "코드 " << ch << " 의 값은 ";
	cout << int(ch) << endl;
	cout << "네, 코드 Z의 값은 ";
	cout << static_cast<int>(ch) << endl;
	return 0;
}

위 예제에서 auks는 double형으로 연산이 이루어진 후에 결과값이 int형으로 변환되고, bats와 coots는 int형으로 변환이 된 상태로 연산이 이루어졌기 때문에 양쪽의 결과값이 다르게 출력됨
변수 ch의 경우 그냥 출력하면 char형이기 때문에 문자 Z가 출력되지만, int형으로 변환하면 해당 문자에 해당하는 ASCII 코드값이 출력됨


C++11에서의 auto 선언

C+11에서는 초기화하는 값을 보고 변수형을 추론할 수 있으며, 따라서 초기화 선언시 데이터형을 쓰지 않고 auto를 사용할 수 있음
자동으로 변수형을 추론하는 것은 STL(Standard Template Library)를 사용할 때처럼 복잡한 변수형을 다룰 때 유용함

  • C++98의 경우
std::vector<double> scores;
std::vector<double>:: iterator pv = scores.begin();
  • C+11의 경우
std::vector<double> scores;
auto pv = scores.begin();


연습문제

  1. 사용자의 요구에 적합한 데이터형을 골라서 사용할 수 있기 때문
    메모리 절약 / 저장 용량 확보 / 계산 속도 등을 고려
  2. a) short a = 80;
    b) unsigned int b = 42110;
    c) unsigned long c = 3000000000;
  3. C++에서는 정수형의 한계를 벗어나지 않도록하는 안전장치를 제공하지 않음
  4. 33L은 long형 상수, 33은 int형 상수
  5. 같지 않음. 다만 ASCII코드를 사용하는 시스템이라면 'A'가 ASCII코드로 65이기 때문에 char grade = 65;char grade = 'A';는 같은 기능을 할 수 있음
    • char chr = 88;, cout << c << endl;
    • cout.put(char(88));
    • cout << char(88) << endl;
    • cout << (char)88 << endl;
  6. long형이 4바이트라면 10자리 수까지(2,147,483,648)까지 저장할 수 있으므로 13자리 유효숫자를 허용하는 double형에 대입할 때 데이터 손실이 발생하지 않음
    long long형은 19자리 수까지 저장할 수 있으므로 double형에 대입할경우 데이터 손실이 발생할 수 있음
  7. a) int a = 8 * 9 + 2;, a = 74
    b) int b = 6 * 3 / 4;, b = 4
    c) int c = 3 / 4 * 6;, c = 0
    d) double d = 6.0 * 3 / 4;, d = 4.5
    e) int e = 15 % 4;, e = 3
  8. int a = int(x1) + int(x2);
  9. a) int
    b) float
    c) char
    d) char 32_t
    e) double

'개인공부 > C++ 기초플러스' 카테고리의 다른 글

C++ Primer 04 Exercise  (0) 2023.02.18
C++ Primer 04  (0) 2023.02.18
C++ Primer 03 Exercise  (0) 2023.02.18
C++ Primer 02  (0) 2023.02.18
C++ Primer 01  (0) 2023.02.18

댓글