11.5 — Argumenty domyślne

A argument domyślny to domyślna wartość podana dla parametru funkcji. Na przykład:

void print(int x, int y=10) // 10 is the default argument
{
    std::cout << "x: " << x << '\n';
    std::cout << "y: " << y << '\n';
}

Podczas wykonywania wywołania funkcji osoba wywołująca może opcjonalnie podać argument dla dowolnego parametru funkcji, który ma argument domyślny. Jeżeli wywołujący poda argument, używana jest wartość argumentu z wywołania funkcji. Jeżeli osoba wywołująca nie poda argumentu, używana jest wartość argumentu domyślnego.

Rozważ następujący program:

#include <iostream>

void print(int x, int y=4) // 4 is the default argument
{
    std::cout << "x: " << x << '\n';
    std::cout << "y: " << y << '\n';
}

int main()
{
    print(1, 2); // y will use user-supplied argument 2
    print(3); // y will use default argument 4, as if we had called print(3, 4)

    return 0;
}

Ten program generuje następujące dane wyjściowe:

x: 1
y: 2
x: 3
y: 4

W pierwszym wywołaniu funkcji osoba wywołująca podała jawne argumenty dla obu parametrów, więc te wartości argumentów zostaną użyte. W drugim wywołaniu funkcji wywołujący pominął drugi argument, dlatego użyto wartości domyślnej 4 .

Pamiętaj, że aby określić argument domyślny, musisz użyć znaku równości. Użycie nawiasów lub inicjalizacja nawiasów nie będzie działać:

void foo(int x = 5);   // ok
void goo(int x ( 5 )); // compile error
void boo(int x { 5 }); // compile error

Być może, co zaskakujące, domyślne argumenty są obsługiwane przez kompilator w miejscu wywołania. W powyższym przykładzie, gdy kompilator zobaczy print(3), przepisze wywołanie tej funkcji jako print(3, 4), tak aby liczba argumentów odpowiadała liczbie parametrów. Przepisane wywołanie funkcji działa wtedy normalnie.

Kluczowa informacja

Domyślne argumenty są wstawiane przez kompilator w miejscu wywołania funkcji.

Domyślne argumenty są często używane w C++ i zobaczysz je często w kodzie, który napotkasz (oraz w przyszłych lekcjach).

Kiedy używać argumentów domyślnych

Argumenty domyślne są doskonałą opcją, gdy funkcja potrzebuje wartości, która ma rozsądną wartość domyślną, ale chcesz, aby obiekt wywołujący mógł ją zastąpić, jeśli sobie tego życzy.

Na przykład oto kilka prototypów funkcji, dla których często można używać argumentów domyślnych:

int rollDie(int sides=6);
void openLogFile(std::string filename="default.log");

Nota autora

Ponieważ użytkownik może wybrać, czy podać określoną wartość argumentu, czy użyć wartości domyślnej, parametr z podaną wartością domyślną jest czasami nazywany parametr opcjonalny. Jednakże termin parametr opcjonalny jest również używany w odniesieniu do kilku innych typów parametrów (w tym parametrów przekazywanych przez adres i parametrów wykorzystujących std::optional), dlatego zalecamy unikanie tego terminu.

Argumenty domyślne są również przydatne w przypadkach, gdy musimy dodać nowy parametr do istniejącej funkcji. Jeśli dodamy nowy parametr bez argumentu domyślnego, spowoduje to przerwanie wszystkich istniejących wywołań funkcji (które nie dostarczają argumentu dla tego parametru). Może to skutkować częstymi aktualizacjami istniejących wywołań funkcji (i może nawet nie być możliwe, jeśli nie posiadasz kodu wywołującego). Jeśli jednak zamiast tego dodamy nowy parametr z domyślnym argumentem, wszystkie istniejące wywołania funkcji będą nadal działać (ponieważ będą używać domyślnego argumentu dla parametru), jednocześnie pozwalając nowym wywołaniom funkcji na określenie jawnego argumentu, jeśli jest to wymagane.

Wiele domyślnych argumentów

Funkcja może mieć wiele parametrów z domyślnymi argumentami:

#include <iostream>

void print(int x=10, int y=20, int z=30)
{
    std::cout << "Values: " << x << " " << y << " " << z << '\n';
}

int main()
{
    print(1, 2, 3); // all explicit arguments
    print(1, 2); // rightmost argument defaulted
    print(1); // two rightmost arguments defaulted
    print(); // all arguments defaulted

    return 0;
}

Wygenerowane zostaną następujące dane wyjściowe:

Values: 1 2 3
Values: 1 2 30
Values: 1 20 30
Values: 10 20 30

C++ nie (od C++23) obsługują składnię wywołań funkcji, taką jak print(,,3) (w celu zapewnienia jawnej wartości z podczas używania domyślnych argumentów dla x i y. Ma to trzy główne konsekwencje:

  1. W wywołaniu funkcji wszelkie jawnie podane argumenty muszą być argumentami znajdującymi się skrajnie po lewej stronie (argumentów z wartościami domyślnymi nie można pominąć).

Na przykład:

void print(std::string_view sv="Hello", double d=10.0);

int main()
{
    print();           // okay: both arguments defaulted
    print("Macaroni"); // okay: d defaults to 10.0
    print(20.0);       // error: does not match above function (cannot skip argument for sv)

    return 0;
}
  1. Jeśli parametrowi podano argument domyślny, wszystkie kolejne parametry (po prawej) również muszą mieć podane argumenty domyślne.

Niedozwolone jest:

void print(int x=10, int y); // not allowed

Reguła

Jeśli parametrowi podano argument domyślny, wszystkie kolejne parametry (po prawej) również muszą mieć podane argumenty domyślne.

  1. Jeśli więcej niż jeden parametr ma argument domyślny, parametr znajdujący się najbardziej po lewej stronie powinien być tym, który najprawdopodobniej zostanie jawnie ustawiony przez użytkownika.

Argumenty domyślne nie mogą być ponownie zadeklarowane i muszą zostać zadeklarowane przed użyciem.

Po zadeklarowaniu argumentu domyślnego nie można ponownie zadeklarować w tej samej jednostce tłumaczenia. Oznacza to, że w przypadku funkcji z deklaracją forward i definicją funkcji argument domyślny można zadeklarować albo w deklaracji forward, albo w definicji funkcji, ale nie w obu przypadkach.

#include <iostream>

void print(int x, int y=4); // forward declaration

void print(int x, int y=4) // compile error: redefinition of default argument
{
    std::cout << "x: " << x << '\n';
    std::cout << "y: " << y << '\n';
}

Argument domyślny musi zostać również zadeklarowany w jednostce translacyjnej, zanim będzie można go użyć:

#include <iostream>

void print(int x, int y); // forward declaration, no default argument

int main()
{
    print(3); // compile error: default argument for y hasn't been defined yet

    return 0;    
}

void print(int x, int y=4)
{
    std::cout << "x: " << x << '\n';
    std::cout << "y: " << y << '\n';
}

Najlepszą praktyką jest deklarowanie domyślnego argumentu w deklaracji forward, a nie w definicji funkcji, ponieważ jest bardziej prawdopodobne, że deklaracja forward będzie widoczna w innych plikach i uwzględniona przed użyciem (szczególnie jeśli znajduje się w nagłówku) plik).

in foo.h:

#ifndef FOO_H
#define FOO_H
void print(int x, int y=4);
#endif

in main.cpp:

#include "foo.h"
#include <iostream>

void print(int x, int y)
{
    std::cout << "x: " << x << '\n';
    std::cout << "y: " << y << '\n';
}

int main()
{
    print(5);

    return 0;
}

Zauważ, że w powyższym przykładzie możemy użyć domyślnego argumentu dla funkcji print() ponieważ main.cpp #includes foo.h, który zawiera deklarację forward definiującą wartość domyślną argument.

Najlepsza praktyka

Jeśli funkcja posiada deklarację forward (zwłaszcza w pliku nagłówkowym), umieść tam argument domyślny. W przeciwnym razie w definicji funkcji umieść argument domyślny.

Argumenty domyślne i przeciążanie funkcji

Funkcje z argumentami domyślnymi mogą zostać przeciążone. Na przykład dozwolone jest co następuje:

#include <iostream>
#include <string_view>

void print(std::string_view s)
{
    std::cout << s << '\n';
}

void print(char c = ' ')
{
    std::cout << c << '\n';
}

int main()
{
    print("Hello, world"); // resolves to print(std::string_view)
    print('a');            // resolves to print(char)
    print();               // resolves to print(char)

    return 0;
}

Wywołanie funkcji print() faktycznie wywołuje print(char), co działa tak, jakby użytkownik jawnie wywołał print(' ').

Rozważmy teraz następujący przypadek:

void print(int x);                  // signature print(int)
void print(int x, int y = 10);      // signature print(int, int)
void print(int x, double y = 20.5); // signature print(int, double) 

Wartości domyślne nie są częścią sygnatury funkcji, więc te deklaracje funkcji są zróżnicowanymi przeciążeniami.

Powiązana treść

Omawiamy przeciążenie funkcji zróżnicowanie na lekcji 11.2 -- Różnicowanie przeciążenia funkcji

Domyślne argumenty mogą prowadzić do niejednoznacznych dopasowań

Domyślne argumenty mogą łatwo prowadzić do niejednoznacznych wywołań funkcji:

void foo(int x = 0)
{
}

void foo(double d = 0.0)
{
}

int main()
{
    foo(); // ambiguous function call

    return 0;
}

W tym przykładzie kompilator nie jest w stanie stwierdzić, czy foo() powinno zakończyć się na foo(0) lub foo(0.0).

Oto nieco bardziej złożony przykład:

void print(int x);                  // signature print(int)
void print(int x, int y = 10);      // signature print(int, int)
void print(int x, double y = 20.5); // signature print(int, double) 

int main()
{
    print(1, 2);   // will resolve to print(int, int)
    print(1, 2.5); // will resolve to print(int, double) 
    print(1);      // ambiguous function call

    return 0;
}

W przypadku wywołania print(1), kompilator nie jest w stanie stwierdzić, czy ma to rozwiązanie print(int), print(int, int) lub print(int, double).

W przypadku, gdy chcemy wywołać print(int, int) lub print(int, double) , zawsze możemy jawnie określić drugi parametr. A co jeśli będziemy chcieli zadzwonić print(int)? Nie jest oczywiste, jak możemy to zrobić.

Domyślne argumenty nie działają w przypadku funkcji wywoływanych poprzez wskaźniki funkcji Zaawansowane

Omówimy ten temat w lekcji 20.1 -- Wskaźniki funkcji. Ponieważ przy użyciu tej metody nie są uwzględniane argumenty domyślne, zapewnia to również obejście umożliwiające wywołanie funkcji, która w innym przypadku byłaby niejednoznaczna ze względu na argumenty domyślne.

guest
Twój adres e-mail nie zostanie wyświetlony
Znalazłeś błąd? Zostaw komentarz powyżej!
Komentarze związane z poprawkami zostaną usunięte po przetworzeniu, aby pomóc zmniejszyć bałagan. Dziękujemy za pomoc w ulepszaniu witryny dla wszystkich!
Awatary z https://gravatar.com/ są połączone z podanym adresem e-mail.
Powiadamiaj mnie o odpowiedziach:  
138 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze