순서상으로는 Chapter 0부터 해야 되지만, 지금 Chapter 3을 듣고 있어서... 우선 3부터 정리합니다.
현재 정리하고 있는 강의는 다음 강의입니다.
https://www.inflearn.com/course/following-c-plus
홍정모의 따라하며 배우는 C++ 강의 | 홍정모 - 인프런
홍정모 | , 6,000+명의 수강생이 증명합니다 👍모던 C++ 바이블, 따배씨++를 만나보세요! <2024 프로그래밍 공부 순서> [임베딩 영상] 뛰어난 프로그래밍 실력을 갖추고 싶다면. [사진] 모던(Modern) C+
www.inflearn.com
Chapter 3.1 연산자 우선순위와 결합 법칙
C++에서는 연산자 간의 우선순위가 있다. 여러 연산자가 나열되어 있는 경우, 우선순위가 높은 연산자를 먼저 실행하게 된다.
https://en.cppreference.com/w/cpp/language/operator_precedence
C++ Operator Precedence - cppreference.com
The following table lists the precedence and associativity of C++ operators. Operators are listed top to bottom, in descending precedence. a, b and c are operands. ↑ The operand of sizeof cannot be a C-style type cast: the expression sizeof (int) * p is
en.cppreference.com
사실 이 우선순위를 다 외우고 있기는 어려울 것 같고, 원하는 결과가 나오지 않는다면 표를 참고해서 문제를 해결해야 한다.
그리고 원하지 않는 결과가 나오는 것을 방지하려면, 괄호를 통해서 연산의 범위를 잘 정해주는 것이 위험을 줄이는 방법이다. (다른 개발자들도 우선순위를 모두 외우지는 못한다!)
연산자 우선순위가 적용되는 방향이 Left-to-right가 있고, Right-to-left가 있으니 이 또한 주의해야 한다.
우리가 수학에서 많이 쓰는 우선순위 (*, /가 +, -보다 더 우선순위가 높다)는 헷갈리지 않지만, 그렇지 않은 경우가 어려운 것 같다.
수업 코드 1
int a = 1;
int b = 2;
int c = 3;
a = b = c; // c -> b -> a로 assignment 됨.
std::cout << "a: " << a << " b: " << b << " c: " << c << std::endl; // a, b, c 모두 3
-> a = b = c로 해주는 경우 c를 b로 assignment 하고, b를 다시 a에 assignment 해서 결국 a, b, c가 같은 값이 되어 버린다.
수업 코드 2
int w = 3;
double t = 20;
t /= --w + 5; // --w + 5부터 진행하고, /=를 적용함.
std::cout << w << std::endl;
std::cout << "t: " << t << std::endl;
-> /= 연산자의 경우, right-to-left라서 우측부터 진행하고 좌측 진행.
따라서 w는 2가 되고(--w 영향), t는 2.85714 값이 나온다. 이는 t가 double이기 때문에, 결과가 소수점으로 출력된다.
Chapter 3.2 산술 연산자 (Arithmetic Operators)
몫 연산자(/)와 나머지 연산자(%)에 대해서 정리하는 챕터이다.
수업코드 1
int x = 7;
int y = 4;
// x / y ==> 몫, x % y ==> 나머지
// 나누기 시 한쪽이 실수면 결과가 실수로 나온다.
cout << "x / y : " << x / y << endl;
cout << "x % y : " << x % y << endl;
cout << "only x float case: " << float(x) / y << endl;
cout << "only y float case: " << x / (float)y << endl;
cout << "x and y float case: " << float(x) / float(y) << endl;
// 음의 정수의 경우, 소수점을 모두 절삭함.
cout << "-5 / 2 ? " << -5 / 2 << endl;
// 왼쪽에 있는 숫자가 음수면 나머지의 결과도 음수다.
// 왼쪽에 있는 숫자가 양수면 나머지의 결과도 양수다.
cout << "-5 % 2 ? " << -5 % 2 << endl;
cout << "5 % 2 ? " << 5 % 2 << endl;
/ 연산자는 몫을 나타내고, %는 나머지를 나타낸다.
피연산자들이 둘 다 int면 몫과 나머지 모두 int로 출력한다. 단, 피연산자 중 한 개라도 float이면 결과를 float로 출력한다.
음의 정수의 경우 몫 계산 시 소수점을 절삭하며, 나머지 계산 시 왼쪽에 있는 숫자의 부호에 맞춰서 결과가 나온다.
Chapter 3.3 증감 연산자 (Increment decrement operators)
증감 연산자의 위치에 따른 차이를 알려주는 챕터이다.
수업코드 1
int add(int a, int b)
{
return a + b;
}
int x = 6, y = 6;
cout << x << " " << y << endl; // 6 6
// 앞에 +가 붙는 경우 x에 1을 더해주고 출력
// 뒤에 +가 붙는 경우 x를 stream에 보낸 다음에 1을 더해지는 것.
cout << ++x << " " << --y << endl; // 7 5
cout << x << " " << y << endl; // 7 5
cout << x++ << " " << y-- << endl; // 7 5
cout << x << " " << y << endl; // 8 4
int a = 1, b = 2;
int v = add(a, ++b);
cout << v << endl;
증감 연산자가 앞에 붙는 경우, 바로 해당 변수에 적용해 준다.
따라서 ++x를 출력할 때 기존 x 대비 +1이 된 것을 알 수 있다.
하지만 증감 연산자가 뒤에 붙는 경우, 해당 출력 코드가 종료된 이후에 적용된다.
따라서 x++를 출력할 때는 여전히 x의 값이 7이지만, 그다음 줄에서 x를 cout 했을 때 8이 되는 것을 알 수 있다.
그리고 add(a, ++b)를 실행시켰을 때는 b가 기존 대비 1만큼 올라간 값을 기준으로 add 함수를 타게 된다. (함수의 인자값으로 들어갈 때도 동일하다는 것을 보여주는 것)
Chapter 3.4 sizeof, 쉼표 연산자, 조건부 연산자
sizeof와 쉼표 연산자, 조건부 연산자에 대해서 설명하는 챕터이다.
우선 sizeof에 대해서 정리해 보자.
수업코드 1
using namespace std;
float a;
// sizeof는 데이터 타입을 넣거나, 변수를 넣을 수 있다.
// struct나 class 처럼 사용자가 만든 자료형에도 사용할 수 있음.
// sizeof는 연산자(Operator)임.
// 변수명에 사용하는 경우는 괄호를 사용하지 않더라도 가능.
cout << sizeof(float) << endl; // 4 바이트, 32비트
cout << sizeof(a) << endl; // 4바이트, 32비트
cout << sizeof a << endl;
sizeof는 변수나 데이터 타입의 size를 출력하는 연산자이다. 출력 기준은 byte이다.
자료형을 direct로 인자값으로 넣을 수도 있고, 혹은 변수명을 인자값으로 넣을 수도 있다.
수업 코드 2
// comma operator
int x = 3;
int y = 10;
int z = (++x, ++y);
// ++x;
// ++y;
// int z = y;
cout << "x: " << x << " " << " y: " << y << " " << " z: " << z << endl;
int a1 = 1, b1 = 10;
int z1, z2;
z1 = a1, b1; // =가 , 보다 우선순위가 더 높다, 따라서 결과가 z1 = a1를 하는 것과 동일한 상황임.
z2 = (++a1, a1 + b1);
cout << z1 << endl;
cout << z2 << endl;
다음은 쉼표 연산자에 대한 설명이다.
z = (++x, ++y)로 작성하는 경우, 먼저 왼쪽을 실행하고(++x), 다음으로 오른쪽을 실행하고(++y), z에 y 값을 할당한다.
따라서 아래 출력 값을 확인해 보면, x는 4가 되어 있고, y는 11이 되어 있으며, z에는 y 값인 11이 할당되어 있는 것을 확인할 수 있다.
코드상으로는 3줄을 써야 하는 것을 1줄로 묶은 느낌인데, for문에서 많이 쓴다고 한다.
그리고 z1를 보면, z1 = a1, b1; 로 코드를 작성한 것을 알 수 있는데, = 연산자가 , 보다 우선순위가 높기 때문에 b1에는 아무 일도 일어나지 않고 a1 값이 z1에 할당되는 것을 확인할 수 있다.
수업 코드 3
int getPrice(bool onSale)
{
if (onSale) return 10;
else return 100;
}
// conditional operator (arithmetric if)
bool onSale = true;
// const로 사용하는 경우, 처음 변수 정의할 때 값을 줘야하니 conditional operator를 사용하기에 적합.
// 조건이 복잡하거나, 값이 복잡한 경우에는 if문으로 쪼개는 것이 좋다. 간단한 경우 사용하는 것이 적합.
const int price = (onSale == true) ? 10 : 100;
//const int price = getPrice(onSale);
cout << "Price: " << price << endl;
다음은 조건 연산자에 대한 설명이다.
const 변수를 사용할 때, 조건에 따라 해당 변수에 값을 줘야 하는 경우 사용한다고 한다.
현재 코드 상으로는 onSale 변수가 true면 price가 10이고, false면 100이 되는 것을 알 수 있다.
삼항 연산자를 활용하는 것이기 때문에, 조건이 가급적 간단할 때만 사용하는 것을 추천한다고 한다.
삼항 연산자를 사용하지 않게 되면, getPrice 함수를 따로 짜서 삼항 연산자를 if문 형식으로 풀어서 구현하는 방법도 있다.
수업 코드 4
int sample_x = 5;
// ? : 보다 <<가 우선순위가 높아서 벌어지는 현상.
// cout << (x % 2 == 0) ? "even" : "odd" << endl;
cout << ((x % 2 == 0) ? "even" : "odd") << endl;
마지막으로 연산자 우선순위 때문에 벌어지는 문제를 보여주는 코드이다.
위 코드는 아예 컴파일 자체가 안되고, 다음과 같은 오류가 나온다.
이는? 와 :보다 <<가 우선순위가 높기 때문에, cout << (x % 2 == 0)에서 << 연산이 끊기고, 그 뒤에 ? "even" : "odd"만 딸랑 남게 되기 때문에 문제가 발생한다고 볼 수 있다.
따라서, 여러 가지 연산이 복합적으로 사용될 때는 괄호를 잘 활용해서 묶어줘야만 의도된 대로 코드가 작동할 수 있다.
Chapter 3.5 관계 연산자 (Relational Operators)
해당 챕터에서는 관계 연산자에 대해서 알아본다.
수업 코드 1
if (x == y)
{
cout << "Equal ! " << endl;
}
if (x != y)
{
cout << "Not Equal ! " << endl;
}
if (x > y)
{
cout << "x is bigger than y" << endl;
}
if (x < y)
{
cout << "x is smaller than y" << endl;
}
if (x >= y)
{
cout << "x is bigger than y or equal to y" << endl;
}
if (x <= y)
{
cout << "x is smaller than y or equal to y" << endl;
}
관계 연산자는 크게 ==, !=, >, <, >=, <=가 있다. 부등호는 수학에서 사용하는 것과 동일해서 크게 어려울 게 없고, 코딩을 처음 해보는 분이라면 같다는 표기가 ==라는 것만 주의하면 될 것 같다.
int 변수에서는 크게 문제가 없지만, float나 double처럼 실수형 변수에서가 주의할 내용들이 조금 있다.
수업 코드 2
double d1(100 - 99.99); // 0.01
double d2(10 - 9.99); // 0.01
const double epsilon = 1e-10;
if (abs(d1 - d2) < epsilon)
{
cout << "Approximatedly equal" << endl;
}
else
{
cout << "Not Equal" << endl;
}
if (d1 == d2)
{
cout << "Equal " << endl;
}
else
{
cout << "Not Equal" << endl;
cout << std::setprecision(20);
cout << "d1: " << d1 << " d2: " << d2 << endl;
cout << abs(d1 - d2) << endl;
if (d1 > d2) cout << "d1 > d2" << endl;
else cout << "d1 < d2" << endl;
}
double 변수 d1, d2를 정의하고, 각각 두 값이 0.01이 되도록 연산해 줬다.
이전에 강의에서도 언급되었듯이, 실제로 우리가 인식하는 값과 컴퓨터가 계산한 값이 다를 수 있다.
따라서 d1 == d2를 확인해 보면, Not Equal이 나오는 것을 확인할 수 있다.
그래서 실제로 왜 다른지를 확인해 보면(setprecision을 높여서 확인), 아주 엄밀하게 따지면 아주 작은 값의 차이가 있는 것을 알 수 있다.
그래서 보통 소수점 변수에 대해서 계산하는 경우, 두 변수간 차이(abs(d1 - d2))를 계산해서 이 값이 특정 값보다 작은 경우는 두 변수의 값이 같다고 인정하는 경우가 많다고 한다. 어느 정도의 차이까지 인정할지는 domain에 따라 다르므로, domain knowledge를 기반으로 정한다고 한다.
Chapter 3.6 논리 연산자 (Logical operators)
해당 챕터에서는 논리 연산자(!(not), &&(and), ||(or))에 대해서 알아본다.
수업 코드 1
int x = 2;
int y = 2;
// short circuit evaluation
// &&로 묶여 있어서 왼쪽에 있는 식을 먼저 계산하고 오른쪽 식을 진행하게 됨.
// 이때, And 연산자는 왼쪽을 판단하고 false인 경우 오른쪽 계산을 진행하지 않고 false를 return함.
if (x == 1 && y++ == 2)
{
// do something
}
short circuit evaluation에 대한 설명이다.
if 조건문에서 만약 좌측 조건이 false가 되는 경우, 우측 코드가 작동하지 않는다는 설명이다.
좌측 조건이 true인 경우, 우측 코드가 작동하면서 y는 3으로 값이 올라가게 된다.
수업 코드 2
bool a = false;
bool b = false;
// De Morgan's Law
// !(a && b); <==> !a || !b
// !(a || b); <==> !a && !b
// XOR
// false false -> false
// false true -> true
// true false -> true
// true true -> false
// XOR는 이렇게 표현한다.
cout << (a != b) << endl;
수학 시간에 배웠던 드 모르간 법칙에 대해서도 설명이 있었다.
사실 수학시간에 배운 걸 그대로 적용하면 되는 거라 어렵지는 않다. 명제 파트에서 배운 내용과 비슷하다.
a && b(a and b)의 !(not)을 적용하게 되면 !a || !b(not a or not b)가 된다는 내용이다.
그리고 XOR에 대해서도 설명이 있었는데, 이는 a != b 연산으로 구현이 가능하다고 한다.
연산 결과를 보면, a와 b의 값이 다르면 true이고 같으면 false이므로, a와 b가 다르다는 연산으로 구현이 가능하다.
수업 코드 3
bool v1 = true;
bool v2 = false;
bool v3 = false;
// Logical AND가 logical OR보다 우선순위가 높다!
// 괄호를 통해서 잘 나눠주는 것이 적합하다.
// 다른 사람들도 실수할 수 있는 부분을 배려해서 코딩하자.
bool r1 = v1 || v2 && v3;
bool r2 = (v1 || v2) && v3;
bool r3 = v1 || (v2 && v3);
cout << "r1: " << r1 << " " << " r2: " << r2 << " " << endl;
cout << "r3: " << r3 << endl;
다음은 And가 Or보다 우선순위가 높다는 것을 설명하는 내용이다.
v1 || v2 && v3로 연산하는 경우, v1 || (v2 && v3) 연산하는 것과 결과가 같다.
물론 And가 Or보다 우선순위가 높다는 사실을 기억하는 것도 좋지만, 코딩을 할 때 조금 더 분명하게 괄호로 잘 나눠주는 것이 더 바람직하다고 한다.
Chapter 3.7 이진수 (Binary Numbers)
이진수에 대해서 설명하는 챕터이다. 코드로 구현하는 챕터는 아니고, 이론적인 내용으로 진행된다.
우선 10진법을 정리해보자면, 다음과 같다.
11이라는 숫자는 $1$x$10^1$ + $1$x$10^0$으로 이루어진다.
같은 원리로, 이진법일 때 11이라는 숫자는 $1$x$2^1$ + $1$x$2^0$로 표현된다.
다음으로는 2진수 덧셈이 있다.
우리가 익숙한 10진수 덧셈과 동일하지만, 2진수는 0과 1로만 이루어져 있어서 자릿수가 2일 때 넘어간다는 점을 기억하고 있으면 된다.
0 1 1 0
+ 0 1 1 1
----------------
1
1
0 1 1 0
+ 0 1 1 1
----------------
0 1
0 1 1 0
+ 0 1 1 1
----------------
1 1 0 1
로 계산이 된다.
더해서 2가 되면, 다음 자릿수로 숫자가 넘어간다는 점만 기억하고 있으면 된다.
다음으로는 음의 정수를 표현하는 방법을 알아보자.
-5를 8비트, 1byte 2진수로 표현해본다고 하면, 우선 부호를 제외하고 5를 먼저 표현해 본다.
0 0 0 0 0 1 0 1로 표현된다.
여기서 보수(Complement)를 취한다. 이는 0을 1로, 1을 0으로 바꿔주면 된다.
1 1 1 1 1 0 1 0로 바뀐다.
여기서 + 1을 해준다.
1 1 1 1 1 0 1 1이 된다.
이것이 최종적으로 -5를 표현한 것으로 이해하면 된다.
2진수에서 부호(-, +)를 감안해서 표현하는 경우, 맨 앞자리가 부호를 나타낸다고 보면 된다.
따라서 아까 5를 나타낼 때 맨 앞자리가 0이었고, -5를 나타낼 때는 맨 앞자리가 1이다.
마지막에 +1을 해주는 이유는, 숫자 0 때문이다.
0을 4bit로 표현한다면 0 0 0 0이 되는데, 이를 보수로 표현하면 1 1 1 1이다.
이 상태로 사용한다면 0과 -0이 존재하는 셈인 것이다.
따라서 여기에 +1을 해서 0 0 0 0으로 만들어준다면, 0과 -0이 모두 0 0 0 0으로 표현되는 셈이다.
다음으로는 10진수를 2진수로 바꾸는 법에 대해서 알아보자.
148을 2진수로 바꾼다고 한다면,
148 / 2 = 74, 나머지 0
74 / 2 = 37, 나머지 0
37 / 2 = 18, 나머지 1
18 / 2 = 9, 나머지 0
9 / 2 = 4, 나머지 1
4 / 2 = 2, 나머지 0
2 / 2 = 1, 나머지 0
1 / 2 = 0, 나머지 1
여기서 나머지 부분을 아래부터 위로 쪽 이어서 적으면, 1 0 0 1 0 1 0 0이 된다.
하나의 2진수 정수에 대해서 이를 signed int로 볼 때와 unsigned int로 볼 때를 나눠서 생각해 보자.
1 0 0 1 1 1 1 0이라는 2진수 정수가 있다.
우선 이를 signed int로 보자면, 부호가 없으므로 단순히 변환만 해주면 된다.
128 + 16 + 8 + 4 + 2 이므로 158이 된다.
unsigned int라고 하면, 맨 앞자리가 부호가 된다. 맨 앞자리가 1이므로, 이는 음수라는 의미이며, 앞에서 언급했듯이 음수 2진수 값을 표현하기 위해서는 우선 양수로 바꾼 후, 처리해줘야 한다.
1 0 0 1 1 1 1 0에서 보수를 취해주면,
0 1 1 0 0 0 0 1이 되고, 여기서 +1을 취해주면,
0 1 1 0 0 0 1 0이 된다.
이를 계산하면 64 + 32 + 2 = 98이며, 기존에 음수였으므로 -98이다.
Chapter 3.8 비트단위 연산자 (Bitwise Operators)
bool 자료형을 생각해 보자. 이는 0과 1만 표현하면 되니, 사실상 1 bit만 있어도 정보를 모두 표현할 수 있다.
그러나 메모리 할당의 가장 기본 크기는 1 byte로, 8 bit를 사용한다. 그럼 메모리의 나머지 7 bit는 노는 것이다.
따라서 메모리를 아끼고, 전부 계산에 사용해서 의미 있게 사용하기 위해서 비트단위 연산자를 사용하게 된다.
비트단위 연산자를 사용하면 계산 속도가 빠르다.
비트단위 연산자는 총 6개로, << (left shift), >> (right shift), ~(not), &(and), |(or), ^(XOR)가 있다.
<<는 cout에서, >>는 cin에서 볼 수 있는데, 생긴 거만 같지 사실 cout과 cin에서 사용하는 것과 의미는 다르다.
cout과 cin은 라이브러리에서 강제로 연산자를 오버로딩해서 사용하고 있는 케이스라고 보면 된다.
#include <bitset>
unsigned int a = 0b01100;
unsigned int b = a << 1;
cout << std::bitset<5>(a) << endl;
cout << std::bitset<5>(b) << " " << b << endl;
a는 01100으로, 원래는 값이 12이다.
이를 b = a << 1로 해서, left shift를 적용해 주게 되면, b는 1 숫자를 왼쪽으로 당긴 결과인 11000으로 변하게 되고, 그 값은 24가 되면서 2배가 된다.
이처럼, left shift 해주는 경우는 이진수 기준으로 1을 왼쪽으로 이동시켜 주고, 숫자를 2배로 올려준다.
만약 left shift를 4번 해주면, 2의 4 제곱만큼 커지는 개념이다.
right shift는 반대의 개념이다. 이진수 기준으로 오른쪽으로 이동시키게 되고, 자릿수가 반대로 이동되니 오히려 숫자를 /2로 만들어준다.
다음으로는 bitwise and, or, xor, not 정리해 본다.
unsigned int a = 0b01100;
unsigned int b = 0b10110;
cout << "bitwise and " << std::bitset<5>(a & b) << endl;
cout << "bitwise or " << std::bitset<5>(a | b) << endl;
cout << "bitwise XOR " << std::bitset<5>(a ^ b) << endl;
cout << "Not " << std::bitset<5>(~a) << endl;
&는 이진수 기준으로 and를 나타낸다.
a가 01100이고 b가 10110이니, 각 자리마다 생각해 보면, 첫자리는 a가 0이고 b가 1이니 0으로,
두 번째 자리는 a가 1이고 b가 0이니 0, 세 번째 짜리는 a가 1이고 b가 1이니 and로 1,
네 번째 자리는 a가 0이고 b가 1이니 0, 마지막 자리는 a가 0이고 b가 0이니 0이다. 따라서 a & b의 결과는 00100이다.
bitwise or인 |도 똑같은 원리로 생각하면 되고, bitwise XOR는 이전 강의에서도 얘기했듯이 != 관점으로 생각하면 된다.
즉, 두 값이 달라야 1이고, 같으면 0이 되는 것이다.
그리고 bitwise not인 ~a는 보수를 생각하면 된다.
마지막으로 수업시간에 얘기해 주신 Quiz가 있다. 이를 풀어보자.
1. 0110 >> 2를 10진수로 결과를 표현하면?
2. 5 | 12를 10진수로 결과를 내면?
3. 5 & 12를 10진수로 결과를 내면?
4. 5 ^ 12를 10진수로 결과를 내면?
답은 접은 글로 적어둡니다.
A1: 0001 (두 번 밀리면 자릿수를 벗어난 1은 그냥 사라짐.), 10진수로는 1
A2: 13 (5를 0101로, 12를 1100로 바꾼 뒤 계산하면 됨.)
A3: 4
A4: 9
Chapter 3.9 비트 플래그, 비트 마스크 (Bit flags, Bit masks)
이 챕터에서는 비트 플래그와 비트 마스크에 대해서 다룬다.
수업 코드 1
const unsigned char opt0 = 1 << 0;
const unsigned char opt1 = 1 << 1;
const unsigned char opt2 = 1 << 2;
const unsigned char opt3 = 1 << 3;
unsigned char items_flag = 0;
// 00000000이니까, 8개에 대해서 true/false를 만들 수 있다.
cout << "No item " << bitset<8>(items_flag) << endl;
// item0 on
items_flag |= opt0;
cout << "Item0 obtained " << bitset<8>(items_flag) << endl;
// item 3 on
items_flag |= opt3;
cout << "Item3 obtained " << bitset<8>(items_flag) << endl;
// item 3 lost
items_flag &= ~opt3;
cout << "Item3 lost " << bitset<8>(items_flag) << endl;
// Has item1 ?
if (items_flag & opt1) {cout << "has Item1 " << bitset<8>(items_flag) << endl;}
else { cout << "not have Item1 " << bitset<8>(items_flag) << endl;}
// Has item0 ?
if (items_flag & opt0) { cout << "has Item0 " << bitset<8>(items_flag) << endl; }
else { cout << "not have Item0 " << bitset<8>(items_flag) << endl; }
// obtain item 2, 3
items_flag |= (opt2 | opt3);
cout << "Obtain item 2 and item 3 " << bitset<8>(items_flag) << endl;
// item 2를 가지고 있고 item 1을 가지고 있지 않은 경우
if ((items_flag & opt2) && !(items_flag & opt1))
{
// 상태를 바꿔줄 땐 XOR 사용
//items_flag ^= opt2;
//items_flag ^= opt1;
items_flag ^= (opt2 | opt1);
}
cout << bitset<8>(items_flag) << endl;
게임에서 8개의 아이템이 있다고 할 때, 각 아이템을 가지고 있는지 아닌지를 표현한다면 bool 변수 8개가 필요하다.
하지만 아이템의 개수가 100개 1000개로 늘어난다면, 개별 변수로 선언해가지고는 감당하기 어렵다.
따라서 이를 비트 플래그라는 방식으로 대응하는 것이다. 4 byte (32bit)짜리 변수 1개면 32 종류의 아이템의 소지 여부를 bool 변수로 나타낼 수 있는 것이다.
우선 아무것도 가지고 있지 않을 때, bitset으로 출력해보면 00000000인 것을 알 수 있다.
다음으로, item0 번을 가지면 첫 번째 값이 1로 바뀌어야 하며, 이는 |=를 통해서 구현할 수 있다.
만약 item3 번을 가지고 있다가 이를 잃는다면, opt3을 not으로 바꾼 뒤 이를 and로 연산하면 된다.
이를 조금 더 설명해보자면,
기존에 0000 1001 이였을 때, ~opt3은 1111 0111이다. 여기서 & 연산자로 계산해 보면 0000 0001이 된다.
&가 and이니, 4번째 자리 숫자를 제외하고 나머지를 그대로 유지하게 된다. 4번째 자리는 반대로 변경된다.
해당 아이템을 가지고 있는지 여부는 &를 통해서 확인할 수 있는데, 이는 확인하고자 하는 자릿수만 1이고 나머지가 0인 변수와 and 연산자이기 때문에 여부를 확인할 수 있다.
그리고 item 2과 item 3을 한 번에 얻는다고 하면, item 2인 char와 item 3인 char를 or로 해서 합쳐주고 다시 or 연산자를 해준다.
마지막으로, item 2를 가지고 있고, item 1을 가지고 있지 않다면, 전자는 &로, 후자는 &를 한 뒤 not(!)을 붙여서 조건을 완성하면 된다.
상태를 바꿔줄 땐 XOR를 사용하는데, 이 부분도 예시를 통해 생각해 보자.
item flag가 0000 1001이고, 4번째 아이템의 여부를 바꾸려고 한다면, item3이 0000 1000일 태니
XOR의 성질은 같으면 0, 다르면 1이다.
그래서 0000 0001이 된다.
수업 코드 2
const unsigned int red_mask = 0xFF0000;
const unsigned int green_mask = 0x00FF00;
const unsigned int blue_mask = 0x0000FF;
cout << bitset<32>(red_mask) << endl;
cout << bitset<32>(green_mask) << endl;
cout << bitset<32>(blue_mask) << endl;
unsigned int pixel_color = 0xDAA520;
cout << bitset<32>(pixel_color) << endl;
unsigned char green = (pixel_color & green_mask) >> 8;
unsigned char blue = pixel_color & blue_mask;
unsigned char red = (pixel_color & red_mask) >> 16;
cout << "blue: " << bitset<8>(blue) << " " << int(blue) << endl;
cout << "green: " << bitset<8>(green) << " " << int(green) << endl;
cout << "red: " << bitset<8>(red) << " " << int(red) << endl;
다음은 비트 마스크이다.
수업시간에 왜 마스크인지는 얘기를 안 해주셨지만, int에서 일부분만 추출한다는 측면에서 나머지 값들에 마스크를 씌운다는 느낌이 아닐까 하고 생각해 본다.
pixel_color는 16진수로 구성되어 있고, 각각 red값 2개 + green값 2개 + blue값 2개로 구성된다.
따라서 DAA520 중 DA는 red, A5는 green, 20는 blue에 해당하는 값이다. 이를 적절하게 추출하고자 하는 것이 목표이다.
red_mask는 0 xFF0000로 되어 있는데, 이는 FF가 255이고, 1 byte(8 bit)를 전부 1로 채운 값이 된다.
그래서 출력값의 첫 번째가 0 8개 + 1 8개 + 0 16개로 구성이 되어 있는 것이다.
개념적으로 보자면 앞에서 다루었던 비트 플래그와 유사하다.
blue의 경우, 맨 오른쪽 자리를 차지하기 때문에 단순히 &를 활용하면 값을 추출할 수 있다.
그러나 green이나 red의 경우 오른쪽에 각각 8자리, 16자리의 수가 있기 때문에 실제 값을 제대로 추출할 수 없다.
따라서 이전에 배웠던 비트 연산자를 이용해서, 각각 8자리, 16자리씩 오른쪽으로 이동(>> 연산자 사용)을 해야만 실제 값을 제대로 얻을 수 있다.
블로그에 정리해 보니, 확실히 내용을 제대로 복습할 수 있는 것 같다.
다음 글에서는 Chapter 4를 정리해보려고 한다.
'C++ > 따라하며 배우는 C++' 카테고리의 다른 글
C++ 공부 관련 정리글을 업로드 해보려고 합니다. (0) | 2025.02.02 |
---|