따라서 우리는 이미 프로그램에서 함수를 선언, 정의 및 사용하는 방법을 알고 있습니다. 이 장에서 우리는 그것들의 특별한 형태인 오버로드된 함수에 대해 이야기할 것입니다. 두 함수가 동일한 이름을 갖고 동일한 범위에서 선언되지만 형식 매개변수 목록이 다른 경우 오버로드된 함수라고 합니다. 이러한 함수가 어떻게 선언되고 왜 유용한지 설명하겠습니다. 그런 다음 우리는 그들의 결의 문제를 고려할 것입니다. 프로그램 실행 중에 호출되는 여러 오버로드된 함수에 대한 정보입니다. 이 문제는 C++에서 가장 어려운 문제 중 하나입니다. 세부 사항에 대해 알고 싶은 사람은 장의 끝 부분에 있는 두 섹션을 읽는 것이 흥미로울 것입니다.

9.1. 오버로드된 함수 선언

이제 프로그램에서 함수를 선언, 정의 및 사용하는 방법을 배웠으므로 초과 적재 C++의 또 다른 측면입니다. 오버로딩을 사용하면 다른 유형의 인수에 대해 유사한 작업을 수행하는 동일한 이름의 여러 함수를 가질 수 있습니다.
미리 정의된 오버로드된 기능을 이미 활용했습니다. 예를 들어 표현식을 평가하려면

정수 덧셈 연산이 호출되는 동안 표현식의 평가

1.0 + 3.0

부동 소수점 덧셈을 수행합니다. 이 작업 또는 그 작업의 선택은 사용자가 감지할 수 없을 정도로 이루어집니다. 더하기 연산자는 다른 유형의 피연산자를 수용하기 위해 오버로드됩니다. 컨텍스트를 인식하고 피연산자의 유형에 적합한 연산을 적용하는 것은 프로그래머가 아니라 컴파일러의 책임입니다.
이 장에서는 자신의 오버로드된 함수를 정의하는 방법을 보여줍니다.

9.1.1. 함수 이름을 오버로드해야 하는 이유

내장된 덧셈 연산과 마찬가지로, 같은 동작을 수행하지만 다른 유형의 매개변수에 대해 함수 집합이 필요할 수 있습니다. 전달된 매개변수 값 중 가장 큰 값을 반환하는 함수를 정의한다고 가정합니다. 과부하가 없다면 이러한 각 함수에 고유한 이름을 부여해야 합니다. 예를 들어, 함수의 max() 제품군은 다음과 같습니다.

정수 i_max(int, 정수); int vi_max(상수 벡터 &); int matrix_max(const 행렬 &);

그러나 모두 동일한 작업을 수행합니다. 매개변수 값 중 가장 큰 값을 반환합니다. 사용자의 관점에서 여기에는 단 하나의 작업만 있습니다. 최대값 계산 및 구현 세부 정보는 거의 관심이 없습니다.
언급된 어휘 복잡성은 프로그래밍 환경 제한을 반영합니다. 동일한 범위에서 발생하는 모든 이름은 고유한 엔터티(객체, 함수, 클래스 등)를 참조해야 합니다. 프로그래머가 모든 이름을 기억하거나 어떻게 든 찾아야하기 때문에 실제로 이러한 제한은 특정 불편을 초래합니다. 함수 오버로딩은 이 문제에 도움이 됩니다.
오버로드를 사용하여 프로그래머는 다음과 같이 작성할 수 있습니다.

정수 ix = 최대(j, k); 벡터 벡; //... int iy = max(vec);

이 접근 방식은 많은 상황에서 매우 유용합니다.

9.1.2. 함수 이름을 오버로드하는 방법

C++에서는 매개변수 목록이 매개변수의 수나 유형이 다른 경우 둘 이상의 함수에 동일한 이름을 지정할 수 있습니다. 에 이 예오버로드된 max() 함수를 선언합니다.

intmax(int, 정수); int max(상수 벡터 &); int max(const 행렬 &);

오버로드된 각 선언에는 적절한 매개변수 목록이 있는 별도의 max() 함수 정의가 필요합니다.
함수 이름이 일부 범위에서 두 번 이상 선언되면 두 번째(및 후속) 선언은 컴파일러에서 다음과 같이 해석됩니다.

  • 두 함수의 매개변수 목록이 숫자로 다른 경우 또는 매개변수 유형, 함수는 오버로드된 것으로 간주됩니다. // 오버로드된 함수 void print(const string &); 무효 인쇄(벡터 &);
  • 두 함수 선언의 반환 유형과 매개변수 목록이 같으면 두 번째 선언이 반복된 것으로 간주됩니다. // 동일한 함수 선언 void print(const string &str); 무효 인쇄(const string &); 선언을 비교할 때 매개변수 이름은 고려되지 않습니다.
    두 함수의 매개변수 목록이 같지만 반환 유형이 다른 경우 두 번째 선언은 잘못된 것으로 간주되고(첫 번째 선언과 일치하지 않음) 컴파일러에 의해 다음과 같은 오류 플래그가 지정됩니다. unsigned int max(int ​​​​i1, 정수 i2); int max(int ​​i1, int i2);
    // 오류: 유형만 다릅니다.
    // 반환 값

오버로드된 함수는 반환 유형만 다를 수 없습니다. 두 함수의 매개변수 목록이 기본 인수 값만 다르면 두 번째 선언이 반복되는 것으로 간주됩니다.

// 같은 함수 선언 int max (int *ia, int sz); 정수 최대 (int *ia, 정수 = 10);

typedef 키워드는 대체 이름을 생성합니다. 기존 유형데이터, 새로운 유형이 생성되지 않습니다. 따라서 두 함수의 매개변수 목록이 하나는 typedef를 사용하고 다른 하나는 typedef가 별칭인 유형을 사용한다는 점에서만 다른 경우 목록은 calc() 함수의 다음 두 선언에서와 같이 동일한 것으로 간주됩니다. 이 경우 반환 값이 앞에서 지정한 것과 다르기 때문에 두 번째 선언에서 컴파일 오류가 발생합니다.

// typedef는 새로운 유형을 도입하지 않습니다. typedef double DOLLAR; // 오류: 매개변수 목록은 같지만 // 반환 유형이 다릅니다. extern DOLLAR calc(DOLLAR); 외부 정수 계산(이중);

const 또는 volatile 지정자는 이러한 비교에서 고려되지 않습니다. 따라서 다음 두 선언은 동일한 것으로 간주됩니다.

// 같은 함수 선언 void f(int); 무효 f(상수 정수);

const 지정자는 함수 정의 내에서만 중요합니다. 이는 함수 본문에서 매개변수 값을 변경하는 것이 금지되어 있음을 나타냅니다. 그러나 값으로 전달된 인수는 일반 트리거 변수처럼 함수 본문에서 사용할 수 있습니다. 함수 외부에서는 변경 사항이 표시되지 않습니다. (인수 전달 방법, 특히 값으로 전달하는 방법은 섹션 7.3에서 설명합니다.) 값으로 전달된 매개변수에 const 지정자를 추가해도 해석에 영향을 미치지 않습니다. int 유형의 모든 값은 f(const int) 함수와 마찬가지로 f(int)로 선언된 함수에 전달할 수 있습니다. 둘 다 동일한 인수 값 집합을 사용하기 때문에 위의 선언은 오버로드된 것으로 간주되지 않습니다. f()는 다음과 같이 정의할 수 있습니다.

무효 f(int i) ( )

무효 f(const int i) ( )

동일한 기능이 두 번 정의되기 때문에 하나의 프로그램에 이 두 정의가 있으면 오류입니다.
그러나 const 또는 volatile 지정자가 포인터 또는 참조 형식의 매개 변수에 적용되면 선언을 비교할 때 고려됩니다.

// 다른 함수가 선언됨 void f(int*); 무효 f(const int*); // 여기에서 다른 함수가 선언됩니다.
무효 f(int&);
무효 f(const int&);

9.1.3. 함수 이름에 과부하가 걸리지 않는 경우

어떤 경우에 이름 오버로딩이 도움이 되지 않습니까? 예를 들어, 함수에 다른 이름을 할당할 때 프로그램을 더 쉽게 읽을 수 있습니다. 여기 몇 가지 예가 있어요. 다음 함수는 동일한 추상 날짜 유형에서 작동합니다. 표면적으로는 과부하에 좋은 후보입니다.

무효 setDate(날짜&, 정수, 정수, 정수); 날짜 &convertDate(const string &); 무효 printDate(const Date&);

이러한 함수는 동일한 데이터 유형인 Date 클래스에서 작동하지만 의미상 다른 작업을 수행합니다. 이 경우, 다른 이름의 사용과 관련된 어휘적 복잡성은 데이터 유형에 대한 일련의 작업을 제공하고 이러한 작업의 의미에 따라 기능을 명명하는 프로그래머의 규칙에서 비롯됩니다. 사실, C++ 클래스 메커니즘은 그러한 규칙을 불필요하게 만듭니다. 이러한 함수를 Date 클래스의 멤버로 만들어야 하지만 작업의 의미를 반영하는 다른 이름을 남겨야 합니다.

#포함 class Date ( public: set(int, int, int); Date& convert(const string &); void print(); // ...
};

다른 예를 들어보겠습니다. 다음 다섯 가지 Screen 멤버 함수는 동일한 클래스의 멤버인 화면 커서에 대해 다양한 작업을 수행합니다. 일반 이름 move()로 이러한 함수를 오버로드하는 것이 합리적으로 보일 수 있습니다.

화면 이동 및 홈(); 스크린& moveAbs(int, int); Screen& moveRel(int, int, char *방향); 화면 이동X(int); 화면 이동Y(int);

그러나 마지막 두 함수는 동일한 매개변수 목록을 가지므로 오버로드할 수 없습니다. 서명을 고유하게 만들기 위해 하나의 함수로 결합해 보겠습니다.

// moveX()와 moveY()를 합친 함수 Screen& move(int, char xy);

이제 모든 함수에는 다른 매개변수 목록이 있으므로 move()라는 이름으로 오버로드될 수 있습니다. 그러나 이렇게 해서는 안 됩니다. 다른 이름에는 정보가 포함되어 있지 않으면 프로그램을 이해하기가 더 어렵습니다. 예를 들어, 이러한 기능에 의해 수행되는 커서 이동 작업은 다릅니다. 예를 들어 moveHome()은 화면의 왼쪽 상단 모서리로 특수한 종류의 이동을 수행합니다. 아래의 두 호출 중 어느 것이 더 사용자 친화적이고 기억하기 더 쉬울까요?

// 어떤 호출이 더 명확합니까? myScreen.home(); // 우리는 이것을 생각합니다! myScreen.move();

어떤 경우에는 함수 이름을 오버로드하거나 다른 이름을 할당할 필요가 없습니다. 기본 인수 값을 사용하면 여러 함수를 하나로 결합할 수 있습니다. 예를 들어 커서 제어 기능

MoveAbs(int, 정수); moveAbs(int, int, char*);

char* 유형의 세 번째 매개변수가 있는 경우 다릅니다. 구현이 유사하고 세 번째 인수에 대해 합리적인 기본값을 찾을 수 있는 경우 두 함수를 하나로 대체할 수 있습니다. 이 경우 값이 0인 포인터가 기본값의 역할에 적합합니다.

이동(int, int, char* = 0);

특정 기능은 응용 프로그램 논리에서 필요할 때 사용해야 합니다. 오버로드된 함수가 있다는 이유만으로 프로그램에 포함할 필요는 없습니다.

9.1.4. 오버로딩 및 범위 A

오버로드된 모든 함수는 동일한 범위에서 선언됩니다. 예를 들어, 로컬로 선언된 함수는 오버로드하지 않고 단순히 전역 함수를 숨깁니다.

#포함 무효 인쇄(const string &); 무효 인쇄(이중); // print() 오버로드 void fooBar(int ival)
{
// 분리된 범위: print()의 두 구현을 모두 숨깁니다.
외부 무효 인쇄(int); // 오류: 이 영역에는 print(const string &)가 표시되지 않습니다.
print("값: ");
인쇄(이발); // 정답: print(int)가 보입니다.
}

각 클래스는 자체 범위를 정의하므로 두 개의 다른 클래스의 멤버인 함수는 서로 오버로드되지 않습니다. (클래스 멤버 함수는 13장에서 다룹니다. 클래스 멤버 함수에 대한 과부하 해결은 15장에서 다룹니다.)
네임스페이스 내에서 이러한 함수를 선언하는 것도 허용됩니다. 그들 각각은 또한 그것과 관련된 자체 범위를 가지고 있으므로 다른 범위에서 선언된 함수가 서로 오버로드되지 않습니다. 예를 들어:

#포함 namespace IBM ( extern void print(const string &); extern void print(double); // 오버로드 print() ) namespace Disney ( // 별도의 범위: // IBM 네임스페이스에서 print() 함수를 오버로드하지 않음 extern void 인쇄(int); )

using 선언과 using 지시문을 사용하면 네임스페이스 멤버를 다른 범위에서 사용할 수 있습니다. 이러한 메커니즘은 오버로드된 함수의 선언에 약간의 영향을 미칩니다. (선언문 사용과 지시문 사용은 8.6절에서 논의했습니다.)

using-declaration은 함수 오버로딩에 어떤 영향을 줍니까? 선언이 발생하는 범위에서 네임스페이스 멤버에 대한 별칭을 도입한다는 점을 상기하십시오. 다음 프로그램에서 이러한 선언은 무엇을 합니까?

네임스페이스 libs_R_us ( int max(int, int); int max(double, double); extern void print(int);
외부 무효 인쇄(이중);
) // 선언문 사용
libs_R_us::max 사용;
libs_R_us::print(double) 사용; // 오류 무효 func()
{
최대(87, 65); // libs_R_us::max(int, int) 호출
최대(35.5, 76.6); // libs_R_us::max(double, double) 호출

첫 번째 using 선언은 두 libs_R_us::max 함수를 전역 범위로 가져옵니다. 이제 모든 max() 함수를 func() 내부에서 호출할 수 있습니다. 인수 유형에 따라 호출할 함수가 결정됩니다. 두 번째 using 선언은 버그입니다. 매개변수 목록을 가질 수 없습니다. libs_R_us::print() 함수는 다음과 같이 선언됩니다.

libs_R_us::print 사용하기

using 선언은 항상 지정된 이름을 가진 모든 오버로드된 함수를 사용할 수 있도록 합니다. 이 제한은 libs_R_us 네임스페이스 인터페이스가 위반되지 않도록 합니다. 통화의 경우 분명히

인쇄(88);

네임스페이스 작성자는 libs_R_us::print(int) 함수가 호출될 것으로 예상합니다. 사용자가 범위에 여러 오버로드된 함수 중 하나만 선택적으로 포함하도록 허용하면 프로그램의 동작을 예측할 수 없게 됩니다.
using 선언이 이미 존재하는 이름을 가진 함수를 범위로 가져오면 어떻게 됩니까? 이러한 함수는 using 선언이 발생하는 바로 그곳에서 선언된 것처럼 보입니다. 따라서 도입된 함수는 주어진 범위에 있는 모든 오버로드된 함수의 이름을 확인하는 프로세스에 참여합니다.

#포함 네임스페이스 libs_R_us ( extern void print(int); extern void print(double); ) extern void print(const string &); // libs_R_us::print(int) 및 libs_R_us::print(double)
// 오버로드 print(const string &)
libs_R_us::print 사용; 무효 fooBar(int ival)
{
// print(const string &)
}

using 선언은 두 개의 선언을 전역 범위에 추가합니다. 하나는 print(int)용이고 다른 하나는 print(double)용입니다. 그것들은 libs_R_us 공간의 별칭이며 전역 print(const string &)가 이미 존재하는 print라는 오버로드된 함수 세트에 포함됩니다. fooBar의 인쇄 과부하를 해결할 때 세 가지 기능이 모두 고려됩니다.
using 선언이 동일한 이름과 동일한 매개변수 목록을 가진 함수가 이미 있는 범위에 일부 함수를 도입하면 오류로 간주됩니다. use-declaration은 print(int)가 이미 전역 범위에 있는 경우 libs_R_us 네임스페이스에서 print(int) 함수의 별칭을 지정할 수 없습니다. 예를 들어:

네임스페이스 libs_R_us ( void print(int); void print(double); ) void print(int); libs_R_us::print 사용; // 오류: 반복 선언 print(int) void fooBar(int ival)
{
인쇄(이발); // 어떤 인쇄? ::print 또는 libs_R_us::print
}

우리는 using 선언과 오버로드된 함수가 어떻게 관련되어 있는지 보여주었습니다. 이제 using 지시문을 사용하는 구체적인 방법을 살펴보겠습니다. using 지시문을 사용하면 네임스페이스 멤버가 해당 공간 외부에 선언된 것으로 나타나 새 범위에 추가됩니다. 이 범위에 같은 이름을 가진 함수가 이미 있으면 오버로드가 발생합니다. 예를 들어:

#포함 네임스페이스 libs_R_us ( extern void print(int); extern void print(double); ) extern void print(const string &); // 지시문 사용
// print(int), print(double) 및 print(const string &)는 요소입니다.
// 동일한 오버로드된 함수 집합
네임스페이스 libs_R_us 사용 무효 fooBar(int ival)
{
print("값: "); // 전역 함수 호출
// print(const string &)
인쇄(이발); // libs_R_us::print(int) 호출
}

이것은 여러 using 지시문이 있는 경우에도 마찬가지입니다. 다른 공간의 구성원인 동일한 이름의 함수는 동일한 집합에 포함됩니다.

네임스페이스 IBM ( int print(int); ) 네임스페이스 Disney ( double print(double); ) // using 지시문 // ​​​​​ 네임스페이스를 사용하여 다른 네임스페이스에서 // ​많은 오버로드된 함수를 생성합니다. IBM; 디즈니 네임스페이스 사용 긴 이중 인쇄(긴 이중); 정수 메인() (
인쇄(1); // IBM::print(int) 호출
인쇄(3.1); // 디즈니 호출::print(double)
반환 0;
}

전역 범위에서 print라는 오버로드된 함수 집합에는 print(int), print(double) 및 print(long double) 함수가 포함됩니다. 원래 다른 네임스페이스에서 정의되었지만 모두 오버로드 해결 중에 main()에서 고려됩니다.
다시 말하지만, 오버로드된 함수는 동일한 범위에 있습니다. 특히, 선언을 사용하고 다른 범위의 이름을 사용할 수 있도록 하는 지시문을 사용한 결과로 끝납니다.

9.1.5. extern "C" 지시문 및 오버로드된 A 함수

섹션 7.7에서 extern "C" 바인드 지시문이 C++ 프로그램에서 일부 객체가 C 부분에 있음을 나타내는 데 사용될 수 있음을 보았습니다. 이 지시문이 오버로드된 함수 선언에 어떤 영향을 줍니까? C++와 C로 작성된 함수가 같은 집합에 포함될 수 있습니까?
바인드 지시문은 오버로드된 많은 함수 중 하나만 지정할 수 있습니다. 예를 들어 다음 프로그램은 올바르지 않습니다.

// 오류: 두 개의 오버로드된 함수에 대해 지정된 지시문 extern "C" extern "C" void print(const char*); extern "C" 무효 print(int);

오버로드된 calc() 함수의 다음 예는 extern "C" 지시문의 일반적인 사용을 보여줍니다.

ClassSmallInt(/* ... */); 클래스 BigNum(/* ... */); // C로 작성된 함수는 프로그램에서 둘 다 호출할 수 있습니다.
// C로 작성되거나 C++로 작성된 프로그램에서 작성됩니다.
// C++ 함수는 클래스인 매개변수를 처리합니다.
extern "C" 이중 계산(이중);
extern SmallInt calc(const SmallInt&);
extern BigNum calc(const BigNum&);

C로 작성된 calc() 함수는 C와 C++ 프로그램 모두에서 호출할 수 있습니다. 다른 두 함수는 클래스를 매개변수로 사용하므로 C++ 프로그램에서만 사용할 수 있습니다. 선언의 순서는 중요하지 않습니다.
bind 지시문은 호출할 함수를 결정하는 것과 관련이 없습니다. 매개변수 유형만 중요합니다. 전달된 인수의 유형과 가장 잘 일치하는 함수가 선택됩니다.

스몰린트 si = 8; int main() ( calc(34); // C 호출 function calc(double) calc(si); // C++ 호출 function calc(const SmallInt &) // ... return 0; )

9.1.6. 오버로드된 함수에 대한 포인터 A

많은 오버로드된 함수 중 하나에 대한 포인터를 선언할 수 있습니다. 예를 들어:

외부 무효 ff(벡터 ); 외부 무효 ff(부호 없는 정수); // pf1이 가리키는 함수는 무엇입니까?
무효(*pf1)(부호 없는 정수) =

ff() 함수가 오버로드되기 때문에 &ff 이니셜라이저만으로는 올바른 옵션을 선택하기에 충분하지 않습니다. 포인터를 초기화하는 함수를 이해하기 위해 컴파일러는 모든 오버로드된 함수 집합에서 포인터가 참조하는 함수와 동일한 반환 유형 및 매개 변수 목록을 가진 함수를 찾습니다. 우리의 경우 ff(unsigned int) 함수가 선택됩니다.
그러나 포인터의 유형과 정확히 일치하는 함수가 없으면 어떻게 될까요? 그러면 컴파일러는 다음과 같은 오류 메시지를 표시합니다.

외부 무효 ff(벡터 ); 외부 무효 ff(부호 없는 정수); // 오류: 일치 항목을 찾을 수 없음: 잘못된 매개변수 목록 void (*pf2)(int) = // 오류: 일치 항목을 찾을 수 없음: 잘못된 반환 유형 double (*pf3)(vector ) = &ff;

할당도 비슷한 방식으로 작동합니다. 포인터 값이 오버로드된 함수의 주소여야 하는 경우 함수에 대한 포인터 유형을 사용하여 할당 연산자의 오른쪽에 있는 피연산자를 선택합니다. 그리고 컴파일러가 원하는 유형과 정확히 일치하는 함수를 찾지 못하면 오류 메시지가 표시됩니다. 따라서 함수 포인터 간의 유형 변환은 수행되지 않습니다.

행렬 계산(const 행렬 &); intcalc(int, int); 정수(*pc1)(int, 정수) = 0;
int (*pc2)(int, double) = 0; // ...
// 정답: 함수 calc(int, int)가 선택되었습니다.
pc1 = // 오류: 일치하지 않음: 잘못된 두 번째 매개변수 유형
pc2=

9.1.7. 보안 바인딩 A

오버로딩을 사용하면 프로그램이 매개변수 목록이 다른 동일한 이름의 여러 기능을 가질 수 있다는 인상을 받습니다. 그러나 이러한 어휘상의 편리함은 원본 텍스트 수준에서만 존재합니다. 대부분의 컴파일 시스템에서 실행 코드를 생성하기 위해 이 텍스트를 처리하는 프로그램은 모든 이름이 고유해야 합니다. 링크 편집기는 일반적으로 외부 링크어휘적으로. 그러한 편집기가 이름 인쇄를 두 번 이상 발견하면 유형 분석으로 구별할 수 없습니다(유형 정보는 일반적으로 이 시점에서 손실됩니다). 따라서 재정의된 문자 인쇄에 대한 메시지를 인쇄하고 종료합니다.
이 문제를 해결하기 위해 매개변수 목록과 함께 함수 이름을 장식하여 고유한 내부 이름을 부여합니다. 컴파일러 다음에 호출된 프로그램은 이 내부 이름만 봅니다. 이 이름 확인이 정확히 수행되는 방법은 구현에 따라 다릅니다. 일반적인 아이디어는 매개변수의 수와 유형을 문자열로 표시하고 이를 함수 이름에 추가하는 것입니다.
섹션 8.2에서 언급했듯이 이러한 코딩은 특히 서로 다른 파일에 있는 서로 다른 매개변수 목록을 가진 같은 이름의 두 함수 선언이 링커에서 동일한 함수의 선언으로 인식되지 않도록 보장합니다. 이 방법은 링크 편집 단계에서 과부하된 기능을 구별하는 데 도움이 되기 때문에 보안 링크에 대해 이야기하고 있습니다.
이름 장식은 extern "C" 지시어로 선언된 함수에는 적용되지 않습니다. 많은 오버로드된 함수 중 하나만 순수 C로 작성할 수 있기 때문입니다. extern "C"로 선언된 서로 다른 매개변수 목록을 가진 두 함수는 링커에서 하나로 해석됩니다. 그리고 같은 캐릭터.

운동 9.1

오버로드된 함수를 선언해야 하는 이유는 무엇입니까?

연습 9.2

다음 호출이 정확하도록 error() 함수의 오버로드된 버전을 선언하는 방법:

정수 인덱스; 정수 상한; 문자 선택 발; // ... error("배열이 범위를 벗어남: ", index, upperBound); error("0으로 나누기"); error("잘못된 선택", selectVal);

운동 9.3

다음 각 예에서 두 번째 선언의 효과를 설명합니다.

(a) intcalc(int, int); int calc(const int, const int); (b) 정수 get(); 더블 get(); (c) 정수 *재설정(int *); double *reset(double *): (d) extern "C" int compute(int *, int); extern "C" 이중 계산(이중 *, 이중);

운동 9.4

다음 초기화 중 오류가 발생하는 것은 무엇입니까? 왜요?

(a) 무효 리셋(int *); 무효(*pf)(무효 *) = 리셋; (b) intcalc(int, int); int (*pf1)(int, int) = 계산; (c) extern "C" int compute(int *, int); int (*pf3)(int*, int) = 계산; (d) 무효(*pf4)(const 행렬 &) = 0;

9.2. 과부하 해결의 3단계

함수 과부하 해결호출되어야 하는 오버로드된 집합에서 함수를 선택하는 프로세스라고 합니다. 이 프로세스는 호출될 때 지정된 인수를 기반으로 합니다. 예를 고려하십시오.

T t1, t2; 무효 f(int, int); 무효 f(float, float); 정수 메인() (
f(t1, t2);
반환 0;
}

여기서 오버로드 해결 과정에서 T의 유형에 따라 표현식 f(t1,t2) 또는 오류를 처리할 때 f(int,int) 또는 f(float,float) 함수가 호출되는지 여부가 결정됩니다. 기록됩니다.
함수 오버로드 해결은 C++ 언어의 가장 복잡한 측면 중 하나입니다. 모든 세부 사항을 이해하려고 시도하면 초보 프로그래머는 심각한 어려움에 직면하게 될 것입니다. 따라서 이 섹션에서는 짧은 리뷰오버로드 해결이 작동하는 방식에 대해 설명하여 프로세스에 대한 최소한의 인상을 얻을 수 있습니다. 더 알고 싶은 사람들을 위해 다음 두 섹션에서 더 자세한 설명을 제공합니다.
함수 오버로드를 해결하는 프로세스는 세 단계로 구성되며 다음 예제에서 보여줍니다.

무효 f(); 무효 f(int); 무효 f(더블, 더블 = 3.4); 무효 f(문자 *, 문자 *); 무효 메인() (
f(5.6);
반환 0;
}

함수 과부하를 해결할 때 다음 단계가 수행됩니다.

  1. 주어진 호출에 대해 오버로드된 함수 집합과 함수에 전달된 인수 목록의 속성이 강조 표시됩니다.
  2. 주어진 인수로 호출할 수 있는 오버로드된 함수의 수와 유형을 고려하여 선택됩니다.
  3. 호출과 가장 일치하는 함수를 찾습니다.

각 항목을 차례로 살펴보겠습니다.
첫 번째 단계는 이 호출에서 고려할 오버로드된 함수 집합을 식별하는 것입니다. 이 집합에 포함된 기능을 후보라고 합니다. 후보 함수는 호출된 것과 동일한 이름을 가진 함수이며 해당 선언은 호출 시점에서 볼 수 있습니다. 이 예에는 f(), f(int), f(double, double) 및 f(char*, char*)의 네 가지 후보가 있습니다.
그 후 전달된 인수 목록의 속성이 식별됩니다. 그들의 수와 유형. 이 예에서 목록은 두 개의 이중 인수로 구성됩니다.
두 번째 단계에서는 후보 집합 중에서 실행 가능한 것(주어진 인수로 호출할 수 있는 것)을 선택합니다.영속 함수는 호출된 함수에 전달된 실제 인수만큼 많은 형식 매개 변수를 가지거나 그 이상을 갖지만 그런 다음 각 추가 매개변수에 대해 기본값입니다. 함수가 지속성으로 간주되려면 호출에 전달된 실제 인수가 선언에 지정된 형식 매개변수 유형으로 변환되어야 합니다.

이 예에는 주어진 인수로 호출할 수 있는 두 개의 확립된 함수가 있습니다.

  • f(int) 함수는 매개변수가 하나만 있고 실제 double 인수가 형식 int 매개변수로 변환되기 때문에 살아남았습니다.
  • f(double,double) 함수는 두 번째 인수에 대한 기본값이 있고 첫 번째 형식 매개변수가 실제 인수의 유형인 double 유형이기 때문에 살아남았습니다.

두 번째 단계 후에 안정적인 기능이 발견되지 않으면 호출이 잘못된 것으로 간주됩니다. 그러한 경우, 우리는 대응이 부족하다고 말합니다.
세 번째 단계는 호출 컨텍스트에 가장 적합한 함수를 선택하는 것입니다. 이러한 기능을 베스트 스탠딩(또는 베스트 핏)이라고 합니다. 이 단계에서 실제 인수 유형을 설정된 함수의 형식 매개변수 유형으로 변환하는 데 사용되는 변환의 순위가 매겨집니다. 가장 적합한 기능은 다음 조건이 충족되는 기능으로 간주됩니다.
실제 인수에 적용된 변환은 잘 확립된 다른 함수를 호출하는 데 필요한 변환보다 나쁘지 않습니다.
일부 인수의 경우 적용된 변환이 잘 확립된 다른 함수에 대한 호출에서 동일한 인수를 캐스트하는 데 필요한 변환보다 낫습니다.
유형 변환과 그 순위는 섹션 9.3에서 자세히 설명합니다. 여기에서는 예제의 순위 변환만 간단히 살펴보겠습니다. 설정된 함수 f(int)의 경우 double 형식의 실제 인수를 int로 표준 캐스트해야 합니다. 확립된 함수 f(double,double)의 경우 실제 인수 double의 유형은 형식 매개변수의 유형과 정확히 일치합니다. 정확한 일치는 표준 변환보다 낫기 때문에(변환이 없는 것이 항상 변환을 갖는 것보다 낫습니다), f(double,double)은 이 호출에 가장 적합한 함수로 간주됩니다.
세 번째 단계에서 확립된 기능 중 가장 좋은 것만 찾을 수 없는 경우, 즉 다른 모든 것보다 더 적합한 확립된 기능이 없는 경우 호출은 모호한 것으로 간주됩니다. 잘못된.
(섹션 9.4에서는 모든 오버로드 해결 단계에 대해 자세히 설명합니다. 해결 프로세스는 오버로드된 클래스 멤버 함수 및 오버로드된 연산자를 호출할 때도 사용됩니다. 섹션 15.10에서는 클래스 멤버 함수에 적용되는 오버로드 해결 규칙에 대해 설명하고 섹션 15.11에서는 (오버로드된 연산자. 오버로드 해결은 템플릿에서 인스턴스화된 함수도 고려해야 합니다. 섹션 10.8은 템플릿이 이 해결에 미치는 영향을 설명합니다.)

운동 9.5

함수 과부하 해결 프로세스의 마지막(세 번째) 단계에서는 어떻게 됩니까?

9.3. 인수 유형 변환 A

함수 오버로드 해결 프로세스의 두 번째 단계에서 컴파일러는 호출된 함수의 각 실제 인수에 적용해야 하는 변환을 식별하고 순위를 지정하여 잘 설정된 함수의 해당 형식 매개변수 유형으로 변환합니다. 순위는 세 가지 가능한 결과 중 하나를 제공할 수 있습니다.

  • 정확히 일치. 실제 인수의 유형은 형식 매개변수의 유형과 정확히 일치합니다. 예를 들어, 오버로드된 print() 함수 집합에 다음이 있는 경우 void print(unsigned int); 무효 인쇄(const char*); 무효 인쇄(문자);
  • 다음 세 호출 각각은 정확히 일치하는 결과를 생성합니다.
    unsigned int a;
인쇄("아"); // 일치하는 print(char); 인쇄("아"); // 일치하는 print(const char*); 인쇄(a); // 일치하는 print(unsigned int);
  • 유형 변환과 일치합니다. 실제 인수의 유형은 형식 매개변수의 유형과 일치하지 않지만 변환될 수 있습니다. void ff(char); ff(0); // int 유형의 인수는 char 유형으로 캐스트됩니다.
  • 준수 부족. 필요한 변환이 존재하지 않기 때문에 실제 인수의 유형을 함수 선언의 형식 매개변수 유형으로 캐스트할 수 없습니다. print() 함수에 대한 다음 두 호출 각각에 대해 일치하는 항목이 없습니다.
  • // print() 함수는 위와 같이 선언됩니다. int *ip; 클래스 SmallInt ( /* ... */ ); SmallInt si; 인쇄(IP); // 오류: 일치하지 않음
    인쇄(si); // 오류: 일치하지 않음
  • 정확한 일치를 설정하기 위해 실제 인수의 유형이 형식 매개변수의 유형과 일치하지 않아도 됩니다. 다음과 같은 몇 가지 사소한 변환을 인수에 적용할 수 있습니다.

    • l-값을 r-값으로 변환하는 단계;
    • 배열을 포인터로 변환하는 단계;
    • 함수를 포인터로 변환하는 단계;
    • 지정자 변환.

    (아래에서 더 자세히 설명합니다.) 형식 변환과의 일치 범주는 가장 복잡합니다. 유형 확장(프로모션), 표준 변환 및 사용자 정의 변환과 같은 여러 종류의 캐스트를 고려해야 합니다. (유형 확장과 표준 변환은 이 장에서 논의됩니다. 사용자 정의 변환은 클래스가 자세히 논의된 후에 나중에 소개될 것입니다. 그들은 "표준" 집합을 정의할 수 있는 멤버 함수인 변환기에 의해 수행됩니다. ” 클래스의 변환. 15장에서 이러한 변환기와 이러한 변환기가 함수 과부하 해결에 미치는 영향을 살펴보겠습니다.)
    주어진 호출에 대해 가장 잘 확립된 함수를 선택할 때 컴파일러는 실제 인수에 적용된 변환이 "최상"인 함수를 찾습니다. 유형 변환은 다음과 같이 순위가 매겨집니다. 정확히 일치는 유형 확장보다 우수하고 유형 확장은 표준 변환보다 우수하며 이는 다시 사용자 정의 변환보다 우수합니다. 9.4절의 순위로 다시 돌아가겠지만 지금은 간단한 예가장 적절한 기능을 선택하는 데 도움이 되는 방법을 보여 드리겠습니다.

    9.3.1. 일치검색에 대해 자세히 알아보기

    가장 단순한 경우는 실제 인수의 유형이 형식 매개변수의 유형과 동일한 경우에 발생합니다. 예를 들어, 아래와 같이 두 개의 오버로드된 max() 함수가 있습니다. 그런 다음 max()에 대한 각 호출은 다음 선언 중 하나와 정확히 일치합니다.

    정수 최대( 정수, 정수); 이중 최대(이중, 이중); 정수 i1; 무효 계산(더블 d1) (
    최대(56, i1); // 정확히 일치하는 max(int, int);
    최대(d1, 66.9); // 정확히 일치하는 max(double, double);
    }

    열거형은 그 안에 정의된 것과 정확히 일치합니다. 열거 요소, 그리고 해당 유형으로 선언된 객체:

    열거형 토큰( INLINE = 128, VIRTUAL = 129, ); 토큰 curTok = INLINE; 열거형 통계(실패, 통과); 외부 무효 ff(토큰);
    외부 무효 ff(Stat);
    외부 무효 ff(int); 정수 메인() (
    ff(통과); // ff(Stat)와 정확히 일치
    ff(0); // ff(int)와 정확히 일치
    ff(커톡); // ff(Tokens)와 정확히 일치
    // ...
    }

    실제 인수는 형식 매개변수와 정확히 일치할 수 있다고 위에서 언급했습니다. 형식을 변환하는 데 약간의 사소한 변환이 필요하더라도 첫 번째는 l-value를 r-value로 변환하는 것입니다. l-value는 다음 조건을 충족하는 객체입니다.

    • 개체의 주소를 얻을 수 있습니다.
    • 당신은 객체의 가치를 얻을 수 있습니다;
    • 이 값은 수정하기 쉽습니다(객체 선언에 const 지정자가 없는 경우).

    이에 반해 r-value는 값이 평가되는 표현식이거나 주소를 얻을 수 없고 값을 수정할 수 없는 임시 객체를 나타내는 표현식이다. 다음은 간단한 예입니다.

    intcalc(int); int main() ( int lval, res; lval = 5; // lvalue: lval; rvalue: 5
    res = 계산(lval);
    // 좌변치:해상도
    // rvalue: 값을 저장할 임시 객체,
    // calc() 함수에서 반환
    반환 0;
    }

    첫 번째 대입문에서 변수 lval은 l값이고 리터럴 5는 r값입니다. 두 번째 대입문에서 res는 l값이고 calc() 함수에서 반환된 결과를 저장하는 임시 객체는 r값입니다.
    어떤 상황에서는 값이 예상되는 컨텍스트에서 l-값인 표현식을 사용할 수 있습니다.

    inbj1; 인비제이2; 정수 메인() (
    // ...
    내부 로컬 = obj1 + obj2;
    반환 0;
    }

    여기서 obj1 및 obj2는 l-값입니다. 그러나 main() 함수에서 더하기를 수행하기 위해 변수 obj1 및 obj2에서 해당 값을 추출합니다. l-value 표현식으로 표현되는 객체의 값을 추출하는 작업을 l-value를 r-value로 변환하는 작업이라고 합니다.
    함수가 값으로 전달된 인수를 예상할 때 인수가 l-값이면 r-값으로 변환됩니다.

    #포함 stringcolor("보라색"); 무효 인쇄(문자열); 정수 메인() (
    인쇄(색상); // 완전 일치: lvalue 변환
    // rvalue에서
    반환 0;
    }

    print(color) 호출의 인수는 값으로 전달되기 때문에 l-value는 r-value로 변환되어 color의 값을 추출하고 print(string) 프로토타입 함수에 전달합니다. 그러나 이러한 캐스트가 발생하더라도 실제 색상 인수는 print(string) 선언과 정확히 일치하는 것으로 가정됩니다.
    함수를 호출할 때 항상 이러한 변환을 인수에 적용할 필요는 없습니다. 참조는 l-값입니다. 함수에 참조 매개변수가 있으면 함수가 호출될 때 l-값을 받습니다. 따라서 설명된 변환은 형식 참조 매개변수가 해당하는 실제 인수에 적용되지 않습니다. 예를 들어 다음 함수가 선언되었다고 가정해 보겠습니다.

    #포함 무효 인쇄(목록 &);

    아래 호출에서 li는 목록 개체를 나타내는 l-값입니다. , print() 함수에 전달:

    목록 리(20); 정수 메인() (
    // ...
    인쇄(리); // 정확히 일치: lvalue에서 로의 변환 없음
    // r값
    반환 0;
    }

    li를 참조 매개변수와 일치시키는 것은 정확히 일치하는 것으로 간주됩니다.
    여전히 정확한 일치를 수정하는 두 번째 변환은 배열을 포인터로 변환하는 것입니다. 7.3절에서 언급했듯이 함수 매개변수는 배열 유형이 아니며 대신 첫 번째 요소에 대한 포인터로 변환됩니다. 유사하게, NT의 실제 배열 유형 인수(여기서 N은 배열의 요소 수이고 T는 각 요소의 유형임)는 항상 T에 대한 포인터로 캐스트됩니다. 실제 인수의 이러한 유형 변환을 array-to라고 합니다. - 포인터 변환. 그럼에도 불구하고 실제 인수는 "T에 대한 포인터" 유형의 형식 매개변수와 정확히 일치하는 것으로 간주됩니다. 예를 들어:

    인공지능; 무효 putValues(int*); 정수 메인() (
    // ...
    putValues(ai); // 정확히 일치: 배열을 다음으로 변환
    // 포인터
    반환 0;
    }

    putValues() 함수를 호출하기 전에 배열이 포인터로 변환되어 실제 인수 ai(정수 3개로 구성된 배열)가 int에 대한 포인터로 캐스팅됩니다. putValues() 함수의 형식 매개변수는 포인터이고 실제 인수는 호출될 때 변환되지만 둘 사이에 정확한 대응이 설정됩니다.
    정확한 일치를 설정할 때 함수를 포인터로 변환하는 것도 가능합니다. (섹션 7.9에서 언급했습니다.) 배열 매개변수와 마찬가지로 함수 매개변수는 함수 포인터가 됩니다. "function" 유형의 실제 인수도 자동으로 함수 포인터 유형으로 캐스트됩니다. 실제 인수의 이러한 유형 변환을 함수-포인터 변환이라고 합니다. 변환이 수행되는 동안 실제 인수는 형식 매개변수와 정확히 일치하는 것으로 간주됩니다. 예를 들어:

    Int lexicoCompare(const string &, const string &); typedef int(*PFI)(const string &, const string &);
    무효 정렬(문자열 *, 문자열 *, PFI); 문자열로; 정수 메인()
    {
    // ...
    정렬(처럼,
    as + sizeof(as)/sizeof(as - 1),
    lexicoCompare // 정확히 일치
    // 함수를 포인터로 변환
    ); 반환 0;
    }

    sort() 호출 전에 lexicoCompare 인수를 "function" 유형에서 "pointer to function" 유형으로 캐스팅하는 함수-포인터 변환이 적용됩니다. 함수에 대한 형식 인수는 포인터이고 실제 인수는 함수의 이름이므로 함수가 포인터로 변환되었지만 실제 인수는 정확히 sort() 함수의 세 번째 형식 매개변수로 간주됩니다. .
    위의 마지막은 지정자의 변환입니다. 포인터에만 적용되며 지정된 포인터를 지정하는 유형에 const 또는 volatile 지정자(또는 둘 다)를 추가하는 것으로 구성됩니다.

    정수 = (4454, 7864, 92, 421, 938); 정수 * 파이 = 에이; 부울 is_equal(const int * , const int *); void func(int *parm) ( // pi와 parm의 정확한 일치: 지정자의 변환
    if (is_equal(pi, parm))
    // ... 반환 0;
    }

    is_equal() 함수를 호출하기 전에 실제 인수 pi와 parm은 "pointer to int"에서 "pointer to const int"로 변환됩니다. 이 변환은 주소 지정 유형에 const 지정자를 추가하는 것으로 구성되므로 지정자 변환 범주에 속합니다. 함수가 const int에 대한 두 개의 포인터를 수신할 것으로 예상하고 실제 인수가 int에 대한 포인터이지만 is_equal() 함수의 형식 매개변수와 실제 매개변수 사이에 정확히 일치하는 항목이 있다고 가정합니다.
    지정자 변환은 포인터가 주소를 지정하는 형식에만 적용됩니다. 형식 매개변수에 const 또는 volatile 지정자가 있지만 실제 인수에는 없는 경우에는 사용되지 않습니다.

    외부 무효 takeCI(const int); 정수 메인() (
    정수 ii = ...;
    테이크CI(ii); // 지정자 변환이 적용되지 않음
    반환 0;
    }

    takeCI() 함수의 형식 매개변수가 const int 형식이고 int 형식의 인수 ii로 호출되지만 지정자 변환이 없습니다. 실제 인수와 형식 매개변수 간에 정확히 일치하는 항목이 있습니다.
    위의 모든 사항은 인수가 포인터이고 const 또는 volatile 지정자가 해당 포인터를 참조하는 경우에도 마찬가지입니다.

    외부 무효 초기화(int *const); 외부 정수 *파이; 정수 메인() (
    // ...
    초기화(파이); // 지정자 변환이 적용되지 않음
    반환 0;
    }

    init() 함수의 형식 매개변수에 있는 const 지정자는 포인터가 지정하는 유형이 아니라 포인터 자체를 참조합니다. 따라서 컴파일러는 실제 인수에 적용할 변환을 구문 분석할 때 이 지정자를 고려하지 않습니다. pi 인수에는 지정자 변환이 적용되지 않습니다. 이 인수와 형식 매개변수는 정확히 일치하는 것으로 간주됩니다.
    이러한 변환 중 처음 세 가지(l값에서 r값으로, 배열에서 포인터로, 함수에서 포인터로)는 종종 l값 변환이라고 합니다. (섹션 9.4에서 l-값 변환과 지정자 변환이 모두 정확한 일치를 위반하지 않는 변환 범주에 속하지만 첫 번째 변환만 필요할 때만 해당 정도가 더 높은 것으로 간주됩니다. 다음 섹션에서는 이것에 대해 좀 더 자세히 이야기하십시오. .)
    명시적 유형 캐스팅을 사용하여 정확히 일치하도록 강제할 수 있습니다. 예를 들어, 두 개의 오버로드된 함수가 있는 경우:

    외부 무효 ff(int); 외부 무효 ff(무효 *);

    Ff(0xffbc); // ff(int) 호출

    리터럴 0xffbc가 16진 상수로 작성되더라도 정확히 ff(int)와 일치합니다. 프로그래머는 명시적으로 캐스트 작업을 수행하여 컴파일러가 ff(void *) 함수를 호출하도록 할 수 있습니다.

    Ff(재해석_캐스트 (0xffbc)); // ff(void*) 호출

    이러한 캐스트가 실제 인수에 적용되면 변환되는 형식을 획득합니다. 명시적 형식 변환은 과부하 해결 프로세스를 제어하는 ​​데 도움이 됩니다. 예를 들어, 오버로드 해결이 모호한 결과를 생성하는 경우(실제 인수가 둘 이상의 잘 확립된 함수와 동등하게 잘 일치함) 명시적 캐스트를 사용하여 모호성을 해결하여 컴파일러가 특정 함수를 선택하도록 할 수 있습니다.

    9.3.2. 유형 확장에 대한 추가 정보

    유형 확장은 다음 변환 중 하나입니다.

    • char, unsigned char 또는 short 유형의 실제 인수는 int 유형으로 확장됩니다. unsigned short 유형의 실제 인수는 int의 기계 크기가 short의 크기보다 크면 int 유형으로 확장되고 그렇지 않으면 unsigned int 유형으로 확장됩니다.
    • float 유형의 인수는 double 유형으로 확장됩니다.
    • 열거형 형식 인수는 모든 열거형 멤버 값을 나타낼 수 있는 다음 형식 중 첫 번째 형식으로 확장됩니다. int, unsigned int, long, unsigned long;
    • bool 인수는 int 유형으로 확장됩니다.

    실제 인수의 유형이 방금 나열된 유형 중 하나이고 형식 매개변수가 해당 확장 유형인 경우 유사한 확장이 적용됩니다.

    외부 무효 manip(int); 정수 메인() (
    manip("아"); // char형을 int로 확장
    반환 0;
    }

    문자 리터럴은 char 유형입니다. int로 확장됩니다. 확장 유형은 manip() 함수의 형식 매개변수 유형에 해당하므로 이를 호출하려면 인수 유형을 확장해야 한다고 말합니다.
    다음 예를 고려하십시오.

    extern 무효 인쇄(부호 없는 정수); 외부 무효 인쇄(int); 외부 무효 인쇄(문자); 서명되지 않은 char uc;
    인쇄(UC); // 인쇄(int); uc에는 유형 확장만 필요합니다.

    unsigned char이 1바이트의 메모리를 사용하고 int가 4바이트를 사용하는 하드웨어 플랫폼에서 확장은 unsigned char를 int로 변환합니다. 이는 모든 unsigned char 값을 나타낼 수 있기 때문입니다. 이러한 기계 아키텍처의 경우 예제에 표시된 많은 오버로드된 함수 중에서 print(int)가 unsigned char 인수에 가장 적합한 일치를 제공합니다. 다른 두 함수의 경우 일치에는 표준 캐스트가 필요합니다.
    다음 예제에서는 실제 열거형 인수의 확장을 보여줍니다.

    Enum Stat(실패, 합격); 외부 무효 ff(int);
    외부 무효 ff(문자); 정수 메인() (
    // 정답: enum 멤버 Pass가 int 유형으로 확장됩니다.
    ff(통과); // ff(정수)
    ff(0); // ff(정수)
    }

    때때로 열거형을 확장하면 놀라움이 생깁니다. 컴파일러는 종종 요소의 값을 기반으로 열거형의 표현을 선택합니다. 위의 아키텍처(char의 경우 1바이트, int의 경우 4바이트)가 다음과 같이 열거형을 정의한다고 가정합니다.

    열거형 e1 ( a1, b1, c1 );

    각각 값이 0, 1 및 2인 세 개의 요소 a1, b1 및 c1이 있고 이러한 모든 값이 char 유형으로 표시될 수 있기 때문에 컴파일러는 일반적으로 유형 e1을 나타내기 위해 char를 선택합니다. 그러나 다음 요소 집합이 있는 열거형 e2를 고려하십시오.

    열거형 e2 ( a2, b2, c2=0x80000000 );

    상수 중 하나가 0x80000000 값을 가지므로 컴파일러는 값 0x80000000, 즉 unsigned int를 저장하기에 충분한 유형으로 e2를 나타내도록 선택해야 합니다.
    따라서 e1과 e2가 모두 열거형이지만 표현은 다릅니다. 이 때문에 e1과 e2는 다른 유형으로 확장됩니다.

    #포함 문자열 형식(int);
    문자열 형식(부호 없는 정수); 정수 메인() (
    형식(a1); // 호출 형식(int)
    형식(a2); // 호출 형식(unsigned int)
    반환 0;
    }

    format()이 처음 호출되면 char가 형식 e1을 나타내는 데 사용되므로 실제 인수가 int 형식으로 확장되므로 오버로드된 format(int) 함수가 호출됩니다. 두 번째 호출에서 실제 인수 e2의 유형은 unsigned int이고 인수가 unsigned int로 확장되어 오버로드된 format(unsigned int) 함수가 호출됩니다. 따라서 오버로드 해결 프로세스와 관련된 두 열거형의 동작은 다를 수 있으며 형식 확장이 발생하는 방식을 결정하는 요소의 값에 따라 달라질 수 있습니다.

    9.3.3. 표준 변환에 대해 자세히 알아보기

    표준 변환에는 다음과 같은 다섯 가지 유형이 있습니다.

    1. 정수 유형 변환: 정수 유형 또는 열거형에서 다른 정수 유형으로 캐스팅(위에서 유형 확장으로 분류된 변환 제외)
    2. 부동 소수점 유형 변환: 모든 부동 소수점 유형에서 다른 부동 소수점 유형으로 캐스팅(위에서 유형 확장으로 분류된 변환 제외)
    3. 정수 유형과 부동 소수점 유형 간의 변환: 모든 부동 소수점 유형에서 임의의 정수 유형으로 또는 그 반대로 캐스팅;
    4. 포인터 변환: 정수 값 0을 포인터 유형으로 캐스팅하거나 모든 유형의 포인터를 void* 유형으로 변환합니다.
    5. bool 유형으로의 변환: 모든 정수 유형, 부동 소수점 유형, 열거 유형 또는 포인터 유형에서 bool 유형으로 캐스팅.

    여기 몇 가지 예가 있어요.

    외부 무효 인쇄(무효*); 외부 무효 인쇄(이중); 정수 메인() (
    정수 나;
    인쇄(i); // 일치하는 print(double);
    // i는 int에서 double로의 표준 변환을 거칩니다.
    인쇄(&i); // 일치하는 print(void*);
    // &i는 표준 변환을 거칩니다.
    // int*에서 void*로
    반환 0;
    }

    그룹 1, 2, 3에 속하는 변환은 대상 유형이 소스 유형의 모든 값을 나타내지 않을 수 있기 때문에 잠재적으로 위험합니다. 예를 들어, float는 모든 int 값을 적절하게 나타낼 수 없습니다. 이러한 이유 때문에 이러한 그룹에 포함된 변환은 형식 확장이 아닌 표준 변환으로 분류됩니다.

    정수 나; voidcalc(float); int main() ( calc(i); // 정수 유형과 부동 소수점 유형 간의 표준 변환은 // i 값에 따라 // 잠재적으로 위험합니다. return 0; )

    calc() 함수가 호출되면 정수 유형 int에서 부동 소수점 유형 float로의 표준 변환이 적용됩니다. 변수 i의 값에 따라 정밀도 손실 없이 부동 소수점으로 저장하는 것이 불가능할 수 있습니다.
    모든 표준 변경에는 동일한 양의 작업이 필요하다고 가정합니다. 예를 들어 char에서 unsigned char로의 변환은 char에서 double로의 변환보다 우선 순위가 높지 않습니다. 유형의 근접성은 고려되지 않습니다. 두 개의 설정된 함수가 일치시키기 위해 실제 인수의 표준 변환이 필요한 경우 호출은 모호한 것으로 간주되고 컴파일러에서 오류로 플래그를 지정합니다. 예를 들어, 두 개의 오버로드된 함수가 있다고 가정합니다.

    extern 무효 manip(long); extern void manip(float);

    다음 호출은 모호합니다.

    int main() ( manip(3.14); // 오류: ambiguity // manip(float)은 manip(int)보다 낫지 않습니다. return 0; )

    상수 3.14는 double 유형입니다. 하나 또는 다른 표준 변환의 도움으로 오버로드된 기능과 일치를 설정할 수 있습니다. 목표로 이어지는 두 가지 변환이 있으므로 호출이 모호한 것으로 간주됩니다. 어느 변환도 다른 변환보다 우선하지 않습니다. 프로그래머는 명시적 유형 캐스팅을 통해 모호성을 해결할 수 있습니다.

    Manip(static_cast (3.14)); // 조작(긴)

    또는 상수가 float 유형임을 나타내는 접미사를 사용하여:

    마닙(3.14F)); // 조작(float)

    다음은 여러 오버로드된 함수와 일치하기 때문에 오류로 플래그가 지정된 모호한 호출의 몇 가지 예입니다.

    extern void farith(unsigned int); extern void farith(float); 정수 메인() (
    // 다음 호출은 각각 모호합니다.
    farith("아"); // 인수는 char 유형입니다.
    패리스(0); // 인수는 int 유형입니다.
    패리스(2ul); // 인수는 unsigned long 유형입니다.
    패리스(3.14159); // 인수는 이중 유형입니다.
    패리스(참); // 인수는 bool 유형입니다.
    }

    표준 포인터 변환은 때때로 직관적이지 않습니다. 특히 값 0은 모든 유형에 대한 포인터로 캐스트됩니다. 이렇게 얻은 포인터를 null이라고 합니다. 값 0은 정수 유형의 상수 표현식으로 나타낼 수 있습니다.

    무효 집합(int*); 정수 메인() (
    // 0에서 int*로 포인터 변환이 인수에 적용됨
    // 두 호출 모두에서
    세트(0L);
    세트(0x00);
    반환 0;
    }

    상수 표현식 0L(long int 유형의 값 0) 및 상수 표현식 0x00(16진수 정수 값 0)은 정수 유형이므로 int* 유형의 널 포인터로 변환될 수 있습니다.
    그러나 열거형은 정수 유형이 아니기 때문에 0과 같은 요소는 포인터 유형으로 캐스팅되지 않습니다.

    열거 EN ( zr = 0 ); 세트(zr); // 오류: zr은 int* 유형으로 변환할 수 없습니다.

    set() 함수에 대한 호출은 zr이 0이더라도 열거 요소의 값 zr과 int* 유형의 형식 매개변수 사이에 변환이 없기 때문에 오류입니다.
    상수 표현식 0은 int 유형입니다. 포인터 유형으로 변환하려면 표준 변환이 필요합니다. 오버로드된 함수 집합에 형식 매개변수가 int인 함수가 있는 경우 실제 인수가 0인 경우 오버로드가 허용됩니다.

    무효 인쇄(int); 무효 인쇄(무효*); 무효 세트(const char*);
    무효 집합(문자*); 정수 메인()(
    인쇄(0); // 호출됨 print(int);
    세트(0); // 모호성
    반환 0;
    }

    print(int)에 대한 호출은 정확히 일치하는 반면, print(void*)에 대한 호출은 값 0을 포인터 유형으로 캐스팅해야 합니다. 일치가 변환보다 낫기 때문에 이 호출을 해결하려면 다음을 선택합니다. 인쇄 기능(int). set()에 대한 호출은 0이 표준 변환을 적용하여 두 오버로드된 함수의 형식 매개변수와 일치하기 때문에 모호합니다. 두 기능 모두 동일하게 양호하므로 모호성이 수정됩니다.
    마지막으로 가능한 포인터 변환을 사용하면 모든 유형의 포인터를 void* 유형으로 캐스팅할 수 있습니다. void*는 모든 데이터 유형에 대한 일반 포인터이기 때문입니다. 여기 몇 가지 예가 있어요.

    #포함 외부 무효 리셋(무효 *); 무효 func(int *pi, 문자열 *ps) (
    // ...
    리셋(파이); // 포인터 변환: int*에서 void*로
    /// ...
    리셋(ps); // 포인터 변환: string*에서 void*로
    }

    표준 변환을 사용하여 데이터 유형에 대한 포인터만 void*로 캐스트할 수 있으며 함수에 대한 포인터는 다음과 같이 수행할 수 없습니다.

    형식 정의 int(*PFV)(); extern PFV 테스트 케이스; // 함수에 대한 포인터 배열 extern void reset(void *); 정수 메인() (
    // ...
    재설정(텍스트 케이스); // 오류: 표준 변환 없음
    // int(*)()와 void* 사이
    반환 0;
    }

    9.3.4. 연결

    함수의 실제 인수 또는 형식 매개변수는 참조가 될 수 있습니다. 이것은 유형 변환 규칙에 어떤 영향을 줍니까?
    참조가 실제 인수일 때 어떤 일이 발생하는지 고려하십시오. 해당 유형은 참조 유형이 아닙니다. 참조 인수는 해당 객체의 유형과 유형이 동일한 l-값으로 처리됩니다.

    정수 나; 정수&리 = 나; 무효 인쇄(int); 정수 메인() (
    인쇄(i); // 인수는 int 유형의 lvalue입니다.
    인쇄(리); // 같은
    반환 0;
    }

    두 호출의 실제 인수는 int 유형입니다. 참조를 사용하여 두 번째 호출에서 전달하는 것은 인수 자체의 유형에 영향을 미치지 않습니다.
    컴파일러에서 고려하는 표준 유형 변환 및 확장은 실제 인수가 유형 T에 대한 참조일 때와 해당 유형 자체일 때 동일합니다. 예를 들어:

    정수 나; 정수&리 = 나; 무효 계산(이중); 정수 메인() (
    계산(i); // 정수형 간의 표준 변환
    // 및 부동 소수점 유형
    계산(ri); // 같은
    반환 0;
    }

    그리고 형식 참조 매개변수는 실제 인수에 적용된 변환에 어떤 영향을 줍니까? 비교 결과는 다음과 같습니다.

    • 실제 인수는 참조 매개변수 이니셜라이저로 적합합니다. 이 경우, 우리는 그들 사이에 정확히 일치하는 것이 있다고 말합니다: void swap(int &, int &); 무효 manip(int i1, int i2) (
      // ...
      스왑(i1, i2); // 정답: swap(int &, int &) 호출
      // ...
      반환 0;
      }
    • 실제 인수는 참조 매개변수를 초기화할 수 없습니다. 이러한 상황에서는 정확히 일치하는 항목이 없으며 인수를 사용하여 함수를 호출할 수 없습니다. 예를 들어:
    • 정수 객체; 무효 fred(더블 &); int main() ( frd(obj); // 오류: 매개변수 유형은 const double & return 0; )
    • frd() 함수를 호출하는 것은 오류입니다. 실제 인수는 int 유형이며 형식 참조 매개변수와 일치하도록 double 유형으로 변환해야 합니다. 이 변환의 결과는 임시 변수입니다. 참조에는 const 지정자가 없으므로 이러한 변수를 사용하여 초기화할 수 없습니다.
      다음은 형식 참조 매개변수와 실제 인수가 일치하지 않는 또 다른 예입니다.
    • 클래스 B; 무효 takeB(B&); BgiveB(); 정수 메인() (
      테이크B(기브B()); // 오류: 매개변수는 const B & 유형이어야 합니다.
      반환 0;
      }
    • takeB() 함수 호출은 오류입니다. 실제 인수는 반환 값입니다. const 지정자 없이 참조를 초기화하는 데 사용할 수 없는 임시 변수입니다.
      두 경우 모두 형식 참조 매개변수에 const 지정자가 있으면 해당 매개변수와 실제 인수 간에 정확히 일치할 수 있습니다.

    l값에서 r값으로의 변환과 참조 초기화는 모두 정확히 일치하는 것으로 간주됩니다. 이 예에서 첫 번째 함수 호출은 오류를 발생시킵니다.

    무효 인쇄(int); 무효 인쇄(int&); 인티오브이;
    정수 & ri = iobj; 정수 메인() (
    인쇄(iobj); // 오류: 모호성
    인쇄(리); // 오류: 모호성
    인쇄(86); // 정답: print(int) 호출
    반환 0;
    }

    iobj 객체는 두 print() 함수에 매핑될 수 있는 인수입니다. 즉, 호출이 모호합니다. 참조 ri가 두 print() 함수에 해당하는 객체를 나타내는 다음 줄에도 동일하게 적용됩니다. 그러나 세 번째 호출에서는 모든 것이 정상입니다. 그에게 print(int&)는 잘 정립되지 않았습니다. 정수 상수는 r-값이므로 참조 매개변수를 초기화할 수 없습니다. print(86)을 호출하기 위해 설정된 유일한 함수는 print(int)입니다. 이것이 오버로드 해결에서 선택되는 이유입니다.
    간단히 말해서 형식 인수가 참조인 경우 실제 인수는 참조를 초기화할 수 있는 경우 정확히 일치하고 그렇지 않은 경우에는 일치하지 않습니다.

    운동 9.6

    정확한 일치를 설정할 때 허용되는 두 가지 사소한 변환의 이름을 지정하십시오.

    운동 9.7

    다음 함수 호출에서 각 인수 변환의 순위는 얼마입니까?

    (a) 무효 print(int *, int); 정수형; 인쇄(arr, 6); // 함수 호출(b) void manip(int, int); manip("아", "z"); // 함수 호출(c) int calc(int, int); 더블 도비; double = calc(55.4, dobj) // 함수 호출(d) void set(const int *); 정수 * 파이; 세트(파이); // 함수 호출

    운동 9.8

    실제 인수의 유형과 형식 매개변수 사이에 변환이 없기 때문에 다음 호출 중 어떤 것이 잘못된 호출입니까?

    (a) enum Stat( Fail, Pass ); 무효 테스트(Stat); 텍스트(0); // 함수 호출(b) void reset(void *); 리셋(0); // 함수 호출(c) void set(void *); 정수 * 파이; 세트(파이); // 함수 호출(d) #include 목록 오페라(); 무효 인쇄(oper()); // 함수 호출(e) void print(const int); 인티오브이; 인쇄(iobj); // 함수 호출

    9.4. 함수 과부하 해결 세부 정보

    우리는 이미 섹션 9.2에서 함수 과부하 해결 프로세스가 세 단계로 구성된다고 언급했습니다.

    1. 주어진 호출을 해결하기 위해 후보 함수 세트와 실제 인수 목록의 속성을 설정합니다.
    2. 설정된 함수(수와 유형을 고려하여 주어진 실제 인수 목록으로 호출할 수 있는 함수)의 후보 세트에서 선택하십시오.
    3. 설정된 함수의 형식 매개변수와 일치하도록 실제 인수에 적용할 변환의 순위를 지정하여 호출에 가장 적합한 함수를 선택합니다.

    이제 이러한 단계를 더 자세히 살펴볼 준비가 되었습니다.

    9.4.1. 후보자 기능

    후보 함수는 호출된 것과 동일한 이름을 가진 함수입니다. 후보자는 두 가지 방법으로 선택됩니다.

    • 함수 선언은 호출 지점에서 볼 수 있습니다. 다음 예에서
      무효 f(); 무효 f(int); 무효 f(더블, 더블 = 3.4); 무효 f(char*, char*); 정수 메인() (
      f(5.6); // 이 호출을 해결할 후보가 4개 있습니다.
      반환 0;
      }
    • 4개의 f() 함수가 모두 이 조건을 충족합니다. 따라서 후보 집합에는 네 가지 요소가 포함됩니다.
    • 실제 인수의 유형이 일부 네임스페이스 내에서 선언되면 호출된 함수와 동일한 이름을 가진 이 공간의 멤버 함수가 후보 집합에 추가됩니다. namespace NS ( class C ( /* ... */ ); void takeC( C&); ) // 유형 cobj는 NS 네임스페이스에 선언된 클래스 C입니다.
      NS::Cobj; 정수 메인() (
      // 호출 지점에서 takeC() 함수가 보이지 않습니다.
      takeC(cobj); // 정답: NS::takeC(C&)가 호출되고,
      // 인수가 NS::C 유형이므로
      // takeC() 함수가 고려됩니다.
      // NS 네임스페이스에 선언
      반환 0;
      }

    따라서 후보 컬렉션은 호출 지점에서 볼 수 있는 함수 집합과 실제 인수 유형과 동일한 네임스페이스에 선언된 함수 집합의 합집합입니다.
    호출 시점에 표시되는 오버로드된 함수 집합을 식별할 때 앞에서 이미 설명한 규칙이 적용됩니다.
    중첩 범위에서 선언된 함수는 외부 범위에서 같은 이름의 함수를 오버로드하지 않고 숨깁니다. 이러한 상황에서는 중첩 범위 내의 함수만 후보가 됩니다. 호출될 때 숨겨지지 않는 것들. 다음 예에서 호출 지점에서 볼 수 있는 후보 함수는 format(double) 및 format(char*)입니다.

    문자* 형식(int); void g() ( char *format(double); char* format(char*); format(3); // format(double) 호출
    }

    전역 범위에 선언된 format(int)은 숨겨져 있으므로 후보 함수 집합에 포함되지 않습니다.
    호출 시점에 표시되는 선언을 사용하여 후보자를 소개할 수 있습니다.

    네임스페이스 libs_R_us ( int max(int, int); double max(double, double); ) char max(char, char); 무효 함수()
    {
    // 네임스페이스의 함수는 보이지 않습니다.
    // 세 가지 호출은 모두 전역 함수 max(char, char)를 위해 해결됩니다.
    최대(87, 65);
    최대(35.5, 76.6);
    최대("J", "L");
    }

    libs_R_us 네임스페이스에 정의된 max() 함수는 호출 지점에서 보이지 않습니다. 유일하게 보이는 것은 전역 범위의 max() 함수입니다. 그것은 후보 함수 세트에 포함되며 func()에 대한 세 번의 호출 각각에서 호출됩니다. using 선언을 사용하여 libs_R_us 네임스페이스에서 max() 함수를 노출할 수 있습니다. using 선언을 어디에 둘 것인가? 전역 범위에 포함하는 경우:

    문자 최대(문자, 문자); libs_R_us::max 사용; // 사용 선언

    그런 다음 libs_R_us의 max() 함수가 전역 범위에 선언된 max()를 이미 포함하는 오버로드된 함수 집합에 추가됩니다. 이제 세 함수 모두 func() 내부에서 볼 수 있으며 후보가 됩니다. 이 상황에서 func() 호출은 다음과 같이 해결됩니다.

    Void func() ( max(87, 65); // libs_R_us::max(int, int) max("J", "L"); // 호출::max(char, char) )

    그러나 이 예제와 같이 func() 함수의 로컬 범위에 using 선언을 삽입하면 어떻게 될까요?

    void func() ( // libs_R_us::max를 사용한 using-declaration; // 위와 같은 함수 호출
    }

    후보 집합에 포함될 max() 함수는 무엇입니까? using 선언은 서로 내포되어 있음을 기억하십시오. 로컬 범위에서 이러한 선언을 사용하면 전역 함수 max(char, char)가 숨겨져

    Libs_R_us::max(int, int); libs_R_us::max(더블, 더블);

    그들은 후보자입니다. 이제 func() 호출은 다음과 같이 해결됩니다.

    Void func() ( // using-declaration // 전역 함수 max(char, char)는 libs_R_us::max를 사용하여 숨겨짐; max(87, 65); // libs_R_us::max(int, int)가 호출됨
    최대(35.5, 76.6); // libs_R_us::max(double, double)가 호출됩니다.
    최대("J", "L"); // libs_R_us::max(int, int) 호출
    }

    using 지시문은 후보 함수 집합의 구성에도 영향을 줍니다. 그것들을 사용하여 libs_R_us 네임스페이스의 max() 함수를 func()에서 볼 수 있도록 하기로 결정했다고 가정합니다. 다음 using 지시문을 전역 범위에 배치하면 후보 함수 집합은 전역 함수 max(char, char)와 libs_R_us에 선언된 max(int, int) 및 max(double, double) 함수로 구성됩니다.

    네임스페이스 libs_R_us ( int max(int, int); double max(double, double); ) char max(char, char);
    네임스페이스 libs_R_us 사용 // 지시문 사용 void func()
    {
    최대(87, 65); // libs_R_us::max(int, int) 호출
    최대(35.5, 76.6); // libs_R_us::max(double, double)가 호출됩니다.
    }

    다음 예제와 같이 using 지시문을 로컬 범위에 넣으면 어떻게 됩니까?

    Void func() ( // libs_R_us 네임스페이스를 사용하는 지시문; // 위와 같은 함수 호출
    }

    max() 함수 중 어떤 것이 후보에 포함됩니까? using 지시문은 네임스페이스 멤버를 해당 지시문이 배치된 지점에서 해당 공간 외부에 선언된 것처럼 보이게 합니다. 이 예에서 libs_R_us의 멤버는 func() 함수의 로컬 범위에서 볼 수 있습니다. 마치 전역 범위에서 공간 밖에서 선언된 것처럼 보입니다. func() 내부에서 볼 수 있는 오버로드된 함수 집합은 이전과 동일합니다. 포함

    최대(문자, 문자); libs_R_us::max(int, int); libs_R_us::max(더블, 더블);

    using 지시문은 로컬 또는 전역 범위에 나타나며 func() 함수에 대한 호출 해석은 영향을 받지 않습니다.

    Void func() ( using namespace libs_R_us; max(87, 65); // libs_R_us::max(int, int) 호출
    최대(35.5, 76.6); // libs_R_us::max(double, double)가 호출됩니다.
    최대("J", "L"); // 호출::max(int, int)
    }

    따라서 후보 집합은 실제 인수 유형과 연결된 네임스페이스에 선언된 함수뿐만 아니라 using 선언 및 using 지시문에 의해 도입된 함수를 포함하여 호출 지점에서 볼 수 있는 함수로 구성됩니다. 예를 들어:

    네임스페이스 basicLib ( int print(int); double print(double); ) 네임스페이스 matrixLib ( class matrix ( /* ... */ ); void print(const maxtrix &); ) void display() ( basicLib::print 사용 ; matrixLib::행렬 mObj;
    인쇄(mObj); // maxtrixLib 호출::print(const maxtrix &) print(87); // basicLib::print(const maxtrix &)가 호출됩니다.
    }

    print(mObj)의 후보는 호출 지점에서 볼 수 있기 때문에 basicLib::print(int) 및 basicLib::print(double) 함수의 display() 내부 using 선언입니다. 실제 함수 인수는 matrixLib::matrix 유형이므로 matrixLib 네임스페이스에 선언된 print() 함수도 후보가 됩니다. print(87)의 후보 함수는 무엇입니까? 호출 지점에서는 basicLib::print(int) 및 basicLib::print(double)만 표시됩니다. 인수가 int 유형이므로 다른 후보를 검색할 때 추가 네임스페이스가 고려되지 않습니다.

    9.4.2. 확립된 기능

    잘 정립된 기능이 후보 중 하나입니다. 형식 매개변수 목록은 호출된 함수의 실제 인수 목록과 같거나 더 많은 수의 요소를 갖습니다. 후자의 경우, 추가 옵션기본값이 제공됩니다. 그렇지 않으면 주어진 인수 수로 함수를 호출할 수 없습니다. 함수가 안정적인 것으로 간주되려면 각 실제 인수에서 해당 형식 매개변수의 유형으로의 변환이 있어야 합니다. (이러한 변환은 섹션 9.3에서 논의되었습니다.)
    다음 예에서 f(5.6)에 대한 호출에는 f(int) 및 f(double)이라는 두 개의 설정된 함수가 있습니다.

    무효 f(); 무효 f(int); 무효 f(더블); 무효 f(char*, char*); 정수 메인() (
    f(5.6); // 2개의 설정된 함수: f(int) 및 f(double)
    반환 0;
    }

    f(int) 함수는 호출의 실제 인수 수에 해당하는 형식 매개변수가 하나만 있기 때문에 살아남았습니다. 또한 double 형식의 인수를 int로 표준 변환하는 방법이 있습니다. f(double) 함수도 살아남았습니다. 또한 double 유형의 매개변수가 하나 있으며 실제 인수와 정확히 일치합니다. 후보 함수 f() 및 f(char*, char*)는 단일 인수로 호출할 수 없기 때문에 생존 함수 목록에서 제외됩니다.
    다음 예에서 format(3)을 호출하기 위해 설정된 유일한 함수는 format(double)입니다. format(char*) 후보는 단일 인수로 호출할 수 있지만 실제 int 인수의 형식에서 형식 매개 변수 char* 형식으로 변환되지 않으므로 함수가 잘 정립된 것으로 간주할 수 없습니다.

    문자* 형식(int); void g() ( // 전역 함수 format(int)은 숨겨져 있습니다. char* format(double); char* format(char*); format(3); // 설정된 함수는 하나만 있습니다: format(double) )

    다음 예제에서 세 가지 후보 함수는 모두 func() 내부에서 max()를 호출할 수 있게 됩니다. 모두 두 개의 인수로 호출할 수 있습니다. 실제 인수는 int 유형이므로 libs_R_us::max(int, int) 함수의 형식 매개변수와 정확히 일치하며 변환하여 libs_R_us::max(double, double) 함수의 매개변수 유형으로 캐스팅할 수 있습니다. 정수를 부동 소수점으로, 그리고 정수 형식 변환을 통해 libs_R_us::max(char, char) 함수 매개변수 형식으로.


    libs_R_us::max 사용; 문자 최대(문자, 문자);
    무효 함수()
    {
    // 세 가지 max() 함수 모두 잘 설정됨
    최대(87, 65); // libs_R_us::max(int, int)를 사용하여 호출
    }

    여러 매개변수가 있는 후보 함수는 다른 모든 인수에 대해 이러한 변환이 존재하더라도 실제 인수 중 하나를 해당 형식 매개변수의 유형으로 캐스트할 수 없다는 것이 발견되는 즉시 대기 상태에서 제거됩니다. 다음 예제에서 함수 min(char *, int)은 첫 번째 int 인수의 유형을 해당 char * 매개변수의 유형으로 변환할 수 없기 때문에 살아남은 집합에서 제외됩니다. 그리고 이것은 두 번째 인수가 두 번째 매개변수와 정확히 일치한다는 사실에도 불구하고 발생합니다.

    extern 이중 최소(이중, 이중); extern 이중 최소(char*, int); 무효 함수()
    {
    // 하나의 후보 함수 min(double, double)
    최소(87, 65); // min(double, double) 호출
    }

    후보 집합에서 매개변수 수가 부적절하고 매개변수에 적절한 변환이 없는 모든 함수를 제외하고 서 있는 함수가 없으면 함수 호출 처리가 컴파일 오류로 종료됩니다. 이 경우 일치하는 항목이 없다고 합니다.

    무효 인쇄(unsigned int); 무효 인쇄(문자*); 무효 인쇄(문자); int*ip;
    클래스 SmallInt ( /* ... */ );
    SmallInt si; 정수 메인() (
    인쇄(IP); // 오류: 설정된 함수 없음: 일치 항목을 찾을 수 없음
    인쇄(si); // 오류: 설정된 함수 없음: 일치 항목을 찾을 수 없음
    반환 0;
    }

    9.4.3. 가장 잘 만들어진 기능

    가장 좋은 것은 공식 매개변수가 실제 인수의 유형과 가장 근접하게 일치하는 확립된 함수의 것으로 간주됩니다. 이러한 함수의 경우 각 인수에 적용된 유형 변환의 순위를 지정하여 매개변수와 얼마나 잘 일치하는지 결정합니다. (섹션 6.2는 지원되는 유형 변환에 대해 설명합니다.) 가장 잘 확립된 함수는 두 가지 조건을 동시에 충족하는 함수입니다.

    • 인수에 적용된 변환은 잘 확립된 다른 함수를 호출하는 데 필요한 변환보다 나쁘지 않습니다.
    • 적어도 하나의 인수에 대해 적용된 변환은 잘 확립된 다른 함수의 동일한 인수보다 낫습니다.

    실제 인수를 해당 형식 매개변수의 유형으로 캐스팅하려면 여러 변환을 수행해야 할 수 있습니다. 따라서 다음 예에서

    정수형; 무효 putValues(const int *); 정수 메인() (
    putValues(arr); // 2개의 변환이 필요함
    // 배열을 포인터로 + 지정자 변환
    반환 0;
    }

    "3개의 int 배열" 유형에서 "const int에 대한 포인터" 유형으로 arr 인수를 캐스팅하기 위해 변환 시퀀스가 ​​적용됩니다.

    1. 3개의 int 배열을 int에 대한 포인터로 변환하는 배열을 포인터로 변환합니다.
    2. int에 대한 포인터를 const int에 대한 포인터로 변환하는 지정자 변환입니다.

    따라서 실제 인수를 설정된 함수의 형식 매개변수 유형으로 변환하려면 일련의 변환이 필요하다고 말하는 것이 더 정확할 것입니다. 하나가 아니라 여러 변환이 적용되기 때문에 함수 과부하 해결 프로세스의 세 번째 단계는 실제로 변환 시퀀스의 순위를 지정합니다.
    이러한 시퀀스의 순위는 포함된 변환 중 최악의 순위로 간주됩니다. 섹션 9.2에서 설명한 대로 유형 변환의 순위는 다음과 같습니다. 정확히 일치는 유형 확장보다 우수하고 유형 확장은 표준 변환보다 우수합니다. 이전 예에서 두 변경 사항 모두 정확히 일치 순위를 갖습니다. 따라서 전체 시퀀스는 동일한 순위를 갖습니다.
    이러한 컬렉션은 표시된 순서대로 적용된 여러 변환으로 구성됩니다.

    l-값 변환 -> 유형 확장 또는 표준 변환 -> 지정자 변환

    l-값 변환이라는 용어는 9.2절에서 논의된 정확한 일치 변환 중 처음 세 가지인 l-값에서 r값으로 변환, 배열에서 포인터로 변환 및 함수에서 포인터로 변환을 나타냅니다. 변환 시퀀스는 0 또는 1개의 l-value 변환, 0 또는 1개의 유형 확장 또는 표준 변환, 마지막으로 0 또는 1개의 지정자 변환으로 구성됩니다. 형식 매개변수의 유형에 실제 인수를 캐스트하는 데 각 종류의 변환 하나만 적용할 수 있습니다.

    설명된 시퀀스를 표준 변환 시퀀스라고 합니다. 순서도 있다 사용자 정의클래스의 구성원인 변환기 함수와 연결된 변환입니다. (컨버터와 사용자 정의 변환 시퀀스는 15장에서 설명합니다.)

    다음 예에서 실제 인수가 변경되는 순서는 무엇입니까?

    네임스페이스 libs_R_us ( int max(int, int); double max(double, double); ) // 사용 선언
    libs_R_us::max 사용; 무효 함수()
    {
    문자 c1, c2;
    최대(c1, c2); // libs_R_us::max(int, int) 호출
    }

    max() 함수 호출에 대한 인수는 char 유형입니다. libs_R_us::max(int,int) 함수 호출 시 인수 변환 순서는 다음과 같습니다.

    1a. 인수는 값으로 전달되므로 l 값을 r 값으로 변환하면 인수 c1 및 c2의 값이 추출됩니다.

    2a. 인수는 유형 확장을 사용하여 char에서 int로 변환됩니다.
    libs_R_us::max(double,double) 함수 호출 시 인수 변환 순서는 다음과 같습니다.
    1b. l 값을 r 값으로 변환하여 인수 c1 및 c2의 값을 추출합니다.

    2b. 정수 유형과 부동 소수점 유형 간의 표준 변환은 인수를 char 유형에서 double 유형으로 캐스트합니다.

    첫 번째 시퀀스의 순위는 유형 확장(최악의 변경 적용)이고 두 번째 순위는 표준 변환입니다. 유형 확장이 유형 변환보다 낫기 때문에 libs_R_us::max(int,int) 함수가 이 호출에 가장 적합하도록 선택됩니다.
    인수 변환의 순위 지정 시퀀스가 ​​잘 확립된 단일 기능을 나타낼 수 없는 경우 호출은 모호한 것으로 간주됩니다. 이 예에서 calc()에 대한 두 호출에는 다음 순서가 필요합니다.

    1. l-value를 r-value로 변환하여 i 및 j 인수 값을 추출합니다.
    2. 실제 인수를 해당 형식 매개변수로 캐스팅하기 위한 표준 변환입니다.

    이 시퀀스 중 어느 것이 다른 시퀀스보다 낫다고 말할 수 없기 때문에 호출이 모호합니다.

    정수 i, j; extern long calc(long, long); extern 이중 계산(이중, 이중); void jj() ( // 오류: 모호성, 최상의 일치 없음
    계산(i,j);
    }

    지정자 변환(포인터를 지정하는 형식에 const 또는 volatile 지정자를 추가)은 정확히 일치하는 순위를 갖습니다. 그러나 두 변환 시퀀스 중 하나에 끝에 추가 지정자 변환이 있다는 점만 다른 경우에는 없는 시퀀스가 ​​더 나은 것으로 간주됩니다. 예를 들어:

    무효 리셋(int *); 무효 리셋(const int *); 정수 * 파이; 정수 메인() (
    리셋(파이); // 지정자를 변환하지 않는 것이 좋습니다.
    // 리셋(int *) 선택
    반환 0;
    }

    첫 번째 후보 함수 reset(int*)에 대한 실제 인수에 적용된 표준 변환 시퀀스는 정확히 일치하며 인수 값을 추출하기 위해 l-값에서 r-값으로 이동하기만 하면 됩니다. 두 번째 후보 함수인 reset(const int *) 의 경우 l-value에서 r-value로의 변환도 적용되지만 포인터에서 int로의 결과 값을 const int에 대한 포인터로 캐스팅하는 지정자 변환도 뒤따릅니다. 두 시퀀스 모두 정확히 일치하지만 모호성은 없습니다. 두 번째 시퀀스가 ​​첫 번째 시퀀스와 끝에 지정자 변환이 있다는 점에서 다르기 때문에 이러한 변환이 없는 시퀀스가 ​​가장 좋은 것으로 간주됩니다. 따라서 reset(int*)이 가장 좋은 기능입니다.
    다음은 캐스팅 지정자가 선택되는 시퀀스에 영향을 미치는 또 다른 예입니다.

    정수 추출(무효*);
    정수 추출(const void *);

    정수 메인() (
    추출물(파이); // 선택 추출(void *)
    반환 0;
    }

    여기에서 호출할 두 개의 확립된 함수가 있습니다: extract(void*) 및 extract(const void*). extract(void*) 함수에 대한 변환 시퀀스는 l-값을 추출할 r-값으로 변환하는 것으로 구성됩니다. 인수 값, 다음에 표준 포인터 변환: int에 대한 포인터에서 void에 대한 포인터로. extract(const void*) 함수의 경우 이 시퀀스는 void에 대한 포인터에서 const void에 대한 포인터로 결과 유형을 캐스팅하기 위한 지정자의 추가 변환에 의해 첫 번째 시퀀스와 다릅니다. 이 변환에 의해서만 시퀀스가 ​​다르기 때문에 첫 번째 것이 더 적합한 것으로 선택되고 따라서 extract(const void*) 함수가 가장 좋은 것이 될 것입니다.
    const 및 volatile 지정자는 참조 매개변수 초기화 순위에도 영향을 줍니다. 이러한 두 초기화가 const 및 volatile 지정자를 추가하는 것만 다를 경우 추가 지정자가 없는 초기화는 오버로드 해결에서 더 나은 것으로 간주됩니다.

    #포함 무효 조작(벡터 &); 무효 manip(const 벡터 &); 벡터 에프();
    외부 벡터 벡; 정수 메인() (
    manip(벡); // 선택 manip(벡터 &)
    manip(f()); // 선택 manip(const 벡터 &)
    반환 0;
    }

    첫 번째 호출에서 모든 함수 호출에 대한 참조 초기화는 정확히 일치합니다. 그러나 이 도전은 여전히 ​​모호하지 않을 것입니다. 두 번째 경우에 추가 const 사양이 있는 것을 제외하고 두 초기화 모두 동일하므로 이러한 사양이 없는 초기화가 더 나은 것으로 간주되므로 잘 정립된 manip(vector &).
    두 번째 호출의 경우 잘 정립된 함수 manip(const vector &). 실제 인수는 f()에 의해 반환된 결과를 포함하는 임시 변수이므로 이러한 인수는 manip(vector)의 비 const 형식 참조 매개변수를 초기화하는 데 사용할 수 없는 r-값입니다. &). 따라서 유일하게 잘 확립된 manip(const vector &).
    물론 함수에는 여러 개의 실제 인수가 있을 수 있습니다. 모든 인수의 변환 순서 순위를 고려하여 가장 좋은 것을 선택해야 합니다. 예를 고려하십시오.

    외부 정수 ff(문자*, 정수); 외부 int ff(int, int); int main() ( ff(0, "a"); // ff(int, int)
    반환 0;
    }

    두 개의 int 인수를 사용하는 ff() 함수는 다음과 같은 이유로 최상의 함수로 선택됩니다.

    1. 그녀의 첫 번째 주장이 더 낫다. 0은 형식 int 매개변수와 정확히 일치하지만 char * 유형 매개변수와 일치하려면 표준 포인터 변환이 필요합니다.
    2. 두 번째 인수는 동일한 순위를 갖습니다. char 유형의 인수 "a"에 대해 두 함수 중 하나의 두 번째 형식 매개변수와 대응 관계를 설정하려면 유형 확장 순위를 갖는 변환 시퀀스가 ​​적용되어야 합니다.

    다음은 또 다른 예입니다.

    int 계산(const int&, short); int 계산(int&, double); 외부 intiobj;
    정수 메인() (
    계산(iobj, "c"); // 계산(int&, double)
    반환 0;
    }

    compute(const int&, short) 및 compute(int&, double) 함수는 모두 살아남았습니다. 두 번째는 다음과 같은 이유로 최고로 선택됩니다.

    1. 그녀의 첫 번째 주장이 더 낫다. 첫 번째로 설정된 함수에 대한 참조 초기화는 두 번째 함수에 필요하지 않은 const 지정자를 추가해야 하기 때문에 더 나쁩니다.
    2. 두 번째 인수는 동일한 순위를 갖습니다. char 유형의 인수 "c"에 두 함수 중 하나의 두 번째 형식 매개변수와 대응 관계를 설정하려면 표준 변환 순위를 갖는 변환 시퀀스가 ​​적용되어야 합니다.

    9.4.4. 기본값이 있는 인수

    기본값이 있는 인수를 사용하면 잘 정립된 많은 기능을 확장할 수 있습니다. 잔차는 주어진 실제 인수 목록으로 호출되는 함수입니다. 그러나 이러한 함수는 지정되지 않은 각 매개변수에 대해 일부 기본값이 있는 경우 제공된 실제 인수보다 더 형식적인 매개변수를 가질 수 있습니다.

    외부 무효 ff(int); 외부 무효 ff(long, int = 0); 정수 메인() (
    ff(2L); // ff(long, 0); ff(0, 0); // 일치하는 ff(long, int);
    ff(0); // ff(int);
    ff(3.14); // 오류: 모호성
    }

    첫 번째 및 세 번째 호출의 경우 실제 인수가 하나만 전달되었음에도 불구하고 ff() 함수가 해결됩니다. 이는 다음과 같은 이유 때문입니다.

    1. 두 번째 형식 매개변수에 대한 기본값이 있습니다.
    2. long 유형의 첫 번째 매개변수는 첫 번째 호출의 실제 인수와 정확히 일치하며 표준 변환의 순위를 갖는 시퀀스에 의해 세 번째 호출의 인수 유형으로 캐스트될 수 있습니다.

    첫 번째 인수에 표준 변환을 적용하여 설정된 두 함수를 모두 선택할 수 있기 때문에 마지막 호출은 모호합니다. ff(int) 함수는 매개변수가 하나만 있기 때문에 선호되지 않습니다.

    운동 9.9

    main() 내부에서 compute()를 호출하기 위해 오버로드를 해결할 때 어떤 일이 발생하는지 설명하십시오. 어떤 기능이 후보자입니까? 그들 중 첫 걸음을 떼면 어느 쪽이 서게 될까요? 각 설정된 기능에 대한 형식 매개변수에 해당하도록 실제 인수에 어떤 변환 순서를 적용해야 합니까? 어떤 기능이 가장 잘 서게 될까요?

    네임스페이스 primerLib( void compute(), void compute(const void *); ) 사용하여 primerLib::compute;
    무효 계산(int);
    무효 계산(더블, 더블 = 3.4);
    무효 계산(char*, char* = 0); 정수 메인() (
    계산(0);
    반환 0;
    }

    compute()를 호출하기 전에 using 선언이 main() 내부에 배치되면 어떻게 됩니까? 같은 질문에 답하세요.

    함수 오버로딩은 이름은 같지만 매개변수가 다른 여러 함수(2개 이상)의 정의입니다. 오버로드된 함수의 매개변수 집합은 순서, 개수 및 유형이 다를 수 있습니다. 따라서 유사한 작업을 수행하지만 프로그램 논리가 다른 함수의 이름이 중복되는 것을 피하기 위해 함수 오버로딩이 필요합니다. 예를 들어, 직사각형의 면적을 계산하는 areaRectangle() 함수를 생각해 보십시오.

    Float areaRectangle(float, float) // 두 개의 매개변수 a(cm), b(cm)로 사각형의 면적을 계산하는 함수 ( return a * b; // 사각형의 변의 길이를 곱하여 반환 결과 제품)

    따라서 이것은 두 개의 float 유형 매개변수가 있는 함수이며 함수에 전달된 인수는 센티미터 단위여야 하며 float 유형의 반환 값도 센티미터 단위여야 합니다.

    초기 데이터(직사각형 변)가 미터와 센티미터로 주어진다고 가정합니다. 예: a = 2m 35 cm; b = 1m 86 cm 이 경우 매개변수가 4개인 함수를 사용하는 것이 편리합니다. 즉, 직사각형 변의 각 길이는 미터와 센티미터의 두 매개변수로 함수에 전달됩니다.

    Float areaRectangle(float a_m, float a_sm, float b_m, float b_sm) // 4개의 매개변수로 직사각형의 면적을 계산하는 함수 a(m) a(cm); b(m) b(cm) ( return (a_m * 100 + a_sm) * (b_m * 100 + b_sm); )

    함수 본문에서 미터 단위로 전달된 값(a_m 및 b_m)은 센티미터로 변환되어 값 a_sm b_sm에 추가된 후 합계를 곱하고 사각형의 면적을 얻습니다. 물론 cm 단위로 원래 데이터를 센티미터로 변환하고 첫 번째 기능을 사용할 수 있었지만 지금은 그것에 관한 것이 아닙니다.

    이제 가장 중요한 것은 서명은 다르지만 이름은 같은 두 개의 함수(오버로드된 함수)가 있다는 것입니다. 서명은 함수 이름과 해당 매개변수의 조합입니다. 이러한 함수를 호출하는 방법은 무엇입니까? 그리고 오버로드된 함수를 호출하는 것은 일반 함수를 호출하는 것과 다르지 않습니다. 예를 들면 다음과 같습니다.

    AreaRectangle(32, 43); // 두 개의 매개변수 a(cm) 및 b(cm)를 사용하여 직사각형의 면적을 계산하는 함수가 호출됩니다. areaRectangle(4, 43, 2, 12); // 4개의 매개변수가 있는 직사각형의 면적을 계산하는 함수가 호출됩니다. a(m) a(cm); b(m) b(cm)

    보시다시피 컴파일러는 원하는 기능, 오버로드된 함수의 서명만 분석합니다. 함수 오버로딩을 우회하여 단순히 다른 이름으로 함수를 선언할 수 있으며 그 역할을 잘 수행할 것입니다. 그러나 예를 들어 10과 같이 그러한 기능이 두 개 이상 필요하면 어떻게 될지 상상해보십시오. 그리고 각각에 대해 의미있는 이름을 만들어야하며 기억하기 가장 어려운 것은 그것들입니다. 이것이 바로 이것이 필요하지 않는 한 함수를 오버로드하는 것이 더 쉽고 더 나은 이유입니다. 원천프로그램은 아래와 같습니다.

    #include "stdafx.h" #include << "S1 = " << areaRectangle(32,43) << endl; // вызов перегруженной функции 1 cout << "S2 = " << areaRectangle(4, 43, 2, 12) << endl; // вызов перегруженной функции 2 return 0; } // перегруженная функция 1 float areaRectangle(float a, float b) //функция, вычисляющая площадь прямоугольника с двумя параметрами a(см) и b(см) { return a * b; // умножаем длинны сторон прямоугольника и возвращаем полученное произведение } // перегруженная функция 2 float areaRectangle(float a_m, float a_sm, float b_m, float b_sm) // функция, вычисляющая площадь прямоугольника с 4-мя параметрами a(м) a(см); b(м) b(cм) { return (a_m * 100 + a_sm) * (b_m * 100 + b_sm); }

    // 코드 코드::블록

    // Dev-C++ 코드

    #포함 네임스페이스 std 사용 // 오버로드된 함수의 프로토타입 float areaRectangle(float a, float b); float areaRectangle(float a_m, float a_sm, float b_m, float b_sm); int 메인() (아웃<< "S1 = " << areaRectangle(32,43) << endl; // вызов перегруженной функции 1 cout << "S2 = " << areaRectangle(4, 43, 2, 12) << endl; // вызов перегруженной функции 2 return 0; } // перегруженная функция 1 float areaRectangle(float a, float b) //функция, вычисляющая площадь прямоугольника с двумя параметрами a(см) и b(см) { return a * b; // умножаем длинны сторон прямоугольника и возвращаем полученное произведение } // перегруженная функция 2 float areaRectangle(float a_m, float a_sm, float b_m, float b_sm) // функция, вычисляющая площадь прямоугольника с 4-мя параметрами a(м) a(см); b(м) b(cм) { return (a_m * 100 + a_sm) * (b_m * 100 + b_sm); }

    프로그램의 결과는 그림 1에 나와 있습니다.

    함수 오버로딩

    연산의 오버로딩(연산자, 기능, 프로시저)- 프로그래밍에서 - 이름은 같지만 매개변수 유형이 다른 여러 가지 작업(연산자, 함수 또는 프로시저)의 한 범위에서 동시에 존재할 수 있는 가능성으로 구성된 다형성을 구현하는 방법 중 하나 적용됩니다.

    술어

    "과부하"라는 용어는 1990년대 전반기에 프로그래밍 언어에 관한 책의 러시아어 번역에 나타난 영어 "과부하"의 추적 용지입니다. 아마도 이것은 러시아어로 된 "과부하"라는 단어가 새로 제안 된 것과 근본적으로 다른 자체 의미를 가지고 있기 때문에 가장 좋은 번역 옵션은 아니지만 뿌리를 내리고 꽤 널리 퍼져 있습니다. 소비에트 시대의 출판물에서 유사한 메커니즘이 러시아어 "재 정의"또는 "재 정의"로 호출되었지만이 옵션은 논쟁의 여지가 없습니다. 영어 "재정의", "과부하"및 " 재정의하다”.

    출현 이유

    대부분의 초기 프로그래밍 언어에는 동일한 이름을 가진 하나의 작업만 프로그램에서 동시에 사용할 수 있다는 제한이 있었습니다. 따라서 프로그램의 특정 지점에서 볼 수 있는 모든 함수와 프로시저는 서로 다른 이름을 가져야 합니다. 프로그래밍 언어의 일부인 함수, 프로시저 및 연산자의 이름과 지정은 프로그래머가 자신의 함수, 프로시저 및 연산자의 이름을 지정하는 데 사용할 수 없습니다. 어떤 경우에는 프로그래머가 이미 존재하는 다른 이름으로 자신의 프로그램 개체를 만들 수 있지만 새로 만든 개체는 이전 개체와 "겹쳐" 두 옵션을 동시에 사용할 수 없게 됩니다.

    이 상황은 상당히 일반적인 경우에 불편합니다.

    • 때로는 언어에서 이미 사용 가능한 것과 의미가 동등한 프로그래머가 생성한 데이터 유형에 작업을 설명하고 적용해야 할 필요가 있습니다. 고전적인 예는 복소수 작업을 위한 라이브러리입니다. 일반 숫자 유형과 마찬가지로 산술 연산을 지원하며 이러한 유형의 연산에 대해 "더하기", "빼기", "곱하기", "나누기"를 만드는 것이 자연스럽습니다. 다른 숫자와 동일한 연산 기호로 표시합니다. 유형. 언어에 정의된 요소 사용 금지로 인해 ComplexPlusComplex, IntegerPlusComplex, ComplexMinusFloat 등과 같은 이름을 가진 많은 함수가 생성됩니다.
    • 같은 의미의 연산이 다른 유형의 피연산자에 적용될 때 강제로 다른 이름을 지정해야 합니다. 다른 유형의 함수에 대해 동일한 이름의 함수를 사용할 수 없기 때문에 동일한 것에 대해 다른 이름을 발명해야 하므로 혼동을 일으키고 오류가 발생할 수도 있습니다. 예를 들어, 고전적인 C 언어에는 숫자의 모듈러스를 찾기 위한 두 가지 버전의 표준 라이브러리 함수가 있습니다. abs() 및 fabs() - 첫 번째는 정수 인수를 위한 것이고 두 번째는 실수 인수를 위한 것입니다. 약한 C 유형 검사와 결합된 이 상황은 찾기 어려운 오류로 이어질 수 있습니다. 프로그래머가 계산에서 abs(x)를 작성하면(여기서 x는 실제 변수인 경우) 일부 컴파일러는 경고 없이 코드를 생성합니다. 소수 부분을 삭제하여 x를 정수로 변환하고 결과 정수에서 모듈러스를 계산합니다!

    부분적으로 문제는 객체 프로그래밍을 통해 해결됩니다. 새로운 데이터 유형이 클래스로 선언되면 해당 데이터 유형에 대한 작업은 동일한 이름의 클래스 메소드를 포함하여 클래스 메소드로 형식화될 수 있습니다(다른 클래스의 메소드는 다음을 가질 필요가 없기 때문에 다른 이름), 그러나 첫째, 다른 유형의 값에 대한 이러한 설계 방식의 작업은 불편하고 둘째, 새 연산자를 만드는 문제를 해결하지 못합니다.

    연산자 오버로딩 자체는 "구문적 설탕"일 뿐이지만 개발자가 보다 자연스러운 방식으로 프로그래밍할 수 있고 사용자 정의 유형이 내장 유형처럼 작동하도록 하기 때문에 유용할 수 있습니다. 보다 일반적인 위치에서 문제에 접근하면 언어를 확장하고 새로운 작업 및 구문 구조로 보완할 수 있는 도구가 있음을 알 수 있습니다(그리고 작업의 오버로드는 개체, 매크로와 함께 이러한 도구 중 하나입니다. , 기능, 클로저) 특정 작업을 지향하는 언어를 설명하는 수단인 메타 언어로 이미 전환합니다. 그것의 도움으로 가장 자연스럽고 이해하기 쉽고 간단한 형식으로 솔루션을 설명할 수 있는 가장 적절한 각 특정 작업에 대한 언어 확장을 구축할 수 있습니다. 예를 들어 연산 오버로딩에 대한 응용 프로그램에서 복잡한 수학적 유형(벡터, 행렬)의 라이브러리를 만들고 이를 사용하여 자연스럽고 "수학적" 형식으로 연산을 설명하면 "벡터 연산을 위한 언어"가 생성됩니다. 계산이 숨겨져 있고 기술이 아닌 문제의 본질에 초점을 맞춘 벡터 및 행렬 연산의 관점에서 문제의 솔루션을 설명할 수 있습니다. 이러한 수단이 한때 Algol-68 언어에 포함된 것도 이러한 이유 때문입니다.

    과부하 메커니즘

    구현

    연산자 오버로딩은 언어에 두 개의 상호 관련된 기능을 도입하는 것을 포함합니다. 동일한 범위에서 동일한 이름을 가진 여러 프로시저 또는 함수를 선언하는 기능과 자신의 연산 구현(즉, 일반적으로 연산 기호, 피연산자 사이에 중위 표기법으로 작성됨). 기본적으로 구현은 매우 간단합니다.

    • 동일한 이름의 여러 연산이 존재하도록 하려면 이름(표기법)뿐만 아니라 컴파일러가 연산(프로시저, 함수 또는 연산자)을 인식하는 규칙을 언어에 도입하면 충분합니다. 또한 매개변수의 유형에 따라. 따라서 i가 정수로 선언된 abs(i)와 x가 실수로 선언된 abs(x)는 서로 다른 두 연산입니다. 기본적으로 그러한 해석을 제공하는 데에는 어려움이 없습니다.
    • 작업을 정의하고 재정의하려면 적절한 구문 구조를 언어에 도입해야 합니다. 많은 옵션이있을 수 있지만 실제로 서로 다르지 않으므로 "형식의 항목을 기억하는 것으로 충분합니다.<операнд1> <знакОперации> <операнд2>»는 근본적으로 함수를 호출하는 것과 유사합니다. «<знакОперации>(<операнд1>,<операнд2>)". 프로그래머가 연산자의 동작을 함수의 형태로 설명할 수 있도록 하는 것으로 충분하며 설명의 문제가 해결됩니다.

    옵션 및 문제

    일반적으로 일반적인 아이디어 수준에서 절차와 기능을 오버로딩하는 것은 구현하거나 이해하는 것이 어렵지 않습니다. 그러나 그 안에도 고려해야 할 몇 가지 "함정"이 있습니다. 연산자 오버로딩을 허용하면 언어 구현자와 해당 언어로 작업하는 프로그래머 모두에게 더 많은 문제가 발생합니다.

    식별 문제

    프로시저와 함수의 오버로딩을 허용하는 언어 번역기의 개발자가 직면하는 첫 번째 질문은 동일한 이름의 프로시저 중에서 이 특정 경우에 적용해야 하는 프로시저를 선택하는 방법입니다. 이 호출에 사용된 실제 매개변수의 유형과 정확히 일치하는 형식 매개변수 유형인 프로시저의 변형이 있으면 모든 것이 좋습니다. 그러나 컴파일러가 특정 상황에서 자동으로 형식 안전 변환을 수행한다고 가정하면 거의 모든 언어에서 형식 사용에 어느 정도의 자유가 있습니다. 예를 들어 실수 및 정수 인수에 대한 산술 연산에서 정수는 일반적으로 실수 유형으로 자동 변환되고 결과는 실수입니다. add 함수의 두 가지 변형이 있다고 가정합니다.

    int add(int a1, int a2); float add(float a1, float a2);

    컴파일러는 x가 float이고 i가 int인 식 y = add(x, i)를 어떻게 처리해야 합니까? 분명히 정확히 일치하는 것은 없습니다. 두 가지 옵션이 있습니다: y=add_int((int)x,i) 또는 y=add_flt(x, (float)i) (여기서 add_int 및 add_float 이름은 각각 함수의 첫 번째 버전과 두 번째 버전을 나타냄) .

    문제가 발생합니다. 컴파일러에서 오버로드된 함수의 사용을 허용해야 합니까? 그렇다면 어떤 기준으로 사용된 특정 변형을 선택할 것입니까? 특히 위의 예에서 변환기는 선택 시 변수 y의 유형을 고려해야 합니까? 위의 상황이 가장 간단하고 훨씬 더 복잡한 경우가 가능하다는 점에 유의해야 합니다. 이는 내장 유형이 언어 규칙에 따라 변환될 수 있을 뿐만 아니라 프로그래머가 선언한 클래스도 변환할 수 있다는 사실에 의해 악화됩니다. , 친척 관계가 있는 경우 서로 캐스팅할 수 있습니다. 이 문제에 대한 두 가지 해결책이 있습니다.

    • 부정확한 식별을 전혀 금지합니다. 각각의 특정 유형 쌍에 대해 오버로드된 프로시저 또는 작업의 정확히 적합한 변형이 있어야 합니다. 이러한 옵션이 없으면 컴파일러에서 오류가 발생해야 합니다. 이 경우 프로그래머는 실제 매개변수를 원하는 유형 세트로 캐스트하기 위해 명시적 변환을 적용해야 합니다. 이 접근 방식은 내장 연산자와 오버로드된 연산자의 동작에 상당한 차이를 가져오기 때문에 형식을 다룰 때 상당한 자유를 허용하는 C++와 같은 언어에서는 불편합니다(산술 연산은 일반 숫자에 적용될 수 있음) 생각하지 않고 다른 유형으로 - 명시적 변환만 가능) 또는 작업을 위한 수많은 옵션의 출현.
    • "가장 가까운 맞춤"을 선택하기 위한 특정 규칙을 설정합니다. 일반적으로 이 변형에서 컴파일러는 안전한(비손실 정보) 유형 변환을 통해서만 소스에서 호출을 얻을 수 있는 변형을 선택하고, 그 중 몇 가지가 있는 경우 더 적은 수의 변형이 필요한 경우 선택할 수 있습니다. 이러한 전환. 결과가 둘 이상의 가능성을 남기면 컴파일러에서 오류가 발생하고 프로그래머가 명시적으로 변형을 지정해야 합니다.

    작업 과부하 특정 고려 사항

    프로시저 및 함수와 달리 프로그래밍 언어의 중위 연산에는 기능에 크게 영향을 미치는 두 가지 추가 속성이 있습니다. c: as (a + b )*c or like a+(b*c) ? 표현 a-b+c는 (a-b)+c 또는 a-(b+c) ?).

    언어에 내장된 작업에는 항상 미리 정의된 전통적인 우선 순위와 연관성이 있습니다. 이러한 작업의 재정의된 버전에는 어떤 우선 순위와 연관성이 있습니까? 더군다나 프로그래머가 만든 새 작업은 무엇입니까? 설명이 필요할 수 있는 다른 미묘함이 있습니다. 예를 들어, C에는 두 가지 형태의 증가 및 감소 연산자 ++ 및 -- - 접두사와 접미사가 있으며 서로 다르게 작동합니다. 이러한 연산자의 오버로드된 버전은 어떻게 작동해야 합니까?

    다른 언어는 이러한 문제를 다른 방식으로 처리합니다. 따라서 C++에서 오버로드된 연산자 버전의 우선 순위와 연관성은 언어에 정의된 것과 동일하게 유지됩니다. 특수 서명을 사용하여 증가 및 감소 연산자의 접두어 및 후위 형식을 별도로 오버로드하는 것이 가능합니다.

    따라서 int는 서명의 차이를 만드는 데 사용됩니다.

    신규 운영 발표

    새로운 작전이 발표된 상황은 훨씬 더 복잡합니다. 이러한 선언의 가능성을 언어로 포함하는 것은 어렵지 않지만 구현에는 상당한 어려움이 있습니다. 실제로 새 작업을 선언하는 것은 새 프로그래밍 언어 키워드를 만드는 것이며 텍스트의 작업이 일반적으로 구분 기호 없이 다른 토큰을 따를 수 있다는 사실 때문에 복잡합니다. 그들이 나타날 때 어휘 분석기의 구성에 추가적인 어려움이 발생합니다. 예를 들어, 언어에 이미 연산 "+"와 단항 "-"(기호 변경)이 있는 경우 표현식 a+-b는 a +(-b)로 정확하게 해석될 수 있지만 새로운 연산 +- 가 프로그램에서 선언되면 동일한 표현식이 이미 a (+-) b 로 구문 분석될 수 있기 때문에 즉시 모호성이 발생합니다. 언어의 개발자와 구현자는 이러한 문제를 어떤 방식으로든 처리해야 합니다. 옵션은 다시 다를 수 있습니다. 모든 새 작업은 단일 문자여야 하고 불일치가 있는 경우 작업의 "가장 긴" 버전이 선택된다고 가정합니다(즉, 다음 문자 집합이 읽을 때까지). 번역기는 모든 작업과 일치하고 계속 읽음) 번역 중 충돌을 감지하고 논란의 여지가 있는 경우 오류를 생성하려고 시도합니다. 어떤 식으로든 새로운 작업 선언을 허용하는 언어는 이러한 문제를 해결합니다.

    새로운 작업의 경우 연관성 및 우선 순위를 결정하는 문제도 있음을 잊어서는 안됩니다. 더 이상 표준 언어 작업 형태의 기성 솔루션이 없으며 일반적으로 언어 규칙에 따라 이러한 매개변수를 설정하기만 하면 됩니다. 예를 들어, 모든 새로운 연산을 왼쪽 연관 연산으로 만들고 동일, 고정, 우선 순위를 부여하거나 둘 다 지정하는 수단을 언어에 도입합니다.

    오버로딩 및 다형성 변수

    각 변수에 미리 선언된 형식이 있는 강력한 형식의 언어에서 오버로드된 연산자, 함수 및 프로시저를 사용할 때 아무리 복잡하더라도 각 특정 경우에 사용할 오버로드된 연산자의 버전을 결정하는 것은 컴파일러의 몫입니다. . 이것은 컴파일된 언어의 경우 연산자 오버로딩을 사용해도 성능이 저하되지 않는다는 것을 의미합니다. 어떤 경우에도 프로그램의 개체 코드에 잘 정의된 연산 또는 함수 호출이 있습니다. 언어에서 다형성 변수, 즉 다른 시간에 다른 유형의 값을 포함할 수 있는 변수를 사용할 수 있는 경우 상황이 다릅니다.

    오버로드된 연산이 적용될 값의 유형은 코드 번역 시 알 수 없기 때문에 컴파일러는 사전에 올바른 옵션을 선택할 수 있는 능력을 박탈당합니다. 이 경우 이 작업을 수행하기 직전에 인수의 값 유형을 결정하고 이 유형 집합에 해당하는 변형을 동적으로 선택하는 조각을 개체 코드에 포함해야 합니다. 게다가, 같은 코드라도 두 번째로 호출되더라도 다르게 실행될 수 있기 때문에 이러한 정의는 작업을 실행할 때마다 이루어져야 합니다.

    따라서 다형성 변수와 함께 연산자 오버로딩을 사용하면 호출할 코드를 동적으로 결정하는 것이 불가피합니다.

    비판

    과부하를 사용하는 것이 모든 전문가에게 유익한 것은 아닙니다. 함수 및 프로시저 오버로딩이 일반적으로 바람직하지 않은 경우(부분적으로는 일부 일반적인 "연산자" 문제로 이어지지 않기 때문에 부분적으로는 오용할 가능성이 적기 때문에) 연산자 오버로딩은 원칙적으로 특정 언어 구현에서 입니다. 많은 프로그래밍 이론가와 실무자로부터 상당히 심한 비판을 받고 있습니다.

    비평가들은 위에서 설명한 식별, 우선 순위 및 연관성의 문제가 종종 과부하된 연산자를 불필요하게 어렵거나 부자연스럽게 처리한다고 지적합니다.

    • 신분증. 언어에 엄격한 식별 규칙이 있는 경우 프로그래머는 오버로드된 연산이 있는 유형 조합을 기억하고 수동으로 피연산자를 캐스팅해야 합니다. 언어가 "대략적인" 식별을 허용한다면 다소 복잡한 상황에서 프로그래머가 염두에 두었던 작업의 변형이 수행될 것이라고 결코 확신할 수 없습니다.
    • 우선 순위와 연관성. 엄격하게 정의된 경우 이는 불편하고 주제 영역과 관련이 없을 수 있습니다(예: 집합이 있는 연산의 경우 우선 순위가 산술 연산과 다름). 프로그래머가 설정할 수 있는 경우 오류의 추가 소스가 됩니다(한 작업의 다른 변형이 다른 우선 순위 또는 연관성을 갖는 것으로 판명된 경우에만).

    자신의 오퍼레이션을 사용하는 것의 편리함이 프로그램의 제어성을 저하시키는 불편함을 얼마나 더 가중시킬 수 있는지는 명쾌한 답이 없는 질문이다.

    언어 구현의 관점에서 볼 때 동일한 문제로 인해 번역기가 복잡해지고 효율성과 신뢰성이 떨어집니다. 그리고 다형성 변수와 함께 오버로딩을 사용하면 컴파일 중에 하드코딩된 작업을 호출하는 것보다 분명히 느리고 개체 코드를 최적화할 기회가 더 적습니다. 다양한 언어로 오버로딩을 구현하는 특정 기능은 별도의 비판을 받습니다. 따라서 C++에서 비판의 대상은 오버로드된 함수 이름의 내부 표현에 대한 합의가 부족하여 다른 C++ 컴파일러에서 컴파일된 라이브러리 수준에서 비호환성을 야기할 수 있습니다.

    일부 비평가들은 소프트웨어 개발 이론과 실제 산업 관행의 일반 원칙을 기반으로 하는 과부하 작업에 대해 반대합니다.

    • Wirth 또는 Hoare와 같은 언어 구축에 대한 "청교도" 접근 방식의 지지자들은 단순히 연산자 오버로딩이 제거될 수 있기 때문에 반대합니다. 그들의 의견으로는 이러한 도구는이 합병증에 해당하는 추가 기능을 제공하지 않고 언어와 번역기를 복잡하게 만듭니다. 그들의 의견으로는 언어의 작업 지향적 확장을 만드는 아이디어가 매력적으로 보입니다. 실제로 언어 확장 도구를 사용하면 이 확장을 개발한 작성자만 프로그램을 이해할 수 있습니다. 프로그램은 다른 프로그래머가 이해하고 분석하기가 훨씬 더 어려워져 유지 관리, 수정 및 팀 개발이 더 어려워집니다.
    • 과부하를 사용할 가능성은 종종 도발적인 역할을 합니다. 프로그래머는 가능한 한 그것을 사용하기 시작합니다. 결과적으로 프로그램을 단순화하고 합리화하도록 설계된 도구가 복잡성과 혼란의 원인이 됩니다.
    • 오버로드된 연산자는 종류에 따라 예상한 대로 정확하게 수행하지 못할 수 있습니다. 예를 들어, a + b는 일반적으로 (항상 그런 것은 아님) b + a 와 같은 것을 의미하지만 + 연산자가 오버로드되는 언어에서 "one" + "two"는 "two" + "one"과 다릅니다. 문자열 연결.
    • 연산자 오버로딩은 프로그램 조각을 더 상황에 맞게 만듭니다. 표현식에 포함된 피연산자의 유형을 모르면 오버로드된 연산자를 사용하는 경우 표현식이 수행하는 작업을 이해할 수 없습니다. 예를 들어, C++ 프로그램에서 명령문은<< может означать и побитовый сдвиг, и вывод в поток. Выражение a << 1 возвращает результат побитового сдвига значения a на один бит влево, если a - целая переменная, но если a является выходным потоком , то же выражение выведет в этот поток строку «1» .

    분류

    다음은 연산자 오버로딩 허용 여부와 연산자가 미리 정의된 집합으로 제한되는지 여부에 따라 일부 프로그래밍 언어를 분류한 것입니다.

    운영 과부하 없음 과부하가 있다
    제한된 작업 세트
    • 오브젝티브-C
    • 파이썬
    새로운 작업을 정의할 수 있습니다.
    • PostgreSQL
    • 또한보십시오

      위키미디어 재단. 2010년 .

      다른 사전에 "Function Overloading"이 무엇인지 확인하십시오.

        - (연산자, 기능, 절차) 다형성을 구현하는 방법 중 하나를 프로그래밍할 때 여러 다른 변형(연산자, 기능 또는 ... Wikipedia



    C에서 함수 오버로딩을 달성하는 방법은 무엇입니까? (십)

    C에서 함수 오버로딩을 달성하는 방법이 있습니까? 다음과 같이 오버로드될 수 있는 간단한 기능을 찾고 있습니다.

    foo(int a) foo(char b) foo(float c, int d)

    직접적인 방법은 없다고 생각합니다. 존재하는 경우 해결 방법을 찾고 있습니다.

    아래 코드가 함수 오버로딩을 이해하는 데 도움이 되기를 바랍니다.

    #포함 #포함 int fun(int a, ...); int main(int argc, char *argv)( fun(1,10); fun(2,"cquestionbank"); return 0; ) int fun(int a, ...)( va_list vl; va_start(vl,a) ); if(a==1) printf("%d",va_arg(vl,int)); else printf("\n%s",va_arg(vl,char *)); )

    내 말은, 당신은 - 아니, 당신은 할 수 없습니다.

    va_arg 함수를 다음과 같이 선언할 수 있습니다.

    무효 my_func(char* 형식, ...);

    그러나 첫 번째 인수에 변수의 수와 유형에 대한 정보를 전달해야 합니다(예: printf() ).

    예, 좋아요.

    예를 들면 다음과 같습니다.

    void printA(int a)( printf("Hello world from printA: %d\n",a); ) void printB(const char *buff)( printf("Hello world from printB: %s\n",buff) ; ) #define Max_ITEMS() 6, 5, 4, 3, 2, 1, 0 #define __VA_ARG_N(_1, _2, _3, _4, _5, _6, N, ...) N #define _Num_ARGS_(... ) __VA_ARG_N(__VA_ARGS__) #define NUM_ARGS(...) (_Num_ARGS_(_0, ## __VA_ARGS__, Max_ITEMS()) - 1) #define CHECK_ARGS_MAX_LIMIT(t) if(NUM_ARGS(args)>t) #define CH if(NUM_ARGS(args) #define print(x , args ...) \ CHECK_ARGS_MIN_LIMIT(1) printf("오류");fflush(stdout); \ CHECK_ARGS_MAX_LIMIT(4) printf("오류");fflush(stdout) ; \ (( \ if (__builtin_types_compatible_p (typeof (x), int)) \ printA(x, ##args); \ else \ printB (x,##args); \ )) int main(int argc, char* * argv) ( int a=0; print(a); print("hello"); return (EXIT_SUCCESS); )

    printA와 printB에서 0과 hello를 출력합니다.

    컴파일러가 gcc이고 새 오버로드를 추가할 때마다 수동 업데이트를 수행하는 데 신경 쓰지 않는다면 매크로를 대량으로 만들고 호출자 관점에서 원하는 결과를 얻을 수 있습니다. 있을 수있다

    __builtin_types_compatible_p를 살펴본 다음 이를 사용하여 다음과 같은 작업을 수행하는 매크로를 정의합니다.

    #define foo(a) \ ((__builtin_types_compatible_p(int, a)?foo(a):(__builtin_types_compatible_p(float, a)?foo(a):)

    하지만 예, 불쾌합니다.

    편집하다: C1X는 다음과 같은 형식 표현식을 지원합니다.

    #define cbrt(X) _Generic((X), long double: cbrtl, \ 기본값: cbrt, \ float: cbrtf)(X)

    이미 언급했듯이 오버로딩은 C에서 지원하지 않는다는 의미입니다. 문제를 해결하는 일반적인 관용구는 함수가 태그가 있는 공용체를 취하는 것입니다. 이것은 struct 매개변수를 사용하여 구현되며, 여기서 struct 자체는 enum 과 같은 유형 표시기 유형과 다른 값 유형의 조합으로 구성됩니다. 예시:

    #포함 typedef 열거형( T_INT, T_FLOAT, T_CHAR, ) my_type; typedef struct( my_type 유형; 공용체( int a; float b; char c; ) my_union; ) my_struct; void set_overload (my_struct *whatever) ( switch (whatever->type) ( case T_INT:whatever->my_union.a = 1; break; case T_FLOAT:what->my_union.b = 2.0; break; case T_CHAR:whatever-> my_union.c = "3"; ) ) 무효 printf_overload (my_struct *whatever) ( switch (whatever->type) ( case T_INT: printf("%d\n",what->my_union.a); break; case T_FLOAT : printf("%f\n", every->my_union.b); break; case T_CHAR: printf("%c\n", every->my_union.c); break; ) ) int main(int argc, char* argv) ( my_struct s; s.type=T_INT; set_overload(&s); printf_overload(&s); s.type=T_FLOAT; set_overload(&s); printf_overload(&s); s.type=T_CHAR; set_overload(&s) ; printf_overload(&s); )

    C++만 사용하고 이 기능 외에 다른 C++ 기능은 사용하지 않을 수 있나요?

    지금까지 엄격하게 엄격한 C가 없었다면 대신 가변 함수를 권장합니다.

    다음 접근 방식은 다음과 유사합니다. a2800276, 그러나 일부 C99 매크로 사용:

    // `size_t`가 필요합니다. #include // 열거형을 허용하는 인수 유형 sum_arg_types ( SUM_LONG, SUM_ULONG, SUM_DOUBLE ); // 인자를 담을 구조체 struct sum_arg ( enum sum_arg_types type; union ( long as_long; unsigned long as_ulong; double as_double; ) value; ); // 배열의 크기 결정 #define count(ARRAY) ((sizeof (ARRAY))/(sizeof *(ARRAY))) // 이것이 우리 함수가 호출되는 방식입니다 #define sum(...) _sum( count(sum_args(__VA_ARGS__)), sum_args(__VA_ARGS__)) // `struct sum_arg` 배열 생성 #define sum_args(...) ((struct sum_arg )( __VA_ARGS__ )) // 인수에 대한 이니셜라이저 생성 #define sum_long (VALUE) ( SUM_LONG, ( .as_long = (VALUE) ) ) #define sum_ulong(VALUE) ( SUM_ULONG, ( .as_ulong = (VALUE) ) ) #define sum_double(VALUE) ( SUM_DOUBLE, ( .as_double = (VALUE) ) ) // 다형성 함수 long double _sum(size_t count, struct sum_arg * args) ( long double 값 = 0; for(size_t i = 0; i< count; ++i) { switch(args[i].type) { case SUM_LONG: value += args[i].value.as_long; break; case SUM_ULONG: value += args[i].value.as_ulong; break; case SUM_DOUBLE: value += args[i].value.as_double; break; } } return value; } // let"s see if it works #include int main() ( unsigned long foo = -1; long double 값 = sum(sum_long(42), sum_ulong(foo), sum_double(1e10)); printf("%Le\n", value); return 0; )

    당분간 질문의 _Generic 이후 _Generic, 표준 C(확장 없음)가 효과적으로 받았다 C11에 _Generic 단어 _Generic 추가 덕분에 (연산자가 아닌) 오버로딩 함수에 대한 지원. (버전 4.9부터 GCC에서 지원됨)

    (오버로딩은 질문에 표시된 방식으로 실제로 "내장된" 것은 아니지만 이와 같이 작동하는 것을 쉽게 파괴할 수 있습니다.)

    Generic은 sizeof 및 _Alignof와 동일한 제품군에 있는 컴파일 시간 연산자입니다. 표준 섹션 6.5.1.1에 설명되어 있습니다. 두 가지 주요 매개변수가 필요합니다. 표현식(런타임에 평가되지 않음)과 유형/표현식 연관 목록(스위치 블록과 비슷함)입니다. _Generic은 표현식의 제네릭 유형을 가져온 다음 해당 유형에 대한 목록에서 최종 결과 표현식을 선택하기 위해 "전환"합니다.

    Generic(1, float: 2.0, char *: "2", int: 2, 기본값: get_two_object());

    위의 표현식은 2로 평가됩니다. 제어 표현식의 유형은 int 이므로 int와 연관된 표현식을 값으로 선택합니다. 이 중 어느 것도 런타임에 남아 있지 않습니다. (기본 절은 필수입니다. 지정하지 않고 유형이 일치하지 않으면 컴파일 오류가 발생합니다.)

    함수 오버로딩에 유용한 기술은 C 전처리기에 의해 삽입될 수 있고 제어 매크로에 전달된 인수 유형에 따라 결과 표현식을 선택할 수 있다는 것입니다. 따라서 (C 표준의 예):

    #define cbrt(X) _Generic((X), \ long double: cbrtl, \ 기본값: cbrt, \ float: cbrtf \)(X)

    이 매크로는 인수 유형을 매크로에 전달하고 적절한 구현 함수를 선택한 다음 원래 매크로를 해당 함수에 전달하여 오버로드된 cbrt 작업을 구현합니다.

    따라서 원래 예제를 구현하기 위해 다음과 같이 할 수 있습니다.

    Foo_int (int a) foo_char (char b) foo_float_int (float c , int d) #define foo(_1, ...) _Generic((_1), \ int: foo_int, \ char: foo_char, \ float: _Generic(( FIRST(__VA_ARGS__,)), \int: foo_float_int))(_1, __VA_ARGS__) #define FIRST(A, ...) A

    이 경우 세 번째 경우에 default: 바인딩을 사용할 수 있지만 원칙을 여러 인수로 확장하는 방법을 보여주지는 않습니다. 최종 결과는 인수 유형에 대해 (많이) 걱정하지 않고 코드에서 foo(...)를 사용할 수 있다는 것입니다.

    더 많은 인수를 오버로드하거나 숫자를 변경하는 함수와 같은 더 복잡한 상황의 경우 유틸리티 매크로를 사용하여 정적 디스패치 구조를 자동으로 생성할 수 있습니다.

    무효 print_ii(int a, int b) ( printf("int, int\n"); ) 무효 print_di(double a, int b) ( printf("double, int\n"); ) 무효 print_iii(int a, int b, int c) ( printf("int, int, int\n"); ) void print_default(void) ( printf("알 수 없는 인수\n"); ) #define print(...) OVERLOAD(print, (__VA_ARGS__), \ (print_ii, (int, int)), \ (print_di, (double, int)), \ (print_iii, (int, int, int)) \) #define OVERLOAD_ARG_TYPES (int, double) #define OVERLOAD_FUNCTIONS (print) #include "activate-overloads.h" int main(void) ( print(44, 47); // "int, int"를 출력합니다. print(4.4, 47); // "double, int"를 출력합니다. (1, 2, 3); // "int, int, int"를 출력합니다. print(""); // "알 수 없는 인수"를 출력합니다)

    (여기서 구현). 약간의 노력을 기울이면 기본 제공 오버로드 지원이 있는 언어와 매우 유사하게 보일 수 있도록 상용구를 줄일 수 있습니다.

    제쳐두고, 그것은 이미 과부하가 가능했습니다. C99의 인수(유형이 아닌).

    C가 평가되는 방식이 당신을 움직일 수 있다는 점에 유의하십시오. 예를 들어 리터럴 문자를 전달하려고 하면 foo_int가 선택되고 오버로드가 문자열 리터럴을 지원하도록 하려면 foo_int가 필요합니다. 그러나 전반적으로 꽤 시원합니다.

    Leushenko의 대답은 정말 멋집니다: foo 예제만 GCC로 컴파일되지 않고 foo(7) 에서 실패하고 FIRST 매크로와 실제 함수 호출((_1, __VA_ARGS__) 과 충돌하여 추가 쉼표가 남습니다. 또한, foo(double) 과 같은 추가 오버로드를 제공하려는 경우 문제가 발생합니다.

    그래서 나는 빈 과부하를 허용하는 것을 포함하여 이 질문에 더 자세히 대답하기로 결정했습니다(foo(void) - 문제를 일으켰습니다...).

    이제 아이디어는 서로 다른 매크로에 둘 이상의 제네릭을 정의하고 인수의 수에 따라 올바른 것을 선택하는 것입니다!

    이 답변을 기반으로 인수의 수는 매우 간단합니다.

    #define foo(...) SELECT(__VA_ARGS__)(__VA_ARGS__) #define SELECT(...) CONCAT(SELECT_, NARG(__VA_ARGS__))(__VA_ARGS__) #define CONCAT(X, Y) CONCAT_(X, Y) # CONCAT_(X, Y) 정의 X ## Y

    좋습니다. SELECT_1 또는 SELECT_2(원하거나 필요한 경우 더 많은 인수)를 결정하므로 적절한 정의가 필요합니다.

    #define SELECT_0() foo_void #define SELECT_1(_1) _Generic ((_1), \ int: foo_int, \ char: foo_char, \ double: foo_double \) #define SELECT_2(_1, _2) _Generic((_1), \ double : _Generic((_2), \ int: foo_double_int \) \)

    첫째, 빈 매크로 호출(foo())은 여전히 ​​토큰을 생성하지만 비어 있습니다. 따라서 count 매크로는 매크로가 비어 있다고 하더라도 실제로 0 대신 1을 반환합니다. __VA_ARGS__ 다음에 쉼표를 사용하여 __VA_ARGS__하면 이 문제를 "쉽게" 고칠 수 있습니다. 조건부로, 목록이 비어 있는지 여부에 따라:

    #define NARG(...) ARG4_(__VA_ARGS__ 쉼표(__VA_ARGS__) 4, 3, 2, 1, 0)

    그것 보았다쉽지만 COMMA 매크로는 상당히 무겁습니다. 다행히도 이 주제는 Jens Gustedt의 블로그에서 이미 다룹니다(고마워요, Jens). 주요 트릭은 함수 매크로가 괄호 뒤에 오지 않는 한 확장되지 않는다는 것입니다. 자세한 설명은 Jens 블로그를 참조하세요... 필요에 따라 매크로를 약간 수정하면 됩니다(간결함을 위해 더 짧은 이름과 더 적은 인수를 사용하겠습니다) .

    #define ARGN(...) ARGN_(__VA_ARGS__) #define ARGN_(_0, _1, _2, _3, N, ...) N #define HAS_COMMA(...) ARGN(__VA_ARGS__, 1, 1, 1, 0 ) #define SET_COMMA(...) , #define COMMA(...) SELECT_COMMA \ (\ HAS_COMMA(__VA_ARGS__), \ HAS_COMMA(__VA_ARGS__ ()), \ HAS_COMMA(SET_COMMA __VA_ARGS__), \ HAS_COMMA(SET_COMMA () __VA)AR \) #define SELECT_COMMA(_0, _1, _2, _3) SELECT_COMMA_(_0, _1, _2, _3) #define SELECT_COMMA_(_0, _1, _2, _3) COMMA_ ## _0 ## _1 ## _2 ## _3 # define COMMA_0000 , #define COMMA_0001 #define COMMA_0010 , // ... (다른 모든 것은 쉼표로) #define COMMA_1111 ,

    그리고 이제 우리는 괜찮아...

    한 블록의 전체 코드:

    /* * demo.c * * 작성된 날짜: 2017-09-14 * 작성자: sboehler */ #include void foo_void(void) ( puts("void"); ) void foo_int(int c) ( printf("int: %d\n", c); ) void foo_char(char c) ( printf("char: %c \n", c); ) 무효 foo_double(더블 c) ( printf("더블: %.2f\n", c); ) 무효 foo_double_int(더블 c, int d) ( printf("더블: %.2f, int: %d\n", c, d); ) #define foo(...) SELECT(__VA_ARGS__)(__VA_ARGS__) #define SELECT(...) CONCAT(SELECT_, NARG(__VA_ARGS__))(__VA_ARGS__) # define CONCAT(X, Y) CONCAT_(X, Y) #define CONCAT_(X, Y) X ## Y #define SELECT_0() foo_void #define SELECT_1(_1) _Generic ((_1), \ int: foo_int, \ char : foo_char, \ double: foo_double \) #define SELECT_2(_1, _2) _Generic((_1), \ double: _Generic((_2), \ int: foo_double_int \) \) #define ARGN(...) ARGN_( __VA_ARGS__) #define ARGN_(_0, _1, _2, N, ...) N #define NARG(...) ARGN(__VA_ARGS__ COMMA(__VA_ARGS__) 3, 2, 1, 0) #define HAS_COMMA(...) ARGN(__VA_ARGS__, 1, 1, 0) #define SET_COMMA(...) , #define COMMA(...) SELECT_COMMA \ (\ HAS_COMMA(__VA_ARGS__), \ HAS_COMMA(__VA_ARGS__ ()), \ HAS_C OMMA(SET_COMMA __VA_ARGS__), \ HAS_COMMA(SET_COMMA __VA_ARGS__ ()) \) #define SELECT_COMMA(_0, _1, _2, _3) SELECT_COMMA_(_0, _1, _2, _3) #define SELECT_COMMA_(3) COMMA_ ## _0 ## _1 ## _2 ## _3 COMMA_1001 , #define COMMA_1010 , #define COMMA_1011 , #define COMMA_1100 , #define COMMA_1101 , #define COMMA_1110 , #define COMMA_1110 , #define COMMA (푸(); foo(7); 푸(10.12); foo(12.10, 7); foo((문자)"s"); 반환 0; )

    C++ 언어는 서로 다른 데이터 유형에 대해 서로 다른 작업을 수행하는 함수에 대해 동일한 식별자를 사용하는 기능을 구현하므로 결과적으로 동일한 이름을 가진 여러 함수를 사용할 수 있지만 숫자와 유형 모두에서 매개변수 목록이 다릅니다.

    이러한 기능을 호출 과부하, 메커니즘 자체 초과 적재기능.

    컴파일러는 실제 인수의 유형을 이러한 모든 함수의 헤더에 있는 형식 매개변수 유형과 비교하여 동일한 이름을 가진 함수 중 어떤 것을 호출해야 하는지 결정합니다. 컴파일러는 인수의 유형과 수에 따라 해당 함수에 대한 필수 호출을 형성합니다.

    호출할 함수를 찾는 것은 세 가지 개별 단계로 수행됩니다.

    1. 매개변수와 정확히 일치하는 함수를 검색하고 찾으면 사용합니다.

    2. 내장 데이터 유형 변환을 사용하여 적절한 함수를 검색합니다.

    3. 사용자 정의 변환을 사용하여 적절한 함수를 검색합니다.

    함수 오버로딩 예제

    함수의 예를 들어보자 에스두 가지 옵션이 있는 1개 엑스,~에, 다음과 같이 전달된 인수 유형에 따라 작동합니다.

    – 매개변수 유형이 정수인 경우 함수 에스 1은 값을 더하고 결과 합계를 반환합니다.

    – 매개변수 유형인 경우 , 기능 에스 1은 값을 곱하고 결과 제품을 반환합니다.

    – 매개변수 유형이 실수인 경우 함수 에스 1은 값을 나누고 몫을 반환합니다.

    #포함

    int S1 (int x, int y) (

    긴 S1 (긴 x, 긴 y) (

    더블 S1 (더블 x, 더블 y) (

    정수 a = 1, b = 2, c;

    긴 i = 3, j = 4, k;

    이중 x = 10, y = 2, z;

    printf("\n c = %d; k = %ld; z = %lf . \n", c, k, z);

    결과적으로 다음을 얻습니다.

    = 3; 케이 = 12; = 5.000000 .

    변수 매개변수 함수

    사용자 정의 함수의 매개변수 목록에서 줄임표는 인수의 개수를 미리 알 수 없는 경우에 사용됩니다. 이 경우 프로토타입에 다음과 같이 무한한 수의 매개변수를 지정할 수 있습니다.

    공허 f1 (정수 , 더블비 , …);

    이러한 표기법은 컴파일러에게 매개변수에 필요한 실제 인수 뒤에 그리고 이 함수를 호출할 때 다른 인수를 따를 수도 있고 따르지 않을 수도 있습니다.

    이 메커니즘을 사용하는 주요 기능을 나열합니다.

    1. 이러한 기능의 매개변수에 액세스하기 위해 다음과 같은 여러 매크로 명령이 사용됩니다.

    _ 목록 그리고 _ 시작 – 매개변수에 대한 액세스를 준비하기 위한 매크로

    _ 인수 – 매개변수의 사용

    _ - 출구 전 청소.

    헤더 파일에 선언되어 있습니다. 표준 . 시간 .

    2. 그러한 함수에는 전달될 인수의 수를 전달하기 위해 적어도 하나의 (명명된) 매개변수가 있어야 합니다.

    3. 매크로의 경우 _ 시작두 개의 인수를 전달해야 합니다. 매개변수 목록의 이름은 다음을 지정합니다. _ 목록그리고 그들의 번호.

    4. 매크로의 지정된 순서를 깨는 것은 불가능합니다. 그렇지 않으면 예측할 수 없는 결과를 초래할 수 있습니다.

    5. 매크로의 경우 _ 인수매개변수 목록의 이름 외에 원하는 유형도 전달해야 합니다. 유형이 일치하지 않으면 오류가 발생합니다.

    줄임표를 사용하면 매개변수 유형 검사가 완전히 비활성화됩니다. 줄임표는 매개변수의 수와 유형이 모두 변경된 경우에만 필요합니다.

    다음 예는 이러한 가능성을 보여줍니다.

    #포함

    #포함

    무효 f1(더블 s, int n ...) (

    va_start(p, n);

    printf(" \n 더블 S = %lf ", s);

    for(int i=1; 나는<=n; i++) {

    v = va_arg(p, 정수);

    printf("\n 인수 %d = %d ", i, v);

    무효 메인(무효) (

    f1(1.5, 3, 4, 5, 6);

    결과적으로 다음을 얻습니다.

    더블 에스 = 1.500000

    인수 1 = 4

    인수 2 = 5

    인수 3 = 6

    계속하려면 아무 키나 누르세요.