C++

C++ 입문 데이터 갖고 놀기

레나19 2022. 3. 9. 18:19

프로젝트 우클릭해서  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)

7.1 프로세스와 메모리 할당 (velog.io)

지역변수 - 스텍영역 공부하자. 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; //이런 식으로 해주던가 해야한다.
}

디버깅할 때 브레이크포인트 잡아두고 커서 올리기만 하면 결과값이 표시된다.

단 커서 올리는 지점이 브레이크포인트보다 위에 있어야 함(실행이 된 상태여야 함)

 

'C++' 카테고리의 다른 글

  (0) 2022.03.20
포인터  (0) 2022.03.14
함수  (0) 2022.03.10
코드의 흐름제어  (0) 2022.03.10
Part1. 어셈블리언어 입문  (0) 2022.03.07