Статичне ключове слово та його різні види використання в C ++


196

Ключове слово static, яке має декілька значень у С ++, мені здається дуже заплутаним, і я ніколи не можу думати про те, як він насправді повинен працювати.

Наскільки я розумію, staticтривалість зберігання - це означає, що вона триває протягом життя програми у випадку глобальної, але коли ви говорите про локальну, це означає, що за замовчуванням вона ініціалізується нулем.

Стандарт C ++ говорить про це членам даних класу з ключовим словом static:

3.7.1 Статична тривалість зберігання [basic.stc.static]

3 Ключове слово статичний може використовуватися для оголошення локальної змінної зі статичною тривалістю зберігання.

4 Статичне слово ключового слова, застосоване до члена даних класу у визначенні класу, дає статичну тривалість зберігання члена даних.

Що це означає з локальною змінною ? Це локальна змінна? Оскільки є також те, що коли ви оголошуєте функцію локальною як staticтаку, що вона ініціалізується лише один раз, перший раз вона входить у цю функцію.

Він також говорить лише про тривалість зберігання щодо членів класу, а як це неспецифічний характер, це також властивість staticні? Або це тривалість зберігання?

А як щодо справи staticта обсягу файлів? Чи вважається, що всі глобальні змінні за замовчуванням мають статичну тривалість зберігання? Наступне (з розділу 3.7.1), схоже, вказує так:

1 Усі змінні, які не мають динамічної тривалості зберігання, не мають тривалості зберігання потоків і не є локальними, мають статичну тривалість зберігання. Зберігання цих організацій триватиме протягом тривалості програми (3.6.2, 3.6.3)

Як staticставиться до зв’язку змінної?

Це все staticключове слово прямо заплутане, може хтось уточнив різні способи використання англійської мови, а також підкаже, коли ініціалізувати staticучасника класу?


Відповіді:


147

Змінні:

staticзмінні існують протягом "життя" блоку перекладу, визначеного в ньому , і:

  • Якщо він знаходиться в області простору імен (тобто поза функціями та класами), то до нього не можна отримати доступ з будь-якого іншого блоку перекладу. Це відомо як "внутрішня зв'язок" або "статична тривалість зберігання". (Не робіть цього в заголовках, за винятком constexpr. Іншого, і в кінцевому підсумку ви маєте окрему змінну в кожному блоці перекладу, що шалено заплутано)
  • Якщо це змінна у функції , вона не може бути доступна поза функцією, як і будь-яка інша локальна змінна. (це місцеве, про яке вони згадали)
  • члени класу не мають обмеженого діапазону через static, але можуть бути адресовані як класом, так і екземпляром (як std::string::npos). [Примітка: ви можете оголосити статичних членів у класі, але вони, як правило, все ж повинні бути визначені в блоці перекладу (файл cpp), і як такий, у класі є лише один]

місця розташування як код:

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

Перед виконанням будь-якої функції в блоці перекладу (можливо, після mainпочатку виконання) змінні зі статичною тривалістю зберігання (область простору імен) у цьому блоці перекладу будуть "постійними ініціалізованими" ( constexprде це можливо, або нулем інакше), а потім не- місцеві жителі "динамічно ініціалізуються" належним чином у порядку, визначеному в блоці перекладу (для таких речей std::string="HI";немає constexpr). Нарешті, локальна функція статики буде ініціалізована при першому виконанні "досягає" рядка, де вони оголошені. Усі staticзмінні знищені в зворотному порядку ініціалізації.

Найпростіший спосіб отримати все це правильно - це зробити всі статичні змінні, які не constexprініціалізовані у статичні локальні функції, що гарантує, що всі ваші статичні / глобальні ініціалізовані належним чином при спробі їх використання незалежно від того, запобігаючи статичній ініціалізації замовити фіаско .

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

Будьте уважні, тому що, коли специфікація каже, що змінні області простору імен за замовчуванням мають "статичну тривалість зберігання", вони означають біт "термін служби одиниці перекладу", але це не означає, що до неї не можна отримати доступ поза файлом.

Функції

Значно простіше, staticчасто використовується як функція члена класу, і лише дуже рідко використовується для вільно стоячої функції.

Функція статичного члена відрізняється від функції звичайного члена тим, що її можна викликати без екземпляра класу, і оскільки вона не має примірника, вона не може отримати доступ до нестатичних членів класу. Статичні змінні корисні, коли ви хочете мати функцію для класу, яка, безумовно, абсолютно не стосується будь-яких членів екземпляра або для управління staticзмінними членів.

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
}

staticВільна функція означає , що функція не називатися будь-яким іншим блоком перекладу, і , таким чином линкер може ігнорувати його повністю. Це має невелику кількість цілей:

  • Можна використовувати у файлі cpp, щоб гарантувати, що функція ніколи не використовується з жодного іншого файлу.
  • Можна помістити в заголовок, і кожен файл матиме власну копію функції. Не корисно, так як inline робить майже те саме.
  • Прискорює час зв’язку за рахунок скорочення роботи
  • Можна помістити функцію з однаковою назвою в кожен блок перекладу, і всі вони можуть робити різні речі. Наприклад, ви можете помістити static void log(const char*) {}в кожен файл cpp, і всі вони могли входити по-різному.

1
Що з учасниками класу? Це не третій окремий випадок?
Етьєнн

4
@Etienne - члени даних статичного класу такі ж, як статичні глобальні змінні, за винятком того, що ви можете отримати доступ до них з інших одиниць перекладу, і будь-який доступ (крім функцій-членів) повинен визначати classname::область. Статичні функції членів класу схожі на глобальні функції, але відносяться до класу, або як звичайні члени, але без this(це не вибір - ці два мають бути рівнозначними).
Steve314

1
@LuchianGrigore: хоча я бачу вашу думку, я не впевнений, які формулювання використовувати.
Mooing Duck

1
@ Steve314: Я розумію, що ти маєш на увазі, але, маючи справу з таким жахливо перевантаженим терміном, як статичний , я б хотів, щоб ми були дещо обережнішими. Зокрема, всі глобальні (дійсно рівні простору імен) мають статичну тривалість, тому додавання статичних у статичні глобальні змінні може розумітися як таке namespace A { static int x; }, що означає внутрішню зв'язок і сильно відрізняється від поведінки членів статичних класів даних .
David Rodríguez - dribeas

1
"Якщо він знаходиться в області простору імен, то до нього не можна отримати доступ з будь-якого іншого блоку перекладу ..." Що ви маєте на увазі, якщо він знаходиться в області простору імен? Чи це не завжди так, ви можете навести приклад і зустрічний приклад?
AturSams

66

Статична тривалість зберігання означає, що змінна перебуває там же в пам’яті протягом життя програми.

Зв'язок є ортогональним для цього.

Я думаю, що це найважливіша відмінність, яку ви можете зробити. Розуміти це та інше, а також пам’ятати про це має бути просто (не звертаючись до @Tony безпосередньо, а хто б це не читав у майбутньому).

Ключове слово staticможна використовувати для позначення внутрішніх зв'язків та статичного зберігання, але по суті вони різні.

Що це означає з локальною змінною? Це локальна змінна?

Так. Незалежно від того, коли змінна ініціалізована (при першому виклику до функції та коли шлях виконання досягне точки декларування), вона буде перебувати там же в пам'яті протягом життя програми. У цьому випадку staticнадає йому статичне зберігання.

А як щодо справи зі статичним і файловим обсягами? Чи вважається, що всі глобальні змінні за замовчуванням мають статичну тривалість зберігання?

Так, усі глобалісти мають за визначенням статичну тривалість зберігання (тепер, коли ми з'ясували, що це означає). Але змінені області змінних простору імен не декларуються static, тому що це дасть їм внутрішню зв'язок, тому змінна на одиницю перекладу.

Як статичне відношення до зв’язку змінної?

Він дає внутрішнім зв'язкам змінні з простором імен. Це дає членам та локальним змінним статичну тривалість зберігання.

Розширимо все це:

//

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
}

Це все статичне ключове слово прямо заплутано

Однозначно, якщо ви не знайомі з цим. :) Намагаючись уникати додавання нових ключових слів до мови, комітет повторно використав це, IMO, для цього - плутанину. Він використовується для позначення різних речей (можна сказати, ймовірно, протилежних речей).


1
Дозвольте мені зрозуміти це - ви говорите, що коли я скажу static int xв області простору імен, це дає йому нестатичне зберігання?
Майкл Агар

31

Для того, щоб уточнити питання, я б краще класифікувати використання "статичного" ключового слова у трьох різних формах:

(А). змінні

(В). функції

(С). членські змінні / функції класів

пояснення подано нижче для кожного з підзаголовків:

(A) "статичне" ключове слово для змінних

Це може бути трохи хитро, але якщо його пояснити і зрозуміти правильно, це досить просто.

Щоб пояснити це, спочатку дійсно корисно знати про область, тривалість та зв’язок змінних, без яких завжди важко зрозуміти через мутне поняття статичного ключового слова

1. Область застосування : визначає, де у файлі доступна змінна. Вона може бути двох типів: (i) Локальна або Блокова область застосування . (ii) Глобальний обсяг

2. Тривалість : визначає, коли змінна створюється та знищується. Знову це два типи: (i) Тривалість автоматичного зберігання (для змінних, що мають локальну чи блокову область). (ii) Статична тривалість зберігання (для змінних, що мають глобальний діапазон або локальні змінні (у функції або в кодовому блоці) зі статичним специфікатором).

3. Зв'язок : визначає, чи можна отримати зміну (або зв'язати) змінну в іншому файлі. Знову (і на щастя) він має два типи: (i) внутрішній зв'язок (для змінних, які мають блок-сферу та глобальний обсяг / масштаб файлів / область загального простору імен) (ii) зовнішній зв'язок (для змінних, що мають лише глобальний обсяг / область застосування / Глобальний обсяг простору імен)

Розглянемо приклад нижче для кращого розуміння простих глобальних та локальних змінних (немає локальних змінних зі статичною тривалістю зберігання):

//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

Зараз приходить концепція зв’язку. Коли глобальна змінна, визначена в одному файлі, призначена для використання в іншому файлі, зв'язок змінної відіграє важливу роль.

Зв'язок глобальних змінних визначається ключовими словами: (i) статичні та (ii) зовнішні

(Тепер ви отримаєте пояснення)

статичне ключове слово можна застосувати до змінних з локальною та глобальною сферою, і в обох випадках вони означають різні речі. Я спочатку поясню використання "статичного" ключового слова у змінних із загальною сферою (де я також уточнюю використання ключового слова "екстерн"), а пізніше для тих, що мають локальну область застосування.

1. Статичне ключове слово для змінних із загальним масштабом

Глобальні змінні мають статичну тривалість, тобто вони не виходять за межі, коли конкретний блок коду (наприклад, main ()), в якому він використовується, закінчується. Залежно від зв'язку, до них можна отримати доступ лише в тому самому файлі, де вони оголошені (для статичної глобальної змінної), або поза файлом, навіть поза файлом, у якому вони оголошені (глобальні змінні типу зовнішнього типу)

У випадку глобальної змінної, яка має специфікатор extern, і якщо доступ до цієї змінної знаходиться за межами файлу, в якому вона ініціалізована, вона повинна бути оголошена вперед у файлі, де вона використовується, як і функція повинна бути переадресована оголошено, якщо його визначення знаходиться у файлі, відмінному від того, де воно використовується.

На відміну від цього, якщо глобальна змінна має статичне ключове слово, вона не може бути використана у файлі, поза яким вона була оголошена.

(див. приклад для пояснення)

наприклад:

//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;
}

тепер будь-яка змінна в c ++ може бути або const, або non-const, і для кожного 'const-ness' ми отримуємо два випадки зв'язку c ++ за замовчуванням, якщо жоден не вказаний:

(i) Якщо глобальна змінна non-const, її зв'язок за замовчуванням є зовнішньою, тобто до глобальної змінної non-const можна отримати доступ до іншого .cpp-файлу шляхом прямого оголошення з використанням ключового слова extern (іншими словами, non const global змінні мають зовнішній зв'язок (зі статичною тривалістю курсу)). Також використання ключового слова extern у вихідному файлі, де воно було визначене, є зайвим. У цьому випадку, щоб зробити глобальну змінну non-const недоступною для зовнішнього файлу, використовуйте специфікатор 'static' перед типом змінної .

(ii) Якщо глобальна змінна const, її зв'язок за замовчуванням є статичною , тобто глобальна змінна const не може бути доступна у файлі, відмінному від того, де вона визначена (іншими словами, глобальні змінні const мають внутрішню зв'язок (зі статичною тривалістю) звичайно)). Також використання статичного ключового слова для запобігання доступу до глобальної змінної const в інший файл не є надмірним. Тут, щоб зробити глобальну змінну const мати зовнішню зв'язок, використовуйте специфікатор 'extern' перед типом змінної

Ось підсумок глобальних змінних областей із різними зв'язками

//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

Далі ми досліджуємо, як поводяться вищезгадані глобальні змінні під час доступу до іншого файлу.

//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. Статичне ключове слово для змінних з локальною областю

Оновлення (серпень 2019 року) для статичного ключового слова для змінних в локальному масштабі

Далі це можна розділити на дві категорії:

(i) статичне ключове слово для змінних у функціональному блоці та (ii) статичне ключове слово для змінних у неназваному локальному блоці.

(i) статичне ключове слово для змінних у функціональному блоці.

Раніше я згадував, що змінні з локальною областю мають автоматичну тривалість, тобто вони існують при введенні блоку (будь то нормальний блок, чи це функціональний блок) і перестають існувати, коли блок закінчується, короткий короткий сюжет, змінні з локальною областю мають автоматичну тривалість і автоматичні змінні тривалості (і об'єкти) не мають зв'язку, тобто вони не видно поза кодовим блоком.

Якщо статичний специфікатор застосовується до локальної змінної у функціональному блоці, він змінює тривалість змінної з автоматичної на статичну і її тривалість життя становить всю тривалість програми, що означає, що вона має фіксовану локацію пам'яті і її значення ініціалізується лише один раз перед запуском програми, як зазначено у посиланні на cpp (ініціалізацію не слід плутати з призначенням)

давайте подивимось на приклад.

//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;
}

Дивлячись на вищевказаний критерій статичних локальних змінних та статичних глобальних змінних, можна спробувати запитати, якою може бути різниця між ними. Хоча глобальні змінні доступні в будь-якій точці коду (так само, як і в різній одиниці перекладу залежно від const -ness та extern -ness), статична змінна, визначена у функціональному блоці, не є безпосередньо доступною. Змінна повинна бути повернута значенням функції або посиланням. Продемонструємо це на прикладі:

//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;
}

Більше пояснення щодо вибору статичної глобальної та статичної локальної змінної можна знайти в цій потоці stackoverflow

(ii) статичне ключове слово для змінних в неназваному локальному блоці.

статичні змінні в локальному блоці (а не функціональний блок) не можуть бути доступні за межами блоку, коли локальний блок вийде за межі області. Немає застережень до цього правила.

    //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 представив ключове слово, constexprяке гарантує оцінку виразу під час компіляції та дозволяє компілятору оптимізувати код. Тепер, якщо значення статичної змінної const у межах області відоме під час компіляції, код оптимізується таким чином, як у constexpr. Ось невеликий приклад

Я рекомендую читачам також шукати різницю між змінними constexprта static constдля змінних у цій потоці stackoverflow . цим завершується моє пояснення статичного ключового слова, застосованого до змінних.

B. "статичне" ключове слово, яке використовується для функцій

з точки зору функцій статичне ключове слово має прямолінійне значення. Тут йдеться про зв'язок функції. Зазвичай всі функції, оголошені у файлі cpp, за замовчуванням мають зовнішню зв'язок, тобто функція, визначена в одному файлі, може використовуватися в іншому файлі cpp шляхом прямого оголошення.

використання статичного ключового слова перед тим, як оголошення функції обмежує його зв'язок із внутрішнім , тобто статична функція не може використовуватися у файлі поза його визначенням.

C. Ключове слово Staitc, яке використовується для змінних членів та функцій класів

1. "статичне" ключове слово для змінних членів класів

Почніть безпосередньо з прикладу

#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;
}

У цьому прикладі статична змінна m_designNum зберігає своє значення, і ця змінна одного приватного члена (оскільки є статичною) поділяється в / в з усіма змінними об'єкта типу DesignNumber

Також, як і інші змінні члена, статичні змінні елемента класу не асоціюються з жодним об'єктом класу, що демонструється друком будь-якого номера в головній функції

const vs non-const статичні змінні члена в класі

(i) статичні змінні елемента нестандартних класів У попередньому прикладі статичні члени (як державні, так і приватні) були неконстантами. Стандарт ISO забороняє нестандартні статичні члени ініціалізуватися у класі. Отже, як і в попередньому прикладі, вони повинні бути ініціалізовані після визначення класу, із застереженням, що статичне ключове слово потрібно опустити

(ii) const-статичні змінні члена класу це просто і відповідає умові ініціалізації інших змінних const-членів, тобто змінних const статичних членів класу можна ініціалізувати в точці оголошення, і вони можуть бути ініціалізовані в кінці декларації класу з одним застереженням, що ключове слово const потрібно додати до статичного члена при ініціалізації після визначення класу.

Проте я рекомендую ініціалізувати const статичні змінні члена в точці декларування. Це поєднується зі стандартною умовою C ++ і робить код більш чистим

для отримання додаткових прикладів статичних змінних членів у класі шукайте наступне посилання від learncpp.com http://www.learncpp.com/cpp-tutorial/811-static-member-variables/

2. "статичне" ключове слово для функції членів класів

Подібно до змінних членів класів можуть бути статичними, так можуть бути і членські функції класів. Нормальні функції членів класів завжди пов'язані з об'єктом типу класу. На відміну від цього, статичні функції членів класу не пов'язані з жодним об'єктом класу, тобто вони не мають * цього вказівника.

По-друге, оскільки функції статичного члена класу не мають * цього вказівника, їх можна викликати, використовуючи ім’я класу та оператор роздільної здатності основної функції (ClassName :: functionName ();)

По-третє, статичні функції члена класу можуть отримувати доступ лише до статичних змінних класу класу, оскільки нестатичні змінні елементи класу повинні належати об'єкту класу.

Щоб отримати додаткові приклади статичних функцій членів у класі, перегляньте наступне посилання від learncpp.com

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


1
1) Перед тим, як c ++ 17 в класі можуть бути ініціалізовані лише цілісні статичні змінні члена const, наприклад struct Foo{static const std::string name = "cpp";};помилка, nameповинна бути визначена поза класом; з вбудованими змінними в c ++ 17 можна кодувати: struct Foo{static inline const std::string name = "cpp";};2) Доступ до загальнодоступних статичних функцій члена / члена можна отримати за назвою класу з оператором роздільної здатності, а також екземпляром з точковим оператором (наприклад: instance.some_static_method ())
oz1

Чи не повинен "m_anyVariable" стати "m_anyNumber"? у вашому останньому прикладі коду?
gebbissimo

Я не можу судити про повноту та правильність відповіді, але це здається дійсно всеосяжним і легко було слідувати. Дуже дякую! Якщо ви хотіли вдосконалити його, короткий підсумок на початку може бути корисним, оскільки це досить довгий текст, а основні моменти можуть бути легко візуалізовані як вкладений список або деревна схема для людей, які знають такі терміни, як "внутрішній / зовнішній" зв’язок »
gebbissimo

18

Насправді це досить просто. Якщо ви оголосите змінну як статичну в області функції, її значення зберігається між послідовними викликами до цієї функції. Так:

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

покаже 678замість 666, тому що воно запам'ятовує збільшене значення.

Що стосується статичних членів, вони зберігають свою цінність у примірниках класу. Отже наступний код:

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

буде надрукувати 4, тому що first.a та second.a по суті є однаковою змінною. Щодо ініціалізації, дивіться це питання.


Це не стосується змінних областей простору імен.
Майкл Агар

10

Коли ви оголошуєте staticзмінну в області файлу, то ця змінна доступна лише в цьому конкретному файлі (технічно це блок перекладу *, але давайте не надто ускладнювати це). Наприклад:

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;
}

Для локальної змінної staticозначає, що змінна буде ініціалізована нулем і збереже своє значення між викликами:

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;
}

Для змінних класів це означає, що існує лише один екземпляр цієї змінної, який поділяється між усіма членами цього класу. Залежно від дозволів, до змінної можна отримати доступ за межами класу, використовуючи її повноцінне ім'я.

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;
};

Позначення некласової функції як staticробить функцію доступною лише з цього файлу та недоступною для інших файлів.

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;
}

Для функцій учасників класу позначення їх staticозначає, що функцію не потрібно викликати в певному екземплярі об'єкта (тобто він не має thisвказівника).

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

Статичні змінні поділяються між усіма примірниками класу, замість того, щоб кожен клас мав власну змінну.

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;

У кожному екземплярі "MyClass" є власний "myVar", але спільний "myStaticVar". Насправді вам навіть не потрібен екземпляр MyClass для доступу до "myStaticVar", і ви можете отримати доступ до нього поза класом так:

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

При використанні всередині функції як локальної змінної (а не як змінної члена класу) статичне ключове слово робить щось інше. Це дозволяє створити стійку змінну, не даючи глобальної сфери.

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;
}

Це глобальна змінна з точки зору стійкості ... але без глобальної за обсягом / доступністю.

Ви також можете мати статичні функції членів. Статичні функції - це в основному не членські функції, але всередині простору імен імені класу та з приватним доступом до членів класу.

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;
}

Коли ви викликаєте функцію-члена, є прихований параметр, який називається "це", тобто вказівник на екземпляр класу, що викликає функцію. Статичні функції членів не мають цього прихованого параметра ... вони можуть викликатись без екземпляра класу, але також не можуть отримати доступ до нестатичних змінних членів класу, оскільки у них немає вказівника "цей" для роботи. Їх не викликають у жодному конкретному екземплярі класу.


1
"Якщо припустити, що це загальнодоступне." - це не.
Лучіан Григоре

2
myStaticVarпотрібно також визначити. Важливо сказати, що, відповідаючи на запитання про семантику staticключового слова, ви не думаєте?
Преторіанський

@Praetorian: Спасибі, виправлено.
Джамін Грей

1
@JaminGrey Під "статичним автономним" я мав на увазі статичні функції, які не є членами, і я пишу такі, коли мені потрібна нова функціональність лише у поточному файлі CPP, і не бажаю, щоб лінкер мав обробляти додатковий символ.
VR

1
@VR Дивно! Я ніколи не знав, що функціонал існує. Дякую за розширення моїх знань!
Джамін Грей

1

Я не програміст на C, тому не можу дати вам інформацію про використання статики в програмі C належним чином, але якщо мова йде про об'єктно-орієнтоване програмування, статичне в основному оголошує змінну або функцію чи клас однаковими. протягом усього життя програми. Візьмемо для прикладу.

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

Коли ви створюєте інстанціювання цього класу в своєму Main, ви робите щось подібне.

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

Ці два екземпляри класу абсолютно відрізняються один від одного і діють незалежно один від одного. Але якби ви відтворили клас А так.

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

Давайте знову повернемося до основного.

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

Тоді a1 і a2 ділиться однаковою копією int x, при цьому будь-які операції над x в a1 безпосередньо впливатимуть на операції x у a2. Тож якби я це робив

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
}

Обидва екземпляри класу A поділяють статичні змінні та функції. Сподіваюся, що це відповість на ваше запитання. Мої обмежені знання C дозволяють мені сказати, що визначаючи функцію чи змінну як статичну, у файлі видно лише те, що функція або змінна визначена як статична. Але це краще відповість хлопцем C, а не я. C ++ дозволяє як C, так і C ++ способи оголошення ваших змінних статичними, оскільки вони повністю зворотно сумісні з C.


1

Що це означає з локальною змінною? Це локальна змінна?

Так - неглобальна, наприклад, локальна змінна функції.

Оскільки є також те, що коли ви оголошуєте функцію локальної як статичну, вона ініціалізується лише один раз, вперше вона входить у цю функцію.

Правильно.

Він також говорить лише про тривалість зберігання щодо членів класу, а як це неспецифічний екземпляр, це також властивість статичного немає? Або це тривалість зберігання?

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

тобто, всі екземпляри Rчастки int R::a- int R::aніколи не копіюються.

А як щодо справи зі статичним і файловим обсягами?

Ефективно глобальний, який має конструктор / деструктор, де це доречно - ініціалізація не відкладається до доступу.

Як статичне відношення до зв’язку змінної?

Для функції локальної вона є зовнішньою. Доступ: Доступ до функції (якщо, звичайно, ви не повернете її).

Для класу це зовнішній вигляд. Доступ: застосовуються стандартні специфікатори доступу (загальнодоступні, захищені, приватні).

static також можна вказати внутрішню посилання залежно від місця декларування (файл / простір імен).

Це все статичне ключове слово прямо заплутано

У C ++ це занадто багато цілей.

хтось може уточнити різні способи використання англійської мови, а також підкаже, коли ініціалізувати статичного члена класу?

Він автоматично ініціалізується раніше, mainякщо він завантажений і має конструктор. Це може здатися хорошою справою, але порядок ініціалізації значною мірою не підпорядковується вам, тому складну ініціалізацію стає дуже складно підтримувати, і ви хочете мінімізувати це - якщо у вас є статична, то функціонувати локальні масштаби набагато краще в бібліотеках і проектів. Що стосується даних зі стаціонарною тривалістю зберігання, ви повинні намагатися мінімізувати цю конструкцію, особливо якщо вона є змінною (глобальні змінні). "Час" ініціалізації також змінюється з кількох причин - завантажувач і ядро ​​мають деякі хитрощі, щоб мінімізувати сліди пам'яті та відкласти ініціалізацію, залежно від даних, про які йдеться.


1

Статичний об’єкт: ми можемо визначити членів класу статичними за допомогою статичного ключового слова. Коли ми оголошуємо члена класу статичним, це означає, що незалежно від того, скільки об’єктів класу створено, є лише одна копія статичного члена.

Статичний член поділяється всіма об'єктами класу. Усі статичні дані ініціалізуються до нуля при створенні першого об'єкта, якщо іншої ініціалізації немає. Ми не можемо помістити його у визначення класу, але його можна ініціалізувати поза класом, як це зроблено в наступному прикладі, шляхом повторного декларування статичної змінної, використовуючи оператор роздільної здатності :: щоб визначити, до якого класу він належить.

Спробуємо наступний приклад, щоб зрозуміти поняття членів статичних даних:

#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;
}

Коли вищезгаданий код складається і виконується, він дає такий результат:

Constructor called.
Constructor called.
Total objects: 2

Члени статичної функції: Оголошуючи член функції статичним, ви робите його незалежним від будь-якого конкретного об'єкта класу. Функцію статичного члена можна викликати, навіть якщо немає об'єктів класу та статичних функцій доступні лише за допомогою імені класу та оператора роздільної здатності ::.

Функція статичного члена може отримувати доступ лише до статичного члена даних, до інших функцій статичного члена та до будь-яких інших функцій поза класом.

Статичні функції членів мають область класу, і вони не мають доступу до цього покажчика класу. Ви можете використовувати статичну функцію члена, щоб визначити, були створені деякі об'єкти класу чи ні.

Спробуємо наступний приклад, щоб зрозуміти поняття членів статичної функції:

#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;
}

Коли вищезгаданий код складається і виконується, він дає такий результат:

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

1
Було б правильно зазначити, що цю парадигму було взято з tutorialspoint.com/cplusplus/cpp_static_members.htm
BugShotGG
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.