프로젝트 우클릭해서 Open folder in file explorer 가서 주소창에 cmd. 하면 그 주소창에 cmd 실행된다.
거기서 exe 파일 입력해서 실행시켜주면 파일 실행된다.
확장자 sln 을 실행시켜주면 visual studio 아까 작업하던 프로젝트가 나온다.
어셈블리어 - 컴퓨터가 바라보는 세상.
디버깅켠 상태에서 debug-window-disassembly 누르면 어셈블리어가 나온다.
로우레벨까지 이해하고 싶으면 어셈블리어 이해가 필요하다. 라는 정도만 알아두자.
오랜만에 보면 코드 기억이 안날수도 있으므로 주석 달아주는 습관? 있으면 좋긴 하다.
프로그래밍 단순하게 생각하면 : 적절한 데이터를 저장하고 가공하는 것 : 데이터 + 로직
part1 정수
#include <iostream>
using namespace std;
// coment short cut -> ctrl k c(commend), ctrl+k+u , remove command
/*
this is left as comment
*/
//0이 아닌 초기화 값이 있으면 .data 영역으로 간다.
int hp = 100;
char a; //1바이트 (-128 , 127)
short b; //2바이트 (-32768 , 32767)
int c; //4바이트 (-21.4억, 21.4억)
__int64 d; // 8바이트 (long long) 이거 전부 앞에 signed가 생략되어 있는 형태다(부호 나타내는 게 제일 앞에 있다는 말)
signed int e; // 이런 식으로. 보통 signed를 생략해준다.
unsigned char ua; // 1바이트 unsigned- 최상위 비트 부호가 없어진다.(0, 255)
unsigned short ub; //2바이트 (0, 65536)
//귀찮은데 그냥 대충 4바이트로 가면 안될까?
// -> 콘솔/모바일 게임 -> 메모리가 늘 부족하다.
// -> 온라인 게임 -> 자주 쓰는 데이터는 줄여주는 게 중요하다(이런거 신경안쓰면 나중에 나비효과처럼 엄청 커져서 후폭풍이 올 수 있다.)
//참고 _ 이론적으로 양수만 존재할 수 있는 데이터- 예를 들어 케릭터 레벨 unsigned 하면 되지 않느냐?
//무조건 unsigned를 사용할지 안할지는 의견이 갈림.
// - 레벨이 음수라는 것은 말이 안된다. 아니다 차라리 crash 시켜서 버그를 빨리 찾는 게 낫다.
// 의견이 분분하다.에러가 안나려면 unsigned 안쓰는 게 낫나??
// 초기값이 -이거나, 초기값이 없는 변수라면 .bss영역으로 간다.
int main()
{
b = 32767;
cout << b << endl;
b = b + 1;
cout << b << endl;
// 계산기 켜서 해보면 2바이트로 32767에서 1 더해보면 -32768이 된다. 버그 발생
//정수 오버플로우
ub = 0;
ub = ub - 1;
cout << ub << endl;
//정수 언더플로우 발생
cout << "My HP is " << hp <<" left" << endl;
}
불리언과 부동소수점
#include <iostream>
using namespace std;
//오늘의 주제 : 불리언(bool)과 실수
// 불리언(boolean) 참/거짓
bool isHightLevel = true;
bool isPlayer = true;
bool isMale = false;
// [Note]
// 사실 bool은 그냥 1바이트 정수에 불과하다. false = 0, true = 1 , 1바이트
// 왜 정수시간에 안다뤘을까?
// 어셈블리에서 bool이라는 것은 없음
// bool만 봐도 참/거짓 둘 중 하나라는 힌트를 줍니다.(가독성)
// bool 사용하는 거 최하위비트인 1비트만 사용한다.
int isFemale = 1;
//실수(부동소수점)
//float double
// float은 4바이트인데 어떻게 숫자를 표현할까?
// 점 앞/뒤를 기준으로 16(2바이트)/16(2바이트)씩 끊으면?
// (0-65535) . (0-65535) 표현할 수 있는 수의 범위가 작다.
// 부동소수점 방식을 사용한다.
// . 을 유동적으로 움직여서 표현하는 방법
// ex) 3.1415926535
// 3.1415926535 = 0.31415926535 * 10 = 314.15926535 * 10^-2
//
// 1) 정규화 0.31415926535 * 10
// 2) 31415926535 (유효숫자) * 1(지수) 이렇게 실수는 구성한다.
// float 부호(1) 지수(8) 유효숫자(23) = 32비트 = 4바이트
// double 부호(1) 지수 (11) 유효숫자 (52) = 64비트 = 8바이트
float attackSpeed = -3.375f; //4바이트
double attackSpeed2 = 123.4123; //8바이트
// ex) -3.375라는 값은 비트에 어떻게 저장되나?
// 1) 2진수 변환 = (3) + (0.375) = 0b11 + 0b0.011 = 0b11.011
// 0.375 = 0.5 * 0 + 0.25 * 1 + 0.125 * 1 = 0b0.011 2진수 바이너리 숫자는 앞에 0b를 붙여준다.
// 그래서 0.011 에 2진수라는 것을 나타내기 위해 0b0.011
//
// 2) 정규화 0b1.1011 * 2^1
// 1 (부호) 1(지수) 1011(유효숫자)
// 단 지수는 unsigned byte라고 가정하고 숫자 +127 만들어줌
// 예상 결과 : 0b 1 1000_000(2) 1011_0000_0000_0000(52자리까지 0으로 채워짐)
// 프로그래밍할 때 부동소수점은 항상 '근사값'이라는 것을 기억하자
// 1/3 = 0.333333333333333333333333333333
// 특히 수가 커질수록 오차 범위도 매우 커짐
// 실수 2개를 == 으로 비교하는 것은 피하자. 근사값이기 때문에 다를 경우가 아주 크다.
int main()
{
if (isFemale == 1) {
//Todo
}
// 가독성 차이
if (isMale == false) {
//Todo
}
}
디버깅시
브레이크포인트를 걸어주고 진행해줘야 밑에 뭐가 뜨는데 watch1 들어가서 변수 입력하면 값이 보이고
&변수이름 적어주면 주소가 나온다.
메모리 찾으려면 디버그 - window- 메모리 - 아무거나 누르면 메모리 뜬다.
거기서 변수 주소 입력하면 어떻게 2진수로 그 값이 표현되고 있는지 나온다.
리틀 앤디안으로 저장이 되어있기 때문에 숫자를 뒤집어줘서 계산기에 2진수 넣어주면 답이 나온다.
위의 예는 -3.375인데 2진수로 00 00 58 c0 라고 되어있는데[ 이거를 계산기에 치면 -3.375를 직접 2진수로 계산한 값과 동일하게 비트가 표현되있는 것을 확인할 수 있다.
컨트롤 D 누르면 그 줄에 있는 코드가 밑으로 복사됨.
문자와 문자열
#include <iostream>
using namespace std;
// 오늘의 주제 : 문자와 문자열
// bool은 그냥 정수지만, 참/거짓을 나타내기 위해 사용한다.
// char 도 마찬가지. 그냥 정수지만 '문자' 의미를 나타내기 위해 사용
//char : 알파벳 / 숫자 문자를 나타낸다
//wchar_t : 유니코드 문자를 나타낸다.
//ASCII (American Standard Code for Information Interchage)
// '문자'를 사용하겠다는 의미로 작은 따옴표 ' ' 사용한다. 이렇게 하는 게 가독성을 높여준다.
char ch = 97; //a가 출력된다.
char ch2 = '1'; // 실질적으로 받아지는 건 49지만 출력은 1이 된다.
char ch3 = 'a' + 1; //b 출력된다.
//전 세계 모든 문자에 대해 유일한 코드를 부여한 것이 유니코드(unicode)
// 참고) 유니코드에서 가장 많은 번호를 차지하는 게 한국어/중국어이다.
// 유니코드는 표기 방식이 여러가지가 있는데 대표적으로 UTF8 UTF16 , 2가지 인코딩 방법이 있다.
//UTF8
// - 알파벳, 숫자 1바이트 (ASCII 동일한 번호)
// - 유럽 지역 문자는 2바이트
// - 한글, 한자 등 3바이트
//UTF 16
// - 알파벳, 숫자, 한글, 한자 등 거의 대부분 문자 2바이트
// - 매우 예외적인 고대 문자만 4바이트 (거의 사용 안함)
wchar_t wch = L'안'; // 유니코드 사용시 앞에 L 붙여줌. 2바이트는 wchar_t(wide character) 로 해줘야하고 글자 앞에 L을 붙여준다.
//앞에 L을 붙여줘야 유니코드로 변환이 된다.
wchar_t wch1 = 0xc548; //L'안'
// Escape Sequence
// 표기하기 애매한 애들을 표현
// \0 = 아시크코드 null
// \t = 아스키코드9 = tab
// \n = 아스키코드 10 = LineFeed(한줄 아래로)
// \r = 아스키코드13 = CarriageReturn (커서 <<)
// 문자열
// 문자들이 열을 지어서 모여 있는 것 (문자 배열)
// Ex) Hello World
// 항상 문자열 뒤에 \0 (null) 이 들어가있어야한다.
char str[] = { 'h', 'e','l','l','o' };
char str2[] = "Hello World"; // 위에 커서를 대보면 12개가 선언되있다. \0 이 생략되어 있다.
wchar_t str3[] = L"Hello World";
int main()
{
cout << ch << endl;
cout << ch2 << endl;
cout << ch3 << endl;
//cout 은 char 전용
cout << wch << endl; // 이러면 어떤 숫자가 나온다.
wcout.imbue(locale("kor")); // 이거를 붙여줘야 한국어가 출력된다.
wcout << wch << endl;
wcout << wch1 << endl;
cout << str << endl;
char str1[] = { 'h', 'e','l','l','o' };
cout << str1 << endl; // hello 에서 끝나는 값 \0 을 만날때까지 계속 출력이 된다.
//그래서 마지막에 \0 을 붙여야 한다.
char str1[] = { 'h', 'e','l','l','o','\0'};
}
접었다 폈다 할 수 있게 하는 것
#pragma region 이름
끝나는 부분
#pragma endregion
#include <iostream>
using namespace std;
// 오늘의 주제 : 데이터 연산
// 데이터를 가공하는 방법에 대해서 알아봅시다.
//a라는 이름의 바구니를 할당하고 안에 1을 넣는다.
int a = 1;
int b = 2;
int main()
{
#pragma region 산술연산
//산술 연산자
// a에 b를 대입하고 b를 반환하라.
//-> b라는 바구니 안에 있는 값을, a라는 바구니 안에다 복사한다.
//
//
// 대입 연산자
a = b;
// 사칙연산자
// 언제 필요한가?
// ex) 데미지 계산
// ex) 체력을 깎는다거나
// ex) 루프문에서 카운터를 1 증가시킨다거나
a = a + 3;
a = a - 3;
a = a * 3;
a = a / 3;
a = a % 3;
a += 3;
a -= 3;
a *= 3;
a /= 3;
a %= 3;
a++;
++a;
b = a++;
b = ++a;
b = (a + 1) * 3;
//이거는 자바랑 다 똑같네
//디버깅해서 어셈블리어로 한번 봐보자 .
#pragma endregion
}
비교연산과 논리연산
#include <iostream>
using namespace std;
// 오늘의 주제 : 데이터 연산
// 데이터를 가공하는 방법에 대해서 알아봅시다.
//a라는 이름의 바구니를 할당하고 안에 1을 넣는다.
int a = 1;
int b = 2;
bool isSame;
bool isDifferent;
bool isGreater;
bool isSmaller;
bool test;
int hp = 100;
bool isInvincible = true;
int main()
{
#pragma region 비교연산
// 언제 필요한가?
// ex) 체력이 0이 되면 사망
// ex) 체력이 30% 이하이면 궁극기를 발동 ( 100 * hp / maxHp )
// ex) 경험치가 100 이상이면 레벨업
// a == b : a와 b의 값이 같은가?
// 같으면 1, 다르면 0
isSame = (a == b);
// a!= b : a와 b의 값이 다른가?
// 다르면 1, 같으면 0
isDifferent = (a != b);
// a>b :
// a>=b
isGreater = (a > b);
#pragma endregion
#pragma region 논리연산
//언제 필요한가? 조건에 대한 논리적 사고가 필요할 때
// ex) 로그인할 때 아이디도 같고 and 비밀번호도 같아야 한다.
// ex) 길드 마스터 OR 운영자 계정이면 길드 해산 가능
// !not
// 0이면 1, 그 외 0
test = !isSame; // 사실상 isDifferent 의 의미
// && and
// a && b -> 둘다 1이면 1, 그 외 0
test = (hp <= 0 && isInvincible == false); //죽음
//어셈블리어로 확인해보면 hp<=0 이 0 이면 뒤에 isInvincible은 확인도 안하고 false로 출력한다. 이걸 이용해서 메모리를 아낄 수도 있다.
// || or
// a||b -> 둘 중 하나라도 1이면 1 (둘다 0이면 0)
test = (hp > 0 || isInvincible == true);
#pragma endregion
}
비트 연산과 비트 플래그
#include <iostream>
using namespace std;
// 오늘의 주제 : 데이터 연산
// 데이터를 가공하는 방법에 대해 알아봅시다.
unsigned char flag; //부호를 없애야 >> 를 하더라도 부호비트가 딸려오지 않는다.
int main()
{
#pragma region 비트연산
// 언제 필요한가? (사실 많이는 없음)
// 비트 단위의 조작이 필요할 때
// - 대표적으로 BitFlag
// ~bitwise not
// 단일 숫자의 모든 비트를 대상응로, 0은 1, 1은 0으로 뒤바뀜
// & bitwise and
// 두 숫자의 모든 비트 쌍을 대상으로, and를 한다.
// | bitwise or
// 두 숫자의 모든 비트 쌍을 대상으로, or를 한다.
// ^ bitwise xor
// 두 숫자의 모든 비트 쌍을 대상으로, xor를 한다.
// << 비트 좌측으로 이동.
// 비트 열을 N만큼 왼쪽으로 이동.
// 왼쪽으로 넘치는 비트는 버림. 새로 생성되는 N개의 우측 비트는 0
// 곱하기 2를 할 때 자주 보이는 패턴
// >> 비트 우측 이동
// 비트열을 N만큼 오른쪽으로 이동
// 오른쪽의 넘치는 N개의 비트는 버림
// 왼쪽 생성되는 N개의 비트는
// - 부호 비트가 존재할 경우(음수일 경우) 부호 비트가 보여지는대로 채워짐, (부호있는 정수라면은 이 부분을 유의해야 함)
// - 아니면 0으로 채워짐.
// 실습
// 0b0000 [무적][변이][스턴][공중부양]
// 무적상태를 만든다.
//flag = 8; //이것보다는
flag = (1 << 3); // 이게 더 전달력이 좋다.
// 변이 상태를 추가한다. (무적+변이)
flag |= (1 << 2);
// 무적인지 확인하고 싶다? (다른 상태는 관심 없음)
//bitmask (필요없는 정보를 가릴 수 있다.)
bool invincible = ((flag & (1 << 3)) != 0);
int a = 1;
int b = 123;
a = a ^ b; // 암호학에서 많이 사용한다. XOR 를 두 번 해주면 처음 했던 게 다시 나온다.
a = a ^ b; // 다시 a가 나온다.
// 무적이거나 스턴 상태인지 확인하고 싶다면?
bool stunOrinvincible = ((flag & 0b1010) != 0);
//or
bool mask = (1 << 3) | (1 << 1);
bool stunOrinvincible = ((flag & mask) != 0); // 이런 식으로 할 수도 있다.
#pragma endregion
}
Const
#include <iostream>
using namespace std;
unsigned char flag; //부호를 없애야 >> 를 하더라도 부호비트가 딸려오지 않는다.
// 한번 정해지면 절대 바뀌지 않을 값들
// constant의 약자인 const를 붙임 (변수를 상수화 한다고 함)
// const를 붙였으면 초기값을 반드시 지정해야 함
const int AIR = 0;
const int STUN = 1;
const int POLYMORPH = 2;
const int INVINCIBLE = 3;
//const 붙이면 main에서 바꾸려고 해도 안바뀐다.
// 여기서 공포 추가하려거든
const int TERRIFIED = 4;
//이렇게 하면 쉽게 추가해줄 수 있다.
int main()
{
#pragma region 비트연산
// 실습
// 0b0000 [무적][변이][스턴][공중부양]
// 무적상태를 만든다.
//flag = 8; //이것보다는
//flag = (1 << 3); // 이게 더 전달력이 좋다.
flag = (1<<INVINCIBLE); //위의 코드보다 가독성이 더 좋아진다.
// 변이 상태를 추가한다. (무적+변이)
//flag |= (1 << 2);
flag = (1 << POLYMORPH);
// 무적인지 확인하고 싶다? (다른 상태는 관심 없음)
//bitmask (필요없는 정보를 가릴 수 있다.)
bool invincible = ((flag & (1 << 3)) != 0);
// 무적이거나 스턴 상태인지 확인하고 싶다면?
bool stunOrinvincible = ((flag & 0b1010) != 0);
//or
bool mask = (1 << 3) | (1 << 1);
bool stunOrinvincible = ((flag & mask) != 0); // 이런 식으로 할 수도 있다.
#pragma endregion
}
메모리구조
week3(2) - 메모리 구조와 레지스터 종류 (tistory.com)
지역변수 - 스텍영역 공부하자. sp, bp 이런거. 이해가 잘 안간다.
전역변수 - 데이터영역
데이터영역도 세가지로 나뉜다. (.data .bas .rodata)
#include <iostream>
using namespace std;
unsigned char flag; //부호를 없애야 >> 를 하더라도 부호비트가 딸려오지 않는다.
// 한번 정해지면 절대 바뀌지 않을 값들
// constant의 약자인 const를 붙임 (변수를 상수화 한다고 함)
// const를 붙였으면 초기값을 반드시 지정해야 함
// const도 바뀌지 않는 읽기전용 데이터이다.
// 그러면 .rodata 에 있나?
// 사실 C++ 표준에서는 그러라는 말이 없음.
// 컴파일러(dVS) 마음이다.
const int AIR = 0;
const int STUN = 1;
const int POLYMORPH = 2;
const int INVINCIBLE = 3;
// 전역변수 - 언제 어디서든 바꿀 수 있는 값
// [ 데이터 영역] 에 전역변수가 올라간다.
// .data (초기값이 있는 경우)
int a = 2;
// .bas (초기값이 없는 경우)
int b;
// .rodata (읽기 전용 데이터)
const char* msg = "Hello World";
int main()
{
//[스텍 영역]
int c = 3;
// 이 범위 안에서만 변수 c가 유효하다.
//아직 이해가 안가서 함부로 쓰는거일수도 있다. 함수는 스텍영역인 것 같다.
#pragma region 비트연산
// 실습
// 0b0000 [무적][변이][스턴][공중부양]
// 무적상태를 만든다.
//flag = 8; //이것보다는
//flag = (1 << 3); // 이게 더 전달력이 좋다.
// flag = (1<<INVINCIBLE); //위의 코드보다 가독성이 더 좋아진다.
// 변이 상태를 추가한다. (무적+변이)
//flag |= (1 << 2);
// flag = (1 << POLYMORPH);
// 무적인지 확인하고 싶다? (다른 상태는 관심 없음)
//bitmask (필요없는 정보를 가릴 수 있다.)
// bool invincible = ((flag & (1 << 3)) != 0);
// 무적이거나 스턴 상태인지 확인하고 싶다면?
// bool stunOrinvincible = ((flag & 0b1010) != 0);
//or
// bool mask = (1 << 3) | (1 << 1);
// bool stunOrinvincible = ((flag & mask) != 0); // 이런 식으로 할 수도 있다.
#pragma endregion
}
유의사항
#include <iostream>
using namespace std;
// 오늘의 주제 : 유의사항
// 1) 변수의 유효범위
//전역 변수
// int hp = 20;
// 2) 연산의 우선순위 - () 소괄호 해줘서 오해여부를 없애주자.
// 3) 타입변환 (casting)
// 바구니 교체
// 작은 바구니에서 큰 바구니로의 교체는 괜찮은데 큰 바구니에서 작은 바구니는 데이터의 손실이 발생할 수 이싿.
//4) 사칙 연산 관련
// 오버플로우 주의- 단위가 커지면 __int64를 쓴다.
//나눗셈
// - 0 으로 나누는지 조심
// 실수관련
// 함수는 스텍메모리 범위
// 지역변수는 함수의 { } 중괄호 범위가 생존범위
//{ } 안에서만 사용 가능
//같은 이름을 두 번 사용할 때 문제
int main()
{
{
int hp = 10; // 이 안의 중괄호에서만 유효하다.
cout << hp << endl;
}
int hp = 123;
__int64 hp7 = 100000909*18818301;
// 이렇게 하면 오류 안난다.
//만약 전역변수에도 hp가 초기화선언 되어 있으면, 그것도 오류 안난다.
//실행시키면 main에 있는 hp를 실행시켜준다.
//그런데 이름이 같게 선언하는 거 자체가 잘못한거다.
//3. 형변환
int hp1 = 77777;
cout << hp1 << endl;
//바구니 교체
short hp2 = hp1; // 왼쪽 비트데이터가 짤린 상태로 저장
float hp3 = hp1; // 실수로 변환할 때 정밀도 차이가 있기 때문에 데이터 손실
unsigned int hp4 = hp1; // 비트 단위로 보면 똑같은데, 분석하는 방법이 달라짐
//4. 실수
int maxHp = 1000;
float ratio = hp / maxHp; //이렇게 하면 0 이 나온다. 0.123이 나오는게 아니다. 왜?
// int/int 끼리 연산하면 int가 나온다. 둘 중 하나는 무조건 float이어야 한다.
ratio = hp / (float)maxHp; //이런 식으로 해주던가 해야한다.
}
디버깅할 때 브레이크포인트 잡아두고 커서 올리기만 하면 결과값이 표시된다.
단 커서 올리는 지점이 브레이크포인트보다 위에 있어야 함(실행이 된 상태여야 함)