Słowo kluczowe static i jego różne zastosowania w C ++


195

To słowo kluczowe staticma w C ++ kilka znaczeń, które uważam za bardzo mylące i nigdy nie mogę oprzeć myśli o tym, jak powinno ono działać.

Z tego, co rozumiem, istnieje staticczas przechowywania, co oznacza, że ​​trwa przez cały okres istnienia programu w przypadku globalnym, ale gdy mówisz o lokalnym, oznacza to, że domyślnie jest on zerowany.

Standard C ++ mówi to dla członków danych klasy ze słowem kluczowym static:

3.7.1 Statyczny czas przechowywania [basic.stc.static]

3 Słowo kluczowe static może być użyte do zadeklarowania zmiennej lokalnej o statycznym czasie przechowywania.

4 Słowo kluczowe static zastosowane do elementu danych klasy w definicji klasy określa czas przechowywania statycznego elementu danych.

Co to znaczy ze zmienną lokalną ? Czy to lokalna zmienna funkcji? Ponieważ jest też to, że kiedy deklarujesz funkcję lokalną static, ponieważ jest ona inicjalizowana tylko raz, po raz pierwszy wchodzi w tę funkcję.

Mówi także tylko o czasie przechowywania w odniesieniu do członków klasy, a co z tym, że nie jest specyficzne dla instancji, to także właściwość „ staticnie”? A może to czas przechowywania?

A teraz co ze sprawą statici zakresem pliku? Czy domyślnie uważa się, że wszystkie zmienne globalne mają statyczny czas przechowywania? Wydaje się to wskazywać na to (z sekcji 3.7.1):

1 Wszystkie zmienne, które nie mają dynamicznego czasu przechowywania, nie mają czasu przechowywania wątków i nielokalne, mają statyczny czas przechowywania. Przechowywanie tych podmiotów będzie trwało przez czas trwania programu (3.6.2, 3.6.3)

Jak staticodnosi się do powiązania zmiennej?

To całe staticsłowo kluczowe jest wręcz mylące, czy ktoś może wyjaśnić różne zastosowania tego języka w języku angielskim, a także powiedzieć mi, kiedy należy zainicjować członka staticklasy?


Odpowiedzi:


147

Zmienne:

staticistnieją zmienne dla „okresu istnienia” jednostki tłumaczeniowej, w której jest zdefiniowane , oraz:

  • Jeśli znajduje się w zakresie przestrzeni nazw (tj. Poza funkcjami i klasami), nie można uzyskać do niego dostępu z żadnej innej jednostki tłumaczeniowej. Jest to znane jako „wewnętrzne połączenie” lub „statyczny czas przechowywania”. (Nie rób tego w nagłówkach oprócz constexpr. Cokolwiek innego, a skończysz z osobną zmienną w każdej jednostce tłumaczeniowej, co jest szalenie mylące)
  • Jeśli jest to zmienna w funkcji , nie można uzyskać do niej dostępu spoza funkcji, tak jak w przypadku każdej innej zmiennej lokalnej. (to lokal, o którym wspominali)
  • członkowie klasy nie mają ograniczonego zakresu z powodu static, ale mogą być zaadresowane zarówno z klasy, jak i instancji (jak std::string::npos). [Uwaga: możesz zadeklarować elementy statyczne w klasie, ale zwykle powinny one być nadal zdefiniowane w jednostce tłumaczenia (plik cpp), i jako taki, jest tylko jeden na klasę]

lokalizacje jako kod:

static std::string namespaceScope = "Hello";
void foo() {
    static std::string functionScope= "World";
}
struct A {
   static std::string classScope = "!";
};

Przed wykonaniem jakiejkolwiek funkcji w jednostce tłumaczącej (prawdopodobnie po mainrozpoczęciu wykonywania) zmienne o statycznym czasie przechowywania (zakres przestrzeni nazw) w tej jednostce tłumaczącej będą „inicjalizowane na stałe” (w constexprmiarę możliwości lub w przeciwnym razie zero), a następnie locals są „dynamicznie inicjowane” poprawnie w kolejności, w jakiej są zdefiniowane w jednostce tłumaczeniowej (dla takich rzeczy std::string="HI";nie są constexpr). Na koniec, funkcja-lokalna statyka zostanie zainicjalizowana, gdy wykonanie po raz pierwszy „osiągnie” linię, w której zostały zadeklarowane. Wszystkie staticzmienne zostały zniszczone w odwrotnej kolejności inicjalizacji.

Najłatwiejszym sposobem na uzyskanie tego wszystkiego jest uczynienie wszystkich zmiennych statycznych, które nie zostały constexprzainicjalizowane, lokalnymi funkcjami, co zapewnia, że ​​wszystkie statystyki / globały są poprawnie inicjowane podczas próby ich użycia bez względu na wszystko, co zapobiega inicjalizacji statycznej zamów fiasko .

T& get_global() {
    static T global = initial_value();
    return global;
}

Bądź ostrożny, ponieważ gdy specyfikacja mówi, że zmienne zakresu nazw mają domyślnie „statyczny czas przechowywania”, oznaczają one bit „żywotności jednostki tłumaczeniowej”, ale to nie znaczy, że nie można uzyskać do niego dostępu poza plikiem.

Funkcje

Znacznie prostszy, staticjest często używany jako funkcja członka klasy i tylko bardzo rzadko stosowany jako funkcja wolnostojąca.

Statyczna funkcja składowa różni się od zwykłej funkcji składowej tym, że można ją wywoływać bez wystąpienia klasy, a ponieważ nie ma ona wystąpienia, nie ma dostępu do niestatycznych członków klasy. Zmienne statyczne są przydatne, gdy chcesz mieć funkcję dla klasy, która absolutnie nie odnosi się do żadnych elementów instancji lub do zarządzania staticzmiennymi elementów.

struct A {
    A() {++A_count;}
    A(const A&) {++A_count;}
    A(A&&) {++A_count;}
    ~A() {--A_count;}

    static int get_count() {return A_count;}
private:
    static int A_count;
}

int main() {
    A var;

    int c0 = var.get_count(); //some compilers give a warning, but it's ok.
    int c1 = A::get_count(); //normal way
}

staticWolnego funkcja oznacza, że funkcja nie będzie określana przez inną jednostkę translacji, a zatem łącznik może ignorować go całkowicie. Ma to niewielką liczbę celów:

  • Może być używany w pliku cpp, aby zagwarantować, że funkcja nigdy nie będzie używana z żadnego innego pliku.
  • Można umieścić w nagłówku, a każdy plik będzie miał własną kopię funkcji. Nieprzydatne, ponieważ inline robi prawie to samo.
  • Przyspiesza czas łącza, redukując pracę
  • Może umieścić funkcję o tej samej nazwie w każdej jednostce tłumaczeniowej i wszyscy mogą robić różne rzeczy. Na przykład, możesz umieścić static void log(const char*) {}w każdym pliku cpp i każdy z nich może zalogować się w inny sposób.

1
Co z członkami klasy? Czy to nie trzeci osobny przypadek?
Étienne

4
@Etienne - elementy danych klasy statycznej są takie same jak statyczne zmienne globalne, z tym wyjątkiem, że można uzyskać do nich dostęp z innych jednostek tłumaczeniowych, a każdy dostęp (z wyjątkiem funkcji członka) musi określać classname::zakres. Statyczne funkcje składowe klasy są jak funkcje globalne, ale mają zasięg do klasy lub jak zwykłe składowe, ale bez nich this(to nie jest wybór - te dwa powinny być równoważne).
Steve314,

1
@LuchianGrigore: choć rozumiem twój punkt widzenia, nie jestem pewien, jakiego sformułowania użyć.
Mooing Duck

1
@ Steve314: Rozumiem, co masz na myśli, ale mając do czynienia z tak strasznie przeciążonym terminem, jak statycznym , chciałbym, abyśmy wszyscy byli nieco bardziej ostrożni. W szczególności wszystkie zmienne globalne (naprawdę na poziomie przestrzeni nazw) mają statyczny czas trwania, więc dodawanie statycznych do statycznych zmiennych globalnych można rozumieć jako namespace A { static int x; }, co oznacza wewnętrzne powiązanie i bardzo różni się od zachowania członków danych klasy statycznej .
David Rodríguez - dribeas

1
„Jeśli znajduje się w zakresie przestrzeni nazw, to nie można uzyskać do niego dostępu z żadnej innej jednostki tłumaczeniowej ...” Co masz na myśli, jeśli znajduje się w zakresie przestrzeni nazw? Czy nie zawsze tak jest, czy możesz podać przykład i kontrprzykład?
AturSams

66

Statyczny czas przechowywania oznacza, że ​​zmienna przebywa w tym samym miejscu w pamięci przez cały okres istnienia programu.

Powiązanie jest do tego ortogonalne.

Myślę, że to najważniejsze rozróżnienie, jakie możesz zrobić. Zrozum to i resztę, a także pamiętaj o tym, powinno to być łatwe (nie zwracając się bezpośrednio do @Tony, ale ktokolwiek może to przeczytać w przyszłości).

Słowo kluczowe staticmoże być użyte do oznaczenia wewnętrznego połączenia i przechowywania statycznego, ale w gruncie rzeczy są one różne.

Co to znaczy ze zmienną lokalną? Czy to lokalna zmienna funkcji?

Tak. Niezależnie od tego, kiedy zmienna zostanie zainicjowana (przy pierwszym wywołaniu funkcji i kiedy ścieżka wykonania osiągnie punkt deklaracji), pozostanie w tym samym miejscu w pamięci przez cały czas życia programu. W takim przypadku staticdaje to przechowywanie statyczne.

A co ze skrzynką o zakresie statycznym i zakresie plików? Czy domyślnie uważa się, że wszystkie zmienne globalne mają statyczny czas przechowywania?

Tak, wszystkie globały mają z definicji statyczny czas przechowywania (teraz, gdy wyjaśniliśmy, co to oznacza). Ale zmienne o zasięgu przestrzeni nazw nie są deklarowane static, ponieważ dałoby im to wewnętrzne powiązanie, a więc zmienną na jednostkę tłumaczenia.

Jak statyczny odnosi się do powiązania zmiennej?

Daje wewnętrzne powiązanie zmiennych o zasięgu nazw. Daje członkom i zmiennym lokalnym czas przechowywania statyczny.

Rozwińmy to wszystko:

//

static int x; //internal linkage
              //non-static storage - each translation unit will have its own copy of x
              //NOT A TRUE GLOBAL!

int y;        //static storage duration (can be used with extern)
              //actual global
              //external linkage
struct X
{
   static int x;     //static storage duration - shared between class instances 
};

void foo()
{
   static int x;     //static storage duration - shared between calls
}

To całe statyczne słowo kluczowe jest wręcz mylące

Zdecydowanie, chyba że go znasz. :) Próbując uniknąć dodawania nowych słów kluczowych do języka, komitet ponownie użył tego, IMO, w tym celu - zamieszanie. Jest używany do oznaczania różnych rzeczy (powiedziałbym, prawdopodobnie przeciwnych).


1
Pozwólcie, że wyjaśnię to wprost - mówicie, że kiedy mówię static int xw zakresie przestrzeni nazw, daje to niestatyczną pamięć?
Michael Hagar

30

Aby wyjaśnić pytanie, wolę kategoryzować użycie słowa kluczowego „statycznego” w trzech różnych formach:

(ZA). zmienne

(B). Funkcje

(DO). zmienne składowe / funkcje klas

wyjaśnienie poniżej znajduje się dla każdego z podtytułów:

(A) słowo kluczowe „static” dla zmiennych

Ten może być trochę trudny, ale jeśli zostanie odpowiednio wyjaśniony i zrozumiany, jest dość prosty.

Aby to wyjaśnić, po pierwsze, naprawdę warto wiedzieć o zakresie, czasie trwania i powiązaniu zmiennych, bez których rzeczy zawsze trudno jest dostrzec przez mętną koncepcję standardowego słowa kluczowego

1. Zakres : określa, gdzie w pliku zmienna jest dostępna. Może być dwojakiego rodzaju: (i) Lokalny lub Blokowy . (ii) Globalny zakres

2. Czas trwania : określa, kiedy zmienna jest tworzona i niszczona. Ponownie jest on dwojakiego rodzaju: (i) Automatyczny czas przechowywania (dla zmiennych o zasięgu lokalnym lub blokowym). (ii) Statyczny czas przechowywania (dla zmiennych o zasięgu globalnym lub zmiennych lokalnych (w funkcji lub w bloku kodu) ze specyfikatorem statycznym ).

3. Łączenie : Określa, czy można uzyskać dostęp do zmiennej (lub połączyć ją) w innym pliku. Ponownie (i na szczęście) jest on dwojakiego rodzaju: (i) Wewnętrzne powiązanie (dla zmiennych mających zakres bloku i zakres globalny / zakres pliku / zakres globalnej przestrzeni nazw) (ii) Połączenie zewnętrzne (dla zmiennych mających tylko zakres globalny / zakres pliku / Globalny zakres przestrzeni nazw)

Odwołajmy się do poniższego przykładu, aby lepiej zrozumieć zwykłe zmienne globalne i lokalne (brak zmiennych lokalnych ze statycznym czasem przechowywania):

//main file
#include <iostream>

int global_var1; //has global scope
const global_var2(1.618); //has global scope

int main()
{
//these variables are local to the block main.
//they have automatic duration, i.e, they are created when the main() is 
//  executed and destroyed, when main goes out of scope
 int local_var1(23);
 const double local_var2(3.14);

 {
/* this is yet another block, all variables declared within this block are 
 have local scope limited within this block. */
// all variables declared within this block too have automatic duration, i.e, 
/*they are created at the point of definition within this block,
 and destroyed as soon as this block ends */
   char block_char1;
   int local_var1(32) //NOTE: this has been re-declared within the block, 
//it shadows the local_var1 declared outside

 std::cout << local_var1 <<"\n"; //prints 32

  }//end of block
  //local_var1 declared inside goes out of scope

 std::cout << local_var1 << "\n"; //prints 23

 global_var1 = 29; //global_var1 has been declared outside main (global scope)
 std::cout << global_var1 << "\n"; //prints 29
 std::cout << global_var2 << "\n"; //prints 1.618

 return 0;
}  //local_var1, local_var2 go out of scope as main ends
//global_var1, global_var2 go out of scope as the program terminates 
//(in this case program ends with end of main, so both local and global
//variable go out of scope together

Teraz pojawia się koncepcja powiązania. Gdy zmienna globalna zdefiniowana w jednym pliku ma być używana w innym pliku, powiązanie zmiennej odgrywa ważną rolę.

Powiązanie zmiennych globalnych określają słowa kluczowe: (i) statyczny i (ii) zewnętrzny

(Teraz dostajesz wyjaśnienie)

statyczne słowo kluczowe można zastosować do zmiennych o zasięgu lokalnym i globalnym, aw obu przypadkach oznaczają one różne rzeczy. Najpierw wyjaśnię użycie słowa kluczowego „static” w zmiennych o zasięgu globalnym (gdzie wyjaśnię również użycie słowa kluczowego „extern”), a później tych o zasięgu lokalnym.

1. Statyczne słowo kluczowe dla zmiennych o zasięgu globalnym

Zmienne globalne mają statyczny czas trwania, co oznacza, że ​​nie wykraczają poza zakres, gdy kończy się określony blok kodu (np. Main ()), w którym jest używany. W zależności od powiązania można uzyskać do nich dostęp tylko w tym samym pliku, w którym są zadeklarowane (dla statycznej zmiennej globalnej), lub poza plikiem, nawet poza plikiem, w którym zostały zadeklarowane (zmienne globalne typu zewnętrznego)

W przypadku zmiennej globalnej mającej specyfikator zewnętrzny i jeśli dostęp do tej zmiennej jest uzyskiwany poza plikiem, w którym została zainicjowana, należy ją zadeklarować w pliku, w którym jest używana, tak jak funkcja musi być przekazana do przodu zadeklarowane, jeśli jego definicja znajduje się w pliku innym niż miejsce, w którym jest używana.

Natomiast jeśli zmienna globalna ma słowo kluczowe static, nie można jej użyć w pliku, poza którym została zadeklarowana.

(patrz wyjaśnienie poniżej)

na przykład:

//main2.cpp
 static int global_var3 = 23;  /*static global variable, cannot be                            
                                accessed in anyother file */
 extern double global_var4 = 71; /*can be accessed outside this file                  linked to main2.cpp */
 int main() { return 0; }

main3.cpp

//main3.cpp
#include <iostream>

int main()
{
   extern int gloabl_var4; /*this variable refers to the gloabal_var4
                            defined in the main2.cpp file */
  std::cout << global_var4 << "\n"; //prints 71;

  return 0;
}

teraz dowolna zmienna w c ++ może być stała lub nie-stała i dla każdej „const-ness” otrzymujemy dwa przypadki domyślnego połączenia c ++, w przypadku gdy nie zostanie określona żadna:

(i) Jeśli zmienna globalna nie jest stała, jej łączenie jest domyślnie zewnętrzne , tj. do niestałej zmiennej globalnej można uzyskać dostęp w innym pliku .cpp poprzez deklarację przesyłania dalej za pomocą słowa kluczowego extern (innymi słowy, niestała globalna zmienne mają powiązanie zewnętrzne (oczywiście ze statycznym czasem trwania)). Również użycie słowa kluczowego extern w oryginalnym pliku, w którym został zdefiniowany, jest zbędne. W takim przypadku, aby stała zmienna globalna stała się niedostępna dla pliku zewnętrznego, należy użyć specyfikatora „static” przed typem zmiennej .

(ii) Jeśli zmienna globalna jest stała, jej łączenie jest domyślnie statyczne , tj. stała zmienna globalna nie może być dostępna w pliku innym niż tam, gdzie została zdefiniowana (innymi słowy, stałe zmienne globalne mają wewnętrzne powiązanie (z czasem trwania statycznym oczywiście)). Również użycie słowa kluczowego static, aby uniemożliwić dostęp do stałej zmiennej globalnej w innym pliku, jest zbędne. Tutaj, aby stała zmienna globalna miała zewnętrzny link, użyj specyfikatora „extern” przed typem zmiennej

Oto podsumowanie globalnych zmiennych zasięgu z różnymi powiązaniami

//globalVariables1.cpp 

// defining uninitialized vairbles
int globalVar1; //  uninitialized global variable with external linkage 
static int globalVar2; // uninitialized global variable with internal linkage
const int globalVar3; // error, since const variables must be initialized upon declaration
const int globalVar4 = 23; //correct, but with static linkage (cannot be accessed outside the file where it has been declared*/
extern const double globalVar5 = 1.57; //this const variable ca be accessed outside the file where it has been declared

Następnie badamy, jak zachowują się powyższe zmienne globalne, gdy są dostępne w innym pliku.

//using_globalVariables1.cpp (eg for the usage of global variables above)

// Forward declaration via extern keyword:
 extern int globalVar1; // correct since globalVar1 is not a const or static
 extern int globalVar2; //incorrect since globalVar2 has internal linkage
 extern const int globalVar4; /* incorrect since globalVar4 has no extern 
                         specifier, limited to internal linkage by
                         default (static specifier for const variables) */
 extern const double globalVar5; /*correct since in the previous file, it 
                           has extern specifier, no need to initialize the
                       const variable here, since it has already been
                       legitimately defined perviously */

2. Statyczne słowo kluczowe dla zmiennych o zasięgu lokalnym

Aktualizacje (sierpień 2019 r.) Statycznego słowa kluczowego dla zmiennych w zasięgu lokalnym

Można to dodatkowo podzielić na dwie kategorie:

(i) słowo kluczowe static dla zmiennych w bloku funkcyjnym oraz (ii) słowo kluczowe static dla zmiennych w nienazwanym bloku lokalnym.

(i) słowo kluczowe static dla zmiennych w bloku funkcyjnym.

Wcześniej wspomniałem, że zmienne o zasięgu lokalnym mają automatyczny czas trwania, tj. Powstają, gdy blok jest wprowadzany (czy to zwykły blok, czy to blok funkcyjny), i przestają istnieć, gdy blok się kończy, krótko mówiąc, zmienne z zasięgiem lokalnym mają automatyczny czas trwania i automatyczne zmienne czasu trwania (i obiekty) nie mają powiązania, co oznacza, że ​​nie są widoczne poza blokiem kodu.

Jeśli specyfikator statyczny jest zastosowany do zmiennej lokalnej w bloku funkcyjnym, zmienia on czas trwania zmiennej z automatycznej na statyczną, a jej czas życia to cały czas trwania programu, co oznacza, że ​​ma on stałe położenie w pamięci, a jego wartość jest inicjowana tylko raz przed uruchomieniem programu, jak wspomniano w dokumentacji cpp (inicjalizacji nie należy mylić z przypisaniem)

spójrzmy na przykład.

//localVarDemo1.cpp    
 int localNextID()
{
  int tempID = 1;  //tempID created here
  return tempID++; //copy of tempID returned and tempID incremented to 2
} //tempID destroyed here, hence value of tempID lost

int newNextID()
{
  static int newID = 0;//newID has static duration, with internal linkage
  return newID++; //copy of newID returned and newID incremented by 1
}  //newID doesn't get destroyed here :-)


int main()
{
  int employeeID1 = localNextID();  //employeeID1 = 1
  int employeeID2 = localNextID();  // employeeID2 = 1 again (not desired)
  int employeeID3 = newNextID(); //employeeID3 = 0;
  int employeeID4 = newNextID(); //employeeID4 = 1;
  int employeeID5 = newNextID(); //employeeID5 = 2;
  return 0;
}

Patrząc na powyższe kryterium dla statycznych zmiennych lokalnych i statycznych zmiennych globalnych, można pokusić się o pytanie, jaka może być różnica między nimi. Natomiast zmienne globalne są dostępne w dowolnym momencie w ciągu kodu (w samo jak jednostka różni się w zależności od tłumaczenia const -ness i extern -ness), zmienna statyczna zdefiniowana w bloku funkcyjnego nie jest bezpośrednio dostępne. Zmienna musi zostać zwrócona przez wartość funkcji lub odwołanie. Pokażmy to na przykładzie:

//localVarDemo2.cpp 

//static storage duration with global scope 
//note this variable can be accessed from outside the file
//in a different compilation unit by using `extern` specifier
//which might not be desirable for certain use case.
static int globalId = 0;

int newNextID()
{
  static int newID = 0;//newID has static duration, with internal linkage
  return newID++; //copy of newID returned and newID incremented by 1
}  //newID doesn't get destroyed here


int main()
{
    //since globalId is accessible we use it directly
  const int globalEmployee1Id = globalId++; //globalEmployeeId1 = 0;
  const int globalEmployee2Id = globalId++; //globalEmployeeId1 = 1;

  //const int employeeID1 = newID++; //this will lead to compilation error since newID++ is not accessible direcly. 
  int employeeID2 = newNextID(); //employeeID3 = 0;
  int employeeID2 = newNextID(); //employeeID3 = 1;

  return 0;
}

Więcej wyjaśnień na temat wyboru statycznej globalnej i statycznej zmiennej lokalnej można znaleźć w tym wątku przepływu stosu

(ii) statyczne słowo kluczowe dla zmiennych w nienazwanym bloku lokalnym.

do zmiennych statycznych w bloku lokalnym (nie bloku funkcyjnym) nie można uzyskać dostępu poza blokiem, gdy blok lokalny wykracza poza zakres. Żadnych zastrzeżeń do tej zasady.

    //localVarDemo3.cpp 
    int main()
    {

      {
          const static int static_local_scoped_variable {99};
      }//static_local_scoped_variable goes out of scope

      //the line below causes compilation error
      //do_something is an arbitrary function
      do_something(static_local_scoped_variable);
      return 0;
    }

C ++ 11 wprowadził słowo kluczowe, constexprktóre gwarantuje ocenę wyrażenia w czasie kompilacji i pozwala kompilatorowi zoptymalizować kod. Teraz, jeśli wartość stałej stałej statycznej w zakresie jest znana w czasie kompilacji, kod jest optymalizowany w sposób podobny do tego z constexpr. Oto mały przykład

Polecam także czytelnikom, aby sprawdzili różnicę między zmiennymi constexpri static constdla zmiennych w tym wątku przepełnienia stosu . to kończy moje wyjaśnienie statycznego słowa kluczowego stosowanego do zmiennych.

B. słowo kluczowe „static” używane dla funkcji

pod względem funkcji słowo kluczowe static ma bezpośrednie znaczenie. Tutaj odnosi się do powiązania funkcji Zwykle wszystkie funkcje zadeklarowane w pliku cpp mają domyślnie powiązanie zewnętrzne, tj. Funkcja zdefiniowana w jednym pliku może być użyta w innym pliku cpp poprzez deklarację przesyłania dalej.

użycie statycznego słowa kluczowego przed deklaracją funkcji ogranicza jego powiązanie z wewnętrznym , tzn. funkcja statyczna nie może być używana w pliku poza jego definicją.

C. Staitc Słowo kluczowe używane do zmiennych składowych i funkcji klas

1. Słowo kluczowe „static” dla zmiennych składowych klas

Zaczynam bezpośrednio od przykładu tutaj

#include <iostream>

class DesignNumber
{
  private:

      static int m_designNum;  //design number
      int m_iteration;     // number of iterations performed for the design

  public:
    DesignNumber() {     }  //default constructor

   int  getItrNum() //get the iteration number of design
   {
      m_iteration = m_designNum++;
      return m_iteration;
   }
     static int m_anyNumber;  //public static variable
};
int DesignNumber::m_designNum = 0; // starting with design id = 0
                     // note : no need of static keyword here
                     //causes compiler error if static keyword used
int DesignNumber::m_anyNumber = 99; /* initialization of inclass public 
                                    static member  */
enter code here

int main()
{
   DesignNumber firstDesign, secondDesign, thirdDesign;
   std::cout << firstDesign.getItrNum() << "\n";  //prints 0
   std::cout << secondDesign.getItrNum() << "\n"; //prints 1
   std::cout << thirdDesign.getItrNum() << "\n";  //prints 2

   std::cout << DesignNumber::m_anyNumber++ << "\n";  /* no object
                                        associated with m_anyNumber */
   std::cout << DesignNumber::m_anyNumber++ << "\n"; //prints 100
   std::cout << DesignNumber::m_anyNumber++ << "\n"; //prints 101

   return 0;
}

W tym przykładzie zmienna statyczna m_designNum zachowuje swoją wartość, a ta pojedyncza zmienna prywatna (ponieważ jest statyczna) jest współużytkowana przez wszystkie zmienne typu obiektu DesignNumber

Podobnie jak inne zmienne składowe, statyczne zmienne składowe klasy nie są powiązane z żadnym obiektem klasy, o czym świadczy wydrukowanie dowolnej liczby w funkcji głównej

const vs niestałe statyczne zmienne składowe w klasie

(i) niestacjonarne zmienne klasy członka W poprzednim przykładzie statyczne elementy (publiczne i prywatne) nie były stałymi. Norma ISO zabrania inicjowania elementów niestałych w klasie. Dlatego, podobnie jak w poprzednim przykładzie, należy je zainicjować po definicji klasy, z zastrzeżeniem, że słowo kluczowe static musi zostać pominięte

(ii) const-static zmienne składowe klasy jest to proste i zgodne z konwencją inicjalizacji innych zmiennych const składowych, tzn. stałe zmienne składowe const klasy mogą być inicjalizowane w punkcie deklaracji i mogą być inicjalizowane na końcu deklaracji klasy z jednym zastrzeżeniem, że słowo kluczowe const musi zostać dodane do elementu statycznego podczas inicjalizacji po definicji klasy.

Poleciłbym jednak zainicjować stałe statyczne zmienne składowe w punkcie deklaracji. Jest to zgodne ze standardową konwencją C ++ i sprawia, że ​​kod wygląda na czystszy

Aby uzyskać więcej przykładów statycznych zmiennych składowych w klasie, wyszukaj następujący link z learncpp.com http://www.learncpp.com/cpp-tutorial/811-static-member-variables/

2. Słowo kluczowe „static” dla funkcji składowej klas

Podobnie jak zmienne składowe klas mogą być statyczne, podobnie jak składowe funkcje klas. Normalne funkcje składowe klas są zawsze powiązane z obiektem typu klasy. Natomiast statyczne funkcje składowe klasy nie są powiązane z żadnym obiektem klasy, tj. Nie mają * tego wskaźnika.

Po drugie, ponieważ statyczne funkcje składowe klasy nie mają * tego wskaźnika, można je wywoływać, używając nazwy klasy i operatora rozpoznawania zakresu w funkcji głównej (ClassName :: functionName ();)

Po trzecie, statyczne funkcje składowe klasy mogą uzyskiwać dostęp tylko do statycznych zmiennych składowych klasy, ponieważ niestatyczne zmienne składowe klasy muszą należeć do obiektu klasy.

Aby uzyskać więcej przykładów statycznych funkcji składowych w klasie, wyszukaj poniższy link z learncpp.com

http://www.learncpp.com/cpp-tutorial/812-static-member-functions/


1
1) Przed wersją c ++ 17 tylko integralne stałe elementów stałych mogą być inicjowane w klasie, na przykład struct Foo{static const std::string name = "cpp";};błąd, namemusi być zdefiniowany poza klasą; ze zmiennymi wbudowanymi wprowadzonymi w c ++ 17 można kodować: struct Foo{static inline const std::string name = "cpp";};2) Publiczne statyczne funkcje
składowe

Czy „m_anyVariable” nie powinno stać się „m_anyNumber”? w twoim ostatnim przykładzie kodu?
gebbissimo

Nie jestem w stanie ocenić kompletności i poprawności odpowiedzi, ale wydaje się ona naprawdę wyczerpująca i łatwa do naśladowania. Wielkie dzięki! Jeśli chcesz to poprawić, krótkie podsumowanie na początku może być przydatne, ponieważ jest to dość długi tekst, a główne punkty mogą być łatwo wizualizowane jako lista zagnieżdżona lub diagram drzewa dla osób znających terminy takie jak „wewnętrzny / zewnętrzny” linkage "
gebbissimo

18

To jest właściwie dość proste. Jeśli zadeklarujesz zmienną jako statyczną w zakresie funkcji, jej wartość zostanie zachowana między kolejnymi wywołaniami tej funkcji. Więc:

int myFun()
{
static int i=5;
i++;
return i;
}
int main()
{
printf("%d", myFun());
printf("%d", myFun());
printf("%d", myFun());
}

pokaże 678zamiast 666, ponieważ pamięta zwiększoną wartość.

Jeśli chodzi o elementy statyczne, zachowują swoją wartość we wszystkich instancjach klasy. Więc następujący kod:

struct A
{
static int a;
};
int main()
{
A first;
A second;
first.a = 3;
second.a = 4;
printf("%d", first.a);
}

wypisze 4, ponieważ first.a i second.a są zasadniczo tą samą zmienną. Jeśli chodzi o inicjalizację, zobacz to pytanie.


Nie dotyczy to zmiennych zakresu przestrzeni nazw.
Michael Hagar

10

Kiedy deklarujesz staticzmienną w zakresie pliku, wówczas ta zmienna jest dostępna tylko w tym konkretnym pliku (technicznie * jednostka tłumacząca, ale nie komplikujmy tego zbytnio). Na przykład:

a.cpp

static int x = 7;

void printax()
{
    cout << "from a.cpp: x=" << x << endl;
}

b.cpp

static int x = 9;

void printbx()
{
    cout << "from b.cpp: x=" << x << endl;
}

main.cpp:

int main(int, char **)
{
    printax(); // Will print 7
    printbx(); // Will print 9

    return 0;
}

W przypadku zmiennej lokalnejstatic oznacza, że ​​zmienna zostanie zainicjowana zerem i zachowa swoją wartość między wywołaniami:

unsigned int powersoftwo()
{
    static unsigned lastpow;

    if(lastpow == 0)
        lastpow = 1;
    else
        lastpow *= 2;

    return lastpow;
}

int main(int, char **)
{
    for(int i = 0; i != 10; i++)
        cout << "2^" << i << " = " << powersoftwo() << endl;
}

W przypadku zmiennych klasowych oznacza to, że istnieje tylko jedno wystąpienie tej zmiennej, które jest współużytkowane przez wszystkich członków tej klasy. W zależności od uprawnień do zmiennej można uzyskać dostęp spoza klasy przy użyciu jej w pełni kwalifikowanej nazwy.

class Test
{
private:
    static char *xxx;

public:
    static int yyy;

public:
    Test()
    {        
        cout << this << "The static class variable xxx is at address "
             << static_cast<void *>(xxx) << endl;
        cout << this << "The static class variable yyy is at address "
             << static_cast<void *>(&y) << endl;
    }
};

// Necessary for static class variables.
char *Test::xxx = "I'm Triple X!";
int Test::yyy = 0;

int main(int, char **)
{
    Test t1;
    Test t2;

    Test::yyy = 666;

    Test t3;
};

Oznaczenie funkcji nieklasowej jako static nieklasowej powoduje, że funkcja jest dostępna tylko z tego pliku i niedostępna z innych plików.

a.cpp

static void printfilename()
{ // this is the printfilename from a.cpp - 
  // it can't be accessed from any other file
    cout << "this is a.cpp" << endl;
}

b.cpp

static void printfilename()
{ // this is the printfilename from b.cpp - 
  // it can't be accessed from any other file
    cout << "this is b.cpp" << endl;
}

W przypadku funkcji składowych klasy oznaczenie ich jako staticoznacza, że ​​funkcja nie musi być wywoływana w konkretnej instancji obiektu (tzn. Nie ma thiswskaźnika).

class Test
{
private:
    static int count;

public:
    static int GetTestCount()
    {
        return count;
    };

    Test()
    {
        cout << this << "Created an instance of Test" << endl;
        count++;
    }

    ~Test()
    {
        cout << this << "Destroyed an instance of Test" << endl;
        count--;
    }
};

int Test::count = 0;

int main(int, char **)
{
    Test *arr[10] = { NULL };

    for(int i = 0; i != 10; i++)
        arr[i] = new Test();

    cout << "There are " << Test::GetTestCount() << " instances of the Test class!" << endl;

    // now, delete them all except the first and last!
    for(int i = 1; i != 9; i++)
        delete arr[i];        

    cout << "There are " << Test::GetTestCount() << " instances of the Test class!" << endl;

    delete arr[0];

    cout << "There are " << Test::GetTestCount() << " instances of the Test class!" << endl;

    delete arr[9];

    cout << "There are " << Test::GetTestCount() << " instances of the Test class!" << endl;

    return 0;
}

8

Zmienne statyczne są wspólne dla każdej instancji klasy, zamiast każdej klasy mającej własną zmienną.

class MyClass
{
    public:
    int myVar; 
    static int myStaticVar;
};

//Static member variables must be initialized. Unless you're using C++11, or it's an integer type,
//they have to be defined and initialized outside of the class like this:
MyClass::myStaticVar = 0;

MyClass classA;
MyClass classB;

Każde wystąpienie „MyClass” ma swój własny „myVar”, ale ma ten sam „myStaticVar”. W rzeczywistości nie potrzebujesz nawet instancji MyClass, aby uzyskać dostęp do „myStaticVar”, i możesz uzyskać do niej dostęp poza klasą w następujący sposób:

MyClass::myStaticVar //Assuming it's publicly accessible.

W przypadku użycia wewnątrz funkcji jako zmiennej lokalnej (a nie jako zmiennej składowej klasy) słowo kluczowe static robi coś innego. Umożliwia tworzenie trwałych zmiennych bez podawania globalnego zasięgu.

int myFunc()
{
   int myVar = 0; //Each time the code reaches here, a new variable called 'myVar' is initialized.
   myVar++;

   //Given the above code, this will *always* print '1'.
   std::cout << myVar << std::endl;

   //The first time the code reaches here, 'myStaticVar' is initialized. But ONLY the first time.
   static int myStaticVar = 0;

   //Each time the code reaches here, myStaticVar is incremented.
   myStaticVar++;

   //This will print a continuously incrementing number,
   //each time the function is called. '1', '2', '3', etc...
   std::cout << myStaticVar << std::endl;
}

Jest to zmienna globalna pod względem trwałości ... ale bez globalnego zasięgu / dostępności.

Możesz także mieć statyczne funkcje składowe. Funkcje statyczne są w zasadzie funkcjami nie będącymi członkami, ale znajdują się w przestrzeni nazw nazwy klasy i mają prywatny dostęp do członków klasy.

class MyClass
{
    public:
    int Func()
    {
        //...do something...
    }

    static int StaticFunc()
    {
        //...do something...
    }
};

int main()
{
   MyClass myClassA;
   myClassA.Func(); //Calls 'Func'.
   myClassA.StaticFunc(); //Calls 'StaticFunc'.

   MyClass::StaticFunc(); //Calls 'StaticFunc'.
   MyClass::Func(); //Error: You can't call a non-static member-function without a class instance!

   return 0;
}

Kiedy wywołujesz funkcję członka, pojawia się ukryty parametr o nazwie „this”, który jest wskaźnikiem do instancji klasy wywołującej funkcję. Statyczne funkcje składowe nie mają tego ukrytego parametru ... można je wywoływać bez instancji klasy, ale także nie mogą uzyskać dostępu do niestatycznych zmiennych składowych klasy, ponieważ nie mają wskaźnika „ten” do pracy. Nie są wywoływane w żadnej konkretnej instancji klasy.


1
„Zakładając, że jest publicznie dostępny”. - to nie jest.
Luchian Grigore

2
myStaticVarnależy również zdefiniować. Warto wspomnieć, że odpowiadając na pytanie dotyczące semantyki staticsłowa kluczowego, nie sądzisz?
Praetorian

@Praetorian: Dzięki, naprawiono.
Jamin Gray

1
@JaminGrey Przez „static standalone” miałem na myśli statyczne funkcje nie będące członkami i piszę takie, ilekroć potrzebuję nowych funkcji tylko w bieżącym pliku CPP i nie chcę, aby linker musiał przetwarzać dodatkowy symbol.
VR

1
@VR Odd! Nigdy nie wiedziałem, że funkcjonalność istnieje. Dzięki za poszerzenie mojej wiedzy!
Jamin Gray

1

Nie jestem programistą w języku C, więc nie mogę podać ci informacji na temat używania statycznego w programie w języku C, ale jeśli chodzi o programowanie obiektowe, statycznie deklaruje zmienną, funkcję lub klasę jako takie same przez cały czas trwania programu. Weź na przykład.

class A
{
public:
    A();
    ~A();
    void somePublicMethod();
private:
    void somePrivateMethod();
};

Kiedy tworzysz tę klasę w swoim Main, robisz coś takiego.

int main()
{
   A a1;
   //do something on a1
   A a2;
   //do something on a2
}

Te dwie instancje klasy są całkowicie różne i działają niezależnie od siebie. Ale jeśli miałbyś odtworzyć klasę A w ten sposób.

class A
{
public:
    A();
    ~A();
    void somePublicMethod();
    static int x;
private:
    void somePrivateMethod();
};

Wróćmy ponownie do głównego.

int main()
{
   A a1;
   a1.x = 1;
   //do something on a1
   A a2;
   a2.x++;
   //do something on a2
}

Wtedy a1 i a2 będą dzielić tę samą kopię int x, dzięki czemu wszelkie operacje na x w a1 będą miały bezpośredni wpływ na operacje x w a2. Więc gdybym to zrobił

int main()
{
   A a1;
   a1.x = 1;
   //do something on a1
   cout << a1.x << endl; //this would be 1
   A a2;
   a2.x++;
   cout << a2.x << endl; //this would be 2 
   //do something on a2
}

Oba wystąpienia klasy A współużytkują zmienne statyczne i funkcje. Mam nadzieję, że to odpowiada na twoje pytanie. Moja ograniczona znajomość C pozwala mi powiedzieć, że zdefiniowanie funkcji lub zmiennej jako statycznej oznacza, że ​​jest widoczne tylko dla pliku, w którym funkcja lub zmienna jest zdefiniowana jako statyczna. Ale lepiej by to udzielił facet C, a nie ja. C ++ umożliwia zarówno deklarowanie zmiennych C, jak i C ++ jako statyczne, ponieważ jest całkowicie wstecznie kompatybilny z C.


1

Co to znaczy ze zmienną lokalną? Czy to lokalna zmienna funkcji?

Tak - nieglobalny, taki jak zmienna lokalna funkcji.

Ponieważ istnieje również to, że kiedy deklarujesz funkcję lokalną jako statyczną, że jest ona inicjalizowana tylko raz, po raz pierwszy wchodzi w tę funkcję.

Dobrze.

Mówi także tylko o czasie przechowywania w odniesieniu do członków klasy, a co z tym, że nie jest specyficzny dla instancji, jest to także właściwość statyczna nie? A może to czas przechowywania?

class R { static int a; }; // << static lives for the duration of the program

to znaczy wszystkie przypadki Rudostępniania int R::a- int R::anigdy nie są kopiowane.

A co ze skrzynką o zakresie statycznym i zakresie plików?

Skutecznie globalny, który ma konstruktor / destruktor tam, gdzie to właściwe - inicjalizacja nie jest odraczana do czasu dostępu.

Jak statyczny odnosi się do powiązania zmiennej?

W przypadku funkcji lokalnej jest ona zewnętrzna. Dostęp: jest dostępny dla funkcji (chyba, że ​​ją zwrócisz).

W przypadku klasy jest ona zewnętrzna. Dostęp: zastosowanie mają standardowe specyfikatory dostępu (publiczne, chronione, prywatne).

static może również określać wewnętrzne powiązanie, w zależności od tego, gdzie zostało zadeklarowane (plik / przestrzeń nazw).

To całe statyczne słowo kluczowe jest wręcz mylące

Ma zbyt wiele celów w C ++.

czy ktoś może wyjaśnić różne zastosowania tego języka angielskiego, a także powiedzieć mi, kiedy zainicjować statycznego członka klasy?

Jest automatycznie inicjowany wcześniej, mainjeśli jest załadowany i ma konstruktor. Może to zabrzmieć dobrze, ale kolejność inicjalizacji jest w dużej mierze poza twoją kontrolą, więc złożona inicjalizacja staje się bardzo trudna do utrzymania i chcesz ją zminimalizować - jeśli musisz mieć statyczny, to lepiej działaj w lokalnych skalach w bibliotekach i projektowanie. Jeśli chodzi o dane ze statycznym czasem przechowywania, powinieneś spróbować zminimalizować ten projekt, szczególnie jeśli jest zmienny (zmienne globalne). „Czas” inicjalizacji jest również różny z wielu powodów - moduł ładujący i jądro mają pewne sztuczki, aby zminimalizować zużycie pamięci i opóźnić inicjalizację, w zależności od danych.


1

Obiekt statyczny: Możemy zdefiniować członków klasy statycznych za pomocą słowa kluczowego static. Kiedy deklarujemy członka klasy jako statyczny, oznacza to, bez względu na to, ile obiektów klasy zostanie utworzonych, istnieje tylko jedna kopia tego członka.

Element statyczny jest wspólny dla wszystkich obiektów klasy. Wszystkie dane statyczne są inicjowane do zera podczas tworzenia pierwszego obiektu, jeśli nie ma innej inicjalizacji. Nie możemy umieścić go w definicji klasy, ale można ją zainicjować poza klasą, jak to pokazano w poniższym przykładzie, poprzez ponowne ustawienie zmiennej statycznej, używając operatora rozdzielczości zakresu :: w celu określenia, do której klasy należy.

Spróbujmy następującego przykładu, aby zrozumieć pojęcie statycznych elementów danych:

#include <iostream>

using namespace std;

class Box
{
   public:
      static int objectCount;
      // Constructor definition
      Box(double l=2.0, double b=2.0, double h=2.0)
      {
         cout <<"Constructor called." << endl;
         length = l;
         breadth = b;
         height = h;
         // Increase every time object is created
         objectCount++;
      }
      double Volume()
      {
         return length * breadth * height;
      }
   private:
      double length;     // Length of a box
      double breadth;    // Breadth of a box
      double height;     // Height of a box
};

// Initialize static member of class Box
int Box::objectCount = 0;

int main(void)
{
   Box Box1(3.3, 1.2, 1.5);    // Declare box1
   Box Box2(8.5, 6.0, 2.0);    // Declare box2

   // Print total number of objects.
   cout << "Total objects: " << Box::objectCount << endl;

   return 0;
}

Gdy powyższy kod zostanie skompilowany i wykonany, daje następujący wynik:

Constructor called.
Constructor called.
Total objects: 2

Statyczne elementy funkcji: Deklarując element funkcji jako statyczny, uniezależniasz go od dowolnego określonego obiektu klasy. Statyczną funkcję składową można wywołać, nawet jeśli nie istnieją żadne obiekty klasy, a do funkcji statycznych można uzyskać dostęp tylko przy użyciu nazwy klasy i operatora rozpoznawania zasięgu ::.

Funkcja członka statycznego może uzyskiwać dostęp tylko do elementu danych statycznych, innych funkcji członka statycznego i wszelkich innych funkcji spoza klasy.

Statyczne funkcje składowe mają zasięg klasy i nie mają dostępu do tego wskaźnika klasy. Możesz użyć funkcji członka statycznego, aby ustalić, czy niektóre obiekty klasy zostały utworzone, czy nie.

Spróbujmy następującego przykładu, aby zrozumieć pojęcie elementów funkcji statycznej:

#include <iostream>

using namespace std;

class Box
{
   public:
      static int objectCount;
      // Constructor definition
      Box(double l=2.0, double b=2.0, double h=2.0)
      {
         cout <<"Constructor called." << endl;
         length = l;
         breadth = b;
         height = h;
         // Increase every time object is created
         objectCount++;
      }
      double Volume()
      {
         return length * breadth * height;
      }
      static int getCount()
      {
         return objectCount;
      }
   private:
      double length;     // Length of a box
      double breadth;    // Breadth of a box
      double height;     // Height of a box
};

// Initialize static member of class Box
int Box::objectCount = 0;

int main(void)
{

   // Print total number of objects before creating object.
   cout << "Inital Stage Count: " << Box::getCount() << endl;

   Box Box1(3.3, 1.2, 1.5);    // Declare box1
   Box Box2(8.5, 6.0, 2.0);    // Declare box2

   // Print total number of objects after creating object.
   cout << "Final Stage Count: " << Box::getCount() << endl;

   return 0;
}

Gdy powyższy kod zostanie skompilowany i wykonany, daje następujący wynik:

Inital Stage Count: 0
Constructor called.
Constructor called.
Final Stage Count: 2

1
Należy wspomnieć, że paradygmaty te zostały zaczerpnięte z tutorialspoint.com/cplusplus/cpp_static_members.htm
BugShotGG
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.