일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- dart 언어
- jupyter
- Data Structure
- 도커
- c#
- Unity
- 포인터
- 깃
- 유니티
- Houdini
- C# delegate
- c# 추상 클래스
- git
- c언어
- Algorithm
- docker
- C++
- gitlab
- 다트 언어
- 구조체
- 플러터
- Flutter
- HTML
- vim
- jupyter lab
- c# winform
- C언어 포인터
- Python
- github
- c# 윈폼
- Today
- Total
nomad-programmer
[Programming/C++] 함수 포인터의 변형 (함수 포인터 배열 관련) 본문
함수 포인터는 표기법이 난해하다는 문제를 가지고 있다.
const double* f1(const double arr[], int n);
const double* f2(const double [], int);
const double* f3(const double*, int);
// 함수 포인터 배열
const double* (*pa[3]) (const double*, int) = {f1, f2, f3};
pa는 리턴형이 const double*이고, 매개변수로 const double*와 int형을 받을 수 있는 함수 포인터 3개를 저장할 수 있는 함수 포인터 배열이다.
pa를 가리키는 포인터를 만드려면 어떻게 해야 할까? 명확하게, 선언은 pa를 선언하는 것과 유사하지만, *가 더 필요하다. 새로운 포인터 pd를 호출할 경우, 포인터를 가리키는 것이 필요하지 배열 이름이 필요한 것은 아니다. (*pd)[3]로 선언이 되어햐 하며, 괄호로 *와 pd를 묶어 준다.
*pd[3] // 3개의 포인터 배열
(*pd)[3] // 3개의 원소를 가지는 배열 포인터
다시 말해, pd는 포인터이고 세 개의 원소를 가지는 배열을 가리킨다. pa의 원래 선언에서 뒷부분과 같이 표현될 수 있는며 다음과 같다.
const double* (*(*pd)[3]) (const double *, int) = &pa;
pd가 배열을 가리키는 포인터이고, *pd가 배열이고, *(pd)[ i ] 가 배열의 원소일 때, 함수를 호출하기 위해서는 함수를 가리켜야 한다. 단순하게 함수를 호출하는 구문인 (*pd)[ i ](av, 3)과 *(*pd)[ i ](av, 3)은 함수를 가리키기 위해 리턴된 포인터이다. 다른 방법으로 함수를 호출하기 위해 (*(*pd)[ i ])(av, 3)을 사용하거나, double 값을 가리키기 위해 *(*(*pd)[ i ]) (av, 3) 을 사용할 수 있다.
pa와 &pa의 차이를 확실하게 알아야 한다. pa는 배열의 첫 번째 원소의 주소이다 (&pa[0]와 같다). 그러므로, 단일 포인터의 주소이다. 그러나, &pa는 전체 배열의 주소이다 (세 개 포인터 블록을 말함). 숫자상으로 보면 pa와 &pa는 같은 값을 가질 수도 있으나, 데이터 형이 다르다.
한 가지 큰 차이는 pa+1은 배열 내 다음 원소의 주소이고, &pa+1은 pa 배열 뒤로 12바이트 다음 블록의 주소라는 것이다 (각 주소를 4바이트로 가정).
또 다른 차이는 pa는 첫 번째 원소의 값을 한 번에 얻을 수 있지만, &pa는 같은 값을 얻기 위해 두 단계를 거쳐야 한다.
**&pa == *pa == pa[0]
다음의 예제를 살펴보자.
#include <iostream>
using namespace std;
// 표현식은 다르지만 모두 동일한 함수이다.
const double* f1(const double arr[], int n);
const double* f2(const double[], int n);
const double* f3(const double*, int n);
int main() {
double av[3] = { 123.123, 155.55, 2322.97 };
// 함수를 가리킨다.
const double* (*p1) (const double*, int) = f1;
// C++11 자동 형 변환
auto p2 = f2;
// C++11 이전 버전일 경우 아래 구문으로 대체
//const double* (*p2) (const double*, int) = f2;
cout << "함수 포인터: " << endl;
cout << "주소 값" << endl;
cout << (*p1)(av, 3) << ": " << *(*p1)(av, 3) << endl;
cout << p2(av, 3) << ": " << *p2(av, 3) << endl;
// 포인터들의 배열 pa
// auto는 리스트 초기화에 사용할 수 없다.
const double* (*pa[3]) (const double*, int) = { f1, f2, f3 };
// 그러나, 단일 값을 초기화할 때는 사용할 수 있다.
// pb는 pa의 첫 번째 원소를 가리킨다.
auto pb = pa;
// C++11 이전 버전일 경우 아래 구문으로 대체
//const double* (**pb) (const double*, int) = pa;
cout << endl << "함수 포인터를 원소로 가지는 배열: " << endl;
cout << "주소 값" << endl;
for (int i = 0; i < 3; i++) {
cout << pa[i](av, 3) << ": " << *pa[i](av, 3) << endl;
cout << endl << "함수 포인터를 가리키는 포인터: " << endl;
cout << "주소 값" << endl;
}
for (int i = 0; i < 3; i++) {
cout << pb[i](av, 3) << ": " << *pb[i](av, 3) << endl;
}
// 함수 포인터를 원소로 가지는 배열을 가리키는 포인터
cout << endl << "포인터를 원소로 가지는 배열을 가리키는 포인터: " << endl;
cout << "주소 값" << endl;
// pc를 선언하는 간단한 방법
auto pc = &pa;
// C++11 이전 버전일 경우 아래 구문으로 대체
//const double* (*(*pc)[3]) (const double*, int) = &pa;
cout << (*pc)[0](av, 3) << ": " << *(*pc)[0](av, 3) << endl;
// pd를 선언하는 복잡한 방법
const double* (*(*pd)[3]) (const double*, int) = &pa;
// pdb에 리턴 값 저장
const double* pdb = (*pd)[1](av, 3);
cout << pdb << ": " << *pdb << endl;
// 또 다른 방법
cout << (*(*pd)[2](av, 3)) << ": " << *(*(*pd)[2])(av, 3) << endl;
return 0;
}
const double* f1(const double* arr, int n) {
return arr;
}
const double* f2(const double arr[], int n) {
return arr + 1;
}
const double* f3(const double arr[], int n) {
return arr + 2;
}
// 결과
함수 포인터:
주소 값
0000000BC4AFF5B8: 123.123
0000000BC4AFF5C0: 155.55
함수 포인터를 원소로 가지는 배열:
주소 값
0000000BC4AFF5B8: 123.123
함수 포인터를 가리키는 포인터:
주소 값
0000000BC4AFF5C0: 155.55
함수 포인터를 가리키는 포인터:
주소 값
0000000BC4AFF5C8: 2322.97
함수 포인터를 가리키는 포인터:
주소 값
0000000BC4AFF5B8: 123.123
0000000BC4AFF5C0: 155.55
0000000BC4AFF5C8: 2322.97
포인터를 원소로 가지는 배열을 가리키는 포인터:
주소 값
0000000BC4AFF5B8: 123.123
0000000BC4AFF5C0: 155.55
2322.97: 2322.97
auto의 진가
C++11의 목표 중의 하나가 C++을 보다 쉽게 사용하고, 프로그래머가 세부적인 것은 신경을 덜 쓰면서도 설계에 더 집중할 수 있도록 하는 것이다.
// C++11 자동 형 변환
auto pc = &pa;
// C++98, 직접 복잡하게 선언해야 한다.
const double *(*(pc)[3]) (const double*, int) = &pa;
자동 형 변환은 컴파일러의 임무에 대한 철학적 변화를 반영한 것이다. C++98에서 컴파일러는 프로그래머가 잘못을 하였을 때 알려 줘야 하는 지식에 중점을 두었다. C++11에서는 이것은 최소한으로 가져가면서, 프로그래머가 올바른 선언을 하는 데 도움을 줄 수 있는 지식에 중점을 두고 있다.
여기에 잠재적 결점이 있다. 자동 형 변환은 초기화하는 형과 변수의 형을 매치시키는 것을 보장한다. 그러나, 초기화 시에 잘못된 형을 제공할 가능성이 여전히 존재한다.
// *pa가 아니라 &pa를 사용해야 한다.
auto pc = *pa;
이 선언은 pc를 *pa의 형과 매치를 시키고 컴파일러는 에러를 발생시킨다.
typedef를 이용한 단순화
C++은 선언을 단순하게 하기 위해 auto 외에 다른 방법을 제공한다. typedef 키워드는 데이터 형에 가명을 붙일 수 있다.
// double에 real이라는 가명을 만든다.
typedef double real;
이 기법은 식별자로 가명을 선언하고 앞에 typedef 키워드를 삽입한다. 그래서 함수 포인터 형을 다른 이름으로 만들 수 있다.
// p_func는 형 이름이다.
typedef const double* (*p_func) (const double*, int);
// p1 함수 포인터는 f1을 가리키는 포인터이다.
p_func = p1 = f1;
정교하게 만들기 위해 아래와 같이 사용할 수 있다.
// pa는 3개의 함수 포인터를 저장할 수 있는 배열이다.
p_func pa[3] = {f1, f2, f3};
// pd는 3개의 함수 포인터를 저장하고 있는 배열을 가리킨다.
p_func (*pd)[3] = &pa;
typedef는 단지 입력한 가명을 저장하는 것뿐만 아니라, 코드를 작성하면서 오류를 줄여 주고 프로그램을 더 쉽게 이해할 수 있게 도와준다.
'Programming > C++' 카테고리의 다른 글
[Programming/C++] vcpkg (0) | 2024.08.05 |
---|---|
[Programming/C++] 연산자 오버로딩의 대한 예제 (0) | 2023.06.14 |
[Programming/C++] 다중 재귀 호출 (aka. Divide-And-Conquer) (0) | 2023.03.01 |
[Programming/C++] 문자 함수를 위한 cctype 라이브러리 (0) | 2023.02.21 |
[Programming/C++] 배열의 대안 (vector, array 템플릿 클래스) (0) | 2023.02.15 |