14장. 함수 1 01_ 함수의 기본 02_ 인자의 전달
함수의 기본 함수의 정의와 호출의 기본적인 예 void PrintMessage() { cout << "Hi, I'm your first function!!!\n"; } int main() // PrintMessage() 함수를 호출한다. PrintMessage(); return 0; [그림 14-3]
함수의 위치 C++에는 함수의 위치와 관련된 다음과 같은 규칙이 있다. 함수는 자신을 호출하는 함수의 앞에 위치해야 한다. 혹은 함수의 원형만 앞쪽에 있어도 된다. [그림 14-5]
반환 값(Return Values) 함수는 하나의 값을 반환할 수 있다. 실행 결과 // 3을 반환하는 함수 int Three() { return 3; } int main() { // 함수를 호출하고 반환값을 보관한다. int ret = Three(); cout << "ret = " << ret << "\n"; return 0; [그림 14-7] [그림 14-6]
다른 함수에 있는 변수의 사용 다른 함수에 정의되어 있는 변수를 사용할 수 없다. void sub(); int main() { // 변수를 정의한다. int a = 3; // 함수 호출 sub(); return 0; } void sub() // 변수를 사용한다. ++a; // Error
인자(Arguments)의 전달 (1) 함수를 호출하면서 인자를 전달하는 예 실행 결과 int Factorial(int n); int main() { int result = Factorial( 5 ); cout << "5! 는 " << result << "입니다.\n"; return 0; } int Factorial(int n) int result = 1; for (int i = 1; i <= n; ++i) result *= i; return result; [그림 14-10] [그림 14-9]
인자의 전달 (2) 인자는 함수의 매개 변수(Parameters)에 대입된다. [그림 14-11]
인자 전달의 규칙 함수가 호출될 때마다 인자의 값이 매개 변수로 대입되는 가상의 코드가 실행된다. int max(int a, int b) { return a > b ? a : b; } int main() // 3과 5중에 큰 값을 구한다. int ret = max(3, 5); return 0; [그림 14-13]
인자의 전달과 메모리 구조(1) main() 함수가 시작되고 화살표까지 실행된 순간의 메모리 상태 int max(int a, int b) { return a > b ? a : b; } int main() // 3과 5중에 큰 값을 구한다. int arg1 = 3; int arg2 = 5; int ret = max(arg1, arg2); return 0; [그림 14-15]
인자의 전달과 메모리 구조(2) max() 함수가 호출되고 화살표까지 실행된 순간의 메모리 상태 이때 다음과 같은 가상의 코드가 실행된다. int max(int a, int b) { return a > b ? a : b; } int main() // 3과 5중에 큰 값을 구한다. int arg1 = 3; int arg2 = 5; int ret = max(arg1, arg2); return 0; [그림 14-16] int a = arg1; int b = arg2;
인자의 전달과 메모리 구조(3) max() 함수가 끝나고 화살표까지 실행된 순간의 메모리 상태 int max(int a, int b) { return a > b ? a : b; } int main() // 3과 5중에 큰 값을 구한다. int arg1 = 3; int arg2 = 5; int ret = max(arg1, arg2); return 0; [그림 14-17]
포인터 변수를 인자로 전달하기(1) 포인터 변수를 사용해서 함수 밖으로 결과를 전달하는 예 GCD_LCM() 함수가 호출될 때 실행되는 가상의 코드 void GCD_LCM(int a, int b, int* pgcd, int* plcm) { // 유클리드이 호제법을 사용해서 GCD를 구한다. // 중간 소스 코드 생략 (p.321 참조) // 결과를 저장한다. *pgcd = y; *plcm = a * b / *pgcd; } int main() // 28과 35의 최대공약수와 최소공배소를 구한다. int gcd = 0; int lcm = 0; GCD_LCM( 28, 35, &gcd, &lcm); return 0; int a = 28; int b = 35; int* pgcd = &gcd; int* plcm = &lcm;
포인터 변수를 인자로 전달하기(2) 화살표까지 실행된 순간의 메모리 상태 void GCD_LCM(int a, int b, int* pgcd, int* plcm) { // 유클리드이 호제법을 사용해서 GCD를 구한다. // 중간 소스 코드 생략 (p.321 참조) // 결과를 저장한다. *pgcd = y; *plcm = a * b / *pgcd; } int main() // 28과 35의 최대공약수와 최소공배소를 구한다. int gcd = 0; int lcm = 0; GCD_LCM( 28, 35, &gcd, &lcm); return 0; [그림 14-21]
포인터 변수를 인자로 전달하기(3) 화살표까지 실행된 순간의 메모리 상태 void GCD_LCM(int a, int b, int* pgcd, int* plcm) { // 유클리드이 호제법을 사용해서 GCD를 구한다. // 중간 소스 코드 생략 (p.321 참조) // 결과를 저장한다. *pgcd = y; *plcm = a * b / *pgcd; } int main() // 28과 35의 최대공약수와 최소공배소를 구한다. int gcd = 0; int lcm = 0; GCD_LCM( 28, 35, &gcd, &lcm); return 0; [그림 14-22]
포인터 변수를 인자로 전달하기(4) 포인터 타입의 인자를 사용해서 함수의 결과 값을 얻어오는 방법의 정리 함수의 매개 변수는 포인터 타입으로 정의한다. 인자를 넘겨줄 때는 결과 값을 담고 싶은 변수의 주소를 넘겨준다. 함수 안에서 결과를 넘겨줄 때는 매개변수가 가리키는 곳에 값을 넣어준다.
레퍼런스 변수를 인자로 전달하기(1) 레퍼런스 변수를 사용해서 함수 밖으로 결과를 전달하는 예 GCD_LCM() 함수가 호출될 때 실행되는 가상의 코드 void GCD_LCM(int a, int b, int& gcd, int& lcm) { // 유클리드이 호제법을 사용해서 GCD를 구한다. // 중간 소스 코드 생략 (p.325 참조) // 결과를 저장한다. gcd = y; lcm = a * b / *pgcd; } int main() // 28과 35의 최대공약수와 최소공배소를 구한다. int gcd = 0; int lcm = 0; GCD_LCM( 28, 35, gcd, lcm); return 0; int a = 28; int b = 35; int& gcd = &gcd; // 뒤쪽의 gcd는 main()함수 안에 있는 gcd를 말한다 int& lcm = &lcm; // 뒤쪽의 lcm는 main()함수 안에 있는 lcm를 말한다
레퍼런스 변수를 인자로 전달하기(3) 레퍼런스 타입의 인자를 사용해서 함수의 결과 값을 얻어오는 방법의 정리 함수의 매개 변수는 레퍼런스 타입으로 정의한다. 인자를 넘겨줄 때는 결과 값을 담고 싶은 변수를 그대로 넘겨준다. 함수 안에서 결과를 넘겨줄 때는 매개 변수에 값을 넣어준다.
배열을 인자로 전달하기(1) 배열 타입의 인자는 실제로는 포인터를 사용해서 전달된다. UsingArray() 함수가 호출될 때 실행되는 가상의 코드 실행 결과 int main() { char array[20] = "Hello, World!"; UsingArray( array); cout << "In main() : " << array << "\n"; return 0; } void UsingArray(char arr[] ) cout << "In UsingArray() : " << arr << "\n"; arr[12] = '?'; [그림 14-27] char* arr = array; [그림 14-26]
배열을 인자로 전달하기(2) 배열을 인자로 전달하는 방법의 정리 매개 변수의 타입을 적어줄 때 ‘배열의 원소 개수’는 적지 않는다. 인자로 넘겨줄 때는 배열의 이름을 넘겨준다. 인자로 넘어온 배열을 사용할 때는 그냥 평범한 배열을 사용하듯이 하면 된다.
const를 사용한 배열의 보호 인자에 const 속성을 부여해서 배열의 내용이 변경되는 것을 막을 수 있다. UsingArray()가 호출될 때 실행되는 가상의 코드 오류 메시지 void UsingArray( const char arr[] ) { cout << "In UsingArray() : " << arr << "\n"; arr[12] = '?'; // Error } const char* arr = array; [그림 14-28]
2차원 배열의 전달 2차원 배열을 인자로 전달하는 예 실행 결과 int main() { int array[5][3] = {{ 1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}, {13, 14, 15}}; Using2DArray( array); return 0; } void Using2DArray( int arr[][3] ) for (int i = 0; i < 5; ++i) for (int j = 0; j < 3; ++ j) cout << "arr[" << i << "][" << j << "] = " << arr[i][j] << "\n"; [그림 14-29]
기본적인 구조체의 전달 구조체 변수를 인자로 전달하는 예 Distance() 함수가 호출될 때 실행되는 가상의 코드 struct Point { int x, y; }; int main() Point a = {0, 0}; Point b = {3, 4}; double dist_a_b = Distance(a, b); return 0; } double Distance(Point p1, Point p2) // 두 점의 거리를 반환한다. (임시로 0을 반환) return 0.0f; [그림 14-32] Point p1 = a; Point p2 = b;
구조체의 전달과 성능 문제(1) 조금 전의 방식(Call-by-value)으로 구조체 변수를 전달한 경우에 발생하는 성능 문제 인자의 내용이 매개 변수에 복사되는 시간이 낭비된다. 매개 변수의 크기가 커지므로 메모리가 낭비된다. 레퍼런스 변수를 사용해서 넘기면 위의 문제를 해결할 수 있다. Distance() 함수가 호출될 때 실행되는 가상의 코드 double Distance( Point& p1, Point& p2) { // 중간 생략 // return 0.0; } [그림 14-33] Point& p1 = a; Point& p2 = b;
구조체의 전달과 성능 문제(2) 레퍼런스로 넘긴 경우에 인자의 값이 바뀔 염려가 있으므로 const 속성을 사용해서 인자를 보호할 필요가 있다. 구조체 변수를 인자로 전달하는 방법의 정리 구조체를 인자로 넘겨줄 때는 레퍼런스를 사용하자. 함수의 안쪽에서 구조체의 내용을 읽기만 한다면 const와 레퍼런스를 사용하자. double Distance( const Point& p1, const Point& p2)
CRT 함수의 사용 CRT 함수를 사용해서 두 점의 거리를 구하는 예 #include <iostream> #include <cmath> using namespce std; // 중간 코드 생략 double Distance(const Point& p1, const Point& p2) { // 피타고라스의 정리를 사용한다. double distance; distance = sqrt( pow(p1.x - p2.x, 2) + pow(p1.y - p2.y, 2) ); // 결과를 반환한다. return distance; } [그림 14-36]
15장. 함수 2 01_ 함수의 모든 것
오버로딩(Overloading) 오버로딩을 사용해서 두 가지 버전의 max() 함수를 만드는 예 오버로딩을 사용할 수 없다면 서로 다른 이름의 함수를 만들어서 사용해야 한다. void max( int a, int b ); float max( float a, float b ); void max_i( int a, int b ); float max_f( float a, float b );
기본적인 오버로딩의 규칙 인자의 타입을 통해서 호출될 함수를 결정한다. 반환 값만 틀린 경우는 오버로드 할 수 없다. 시그니처가 다르더라도 오버로드 할 수 없는 경우 int VariableReturnType( char c, int i ); // 1번 double VariableReturnType( char c, double d ); // 2번 ... VariableReturnType( ‘A’, 100 ); // 1번 함수 호출 VariableReturnType( ‘A’, 100.12 ); // 2번 함수 호출 int VariableReturnType( char c, int i ); double VariableReturnType( char c, int i ); // Error ... VariableReturnType( ‘A’, 100 ); // 어느 함수를 호출해야 할 지 모름 void SameSignature( int i ); void SameSignature( int& r ); ... int i = 10; SameSignature( i ); // 어느 함수를 호출해야 할 지 모름
적당한 함수를 찾는 순서 오버로드 된 여러 버전의 함수 중에서 알맞은 함수를 찾는 순서 위의 규칙을 예제와 함께 살펴보자. 1. 정확하게 일치하는 경우(Exact Match) 2. 승진에 의한 형변환(Promotion) 3. 표준 형변환(Standard Conversions) 4. 사용자에 의한 형변환(User-defined Conversions) 위의 규칙을 예제와 함께 살펴보자. void WhichOne( float f ); // 1순위 (exact match) void WhichOne( double d ); // 2순위 (promotion) void WhichOne( int c ); // 3순위 (standard conversions) int main() { WhichOne( 3.5f ); return 0; }
디폴트 인자 디폴트 인자를 사용하는 예 bool SetFramesPerSec(int fps = 10) { // 코드 생략 return true; } int main() // 인자 없이 함수를 호출한다. SetFramesPerSec(); return 0; [그림 15-1]
디폴트 인자를 사용하는 규칙 디폴트 인자들은 오른쪽 끝에 모여 있어야 한다. 오버로딩과 디폴트 인자의 충돌하는 경우를 주의해야 한다. // 올바름: 디폴트 인자 b, c는 오른쪽에 모여있다. void DefaultArgs1( int a, int b = 2, int c = 3 ); // 올바름: 디폴트 인자 c는 오른쪽에 있다. void DefaultArgs2( int a, int b, int c = 3 ); // 틀림: 디폴트 인자 a, b의 오른쪽에 보통 인자 c가 있어서는 안 된다. void DefaultArgs3( int a = 1, int b = 2, int c ); // 틀림: 디폴트 인자 a의 오른쪽에 보통 인자 b가 있어서는 안 된다. void DefaultArgs4( int a = 1, int b, int c = 3 ); // 틀림: 디폴트 인자 a의 오른쪽에 보통 인자 b, c가 있어서는 안 된다. void DefulatArgs5( int a = 1, int b, int c); void Ambiguous( int a, int b = 100 ); // 1번 void Ambiguous( int a ); // 2번 ... Ambiguous( 50 ); // 어느 것을 호출해야 할 지 모름
재귀 호출(Recursion)(1) 재귀 호출을 사용해서 10진수를 2진수로 변환하는 예 void Convert2Bin(int dec) { // 10진수가 0보다 작거나 같으면 종료한다. if (dec <= 0) return; // 인자를 2로 나눈 값을 인자로 다시 호출한다. Convert2Bin( dec / 2); // 인자를 2로 나눈 나머지를 출력한다. cout << dec % 2; } int main() // 13을 이진수로 변환한다. Convert2Bin( 13); cout << "\n"; return 0;
재귀 호출(2) 재귀 호출을 과정을 그림으로 펼쳐서 확인하자. [15-9]
함수 포인터 함수 포인터를 사용해서 함수를 가리키는 예 실행 결과 void Dog() { cout << "I'm a dog.\n"; } void Cat() cout << "I'm a cat.\n"; // 중간 생략 void (*p)(); p = &Dog; (*p)(); p = &Cat; [15-10]
함수 포인터의 정의 함수 포인터 타입의 변수를 정의하는 방법 typedef로 함수 포인터 타입의 별칭 만들기 [15-12] typedef void (*FN_TYPE2)(int); // 함수 포인터 타입의 별칭 만들기 FN_TYPE2 Func; // 함수 포인터 타입의 변수 정의 [15-13]
01_ 동적 메모리 할당의 기본 02_ 동적 메모리 할당의 응용 16장. 동적 메모리 할당 01_ 동적 메모리 할당의 기본 02_ 동적 메모리 할당의 응용
기본적인 동적 메모리 할당과 해제(1) 사용자에게 입력받은 정수의 합과 평균을 구하는 예 // 몇 개의 정수를 입력할지 물어본다. int size; cout << "몇 개의 정수를 입력하시겠소? "; cin >> size; // 필요한 만큼의 메모리를 할당한다. int* arr = new int [size]; // 정수를 입력받는다. cout << "정수를 입력하시오.\n"; for (int i = 0; i < size; ++i) cin >> arr[i]; // 평균을 계산하고 출력한다. int sum = 0; for (i = 0; i < size; ++i) { sum += arr[i]; } float ave = (float)sum / (float)size; cout << "합 = " << sum << ", 평균 = " << ave << "\n"; // 사용한 메모리를 해제한다. delete[] arr;
기본적인 동적 메모리 할당과 해제(2) 실행 결과 동적으로 메모리를 할당하기 [16-3] [16-4]
기본적인 동적 메모리 할당과 해제(3) 메모리를 할당했을 때의 메모리 상태 동적으로 할당한 메모리를 해제하기 [16-7] [16-8]
기본적인 동적 메모리 할당과 해제(4) 동적 메모리 할당을 사용하는 방법의 정리 메모리를 할당할 때는 타입과 크기를 지정한다. 그러면 컴퓨터가 메모리를 할당한 후에 그 메모리의 주소를 보관한다. 우리는 이 주소를 보관해두어야 한다. 보관해둔 주소를 통해서 메모리 공간을 사용할 수 있다. 이 때는 배열의 원소를 가리키는 포인터처럼 사용할 수 있다. 사용이 끝난 후에는 반드시 보관해 둔 주소를 알려주면서 메모리를 해제한다. [16-7]
연산자 new, delete, new[], delete[] // int 하나를 담을 수 있는 크기의 메모리 공간을 할당한다. int* p = new int; // 메모리에 값을 넣어본다. *p = 337; // 사용이 끝난 메모리를 해제한다. delete p; [16-9]
동적 메모리 할당의 규칙(1) new, delete와 new[], delete[] 쌍을 맞춰서 사용하자. NULL 포인터를 해제하는 것은 안전하다. 해제한 메모리를 또 해제해서는 안 된다. char* p = NULL; delete p; // 혹은 delete[] p // 다음과 같이 할 필요가 없다. if ( NULL != p ) delete p; // 메모리를 할당한다. short* p = new short [100]; // 메모리를 해제한다. delete[] p; delete[] p; // Error
동적 메모리 할당의 응용(1) 문자열을 뒤집어서 복사해주는 함수 char* ReverseString(const char* src, int len) { char* reverse = new char [len + 1]; for (int i = 0; i < len; ++i) reverse[i] = src[len - i - 1]; reverse[len] = NULL; return reverse; } int main() char original[] = "NEMODORI"; char* copy = ReverseString( original, 8); cout << original << "\n"; cout << copy << "\n"; delete[] copy; copy = NULL; return 0;
동적 메모리 할당의 응용(2) 실행 결과 프로그램의 구조 [16-14] [16-15]
동적 메모리 할당의 응용(3) 화살표까지 실행되었을 때의 메모리 상태 char* ReverseString(const char* src, int len) { char* reverse = new char [len + 1]; for (int i = 0; i < len; ++i) reverse[i] = src[len - i - 1]; reverse[len] = NULL; return reverse; } int main() char original[] = "NEMODORI"; char* copy = ReverseString( original, 8); cout << original << "\n"; cout << copy << "\n"; delete[] copy; copy = NULL; return 0; [16-16]
동적 메모리 할당의 응용(4) 화살표까지 실행되었을 때의 메모리 상태 char* ReverseString(const char* src, int len) { char* reverse = new char [len + 1]; for (int i = 0; i < len; ++i) reverse[i] = src[len - i - 1]; reverse[len] = NULL; return reverse; } int main() char original[] = "NEMODORI"; char* copy = ReverseString( original, 8); cout << original << "\n"; cout << copy << "\n"; delete[] copy; copy = NULL; return 0; [16-17]
동적 메모리 할당의 응용(5) 화살표까지 실행되었을 때의 메모리 상태 char* ReverseString(const char* src, int len) { char* reverse = new char [len + 1]; for (int i = 0; i < len; ++i) reverse[i] = src[len - i - 1]; reverse[len] = NULL; return reverse; } int main() char original[] = "NEMODORI"; char* copy = ReverseString( original, 8); cout << original << "\n"; cout << copy << "\n"; delete[] copy; copy = NULL; return 0; [16-18]
동적 메모리 할당의 응용(6) 화살표까지 실행되었을 때의 메모리 상태 char* ReverseString(const char* src, int len) { char* reverse = new char [len + 1]; for (int i = 0; i < len; ++i) reverse[i] = src[len - i - 1]; reverse[len] = NULL; return reverse; } int main() char original[] = "NEMODORI"; char* copy = ReverseString( original, 8); cout << original << "\n"; cout << copy << "\n"; delete[] copy; copy = NULL; return 0; [16-19]
동적 메모리 할당의 응용(7) 화살표까지 실행되었을 때의 메모리 상태 char* ReverseString(const char* src, int len) { char* reverse = new char [len + 1]; for (int i = 0; i < len; ++i) reverse[i] = src[len - i – 1]; reverse[len] = NULL; return reverse; } int main() char original[] = "NEMODORI"; char* copy = ReverseString( original, 8); cout << original << "\n"; cout << copy << "\n"; delete[] copy; copy = NULL; return 0; [16-20]
동적 메모리 할당의 응용(8) 화살표까지 실행되었을 때의 메모리 상태 char* ReverseString(const char* src, int len) { char* reverse = new char [len + 1]; for (int i = 0; i < len; ++i) reverse[i] = src[len - i - 1]; reverse[len] = NULL; return reverse; } int main() char original[] = "NEMODORI"; char* copy = ReverseString( original, 8); cout << original << "\n"; cout << copy << "\n"; delete[] copy; copy = NULL; return 0; [16-21]
동적 메모리 할당의 응용(9) 화살표까지 실행되었을 때의 메모리 상태 char* ReverseString(const char* src, int len) { char* reverse = new char [len + 1]; for (int i = 0; i < len; ++i) reverse[i] = src[len - i - 1]; reverse[len] = NULL; return reverse; } int main() char original[] = "NEMODORI"; char* copy = ReverseString( original, 8); cout << original << "\n"; cout << copy << "\n"; delete[] copy; copy = NULL; return 0; [16-22]