C++/C++ PRIMER PLUS
함수 - C++의 프로그래밍 모듈
레이드리안
2011. 11. 9. 22:07
■함수의 기초
-C++에서 함수를 사용하려면 다음과 같은 작업을 해야한다
*함수 정의 제공
*함수 원형 제공
*함수 호출
라이브러리 함수를 사용한다면, 그라이브러리 함수는 이미 정의되어 컴파일 되어있다. 또한 그 라이브러리 함수의 원형을 제공하기 위해 표준 라이브러리 헤더파일을 사용할수 있다.
●함수 정의
함수는 두그룹으로 나뉠수 있다. 하나는 리턴값이 있는 함수이고, 다른 하나는 리턴값이 없는 함수이다. 리턴값이 없는 함수를
void형 함수라 한다. void형 함수의 일반적인 사용 형식은 다음과 같다.
void functionName(parameterList)
{
statement(s);
return; //생략할수있다.
}
parameterList는 이 함수에 전달되는 전달인자(parameter또는 argument)의 데이터형과 개수를 지정한다.{
statement(s);
return; //생략할수있다.
}
생략할수 있는 return명령문은 함수의 끝을 표시한다. return 이없다면 함수는 닫는중괄호에서 끝난다. void형 함수는
Pascal의 프로시저, FORTRAN의 서브루틴,VASIC의 서브프로그램 프로시저에 해당한다. 일반적으로 void형 함수는
어떤 종류의 행동을 수행한다.
예로, 주어진 횟수(n)만큼 Chrees!를 출력하는 void형 함수는 다음과 같다.
void cheers(int n) //리턴값없음.
{
using namespace std;
for(int i=0; i<n; i++)
cout<<"Cheers!";
cout<<endl;
}
전달인자 리스트 int n은 chrees() 함수를 호출 할때 전달인자로 하나의 int형을 사용할것을 기대한다는 의미이다.{
using namespace std;
for(int i=0; i<n; i++)
cout<<"Cheers!";
cout<<endl;
}
리턴값이 있는 함수는 자신을 호출한 함수에게 되돌려줄 리턴값을 만든다. 리턴값이 있는 함수는 그것이 리턴하는 값과
동일한 데이터 형으로 선언된다. 일반적으로 사용형식은 다음과 같다.
typeName functionName(parameterList)
{
statement(s);
return value; //value는 typeName형으로 변환된다.
}
리턴값이 있는 함수는 자신을 호출한 함수에게 리턴값을 돌려주기 위하여 return 명령문을 사용한다. 이때 상수나 변수 또는 일반적인 표현식이 리턴값이 될수 있다. 그러나 지켜야할 규칙은 그러한 표현식의 값이 typeName형으로 변환 될수 있어야 한다는것이다.(리턴형이 double형으로 선언되어 있는데 함수가 int형 표현식을 리턴한다면, 그 int형 값은 double형으로 변환된다.){
statement(s);
return value; //value는 typeName형으로 변환된다.
}
C++는 리턴값으로 사용할수 있는 데이터형에 한가지 예외를 두고 있다. 즉, 배열은 리턴값으로 사용할수 없다. 정수,부동소수점수, 포인터, 구조체와 객체등 배열을 제외한 그 밖의 데이터형들은 리턴값으로 사용할수 있다.(배열을 직접 리턴할수 없지만, 구조체나 객체의 일부로 되어있는 배열은 리턴할수 있다.)
일반적으로 함수는 자신의 리턴값을 CPU의 지정된 레지스터나 메모리에 복사하는 방법으로 리턴한다. 그러면 함수를 호출한
프로그램이 그 위치에 놓을 값의 데이터형을 서로 일치 시켜야한다. 함수 원형은 호출한 프로그램에게 그위치에 어떤 데이터형을 리턴해야하는 지 알려준다.
함수는 return 명령문을 수행한 후에 종료된다.if else선택구문에서 각각의 선택 사항에 return 명령문이 하나씩 딸려있다면,
함수는 처음 만나는 return 명령문을 수행한 후에 종료된다. 아래와 같은 예에서,else 는 없어도된다. 그러나 else가 있는것이
내용을 이해하기 쉽게 만든다.
int bigger(int a, int b)
{
if(a>b)
return a; //a가 b보다 크면, 함수는 여기서 종료
else
return b; //그렇지 않으면, 함수는 여기서 종료
}
리턴값이 있는 함수는 Pascal,FORTEAN,BASIC의 함수와 비슷하다. 리턴값이 있는 함수는 호출한 프로그램에게 값을 리턴한다. 호출 프로그램은 리턴값을 변수에 대입하거나, 출력하거나, 또는 다른 목적에 사용할수 있다.{
if(a>b)
return a; //a가 b보다 크면, 함수는 여기서 종료
else
return b; //그렇지 않으면, 함수는 여기서 종료
}
double cube(double x) //x의 세제곱을 구하는 함수
{
return x*x*x; //double 형의 리턴값
}
cube(1.2)와 같은 형태로 이 함수를 호출하면, 1.782라는 값이 리턴된다.{
return x*x*x; //double 형의 리턴값
}
●함수 원형과 함수 호출
■함수 전달인자와 값으로 전달하기
C++는 함수 전달인자를 값으로 전달한다. 즉, 전달인자의 수치 값을 함수에 전달한다. 이때 전달된느 값은 새로운 변수에
대입된다.
double volume =cube(side);
위예문에서 side는 5라는 값을 사용한다. cube()의 함수 머리는 다음과 같이 되어있다.double cube(double x)
이함수가 호출되면 x라는 새로운 double형 변수가 생성되고, 거기에 5라는 값이 대입된다. cube()는 원본 데이터가 아닌side복사본을 가지고 작업하기 때문에, 결과적으로 cube()에서 일어나는 사건이 main() 에 있는 원본 데이터에는 영향을 주지않는다. 이렇게 전달되는 값을 넘겨받는 데 쓰이는 변수를 형식 매개변수라 한다.또한 함수에 전달되는 값을 실제전달인자라 한다.
형식 매개변수를 포함하여, 함수안에서 선언된 모든 변수들은 그 함수 안에서만 활동한다. 함수가 호출되면, 컴퓨터는 이들 변수에 필요한 메모리를 할당한다. 함수가 종료되면, 컴퓨터는 이들 변수에 할당했던 메모리를 해제한다. 이러한 변수들은 함수안에서만 활동하기 때문에 지역변수라고 부른다. 이러한 접근 방법은 데이터의 무결성을 높여준다. 이것은 x 라는 이름의 변수를 main() 함수에서 선언하고, 또 다른 함수에서 깥은 이름의 변수 x를 또 선언하더라도, 이들은 서로간에 아무런 관련이 없는 전혀 별개의 변수들이다.
●여러개의 전달인자
함수는 하나 이상의 전달인자를 가진다. 함수 호출에서 전달인자들은 콤마로 분리한다.
void n_chars(char c,int n) //두개의 전달인자
이 함수 머리는 n_chars()라는 함수가 char형 전달인자 하나와 int형 전달인자 하나를 전달받는다 것을 알려준다.매개변수 c와 n에는 이 함수에 전달되는 값 이 대입된다. 함수가 가지고 있는 두개의 매개변수가 데이터형이 같을 지라도 각각의 매개변수에 데이터형을 따로 지정해야한다. 즉, 일반적인 변수 선언에서 가능했던 변수 선언의 결합이 여기서는 허용되지않는다.
void fifi(floata, float b) //각 변수를 독립적으로 선언한다. (o)
void fifi(float a,b) //결합이 허용되지않는다. (x)
다른 함수들에게 했던 것처럼, 함수 원형은 함수 머리에 단순히 세미콜론만 붙이면 된다.void fifi(float a,b) //결합이 허용되지않는다. (x)
void n_chars(char c, int n); //함수 원형, 스타일1
하나의 전달인자를 사용하는 함수와 마찬가지로, 여기에서도 함수정의에 있는 변수이름과 함수 원형에 있는 변수 이름이일치할필요는 없다. 또한 함수 원형에서는 변수 이름을 생략해도된다.
void n_chars(char,int); //함수 원형, 스타일2
그러나 두 매개변수의 데이터형이 같을 경우에는 변수 이름을 제시하여 서로 구별이 되게하는것이 함수 원형을 더 이해하기 쉽게 만든다.
double melon_density(double weight, double volume);
■함수와 배열
int sum_arr(int arr[], int n) //arr =배열이름, n=크기
대괄호는 arr가 배열이라는것을 나타내고, 대괄호안이 비어있는것은 어떠한 크기의 배열도 사용할수 있다는 것을 뜻하는것처럼 보인다. 그러나 그것은 겉으로만 그렇게 보이는것이다. arr는 사실 배열이 아니라 포인터다. 함수의 나머지 부분에서
arr를 배열처럼 사용할수 있다.
●포인터와 배열을 처리하는 함수
C++는 배열이름을 그 첫번째 원소의 주소로 인식한다.
cookies == &cookies[0] //배열 이름은 첫번째 원소의 주소이다.
이규칙에는 두가지 예외가 있다.첫째, 배열 선언은 기억 장소에 레이블을 붙여야 하므로 배열이름을 사용한다.
둘째, sizeof배열 이름에 적용하면 배열의 전체크기가 바이크 단위로 구해진다.
int sum= sum_arr(cookies,Arsize);
여기서 cookies는 배열이름이다. C++의 규칙에 의하면 cookies는 그 배열의 첫번째 원소의 주소이다. 따라서 sum_arr() 함수는 주소를 전달 받는다. 이 배열의 원소는 int형이기 때문에 cookies는 int형을 지시하는 포인터, 즉 int *이 어야 한다. 이말은함수 머리가 정확히 다음과 같아야한다.
int sum_arr(int *arr, int n) //arr=배열이름, n=배열크기
여기서 int *arr이 int arr[]를 대체하였다. C++의 함수 머리나 함수 원형에 사용될때 int arr[]는 동일한 의미이기 떄문에, 두형태 모두 사용할수 있다. 이들은 모두 arr가 int형을 지시하는 포인터라는 것을 의미한다.그러나 배열표기(int arr[])는 그것이 int형을 지시하는 포인터임을 기호적으로 나타낼 뿐만 아니라, int형 배열의 첫번째 원소를 지시한다. 이와는 다른 상황에서 int *arr와 int arr[]표기는 서로 다른 의미를 갖는것을 기억해야한다.
예를들어 함수 몸체에서 포인터를 int tip[]과 같은 형태로 선언할수 없다.
배열 원소에 접근하는데에는 배열 이름이든 포인터이든 대괄호를 사용하는 배열 표기를 사용할수 없다. 그래서 arr가 포인터이든 배열이름이든 상관없이 arr[3]이라는 표기는 그 배열의 네번째 원소를 의미한다. 이시점에서 다음 두가지를 기억하면
도움이 된다.
arr[i] == *(arr+1) //두 가지 형태로 표현된 값
&arr[i] == arr+i //두가지 형태로 표현된 주소값
배열 이름을 포함하여 포인터에 1을 더하는 것은, 실제로는 포인터 지시하는데이터형의 바이트 크기를 더하는것이다.&arr[i] == arr+i //두가지 형태로 표현된 주소값
포인터 덧셈과 배열 첨자 사용은 배열의 시작 위치에서 부터 배열 원소를 카운트 한다는 점에서 동등한 것이다.
●배열을 전달인자로 사용하는 것의 의미
sum_arr(cookies,ArSize)라는 함수 호출은 cookis 배열의 첫번째 원소의 주소와 배열원소의 개수를 sum_arr()함수에 전달한다.sum_arr()함수는 cookies의 주소를 포인터 변수 arr에 대입하고, ArSize를 int형 변수 n에 대입한다. 이말은 배열의 실제 내용을 전달하는것이아니라는 것이다. 그대신에 배열이 어디에있는지(주소),어떤 종류의 배열인지(데이터형),배열원소가 몇개인지(변수n)를 함수에 전달한다.
함수에 일반 변수를 전달하면 복사본을 가지고작업한다. 그러나 함수에 배열을 전달하면 원본을 가지고작업을 한다.
이차이는 C++의 값에 의한 전달 규칙을 위반하지않는다. sum_arr()함수는 여전히 새로운 변수에 대입될 값을 전달하는것이다. 다만 그값이 배열의 내용이 아니라 하나의 주소 일뿐이다.
배열의 주소를 전달인자로 사용하는 것은 전체 배열을 복사하는 것보다 시간과 메모리를 절약할수 있다. 복사본을 사용하는 것은 배열이 클 경우에는 부담이 된다. 복사본을 가지고 작업하면 메모리를 더 많이 요구할 뿐만 아니라 큰 데이터 블록을 복사해야 하므로 시간이 오래걸린다. 반면에 원본을 대상으로 작업하면 부주의에 의해 데이터가 손상될 위험이있다.
그러나 C++의 const제한자가 이문제를 해결할수 있다.
●배열의 내용 출력과 const로 보호하기
배열의 내용을 출력하는 함수를 작성하는것은 쉽다. 배열 이름과 값이 채워진 원소의 개수를 함수에 전달하면, 함수는 루프를
사용하여 각 원소를 출력한다. 그러나 여기에서 고려해야할 사항이있다. 출력이 배열의 원본을 변경시키지 않는다는 보증이다.함수의 목적이 데이터를 변경하는것이아니라면, 데이터가 변경되지않도록 보호해야한다. 일반 변수의 경우에는 C++가 그것을 함수에 값으로 전달하고, 함수는 복사본을 가지고 작업하기 때문에 이 문제가 자동으로 해결된다. 그러나 배열을 사용하는 함수는 원본을 대상으로 작업한다. fill_array()가 동작하는 이유도 배열의 원본을 가지고 작업하기 때문이다. 배열의 값이 변경되지 않도록 하려면, const 키워드를 형식 매개변수를 선언할때 사용할수 있다.
이선언은 포인터 ar가 상수 데이터를 지시하고 있다는 것을 의미하며, ar를 사용해서는 그 데이터를 변경할수 없다는 것을 뜻한다.ar[0]과 같이 그값을 사용할수는 있지만 변경할수는 없다. 이것은 원본 배열이 반드시 상수이어야한다는 의미는아니고,
다만 show_array()함수가 ar를 사용하여 그 데이터를 변경할수 없다는 뜻이다. 따라서 show_array()는 그 배열을 읽기 전용 데이터로 취급한다. show_array()함수 안에서 다음과 같은 명령문을 사용하여 이러한 제한을 어겼다고 가정해보자
이경우, 컴파일러는 잘못된곳에서 컴파일을 멈출것이다.
이 메세지는 C++가 const double ar[]라는 선언을 const double *ar라는 의미로 해석한다는 것을 상기 시켜준다.
따라서, 그 선언은 실제로는 ar가 상수값을 지시한다는 것을 말해준다. 다음은 show_array()함수에 대한코드이다.
●배열의 수정
배열을 대상으로 수행할 세번째 동작은 동일한 재평가율을 각각의 원소에 곱하는것이다.
재평가육,배열이름, 원소의 개수를 함수에 전달인자로 전달해야한다. 리턴값은 필요없으므로 이함수는 다음과같이된다.
void revalue(double r, double ar[], int n)
{
for (int i=0; i<n; i++)
ar[i] *=r;
}
이함수는 원본 배열의 값을 변경할 목적을 가지고 있기 때문에, ar를 선언할때 const를 사용하면 안된다.
●배열의 범위를 사용하는 함수
배열을 처리하는 함수에 접근하는 방법은, 첫번째 전달인자로 배열의 시작위치를 지시하는 포인터를 전달하고, 두번째 전달인자로 배열의 크기를 전달하는것이다.(포인터는 배열이 어디있는지와, 배열에 들어있는 데이터의 종류가 무엇인지알려준다)
그렇게 하면 배열의 모든 데이터를 인식하는데 필요한 정보가 함수에 전달된다.
●포인터와 const
const라는 키워드는 포인터에 두가지 방법으로 사용된다. 첫번째 방법은 상수 객체를 지시하는 포인터를 만드는것이다.
상수 객체를 지시하는 포인터를 사용하여 그 포인터가 지시하는 값을 변경할수 없다.
두번째 방법은 포인터 자신을 상수로 만드는 것이다. 상수 포인터를 사용하여 그 포인터가 지시하는 장소를 변경할수 없다.
다시말해 *pt가 const이므로 변경할수 없다.
두번째의 경우에는 간단한 이유로 C++가 이것을 허락하지않는다. 만약 g_moon의 주소를 pm에 대입할수 있다면, pm을 사용하여 g_moon의 값을 변경할수 있게된다. 그것은 g_moon이 const라는 것을 무의미하게 만든다.
그렇기 때문에 C++는 const변수의 주소를 const가 아닌 일반 포인터에 대입하는 것을 금지한다.
포인터를 지시하는 포인터를 사용할떄 이러한 사정은 더욱 복잡해진다. const가 아닌 포인터를 const포인터에 대입한다는 것은 한다리 건너는 간접 지시인경우에만 사용할수 있다.
그러므로 const가 아닌 주소나 포인터를 const포인터에 대입할수 있다는 규칙은 한다리 건너는 간접 지시인경우에만 유효하다. 즉, 그 포인터가 기본 데이터형을 지시하는 경우에만 유효하다.
const 데이터들을 원소로 가지는 다음과 같은 배열이 있다고 가정을 했을때,
호출을 허용하지않는다.
const를 사용하는 또하나의 방법은 포인터 자신의 값을 변경하지 못하게 막는 것이다.
그러나 finger를 이용하여 sloth의 값을 변경할수는 있다. 그리고 가운데에 있는 선언은 ps를 사용하여 sloth의 값을 변경할수 없다. 그러나 ps가 다른 위치를 가리킬수는 있다. 즉,finger와 *ps는 const이고, *finger와 ps는 const가아니다.
원한다면 const객체를 지시하는 const포인터를 선언할수도 있다.
■함수와 C스타일 문자열
C스타일 문자열은 널 문자로 끝나는 연속된 문자들이다.
문자열을 전달인자로 전달하는 것은 주소를 넘기는것을 의미한다. 그리고 문자열 전달인자를 변경할수 없도록 const를 사용하여 보호 할수 있다.
●C스타일 문자열을 전달인자로 사용하는 함수
문자열을 나타내는 방법은 세가지가 있다.
ⓐchar형의 배열
ⓑ큰따옴표로 묶은 문자열상수(문자열 리터럴)
ⓒ문자열의 주소로 설정된 char형을 지시하는 포인터
세가지 모두 char형을 지시하는 포인터이다. 따라서 이 세가지 모두 문자열 처리 함수에전달인자로 사용할수 있다.
C스타일의 문자열과 일반 배열의 중요한 차이점은 문자열에는 널종결문자가 들어있다는것이다.
따라서 문자열의 크기는 전달인자로 전달할 필요가없다. 대신, 함수는 루프를 사용하여 널 종결문자에 도달할때까지 차례로
문자를 검사해야한다.
●C스타일 문자열을 리턴하는 함수
두개의 전달인자를 사용. 하나는 문자이고 다른 하나는 수이다. 이 함수는 newㄴ를 사용하여 그수의 해당하는 길이의 문자열을 생성하고, 그 문자열을 구성하는 모든 원소를 주어진 문자로 초기화 한다. 그리고 나서, 새로워진 그 문자열을 지시하는 포인터를 리턴한다.,
■함수와 구조체
-구조체 변수와 배열은 둘다 여러개 의 항목을 한군데에 모아서 저장한다는 점에서 비슷하지만, 구조체 변수는 기본적이며, 단일 한 값을 가지는 보통의 변수처럼 행동한다. 구조체 변수는 보통의 변수처럼 함수에 값으로 전달할수 있다. 이때 함수는 구조체의 원본이 아닌 복사본을 대상으로 작업을 한다. 또한 함수는 구조체를 리턴할수 있다.
구조체의 주소를 원한다면 주소연산자&를 사용하면 된다.
구조체를 이용하는 가장 직접적인 방법은 그것을 마치 기본적인 데이터형처럼 사용하는것이다. 이방법은 구조체를 함수에 전달인자로 전달하고, 필요하면 리턴값으로 사용할수도있다. 하지만, 구조체를 값으로 전달할경우 구조체의 덩치가 크다면 구조체를 복사하는데 시간이걸리고 , 기억장소에 대한 요구도 높아진다. 이것은 시스템 성능을 저하시킨다.
그래서 구조체의 내용에 접근할때에는 포인터를 이용하는 방식을 선호한다.
●구조체의 전달과 리턴
- 구조체를 값으로 전달하는것은 구조체의 크기가 비교적 작은 경우에 알맞다.
시간값을 첫번째 맴버, 분값을 두번째 맴버로 가지는 구조체를 나타낼려면 아래와같다.
■함수와 string 클래스 객체
C스타일 문자열과 string 클래스 객체가 같은 목적을 많이 만족시키지만, string 클래스 객체는 배열보다 구조체와 훨씬 관계있다.
■재귀호출
C++ 함수는 자기자신을 호출할수 있는 능력을 가지고있다. 이능력을 재귀호출이라한다.
●단일 재귀호출
재귀함수가 자신을 호출하면 새로 호출되는 함수도 다시 자신을 호출하게 되므로, 호출의 연쇄를 끝내는 어떤것이 없다면 재귀 호출은 끝없이 반복될것이다. 호출의 연쇄를 끝내기 위해 일반적으로 사용하는 방법은 재귀호출 부분을 if명령문의 일부로 만드는 것이다. 예로들어, recurs()라는 void형 재귀함수는 다음과 같은 형태를 가질수 있다.
재귀호출은 흥미로운 연쇄 이벤트를 만든다. if명령문이 참으로 유지되는 동안에, 각각의 recurs()함수 호출은 statements1
부분만 수행하고 statements2부분은 수행을 유보한채 새로운 recurs()함수 호출로 진행한다. if명령문이 거짓이 되면 마지막 함수 호출에서 statements2부분이 수행된다. 마지막 함수 호출이 종료되면, 바로 이전 단계 recurs()호출로 제어가 넘어가 유보해 두었던 statements2부분이 수행된다. 그리고 다시 이 단계의 호출이 종료되면 이보다 앞선 recurs()호출로 제어가 넘어간다. 이러한 식으로 재귀 호출의 사슬을 되짚어 올라간다. 그러므로 recurs()함수의 재귀 호출이 다섯번 일어났다면, statement1부분이 함수가 호출된 순서대로 먼저 다섯번 수행된다. 그러고 나서 recurs()함수가호출된 순서와 반대되는 순서로 statements부분이 다시 다섯번 수행된다. 이러한 식으로 다섯단계까지 아래로 내려간후, 프로그램은 다섯단계를 다시 짚어올라온다.
●다중 재귀 호출
재귀호출은 하나의 작업을 서로 비슷한 두개의 작은 작업으로 반복적으로 분할해 가면서 처리해야하는 상황에서 특별히 유용하다.
■함수를 지시하는 포인터
데이터 항목들과 마찬가지로 함수도 주소를 가지고 있다. 함수의 주소는 그 함수에 해당하는 기계어 코드가 저장되어있는 메모리 블록의 시작 주소이다. 일반적으로 사용자가 함수의 주소를 아는것은 중요하지않고 유용하지도않다. 그러나 어떤 프로그램에서는 그것이 유용할수도 있다. 다른 함수의 주소를 매개변수로 취하는 함수를 작성시에 첫번째 함수가 두번쨰 함수를 메모리에서 찾아내 실행하도록 할수 있다.
●함수 포인터의 기초
-주어진 행수만큼 프로그램 코드를 작성하는데 걸리는 시간을 평가하는 estimate()라는 함수를 설계시 프로그래머는 각자가 원하는 특별한 알고리즘 함수의 주소를 estimate()함수에 전달해야한다.이것을 구현하려면 다음과 같은 절차를 따라야한다
*함수의 주소를 얻는다
*함수를 지시하는 포인터를 선언한다
*함수를 지시하는 포인터를 사용하여 그 함수를 호출한다.
◆함수 주소얻기
think()가 함수라면 think는 그 함수의 주소이다. 그리고 함수를 매개변수로 전달하려면 함수 이름만 전달하면 된다. 함수의 주소를 전달하는것과 함수의 리턴값을 전달하는것은 완전히 다른것이다.
그러나 thought()를 호출하면 think()함수가 먼저 실행되고, think()함수의 리턴값이 thought()함수에 전달된다.
◆함수를 지시하는 포인터의 선언
어떤 데이터형을 지시하는 포인터를 선언하려면, 그 포인터가 정확하게 어떤 데이터형을 지시하는지 선언에서 정확하게 지정해야한다. 마찬가지로, 함수를 지시하는 포인터를 선언할 떄에도, 그 포인터가 지시하는 함수의 데이터형을 지정해야한다. 이것은 포인터 선언이 함수의 리턴형과 함수의 시그내쳐(전달인자리스트)를 알려주어야한다는 것을 의미한다. 즉, 함수원형이 그함수에 대해 제공하는것과 동일한 정보를 선언이 제공해야한다.
시간 평가 함수를 다음과 같이 작성했다면,
double gildong(int); //함수원형
이함수를 지시하는 포인터의 적절한선언은 다음과같다.
double (*pf)(int); //pf는 하나의 int를 전달인자로 취하고
//double형을 리턴하는 함수를 지시한다.
gildong이 있던 자리에 (*pf)가 대신 들어갔을뿐 gildong() 함수의 선언과 모양이 똑같다. gildong이 함수이기 때문에,(*pf)도 함수이다. (*pf)가 함수이면 pf는 함수를 지시하는 포인터이다.
함수를 지시하는 포인터 선언에서는 연산자 우선순위 때문에 *pf를 괄호로 둘러싸야한다.괄호는 *연산자보다 우선순위가 높다. 따라서 *pf(int)는 pf()가 포인터를 리턴하는 함수라는것을 의미하지만, (*pf)(int)는 pf가 함수를 지시하는 포인터라는 것을 의미한다.
gildong()과 pf는 시그내처와 리턴형이 일치해야한다. 일치하지 않는것을 대입하면 컴파일러가 에러를 낸다.
estimate()함수가 gildong()함수를 사용하게 하려면, 다음과 같이 estimate()에 gildong()함수의 주소를 전달해야한다.
●포인터를 사용하여 함수 불러내기
(*pf)가 함수이름과 같은 역할을 한다는것을 기억해야한다. 그러므로 (*pf)를 함수 이름 대신에 사용하면 된다.
■요약
함수는 C++의 프로그래밍 모듈이다. 함수를 사용하려면 함수 정의와 함수 원형이 필요하고, 함수 호출을 사용해야한다. 함수 정의는 그 함수가 무엇을 하는 것인지를 실제로 구현해 놓은 코드이다. 함수 원형은 그 함수에 어떠한 종류의 값이 몇개 전달되어야하며, 그 함수로부터 어떤 종류의 리턴값을 돌려 받는지를 서술하는 함수의 인터페이스이다. 마지막으로 함수 호출은 프로그램에서 그 함수에 전달인자를 전달하여, 프로그램의 제어를 함수에 넘기는것을 말한다.
기본적으로 C++는 전달인자를 함수에 값으로 전달한다. 함수 정의에 있는 형식 매개변수는 함수 호출이 제공하는 값으로 초기화되는 새로운 변수이다. 따라서 C++함수는 데이터의 복사본을 가지고 작업하게 되므로 원본 데이터는 보호된다.
C++는 배열 이름 전달인자를 첫번째 배열 원소의 주소로 간주 한다. 기술적으로 볼때 그 포인터는 원본 주소의 복사본이기 때문에 여전히 값으로 전달된 것이다. 그러나 그 함수는 그 포인터를 사용하여 배열의 원본에 접근한다. 다음의 두선언은 함수의 형식 매개변수를 선언할떄 동등하다.
C++에서는 C스타일의 문자열을 세가지 방식으로 표현한다. 즉, 문자배열,문자열상수, 문자열을 지시하는 포인터를 사용하여 표현한다. 이들은 모두 문자를 지시하는 포인터, 즉 char *형이다. 따라서 char *형 전달인자로 함수에 전달된다. C++는 문자열의 끝을 나타내기 위해 널문자 (\0)를 사용한다. 문자열 함수는 처리하고있는 문자열의 끝을 판단하기 위해 널문자를 사용한다.
C++는 구조체를 기본 데이터형처럼 취급한다. 그러므로 구조체도 함수에 값으로 전달할수 있으며, 함수의 리턴형으로 사용할수도있다. 그러나 구조체의 덩치가 클 경우에는 구조체를 ㅅ지시하는 포인터를 함수에 전달하는것이, 원본 데이터를 가지고 작업할수 있으므로 더 효율적이다.
C++함수는 재귀적으로 자기 자신을 호출할수 있다. 이것은 그 함수의 특정부분에 자기자신을 호출하는 코드가 포함될수 있다는 뜻이다.배열의 내용을 출력하는 함수를 작성하는것은 쉽다. 배열 이름과 값이 채워진 원소의 개수를 함수에 전달하면, 함수는 루프를
사용하여 각 원소를 출력한다. 그러나 여기에서 고려해야할 사항이있다. 출력이 배열의 원본을 변경시키지 않는다는 보증이다.함수의 목적이 데이터를 변경하는것이아니라면, 데이터가 변경되지않도록 보호해야한다. 일반 변수의 경우에는 C++가 그것을 함수에 값으로 전달하고, 함수는 복사본을 가지고 작업하기 때문에 이 문제가 자동으로 해결된다. 그러나 배열을 사용하는 함수는 원본을 대상으로 작업한다. fill_array()가 동작하는 이유도 배열의 원본을 가지고 작업하기 때문이다. 배열의 값이 변경되지 않도록 하려면, const 키워드를 형식 매개변수를 선언할때 사용할수 있다.
void show_array(const double ar[], int n);
다만 show_array()함수가 ar를 사용하여 그 데이터를 변경할수 없다는 뜻이다. 따라서 show_array()는 그 배열을 읽기 전용 데이터로 취급한다. show_array()함수 안에서 다음과 같은 명령문을 사용하여 이러한 제한을 어겼다고 가정해보자
ar[0] +=10;
Cannot modify a const object in function show_array(const double *, int)
따라서, 그 선언은 실제로는 ar가 상수값을 지시한다는 것을 말해준다. 다음은 show_array()함수에 대한코드이다.
void show_array(const double ar[], int n)
{
using namespace std;
for(int i =0; i<n; i++)
{
cout<<(i+1)<<"번 부동산:$";
cout<<ar[i]<<endl;
}
}
●배열의 수정
배열을 대상으로 수행할 세번째 동작은 동일한 재평가율을 각각의 원소에 곱하는것이다.
재평가육,배열이름, 원소의 개수를 함수에 전달인자로 전달해야한다. 리턴값은 필요없으므로 이함수는 다음과같이된다.
void revalue(double r, double ar[], int n)
{
for (int i=0; i<n; i++)
ar[i] *=r;
}
●배열의 범위를 사용하는 함수
배열을 처리하는 함수에 접근하는 방법은, 첫번째 전달인자로 배열의 시작위치를 지시하는 포인터를 전달하고, 두번째 전달인자로 배열의 크기를 전달하는것이다.(포인터는 배열이 어디있는지와, 배열에 들어있는 데이터의 종류가 무엇인지알려준다)
그렇게 하면 배열의 모든 데이터를 인식하는데 필요한 정보가 함수에 전달된다.
●포인터와 const
const라는 키워드는 포인터에 두가지 방법으로 사용된다. 첫번째 방법은 상수 객체를 지시하는 포인터를 만드는것이다.
상수 객체를 지시하는 포인터를 사용하여 그 포인터가 지시하는 값을 변경할수 없다.
두번째 방법은 포인터 자신을 상수로 만드는 것이다. 상수 포인터를 사용하여 그 포인터가 지시하는 장소를 변경할수 없다.
int age=39;
const int *pt =&age;
이선언은 포인터 pt가 const int를 지시하고있음을 말해준다. 그러므로 포인터 pt가 사용하여 그값을 변경할수 없다.const int *pt =&age;
다시말해 *pt가 const이므로 변경할수 없다.
*pt +=1; //pt는 const int를 지시하고 있으므로 사용할수 없다.
cin>>*pt; //마찬가지이유로 사용할수없다.
pt에 대한 이선언은 그것이 지시하는 값이 실제로 상수라는 것을 의미하지않는다. 단지 pt가 관계하는 한에서만 그값이 상수라는것을 의미한다. 예로 pt는 age를 지시하고있지만, age는 const가 아니다. 따라서 age변수를 직접 사용하여 age의 값을 변경할수 있다. 그러나 포인터 pt를 사용해서는 age의 값을 변경할수 없다.cin>>*pt; //마찬가지이유로 사용할수없다.
*pt = 20; //pt는 const int를 지시하고 있으므로 사용할수 없다.
age = 20; //age는 const로 선언되어있지 않았기 떄문에 사용할수 있다.
age = 20; //age는 const로 선언되어있지 않았기 떄문에 사용할수 있다.
const float g_earth =9.80;
const float *pe = &g_earth; //사용가능
cosnt float g_moon=1.63;
float *pm = &g_moon; //사용불가
첫번째의 경우 g_earth나 pe를 사용하여 9.80이라는 값을 변경시킬수 없다. const float *pe = &g_earth; //사용가능
cosnt float g_moon=1.63;
float *pm = &g_moon; //사용불가
두번째의 경우에는 간단한 이유로 C++가 이것을 허락하지않는다. 만약 g_moon의 주소를 pm에 대입할수 있다면, pm을 사용하여 g_moon의 값을 변경할수 있게된다. 그것은 g_moon이 const라는 것을 무의미하게 만든다.
그렇기 때문에 C++는 const변수의 주소를 const가 아닌 일반 포인터에 대입하는 것을 금지한다.
포인터를 지시하는 포인터를 사용할떄 이러한 사정은 더욱 복잡해진다. const가 아닌 포인터를 const포인터에 대입한다는 것은 한다리 건너는 간접 지시인경우에만 사용할수 있다.
int age=39; //age++는 사용할수 있다.
int *pd =&age; //*pd=41은 사용할수 있다.
const int *pt = pd; //*pt=42는 사용할수 없다.
const와 const가 아닌 것을 이런식으로 섞어서 사용하는 포인터 대입은 두다리 건너는 간접지시인 경우에는 더이상 안전하지않다. const와 const가 아닌것을 섞어서 사용하는 것이 허용된다면 다음과 같은것도 가능하기 때문에 안된다.int *pd =&age; //*pd=41은 사용할수 있다.
const int *pt = pd; //*pt=42는 사용할수 없다.
const int **pp2;
int *p1;
const int n=13;
pp2= &p1; //허용되지 않지만 허용된다고 가정한다면
*pp2= &n; //둘다 const인데 p1이 n을 지시하게 만든다.
*p1=10; //const n을 변경하게 만든다.
const가 아닌 주소 (&p1)를 const포인터(pp2)에 대입한다. 그것은 p1을 사용하여 const데이터를 변경하도록 허용한다.int *p1;
const int n=13;
pp2= &p1; //허용되지 않지만 허용된다고 가정한다면
*pp2= &n; //둘다 const인데 p1이 n을 지시하게 만든다.
*p1=10; //const n을 변경하게 만든다.
그러므로 const가 아닌 주소나 포인터를 const포인터에 대입할수 있다는 규칙은 한다리 건너는 간접 지시인경우에만 유효하다. 즉, 그 포인터가 기본 데이터형을 지시하는 경우에만 유효하다.
데이터형 자체가 포인터가 아니라면, const데이터의 주소이든 const가 아닌 데이터의 주소이든 const를 지시하는 포인터에 모두 대입할수 있다. 그러나, const가 아닌데이터의 주소는 const가 아닌 포인터에만 대입할수 있다.
const 데이터들을 원소로 가지는 다음과 같은 배열이 있다고 가정을 했을때,
const int months[12]={31,28,31,30,31,30,31,31,30,31,30,31};
상수 배열의 주소를 대입할 수 없도록 금지하는것은, 상수가 아닌 형식 매개변수를 사용하여 배열의 이름을 전달인자로 함수에 전달할수 없다는것을 의미한다.int sum(int arr[], int n); //const int arr[]이어야한다
...
int j = sum(months, 12); //허용되지않는다.
이함수 호출은 const 포인터인 months를 const가 아닌 포인터 arr에 대입하려고 시도한다. 그러나 컴파일러는 이러한 함수...
int j = sum(months, 12); //허용되지않는다.
호출을 허용하지않는다.
포인터 전달인자를 상수 데이터를 지시하는 포인터로 선언하는 이유는 두가지이다.
●이것은 실수로 데이터를 변경시키는 프로그래밍 에러를 막을수 있다.
●const를 사용하는 함수는 const와 const가 아닌 전달인자를 모두 처리할수 있지만, 함수 원형에서 const를 생략한
함수는 const가 아닌 데이터만 처리할수 있다.
따라서, 가능하다면 형식 포인터 전달인자를 const를 지시하는 포인터로 선언해야한다.
●이것은 실수로 데이터를 변경시키는 프로그래밍 에러를 막을수 있다.
●const를 사용하는 함수는 const와 const가 아닌 전달인자를 모두 처리할수 있지만, 함수 원형에서 const를 생략한
함수는 const가 아닌 데이터만 처리할수 있다.
따라서, 가능하다면 형식 포인터 전달인자를 const를 지시하는 포인터로 선언해야한다.
int age=39;
const int *pt = &age;
두번째 선언에 있는 const는, pt가 지시하는 39라는 값을 변경하지 못하도록 막는다. 그러나 pt자신의 값을 변경하는 것은 막지않는다. 즉, 다음과 같이 pt에 새로운 주소를 대입할수 있다.const int *pt = &age;
int sage=80;
pt = &sage; //O,다른장소를 지시할수 있다.
그러나 pt를 사용하여 그것이 지시하는값(지금은80) 을 변경할수는 없다.pt = &sage; //O,다른장소를 지시할수 있다.
const를 사용하는 또하나의 방법은 포인터 자신의 값을 변경하지 못하게 막는 것이다.
int sloth =3;
const int *ps = &sloth; //const int를 지시하는 포인터
int *const finger = &sloth; //int를 지시하는 const포인터
마지막 선언은 const키워드가 놓이는 위치만 바뀌었다. 이러한 형태의 선언은 finger가 sloth만을 지시하도록 제한한다.const int *ps = &sloth; //const int를 지시하는 포인터
int *const finger = &sloth; //int를 지시하는 const포인터
그러나 finger를 이용하여 sloth의 값을 변경할수는 있다. 그리고 가운데에 있는 선언은 ps를 사용하여 sloth의 값을 변경할수 없다. 그러나 ps가 다른 위치를 가리킬수는 있다. 즉,finger와 *ps는 const이고, *finger와 ps는 const가아니다.
원한다면 const객체를 지시하는 const포인터를 선언할수도 있다.
double trouble = 2.0E30;
const double * const stick = &trouble;
stick은 trouble만을 가리킬수 있고, stick을 사용하여 trouble의 값을 변경할수 없다. stick과 *stick이 모두 const이다.const double * const stick = &trouble;
void show_array(const double ar[], int n);
이 선언에서 const를 사용한 것은, show_array()가 자신에게 전달되는 배열이 어떤 것이든 간에 그 배열에 들어있는 값을 변경할수 없다는것을 뜻한다. 이것은 한다리 건너는 간접지시의 경우에만 동작한다.int gorp =16;
int chips=12;
const int * p_snack = &gorp;
*p_snack =20; //p_snack이 지시하는 값은 변경할수 없다. <NO>
p_snack= &chips; //p_snack은 다른변수를 지시할수 있다. <YES>
int chips=12;
const int * p_snack = &gorp;
p_snack= &chips; //p_snack은 다른변수를 지시할수 있다. <YES>
int gorp=16;
int chips=12;
int const p_snack =&grorp;
*p_snack =20; //p_snack을 사용하여 값을 변경할수 있다.<YES>
p_snack =&chips; //p_snack이 지시하는 변수를 변경할수 없다<NO>
int chips=12;
int const p_snack =&grorp;
*p_snack =20; //p_snack을 사용하여 값을 변경할수 있다.<YES>
■함수와 C스타일 문자열
C스타일 문자열은 널 문자로 끝나는 연속된 문자들이다.
문자열을 전달인자로 전달하는 것은 주소를 넘기는것을 의미한다. 그리고 문자열 전달인자를 변경할수 없도록 const를 사용하여 보호 할수 있다.
●C스타일 문자열을 전달인자로 사용하는 함수
문자열을 나타내는 방법은 세가지가 있다.
ⓐchar형의 배열
ⓑ큰따옴표로 묶은 문자열상수(문자열 리터럴)
ⓒ문자열의 주소로 설정된 char형을 지시하는 포인터
세가지 모두 char형을 지시하는 포인터이다. 따라서 이 세가지 모두 문자열 처리 함수에전달인자로 사용할수 있다.
char ghost[15] = "galloping";
char *str "galumphing";
int n1= strlen(ghost); //ghost는 &ghost[0]이다.
int n2=strlen(str); //char형을 지시하는 포인터
int n3= strlen("gamboling"); //문자열의 주소
일반적으로 말할때에는 전달인자로 문자열을 전달한다고 말할 수있다. 그러나 실제로는 문자열을 구성하는 첫문자의 주소를 전달하는것이다.이것은 문자열 처리 함수의 원형이, 문자열을 나타내는 형식 매개변수의 데이터형으로 char * 를 사용해야한다는 것을 의미한다.char *str "galumphing";
int n1= strlen(ghost); //ghost는 &ghost[0]이다.
int n2=strlen(str); //char형을 지시하는 포인터
int n3= strlen("gamboling"); //문자열의 주소
C스타일의 문자열과 일반 배열의 중요한 차이점은 문자열에는 널종결문자가 들어있다는것이다.
따라서 문자열의 크기는 전달인자로 전달할 필요가없다. 대신, 함수는 루프를 사용하여 널 종결문자에 도달할때까지 차례로
문자를 검사해야한다.
●C스타일 문자열을 리턴하는 함수
두개의 전달인자를 사용. 하나는 문자이고 다른 하나는 수이다. 이 함수는 newㄴ를 사용하여 그수의 해당하는 길이의 문자열을 생성하고, 그 문자열을 구성하는 모든 원소를 주어진 문자로 초기화 한다. 그리고 나서, 새로워진 그 문자열을 지시하는 포인터를 리턴한다.,
■함수와 구조체
-구조체 변수와 배열은 둘다 여러개 의 항목을 한군데에 모아서 저장한다는 점에서 비슷하지만, 구조체 변수는 기본적이며, 단일 한 값을 가지는 보통의 변수처럼 행동한다. 구조체 변수는 보통의 변수처럼 함수에 값으로 전달할수 있다. 이때 함수는 구조체의 원본이 아닌 복사본을 대상으로 작업을 한다. 또한 함수는 구조체를 리턴할수 있다.
구조체의 주소를 원한다면 주소연산자&를 사용하면 된다.
구조체를 이용하는 가장 직접적인 방법은 그것을 마치 기본적인 데이터형처럼 사용하는것이다. 이방법은 구조체를 함수에 전달인자로 전달하고, 필요하면 리턴값으로 사용할수도있다. 하지만, 구조체를 값으로 전달할경우 구조체의 덩치가 크다면 구조체를 복사하는데 시간이걸리고 , 기억장소에 대한 요구도 높아진다. 이것은 시스템 성능을 저하시킨다.
그래서 구조체의 내용에 접근할때에는 포인터를 이용하는 방식을 선호한다.
●구조체의 전달과 리턴
- 구조체를 값으로 전달하는것은 구조체의 크기가 비교적 작은 경우에 알맞다.
시간값을 첫번째 맴버, 분값을 두번째 맴버로 가지는 구조체를 나타낼려면 아래와같다.
■함수와 string 클래스 객체
C스타일 문자열과 string 클래스 객체가 같은 목적을 많이 만족시키지만, string 클래스 객체는 배열보다 구조체와 훨씬 관계있다.
■재귀호출
C++ 함수는 자기자신을 호출할수 있는 능력을 가지고있다. 이능력을 재귀호출이라한다.
●단일 재귀호출
재귀함수가 자신을 호출하면 새로 호출되는 함수도 다시 자신을 호출하게 되므로, 호출의 연쇄를 끝내는 어떤것이 없다면 재귀 호출은 끝없이 반복될것이다. 호출의 연쇄를 끝내기 위해 일반적으로 사용하는 방법은 재귀호출 부분을 if명령문의 일부로 만드는 것이다. 예로들어, recurs()라는 void형 재귀함수는 다음과 같은 형태를 가질수 있다.
void recurs(argument list)
{
statements1
if(test)
recurs(arguments)
statements2
}
조건 검사표현식 test가 마침내 거짓이 되면 재귀호출의 연쇄는 끝나게 된다.{
statements1
if(test)
recurs(arguments)
statements2
}
재귀호출은 흥미로운 연쇄 이벤트를 만든다. if명령문이 참으로 유지되는 동안에, 각각의 recurs()함수 호출은 statements1
부분만 수행하고 statements2부분은 수행을 유보한채 새로운 recurs()함수 호출로 진행한다. if명령문이 거짓이 되면 마지막 함수 호출에서 statements2부분이 수행된다. 마지막 함수 호출이 종료되면, 바로 이전 단계 recurs()호출로 제어가 넘어가 유보해 두었던 statements2부분이 수행된다. 그리고 다시 이 단계의 호출이 종료되면 이보다 앞선 recurs()호출로 제어가 넘어간다. 이러한 식으로 재귀 호출의 사슬을 되짚어 올라간다. 그러므로 recurs()함수의 재귀 호출이 다섯번 일어났다면, statement1부분이 함수가 호출된 순서대로 먼저 다섯번 수행된다. 그러고 나서 recurs()함수가호출된 순서와 반대되는 순서로 statements부분이 다시 다섯번 수행된다. 이러한 식으로 다섯단계까지 아래로 내려간후, 프로그램은 다섯단계를 다시 짚어올라온다.
●다중 재귀 호출
재귀호출은 하나의 작업을 서로 비슷한 두개의 작은 작업으로 반복적으로 분할해 가면서 처리해야하는 상황에서 특별히 유용하다.
■함수를 지시하는 포인터
데이터 항목들과 마찬가지로 함수도 주소를 가지고 있다. 함수의 주소는 그 함수에 해당하는 기계어 코드가 저장되어있는 메모리 블록의 시작 주소이다. 일반적으로 사용자가 함수의 주소를 아는것은 중요하지않고 유용하지도않다. 그러나 어떤 프로그램에서는 그것이 유용할수도 있다. 다른 함수의 주소를 매개변수로 취하는 함수를 작성시에 첫번째 함수가 두번쨰 함수를 메모리에서 찾아내 실행하도록 할수 있다.
●함수 포인터의 기초
-주어진 행수만큼 프로그램 코드를 작성하는데 걸리는 시간을 평가하는 estimate()라는 함수를 설계시 프로그래머는 각자가 원하는 특별한 알고리즘 함수의 주소를 estimate()함수에 전달해야한다.이것을 구현하려면 다음과 같은 절차를 따라야한다
*함수의 주소를 얻는다
*함수를 지시하는 포인터를 선언한다
*함수를 지시하는 포인터를 사용하여 그 함수를 호출한다.
◆함수 주소얻기
think()가 함수라면 think는 그 함수의 주소이다. 그리고 함수를 매개변수로 전달하려면 함수 이름만 전달하면 된다. 함수의 주소를 전달하는것과 함수의 리턴값을 전달하는것은 완전히 다른것이다.
process(think); //process()에 think()의 주소를 전달한다
thought(think()); //thought()에 think()의 리턴값을 전달한다.
process()를 호출하면 process()함수가 process()내부에서 think()함수를 불러낸다.thought(think()); //thought()에 think()의 리턴값을 전달한다.
그러나 thought()를 호출하면 think()함수가 먼저 실행되고, think()함수의 리턴값이 thought()함수에 전달된다.
◆함수를 지시하는 포인터의 선언
어떤 데이터형을 지시하는 포인터를 선언하려면, 그 포인터가 정확하게 어떤 데이터형을 지시하는지 선언에서 정확하게 지정해야한다. 마찬가지로, 함수를 지시하는 포인터를 선언할 떄에도, 그 포인터가 지시하는 함수의 데이터형을 지정해야한다. 이것은 포인터 선언이 함수의 리턴형과 함수의 시그내쳐(전달인자리스트)를 알려주어야한다는 것을 의미한다. 즉, 함수원형이 그함수에 대해 제공하는것과 동일한 정보를 선언이 제공해야한다.
시간 평가 함수를 다음과 같이 작성했다면,
double gildong(int); //함수원형
이함수를 지시하는 포인터의 적절한선언은 다음과같다.
double (*pf)(int); //pf는 하나의 int를 전달인자로 취하고
//double형을 리턴하는 함수를 지시한다.
gildong이 있던 자리에 (*pf)가 대신 들어갔을뿐 gildong() 함수의 선언과 모양이 똑같다. gildong이 함수이기 때문에,(*pf)도 함수이다. (*pf)가 함수이면 pf는 함수를 지시하는 포인터이다.
함수를 지시하는 포인터 선언에서는 연산자 우선순위 때문에 *pf를 괄호로 둘러싸야한다.괄호는 *연산자보다 우선순위가 높다. 따라서 *pf(int)는 pf()가 포인터를 리턴하는 함수라는것을 의미하지만, (*pf)(int)는 pf가 함수를 지시하는 포인터라는 것을 의미한다.
double (*pf)(int); //pf는 double을 리턴하는 함수를 지시하는 포인터
double *pf(int); //pf()는 double형을 지시하는 포인터를 리턴하는 함수.
pf를 바르게 선언한다면, 일치하는 함수의 주소를 그것에 대입할수 있다.double *pf(int); //pf()는 double형을 지시하는 포인터를 리턴하는 함수.
double gildong(int);
double (*pf)(int);
pf= gildong; //pf는 이제 gildong()함수를 지시한다.
double (*pf)(int);
pf= gildong; //pf는 이제 gildong()함수를 지시한다.
gildong()과 pf는 시그내처와 리턴형이 일치해야한다. 일치하지 않는것을 대입하면 컴파일러가 에러를 낸다.
double ned(double);
int ted(int);
double (*pf)(int);
pf=ned; // (X),시그내처가 일치하지않는다.
pf=ted; // (X),리턴형이 일치하지않는다.
int ted(int);
double (*pf)(int);
pf=ned; // (X),시그내처가 일치하지않는다.
pf=ted; // (X),리턴형이 일치하지않는다.
void estimate(int lines, double (*pf)((int));
이 선언의 두번째 전달인자는 int형을 전달인자로 취하고, double형을 리턴하는 함수를 지시하는 포인터이다.estimate()함수가 gildong()함수를 사용하게 하려면, 다음과 같이 estimate()에 gildong()함수의 주소를 전달해야한다.
estimate(50,gildong); //estimate()는 gildong()을 사용한다.
●포인터를 사용하여 함수 불러내기
(*pf)가 함수이름과 같은 역할을 한다는것을 기억해야한다. 그러므로 (*pf)를 함수 이름 대신에 사용하면 된다.
double gildong(int);
double (*pf)(int);
pf = gildong; //이제 pf는 gildong()함수를 지시한다.
double x= gildong(4); //함수이름을 사용한 gildong()함수의 호출
double y=(*pf)(5); //포인터 pf를 사용한 gildong()함수의 호출
실제로는 C++는 pf함수 이름처럼 사용하는것을 허락한다.double (*pf)(int);
pf = gildong; //이제 pf는 gildong()함수를 지시한다.
double x= gildong(4); //함수이름을 사용한 gildong()함수의 호출
double y=(*pf)(5); //포인터 pf를 사용한 gildong()함수의 호출
double y=pf(5); //포인터 pf를 사용한 gildong()함수의 호출
함수는 C++의 프로그래밍 모듈이다. 함수를 사용하려면 함수 정의와 함수 원형이 필요하고, 함수 호출을 사용해야한다. 함수 정의는 그 함수가 무엇을 하는 것인지를 실제로 구현해 놓은 코드이다. 함수 원형은 그 함수에 어떠한 종류의 값이 몇개 전달되어야하며, 그 함수로부터 어떤 종류의 리턴값을 돌려 받는지를 서술하는 함수의 인터페이스이다. 마지막으로 함수 호출은 프로그램에서 그 함수에 전달인자를 전달하여, 프로그램의 제어를 함수에 넘기는것을 말한다.
기본적으로 C++는 전달인자를 함수에 값으로 전달한다. 함수 정의에 있는 형식 매개변수는 함수 호출이 제공하는 값으로 초기화되는 새로운 변수이다. 따라서 C++함수는 데이터의 복사본을 가지고 작업하게 되므로 원본 데이터는 보호된다.
C++는 배열 이름 전달인자를 첫번째 배열 원소의 주소로 간주 한다. 기술적으로 볼때 그 포인터는 원본 주소의 복사본이기 때문에 여전히 값으로 전달된 것이다. 그러나 그 함수는 그 포인터를 사용하여 배열의 원본에 접근한다. 다음의 두선언은 함수의 형식 매개변수를 선언할떄 동등하다.
typeName arr[]
typeName *arr
이들은 둘다 arr가 typeName을 지시하는 포인터라는 것을 의미한다. 그러나 함수의 코드를 작성할 때, arr를 배열 이름처럼 사용하여 배열 원소에 접근할 수 있다.(arr[i]) 포인터를 전달할때 형식 매개변수를 const형을 지시하는 포인터로 선언하면, 원본 데이터를 보호할수 있다. 그리고 배열의 주소를 전달하는것은 배열크기에 대한 정보를 함께 전달하지않는다. 따라서 일반적으로 배열의 크기는 별도의 전달인로 전달해야한다.typeName *arr
C++에서는 C스타일의 문자열을 세가지 방식으로 표현한다. 즉, 문자배열,문자열상수, 문자열을 지시하는 포인터를 사용하여 표현한다. 이들은 모두 문자를 지시하는 포인터, 즉 char *형이다. 따라서 char *형 전달인자로 함수에 전달된다. C++는 문자열의 끝을 나타내기 위해 널문자 (\0)를 사용한다. 문자열 함수는 처리하고있는 문자열의 끝을 판단하기 위해 널문자를 사용한다.
C++는 구조체를 기본 데이터형처럼 취급한다. 그러므로 구조체도 함수에 값으로 전달할수 있으며, 함수의 리턴형으로 사용할수도있다. 그러나 구조체의 덩치가 클 경우에는 구조체를 ㅅ지시하는 포인터를 함수에 전달하는것이, 원본 데이터를 가지고 작업할수 있으므로 더 효율적이다.
C++ 함수의 이름은 함수의 주소처럼 행동한다. 함수를 지시하는 포인터를 전달인자로 사용하여, 호출하기 원하는 두번째 함수의 이름을 첫번째 함수에 전달할수 있다.
연습문제
1. 함수 정의 , 함수원형제공, 함수 호출
2.
a. void igor();
b. float tofu(int);
c. double mpg(double a, double b);
d. long summation(long a[],int size)
e. double doctor(const char *s);
f. void ofcourse(boss a);
g. char *plot(map *pmap);
3. void change_int(arr[], int size, int var)
{
for(int i=0; i<size; i++)
arr[i]=var;
}
4. void arr(int *begin, int *end, int var)
{
for(int *pt = begin; pt !=end; pt++)
*pt=var;
}
5.
7. char[] ={'s','t','r'};
string str="str";
string *p= new str("str");
8.
*"pizza" 는 스트링의 첫번째 문자열을 반환하므로 p가 리턴
"taco"[2]는 "taco"가 배열로 인식되어 2번째 인자 'c'가 리턴
10. glitz를 전달하려면 glitz값을 매개변수로 넣으면된다. 주소값을 전달하기위해서는 &glitz를 전달한다.
값으로 전달해버리면 구조체의 크기에따라 메모리/시간낭비가 심해질수있다.
주소값을넘겨버리면 const사용자를 쓰기전에 원본데이터 파일을 건들여 값이 바뀔수 있다.
11.
int judge(int(*pt)(const char *))