C++

포인터

레나19 2022. 3. 14. 22:09

포인터 맛보기

#include <iostream>
using namespace std;

// 오늘의 주제 : 포인터

void SetHp(int* hp)
{
	*hp = 100;
}


int main()
{
	int hp = 1;
	SetHp(&hp);

	// SetHp의 매개변수를 int* hp로 하고. 이렇게 하면 main에 있는 함수에 hp로 접근하여 값을 바꾸어준다.
	//주소로 들어가서 값을 바꾸어주는거니까.


	//지금까지 사용한 방식
	// number라는 이름의 4바이트 정수 타입의 바구니를 만든다.
	// number라는 변수 스택 메모리에 할당
	// number = 1 이라 함은, number 바구니에 1이라는 숫자를 넣으라는 의미.
	// 따라서 스텍 메모리에 있는 특정 주소 (number 바구니) 에 우리가 원하는 값을 넣은 셈
	//number는 비유하자면 메모리에 이름을 붙인 것
	// 나쁘지 않고 편리한데, 단점은 원본 수정. 
	// 전역변수를 사용하면 아무곳이서나 수정이 가능한데
	// 지역변수 예를 들어 함수안에 있는 변수는 함수 안에서만 유효하고 밖에서는 유효하지 않다.
	//지역변수는 함수 밖에서 수정 불가능한데, 포인터로는 그것을 가능하게 해준다. 
	

	//포인터 : TYPE* 변수이름;
	// 바구니는 바구니인데..포인터는
	// 주소를 저장하는 바구니다!
	// 변수 선언할 때 * 등장했다 -> 포인터 = 주소
	
	int number = 1;
	int* ptr = &number;
	// 디버깅해서 바구니를 보면 ptr 안에 number의 주소가 저장되있는 것을 확인할 수 있다.
	// 포인터는 주소를 상자안에 들고 있다. 
	// 포인터라는 바구니는 4바이트 (32비트 환경) or 8바이트(64비트 환경) 고정크기
	//디버깅시 메모리에 &ptr, &number 이런 식으로 검색하면 찾을 수 있다.

	//근데 남의 주소를 가지고 뭐를 하라는건가?
	// 추가 문법 : [주소를 저장하는 바구니]가 가리키는 주소를 가서 무엇을 해라.
	// *변수이름 = 값;

	//* 포탈을 타고 순간이동한다고 생각해보자.
	//* 이 여러번 등장하니 헷갈리는데, 사용 시점에 따라서 구분하여 기억하자.
	// - 변수 선언(주소를 저장하는 바구니다!)
	//  * 사용할 때 (포탈을 타고 순간이동)

	int value1 = *ptr;
	*ptr = 2;

	//TYPE은 왜 붙여줄까? 어차피 주소만 보여주는데~
	//  * = 포인터의 의미 = 주소를 저장하는 바구니
	//포인터에 대한 추가정보를 준다고 생각하면 된다.
	//주소에 가면 어떤 TYPE이 있는지...
	
	// 시험 삼아 타입 불일치하게 해서 형변환으로 한번 컴퓨터를 이해시켜보려고 한다음 실행해보자.
	__int64* ptr2 = (__int64*) & number;
	
	*ptr2 = 0xAABBCCDDEEFF;
	//이렇게 짜면 디버깅해서 메모리영역 보면 int 메모리 허용범위를 초과해서 그 다음칸에 AABB가 따로 저장되어있는 것을 확인할 수 있다.
	//버그 발생
	return 0;
}

 

포인터 연산

#include <iostream>
using namespace std;

// 오늘의 주제 : 포인터 연산

// 1) 주소연산자 (&)
// 2) 산술 연산자 ( + - )
// 3) 간접 연산자
// 4) 간접 멤버 연산자

struct Player
{
	int hp; //객체 생성해서 디버깅해보면 hp바로 밑이 damage가 메모리 자리를 차지하고 있는 걸 확인할 수 있다.
	int damage;
};

int main()
{
	int number = 1;

	// 1) 주소 연산자 (&)
	// - 해당 변수의 주소를 알려주세요.
	// - 더 정확히 말하면 해당 변수 타입(TYPE)에 따라서 TYPE* 반환
	int* pointer = &number;


	// 2) 산술 연산자 ( + - )

	number += 1;
	//&number 0x012FFC98 000000002  &pointer 0x012FFC8C 012ffc98
	// 
	// int*  
	// * : 포인터 타입이네! (8바이트) 주소를 담는 바구니
	// int : 주소를 따라가면 int(41ㅏ이트 정수형 바구니)가 있다고 가정해라.
	// 포인터에서 + 나 - 등 산술 연산으로 1을 더하거나 빼면
	// 정말 '그 숫자'를 더하고 뺴라는 의미가 아니다.
	// 한번더 TYPE의 크기만큼을 이동하라! 라는 의미
	// 다음/ 이전 바구니로 이동하고 싶다 << [바구니 단위] 의 이동으로
	// 즉, 1을 더하면 = 바구니 1개 이동시켜라
	// 3을 더하면 - 바구니 3개를 이동시켜라. TYPE이 곱해져서 이동이 된다.
 
	pointer += 1; // 디버깅해보니 4증가했다(?) 왜>? 다음주소로 넘어간다. +4 해서 넘어간 주소를 보니 number가 저장된 주소 다음 주소를 가리킨다.
	//pointer에 &number인 012ffc98 이 저장되어있었는데 거기서 4를 더 해준게 012ffc9c 를 가리키고 이 주소는 number 바로 다음 메모리 주소를 가리킨다. 
	//TYPE만큼 더해지고 빼진다.TYPE만큼 연산이 된다. 
	//배열에서 이런거를 종종 쓴다.


	//3) 간접 연산자 (*)
	// * 포탈을 타고 해당 주소로 슝 이동~
	number = 3;
	*pointer = 4;

	// 4) 간접멤버 연산자
	Player player;
	player.hp = 100;
	player.damage = 10;

	Player* playerPtr = &player;

	(*playerPtr).hp = 200; // playerPtr이라는 주소로 이동한 다음에 hp를 200 으로 바꾸어준다. 
	(*playerPtr).damage = 200; //playerPtr 이라는 주소로 이동을 한 다음에 damage를 200으로 바꾸어주는거. hp주소에서 +4만큼 이동한 damage가 저장된 곳에 저장해준다. 

	// 4) 간접 멤버 연산자 (->)
	// * 간접 연산자 (포탈 타고 해당 주소로 gogo)
	// . 구조체의 특정 멤버를 다룰 때 사용
	// -> 는 *와 . 을 한 방에 처리한다.
	playerPtr->hp = 200;
	playerPtr->damage = 200;



	return 0;
}

 

포인터 실습

#include <iostream>
using namespace std;

// 오늘의 주제 : 포인터 실습
//playerInfo와 MonsterInfo로 매개변수 포인터 이용해서 하는 차이점을 알아보자. 
struct StatInfo
{
	int hp; // +0
	int attack;  // +4
	int defence;  // +8
};

void EnterLobby();
StatInfo CreatePlayer(); // 포인터 배우기 이전버전

void CreateMonster(StatInfo* info); // 포인터 배운 이후 버전
bool StartBattle(StatInfo* player, StatInfo* monster);
// 플레이어 승리시 true, 아니면 false

int main()
{
	
	EnterLobby();

	return 0;
}

void EnterLobby()
{
	cout << "로비에 입장했습니다." << endl;

	StatInfo player;  // 디버깅 할 때 변수값을 보기 편하게 0xbbbbbbb로 채워놓아서 이렇게 밑에 주소로 해놓았따.
	player.hp = 0xbbbbbbbb;
	player.attack = 0xbbbbbbbb;
	player.defence = 0xbbbbbbbb;


	player = CreatePlayer(); // 이렇게 하면 만약 데이터가 커진다면성능적인 부하를 야기할 수 있다. 
	//[매개변수][RET][지역변수 (temp(100,10,2), player(100,10,2))] [매개변수][&temp][지역변수 (ret(100,10,2))]
	//디버깅해서 어셈블리를 보면 이동 복사 붙여넣기가 여러번 일어난다.
	//ret라는 중간저장소를 이용해서 다시 복사 이동되는 형식으로 데이터 복사 이동이 일어난다.

	StatInfo monster;
	monster.hp = 0xbbbbbbbb;
	monster.attack = 0xbbbbbbbb;
	monster.defence = 0xbbbbbbbb;

	CreateMonster(&monster);// 반면에 위의 CreatePlayer에 비해 코드가 많이 짧아지고 
	//위와 다르게 temp 없이 주소를 이용해 바로 데이터가 들어간다. 


	// 번외편1) 
	// 구조체끼리 복사할 때 무슨 일이 벌어질까?
	// player = monster; // 한 줄이라도 빠르다고 생각하면 안된다. 데이터 복사하는 것도 데이터가 커지면 조심해서 복사를 해야한다.

	bool victory = 	StartBattle(&player, &monster);
	if (victory)
		cout << "승리!" << endl;
	else
		cout << "패배!" << endl;

}

StatInfo CreatePlayer()
{
	StatInfo ret;

	cout << "플레이어 생성" << endl;

	ret.hp = 100;
	ret.attack = 10;
	ret.defence = 2;

	return ret;

}

void CreateMonster(StatInfo* info)
{

	cout << "몬스터 생성" << endl;

	info->hp = 40;
	info->attack = 8;
	info->defence = 1;

}
bool StartBattle(StatInfo* player, StatInfo* monster)
{
	while (true)
	{
		int damage = player->attack - monster->defence;
		if (damage < 0)
			damage = 0;

		monster->hp -= damage;
		if (monster->hp < 0)
			monster->hp = 0;

		cout << "몬스터 HP : " << monster->hp << endl;

		if (monster->hp == 0)
			return true;

		damage = monster->attack - player->defence;
		if (damage < 0)
			damage = 0;

		cout << "플레이어 HP : " << player->hp << endl;

		player->hp -= damage;
		if (player->hp < 0)
			player->hp = 0;

		if (player->hp == 0)
			return false;
		
	}
}

 

 

핵심 : 포인터로 넘길때와 반환값으로 복사해서 넘길떄의 차이점

구조체 자체를 덮어주는 형식으로 만들어서 반환하는 경우 복사가 여러번 일어날 수 있다.

포인터를 넘겨주는 경우에는 실제 원본을 대상으로 접근해서 작업하기 때문에 작업이 더 빨리 수행될 수 있다.

 

 

참조 기초

#include <iostream>
using namespace std;

// 오늘의 주제 : 참조
//playerInfo와 MonsterInfo로 매개변수 포인터 이용해서 하는 차이점을 알아보자. 
struct StatInfo
{
	int hp; // +0
	int attack;  // +4
	int defence;  // +8 이렇게 주소에 저장이 된다.
};

//[매개변수][RET][지역변수(info)] -> [매개변수(&info)][RET][지역변수]
void CreateMonster(StatInfo* info)
{
	info->hp = 100;
	info->attack = 8;
	info->defence = 5;
}

//포인터를 이용하지 않고 하면 값의 이동 복사가 일어나서 데이터처리부하가 늘어난다.
//[매개변수][RET][지역변수(info)] -> [매개변수(info(100,8,5))][RET][지역변수]
// 값의 저장이 temp에 되고 이 값들이 매개변수로 복사가 되어 다시 이 함수 사용하는 곳에 매개변수로 들어가서 복사와 저장이 된다.?? 뭐 이런식? 암튼 복잡하다. 
void CreateMonster(StatInfo info)
{
	info.hp = 100;
	info.attack = 8;
	info.defence = 5;
}

// 값을 수정하지 않는다면, 둘 다 문제가 없다.
// 1) 값 전달 방식

void PrintInfoByCopy(StatInfo info)
{
	cout << "------------------------" << endl;
	cout << "HP: " << info.hp << endl;
	cout << "ATT: " << info.attack << endl;
	cout << "DEF: " << info.defence << endl;
	cout << "------------------------" << endl;
}

// 2) 주소 전달 방식
void PrintInfoByPtr(StatInfo* info)
{
	cout << "------------------------" << endl;
	cout << "HP: " << info->hp << endl;
	cout << "ATT: " << info->attack << endl;
	cout << "DEF: " << info->defence << endl;
	cout << "------------------------" << endl;
}

// 1) 2) 둘다 결과는 같은데, 내부적으로 수행방법은 다르다. 
// StatInfo 구조체가 1000바이트 짜리 대형 구조체라면?
// - (값 전달) StatInfo 로 넘기면 1000바이트가 복사되는 구조.
// - (주소 전달) StatInfo* 은 8바이트 주소용량(64비트환경)이 복사되는 구조. 데이터복사에 대한 부담이 없다. 
// - (참조 전달) StatInfo& 8바이트.

// 3) 참조 전달 방식
//값 전달처럼 편리하게 사용하고!
//주소 전달처럼 주소값을 이용해 진퉁을 건드리는 일석 이조의 방식.


void PrintInfoByRef(StatInfo& info)
{
	cout << "------------------------" << endl;
	cout << "HP: " << info.hp << endl;
	cout << "ATT: " << info.attack << endl;
	cout << "DEF: " << info.defence << endl;
	cout << "------------------------" << endl;
}

int main()
{
	int number = 1;
	// 4바이트 정수형 바구니를 사용할거야.
	// 앞으로 그 바구니 이름을 number라고 할게.
	// 그러니까 number에서 뭘 꺼내거나, number에 뭘 넣는다고 하면
	// 찰떡같이 알아듣고 해당 주소(data, stack, heap)에 1을 넣어주면 된다.

	int* pointer = &number;

	// * 주소를 담는 바구니
	// int 그 바구니를 따라가면 int 데이터 (바구니)가 있음
	
	*pointer = 2;
	//pointer 바구니에 있는 주소를 타고 이동해서, 그 멀리 있는 바구니에 2를 넣는다.

	int& reference = number; //두번째 이름을 지어줌. 
	//로우레벨(어셈블리) 관점에서 실제 작동 방식은 int*와 똑같음

	//C++ 관점에서는 number라는 바구니에 또 다른 이름을 부여한 것.
	//number라는 바구니에 reference라는 다른 이름을 지어줄께~
	// 앞으로 reference바구니에다가 뭘 꺼내거나 넣으면,
	// 실제 number 바구니(진퉁에다가) 그 값을 꺼내거나 넣으면 됨.
	//사용하는 방식은 위의 위와 같은데 작동방식은 포인터와 같다.

	reference = 3; // 이라고 하면 reference는 number의 주소값을 들고 있고 number에 값이 3이 저장된다.
	// 포인터와 같네;
	//어셈블리로 디버깅해보면 포인터와 작동방식이 똑같다는 것을 확인할 수 있다.

	//그런데 귀찮게 또 다른 이름을 짓는 이유는?
	// 1. 포인터를 사용하자니 -> 이게 마음에 안듬. 
	// 2. 위에처럼 포인터를 사용안하자니 자료복사 등 데이터의 과부하 줄이기 힘듬. 
	//참조 전달 때문에 장점이 있다. 




	StatInfo info;
	CreateMonster(&info);

	PrintInfoByCopy(info);
	PrintInfoByPtr(&info);
	PrintInfoByRef(info);
	return 0;
}

참조 : 작동방식은 pointer처럼 주소를 전달해주는 방식이지만 사용interface는 일반적인 변수사용과 같다.

 

 

포인터 vs 참조

#include <iostream>
using namespace std;

// 오늘의 주제 : 포인터 vs 참조

struct StatInfo
{
	int hp; // +0
	int attack;  // +4
	int defence;  // +8 이렇게 주소에 저장이 된다.
};

//[매개변수][RET][지역변수(info)] -> [매개변수(&info)][RET][지역변수]
void CreateMonster(StatInfo* info)
{
	info->hp = 100;
	info->attack = 8;
	info->defence = 5;
}

//[매개변수][RET][지역변수(info)] -> [매개변수(info(100,8,5))][RET][지역변수]

void CreateMonster(StatInfo info)
{
	info.hp = 100;
	info.attack = 8;
	info.defence = 5;
}

// 값을 수정하지 않는다면, 둘 다 문제가 없다.
// 1) 값 전달 방식

void PrintInfoByCopy(StatInfo info)
{
	cout << "------------------------" << endl;
	cout << "HP: " << info.hp << endl;
	cout << "ATT: " << info.attack << endl;
	cout << "DEF: " << info.defence << endl;
	cout << "------------------------" << endl;
}

StatInfo* FindMonster()
{
	// TODO : Heap 영역에서 뭔가를 찾아봄
	// 찾았다!
	// return monster~;

	return nullptr; //못찾았을 때. 이런 식으로 참조는 이렇게 표현 못하지만 포인터는 가능. 
}

StatInfo globalInfo;
// 2) 주소 전달 방식
void PrintInfo(StatInfo* info)
{	
	if (info == nullptr)
		return;
	//info 가 nullPtr일 수도 있으니까 이렇게 처리를 해주도록 한다.


	cout << "------------------------" << endl;
	cout << "HP: " << info->hp << endl;
	cout << "ATT: " << info->attack << endl;
	cout << "DEF: " << info->defence << endl;
	cout << "------------------------" << endl;

	//const를 어디에다 붙이느냐에 따라 달라질 수 있다.
	// 별 뒤에 붙인다면??
	// StatInfo* const info
	// info라는 바구니의 내용물[주소]을 바꿀 수 없음.
	// info는 주소값을 갖는 바구니 -> 이 주소값이 고정이다!
	// 예를 들어 info = &globalInfo; 라고 하면 컴파일에러가 난다.

	// 별 이전에 붙인다면?
	// const StatInfo* info
	// info가 '가리키고 있는' 바구니의 내용물을 바꿀 수 없음
	// '원격' 바구니의 내용물을 바꿀 수 없음.
	// 예를 들어 info-> hp = 10000; 에러 확인가능하다.
	// 이 경우를 정말 많이 쓴다. 
	// const를 붙임으로서 안정성이 향상된다. 

	// info는 [주소값]을 들고 있고,  주소값에 가면 [ 데이터 ]가 있다. 
	info = &globalInfo;
	info->hp = 10000;
	//위의 변수 수정으로 const 선언해보면 컴파일에러 나는 것을 확인해볼수 있다.
}


void PrintInfo(const StatInfo& info)  //const를 붙여주면 읽기만 가능하고 쓰기는 할 수 없다.
{
	cout << "------------------------" << endl;
	cout << "HP: " << info.hp << endl;
	cout << "ATT: " << info.attack << endl;
	cout << "DEF: " << info.defence << endl;
	cout << "------------------------" << endl;

//	info.hp = 10000; //error 발생.
	//참조같은 경우는 const와 정말 빈번하게 쓰인다. 
}

#define OUT // 아무 의미 없는데 바뀔 수 있다는 의미를 주기 위해서 OUT을 넣어준다.
void ChangeInfo(OUT StatInfo& info)
{
	info.hp = 10000;
}

int main()
{
	StatInfo info;

	//포인터 vs 참조 세기의 대결
	// 성능 : 똑같음!
	// 편의성 : 참조 승!

	// 1) 편의성 관련
	// 편의성이 좋다는 게 꼭 장점만은 아니다.
	// 포인터는 주소를 넘기니까 확실하게 원본을 넘긴다는 힌트를 줄 수 있다.
	// 참조는 자연스럽게 모르고 지나칠 수도 있다.
	// ex) 마음대로 고친다면?
	// const를 사용하여 마음대로 고치지 않게 

	// 참고로 포인터도 const를 사용가능
	// * 기준으로 앞에 붙이느냐, 뒤에 붙이느냐에 따라 의미가 달라진다.

	// 2) 초기화 여부
	// 참조 타입은 바구니의 2번째 이름 (별칭?)
	//  -> 참조하는 대상이 없으면 안된다.
	// 포인터처럼 주소 연산이 안된다.
	// 반면 포인터는 어떤 주소 라는 의미
	// -> 대상이 실존하지 않아도 선언 가능하다.
	
	// 포인터에서 '없다'라는 의미로는 어떻게 선언하나?
	// StatInfo* pointer = nullptr; 
	// StatInfo* pointer = null;
	// StatInfo* pointer = 0;
	// 셋 다 같은 의미인데 가장 위에꺼를 주로 사용한다.
	//어떠한 주소도 가리키지 않는 상태라는 의미
	
	// 참조 타입은 이런 nullptr 개념이 없다.
	
	StatInfo* pointer;
	pointer = &info;
	PrintInfo(pointer);

//	StatInfo& reference;
//	reference = info; // 이렇게 선언하면 컴파일 에러가 난다.
	StatInfo& reference = info; // 이렇게 선언해야 한다.
//	reference++;  // 이것 역시 컴파일 에러가 난다.  
	PrintInfo(reference);

	CreateMonster(&info);

	PrintInfoByCopy(info);
	PrintInfo(&info);
	PrintInfo(info);


	//그래서 결론은?
	// 사실 Team By Team ... 정해진 답은 없다.
	// Ex) 구글에서 만든 오픈소스를 보면 거의 무조건 포인터를 사용한다.
	// Ex) 언리언 엔진에선 reference도 애용한다.
	// return 값이 없는 것도 고려해야 한다면 pointer (null 체크 필수) 
	// 바뀌지 않고 읽는 용도(reading only) 로만 사용한다면 참조도 좋다. const ref&
	// 그 외 일반적으로 ref (명시적으로 호출할 때 OUT을 붙인다.)
	// -- 단, 다른 사람이 pointer를 만들어놓은 걸 이어서 만든다면, 계속 pointer를 사용해준다. 섞어서 사용하지 않는다. 
	
	ChangeInfo(OUT info); // 참조지만 바뀔 수 있다는 것을 명시해주기 위해 OUT을 명시해주었다. 

	// Bonus : 포인터로 사용하던 걸 참조로 넘겨주려면?
	// pointer [주소(&info)] ref.info[데이터]
	// PrintInfoByRef(*pointer);

	// Bonus : 참조로 사용하던 걸 포인터로 넘겨주려면?
	// pointer[주소] reference, info[데이터]
	// PrintInfoByPtr(&reference);

	//이런 식으로 해준다.

	return 0;
}

//단축키 ctrl r + r 로 함수 지정한 상태에서 단축키 누르면 한번에 다 바꿀 수 있다.

포인터는 함수에 넣어서 그 값을 바꾸어주는데 의의가 있어보인다. 

 

배열 기초

#include <iostream>
using namespace std;

// 오늘의 주제 : 배열
struct StatInfo
{
	int hp = 0xAAAAAAAA;
	int attack = 0xBBBBBBBBB;
	int defence = 0xDDDDDDDD;
};

int main()
{

	// 몬스터가 정해진 숫자가 아닌 몬스터 최대 1만마리라면 이렇게 선언해서 만들수 있나?
	StatInfo monsters[10];
	// 배열의 크기는 상수여야 한다. (VC 컴파일러 기준)
	const int monsterCount = 10;
	StatInfo monsters[monsterCount];

	//여태껏 변수들의 [이름]은 바구니였음
	int a = 10;
	int b = a;

	//그런데 배열의 [이름]은 조금 다르게 동작한다.
	// 배열의 이름은 배열의 시작 주소다.
	// 정확히는 시작 위치를 가리키는 TYPE* 포인터
	//주소이기 때문에 디버깅시 메모리검색할 떄 바로 monsters 엔터 해도 검색이 되는 것이다.
	auto WhoAmI = monsters; //auto 는 카멜레온같은 녀석, 변수에 따라서 타입이 자동으로 입력된다.. 
	
	// 주소[(100,10,1)] StatInfo[ ] StatInfo [ ] StatInfo[ ] ...
	// monster_0[ 주소 ]
	StatInfo* monster_0 = monsters;
	monster_0->hp = 100;
	monster_0->attack = 10;
	monster_0->defence = 1;

	// 포인터의 덧셈! 진짜 1을 더하라는 게 아니라, StatInfo 타입 바구니 한개씩 이동하라는 의미
	// StatInfo [ ] 주소[(200,20,2)] StatInfo[ ] StatInfo [ ] StatInfo[ ]
	// monster_1 [ 주소 ]
	StatInfo* monster_1 = monsters + 1;
	monster_1->hp = 200;
	monster_1->attack = 20;
	monster_1->defence = 2;

	// 이번에는 참조를 써보자
	// 포인터와 참조는 자유자재로 변환이 가능하다.
	// StatInfo [ ] StatInfo[ ] monster_2 주소 [  ] StatInfo[ ]
	// monster_2[ 주소 ] 
	StatInfo& monster_2 = *(monsters + 2);
	monster_2.hp = 300;
	monster_2.attack = 30;
	monster_2.defence = 3;

	//[주의] 이거는 완전 다른 의미다.
	// 이거는 temp에 monsters+2 주소에 있는 거를 가져와서 저장하는 방식. 
	StatInfo temp;
	temp = *(monsters + 2);
	temp.hp = 400;
	temp.attack = 40;
	temp.defence = 4;

	// 이를 조금 더 자동화한다!
	for (int i = 0; i < 10; i++)
	{
		StatInfo& monster = *(monsters + i);
		monster.hp = 100 * (i + 1);
		monster.attack = 10*(i+1);
		monster.defence = (i+1);
	}

	// 근데 *(monsters + i ) 너무 불편하고 가독성이 떨어진다... 더 편한 방법?
	// 인덱스!
	// 배열은 0번부터 시작. N번째 인덱스에 해당하는 데이터에 접근하려면 배열이름[N]
	// *(monsters + i) = monsters[i]

	return 0;

	monsters[0].hp = 100;
	monsters[0].attack = 10;
	monsters[0].defence = 1;

	for (int i = 0; i < 10; i++)
	{
		monsters[i].hp = 100 * (i + 1);
		monsters[i].attack = 10 * (i + 1);
		monsters[i].defence = (i + 1);
	}

	//배열 초기화 방법 몇 가지
	int numbers[5] = {}; // 다 0으로 입력된다.
	int numbers1[10] = { 1,2,3,4,5 }; // 설정한 애들은 설정한 값으로. 나머지 값들은 0으로 초기화된다.
	int numbers2[] = { 1,2,3,4,11,24,124,14,1 }; // 데이터 개수만큼의 크기에 해당하는 배열로 만들어진다.

	char helloStr[] = { 'H','e','l','l','o','\0' };

}

 

 

포인터 vs 배열

 

#include <iostream>
using namespace std;

// 오늘의 주제 : 포인터 vs 배열

void Test(int a)
{
	a++;
}
//배열은 함수 인자로 넘기면, 컴파일러가 알아서 포인터로 치환한다.(char[] -> char*
// 즉 배열의 내용 전체를 넘긴게 아니라, 시작 주소 즉 포인터만 넘긴 것. 
void Test(char a[])
{
	a[0] = 'x';
}

int main()
{
	"Hello World"; // 여기에 마우스 커서 올려보면 const char 볼 수 있다.\0 이 합쳐져 12개짜리 문자열 배열을 확인할 수 있다.
	cout << "Hello WOrld" << endl;
	const char msg[] = { 'H','e','l','l','o','\0' }; // char 문자열은 끝났다는 표시로 \0 (null 의 의미) 를 입력해줘야 한다.
	//const가 붙으면 수정할 수가 없다.


	// .data에 저장됨. [주소][H][e][l][l][o][][W][o][r][l][d][\0]
	// test1 [ 주소 ] << 8바이트 (64비트 환경)
	const char* test1 = "Hello World";

	// [H][e][l][l][o][][W][o][r][l][d][\0]
	// test2 = 주소
	char test2[12] = "Hello World";

	//포인터와 배열의 차이
	//포인터는 주소를 담는 바구니가 따로 있다. 원격으로 주소가 가리키고 있다.
	//첫번째 [H] 이게 있는 거 자체가 주소값 test2 이다.
	// - [배열 이름]은 '바구니 모음'의 [시작 주소], 주소값만 넘겨주는 역할을 한다.

	int a = 0;
	// [매개변수][RET][지역변수(a=0)]    [매개변수(a=0)][RET][지역변수(a=0]
	Test(a);
	cout << a << endl;
	//a는 여전히 0 왜냐? 1이라는 값이 들어간거지 아무것도 변화는 없다. 
	
	Test(test2);
	cout << test2 << endl;
	//test2 가 바뀔까 안바뀔까? 바뀐다.
	
	return 0;
}

 

const char* test1 = "Hello World";
Hello World가 메모리 어딘가에 생성되고
test1은 그저 Hello World가 어디있는지만 알려줌
포인터이기 때문에 4바이트 or 8바이트

-> 그 메모리 '어딘가' = 데이터 영역입니다.

char test2[] = "Hello World";
Hello World가 메모리 어딘가에 일단 생성됨

-> 이번엔 메모리 '어딘가' = 스택입니다.

Hello World가 있는곳을 찾아서 4바이트씩 끊어서 계속
test2 배열에 넣어줌 그래서 문자열의 크기가 커지면 크기가같이 커짐

-> 아닙니다. 4바이트가 아니라 char은 1바이트 이기 때문에
'H' 'e' 'l' ... 등을 각각 복사해주고 각 1바이트입니다.
사실상 test3 케이스와 유사하게 동작합니다.

char test3[] = { 'h','e','l','l','o' }; 는
hello가 메모리 어딘가에 생성되는게라 그냥 하나식 test3 배열에 옮겨짐

-> 네 어셈블리를 까보면 스택에 'h' 'e' .. 한 글짜씩 복사됩니다.

그리고 의문이 생길 땐 어셈블리로 까보는걸 추천 드립니다.
그래야 공부도 되고 오래 남습니다.

 

로또 번호 생성기

#include <iostream>
using namespace std;

// 오늘의 주제 : 로또 번호 생성기

void Swap(int& a, int& b)
{
	int temp = a;
	a = b;
	b = temp;
}

void Sort(int numbers[], int count)
{
	for (int i = 0; i < count; i++)
	{
		// i 번째 값이 제일 좋은 후보라고 가정
		int best = i;
		// 다른 후보와 비교를 통해 제일 좋은 후보를 찾아나선다. 
		for (int j = i + 1; j < count; j++)
		{
			if (numbers[j] < numbers[best])
				best = j;
		}

		// 제일 좋은 후보와 교체하는 과정
		if (i != best)
			Swap(numbers[i], numbers[best]);
	}
}

//  3) 로또 번호 생성
void ChooseLotto(int numbers[])
{
	srand((unsigned)time(0)); // 랜덤 시드 설정

	int count = 0;
	while (count != 6)
	{
		int randValue = 1 + (rand() % 45); // 1-45

		// 이미 찾은 값인지?
		bool found = false;
		for (int i = 0; i < count; i++)
		{
			//이미 찾은 값
			if (numbers[i] == randValue)
			{
				found = true;
				break;
			}
		}
		//못찾았으면 추가!
		if (found == false)
		{
			numbers[count] = randValue;
			count++;
			//TODO : 랜덤으로 1~45 사이의 숫자를 6개를 골라주세요! (단, 중복이 없어야 함)
		}
	}
	Sort(numbers, 6);
}

int main()
{
	// 1) Swap  함수 만들기
	int a = 1;
	int b = 2;
	Swap(a, b); // a=2, b = 1

	cout << a << " " << b << endl;

	// 2) 정렬 함수 만들기 (작은 숫자가 먼저 오도록 정렬)
	int numbers[6] = { 1,42,3,15,5,6 };
	//{ 1,3,5,6,15,42 }
	//배열의 크기를 일일이 쓰기 힘드니까. sizeof(numbers) / sizeof(int) 를 활용해준다.
	int size1 = sizeof(numbers); // 6*4 = 24
	int size2 = sizeof(int);  // 4

	//Sort(numbers, 6);
	//위에 것보다
	Sort(numbers, sizeof(numbers) / sizeof(int));
	
	// 3) 로또 번호 생성기
	ChooseLotto(numbers);
	
	for (int i = 0; i < 6; i++)
		cout << numbers[i] << endl;

	return 0;
}

 

다중포인터

#include <iostream>
using namespace std;

// 오늘의 주제 : 다중 포인터

void SetNumber(int* a)
{
	*a = 1;
}

void SetMessage(const char* a)
{
	a = "Bye"; //바구니의 내용만 고칠 뿐이지. int main에 있는 지역변수를 건드린 게 아니다.
	// 함수 종료되면 그냥 그 함수 안에서만 바뀌고 끝. 
}

void SetMessage(const char** a)
{
	*a = "Bye";
}

void SetMessage2(const char*& a)
{
	a = "Wow";
}


int main()
{
	int a = 0;
	SetNumber(&a);
	cout << a << endl;
	//main에 있는 a가 매개변수 포인터를 활용한 방법으로 수정할 수 있다. 

	// msg [주소] 가 들어가있는 바구니 << 8바이트(64비트환경)
	//Hello는 어디있느냐? .rdata[H][e][l][l][o][\0]
	const char* msg = "Hello";

	//[매개변수][리턴주소][지역변수(msg(Hello주소))]  [매개변수(a(Hello주소))][리턴주소][지역변수]
	SetMessage(msg);
	cout << msg << endl;

	// .rdata Hello주소[H][e][l][l][o][\0] 
	// 주소1 [ Hello주소 ] << 8바이트
	// pp [&msg] << 8바이트
	const char** pp = &msg;
	*pp = "Bye";
	cout << msg << endl;

	SetMessage(pp); //SetMessage(&msg); 와 동일
	cout << msg << endl;

	// 다중포인터 뭔가 혼동스럽다?
	// 다중포인터 는 그냥 양파까기라고 생각하면 된다.
	// * 을 하나씩 까면서 간다고 생각하면 된다. 

	SetMessage2(msg); // msg의 주소를 보낸거..
	cout << msg << endl;


	return 0;
}

 

다차원배열

#include <iostream>
using namespace std;

// 오늘의 주제 : 다차원 배열


int main()
{
	int a[10] = { 1,2,3 };

	// [4] [2] [3] [4] [1] << 0층
	// [1] [1] [5] [2] [2] << 1층

	int apartment2D[2][5] = { {4,2,3,4,1}, {1,1,5,2,2} };

	for (int floor = 0; floor < 2; floor++)
	{
		for (int room = 0; room < 5; room++)
		{
			// apartment2D + (floor * 20) +4 *room을 한 주소. 디버깅후 디셈블리어로 확인가능.
			int num = apartment2D[floor][room];
			cout << num << " ";
		}
		cout << endl;
	// 디버깅을 해보면 1차원배열이 쭉 나열되어있는 것처럼 2차원 배열이 메모리에 저장되어있다. 

	int apartment1D[10] = { 4,2,3,4,1,1,1,5,2,2 };
	
	//디버깅해보면 메모리에는 위에꺼와 이것과 같은 방식으로 저장되어 있다. 
	for (int floor = 0; floor < 2; floor++)
	{
		for (int room = 0; room < 5; room++)
		{
			int index = (floor * 5) + room;
			// apartment1D + (floor * 20) +4 *room을 한 주소. 디버깅후 디셈블리어로 확인가능. 
			int num = apartment1D[index];
			cout << num << " ";
		}
		cout << endl;
	}

	// 2차 배열은 언제 사용할까? 대표적으로 2D 로그라이크 맵 사용할 떄. 
	
	int map[5][5] =  // 갈 수 있는 경우를 1, 갈 수 없는 경우를 0으로 표시해줌
	{
		{1,1,1,1,1},
		{1,0,1,1,1},
		{1,0,0,0,0},
		{1,0,1,1,1},
		{1,1,1,1,1},
	};
	return 0;
}

포인터마무리

 

#include <iostream>
using namespace std;

// 오늘의 주제 : 포인터 마무리

// 1) 포인터 vs 배열 2탄
// 2) 주의사항 (마음가짐?)

int& TestRef()
{
	int a = 1;
	return a;
}

int* TestPointer() //함수 안에서만 유효한 주소값을 외부로 넘겨주는 행위는 절대로 하면 안된다. 
{					//왜냐면 함수는 한번 실행되고 사라져야 하는데, 그 메모리주소가 그대로 남아있으면
	int a = 1;		// 앞으로 crash가 날 수 있다.
	return &a;

}

void TestWrong(int* ptr)
{
	int a[100] = {};
	*ptr = 0x12341234;
}

int main()
{
	//p는 주소를 담는 바구니
	// 진퉁은 저 멀리 어딘가에 있음
	//p는 단지 그 곳으로 워프하는 포탈
	int* p;

	//진짜배기 원조 데이터가 저장됨.
	//닭장처럼 데이터의 묶음 (엄청 많고 거대함)
	int arr[10];

	//그런데 상당히 많은 사람들이 [배열 = 포인터]라 착각하는 경향이 있다.

	// - [배열의 이름]은 배열의 시작 주소값을 가리키는 TYPE* 포인터로 변환가능
	p = arr;
	//-[TYPE형 1차원 배열]과 [TYPE*형 포인터]는 완전히 호환이 된다.
	cout << p[0] << endl;
	cout << arr[0] << endl;
	cout << p[5] << endl;
	cout << arr[5] << endl;

	cout << *p << endl;  // p[0]
	cout << *arr << endl; // arr[0]
	cout << *(p + 3) << endl;
	cout << *(arr + 3) << endl;

	// 지옥을 보여드리겠습니다. (2차원 배열 vs 다중포인터)

	// [1][2][3][4]
	int arr2[2][2] = { {1,2}, {3,4} };

	//int** pp = (int**)arr2;   // 예외발생한다.타입형변환도 원래 이렇게 하면 안됨. 
	//cout << (**pp) << endl;



	//아래 내용은 별로 중요한게 아니다. 패스해도 됨.
	int(*p2)[2] = arr2; //연습문제할때나 하는거지 이런코드는 평생 쓸 일 없을거라고 한다;
	cout << (*p2)[0] << endl;
	cout << (*p2)[1] << endl;
	cout << (*(p2 + 1))[0] << endl;
	cout << (*(p2 + 1))[1] << endl;

	cout << p2[0][0] << endl;
	cout << p2[0][1] << endl;
	cout << p2[1][0] << endl;
	cout << p2[1][1] << endl;

	//C++의 무서운 점은 알게 모르게 포인터를 잘못 다루면 크래쉬가 나는 경우가 있다. 

	int* pointer = TestPointer();
	*pointer = 123;
	//위의 두 줄이 문제가 없어보이는데 사실은 아주 큰 문제를 가지고 있다.
	//스텍프레임에서 TestPointer()를 실행시키면 pointer 안에 a의 주소값이 저장되게 된다.
	//하지만 TestPointer()는 한 번 실행되고 나면 사라지는 것인데, 사라져야 할 것의 주소값을 계속 
	// 가지고 있는 것은 앞으로 큰 문제를 야기하게 된다는 거다.
	//그리고 그 사라져야 할 주소값에 123을 저장하게 되면.. 나중에 crash를 야기할 수 있게 된다. 

	TestWrong(pointer);
	//이거를 쓰면 TestWrong 함수와는 관계없는 메모리영역을 수정하는 꼴이 된다. 
	//이거 실행하면 crash가 난다.

	//디버깅모드에서는 문제를 잡아주는데 
	//release모드로 하면 문제 없이 컴파일하고 끝나게 된다. 디버깅하는 습관을 갖자. 
	return 0;
}

 

 

 

 

 

*ptr 부분 크래쉬 이유
코드주니어2021.12.15 PM 20:400

*ptr이 매개변수로 받을때부터 TestPtr에서 &a를 받았는데  어째서 그값이 a[100] 주소를 가르키고있고 그 상태에서*ptr=0x12341234를 넣었을떄 어떻게 크래쉬가 나는지 이해가 되지않아 이렇게 질문을드립니다

 

Rookiss2021.12.16 AM 03:08

일단 TestPtr()이라는 함수가 굉장히 문제가 많은 함수고
스택 영역의 로컬 주소를 저렇게 밖으로 반환하는 것 자체가 절대 해서는 안 되는 행동입니다.

스택 영역은 함수가 종료되고 다른 함수가 호출되면 재사용하는데
운 나쁘게 저렇게 영역이 겹치고 있다면 엉뚱한 메모리를 건드릴 수 있는 것이죠.

[스택 프레임] 구조는 안 보고도 그릴 수 있을 정도로
의외로 중요한 부분이니 완전히 이해를 해야 합니다.

좋아요(1)댓글 달기
코드주니어2021.12.16 AM 03:44

TestPtr로인해 로컬주소가 밖에 노출되었고 TestWrong 스택프레임 영역에 겹쳐져서 그안에 메모리를 건드려가지고 오류가 발생했단거군요. 답변감사합니다

 

TextRPG #3

#include <iostream>
using namespace std;

// 오늘의 주제 : TextRPG2


// main
// - EnterLobby (PlayerInfo)
// -- CreatePlayer
// -- EnterGame (MonsterInfo)
// ---CreateMonsters
// ---EnterBattle

enum PlayerType
{
	PT_Knight = 1,
	PT_Archer = 2,
	PT_Mage = 3,
};

enum MonsterType
{
	MT_Slime = 1,
	MT_Orc = 2,
	MT_Skeleton = 3,
};

struct StatInfo
{
	int hp = 0;
	int attack = 0;
	int defence = 0;
};

void EnterLobby();
void PrintMessage(const char* msg);
void CreatePlayer(StatInfo* playerInfo);
void PrintStatInfo(const char* name, const StatInfo& info);
//PrintStatInfo( const StatInfo info) 이렇게 해줘도 되긴 된다. 그런데 이렇게 하면 데이터의 복사가 일어나서 StatInfo 구조체 데이터가 많아지게 되면 성능적으로 안좋은 영향을 주게 된다.
//그리고 출력으로만 사용하기 때문에 * 대신에 &을 사용했다.

void EnterGame(StatInfo* playerInfo);
void CreateMonsters(StatInfo monsterInfo[], int count);
bool EnterBattle(StatInfo* playerInfo, StatInfo* monsterInfo);

int main()
{
	srand((unsigned)time(nullptr));
	EnterLobby();
	return 0;
}

void EnterLobby()
{
	while (true)
	{
		PrintMessage("로비에 입장했습니다.");


		StatInfo playerInfo;
		CreatePlayer(&playerInfo);
		PrintStatInfo("player", playerInfo);

		EnterGame(&playerInfo);
	}
}

void PrintMessage(const char* msg)
{
	cout << "***************************" << endl;
	cout << msg << endl;
	cout << "***************************" << endl;
	

}

void CreatePlayer(StatInfo* playerInfo)
{
	bool ready = false;

	while (ready == false)
	{
		PrintMessage("캐릭터 생성창");
		PrintMessage("[1] 기사 [2] 궁수 [3] 법사");
		cout << "> ";

		int input;
		cin >> input;
		
		switch (input)
		{
		case PT_Knight:
			playerInfo->hp = 100; // (*playerInfo).hp = 100; 이거랑 같다.
			playerInfo->attack = 10;
			playerInfo->defence = 5;
			ready = true;
			break;
		case PT_Archer:
			playerInfo->hp = 80; 
			playerInfo->attack = 15;
			playerInfo->defence = 3;
			ready = true;
			break;
		case PT_Mage:
			playerInfo->hp = 50; 
			playerInfo->attack = 25;
			playerInfo->defence = 1;
			ready = true;
			break;  // case문에서 break대신 return; 을 사용해주면 함수를 나간다.
			//만약 이게 보기 싫다면 bool 설정해줘서 나가는 식으로 해준다. 
		}

	}
}

void PrintStatInfo(const char* name, const StatInfo& info)
{
	cout << "***********************" << endl;
	cout << name << "HP = " << info.hp << " ATT= " << info.attack << " DEF= " << info.defence << endl;
	cout << "***********************" << endl;
}

void EnterGame(StatInfo* playerInfo)
{
	const int MONSTER_COUNT = 2;
	PrintMessage("게임에 입장했습니다.");

	while (true)
	{
		StatInfo monsterInfo[MONSTER_COUNT];
		CreateMonsters(monsterInfo, MONSTER_COUNT);

		for (int i = 0; i < MONSTER_COUNT; i++)
			PrintStatInfo("Monster", monsterInfo[i]);

		PrintStatInfo("Player", *playerInfo);

		PrintMessage("[1] 전투 [2] 전투 [3] 도망");
		int input;
		cin >> input;

		if (input == 1 || input == 2)
		{
			int index = input - 1;
			bool victory = EnterBattle(playerInfo, &(monsterInfo[index]));
			if (victory == false)
				break;
		}
	}

}

void CreateMonsters(StatInfo monsterInfo[], int count)
{
	for (int i = 0; i < count; i++)
	{
		int randValue = 1+rand() % 3;

		switch (randValue)
		{
		case MT_Slime:
			monsterInfo[i].hp = 30;
			monsterInfo[i].attack = 5;
			monsterInfo[i].defence = 1;
			break;
		case MT_Orc:
			monsterInfo[i].hp = 40;
			monsterInfo[i].attack = 8;
			monsterInfo[i].defence = 2;
			break;
		case MT_Skeleton:
			monsterInfo[i].hp = 50;
			monsterInfo[i].attack = 15;
			monsterInfo[i].defence = 3;
			break;

		}
	}
}

bool EnterBattle(StatInfo* playerInfo, StatInfo* monsterInfo)
{
	while (true)
	{
		int damage = playerInfo->attack - monsterInfo->defence;
		if (damage < 0)
			damage = 0;

		monsterInfo->hp -= damage;
		if (monsterInfo->hp < 0)
			monsterInfo->hp = 0;

		PrintStatInfo("Monster", *monsterInfo);

		if (monsterInfo-> hp == 0)
		{
			PrintMessage("몬스터를 처치했습니다.");
			return true;
		}

		damage = monsterInfo->attack - playerInfo->defence;
		if (damage < 0)
			damage = 0;

		playerInfo->hp -= damage;
		if (playerInfo->hp < 0)
			playerInfo->hp = 0;
		
		PrintStatInfo("Player", *playerInfo);

		if (playerInfo->hp == 0)
		{
			PrintMessage("Game Over");
			return false;
		}
	}
}

 

포인터 연습문제 : strcmp, strlen etc

#include <iostream>
using namespace std;

// 오늘의 주제 : 연습 문제

// 문제 1) 문자열 길이를 출력하는 함수

int StrLen(const char* str)
{
	// str이라는 문자열의 길이를 반환하는 함수 만들어보자. 
	int i = 0;

	while (str[i] != '\0')
		i++;

	return i;
}

// 문제2) 문자열 복사 함수
char* StrCpy(char* dest, char* src)
{
	/*int i = 0;  

	while (src[i] != '\0')
	{
		dest[i] = src[i];
		i++;
	}
	dest[i] = '\0';

	return dest;*/
	//위 방법도 있고 아래방법도 해볼 수 있다.
	char* ret = dest;
	
	while (*src != '\0')
	{
		*dest = *src;
		dest++;
		src++;
	}

	*dest = '\0';

	return ret;
}

//문제 3) 문자열 덧붙이는 함수.
//주소1[H][e][l][l][o][W][o][r][l][d][\0][][][][][][][][][]
//주소2[W][o][r][l][d][\0][][][][][][][][][][][][]
char* StrCat(char* dest, char* src)
{
	/*int len = StrLen(dest);

	int i = 0;
	while (src[i] != '\0')
	{
		dest[len + i] = src[i];
		i++;
	}
	dest[len + i] = '\0';

	return dest;*/
	// 위 말고 포인터 이용한 다른 방법

	char* ret = dest;

	while (*dest)
		dest++;

	while (*src)
		*dest++ = *src++;

	*dest = '\0';
	return ret;
}
// 문제4) 두 문자열을 비교하는 함수
int StrCmp(char* a, char* b)
{
	int i = 0;

	while (a[i] != '\0' || b[i] != '\0')
	{
		if (a[i] > b[i])
			return 1;
		if (a[i] < b[i])
			return -1;
		i++;
	}
	return 0;

}

// 문제5) 문자열을 뒤집는 함수
void ReverseStr(char* str)
{
	int len = StrLen(str);

	for (int i = 0; i < len / 2; i++)
	{
		int temp = str[i];
		str[i] = str[len - 1 - i];
		str[len - 1 - i] = temp;
	}
}



#pragma warning(disable: 4996) // 오류 발생 넘어가준다. 
int main()
{
	const int BUF_SIZE = 100;

	//[H][e][l][l][o][\0][][][][[][][][][][][][][][]
	char a[BUF_SIZE] = "Hello";

	int len = sizeof(a); // BUF_SIZE 크기를 출력해준다. 내용물 크기 X 
	cout << len << endl;
	
	len=strlen(a);
	cout << len << endl; // 문자열의 크기가 나온다. 

	len = StrLen(a);
	cout << len << endl; // 문자열의 크기가 나온다. 

	char b[BUF_SIZE];
	//strcpy(b, a); // #pragma warning(disable: 4996) 이거 없으면 오류발생한다.
	
	//StrCpy(b, a);

	//strcat(a, b);
	
	//StrCat(a, b);
	char c[BUF_SIZE] = "Hello";
	if (a == c)
		cout << "same" << endl;
	else
		cout << "different" << endl;
	//바구니 값 자체는 주소값만 가지고 있기 때문에 다르다고 나온다. 

	int value = strcmp(a, b);
	cout << value << endl;
	value = StrCmp(a, b);
	cout << value << endl;
	ReverseStr(a);
	cout << a << endl;

	return 0;
}

 

달팽이문제

#include <iostream>
using namespace std;
#include <iomanip>

// 오늘의 주제 : 달팽이 문제
 
const int MAX = 100;
int board[MAX][MAX] = {};
int N;

void PrintBoard()
{
	for (int y = 0; y < N; y++)
	{
		for (int x = 0; x < N; x++)
		{
			cout << setfill('0') << setw(2) << board[y][x] << " ";
		}
		cout << endl;
	}
}

enum DIR
{
	RIGHT = 0,
	DOWN = 1,
	LEFT = 2,
	UP = 3,
};

bool CanGo(int y, int x)
{
	if (y < 0 || y >= N)
		return false;
	if (x < 0 || x >= N)
		return false;
	if (board[y][x] != 0)
		return false;
	return true;
}
void SetBoard()
{

	int dir = RIGHT;
	int num = 1;
	int y = 0;
	int x = 0;

	while (true)
	{
		board[y][x] = num;

		if (num == N * N)
			break;

		int nextY;
		int nextX;

		switch (dir)
		{
		case RIGHT:
			nextY = y;
			nextX = x + 1;
			break;
		case DOWN:
			nextY = y + 1;
			nextX = x;
			break;
		case LEFT:
			nextY = y;
			nextX = x - 1;
			break;
		case UP:
			nextY = y - 1;
			nextX = x;
			break;
		}

		if (CanGo(nextY, nextX))
		{
			y = nextY;
			x = nextX;
			num++;
		}
		else
		{
			switch (dir)
			{
			case RIGHT:
					dir = DOWN;
					break;
			case DOWN:
					dir = LEFT;
					break;
			case LEFT:
					dir = UP;
					break;
			case UP:
					dir = RIGHT;
					break;

			}

		}

	}

}

void SetBoard1() //위에 SetBaord()를 더 간략하게 만들 수 있다.
{

	int dir = RIGHT;
	int num = 1;
	int y = 0;
	int x = 0;

	int dy[] = {0, 1, 0, -1};
	int dx[] = { 1, 0, -1, 0 };

	while (true)
	{
		board[y][x] = num;

		if (num == N * N)
			break;

		int nextY = y + dy[dir];
		int nextX = x + dx[dir];

		if (CanGo(nextY, nextX))
		{
			y = nextY;
			x = nextX;
			num++;
		}
		else
		{
			dir = (dir + 1) % 4;
		}
	}
}


int main()
{
	cin >> N;

	SetBoard();
	PrintBoard();
	

	return 0;
}

 

 

 

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

object  (0) 2022.03.27
  (0) 2022.03.20
함수  (0) 2022.03.10
코드의 흐름제어  (0) 2022.03.10
C++ 입문 데이터 갖고 놀기  (0) 2022.03.09