개발개발/c++
c++의 지역변수, 전역변수, 메모리 영역, static 변수, 동적할당, 함수 오버로딩, 함수의 디폴트 인자
유잉유잉유잉
2024. 12. 8. 06:35
728x90
▶️ 지역변수
- 코드블록 안에서 선언하여 사용하는 변수
- 함수의 인자도 지역변수 입니다.
- 함수 종료시 메모리가 제거됩니다.
int main()
{
// 지역변수
int num = 0;
return 0;
}
▶️ 전역변수
- 코드블록 바깥에서 선언하여 사용하는 변수
- 변수명은 앞에 g를 붙여서 전역변수임을 표시합니다.
ex) int gNum = 1000; - 프로그램 시작시 메모리에 생성 됩니다.
- 컴파일 타임에 메모리 크기와 위치가 정해집니다.
- 프로그램 종료시 메모리에서 제거 됩니다.
- 선언한 후 아래 모든 코드에서 전역변수 사용이 가능합니다.
#include <iostream>
// 전역변수
int num = 0;
int main()
{
return 0;
}
- 전역변수 단점
- 메모리 컨트롤이 되지 않아서 의도대로 사용하기 어렵습니다.
- 편한만큼 구조적으로 문제가 생길가능성이 많습니다.- 여기저기에서 사용하기 때문에 문제가 생겼을 때 찾기 어렵습니다.
▶️ c++에서 메모리의 영역
- 스택 : 지역변수와 매개변수가 만들어지는 영역.
- 코드블럭 안에서만 사용되며 코드블럭이 종료될 때, 메모리에서 제거됩니다.
- 데이터 : 전역변수, 정적변수가 만들어지는 영역.
- 정적변수와 전역변수는 초기화값을 대입하지 않은 경우 자동으로 0으로 초기화합니다.
- 전역변수는 컴파일 시점에 메모리에 할당되며, 프로그램 종료시 메모리에서 제거됩니다.
- 정적변수는 호출시(함수 내 정적변수라면 함수 호출시, 객체 내 멤버 정적변수라면 객체 생성시) 메모리에 할당되며, 프로그램 종료시에 메모리에서 제거됩니다.
- 코드 : 프로그램 실행시 코드가 기계어로 저장되는 영역.
- 힙 : 동적 할당한 변수의 메모리가 만들어지는 영역.
- 동적 할당한 변수를 제거하지 않을 경우 메모리릭이 발생합니다.
- 동적 할당한 변수는 사용하지 않는 시점에 직접 제거해주어야 합니다.
▶️ static 변수
- 정적 변수 선언
- 프로그램이 끝날때 까지 메모리가 유지됩니다.
- 너무 많이 사용할 경우 잉여 메모리가 발생할 수 있습니다.
void Test ()
{
// static 변수 : 정적 변수 선언
// 이 변수는 처음 한번만 초기화가 되고
// 다음부터는 초기화가 무시된다.
// 아래 변수는 정적변수지만 지역변수기도 함.
// call이 되지 않으면 따로 메모리에 생성되지 않는다.
// 아래에서 해당 함수가 호출되기때문에 컴파일러가 아래 정적변수는 메모리에 할당한다.
// 만약 함수 호출하는 부분을 주석처리할 경우 해당 변수는 메모리에 할당되지 않는다.
static int numStatic = 50;
++numStatic;
std::cout << "numStatic : " << numStatic << std::endl;
}
int main()
{
Test (); // numStatic: 51
Test (); // numStatic: 52 // 초기화는 무시되고 계속 증가
Test (); // numStatic: 53
Test (); // numStatic: 54
return 0;
}
▶️ 동적할당
- 런타임에 메모리의 생성과 해제를 컨트롤할 수 있는 기능입니다
- c언어 방식 : malloc, free 함수를 이용하여 메모리를 생성하고 해제 합니다.
클래스의 생성자 호출 X - c++ 방식 : new, delete 연산자를 이용하여 메모리를 생성하고 해제합니다
클래스의 생성자 호출 o
- 구조
new 할당할타입;
ex) new int ;
=> 할당할 타입의 메모리 크기만큼 힙 영역에 메모리를 생성하고 그 주소를 반환합니다. - new를 이용하여 동적할당 된 메모리는
delete를 이용하여 메모리 제거를 하지 않을 경우
메모리가 그대로 남아있게 됩니다.
이를 메모리 릭 이라고 합니다. - delete 메모리주소(포인터) ; 를 이용하여 메모리를 제거해야 합니다.
#include <iostream>
// 이런식으로 사용하는 이유
// 초기화시 추가적 작업을 일괄적으로 처리하기 위해서
int* CreateDynamicInt ()
{
return new int;
}
// bool 타입으로 공간할당이 되었는지 아닌지를 판단
bool CreateDynamicInt1 ( int** alloc )
{
*alloc = new int;
return true;
}
// 이중포인터를 사용하지 않는다면 동적할당한 주소를 넘겨줄 수 없음
// => 잘못된 포인터의 사용예시
bool CreateDynamicInt2 ( int* alloc )
{
alloc = new int;
return true;
}
// 다른 방법 : 포인터 레퍼런스를 사용. 근데 대부분 이중포인터를 사용
bool CreateDynamicInt3 ( int *& alloc )
{
alloc = new int;
return true;
}
int main()
{
int* numArr = new int;
*numArr = 500;
delete numArr;
// 동적 배열 할당
int * pArr = new int[10];
pArr[1] = 300;
delete[] pArr;
// 동적할당된
numArr = CreateDynamicInt ();
delete numArr;
CreateDynamicInt1 ( &numArr );
delete numArr;
CreateDynamicInt2 ( numArr );
delete numArr;
CreateDynamicInt3 ( numArr );
delete numArr;
return 0;
}
▶️ 함수 오버로딩
- 같은 이름의 함수를 다른 형태로 여러개 만들어주는 기능입니다.
- 반환타입은 오버로딩에 영향이 없습니다.
- 함수의 인자를 이용하여 같은 형태인지 다른 형태인지를 판단할 수 있습니다.
#include <iostream>
void Output ()
{
std::cout << "" << std::endl;
}
void Output ( int num )
{
std::cout << num << std::endl;
}
void Output ( float num )
{
std::cout << num << std::endl;
}
void Output ( int a, int b )
{
std::cout << a << ", "<< b << std::endl;
}
int main ()
{
Output ();
Output ( 33 );
Output ( 3.3f );
Output ( 3, 3 );
return 0;
}
▶️ 함수의 디폴트 인자
- 함수의 인자에 기본 값을 설정하는 기능입니다.
- 기본값을 설정하면
- 해당 인자에 값을 넘겨줄 경우 디폴트 인자값을 사용하지 않습니다.
- 인자가 없을 경우 기본값으로 사용합니다.
- 디폴트인자는 여러개의 기본값을 지정할 수도 있습니다.
- 가장 오른쪽 인자부터 왼쪽으로 차례대로 디폴트 인자를 지정해야 합니다.
- 디폴트인자가 있는 함수의 오버로딩시 조심히 사용하여야 합니다.
#include <iostream>
int add ( int a, int b = 100 )
{
return a + b;
}
// 오버로딩상황에서 디폴트인자를 사용할 경우 어떤게 사용될지 모름
// 잘못된 예시
/*int add (int a)
{
return a;
}
*/
//// 잘못된 예시2
//int add ( int a, float num2 = 3.3f )
//{
//
//}
▶️ 재귀함수
- 자기 자신을 호출하는 방식입니다.
- 함수가 호출되며 스택이 계속 쌓입니다
- 장점 : 코드를 줄일 수 있습니다.
- 단점 : 너무많은 연산을 할 경우 스택오버플로우가 생깁니다.
#include <iostream>
int Factorial ( int num )
{
if (num == 1)
return 1;
return num * Factorial(num - 1) ;
}
int main ()
{
std::cout << Factorial ( 5 ) << std::endl; // 120 출력
return 0;
}
/*
계산 순서
F(5) - N : 5
return 5 * f(4-1);
F(4) - N:4
return 4 * F(4-1);
F(3) - N : 3
return 3 * f(3-1);
F(2) - N:2
return 2 * F(2-1);
F(1) - N:1
return 1;
*/
▶️ 꼬리재귀
- 재귀호출이 끝날 때 아무런 작업 없이 바로 결과를 반환하는 방식입니다.
- 꼬리재귀도 최적화를 해줘야함. 컴파일 입장에선 반복문이기때문에 스택에 쌓이지 않습니다.
-> 재귀를 할 경우 꼬리재귀로 사용해주길 권장드립니다.
#include <iostream>
int FactorialTail( int num, int result = 1 )
{
if ( num == 1 )
return result;
return FactorialTail( num - 1, num * result );
}
int main()
{
std::cout << FactorialTail ( 5 ) << std::endl; // 120 출력
return 0;
}
/*
꼬리재귀 계산 순서
F(5) - N : 5
return FT(1, 5);
FT(1, 5) - R : 1 N : 5
return FT( 5*1, 5-1)
FT(5, 4) - R : 5 N : 4
return FT(4 * 5, 4-1)
FT(20, 3) - R : 20 N : 3
return FT( 20 * 3, 3-1);
FT(60, 2) - R:60, N : 2
return FT(60 * 2, 2-1);
FT(120, 1) - R:120, N : 1
return 120;
*/
▶️ 함수포인터
- 함수 이름은 해당 함수가 올라가있는 메모리상 주소를 나타냅니다.
- 함수포인터는 함수의 주소를 저장하기 위한 변수입니다.
- 함수의 형태는 반환타입과 인자로 결정이 됩니다.
- 선언방법
반환타입 (*이름)(인자타입) ;
#include <iostream>
void Output ()
{
std::cout << "Output Function" << std::endl;
}
// output()과 같은 타입의 함수다
void OutputTest ()
{
std::cout << "OutputTest Function" << std::endl;
}
int Add ( int num, int num2 )
{
return num + num2;
}
// add()와 Minus()는 같은 타입의 함수이다
int Minus ( int num, int num2 )
{
return num - num2;
}
int main ()
{
// 함수를 호출한다는것은 함수주소();를 호출해준다는 의미이다.
Output (); // Output Function 출력
// 함수의 이름은 해당 함수가 올라가있는 메모리의 주소.
std::cout << Output << std::endl; // 00007FF65EE110B4
// 반환형이 없고, 인자도 없는 함수 포인터 선언
void( *FuncPointer )();
FuncPointer = Output;
// FuncPointer 변수는 output()의 주소를 가지고 있다.
// 그러므로 함수 호출 방법인
// 함수주소(); 방식으로 호출이 가능하다
FuncPointer (); // Output Function 출력
FuncPointer = OutputTest;
FuncPointer (); // OutputTest Function 출력
// Add()는 반환타입 int에 int타입 인자 2개가 있다.
// 그러므로 형태가 달라서 FuncPointer에 주소를 저장할 수 없다.
//FuncPointer = Add; // [에러메세지] "int (*)(int num, int num2)" 형식의 값을 "void (*)()" 형식의 엔터티에 할당할 수 없습니다.
int( *FuncInt )( int, int ) = Add;
std::cout << FuncInt ( 10, 20 ) << std::endl; // 30 출력
FuncInt = Minus;
std::cout << FuncInt ( 30, 10 ) << std::endl; // 20 출력
// 함수포인터도 변수이기에 배열선언이 가능
int( *FuncArr[2] )( int, int );
FuncArr[0] = Add;
FuncArr[1] = Minus;
for (int i = 0 ; i < 2 ; i++)
{
std::cout << FuncArr[i] ( 5, 3 ) << std::endl;
/*
8
2
출력
*/
}
return 0;
}
728x90