7.3 -- Zmienne lokalne

W lekcji >2.5 — Wprowadzenie do zakresu lokalnego, którą wprowadziliśmy local variables, czyli zmienne zdefiniowane wewnątrz funkcji (w tym parametry funkcji).

Okazuje się, że C++ tak naprawdę nie ma ani jednego atrybutu, który definiuje zmienną jako zmienną lokalną. Zamiast tego zmienne lokalne mają kilka różnych właściwości, które odróżniają zachowanie tych zmiennych od innych rodzajów (nielokalnych) zmiennych. Zbadamy te właściwości w tej i następnych lekcjach.

W lekcji >2.5 — Wprowadzenie do zakresu lokalnego, wprowadziliśmy także koncepcję zakresu. Identyfikator scope określa, gdzie w kodzie źródłowym można uzyskać dostęp do identyfikatora. Kiedy dostęp do identyfikatora jest możliwy, mówimy, że jest to in scope. Gdy nie można uzyskać dostępu do identyfikatora, mówimy, że jest to out of scope. Zasięg jest właściwością występującą w czasie kompilacji i próba użycia identyfikatora, gdy jest on poza zakresem, zakończy się błędem kompilacji.

Zmienne lokalne mają zasięg blokowy

Zmienne lokalne mają zakres bloku, co oznacza, że znajdują się w zakresie od punktu definicji do końca bloku, w którym są zdefiniowane.

Powiązana treść

Proszę przejrzeć lekcja 7.1 -- Instrukcje złożone (bloki) jeśli potrzebujesz odświeżenia wiedzy na temat bloków.

int main()
{
    int i { 5 }; // i enters scope here
    double d { 4.0 }; // d enters scope here

    return 0;
} // d and i go out of scope here

Chociaż parametry funkcji nie są zdefiniowane w treści funkcji, w przypadku typowych funkcji można je uznać za część zakresu bloku treści funkcji.

int max(int x, int y) // x and y enter scope here
{
    // assign the greater of x or y to max
    int max{ (x > y) ? x : y }; // max enters scope here

    return max;
} // max, y, and x leave scope here

Wszystkie nazwy zmiennych w zakresie muszą być unikalne

Nazwy zmiennych muszą być unikalne w obrębie danego zakresu zakresie, w przeciwnym razie wszelkie odniesienia do nazwy będą niejednoznaczne. Rozważmy następujący program:

void someFunction(int x)
{
    int x{}; // compilation failure due to name collision with function parameter
}

int main()
{
    return 0;
}

Powyższy program nie kompiluje się, ponieważ zmienna x zdefiniowana w treści funkcji i parametr funkcji x mają tę samą nazwę i oba znajdują się w tym samym zakresie blokowym.

Zmienne lokalne mają automatyczny czas przechowywania

Przechowywanie zmiennej czas trwania (zwykle nazywany po prostu czasu trwania) określa, jakie reguły rządzą, kiedy i jak zmienna zostanie utworzona (utworzona) i zniszczona. W większości przypadków czas przechowywania zmiennej bezpośrednio określa jej lifetime.

Powiązana treść

Co to jest czas życia omawiamy na lekcji >2.5 — Wprowadzenie do zakresu lokalnego.

Na przykład zmienne lokalne mają automatyczny czas przechowywania, co oznacza, że są tworzone w momencie definicji i niszczone na końcu bloku, w którym są zdefiniowane. Na przykład:

int main()
{
    int i { 5 }; // i created and initialized here
    double d { 4.0 }; // d created and initialized here

    return 0;
} // d and i are destroyed here

Z tego powodu zmienne lokalne są czasami nazywane automatycznymi zmienne.

Zmienne lokalne w blokach zagnieżdżonych

Zmienne lokalne można definiować wewnątrz bloków zagnieżdżonych. Działa to identycznie jak zmienne lokalne w blokach treści funkcji:

int main() // outer block
{
    int x { 5 }; // x enters scope and is created here

    { // nested block
        int y { 7 }; // y enters scope and is created here
    } // y goes out of scope and is destroyed here

    // y can not be used here because it is out of scope in this block

    return 0;
} // x goes out of scope and is destroyed here

W powyższym przykładzie zmienna y jest zdefiniowana wewnątrz zagnieżdżonego bloku. Jego zakres jest ograniczony od punktu definicji do końca zagnieżdżonego bloku, a jego czas życia jest taki sam. Ponieważ zakres zmiennej y jest ograniczony do bloku wewnętrznego, w którym jest zdefiniowana, nie jest ona dostępna w żadnym miejscu bloku zewnętrznego.

Zauważ, że bloki zagnieżdżone są uważane za część zakresu bloku zewnętrznego, w którym są zdefiniowane. W rezultacie zmienne zdefiniowane w bloku zewnętrznym możesz będą widoczne wewnątrz bloku zagnieżdżonego:

#include <iostream>

int main()
{ // outer block

    int x { 5 }; // x enters scope and is created here

    { // nested block
        int y { 7 }; // y enters scope and is created here

        // x and y are both in scope here
        std::cout << x << " + " << y << " = " << x + y << '\n';
    } // y goes out of scope and is destroyed here

    // y can not be used here because it is out of scope in this block

    return 0;
} // x goes out of scope and is destroyed here

Zmienne lokalne nie mają powiązania

Identyfikatory mają inną właściwość o nazwie połączenie. Identyfikator połączenie określa, czy deklaracja tego samego identyfikatora w innym zakresie odnosi się do tego samego obiektu (lub funkcji).

Zmienne lokalne nie mają powiązania. Każda deklaracja identyfikatora bez powiązania odnosi się do unikalnego obiektu lub funkcji.

Na przykład:

int main()
{
    int x { 2 }; // local variable, no linkage

    {
        int x { 3 }; // this declaration of x refers to a different object than the previous x
    }

    return 0;
}

Zakres i powiązanie mogą wydawać się nieco podobne. Jednak zakres określa, gdzie w kodzie można zobaczyć i zastosować deklarację pojedynczego identyfikatora. Powiązanie określa, czy wiele deklaracji tego samego identyfikatora odnosi się do tego samego obiektu, czy nie.

Powiązana treść

Omawiamy, co się dzieje, gdy zmienne o tej samej nazwie pojawiają się w zagnieżdżonych blokach na lekcji 7.5 — Ukrywanie zmiennych (ukrywanie nazw).

Powiązanie nie jest zbyt interesujące w kontekście zmiennych lokalnych, ale porozmawiamy o tym więcej na następnych kilku lekcjach.

Zmienne powinny być definiowane w jak najbardziej ograniczonym zakresie

Jeśli zmienna jest używana tylko w obrębie zagnieżdżonego bloku, powinna być zdefiniowana wewnątrz tego zagnieżdżonego blok:

#include <iostream>

int main()
{
    // do not define y here

    {
        // y is only used inside this block, so define it here
        int y { 5 };
        std::cout << y << '\n';
    }

    // otherwise y could still be used here, where it's not needed

    return 0;
}

Ograniczając zakres zmiennej, zmniejszasz złożoność programu, ponieważ zmniejsza się liczba aktywnych zmiennych. Co więcej, łatwiej jest zobaczyć, gdzie zmienne są używane (lub nie są używane). Zmienna zdefiniowana wewnątrz bloku może być używana tylko w tym bloku (lub blokach zagnieżdżonych). Może to ułatwić zrozumienie programu.

Jeśli zmienna jest potrzebna w bloku zewnętrznym, należy ją zadeklarować w bloku zewnętrznym:

#include <iostream>

int main()
{
    int y { 5 }; // we're declaring y here because we need it in this outer block later

    {
        int x{};
        std::cin >> x;

        // if we declared y here, immediately before its actual first use...
        if (x == 4)
            y = 4;
    } // ... it would be destroyed here

    std::cout << y; // and we need y to exist here

    return 0;
}

Powyższy przykład pokazuje jeden z rzadkich przypadków, w których może być konieczne zadeklarowanie zmiennej na długo przed jej pierwszym użyciem.

Nowi programiści czasami zastanawiają się, czy warto tworzyć zagnieżdżony blok tylko po to, aby celowo ograniczyć zakres zmiennej (i wymusić jej wyjście poza zakres / wcześniejsze zniszczenie). Dzięki temu zmienna staje się prostsza, ale w rezultacie ogólna funkcja staje się dłuższa i bardziej złożona. Kompromis generalnie nie jest tego wart. Jeśli utworzenie zagnieżdżonego bloku wydaje się przydatne do celowego ograniczenia zakresu fragmentu kodu, lepszym rozwiązaniem może być umieszczenie tego kodu w osobnej funkcji.

Najlepsza praktyka

Zdefiniuj zmienne w najbardziej ograniczonym istniejącym zakresie. Unikaj tworzenia nowych bloków, których jedynym celem jest ograniczenie zakresu zmiennych.

Czas quizu

Pytanie nr 1

Napisz program, który poprosi użytkownika o wprowadzenie dwóch liczb całkowitych, jednej o nazwie smaller, drugiej o nazwie larger. Jeśli użytkownik wprowadzi mniejszą wartość dla drugiej liczby całkowitej, użyj bloku i zmiennej tymczasowej, aby zamienić mniejsze i większe wartości. Następnie wydrukuj wartości zmiennych smaller i larger . Dodaj komentarze do swojego kodu wskazujące, gdzie umiera każda zmienna. Uwaga: Podczas drukowania wartości smaller powinno zawierać mniejsze wejście i larger większe wejście, bez względu na kolejność, w jakiej zostały wprowadzone.

Wyjście programu powinno odpowiadać następującemu:

Enter an integer: 4
Enter a larger integer: 2
Swapping the values
The smaller value is 2
The larger value is 4

Pokaż rozwiązanie

Pytanie nr 2

Jaka jest różnica między zakresem, czasem trwania i czasem życia zmiennej? Jaki rodzaj zasięgu i czas trwania mają domyślnie zmienne lokalne (i co one oznaczają)?

Pokaż rozwiązanie

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:  
507 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze