• 함수의 정의(implementation) 없이, 함수의이름과매개변수 타입만 선언하는 것.
• 예: int add(int a, int b);
함수를 정의하는곳이 main함수보다 아래의 있을경우 컴파일러는 해당함수가 main함수내에서 사용될때 무슨함수인지를 모른다.
프로그래밍은 기본적으로 위에서 아래로 실행되기때문에 main함수보다 아래에 정의되어있는 함수들은 인식하지못한다
하지만 위에다가 모든정의를 적게되면 사실상 코드가 난잡해질수있으니 사용되는 함수들의 정의 즉, 함수의 기능을전부다 적은 코드는 아래의 적고 #include와같이 전처리기들은 보통 코드파일 맨위에적는데 그와같이 정의된 함수들의 리턴타입,파리미터정도를 적은 함수의 모양을 "선언" 해준다 (declaration) 근데 이것을 코드의 앞에다가 하기때문에 전방선언 (forward declaration)이라고 한다.
2. 코드 구조 예시
#include <iostream>
using namespace std;
// 전방 선언
int add(int a, int b);
int main() {
cout << add(1, 2) << endl; // 함수 호출
return 0;
}
// 함수 정의
int add(int a, int b) {
return a + b;
}
int a = 10;
...
/* 프로그램 작동중 */
...
{
int b = 20;
}
...
/* 프로그램 작동중 */
...
만약 이렇게 코드를 작성한다고한다면 변수 a와 b는 컴퓨터 메모리의 어느한곳을 할당 받게 된다.
블록안에 있거나 함수안에 있는 지역변수들은 해당 블록이 끝나면 사라지게되며, 전역변수는 프로그램이 시작되고 생성이 되며 프로그램이 종료될때 소멸된다.
하지만 필요에 따라서는 메모리에 할당한후 그 기억공간이 필요없다면 미리없어져도 될수있어야한다.
필요에따라서 메모리에 할당도 할수있어야하며 필요에따라서 반환도 해야한다
이것을 동적 메모리할당 ( Dynamic memory allocation)이라고 한다.
그런데 동적 메모리할당으로 생선된 저장공간은 변수처럼 이름이없다. 그래서 이름을통해 액세스 할수없다.
동적으로 할당한곳을 포인터변수가 가리키게 하면 그 포인터를 이용해서 액세스 할수있다.
new : 동적으로 메모리를 할당하는 연산자
1. ptrVar = new TypeName;
2. ptrVar = new TypeName[n];
1번 형식은 지정된 자료형의 데이터 1개를 저장할수있는 공간을 할당하며,
2번 형식은 지정된 자료형의 데이터 n개를 저장할수있는 배열을 할당한다.
❗이때 지정된 자료형은 포인터의 자료형과 일치해야한다.
delete : 동적으로 메모리를 반납하는 연산자
1. delete ptrVar;
2. delete [] ptrVar;
1번형식은 데이터1개를 반환하며
2번형식은 데이터n개가 할당된 배열을 반환한다
❗new로 만든 형식 그대로 delete해주면된다.
//동적으로 할당하는방법
int *intPtr;
intPtr = new int;
...
//동적으로 반환하는방법
delete intPtr;
intPtr = nullptr;
delete연산자를 이용 해당 공간을 시스템에 반환하여도 intPtr포인터는 여전히 그공간을 가리키고있다.
반드시 intPtr = nullptr; 이라는 명령어로 가리키고있는 곳이 없음을 명시해주어야 합니다.
추가설명
delete 연산자는 동적으로 할당된 메모리를 메모리 풀로 반환합니다. 그러나 delete는 포인터 변수 자체를 수정하거나 변경하지 않습니다. 즉, delete는 메모리를 해제하지만 포인터 변수가 그 메모리를 가리키는 것을 그대로 둡니다. 이를 '댕글링 포인터(dangling pointer)'라고 합니다.
댕글링 포인터는 실제적으로 메모리를 가리키고 있지만, 그 메모리는 이미 해제되어 프로그램에서 사용할 수 없는 상태입니다. 댕글링 포인터를 통해 메모리에 접근하려고 하면 예측할 수 없는 결과를 초래할 수 있습니다. 이는 메모리 누수와 함께 동적 메모리 관리에서 가장 주의해야 할 문제 중 하나입니다.
따라서 메모리를 해제한 후에는 포인터를 nullptr로 설정하여 댕글링 포인터를 방지하는 것이 좋습니다. nullptr는 포인터가 어떤 메모리도 가리키고 있지 않음을 나타내는 특별한 값입니다. 이렇게 하면 포인터가 더 이상 유효하지 않은 메모리를 가리키지 않도록 할 수 있습니다.
const 한정어는 초기화를 해주어야 합니다.물론 대입연산자 “=” 를 활용하여 해당하는 데이터타입을 선언과 동시에 할당하여 초기화를 하죠
이제 a 는 바뀌지않는 값 10을 가진 변수가 되었습니다.
여기서 조금더 어려운 개념인 const 한정어가 사용되는 포인터와 주소값의 예를 들어보겠습니다.
int a =10 , b =20; //a,b에 각각 int형 10으로 초기화 해주었습니다.
const int *ptr = &a; // 포인터변수 ptr은 a의 주소를 가리킵니다.
*ptr = 30; // error!!
ptr = &b; // ok!!
위의 문장에서 ptr은 const int * 로 정의 되어있습니다 이는 ptr이 const int에 대한 포인터라는 의미 입니다.
비록 a가 const로 지정되지는 않았지만 ptr이 상수에 대한 포인터라고 지정했으므로 *ptr의 값을 수정하는것은 허용하지 않습니다. 하지만 주소를 변경하는 다음문장은되는게 이상하죠?
*즉, const int *ptr = &a;에서 ptr는 'const int'를 가리키는 포인터이므로, ptr을 통해 가리키는 값(ptr)은 변경할 수 없습니다. 그래서 *ptr = 30;은 오류를 발생시킵니다.
그러나 ptr 자체, 즉 ptr가 가리키는 주소는 const로 지정되지 않았습니다. 따라서 ptr는 다른 주소를 가리킬 수 있습니다. 그래서 ptr = &b;는 허용됩니다.
주소를 변경하는것을 막으려면 const 의 위치를 바꾸어 줘야 합니다.
int a =10 , b =20; //a,b에 각각 int형 10으로 초기화 해주었습니다.
int * const ptr = &a; // 포인터변수 ptr은 a의 주소를 가리킵니다.
*ptr = 30; // ok!!
ptr = &b; // error!!
const 한정어 const int *ptr 이 경우, ptr은 const int를 가리키는 포인터입니다. 따라서 ptr을 통해 가리키는 값(*ptr)은 변경할 수 없습니다. 그러나 ptr 자체, 즉 ptr이 가리키는 주소는 변경 가능합니다.int * const ptr: 이 경우, ptr은 const 포인터입니다. 즉, ptr이 가리키는 주소 자체가 변경할 수 없습니다. 그러나 ptr을 통해 가리키는 값(*ptr)은 변경 가능합니다.
const int *ptr = &a; // 값을 변경할수없음
int * const ptr = &b; // 주소를 변경할수없음
const한정어의 위치에따라 포인터는 다르게 작동하는것을 반드시 알고 넘어 가야 할것 같습니다.
저또한 공부를 하는입장이지만 이렇게 글을 남김으로써 더욱 이해가 잘되고 다음에 다시 볼때도 편하겠네요
함수를 호출하는 과정에는 인수를 전달하고 함수의 위치로 분기하며 결과를 반환하고 호출한 위치로 되돌아오는 동작이 수반된다. 이에 따른 처리시간 및 코드의 증가는 비록 미미한 것이지만, 떄로는 매우 빠른 처리가 필요하여 불필요한 시간 지연을 피하고 싶을 때가 있다.
inline 함수
함수가 가지는 모듈화의 장점을 살리면서 이러한 불필요한 실행 효율 저하를 막기위해 사용할수있inline함수는 작성하는것은 inline키워드를 사용하는것 외에는 일반함수와 동일하다. 그러나 컴파일러가 번역할때에는 일반 함수와는 달리 함수의 처리문장이 호출되는 위치에 직접 삽입된다
#include <iostream>
using namespace std;
inline vo id SwapValues(int &x,int &y){
int temp = x;
x=y;
y=temp;
}