C++

11. Function (2)

Kelvin의 게임개발 2024. 1. 26. 00:01
728x90
반응형

1. Passing Arrays to Function

 

함수의 인자로 배열을 넘길 수 있다

 

반환형 함수이름(타입 배열이름[]);로 함수를 선언하고 호출 시 인자로 배열 이름을 넣어준다 함수이름(배열이름);

 

배열의 이름은 배열 0번째 index의 주소 (배열의 시작주소)를 의미한다 따라서 인자로 배열을 넘기게 되면 기존의 일반 value를 인자로 넘길때와 달리 사본이 전달되지 않고 배열의 시작 주소가 전달되게 된다

 

따라서 배열을 인자로 넘기게 되면 함수 내부에서 원본 수정이 가능하기 때문에 조심해야 한다 이때 const를 매개변수 앞에 선언해주면 원본 수정이 불가능한 rodata가 된다
 
인자로 배열의 이름 (배열의 시작주소)를 넘기기 때문에 함수는 배열의 크기를 알 수 없다 따라서 배열의 크기도 같이 전달해야 반복이 가능하다

void PrintArray(int Numbers[]); //함수 선언시 매개변수로 배열 선언

int main()
{
	int MyNumbers[]{ 1,2,3,4,5 };

	PrintArray(MyNumbers); //MyNumbers배열의 시작 주소가 전달된다 (사본이 아닌)
	PrintArray(MyNumbers, 5); //MyNumbers배열의 시작 주소, 배열의 크기를 같이 전달
}

void PrintArray(int Numbers[])
{
	//Numbers의 크기를 알 수 없다 (인자로 전달받은건 Numbers의 시작 주소이기 때문)
}

void PrintArray(int Numbers[], int ArraySize) //배열의 크기를 같이 넘겨주어야 반복이 가능
{

}

void PrintArray(const int Numbers[]) //매개변수에 const가 붙어 원본 수정이 불가능해짐
{

}

 

 

2. Pass by Reference

 

함수의 매개변수로 배열을 넘기게 되면 배열의 원본 데이터에 접근이 가능하다 (배열의 이름은 배열의 시작 주소이기 때문)

 

이렇게 배열뿐 아니라 모든 타입의 데이터를 매개변수로 넘겨 함수 내부에서 원본 데이터에 접근하고 싶을 경우 참조 타입을 사용하면 된다

 

참조타입으로 넘겨받은 인자를 변경하게 되면 넘겨준 원본 데이터가 변경된다 -> PassByRef

 

참조타입 변수는 자료형& 변수명;로 선언이 가능하다, 참조타입 변수에는 같은 자료형의 변수를 넣어주면 된다 (상수는 X)
 
함수의 매개변수를 참조타입으로 지정했을 때 장점

1. 인자로 넘긴 데이터의 원본값 변경이 가능하다

2. 인자로 넘긴 데이터의 사본을 만들지 않는다 (비용감소, 데이터가 크면 사본 생성할 때 생각보다 더 큰 비용이 소모된다)

 

참조타입은 넣어준 변수의 별칭이라 생각하면 편하다 ex) int& a = b; 하면 b의 별칭을 a라고 사용한다고 생각하면 됨 (a는 이름만 다를뿐 b다라고 생각하자)
 
참조타입을 인자로 넘겼을 때 원본값을 변경하고 싶지 않다면 const 자료형& 변수명;으로 넘겨주면 된다 (rodata로 만들어 수정 불가)

void ScaleNum(int& num);
void ScaleNum1(const int& num);

int main()
{
	int Num = 300;

	ScaleNum(Num); //원본값 Num이 1000으로 변경됨
}

void ScaleNum(int& num) //매개변수를 참조타입으로 지정
{
	if (num > 100)
	{
		num = 1000; //참조타입 변수를 수정했기 때문에 원본값이 변경된다
	}
}

void ScaleNum(const int& num) //매개변수를 const 참조타입으로 지정
{
	if (num > 100)
	{
		num = 1000; //const 참조타입 변수를 수정하려고 했기 때문에 컴파일 에러 발생
	}
}

 

 

3. Scope Rules

 

C++에서는 Scope Rule을 따라 언제 어디서 변수가 사용될 수 있는지 결정한다

 

{ }안에 선언된 변수는 지역변수이다({ } 안에서만 유효하다)
따라서 함수의 { }안에 선언된 변수는 지역변수이고 함수가 호출될 때 유효하고 함수가 종료될 때 유효하지 않게된다

 

정리하면 지역변수는 함수 호출간에 보존되지 않는다, { }안에서만 유효하기 때문
하지만 함수 호출간에 보존되는 변수 유형이 있다 이는 static변수이다

 

static 자료형 변수명;으로 선언이 가능하다

 

static변수의 life time은 프로그램의 life time과 같다 (프로그램이 시작할 때 static변수는 유효하고 프로그램이 종료되는 순간 static변수는 유효하지 않게 된다)

지역변수는 함수가 호출될 떄 마다 매번 초기화 되지만 static변수는 1번만 초기화 된다 따라서 초기화 되지 않은 이전값을 알고 싶을 때 사용하면 유용하다

 

static변수는 전역변수와 같은 방식으로 처리된다 하지만 지역변수로 선언 시 해당 범위는 선언된 { }안이다 이는 전역변수 처럼 값이 유지된다는 장점과 어디에서든 수정되어 이슈를 발생시킬수 있다는 전역변수의 단점을 해소한 기능이다
 
{ }밖에 선언된 변수는 전역변수이다 따라서 함수의 { }밖에 선언된 변수는 전역변수이다

전역변수는 언제 어디서든 사용이 가능하다

전역상수는 괜찮지만 전역변수 사용은 지양해야 한다 (문제가 생겼을 때 디버깅이 힘들며 관리가 힘들다)

void StaticLocalExample();

int num{ 300 }; //{}밖에 선언된 전역변수

int main()
{
	//main() { }에 선언된 지역변수들
	int num{ 100 };
	int num1{ 500 };

	cout << num << endl; //100이 출력된다

	{ //새로운 scope 생성
		int num{ 200 }; // main() { } 안에있는 { }에 선언된 지역변수 -> 해당 { }에서만 유효하기 때문에 위의 변수와 이름이 겹쳐도 상관없다

		cout << num << endl; //{ }안에 선언된 지역변수를 다른 같은 이름의 변수보다 우선 호출
		cout << num1 << endl; //{ }밖의 변수에 접근 가능
	}

	StaticLocalExample(); //500 1500 출력
	StaticLocalExample(); //1500 2500 출력
}

void GlobalVar()
{
	cout << num << endl; //매개변수도 없고 지역변수로 아무것도 선언되지 않았지만 전역변수로 선언된 num 300이 출력된다 (전역변수는 언제 어디서든 접근이 가능하기 때문)
}

void StaticLocalExample()
{
	static int num{ 500 }; //static변수이기 때문에 함수 호출시마다 초기화 되지 않는다 (이전 결과값을 유지한다)

	cout << num << endl;

	num += 1000;

	cout << num << endl;
}

 

 

4. How Do Function Calls Work?

 

함수 호출은 어떻게 작동할까? 함수는 함수 호출 스택 (Function Call Stack)을 사용한다

 

함수 호출 스택은 LIFO 방식이다 (Last In First Out) -> 다른 데이터를 꺼내려면 마지막에 추가된 데이터를 먼저 꺼내야 한다 (책을 쌓고 밑에 책을 꺼내는 방식과 동일)

 

데이터를 추가하게 되면 push, 데이터를 제거하게 되면 pop이라고 한다
 
C++에서 함수를 호출하며 쌓이는 데이터들을 StackFrame이라 부른다
StackFrame에는 매개변수, 지역변수, 함수 반환 주소값 데이터들이 들어있다 (함수 반환 주소값이란 함수 호출이 끝난 뒤 돌아갈 주소값을 의미한다)

 

함수가 호출될 때 마다 StackFrame 데이터들이 생성되고 Function Call stack에 push된다

 

함수가 종료되면 Function Call Stack에서 해당 StackFrame 데이터를 pop한다
Call Stack은 LIFO방식이기 때문에 데이터가 한번에 중간에 들어가거나 나올 수 없다
 


Call Stack의 크기는 유한하다 (Finite) 따라서 너무 많은 StackFrame이 push되면 Stack Overflow가 발생할 수 있다 (Crash발생)
 
 메모리 구조를 잘 알고 있다면 이해하기 쉽다
 메모리는 Code Area, Static Variable(Data Area), Stack Area, Heap Area 영역으로 이루어져 있다
 위에서 설명한 Function Call Stack영역은 메모리상에서 Stack Area에 해당한다

int Func1(int a, int b);
void Func2(int& x, int y, int z);

int main()
{
	//main()호출 StackFrame에 x, y, z 지역변수 선언 데이터가 들어감
	int x{ 10 };
	int y{ 20 };
	int z{};

	z = Func1(x, y); //Func1()호출 StackFrame에 함수 반환 주소값 들어감

	cout << z << endl; //60출력

	return 0;
}

int Func1(int a, int b) //Func1()호출 StackFrame에 매개변수 a, b 데이터가 들어감
{
	int result{}; //Func1()호출 StackFrame에 result 지역변수 선언 데이터가 들어감

	result = a + b;

	Func2(result, a, b); //Func2()호출 StackFrame에 함수 반환 주소값 들어감

	return result;
} //함수 종료 시 Call Stack에서 Func1()호출 StackFrame pop

void Func2(int& x, int y, int z) //Func2()호출 StackFrame에 매개변수 x, y, z 데이터가 들어감
{
	x += y + z;
} //함수 종료 시 Call Stack에서 Func2()호출 StackFrame pop

 

VS의 Call Stack에서 함수 호출 과정을 자세히 디버깅 할 수 있다

 

 

5. Inline Function

 

종종 아주 간단한 함수를 호출하게 될 때 Call Stack에서의 함수 호출 처리과정(StackFrame 처리 (Function Overhead))이 함수 실행 시간보다 길어질 경우가 생길수도 있다 이럴때 사용할 수 있는것이 바로 inline Function이다

 

inline Function은 Function Overhead를 피할 수 있다
inline함수는 함수 호출이 함수 자체의 내용 복사본으로 대체되기 때문에 Function Overhead를 피할 수 있는것이다 -> 일반 함수 호출보다 더 빠르다 하지만 여러번 Inline하게 되면 여러번 코드를 복사하게 되고 더 큰 Binary가 될 수 있다
 
현대의 컴파일러는 inline으로 성능이 향상될 것 같은 함수는 자동으로 inline처리 한다
 
inline 반환형 함수이름(매개변수) { }로 inline함수를 만들 수 있다 (함수 선언/정의 앞에 inline 키워드를 붙히면 됨)

inline int AddNumbers(int a, int b)
{
	return a + b;
}

 

 

6. Recursive Function

 

Recursive Function (재귀함수)는 스스로 혹은 간접적으로 자기자신을 호출하는 함수이다
ex) 수학에서는 팩토리얼, 피보나치 수열 등, 검색이나 정렬에서는  binary search, serach tree등 재귀 함수를 사용하는 곳은 다양하다

 

재귀함수를 만들고 사용할 때 중단이 아주 중요하다 (무한루프에 빠져 Stack Overflow가 발생할 수 있다)

 

재귀 호출이 될 때 마다 해당 함수호출에 맞는 StackFrame이 생성되고 call stack에 push되게 된다

(너무 과한 재귀 호출은 프로젝트 처리 속도를 느리게 한다)

//Factorial
//n! = n * (n-1)!;
unsigned long long Factorial(unsigned long long Input)
{
	if (Input == 0)
	{
		return 1;
	}
	else
	{
		return Input * Factorial(Input - 1); //재귀호출
	}
}

//Fibonacci
//Fib(n) = Fib(n-1) + Fib(n-2);
unsigned long long Fibonacci(unsigned long long Input)
{
	if (Input <= 1)
	{
		return Input;
	}
	else
	{
		return Fibonacci(Input - 1) + Fibonacci(Input - 2); //재귀호출
	}
}

 

 

 

728x90
반응형

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

13. Pointer And Reference (2)  (62) 2024.02.14
12. Pointer And Reference, Memory Allocate (1)  (107) 2024.01.30
10. Function (1)  (127) 2024.01.25
9. Character & String  (134) 2024.01.08
8. Control Program Flow (2)  (113) 2023.12.21