C++

함수

레나19 2022. 3. 10. 20:21

함수 기초

#include <iostream>
using namespace std;

//오늘의 주제 : 함수 (프로시저, 메소드, 루틴 (언어마다 부르는 이름이 다르다.)

/*

함수는 input으로 무엇을 받고, output으로 무엇을 출력할지 정해준다.
반환타입, 함수이름(인자타입, 매개변수)

{
함수내용

return ~~~;
}

*/

void PrintHelloWorld() 
{
	cout << "Hello World" << endl;
	return; // return은 함수에서 빠져나간다. 그리고 반환하는게 없으면(void) return; 이러면 된다.
}

void PrintNumber(int number)
{
	cout << "넘겨주신 숫자는 " << number << "입니다." << endl;
}

//2를 곱하는 함수 만들어보자
//input : int / output : int

// 두 숫자를 곱해서 결과물을 뱉는 함수를 만들어보자.
//input : int, int / output : int

int MultiplyBy2(int a)
{
	int result = a * 2;
	return result;
	// or return a*2;
}

int MultiplyBy(int a, int b)
{
	int c = a + b;
	return c;
}

int main()
{
	int result = MultiplyBy2(3);
	PrintNumber(2);
	PrintHelloWorld();
	PrintNumber(result);
	result = MultiplyBy(3, 5);
	PrintNumber(result);
}

 

스택 프래임

디버깅시 -> 디어셈블리어로 함수 호출부분을 보면 밑의 그림처럼 매개변수-> 변환주소값-> 지역변수 순으로 스텍메모리에 올라와있는 것을 확인할 수 있다.

코드에서 MultiplyBy에 브레이크포인트를 걸어두고 디버깅 실행해서 디어셈블리어로 확인하여주었다. push로 스택메모리에 올라가고 call Multiply해주고 를 확인한다.

레지스터로 ESP(SP)를 복사해서 메모리영역에서 붙여넣기하여 그거를 보면서 F11로 함수안 디셈블리어를 하나하나 실행시켜보면 그 메모리에 3이라는 a라는 값 b라는 값이 저장되는 거를 확인할수가 있다. call MultiplyBy를 하여 return되는게 저장되는거 다 확인할 수 있다. 메모리에는 우클릭해서 현재 변수 크기와 맞춰주면 더 편하게 볼 수 있다. int면 4바이트 이런 식으로. 

1) 함수에서  작성한 지역변수나, 메인함수속 내부함수는 해당 코드가 실행될떄, Stack.push로 스택에 넣어둠과 동시에
함수와 지역변수들은 스택메모리를 차지하는것이고

=> 지역변수는 스택에 들어가지만 함수 OpCode는 코드 영역에 위치해 있습니다.
push pop 비슷하게 하긴 하지만, 메모리를 항상 다시 '풀어주는' 개념이라기 보다는
bp, sp 위치만 변화시켜 유효 범위를 바꿔줍니다.

2)  bp는 현재 스택영역에서의 기준점 sp는 현재 스택의 위치로 이해하였습니다. 메인함수에서 내부함수로 들어갈떄 
내부함수가 끝나고 다시 메인함수에서 나머지 진행을위해 push로 bp(현재 위치)를 넣고 그다음 내부함수 진행을 위해 현재 스택위치 sp를 bp에 넣어서 내부함수 진행을 하는것으로 이해했습니다
(내부함수 끝날떄는 처음 push했던 bp로 돌아오는것)

=>
네! 이 부분이 이해가 굉장히 어려운데 그게 맞습니다.
bp를 push하는 것은 [헨젤과 그레텔]에서 빵 뿌스러기를 생각하면 됩니다.
참고로 해킹 분야에서는 이 bp나 ip 쪽 레지스터를 조작하기 위해 부단한 노력을 합니다.

 

디셈블리어에서 들어가서 F11누르면 하나씩 코딩되서 입력되는 것을 확인할 수 있다.

 

 

지역변수와 값 전달

 

ESP와 &amp;amp;localValue 주소를 미루어보아 둘이 비슷한게 스텍영역메모리에 잡힌다는 것을 확인할 수 있다. 그리고 localValue와 globalValue의 주소가 완전히 다른 것으로 보아 완전히 다른 메모리영역에 올라간다는 것을 알 수 있다.

#include <iostream>
using namespace std;

//오늘의 주제 : 지역 변수와 값의 전달

//전역변수 : 어느 함수에서든 다 접근가능하다. 전역변수는 데이터 영역에 들어간다.
// 참고) 초기화 여부에 따라서 const 등. .rodata  .bss  .data에 들어간다.
//지역변수 : 내 땅에서만 접근가능한게 지역변수. 지역변수는 스텍 영역에 들어간다.
int globalValue = 0;

void Test()
{
	cout << "전역 변수의 값은 : " << globalValue << endl;
	globalValue++;
}

void increaseHp(int hp)
{
	hp = hp + 1;
}

int main()
{

	int hp = 1;
	cout << "increase 호출 전 : " << hp << endl;
	increaseHp(hp);
	cout << "increase 호출 후 : " << hp << endl;
	//아무 변화 없다. 왜냐? increaseHp안에 들어가는거는 지역변수 hp가 아닌 increaseHp(1) 이니까.
	//다른 지역의 함수임. 

	cout << "전역 변수의 값은 : " << globalValue << endl;
	globalValue++;

	Test();

	//지역변수
	int localValue = 0;

	return 0;
	
}

 

호출 스텍

#include <iostream>
using namespace std;

//오늘의 주제 : 호출 스텍
//C++ 은 순서대로 코딩을 해나간다.
// 함수 선언을 안해주면 Func2(1, 2)가 Func1() 이전에 선언이 안되어있기 때문에 위에서 찾지를 못해서 에러가 난다.
//그래서 함수 선언을 아래와 같이 해준다. 위와 같은 에러를 피해주기 위해서
//함수 안에 함수를 호출한다 - 인셉션같다.
void Func1();
void Func2(int a, int b);
void Func3(float a);


void Func1()
{
	cout << "Func1" << endl;
	Func2(1, 2);
}

void Func2(int a, int b)
{
	cout << "Func2" << endl;
	Func3(10);
}

void Func3(float a)
{
	cout << "Func3" << endl;
}

int main()
{
	cout << "main" << endl;
	Func1();

	return 0;
}
//디버깅시 어디서 온 함수인지 모르면 브레이크포인트 잡고 call stack에서 더블클릭하면 어디서 왔는지 알 수 있다.

디버깅시 call stack을 볼 줄 알아야 하고 F10과 F11을 통해 호출스텍 파악하는 거를 잘 익혀야 한다.

 

함수마무리

#include <iostream>
using namespace std;

//오늘의 주제 : 함수 마무리
//오버로딩 (중복 정의 : 함수 이름의 재사용)

int Add(int a, int b)
{
	int result = a + b;
	return result;
}

float Add(int a, float b)
{
	float result = a + b;
	return result;
}
/*
에러나는 경우. 반환하는 int Add(int a, int b)와 같아서 에러
}float Add(int a, int b)
{
	float result = a + b;
	return result;
}

*/

void SetPlayerInfo(int hp, int mp, int attack, int guildId=0)
{

}

//스텍오버플로우

int Factorial(int n)
{
	if (n <= 1)
		return 1;

	return n * Factorial(n - 1);
}

int main()
{
	SetPlayerInfo(100, 40, 10);

	//  int result = Factorial(1000000);  //오버플로우발생을 볼 수 있다.
	int result = Factorial(10);

}
//디버깅시 어디서 온 함수인지 모르면 브레이크포인트 잡고 call stack에서 더블클릭하면 어디서 왔는지 알 수 있다.

TextRPG #1

#include <iostream>
using namespace std;

// 오늘의 주제 : TextRPG

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

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

int playerType;
int hp;
int attack;
int defence;

int monsterType;
int monsterHp;
int monsterAttack;
int monsterDefence;

//위의 정보를 구조체로 묶어서 해주면 더 보기 좋다. struct PlayerInfo, MonsterInfo 
// 묶어주면 더 알아보기 편하다. 예를 들어 player.hp / player.attack 이런 식으로.  
void EnterLobby();
void SelectPlayer();
void EnterField();
void CreateRandomMonster();
void EnterBattle();

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

void EnterLobby()
{
	while (true)
	{
		cout << "-------------------" << endl;
		cout << "로비에 입장했습니다!" << endl;
		cout << "--------------------" << endl;

		//플레이어 직업 선택
		// 이 안에서 다 구현하기보다는 따로 함수를 만들어서 해주는게 여러가지로 낫다.
		// 각기 다른 기능을 할 때는 , 각기 다른 함수로 뺴주는 게 관리 차원에서 낫다.
		SelectPlayer();

		cout << "-------------------" << endl;
		cout << "(1) 필드 입장 (2) 게임 종료" << endl;
		cout << "--------------------" << endl;

		int input;
		cin >> input;

		if (input == 1)
		{
			EnterField();

		}
		else
		{
			return; //else시 return으로 인해 EnterLobby를 빠져나온다.
		}
	}
}

void SelectPlayer()
{
	while(true)  //while문을 이렇게 해주면 직업을 고를때까지 계속 반복해서 실행시켜준다.
	{
	cout << "-------------------" << endl;
	cout << "직업을 골라주세요!" << endl;
	cout << "(1) 기사 (2) 궁수 (3) 법사" << endl;
	cout << ">";

	cin >> playerType;

	if (playerType == PT_Knight)
	{
		cout << "기사 생성중... !" << endl;
		hp = 150;
		attack = 10;
		defence = 5;
		break;
	}
	else if (playerType == PT_Archer)
	{
		cout << "궁수 생성중... !" << endl;
		hp = 100;
		attack = 15;
		defence = 3;
		break;

	}
	else if (playerType == PT_Mage)
	{
		cout << "법사 생성중... !" << endl;
		hp = 80;
		attack = 25;
		defence = 0;
		break;
	}
	}
}

void EnterField()
{
	while (true)
	{
		cout << "-------------------" << endl;
		cout << "필드에 입장했습니다!" << endl;
		cout << "--------------------" << endl;

		cout << "[PLAYER] HP : " << hp << "/ ATT : " << attack << "/ DEF : " << defence << endl;
		CreateRandomMonster();

		cout << "-------------------" << endl;
		cout << "(1) 전투 (2) 도주" << endl;
		cout << "> " << endl;

		int input;
		cin >> input;

		if (input == 1)
		{
			EnterBattle();
			if (hp == 0)
				return;
		}
		else
		{
			return;
		}

		
	}

}

void CreateRandomMonster()
{

	monsterType = 1 + (rand() % 3);   // random 할 때 srand(time(0)); 을 넣어준다.랜덤시드설정

	switch (monsterType)
	{
	case MT_Slime:
		cout << "슬라임 생성중... ! (HP:15 / ATT:5 / DEF:0)" << endl;
		monsterHp = 15;
		monsterAttack = 5;
		monsterDefence = 0;
		break;
	case MT_Orc:
		cout << "오크 생성중... ! (HP:40 / ATT:10 / DEF:3)" << endl;
		monsterHp = 40;
		monsterAttack = 10;
		monsterDefence = 3;
		break;
	case MT_Skeleton:
		cout << "해결 생성중... ! (HP:80 / ATT:15 / DEF:5)" << endl;
		monsterHp = 80;
		monsterAttack = 15;
		monsterDefence = 5;
		break;
	}

}
void EnterBattle()
{
	while (true)
	{
		int damage = attack - monsterDefence;
		if (damage < 0)
			damage = 0;

		//선빵
		monsterHp -= damage;
		if (monsterHp < 0)
			monsterHp = 0;

		cout << "몬스터 남은 체력 : " << monsterHp << endl;
		if (monsterHp == 0)
		{
			cout << "몬스터를 처치했습니다!" << endl;
			return;
		}

		damage = monsterAttack - defence;
		if (damage < 0)
			damage = 0;

		//반격
		hp -= damage;
		if (hp < 0)
			hp = 0;

		cout << "플레이어 남은 체력 : " << hp << endl;
		if (hp == 0)
		{
			cout << "당신은 사망했습니다... GAME OVER" << endl;
			return;
		}
	}
}

//ctrl m + o 모든 적어둔 게 접혀진다.

 

 

 

 

 

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

  (0) 2022.03.20
포인터  (0) 2022.03.14
코드의 흐름제어  (0) 2022.03.10
C++ 입문 데이터 갖고 놀기  (0) 2022.03.09
Part1. 어셈블리언어 입문  (0) 2022.03.07