음악, 삶, 개발

6. Functions 본문

개발 공부/C++ Primer (5th Edition)

6. Functions

Lee_____ 2020. 9. 17. 20:15
Lee : 이 장부터는 구글 번역기를 사용하려고 한다. 내가 직접 타이핑하는 게 너무 오래 걸려서...(평생 다 못 읽음)
따라서, 간혹 어색한 문장이 있을 수 있으니 알아서 유추하기를 바란다.
하지만 대부분, 놀랍게도 굉장히 잘 번역된다.
따라서, 어색한 부분이 있다 해도, 이해하는데 전혀 무리가 없을 것이다.
앞서 type이라는 영어를 일일이 적었는데, 유형으로 번역되어 앞으로는 적혀있을 것이다.
그리고 구글 번역기는 존댓말로 번역된다.

소개

 

이 장에서는 함수를 정의하고 선언하는 방법에 대해 설명합니다. 

인수가 전달되는 방법과 함수에서 반환되는 값에 대해 설명합니다. 

C++에서 함수는 오버로드 될 수 있습니다.

즉, 여러 다른 함수에 동일한 이름을 사용할 수 있습니다.

함수를 오버 로드하는 방법과 컴파일러가 여러 오버로드 된 함수에서 특정 호출에 대해

일치하는 버전을 선택하는 방법을 모두 다룰 것입니다.

이 장은 함수에 대한 포인터를 설명하는 것으로 끝납니다.

함수는 이름이 있는 코드 블록입니다. 

우리는 함수를 호출하여 코드를 실행합니다.
함수는 0 개 이상의 인수를 가질 수 있으며, 일반적으로 결과를 산출합니다.

함수는 오버로드 될 수 있습니다.

즉, 동일한 이름이 여러 다른 함수를 참조할 수 있습니다.


6.1 Function Basics

 

함수 정의는 일반적으로  return type, 이름, 0 개 이상의 매개 변수 목록 및 본문으로 구성됩니다.

매개 변수는 괄호로 묶인 쉼표로 구분된 목록으로 지정됩니다. 

함수가 수행하는 작업은 함수 본문이라고 하는 명령문 블록 (§ 5.1, p. 173)에 지정됩니다. 

한 쌍의 괄호 인 호출 연산자 (call operator)를 통해 함수를 실행합니다. 

호출 연산자는 함수이거나 함수를 가리키는 표현식을 사용합니다. 

괄호 안에는 쉼표로 구분된 인수 목록이 있습니다. 

인수는 함수의 매개 변수를 초기화하는 데 사용됩니다. 

호출 표현식의 유형은 함수의 반환 유형입니다.

 

Writing a Function

 

예를 들어 주어진 숫자의 factorial을 결정하는 함수를 작성합니다. 

숫자 n의 factorial은 1에서 n까지의 숫자의 곱입니다. 

예를 들어 5의 factorial은 120입니다.

1 * 2 * 3 * 4 * 5 = 120

이 함수를 다음과 같이 정의할 수 있습니다.

// factorial of val is val * (val - 1) * (val - 2) . . . * ((val - (val - 1)) * 1)
int fact(int val) {

    int ret = 1; // local variable to hold the result as we calculate it
    while (val > 1) {

        ret *= val--; // assign ret * val to ret and decrement val

    }

    return ret;

}

우리의 함수는 fact라고 명명됩니다. 

하나의 int 매개 변수를 취하고 int 값을 리턴합니다. 

while 루프 내에서 후위 감소 연산자 (§ 4.5, p. 147) -- 를 사용하여 factorial을 계산하여 반복할 때마다 val 값을 1 씩 줄입니다. return 문은 fact 실행을 종료하고 ret 값을 반환합니다.

 

Calling a Function

 

fact를 호출하려면 int 값을 제공해야 합니다. 호출의 결과도 int입니다.

int main() {

    int j = fact(5); // j equals 120, i.e., the result of fact(5)
    cout << "5! is " << j << endl;
    return 0;

}

함수 호출은 두 가지 작업을 수행합니다. 

해당 인수로 함수의 매개 변수를 초기화하고, 제어를 해당 함수로 전송합니다. 

호출 함수의 실행이 일시 중단되고 호출된 함수의 실행이 시작됩니다.
함수의 실행은 매개 변수의 (암시 적) 정의 및 초기화로 시작됩니다. 

따라서 fact를 호출할 때 가장 먼저 발생하는 것은 val이라는 int 변수가 생성되는 것입니다. 

이 변수는 fact 호출의 인수 (이 경우 5)에 의해 초기화됩니다.
함수 실행은 return 문이 나타나면 종료됩니다. 

함수 호출과 마찬가지로 return 문은 두 가지 작업을 수행합니다. 

return에 값 (있는 경우)을 반환하고 호출된 함수의 제어를 호출 함수로 다시 전송합니다.

함수에서 반환된 값은 호출 식의 결과를 초기화하는 데 사용됩니다.

실행은 호출이 나타난 표현의 나머지 부분으로 계속됩니다.

따라서 fact에 대한 호출은 다음과 같습니다.

int val = 5;       // initialize val from the literal 5
int ret = 1;       // code from the body of fact
while (val > 1) {

    ret *= val--;

}
 
int j = ret; // initialize j as a copy of ret

 

Parameters and Arguments

 

인수는 함수의 매개 변수에 대한 이니셜 라이저입니다. 

첫 번째 인수는 첫 번째 매개 변수를 초기화하고 두 번째 인수는 두 번째 매개 변수를 초기화하는 식입니다. 

어떤 인수가 어떤 매개 변수를 초기화하는지 알고 있지만 인수가 평가되는 순서에 대해서는 보장하지 않습니다 (§ 4.1.3, p. 137). 

컴파일러는 선호하는 순서대로 인수를 자유롭게 평가할 수 있습니다.
각 인수의 유형은 동일한 매개 변수와 일치해야 합니다.
이니셜 라이저의 유형이 초기화하는 객체의 유형과 일치해야 하는 방식입니다. 

함수에 매개 변수가 있는 것과 정확히 동일한 수의 인수를 전달해야 합니다. 

모든 호출은 함수에 매개 변수가 있는 만큼의 인수를 전달하도록 보장되기 때문에 매개 변수는 항상 초기화됩니다.
fact에는 int 유형의 단일 매개 변수가 있으므로 호출할 때마다
int로 변환할 수 있는 단일 인수 (§ 4.11, p. 159)를 제공합니다.

fact("hello");       // error: wrong argument type
fact();              // error: too few arguments
fact(42, 10, 0);     // error: too many arguments
fact(3.14);          // ok: argument is converted to int

const char* 에서 int 로의 변환이 없기 때문에 첫 번째 호출이 실패합니다.

두 번째 및 세 번째 호출은 잘못된 수의 인수를 전달합니다.

사실 함수는 하나의 인수로 호출되어야 합니다. 다른 개수로 호출하면 오류입니다.

double에서 int 로의 변환이 있기 때문에 마지막 호출은 합법적입니다.

이 호출에서 인수는 자르기(truncation)를 통해 암시적으로 int로 변환됩니다.

변환 후, 이 호출은 아래와 동일합니다.

fact(3);

 

Function Parameter List

 

함수의 매개 변수 목록은 비워 둘 수 있지만 생략할 수 없습니다. 

일반적으로 빈 매개 변수 목록을 작성하여 매개 변수가 없는 함수를 정의합니다. 

C와의 호환성을 위해 void 키워드를 사용하여 매개 변수가 없음을 나타낼 수도 있습니다.

void f1(){ /* ... */ }     // implicit void parameter list
void f2(void){ /* ... */ } // explicit void parameter list

매개 변수 목록은 일반적으로 쉼표로 구분된 매개 변수 목록으로 구성됩니다.
단일 선언자가 있는 선언처럼 보입니다. 

두 매개 변수의 유형이 동일하더라도 유형은 반복되어야 합니다.

int f3(int v1, v2) { /* ... */ }     // error
int f4(int v1, int v2) { /* ... */ } // ok

두 개의 매개 변수가 같은 이름을 가질 수 없습니다. 

또한 함수의 가장 바깥쪽 범위에 있는 지역 변수는 매개 변수와 같은 이름을 사용할 수 없습니다.
매개 변수 이름은 선택 사항입니다. 

그러나 이름이 지정되지 않은 매개 변수를 사용하는 방법은 없습니다. 

따라서 매개 변수에는 일반적으로 이름이 있습니다. 

가끔 함수에 사용되지 않는 매개 변수가 있습니다. 

이러한 매개 변수는 종종 사용되지 않음을 나타 내기 위해 이름이 지정되지 않은 상태로 유지됩니다. 

매개 변수의 이름을 지정하지 않아도 호출에서 제공해야 하는 인수의 수가 변경되지 않습니다. 

해당 매개 변수가 사용되지 않더라도 호출은 모든 매개 변수에 대한 인수를 제공해야 합니다.

 

Function Return Type

 

대부분의 유형은 함수의 반환 유형으로 사용할 수 있습니다. 

특히 반환 유형은 void 일 수 있으며 이는 함수가 값을 반환하지 않음을 의미합니다. 

그러나 반환 유형은 배열 유형 (§ 3.5, p. 113) 또는 함수 유형이 될 수 없습니다. 

그러나 함수는 배열이나 함수에 대한 포인터를 반환할 수 있습니다. 

§ 6.3.3 (p. 228)에서 배열에 대한 포인터 (또는 참조)를 반환하는 함수를 정의하는 방법과 

§ 6.7 (p. 247)에서 함수에 대한 포인터를 반환하는 방법을 살펴보겠습니다.


6.1.1 Local Objects (지역 변수)

 

C++에서 이름에는 범위 (§ 2.2.4, p. 48)가 있고 객체에는 수명(lifetimes)이 있습니다.

이 두 개념을 모두 이해하는 것이 중요합니다.

 

- 이름의 범위는 해당 이름이 표시되는 프로그램 텍스트의 일부입니다.

- 객체의 수명은 객체가 존재하는 프로그램 실행 시간입니다.

 

지금까지 살펴본 것처럼 함수의 본문은 명령문 블록입니다. 

평소와 같이 블록은 변수를 정의할 수 있는 새로운 범위를 형성합니다. 

함수 본문 내에 정의된 매개 변수와 변수를 지역 변수(local variables)라고 합니다. 

그것들은 그 함수에 "local"되며 외부 범위에서 만들어진 동일한 이름의 선언을 숨깁니다.

함수 외부에 정의된 객체는 프로그램 실행 내내 존재합니다. 

이러한 객체는 프로그램이 시작될 때 만들어지고 프로그램이 끝날 때까지 삭제되지 않습니다.

지역 변수의 수명은 정의 방법에 따라 다릅니다.

 

Automatic Objects (자동 객체)

 

함수의 제어 경로가 변수의 정의를 통과하면 일반 지역 변수에 해당하는 객체가 만들어집니다. 

제어가 변수가 정의된 블록의 끝을 통과하면 파괴됩니다. 

블록이 실행되는 동안에 만 존재하는 객체를 자동 객체(automatic objects)라고 합니다.

실행이 블록을 종료한 후 해당 블록에서 생성된 자동 객체의 값은 정의되지 않습니다.
매개 변수는 자동 객체입니다.

함수가 시작될 때 매개 변수 저장 공간이 할당됩니다.

매개 변수는 함수 본문의 범위에서 정의됩니다.

따라서 함수가 종료되면 소멸됩니다.
함수의 매개 변수에 해당하는 자동 객체는 함수에 전달된 인수에 의해 초기화됩니다. 

지역 변수에 해당하는 자동 객체는 정의에 이니셜 라이저가 포함된 경우 초기화됩니다. 

그렇지 않으면 default 초기화됩니다 (§ 2.2.1, p. 43).

즉, 기본 제공 유형의 초기화되지 않은 지역 변수는 undefined 값을 가집니다.

 

Local static Objects

 

함수 호출에서 수명이 계속되는 지역 변수를 갖는 것이 유용할 수 있습니다. 

지역 변수를 정적(static)으로 정의하여 이러한 객체를 얻습니다.

각 로컬 static 개체는 처음 실행이 객체의 정의를 통과하기 전에 초기화됩니다.

함수가 종료될 때 로컬 static은 파괴되지 않습니다. 프로그램이 종료되면 소멸됩니다.
간단한 예로, 호출 횟수를 세는 함수가 있습니다.

size_t count_calls() {

    static size_t ctr = 0;  // value will persist across calls
    return ++ctr;

}

int main() {

    for (size_t i = 0; i != 10; ++i) {

        cout << count_calls() << endl;

    }

    return 0;

}

이 프로그램은 1부터 10까지의 숫자를 인쇄합니다. 

제어 흐름이 처음으로 ctr 정의를 통과하기 전에 ctr이 생성되고 초기 값이 0으로 지정됩니다.

각 호출은 ctr을 증가시키고 새 값을 반환합니다.

count_calls가 실행될 때마다 변수 ctr은 이미 존재하며 함수가 마지막으로 종료되었을 때 해당 변수에 있던 값을 갖습니다.

따라서 두 번째 호출에서 ctr 값은 1이고 세 번째 호출에서는 2입니다.
로컬 static에 명시적 이니셜 라이저가 없으면 값이 초기화됩니다 (§ 3.3.1, p. 98).

즉, 기본 제공 유형의 로컬 static이 0으로 초기화됩니다.


6.1.2 Function Declarations

 

다른 이름과 마찬가지로 함수 이름은 사용하기 전에 선언되어야 합니다. 

변수 (§ 2.2.2, p. 45)와 마찬가지로 함수는 한 번만 정의할 수 있지만 여러 번 선언할 수 있습니다. 

§ 15.3 (p. 603)에서 다룰 한 가지 예외를 제외하고는 해당 함수를 사용하지 않는 한 정의되지 않은 함수를 선언할 수 있습니다.
함수 선언은 선언에 함수 본문이 없다는 점을 제외하면 함수 정의와 같습니다. 선언에서 세미콜론은 함수 본문을 대체합니다.
함수 선언에는 본문이 없기 때문에 매개 변수 이름이 필요하지 않습니다.
따라서 매개 변수 이름은 선언에서 종종 생략됩니다. 

매개 변수 이름은 필수는 아니지만 함수 사용자가 함수의 기능을 이해하는 도움이 되도록 포함시키는 것이 좋습니다.

// parameter names chosen to indicate that the iterators denote a range of values to print

void print(vector<int>::const_iterator beg, vector<int>::const_iterator end);

반환 유형, 함수 이름 및 매개 변수 유형이라는 세 가지 요소는 함수의 인터페이스를 설명합니다. 

함수를 호출하는 데 필요한 모든 정보를 지정합니다.

함수 선언은 function prototype이라고도 합니다.

 

Function Declarations Go in Header Files

 

변수는 헤더 파일 (§ 2.6.3, p. 76)에서 선언되고 소스 파일에서 정의됩니다. 

같은 이유로 함수는 헤더 파일에 선언되고 소스 파일에 정의되어야 합니다.
함수를 사용하는 각 소스 파일에 함수 선언을 직접 넣는 것은 유혹적이고 합법적 일 수 있습니다. 

그러나 그렇게 하는 것은 지루하고 오류가 발생하기 쉽습니다. 

함수 선언에 헤더 파일을 사용할 때 주어진 함수에 대한 모든 선언이 일치하는지 확인할 수 있습니다. 

또한 함수에 대한 인터페이스가 변경되면 하나의 선언만 변경하면 됩니다.
함수를 정의하는 소스 파일은 해당 함수의 선언을 포함하는 헤더를 포함해야 합니다. 

이렇게 하면 컴파일러가 정의와 선언이 일관 적인지 확인합니다.

 

Best Practices

 

함수를 선언하는 헤더는 해당 함수를 정의하는 소스 파일에 포함되어야 합니다.


6.1.3 Separate Compilation

 

프로그램이 더 복잡 해짐에 따라 프로그램의 다양한 부분을 별도의 파일로 저장하고 싶을 것입니다.

예를 들어 § 6.1 (페이지. 205)의 연습을 위해 작성한 함수를 하나의 파일에 저장하고 

이러한 함수를 사용하는 코드를 다른 소스 파일에 저장할 수 있습니다. 

프로그램이 논리적 부분으로 작성될 수 있도록 C ++는 일반적으로 별도의 컴파일로 알려진 것을 지원합니다. 

별도의 컴파일을 통해 프로그램을 여러 파일로 분할할 수 있으며 각 파일은 독립적으로 컴파일할 수 있습니다.

 

Compiling and Linking Multiple Source Files

 

예를 들어fact 함수의 정의가 fact.cc라는 파일에 있고 선언이 Chapter 6.h라는 헤더 파일에 있다고 가정합니다. 

이러한 함수를 사용하는 모든 파일과 마찬가지로 fact.cc 파일에는 Chapter 6.h 헤더가 포함됩니다. 

factMain.cc라는 두 번째 파일에 fact를 호출하는 기본 함수를 저장합니다. 

실행 파일을 생성하려면 사용하는 모든 코드를 찾을 수 있는 위치를 컴파일러에 알려야 합니다. 

이 파일을 다음과 같이 컴파일할 수 있습니다.

$ CC factMain.cc fact.cc   # generates factMain.exe or a.out
$ CC factMain.cc fact.cc -o main # generates main or main.exe

여기서 CC는 컴파일러의 이름이고 $는 시스템 프롬프트이며 #은 명령 줄 주석을 시작합니다. 

이제 주 기능을 실행할 실행 파일을 실행할 수 있습니다.
소스 파일 중 하나만 변경한 경우 파일만 다시 컴파일하고 싶습니다.
실제로 변경되었습니다. 

대부분의 컴파일러는 각 파일을 개별적으로 컴파일하는 방법을 제공합니다. 

이 프로세스는 일반적으로. obj (Windows) 또는. o (UNIX) 파일 확장자를 가진 파일을 생성하여 

파일에 오브젝트 코드가 포함되어 있음을 나타냅니다.

컴파일러를 사용하면 객체 파일을 함께 연결하여 실행 파일을 만들 수 있습니다. 

우리가 사용하는 시스템에서 다음과 같이 프로그램을 별도로 컴파일합니다.

$ CC -c factMain.cc     # generates factMain.o
$ CC -c fact.cc         # generates fact.o
$ CC factMain.o fact.o  # generates factMain.exe or a.out
$ CC factMain.o fact.o -o main # generates main or main.exe

여러 소스 파일로 구성된 프로그램을 컴파일하고 실행하는 방법을 이해하려면 컴파일러의 사용 설명서를 확인해야 합니다.


6.2 Argument Passing

 

지금까지 살펴본 것처럼 함수를 호출할 때마다 해당 매개 변수가 호출에서 전달된 인수에 의해 생성되고 초기화됩니다.

 

Note

 

매개 변수 초기화는 변수 초기화와 동일한 방식으로 작동합니다.

 

다른 변수와 마찬가지로 매개 변수 유형은 매개 변수와 해당 인수 간의 상호 작용을 결정합니다. 

매개 변수가 reference (§ 2.3.1, p. 50)인 경우 매개 변수는 해당 인수에 바인딩됩니다. 그렇지 않으면 인수 값이 복사됩니다.
매개 변수가 reference 인 경우 해당 인수가 "passed by reference"또는 함수가 "called by reference"이라고 말합니다.

다른 reference와 마찬가지로 reference  매개 변수는 바인딩된 객체의 별칭입니다. 즉, 매개 변수는 해당 인수의 별명입니다.
인수 값이 복사될 때 매개 변수와 인수는 독립적인 객체입니다. 

우리는 그러한 인수가 "passed by value"되거나 또는 함수가 "called by value"된다고 말합니다.


6.2.1 Passing Arguments by Value

 

non-reference 유형 변수를 초기화하면 이니셜 라이저의 값이 복사됩니다.

변수에 대한 변경 사항은 이니셜 라이저에 영향을 주지 않습니다.

int n = 0;  // ordinary variable of type int
int i = n;  // i is a copy of the value in n
i = 42;     // value in i is changed; n is unchanged

값으로 인수를 전달하는 것은 위의 코드와 똑같은 방식으로 작동합니다. 

함수가 매개 변수에 수행하는 작업은 인수에 영향을 줄 수 없습니다. 

예를 들어, fact (§ 6.1, p. 202) 내부에서 매개 변수 val이 감소합니다.

ret *= val--;  // decrements the value of val

fact는 val의 값을 변경하지만 해당 변경은 fact에 전달된 인수에 영향을 주지 않습니다. 

fact(i) 호출은 i의 값을 변경하지 않습니다.

 

Pointer Parameters

 

포인터 (§ 2.3.2, p. 52)는 다른 non-reference 유형처럼 작동합니다.

포인터를 복사하면 포인터 값이 복사됩니다.

복사 후 두 포인터는 구별됩니다.

그러나 포인터는 또한 포인터가 가리키는 객체에 대한 간접 액세스를 제공합니다.

포인터를 통해 대입하여 해당 개체의 값을 변경할 수 있습니다 (§ 2.3.2, p. 55).

int n = 0, i = 42;
int* p = &n, *q = &i; // p points to n; q points to i
*p = 42;              // value in n is changed; p is unchanged
p = q;                // p now points to i; values in i and n are unchanged

포인터 매개 변수에도 동일한 동작이 적용됩니다.

// function that takes a pointer and sets the pointed-to value to zero
void reset(int* ip) {
 
    *ip = 0; // changes the value of the object to which ip points
    ip = 0;   // changes only the local copy of ip; the argument is unchanged

}

reset 호출 후 인수가 가리키는 객체는 0이지만 포인터 인수 자체는 변경되지 않습니다.

int i = 42;
reset(&i);                    // changes i but not the address of i
cout << "i = " << i << endl;  // prints i = 0

Best Practices

 

C 프로그래밍에 익숙한 프로그래머는 종종 포인터 매개 변수를 사용하여 함수 외부의 객체에 액세스 합니다. 

C++에서 프로그래머는 일반적으로 reference 매개 변수를 포인터를 대신해 사용합니다.


6.2.2 Passing Arguments by Reference

 

reference에 대한 작업은 실제로 reference가 refer 하는 객체에 대한 작업입니다 (§ 2.3.1, p. 50).

int n = 0, i = 42;
int& r = n;  // r is bound to n (i.e., r is another name for n)
r = 42; // n is now 42
r = i; // n now has the same value as i
i = r; // i has the same value as n

참조 매개 변수는이 동작을 이용합니다. 

함수가 하나 이상의 인수 값을 변경할 수 있도록 하는 데 자주 사용됩니다.
예를 들어, 포인터 대신 참조를 사용하도록 이전 섹션의 reset 프로그램을 다시 작성할 수 있습니다.

// function that takes a reference to an int and sets the given object to zero
// i is just another name for the object passed to reset 
void reset(int& i)  {
	
    i = 0; // changes the value of the object to which i refers

}

다른 참조와 마찬가지로 참조 매개 변수는 초기화되는 객체에 직접 바인딩됩니다. 

이 버전의 reset을 호출하면 우리가 전달하는 int 객체에 바인딩됩니다. 

여느 참조와 마찬가지로 i에 대한 변경 사항은 i가 참조하는 객체에 적용됩니다. 

이 경우 해당 객체는 재설정 할 인수입니다.
이 버전의 reset을 호출하면 객체를 직접 전달합니다. 주소를 전달할 필요가 없습니다.

int j = 42;
reset(j);  // j is passed by reference; the value in j is changed
cout << "j = " << j  << endl;  // prints j = 0

이 호출에서 매개 변수 i는 j의 또 다른 이름입니다.
reset 내부에서 i를 사용하는 것은 j를 사용하는 것입니다.

 

Using References to Avoid Copies

 

대형 클래스 유형 또는 대형 컨테이너의 오브젝트를 복사하는 것은 비효율적 일 수 있습니다. 

또한 일부 클래스 유형 (IO 유형 포함)은 복사할 수 없습니다. 

함수는 복사 할 수 없는 유형의 개체에 대해 작동하려면 참조 매개 변수를 사용해야 합니다.
예를 들어 두 string의 길이를 비교하는 함수를 작성합니다.

string은 길 수 있으므로 복사를 피하고 싶으므로 매개 변수 참조를 작성합니다.

두 string을 비교하는 데 string 변경이 포함되지 않으므로 매개 변수가 const를 참조하도록 만들 것입니다 (§ 2.4.1, p. 61).

// compare the length of two strings
bool isShorter(const string& s1, const string& s2) {

    return s1.size() < s2.size();

}

§ 6.2.3 (p. 213)에서 볼 수 있듯이 함수는 변경할 필요가없는 참조 매개 변수에 대해 const 참조를 사용해야 합니다.

 

Best Practices

 

함수 내에서 변경되지 않는 참조 매개 변수는 const에 대한 참조 여야 합니다.

 

Using Reference Parameters to Return Additional Information

 

함수는 단일 값만 반환할 수 있습니다. 

그러나 때로는 함수에 반환 할 값이 두 개 이상 있습니다. 

참조 매개 변수를 사용하면 여러 결과를 효과적으로 반환 할 수 있습니다. 

예를 들어, string에서 주어진 문자가 처음 나타나는 위치를 반환하는 find_char라는 함수를 정의합니다.

또한 해당 문자가 발생하는 횟수를 반환하는 함수를 원합니다.

위치와 발생 횟수를 반환하는 함수를 어떻게 정의할 수 있습니까?

위치와 개수를 포함하는 새로운 유형을 정의 할 수 있습니다.

더 쉬운 해결책은 발생 횟수를 유지하기 위해 추가 참조 인수를 전달하는 것입니다.

// returns the index of the first occurrence of c in s
// the reference parameter occurs counts how often c occurs
string::size_type find_char(const string& s, char c, string::size_type& occurs) {

    auto ret = s.size();    // position of the first occurrence, if any
    occurs = 0;             // set the occurrence count parameter

    for (decltype(ret) i = 0; i != s.size(); ++i) {

        if (s[i] == c) {

            if (ret == s.size()) {

                ret = i; // remember the first occurrence of c

            }

            ++occurs; // increment the occurrence count

        }

    }

    return ret; // count is returned implicitly in occurs

}

find_char를 호출할 때 세 가지 인수를 전달해야 합니다. 

검색할 string, 검색 할 문자, 발생 횟수를 저장할 size_type (§ 3.2.2, p. 88) 객체입니다.

s가 문자열이고 ctr이 size_type 객체라고 가정하면 다음과 같이 find_char를 호출할 수 있습니다.

auto index = find_char(s, 'o', ctr);

호출 후 ctr의 값은 o가 발생하는 횟수가 되고 index는 첫 번째 발생이 있는 경우 이를 참조합니다. 

그렇지 않으면 index는 s.size()와 같고 ctr은 0이 됩니다.


6.2.3 const Parameters and Arguments

 

const 인 매개 변수를 사용할 때 § 2.4.3 (p. 63)의 최상위 (top-level) const에 대한 논의를 기억하는 것이 중요합니다. 

이 섹션에서 보았 듯이 최상위 const는 객체 자체에 적용되는 것입니다.

const int ci = 42; // we cannot change ci; const is top-level
int i = ci; // ok: when we copy ci, its top-level const is ignored
int* const p = &i; // const is top-level; we can't assign to p
*p = 0; // ok: changes through p are allowed; i is now 0

다른 초기화와 마찬가지로 매개 변수를 초기화하기 위해 인수를 복사할 때 최상위 const는 무시됩니다. 

결과적으로 매개 변수에 대한 최상위 상수는 무시됩니다. 

const 또는 nonconst 객체를 최상위 const가 있는 매개 변수에 전달할 수 있습니다.

void fcn(const int i) { /* fcn can read but not write to i */ }

const int 또는 plain int를 전달하는 fcn을 호출할 수 있습니다. 

매개 변수에서 최상위 수준 const가 무시된다는 사실은 놀라운 의미를 가질 수 있습니다.

void fcn(const int i) { /* fcn can read but not write to i */ }
void fcn(int i) { /* . . . */ } // error: redefines fcn(int)

C++에서는 같은 이름을 가진 여러 다른 함수를 정의할 수 있습니다.

그러나 매개 변수 목록이 충분히 다른 경우에만 그렇게 할 수 있습니다.

최상위 const는 무시되기 때문에 fcn의 두 버전에 정확히 동일한 유형을 전달할 수 있습니다.

fcn의 두 번째 버전은 오류입니다.

외관에도 불구하고 매개 변수 목록은 fcn의 첫 번째 버전에 있는 목록과 다르지 않습니다.

 

Pointer or Reference Parameters and const

 

매개 변수는 변수가 초기화되는 것과 같은 방식으로 초기화되기 때문에,

일반적인 초기화 규칙을 기억하는 것이 도움이 될 수 있습니다.

const가 아닌 객체에서 낮은 수준의 const를 사용하여 객체를 초기화할 수 있지만,

그 반대는 불가능하며 일반 참조는 동일한 유형의 객체에서 초기화해야 합니다.

int i = 42; 
const int* cp = &i; // ok: but cp can't change i (§ 2.4.2 (p. 62)) 
const int& r = i;   // ok: but r can't change i (§ 2.4.1 (p. 61)) 
const int& r2 = 42; // ok: (§ 2.4.1 (p. 61)) 
int* p = cp;  // error: types of p and cp don't match (§ 2.4.2 (p. 62)) 
int& r3 = r;  // error: types of r3 and r don't match (§ 2.4.1 (p. 61)) 
int& r4 = 42; // error: can't initialize a plain reference from a literal (§ 2.3.1 (p. 50))

위의 변수처럼, 매개 변수 전달에 정확히 동일한 초기화 규칙이 적용됩니다.

int i = 0; 
const int ci = i; 
string::size_type ctr = 0; 
reset(&i);   // calls the version of reset that has an int* parameter 
reset(&ci);  // error: can't initialize an int* from a pointer to a const int object 
reset(i);    // calls the version of reset that has an int& parameter 
reset(ci);   // error: can't bind a plain reference to the const object ci 
reset(42);   // error: can't bind a plain reference to a literal 
reset(ctr);  // error: types don't match; ctr has an unsigned type 
find_char("Hello World!", 'o', ctr); // ok: find_char's first parameter is a reference to const 

참조 버전의 reset (§ 6.2.2, p. 210)은 int 객체에서만 호출할 수 있습니다. 

리터럴, int로 평가되는 표현식, 변환이 필요한 객체 또는 const int 객체는 전달할 수 없습니다. 

마찬가지로 reset의 포인터 버전 (§ 6.2.1, p. 209)에는 int* 만 전달할 수 있습니다.

반면에 string 리터럴을 find_char의 첫 번째 인수로 전달할 수 있습니다 (§ 6.2.2, p. 211).

이 함수의 참조 매개 변수는 const에 대한 참조이며 리터럴로 const에 대한 참조를 초기화할 수 있습니다.

 

Use Reference to const When Possible

 

함수가 (일반) 참조로 변경되지 않는 매개 변수를 정의하는 것은 다소 일반적인 실수입니다. 

이렇게 하면 함수 호출자에게 함수가 인수 값을 변경할 수 있다는 오해의 소지가 있습니다. 

또한 참조 대신 참조를 사용하여 const에 사용할 수 있는 인수 유형을 부당하게 제한합니다. 

방금 본 것처럼 const 객체, 리터럴 또는 일반 참조 매개 변수로의 변환이 필요한 객체는 전달할 수 없습니다.
이 실수의 영향은 놀라 울 정도로 만연 할 수 있습니다. 

예를 들어 § 6.2.2 (p. 211)의 find_char 함수를 고려하십시오. 

이 함수는 (올바르게) string 매개 변수를 const에 대한 참조로 만들었습니다. 해당 매개 변수를 일반 string&로 정의했습니까?

// bad design: the first parameter should be a const string&
string::size_type find_char(string& s, char c, string::size_type& occurs);

위의 함수는 오직 string 객체에서만 호출될 수 있으며,

아래와 같은 호출은 컴파일 타임에서 실패합니다.

find_char("Hello World", 'o', ctr);

더 미묘하게도 우리는 매개 변수를 const에 대한 참조로 (올바르게) 정의하는 다른 함수에서

이 버전의 find_char를 사용할 수 없습니다. 

예를 들어, 문자열이 문장을 나타내는지 여부를 결정하는 함수 내에서 find_char를 사용할 수 있습니다.

bool is_sentence(const string& s) {

    // if there's a single period at the end of s, then s is a sentence
    string::size_type ctr = 0;

    return find_char(s, '.', ctr) == s.size() - 1 && ctr == 1;

}

find_char가 일반 string&을 사용하면 find_char에 대한이 호출은 컴파일 타임 오류가 됩니다. 

문제는 s가 const 문자열에 대한 참조이지만 find_char가 일반 참조를 사용하도록 (잘못) 정의되었다는 것입니다.
is_sentence에서 매개 변수의 유형을 변경하여이 문제를 해결하려고 시도할 수 있습니다. 

그러 나이 수정은 오류를 전파할 뿐입니다. 

is_sentence 호출자는 const가 아닌 string 만 전달할 수 있습니다.
이 문제를 해결하는 올바른 방법은 find_char에서 매개 변수를 수정하는 것입니다. 

find_char를 변경할 수 없는 경우 is_sentence 내에 s의 로컬 문자열 복사본을 정의하고 해당 문자열을 find_char에 전달합니다.


6.2.4 Array Parameters

 

Warning

Using a Marker to Specify the Extent of an Array

Using the Standard Library Conventions

Explicitly Passing a Size Parameter

Array Parameters and const

Array Reference Parameters

Note

Passing a Multidimensional Array

Note


6.2.5 main : Handling Command-Line Options

6.2.6 Functions with Varying Parameters

6.3 Return Types and the return Statement

6.3.1 Functions with No Return Value

6.3.2 Functions That Return a Value

6.3.3 Returning a Pointer to an Array

6.4 Overloaded Functions

6.4.1 Overloading and Scope

6.5 Features for Specialized Uses

6.5.1 Default Arguments

6.5.2 Inline and constexpr Functions

6.5.3 Aids for Debugging

6.6 Function Matching

6.6.1 Argument Type Conversions

6.7 Pointers to Functions