재귀적함수호출 Recursive Function Call

함수가 다시 자기자신 함수를 호출하는것

함수도 메모리에 올라가는것이기에 주소에 접근해 계속 호출이 가능하다.
종료하는 조건을 반드시 만들어야한다.
재귀적함수호출를 너무 많이하면 Stack Ove Flow가 발생한다.

#include <iostream>
#include <vector>

using namespace std;

void countDown(int count) {
	cout << count << endl;
	if (count != 0) {
		countDown(count - 1);
	}
}
int a = 1;

int sumTo(int sumto) {
	
	if (sumto <= 0) {
		return 0;
	}
	else if (sumto <= 1) {
		return 1;
	}	
	else {
		int sum_minus_one = 0;
		sum_minus_one =	sumTo(sumto - 1);
		return sum_minus_one + sumto;
	}
}

int main()
{
	countDown(10);
	cout << "=========================" << endl;
	cout << sumTo << endl;
	//재귀호출하면 10에서 1씩 감소시키며 1이 나올때까지 반복한다.
	//1이 나오면 리턴값으로 하나씩 돌려준다.
	//1(else if(sumto <= 1)의 결과값)+2(sumto)
	//3은 그전에 호출된 sum_minus_one의 결과값으로 간다.
	//3 + 3, 6 + 4, 10 + 5 형식으로 계속 돌려준다.
	//cout << sumTo(10) << endl;
	cout << test(0) << endl;

	vector<int> v;
	v.reserve(10);
	v.push_back(45+10);
	v.push_back(36+9);
	v.push_back(28+8);
	v.push_back(21+7);
	v.push_back(15+6);
	v.push_back(10+5);
	v.push_back(6+4);//return 6을 받고 동작
	v.push_back(3+3);//return 3을 받고 동작
	v.push_back(1+2);//return 1을 받고 동작


	return 0;
}

동적할당메모리를 직접관리하는것보다 vector를 사용하는게 더 쉽다.

동적할당을 할때 new,delete하는게 오래걸린다.
new와 delete를 최소화하는것이 좋다.

vector는 더 작은쪽으로 resize를 하면 메모리를 반납하지않고 크기만 줄여서 보여준다.
size,capacity로 알아볼 수 있다.


reserve로 메모리의 용량을 미리 확보할 수 있다.
용량을 확보하고 출력해보면 확보된 메모리가 다 출력되지 않는다.
사용하는이유 : 메모리를 미리확보해서 new,delete를 줄인다.

#include <iostream>
#include <vector>

using namespace std;

int main()
{
	vector<int> v{ 1,2,3 };
	//size,capacity
	//capacity 는 실제로 메모리에 가지고 있는 갯수
	//size는 메모리에서 현재 사용하는 갯수
	cout << "=====================================" << endl;
	v.resize(2);
	for (int &a : v) {
		cout << a << endl;
	}
    //2가 출력된다.
	cout << v.size() << endl;
    //3이 출력된다.
	cout << v.capacity() << endl;

	//강제로 포인터로 변경해서 데이터가져오기
	//데이터가 남아있는걸 확인할 수 있다.
	int *ptr = v.data();
	cout << ptr[2] << endl;
	cout << "=====================================" << endl;
	v.reserve(1024);
	cout << v.capacity() << endl;
	//사용하고있는 데이터 1,2만 출력된다.
	for (int &a : v) {
		cout << a << endl;
	}
	cout << "=====================================" << endl;
	
	return 0;
}


vector를 stack처럼 사용하기
스택을 메모리에 쌓는다고 생각하면된다.
vector에 추가할때push를 제거할때 pop을 사용한다.

사용하는이유 reserve로 크기를 설정해놓으면 설정한 크기까진
new,delete를 계속 할 필요가 없다
reserve를 너무크게 설정하면 메모리가 낭비된다.

#include <iostream>
#include <vector>

using namespace std;

void printStack(const vector<int> &stack) {
	cout << "printStack" << endl;
	for (auto &a : stack) {
		cout << a << endl;
	}
	cout << "end" << endl;
}
int main()
{
	//push_back은 추가한다.
    //pop_back은 제거한다.
	vector<int> stack;
    //미리 메모리를 확보한다.
	stack.reserve(1024);
	stack.push_back(3);
	printStack(stack);
	stack.push_back(5);
	printStack(stack);
	stack.push_back(7);
	printStack(stack);
	stack.pop_back();
	printStack(stack);
	stack.pop_back();
	printStack(stack);
	stack.pop_back();
	printStack(stack);

	return 0;
}

'개발 소발 > 개발 C++(기초)' 카테고리의 다른 글

c++ 방어적프로그래밍 기초  (0) 2019.08.02
c++ 재귀적 함수호출  (0) 2019.08.01
c++ 스택Stack 힙Heap  (0) 2019.07.31
c++ 함수포인터 Function Pointers  (0) 2019.07.31
c++ 함수 오버로딩  (0) 2019.07.31

CPU와 메모리의 사용알기
스택Stack
힙 Heap

프로그램을 실행하면 운영체제는 메모리를 할당해준다.
메모리는 여러 구역으로 나뉜다.
코드 
-작성한 프로그램 메모리에 올라가있다. 
BSS
-0으로 초기화된 전역,정적변수가 저장된다.
DATA
-전역,정적변수가 저장된다.
스택
-Stack frame으로 순서대로 쌓인다.
-스택은 비교적 빠르다.
-스택은 빠르나 비교적 크기가 작다.

-동적메모리가 할당되면 힙에 저장된다.
-메모리 어느부분에 쌓이는지 알 수 없다.
-할당된메모리가 반납안되면 계속 쌓여 메모리 누수가 생긴다.

 

스택 알아보기

아래 소스에서 실행되는 순서대로 스택에 쌓이게된다.

 

1.Stack frame main(),int a,b

2.Stack frame first(),int x,y

3.Stack frame second(),int x

#include <iostream>

//BSS 파트 처음에 생겼다가 마지막에 사라진다.
int g_i = 0;

//3.Stack frame second(),int x
int second(int x) {
	return 2 * x;
}

//2.Stack frame first(),int x,y
int first(int x) {
	int y = 3;
	return second(x + y);
}

//1.Stack frame main(),int a,b
int main()
{
	using namespace std;
	int a = 1, b;
	b = first(a);
	cout << b << endl;

	return 0;
}

힙 알아보기

#include <iostream>

void initArray() {
	//메모리를 반납하지않으면 사용은 못하는데 메모리엔 할당되어있다.
	int *ptr2 = new int[1000];
}

int main()
{
	int *ptr = nullptr;
	//힙메모리에 동적할당된 주소를 가져온다.
	ptr = new int[1000000];
	//힙메모리에 할당된 메모리를 반납한다.
	delete[] ptr;
	//메모리반납해도 주소는 가지고있다 초기화해준다.
	ptr = nullptr;

	//메모리 누수 예
	initArray();

	return 0;
}

함수포인터Function Pointers

함수도 포인터다. 

즉, 함수도 메모리 주소를 가지고 있다.
함수를 실행하면 함수의 메모리주소를 찾아 실행한다.

int (*포인터명)(파라미터) = 함수명; 형식으로 생성할 수 있다.

함수포인터를 파라미터로 넣을 수 있다.

반복되는 로직이 있으면 포인터를 통해 다 분리해서 사용하는게 가능하다.


함수파라미터도 기본값이 설정가능하다.
함수는 이름 자체가 포인터라 &를 안넣어도 된다.

함수가 파라미터로 자주사용되면 계속쓰기 귀찮으면 typedef로 선언해서 사용할 수 있다.
#include <functional>을 사용해 더 간단히 사용할 수 있다.

 

#include <iostream>
#include <array>
#include <functional>

using namespace std;

int func() {
	return 5;
}

void printNumbers(const array<int, 10>& myArr,bool print_even) {
	for (int a : myArr) 
	{
		if (print_even && a % 2 == 0) {
			cout << a << " ";
		}
		else if (!print_even && a % 2 == 1) {
			cout << a << " ";
		}
	}
	cout << endl;
}

bool isEven(const int& number) {
	
	if (number % 2 == 0) return true;
	else return false;
}
bool isOdd(const int& number) {
	if (number % 2 != 0) return true;
	else return false;
}

void printNumbersFunP(const array<int, 10>& myArr, bool (*check_fcn)(const int&)) {
	for (int a : myArr)
	{
		if (check_fcn(a)==true) {
			cout << a << " ";
		}
	}
	cout << endl;
}

void printNumbersFun(const array<int, 10>& myArr, function<bool(const int&)> funct) {
	for (int a : myArr)
	{
		if (funct(a) == true) {
			cout << a << " ";
		}
	}
	cout << endl;
}

int main()
{
	//함수의 주소가 출력된다.
	cout << func << endl;
	
	//함수포인터 설정하기
	int (*funcPtr)() = func;

	//func()함수와 메모리 주소도 같고 같이 사용한다.
	cout << funcPtr << " " << funcPtr() << " " << &funcPtr << endl;

	//함수포인터 사용하기전 홀수 짝수 출력 함수
	std::array<int, 10> myArr{ 1,2,3,4,5,6,7,8,9,10 };
	printNumbers(myArr, true);
	printNumbers(myArr, false);

	//함수포인터 파라미터로 사용하기(함수명)
	printNumbersFunP(myArr, isEven);
	printNumbersFunP(myArr, isOdd);

	std::function<bool(const int&)> funct = isEven;
	//fuction사용해서 함수포인터 파라미터로 이용하기(포인터명)
	printNumbersFun(myArr, funct);
	funct = isOdd;
	printNumbersFun(myArr, funct);

	return 0;
}

함수오버로딩 Fuction Overloading

동일한 이름의 함수를 여러개 만드는 것을 말한다.

 

매개변수 = parameter


일반적으로 들어오는 매개변수가 다른데 수행하는기능이 비슷한경우 사용한다.
전혀다른 기능을 수행할 수도 있긴하다.
반환형태가 달라도 이름이 같고 매개변수도 같으면 에러가 난다.
즉, 매개변수가 다르면 다른 함수로 판단한다.

매개변수가 없이 오버로딩하는 법은 반환값을 void로 선언하고 참조변수로 받는 방법이 있다.
단, 리턴받지않고 변수를 선언하고 인자로 넣어야하는 단점이 있다.

void add(int &a) {
	a = 30;
}


컴파일할때 어떤 함수를 사용할지 결정되어야한다.
또 인자를 정확히 표현해주어야한다.

	//double형
    cout << typeid(add(1.3, 1.5)).name() << " " << add(1.3, 1.5) << endl;
	//float 형
    cout << typeid(add(1.3f, 1.5f)).name() << " " << add(1.3, 1.5) << endl;

오버로딩 예

#include <iostream>

using namespace std;

void add(int &a) {
	a = 30;
}

int add(int x, int y) {
	return x + y;
}

int add(unsigned int x, unsigned int y) {
	return x + y;
}
double add(double x, double y) {
	return x + y;
}

float add(float x, float y) {
	return x + y;
}

int main()
{
	cout << typeid(add(1, 3)).name() << " " << add(1, 3) << endl;
	cout << typeid(add(1u, 3u)).name() << " " << add(1, 3) << endl;
	cout << typeid(add(1.3, 1.5)).name() << " " << add(1.3, 1.5) << endl;
	cout << typeid(add(1.3f, 1.5f)).name() << " " << add(1.3, 1.5) << endl;

	cout  << (unsigned int)'a' << endl;

	return 0;
}

인라인함수 Inline Functions

사용법 : 함수앞에 inline을 붙여준다.

기존 함수호출시
1.함수를 호출한다.
2.함수를 호출하면 파라미터에 데이터를 복사한다.
3.함수의 로직을 실행하고 반환값을 반환해준다.

inline함수 호출시
inline함수를 사용하면 컴파일할때 로직을 바로 실행한다.

강제로 로직을 실행시키는게 아닌 컴파일러에게 권유?한다고 생각하면된다.
또, 최근 컴파일러는 inline이 없어도 논리적으로 inline이 낫다고 생각하면 그렇게 컴파일한다.

이런게 있다는것만 알아두자.

#include <iostream>

using namespace std;

inline int min(int x, int y) {
	return x > y ? y : x;
}

int main()
{
	cout << min(3,5) << endl;

	//inline함수는 아래 처럼 바로실행된다고 생각하면된다.
	cout << (3 > 5 ? 5 : 3) << endl;
	return 0;
}

반환값

return by value
리턴타입으로 선언된 자료형을 반납해준다.
단점은 변수가 여러번 생성된다.

return by address
리턴타입으로 포인터형식 즉 주소값을 받는다.
동적할당할때 사용할 수 있다.
단점, delete위치가 애매하다.
주소값만 받아오기에 안에 값이 다를 수 있다.

return by reference
변수를 선언해서 받으면 문제없이 받아올 수 있다.
받는쪽에서 참조변수로 받으면 문제가 될 수 있다.
메모리가 안정적으로 선언된 변수,자료형에서는 사용하기 용이하다.

반환값 여러개 받기
구조체를 선언해 여러개를 받는 방법이 있다.
단점은 함수하나를 만들때마다 구조체를 만들어줘야한다.
튜플tuple을 사용하는 법이있다.
get<0>(튜플명)으로 받아야하는 귀찮음이 있지만
c++ 17에선 auto로 받을 수 있다.

 

#include <iostream>
#include <tuple>

using namespace std;

int returnByValue(int a) {
	a += 4;
	cout << "value : " << &a << " a : " <<a << endl;
	return a;
}


int* returnByAddress(int size) {
	return new int[size];
}

int& returnByReference(int a) {
	int b = 10;
	cout << "reference : " << &b << " b : " << b << endl;
	return b;
}

tuple<int,double> getTuple()
{
	int a = 10;
	double b = 3.14;
	return make_tuple(a, b);
}

int main()
{
	//Return By Value
	int val = returnByValue(3);
	cout << "&val : " << &val << " val : " << val << endl;
	
	//동적할당 포인터 반환
	int *ptr = returnByAddress(3);
	cout << "ptr : " << ptr << endl;

	int rVal = returnByReference(3);
	cout << "&rVal : " << &rVal << endl;
	cout << rVal << endl;
	cout << rVal << endl;
	
	int &rVal2 = returnByReference(3);
	cout << "&rVal2 : " << &rVal2 << " rVal2 : "<<rVal2 << endl;
	val = returnByValue(5);
	cout << "&val : " << &val << " val : " << val << endl;
	
	//튜플사용하기
	tuple<int, double> my_t = getTuple();
	cout << get<0>(my_t) << endl;
	cout << get<1>(my_t) << endl;

	//c++ 17 에서 사용가능
	//auto[a, b] = getTuple();
	return 0;
}

주소에 의한 인수전달
Call By address

포인터를 통해 주소를 전달하는걸 말한다.
포인터 변수도 주소값을 담는 변수다. 
즉, 파라미터로 가면 포인터변수가 복사된다.
포인터 자체의 주소는 달라지고 포인터가 가르키고있는 주소는 같다.

주소에 접근해 값을 바꿀 수 있기에 포인터를 사용해 함수에 출력인것 처럼 사용할 수 있다.
const를 사용하면 값변경이 불가능하다.

Call By Reference와 비슷해보이지만 다르다. 오히려 Call By Value와 비슷하다.
다만 메모리 주소를 통해 접근할 수 있다.

 

#include <iostream>

using namespace std;

void ptrTest(const int *ptr) {
	cout << "func() &ptr " << &ptr <<" "<< ptr << " " << *ptr << endl;
}

int main()
{
	int value = 5;
	cout << value << " " << &value << endl;
	int *ptr = &value;

	ptrTest(&value);

	//main()과 func()의 포인터 주소가 다르다.
	cout << "main() &ptr " << &ptr << endl;
	
	ptrTest(ptr);

	return 0;
}

참조에 의한 인수전달
Call by reference

참조에 의한 인수전달은 메모리 주소를 그대로 넘긴다.
변수를 그대로 사용한다고 생각하면 된다.

 

함수를 리턴 타입을 정해 받으면 하나만 값을 받을수 있다.
함수의 출력을 가지고 올때도 사용한다.

참조를 사용하면 여러개의 데이터의 값을 변경할 수 있다.


참조할때 단점
리터럴은 참조파라미터로 사용이 불가능하다.(const사용하면 가능)

포인터도 참조가 가능하다.
파라미터 받을때 int* &ptr형식으로 하면 가능하다.

정적배열로 참조로 가능한데 거의 사용가능하지않는다.
vector(동적할당배열)를 사용하는 경우가 많다.

#include <iostream>
#include <cmath>

using namespace std;

void addOne(int &y) {
	//Call by Value라면 아무의미가 없다.
	cout <<"y : " << y << " &y : " << &y << endl;
	y = y + 1;
}


void addOnePointer(int* &y) {
	//Call by Value라면 아무의미가 없다.
	cout << "y : " << y << " &y : " << &y << endl;
	y = y + 1;
}

//2개의 데이터 값 변경
void getSinCos(const double degrees, double &sin_out, double &cos_out) {
	static const double pi = 3.141592;
	double radians = degrees * pi / 180.0;
	sin_out = std::sin(radians);
	cos_out = std::cos(radians);
}

int main()
{
	//리터럴은 불가능하다.
	//addOne(7);
	
	int x = 5;
	cout << "x : " << x << " &x : " << &x << endl;
	addOne(x);
	cout << "x : " << x << " &x : " << &x << endl;

	double sin_out (0.0);
	double cos_out (0.0);

	//sin_out,cos_out을 참조해 두개 변수 모두 값이 변경된다.
	getSinCos(30.0, sin_out, cos_out);
	cout << "sin_out : " << sin_out << endl;
	cout << "cos_out : " << cos_out << endl;

	//포인터 reference보내기
	int *ptr = &x;
	cout << "ptr : " << ptr << " &ptr : " << &ptr << endl;
	addOnePointer(ptr);


	return 0;
}

Parameter 매개변수와 Argument 인자

매개변수와 인자의 용어차이

매개변수는 함수의 기능을 바꾸어주는 기능을 한다.
매개변수는 함수가 끝나는 동시에 메모리에 반납이 된다.
즉, 새로운 변수가 생성되어 데이터가 복사되는 것이다.

인자는 함수를 사용할때 들어가는 변수,리터럴을 말한다.

#include <iostream>

using namespace std;

int foo(int a, int b);

//함수에서 사용되는 변수가 매개변수 parameter이다.
int foo(int a, int b) {
	cout << "a : " << a << " b : " << b << endl;
	return a + b;
}

int main()
{
	int a = 3, b = 4;

	//함수를 불러올때 사용되는 변수가 인자 argument이다.
	foo(6, 7);
	foo(a, b);

	return 0;
}

 

 

값에 의한 인수 전달
Call by value

함수는 파라미터가 어떤게 들어오는지에따라 결과가 달라진다.
함수를 사용할때 선언된 변수를 인수에 넣게되면 변수를 함수로 보내는게 
아닌 변수주소안의 데이터를 복사해서 보낸다.

그러므로 인자안에서 사칙연산이 가능한 것이다.

Call by value는 함수안에서 생긴결과가 밖으로 영향주지 못한다.

#include <iostream>

using namespace std;

void doSomething(int y) {
	cout << "In fuc " << y << " " << &y << endl;
}

int main()
{
	//리터럴 7이 함수에 복사한다.
	doSomething(7);

	int y = 6;
    //doSomething(y);안의 변수 주소와 다른걸 볼 수 있다.
	cout << "In main " << y << " " << &y << endl;
	doSomething(y);
	doSomething(y+1);

	return 0;
}

+ Recent posts