9) 함수
9-1. 함수 사용 절차
함수 원형 선언 => 함수 정의 => 함수 호출
int sum(int left, int right); // 원형 선언
int main(){
std::cout << sum(3, 4); // 함수 호출
}
int sum(int left, int right){
return left + right;
} // 함수 정의
이 경우에는, 컴파일러는 main함수를 sum함수보다 먼저 컴파일하기 때문에 sum함수가 있다는 사실 (함수 원형 선언)을 알려줘야합니다.
그런데.. 사실 이렇게만 해도 작동은 됩니다.
int sum(int left, int right){
return left + right;
} // 함수 정의
int main(){
std::cout << sum(3, 4); // 함수 호출
}
9-2. 참조자 (reference)
특정 변수의 실제 이름 대신 참조자(&)를 통해서 변수를 사용할 수 있음.
- 여기서 쓰인 &는 포인터에서 배웠던 주소 연산과는 다릅니다 (오버로딩)
- 특별한 개념은 아니고, 그냥 특정 메모리 공간을 여러개의 이름(별명)으로 부르고 싶을 때 사용하는 것입니다.
ex) 두 변수의 값 공유
int x = 10; // 변수의 선언
int &y = x; // 참조자 선언
int main(){
y++;
std::cout << x << std::endl; // 11
x++;
std::cout << y << std::endl; // 12
}
y를 증가시켜도 x가 증가하고, x를 증가시켜도 y가 증가되는 모습입니다.
참조자는 일반 변수와 별 다를게 없어 보이지만, 함수를 호출할 때는 조금 다릅니다.
9-2. 참조자를 이용한 함수 호출
위에서 배운 참조자를 이용하여 함수를 호출하면 조금 특별한 결과를 얻을 수 있습니다.
int square_by_value(int a){
std::cout << &a << std::endl; // 0x7fff2bf1fccc
return a * a;
}
int square_by_ref(int &a){
std::cout << &a << std::endl; // 0x7fff2bf1fcec
return a * a;
}
int square_by_pointer(int* pta){
std::cout << pta << std::endl; // 0x7fff2bf1fcec
return (*pta) * (*pta);
}
int main(){
int a = 3;
std::cout << &a << std::endl; // 0x7fff2bf1fcec
square_by_value(a);
square_by_ref(a);
square_by_pointer(&a);
}
-
call by value
a 변수를 복사한 새로운 임시변수를 생성하여 함수에 전달
=> 원본 데이터 변화 불가능.
-
call by reference : & 연산자 이용
a 변수를 그대로 함수에 전달 => 원본 데이터 변화 가능.
-
call by pointer:
이전에 배운 포인터를 이용해서 함수를 호출할 수도 있습니다.
- 사실 call by value의 일종이긴 합니다.
- 하지만, 주소값은 보존되므로 포인터 역연산(
*
)을 이용하여 원본의 값에 접근할 수 있음.
9-3. *와 &를 헷갈리지 말기
int main(){
// 1. 일반적인 포인터변수의 선언
int num = 12;
int* ptr = #
int** dptr = &ptr;
// 2. 참조자를 이용한 포인터변수의 별칭 선언
int &ref = num;
int* &pref = ptr;
int** &dpref = dptr;
// 3. * 포인터 연산자를 이용하여 주소 역연산
std::cout << ref << std::endl; // 12
std::cout << *pref << std::endl; // 12
std::cout << **dpref << std::endl; // 12
}
구분해야할 것은 4가지입니다.
*
- 변수 선언시 사용 : 포인터 변수임을 명시
- 변수에 사용 : 포인터 변수에 저장된 주소에 저장된 변수값 반환.
&
- 변수 선언시 사용 : 참조자 변수임을 명시
- 변수에 사용 : 변수의 주소값을 반환
9-4. 디폴트 인수
파이썬에서는 그냥 맘대로 함수의 디폴트값을 지정할 수 있었는데.. C++에서는 몇개의 제약이 있습니다.
- 함수 원형에서만 디폴트인수를 지정가능
- 가장 오른쪽에서 순서대로 지정해야함.
void Display(int x, int y, char ch = 'a', int z = 4);
Display(1, 2); // ok
Display(1, 2, 'c'); // ok
Display(5, 6, 'c', 9); // ok
Display(5, , 'c', 9); // 에러!
9-5. 함수 오버로딩(Overloading)
함수 오버로딩은, 같은 이름의 함수를 여러가지 매개변수 형태로 사용할 수 있는 기능입니다.
void Display(int x, int y);
void Display(int x, int y, char ch);
void Display(int x, int y, char ch = 'a', int z = 4);
파이썬에는 함수 오버로딩을 지원하지 않지만, 자바같은 경우에는 메서드 오버로딩이 있습니다.
이렇게 함수 오버로딩이 가능한 이유는 함수 시그니쳐라는 개념 때문입니다.
- 함수의 원형에 명시되는 매개변수 리스트
- 이 매개변수 리스트에 따라서 함수를 구분함.
!!주의사항!!
함수 오버로딩은 모호한 호출을 허용하지 않습니다.
- 모호한 호출 : 여러가지 원형이 모두 적용가능한 경우
int main(){
Display(2, 3, 'a');
}
에러메시지
prog.cc: In function 'int main()':
prog.cc:8:22: error: call of overloaded 'Display(int, int, char)' is ambiguous
8 | Display(2, 3, 'a');
| ^
prog.cc:3:6: note: candidate: 'void Display(int, int, char)'
3 | void Display(int x, int y, char ch);
| ^~~~~~~
prog.cc:5:6: note: candidate: 'void Display(int, int, char, int)'
5 | void Display(int x, int y, char ch = 'a', int z = 4);
| ^~~~~~~
9-6. 변수의 유효범위
-
자동 변수 (automatic variable) : 파이썬의 지역변수랑 같은 개념
- (프로세스의 stack부분에 저장됨)
-
레지스터 변수 (register variable) : CPU의 레지스터에 저장되어 빠르게 접근할 수 있는 변수
-
- 레지스터(processor register)
-
컴퓨터의 CPU가 빠르게 접근가능한 미니 저장소
- register 키워드를 통해서 선언이 가능함.
-
-
정적 변수 (static variable) : 프로그램이 실행되는 내내 유지되는 변수,
-
(프로세스의 data부분에 저장됨)
-
자동으로 0으로 초기화됨.
-
static 키워드
int main(){ for (int i=0; i<) } void printnum(){ static int count = 1; std::cout << count << std::endl; count ++; }
- 한 번 선언된 static 변수는 선언문을 만나도 재선언되지 않음. * 이 경우에는 외부 블록에서 count에 접근이 불가능하지만, 블록 바깥에서 static변수를 선언하게 되면 외부 블록에서 모두 접근이 가능함.
-
외부 파일에서도 사용할 수 있는 변수
- 외부 파일을 include하고, extern 키워드를 통해서 변수를 선언하면 외부 파일의 변수를 가져올 수 있습니다.
// 'exFile.cpp' #include <iostream> int var = 3;
#include <iostream> #include "eXFile.cpp" extern int var;
-
Bonus) C에서의 함수 포인터
함수의 주소를 가리키는 포인터인 함수포인터에 대해서 간략하게 적으려 했는데..
이 개념이 간략하게 정리할 수 있는 개념이 아니라서 따로 섹션을 작성하게 되었습니다.
B-1. 정의
- 메모리 내에서 실행 가능 한 코드를 가리킴.
- 함수 포인터가 가리키는 함수(코드)를 역참조(*)를 통해서 작동시킬 수 있음.
B-2. 문법
- 기본적인 방법
함수의 type (*함수포인터명) (매개변수들);
- 예시
int sum(int, int); // 원본 함수
int (*funcPtr) (int, int); // 원본 함수의 형태에 맞는 함수 포인터 선언
B-3. 문법 해부하기
int (*funcPtr) (int, int)
-
두개의 정수를 매개변수로 받고, 정수를 반환하는 어떤 함수의 포인터
이런 조건만 만족하면 어떤 함수든 가리킬 수(can point) 있음.
-
(*funcPtr)
에서 괄호는 반드시 필요함!-
괄호를 안씌우고
int *funcPtr(int, int)
라고 선언한다면,int*
자료형을 반환하는funcPtr
함수라는 뜻이 되기 때문입니다. -
같은 이유로, 상수 포인터를 만드려면
(*const funcPtr)
이라고 해야합니다. const를 바깥에 빼면 const int 자료형을 반환하는것으로 해석하니까요.
-
B-4. 사용 예시
ex) 함수를 간접적으로 호출가능
int sum(int, int);
int (*funcPtr) (int, int);
int main(){
int a = 10, b = 15;
funcPtr = sum; // sum 함수를 가리킴
std::cout << funcPtr(a, b); // 대신 계산이 가능함.
}
int sum(int a, int b){
return a + b;
}
B-5. 함수 포인터의 장점
- 여러 함수를 for문으로 사용가능합니다.
참고링크 : https://blankspace-dev.tistory.com/241
int (*funcPtrArray[4]) (int, int) = {sum, sub, mul, divide};
// 4개의 함수를 가리키는 배열
int main(){
int a = 10, b = 15;
for (int (*func)(int, int) : funcPtrArray){
std::cout << func(a, b) << std::endl;
}
}
- 다른 함수의 인자로 함수를 전달할 수 있습니다.
EX) 선택정렬 - 정렬의 기준(key)를 상황에 맞게 전달하기.
int key1(int num){
return num;
}
int key2(int num){
return -num;
}
int key3(int num){
return num * num;
}
void selectionSort(int *arrRef, int (*key)(int)){
int length = 10;
int nextIdx, temp;
for (int i=0; i<length; i++){
nextIdx = i;
for(int j=i+1; j<length; j++){
if ( key(*(arrRef+nextIdx)) - key(*(arrRef+j)) > 0 ){
nextIdx = j;
}
}
temp = *(arrRef+nextIdx);
*(arrRef+nextIdx) = *(arrRef+i);
*(arrRef+i) = temp;
std::cout << temp << " ";
}
std::cout << std::endl;
}
int main(){
int arr[10] = {1,-2,3,-4,5,-6,7,-8,9,-10};
selectionSort(arr, key1);
selectionSort(arr, key2);
selectionSort(arr, key3);
}
출력결과
-10 -8 -6 -4 -2 1 3 5 7 9
9 7 5 3 1 -2 -4 -6 -8 -10
1 -2 3 -4 5 -6 7 -8 9 -10
B-6. C언어 함수 포인터의 모호함
참고링크 : https://blog.naver.com/jdk7613/100020321205
void func(){
std::cout << "Good!";
};
int main(){
void (*funcPtr)();
funcPtr = func; // ok
funcPtr = &func; // ok
funcPtr(); // ok
(*funcPtr)(); // ok
}
첫번째 방식만 소개했었는데, 사실 뒤의 방식대로 해도 문제가 없습니다.
생각해보면 뭔가 이상한거죠 ㅡㅡㅋ
그래서 C++에서는 멤버함수포인터라는 새로운 개념이 있다고 하네요..
일단 그건 클래스를 배우고 나서 나중에 소개하겠습니다.